using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using AtlyssCasino.Blackjack; using AtlyssCasino.Blackjack.Netcode; using AtlyssCasino.Jukebox.Netcode; using AtlyssCasino.RoomZoneChat.Netcode; using AtlyssCasino.Roulette; using AtlyssCasino.Roulette.Netcode; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using CodeTalker; using CodeTalker.Networking; using CodeTalker.Packets; using HarmonyLib; using Microsoft.CodeAnalysis; using Mirror; using Nessie.ATLYSS.EasySettings; using Nessie.ATLYSS.EasySettings.UIElements; using Newtonsoft.Json; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("AtlyssCasino")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.10.0.0")] [assembly: AssemblyInformationalVersion("1.10.0")] [assembly: AssemblyProduct("AtlyssCasino")] [assembly: AssemblyTitle("AtlyssCasino")] [assembly: AssemblyVersion("1.10.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace AtlyssCasino { public static class BlackjackSceneWatcher { private const string CASINO_SCENE_NAME = "AtlyssCasino"; private const float EXIT_CONFIRM_DELAY_SECONDS = 0.25f; private static Coroutine? _cleanupCoroutine; private static bool _casinoSceneSeen; public static void Init() { SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; SceneManager.activeSceneChanged += OnActiveSceneChanged; Plugin.Log.LogInfo("[SceneWatcher] Scoped table cleanup initialized."); } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (((Scene)(ref scene)).name == "AtlyssCasino") { _casinoSceneSeen = true; Plugin.Log.LogInfo("[SceneWatcher] Casino scene loaded."); } else if (_casinoSceneSeen) { ScheduleScopedCleanup("scene-loaded:" + ((Scene)(ref scene)).name); } } private static void OnSceneUnloaded(Scene scene) { if (!(((Scene)(ref scene)).name != "AtlyssCasino")) { if (!CasinoRuntimeActivity.IsLocalPlayerInCasino) { CleanupOnConfirmedLeave("casino-unloaded"); } _casinoSceneSeen = CasinoRuntimeActivity.CasinoSceneLoaded; } } private static void OnActiveSceneChanged(Scene oldScene, Scene newScene) { if (!(((Scene)(ref oldScene)).name != "AtlyssCasino") && !(((Scene)(ref newScene)).name == "AtlyssCasino")) { ScheduleScopedCleanup("active-scene:" + ((Scene)(ref oldScene)).name + "->" + ((Scene)(ref newScene)).name); } } private static void ScheduleScopedCleanup(string reason) { if (_cleanupCoroutine != null || (Object)(object)Plugin.Instance == (Object)null) { return; } try { _cleanupCoroutine = ((MonoBehaviour)Plugin.Instance).StartCoroutine(ScopedCleanupAfterPresenceSettles(reason)); } catch (Exception ex) { Plugin.Log.LogError("[SceneWatcher] Failed to schedule scoped cleanup: " + ex.Message); } } private static IEnumerator ScopedCleanupAfterPresenceSettles(string reason) { yield return null; yield return (object)new WaitForSecondsRealtime(0.25f); if (CasinoRuntimeActivity.IsLocalPlayerInCasino) { Plugin.Log.LogDebug("[SceneWatcher] Cleanup skipped (" + reason + "); player is still in casino."); _cleanupCoroutine = null; } else { CleanupOnConfirmedLeave(reason); _cleanupCoroutine = null; } } private static void CleanupOnConfirmedLeave(string reason) { try { bool num = ReleaseAnySeatedTable(); bool flag = ReleaseAnyRouletteTable(); Plugin.HasSetBlackjackBet = false; Plugin.HasSetRouletteBet = false; if (num || flag) { Plugin.Log.LogInfo("[SceneWatcher] Released local casino table state (" + reason + ")."); } } catch (Exception ex) { Plugin.Log.LogError("[SceneWatcher] Scoped cleanup failed: " + ex.Message); } } private static bool ReleaseAnySeatedTable() { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return false; } bool result = false; BlackjackTable[] array = Object.FindObjectsOfType(); foreach (BlackjackTable blackjackTable in array) { int seatForPlayer = blackjackTable.GetSeatForPlayer(mainPlayer); if (seatForPlayer >= 0) { if (BJNetcode.AmHostFresh()) { blackjackTable.ReleaseSeat(seatForPlayer); BJNetcode.BroadcastSeatReleased(((Object)blackjackTable).name, seatForPlayer, blackjackTable.HostSeatIndex); } else { BJNetcode.SendReleaseSeatRequest(((Object)blackjackTable).name); blackjackTable.ApplySeatReleased(seatForPlayer, blackjackTable.HostSeatIndex); } result = true; } } return result; } private static bool ReleaseAnyRouletteTable() { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return false; } bool result = false; ulong localSteam = BJNetcode.GetLocalSteam64(); RouletteTable[] array = Object.FindObjectsOfType(); foreach (RouletteTable rouletteTable in array) { if (rouletteTable.IsPlayerAtTable(mainPlayer)) { if (BJNetcode.AmHostFresh()) { rouletteTable.Leave(mainPlayer); RNNetcode.BroadcastPlayerLeft(((Object)rouletteTable).name, localSteam, rouletteTable.HostSteam64, rouletteTable.PlayerCount); } else { RNNetcode.SendLeaveTableRequest(((Object)rouletteTable).name); rouletteTable.ApplyPlayerLeftBySteam64(localSteam, 0uL); } result = true; } } return result; } } public sealed class CasinoJukebox : MonoBehaviour { private const float FALLBACK_RADIUS = 3f; private const float INPUT_COOLDOWN = 0.5f; private const float RESTART_DEBOUNCE_SEC = 1.25f; private const float RESYNC_TIME_THRESHOLD_SEC = 1.5f; private const float TRACK_END_TOLERANCE_SEC = 0.15f; private const float AUTHORITY_REFRESH_INTERVAL_SEC = 2f; private const float OUTSIDE_UPDATE_INTERVAL_SEC = 0.5f; private static readonly Dictionary _registry = new Dictionary(StringComparer.Ordinal); private AudioSource? _source; private AudioSource? _localExtraSource; private BoxCollider? _collider; private AudioClip? _appliedClip; private bool _setup; private bool _subscribed; private bool _playerNearby; private bool _isPlaying; private bool _appliedPlaying; private bool _autoStartRequested; private bool _trackHasAudioProgress; private bool _playingLocalExtras; private bool _waitingForServerBoundary; private bool _hasLatestServerState; private bool _wasWorldAudioAllowed; private bool _hasClockAuthority; private float _nextOutsideUpdateTime; private int _playlistStep; private int _appliedPlaylistStep = int.MinValue; private int _localExtraIndex; private int _latestServerTrackIndex = -1; private float _trackStartedAt; private float _localExtraStartedAt; private float _lastObservedSourceTime; private float _nextInputTime; private float _nextRestartAttemptTime; private float _appliedVolume = -1f; private float _appliedExtraVolume = -1f; private float _nextAuthorityRefreshTime; private JukeboxWorldState _latestServerState; private string _objectName = "CasinoJukebox"; internal string ObjectName => _objectName; internal int PlaylistStep => _playlistStep; internal bool IsPlaying => _isPlaying; internal bool IsLocallyAudible { get { if (IsWorldAudioAllowedLocally() && _isPlaying) { if (!((Object)(object)_source != (Object)null) || !_source.isPlaying) { if ((Object)(object)_localExtraSource != (Object)null) { return _localExtraSource.isPlaying; } return false; } return true; } return false; } } internal float ElapsedSeconds { get { if (!_isPlaying) { return 0f; } return Mathf.Max(0f, Time.time - _trackStartedAt); } } public void Setup() { _objectName = ((Object)((Component)this).gameObject).name; _registry[_objectName] = this; if (!_setup) { _collider = ((Component)this).GetComponent() ?? ((Component)this).GetComponentInChildren(true); if (!_subscribed) { CasinoJukeboxManager.OnPlaylistChanged += OnPlaylistChanged; _subscribed = true; } _setup = true; _wasWorldAudioAllowed = IsWorldAudioAllowedLocally(); _hasClockAuthority = HasClockAuthority(); _nextAuthorityRefreshTime = Time.unscaledTime + 2f; if (_hasClockAuthority) { CasinoJukeboxManager.EnsureServerTracksLoaded(); } else if (_wasWorldAudioAllowed) { CasinoJukeboxManager.EnsureClientTracksLoaded(); } if (_wasWorldAudioAllowed) { EnsureWorldAudioSource(); } if (JukeboxNetcode.TryGetCachedState(_objectName, out var state)) { ApplyRemoteState(state.PlaylistStep, state.Playing, state.ElapsedSeconds); } else if (!Plugin.IsHeadlessServer && !BJNetcode.AmHost()) { JukeboxNetcode.SendStateRequest(_objectName); } } } internal static void AutoStartAll() { foreach (CasinoJukebox value in _registry.Values) { if (!((Object)(object)value == (Object)null)) { value.PrepareForLocalEntry(); value.RequestAutoStart(); } } } private void PrepareForLocalEntry() { if (_setup && !Plugin.IsHeadlessServer) { _wasWorldAudioAllowed = false; _appliedPlaying = false; StopLocalExtraAudio(); StopLocalWorldAudio(); if (!BJNetcode.AmHost()) { JukeboxNetcode.SendStateRequest(_objectName); } } } internal static CasinoJukebox? FindByName(string objectName) { if (_registry.TryGetValue(objectName, out CasinoJukebox value)) { if ((Object)(object)value != (Object)null) { return value; } _registry.Remove(objectName); } return null; } internal static bool IsAnyWorldJukeboxPlaying() { foreach (CasinoJukebox value in _registry.Values) { if ((Object)(object)value != (Object)null && value.IsLocallyAudible) { return true; } } return false; } internal static void ClearRegistry() { _registry.Clear(); } internal JukeboxWorldState BuildState() { return new JukeboxWorldState { ObjectName = _objectName, PlaylistStep = _playlistStep, Playing = _isPlaying, ElapsedSeconds = ElapsedSeconds }; } internal void HostAdvance(int delta) { if (!HasClockAuthority() || !Plugin.JukeboxEnabled) { return; } CasinoJukeboxManager.EnsureServerTracksLoaded(); if (!CasinoJukeboxManager.HasServerTracks) { Plugin.Log.LogWarning("[Jukebox] Cannot advance: no bundled server songs are loaded."); return; } int i = _playlistStep + delta; if (i < 0) { for (int num = Math.Max(1, CasinoJukeboxManager.ServerTrackCount); i < 0; i += num) { } } ApplyState(i, playing: true, 0f); JukeboxNetcode.BroadcastState(BuildState()); } internal void ApplyRemoteState(int playlistStep, bool playing, float elapsedSeconds) { int previousServerStep = (_hasLatestServerState ? _latestServerState.PlaylistStep : int.MinValue); int latestServerTrackIndex = _latestServerTrackIndex; CacheServerState(playlistStep, playing, elapsedSeconds); if (!ShouldHoldForLocalExtras(previousServerStep, playlistStep, latestServerTrackIndex, _latestServerTrackIndex, playing)) { ApplyState(playlistStep, playing, elapsedSeconds); } } private void RequestAutoStart() { _autoStartRequested = true; TryAutoStart(); } private void TryAutoStart() { if (_setup && _autoStartRequested && !_isPlaying && Plugin.JukeboxEnabled && HasClockAuthority()) { CasinoJukeboxManager.EnsureServerTracksLoaded(); if (CasinoJukeboxManager.HasServerTracks) { ApplyState(_playlistStep, playing: true, 0f); JukeboxNetcode.BroadcastState(BuildState()); } } } private void ApplyState(int playlistStep, bool playing, float elapsedSeconds) { _playlistStep = playlistStep; _isPlaying = playing; elapsedSeconds = Mathf.Max(0f, elapsedSeconds); _trackStartedAt = Time.time - elapsedSeconds; if (!playing || !Plugin.JukeboxEnabled) { _appliedClip = null; _appliedPlaylistStep = playlistStep; _appliedPlaying = false; _playingLocalExtras = false; _waitingForServerBoundary = false; StopLocalExtraAudio(); StopLocalWorldAudio(); return; } bool flag = IsWorldAudioAllowedLocally(); bool flag2 = HasClockAuthority(); if (!flag && !flag2) { _appliedClip = null; _appliedPlaylistStep = playlistStep; _appliedPlaying = false; _playingLocalExtras = false; _waitingForServerBoundary = false; StopLocalExtraAudio(); StopLocalWorldAudio(); return; } if (flag2) { CasinoJukeboxManager.EnsureServerTracksLoaded(); } else { CasinoJukeboxManager.EnsureClientTracksLoaded(); } AudioClip serverClip = CasinoJukeboxManager.GetServerClip(playlistStep); if ((Object)(object)serverClip == (Object)null) { StopLocalExtraAudio(); StopLocalWorldAudio(); Plugin.Log.LogWarning($"[Jukebox] No bundled server clip available for playlist step {playlistStep}."); return; } if (serverClip.length > 0.1f) { elapsedSeconds = Mathf.Min(elapsedSeconds, serverClip.length - 0.05f); } _trackStartedAt = Time.time - elapsedSeconds; ResetPlaybackObservation(elapsedSeconds); if (!flag) { _appliedClip = serverClip; _appliedPlaylistStep = playlistStep; _appliedPlaying = false; _playingLocalExtras = false; _waitingForServerBoundary = false; StopLocalExtraAudio(); StopLocalWorldAudio(); return; } EnsureWorldAudioSource(); if ((Object)(object)_source == (Object)null) { return; } bool flag3 = (Object)(object)_appliedClip != (Object)(object)serverClip || _appliedPlaylistStep != playlistStep || _appliedPlaying != playing; bool flag4 = (Object)(object)_source.clip == (Object)(object)serverClip && _source.isPlaying && flag; if (!flag3 && flag4) { float num = 0f; try { num = _source.time; } catch { } if (Mathf.Abs(num - elapsedSeconds) > 1.5f) { try { _source.time = elapsedSeconds; } catch { } _trackStartedAt = Time.time - elapsedSeconds; } } else { if (!flag3 && flag && !_source.isPlaying && Time.time < _nextRestartAttemptTime) { return; } _appliedClip = serverClip; _appliedPlaylistStep = playlistStep; _appliedPlaying = playing; _playingLocalExtras = false; _waitingForServerBoundary = false; StopLocalExtraAudio(); if ((Object)(object)_source.clip != (Object)(object)serverClip) { _source.clip = serverClip; } CasinoJukeboxPersonalPlayer.StopForWorldJukebox(); _nextRestartAttemptTime = Time.time + 1.25f; _source.Stop(); if (elapsedSeconds > 0f) { try { _source.time = elapsedSeconds; } catch { } } _source.Play(); CasinoJukeboxAudioGuard.RegisterJukeboxSource(_source); Plugin.Log.LogInfo("[Jukebox] Playing " + CasinoJukeboxManager.GetServerTrackName(playlistStep) + " " + $"on '{_objectName}' (step {playlistStep}, " + $"volume={CasinoConfig.EffectiveJukeboxVolume:0.000})."); } } private void Update() { if (!_setup) { return; } bool flag = IsWorldAudioAllowedLocally(); if (!flag && !_wasWorldAudioAllowed && Time.unscaledTime < _nextOutsideUpdateTime) { return; } if (!flag) { _nextOutsideUpdateTime = Time.unscaledTime + 0.5f; } bool clockAuthorityCached = GetClockAuthorityCached(); if (!flag) { if (_wasWorldAudioAllowed) { _wasWorldAudioAllowed = false; _appliedPlaying = false; _playingLocalExtras = false; _waitingForServerBoundary = false; StopLocalExtraAudio(); StopLocalWorldAudio(); } _playerNearby = false; if (Plugin.JukeboxEnabled && clockAuthorityCached) { if (Plugin.IsHeadlessServer || (Object)(object)Plugin.AssetsBundle != (Object)null || _isPlaying) { CasinoJukeboxManager.EnsureServerTracksLoaded(); } if (_isPlaying && HasCurrentTrackEndedByClock()) { HostAdvance(1); } } return; } if (!_wasWorldAudioAllowed) { _wasWorldAudioAllowed = true; CasinoJukeboxManager.EnsureClientTracksLoaded(); _latestServerTrackIndex = CasinoJukeboxManager.GetServerTrackIndex(_playlistStep); if (_isPlaying) { ApplyState(_playlistStep, playing: true, ElapsedSeconds); } } ApplyVolumeIfChanged(); if (!Plugin.JukeboxEnabled) { StopLocalExtraAudio(); StopLocalWorldAudio(); return; } if (_playingLocalExtras) { UpdateLocalExtraPlayback(flag); UpdateLocalInteraction(); return; } if (_waitingForServerBoundary) { StopLocalWorldAudio(); UpdateLocalInteraction(); return; } if ((Object)(object)_source != (Object)null && _isPlaying && _source.isPlaying) { ObserveAudioProgress(); } if (_isPlaying && (Object)(object)_source != (Object)null && !_source.isPlaying && (Object)(object)CasinoJukeboxManager.GetServerClip(_playlistStep) != (Object)null) { if (clockAuthorityCached && HasCurrentTrackEnded()) { HostAdvance(1); UpdateLocalInteraction(); return; } if (Time.time < _nextRestartAttemptTime) { UpdateLocalInteraction(); return; } ApplyState(_playlistStep, playing: true, GetRestartElapsedSeconds()); } if (clockAuthorityCached && _isPlaying && HasCurrentTrackEnded()) { HostAdvance(1); } UpdateLocalInteraction(); } private bool IsWorldAudioAllowedLocally() { if (Plugin.IsHeadlessServer) { return false; } return CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino; } private static bool HasClockAuthority() { if (!Plugin.IsHeadlessServer) { return BJNetcode.AmHost(); } return true; } private bool GetClockAuthorityCached() { if (Plugin.IsHeadlessServer) { _hasClockAuthority = true; return true; } if (Time.unscaledTime < _nextAuthorityRefreshTime) { return _hasClockAuthority; } _nextAuthorityRefreshTime = Time.unscaledTime + 2f; _hasClockAuthority = BJNetcode.AmHost(); return _hasClockAuthority; } private void EnsureWorldAudioSource() { if ((Object)(object)_source == (Object)null) { _source = ((Component)this).GetComponentInChildren(true) ?? ((Component)this).gameObject.AddComponent(); CasinoJukeboxManager.ConfigureAudioSource(_source, spatial: true); _appliedVolume = -1f; } ApplyVolumeIfChanged(); } private void ApplyVolumeIfChanged() { if (!((Object)(object)_source == (Object)null)) { float effectiveJukeboxVolume = CasinoConfig.EffectiveJukeboxVolume; if (!Mathf.Approximately(_appliedVolume, effectiveJukeboxVolume)) { _source.volume = effectiveJukeboxVolume; _appliedVolume = effectiveJukeboxVolume; } } } private void ApplyExtraVolumeIfChanged() { if (!((Object)(object)_localExtraSource == (Object)null)) { float effectiveJukeboxVolume = CasinoConfig.EffectiveJukeboxVolume; if (!Mathf.Approximately(_appliedExtraVolume, effectiveJukeboxVolume)) { _localExtraSource.volume = effectiveJukeboxVolume; _appliedExtraVolume = effectiveJukeboxVolume; } } } private bool CanUseLocalExtras() { if (Plugin.IsHeadlessServer) { return false; } if (BJNetcode.AmHost()) { return false; } if (!Plugin.JukeboxEnabled) { return false; } return CasinoJukeboxManager.LocalTrackCount > 0; } private void CacheServerState(int playlistStep, bool playing, float elapsedSeconds) { _latestServerState = new JukeboxWorldState { ObjectName = _objectName, PlaylistStep = playlistStep, Playing = playing, ElapsedSeconds = elapsedSeconds }; _hasLatestServerState = true; _latestServerTrackIndex = CasinoJukeboxManager.GetServerTrackIndex(playlistStep); } private bool ShouldHoldForLocalExtras(int previousServerStep, int currentServerStep, int previousServerIndex, int currentServerIndex, bool playing) { if (!CanUseLocalExtras()) { return false; } if (!playing) { _playingLocalExtras = false; _waitingForServerBoundary = false; StopLocalExtraAudio(); return false; } if (_waitingForServerBoundary) { if (currentServerStep != previousServerStep && currentServerIndex >= 0) { _waitingForServerBoundary = false; return false; } StopLocalWorldAudio(); return true; } if (_playingLocalExtras) { return true; } if (!IsWorldAudioAllowedLocally()) { return false; } int serverTrackCount = CasinoJukeboxManager.ServerTrackCount; if (serverTrackCount <= 0) { return false; } if (currentServerStep == previousServerStep) { return false; } if (previousServerIndex != serverTrackCount - 1) { return false; } if (currentServerIndex != 0) { return false; } StartLocalExtras(); return true; } private void StartLocalExtras() { if (CanUseLocalExtras()) { StopLocalWorldAudio(); CasinoJukeboxPersonalPlayer.StopForWorldJukebox(); _playingLocalExtras = true; _waitingForServerBoundary = false; _localExtraIndex = 0; PlayLocalExtra(_localExtraIndex); } } private void UpdateLocalExtraPlayback(bool worldAudioAllowed) { if (!worldAudioAllowed || !CanUseLocalExtras()) { FinishLocalExtras(); return; } if ((Object)(object)_localExtraSource == (Object)null || (Object)(object)_localExtraSource.clip == (Object)null || !_localExtraSource.isPlaying) { AudioClip localClip = CasinoJukeboxManager.GetLocalClip(_localExtraIndex); if ((Object)(object)localClip == (Object)null) { FinishLocalExtras(); return; } if ((Object)(object)_localExtraSource == (Object)null || (Object)(object)_localExtraSource.clip != (Object)(object)localClip) { PlayLocalExtra(_localExtraIndex); return; } } if ((Object)(object)_localExtraSource != (Object)null) { ApplyExtraVolumeIfChanged(); } AudioSource? localExtraSource = _localExtraSource; AudioClip val = ((localExtraSource != null) ? localExtraSource.clip : null); if ((Object)(object)val == (Object)null || val.length <= 0.1f) { return; } bool flag = Time.time - _localExtraStartedAt >= val.length - 0.15f; bool flag2 = false; try { flag2 = (Object)(object)_localExtraSource != (Object)null && !_localExtraSource.isPlaying; } catch { } if (flag || flag2) { _localExtraIndex++; if (_localExtraIndex < CasinoJukeboxManager.LocalTrackCount) { PlayLocalExtra(_localExtraIndex); } else { FinishLocalExtras(); } } } private void PlayLocalExtra(int localIndex) { AudioClip localClip = CasinoJukeboxManager.GetLocalClip(localIndex); if ((Object)(object)localClip == (Object)null) { FinishLocalExtras(); return; } if ((Object)(object)_localExtraSource == (Object)null) { _localExtraSource = ((Component)this).gameObject.AddComponent(); _appliedExtraVolume = -1f; } CasinoJukeboxManager.ConfigureAudioSource(_localExtraSource, spatial: true); ApplyExtraVolumeIfChanged(); _localExtraSource.Stop(); _localExtraSource.clip = localClip; _localExtraStartedAt = Time.time; _localExtraSource.Play(); CasinoJukeboxAudioGuard.RegisterJukeboxSource(_localExtraSource); Plugin.Log.LogInfo("[Jukebox] Playing local extra " + CasinoJukeboxManager.GetLocalTrackName(localIndex) + " " + $"on '{_objectName}' (local {localIndex + 1}/{CasinoJukeboxManager.LocalTrackCount})."); } private void FinishLocalExtras() { StopLocalExtraAudio(); _playingLocalExtras = false; _waitingForServerBoundary = CanUseLocalExtras() && _hasLatestServerState; } private void StopLocalWorldAudio() { if ((Object)(object)_source != (Object)null && _source.isPlaying) { _source.Stop(); } CasinoJukeboxAudioGuard.UnregisterJukeboxSource(_source); } private void StopLocalExtraAudio() { if ((Object)(object)_localExtraSource != (Object)null && _localExtraSource.isPlaying) { _localExtraSource.Stop(); } CasinoJukeboxAudioGuard.UnregisterJukeboxSource(_localExtraSource); } private bool HasCurrentTrackEnded() { if ((Object)(object)_source == (Object)null || (Object)(object)_source.clip == (Object)null) { return false; } float length = _source.clip.length; if (length <= 0.1f) { return false; } if (Plugin.IsHeadlessServer) { return HasCurrentTrackEndedByClock(); } if (_source.isPlaying) { float num = 0f; try { num = _source.time; } catch { } if (num > _lastObservedSourceTime) { ObserveAudioProgress(); } return num >= length - 0.15f; } if (!_trackHasAudioProgress) { return false; } return _lastObservedSourceTime >= length - 0.15f; } private bool HasCurrentTrackEndedByClock() { AudioClip serverClip = CasinoJukeboxManager.GetServerClip(_playlistStep); if ((Object)(object)serverClip == (Object)null) { return false; } float length = serverClip.length; if (length <= 0.1f) { return false; } return Time.time - _trackStartedAt >= length - 0.15f; } private void ResetPlaybackObservation(float elapsedSeconds) { _lastObservedSourceTime = Mathf.Max(0f, elapsedSeconds); _trackHasAudioProgress = elapsedSeconds > 0.15f; } private void ObserveAudioProgress() { if (!((Object)(object)_source == (Object)null) && !((Object)(object)_source.clip == (Object)null)) { float num = 0f; try { num = _source.time; } catch { return; } if (!_trackHasAudioProgress || num > _lastObservedSourceTime + 0.02f) { _trackHasAudioProgress = true; _lastObservedSourceTime = num; } } } private float GetRestartElapsedSeconds() { AudioClip serverClip = CasinoJukeboxManager.GetServerClip(_playlistStep); if ((Object)(object)serverClip == (Object)null || serverClip.length <= 0.1f) { return 0f; } float num = Mathf.Max(0f, serverClip.length - 0.15f); if ((Object)(object)_source != (Object)null && (Object)(object)_source.clip == (Object)(object)serverClip) { try { float time = _source.time; if (time > 0f && time < num) { return time; } } catch { } } if (_trackHasAudioProgress) { return Mathf.Clamp(_lastObservedSourceTime, 0f, num); } return Mathf.Clamp(Time.time - _trackStartedAt, 0f, num); } private void UpdateLocalInteraction() { if (Plugin.IsHeadlessServer) { return; } Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } bool flag = IsPlayerInRange(mainPlayer); if (flag && !_playerNearby) { _playerNearby = true; ShowPrompt(); } else if (!flag && _playerNearby) { _playerNearby = false; } if (_playerNearby && !Plugin.IsTypingInUI() && CasinoInput.WasInteractPressed() && !(Time.time < _nextInputTime)) { _nextInputTime = Time.time + 0.5f; if (BJNetcode.AmHostFresh()) { HostAdvance(1); return; } JukeboxNetcode.SendAdvanceRequest(_objectName, 1); Plugin.ShowHUDInfo("Jukebox skip requested..."); } } private bool IsPlayerInRange(Player player) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_collider != (Object)null) { Bounds bounds = ((Collider)_collider).bounds; return ((Bounds)(ref bounds)).Contains(((Component)player).transform.position); } return Vector3.Distance(((Component)this).transform.position, ((Component)player).transform.position) < 3f; } private void ShowPrompt() { if (!CasinoJukeboxManager.HasServerTracks) { Plugin.ShowHUDError("Jukebox has no server songs loaded."); return; } string text = (_isPlaying ? CasinoJukeboxManager.GetServerTrackName(_playlistStep) : "ready"); Plugin.ShowHUDInfo("Jukebox: " + text + ". Press " + CasinoInput.InteractPrompt + " for next song."); } private void OnPlaylistChanged() { if (_playingLocalExtras) { if (CasinoJukeboxManager.LocalTrackCount == 0) { FinishLocalExtras(); } else if (_localExtraIndex >= CasinoJukeboxManager.LocalTrackCount) { FinishLocalExtras(); } else { PlayLocalExtra(_localExtraIndex); } } else { TryAutoStart(); } } private void OnDestroy() { StopLocalExtraAudio(); StopLocalWorldAudio(); if (_subscribed) { CasinoJukeboxManager.OnPlaylistChanged -= OnPlaylistChanged; _subscribed = false; } if (_registry.TryGetValue(_objectName, out CasinoJukebox value) && value == this) { _registry.Remove(_objectName); } } } internal static class CasinoJukeboxAudioGuard { private static readonly List _owners = new List(); private static readonly List _pausedSources = new List(); private static bool _sceneHooksInstalled; internal static void RegisterJukeboxSource(AudioSource? source) { if (!((Object)(object)source == (Object)null)) { InstallSceneHooks(); RemoveDeadOwners(); if (!Contains(_owners, source)) { _owners.Add(source); } PauseCompetingSources(); } } internal static void UnregisterJukeboxSource(AudioSource? source) { for (int num = _owners.Count - 1; num >= 0; num--) { if ((Object)(object)_owners[num] == (Object)null || ((Object)(object)source != (Object)null && _owners[num] == source)) { _owners.RemoveAt(num); } } if (_owners.Count == 0) { RestorePausedSources(); } } private static void InstallSceneHooks() { if (!_sceneHooksInstalled) { SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; _sceneHooksInstalled = true; } } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { RemoveDeadOwners(); if (_owners.Count > 0) { PauseCompetingSources(); } else { RestorePausedSources(); } } private static void OnSceneUnloaded(Scene scene) { RemoveDeadOwners(); RemoveDeadPausedSources(); if (_owners.Count == 0) { RestorePausedSources(); } } private static void PauseCompetingSources() { if (_owners.Count == 0) { return; } AudioSource[] array = Object.FindObjectsOfType(); foreach (AudioSource val in array) { if (ShouldPauseVanillaSource(val)) { try { val.Pause(); _pausedSources.Add(val); } catch (Exception ex) { Plugin.Log.LogWarning("[Jukebox] Failed to pause vanilla music source '" + ((Object)val).name + "': " + ex.Message); } } } } private static void RemoveDeadPausedSources() { for (int num = _pausedSources.Count - 1; num >= 0; num--) { if ((Object)(object)_pausedSources[num] == (Object)null) { _pausedSources.RemoveAt(num); } } } private static bool ShouldPauseVanillaSource(AudioSource source) { if ((Object)(object)source == (Object)null) { return false; } if (!source.isPlaying) { return false; } if ((Object)(object)source.clip == (Object)null) { return false; } if (Contains(_owners, source)) { return false; } if (Contains(_pausedSources, source)) { return false; } if (IsAtlyssJukeboxSource(source)) { return false; } if (!source.loop) { return false; } return LooksLikeVanillaMusicSource(source); } private static bool LooksLikeVanillaMusicSource(AudioSource source) { if (HasAudioComponentName(source, "AudioManager") || HasAudioComponentName(source, "Sound_MapAmbience")) { return true; } string mixerText = GetMixerText(source); if (ContainsAudioWord(mixerText, "music") || ContainsAudioWord(mixerText, "ambience") || ContainsAudioWord(mixerText, "ambient") || ContainsAudioWord(mixerText, "bgm")) { return true; } string value = ((Object)source).name + " " + ((Object)((Component)source).gameObject).name + " " + ((Object)source.clip).name; if (!ContainsAudioWord(value, "music") && !ContainsAudioWord(value, "ambience") && !ContainsAudioWord(value, "ambient")) { return ContainsAudioWord(value, "bgm"); } return true; } private static bool HasAudioComponentName(AudioSource source, string componentName) { Transform val = ((Component)source).transform; while ((Object)(object)val != (Object)null) { Component[] components = ((Component)val).GetComponents(); foreach (Component val2 in components) { if (!((Object)(object)val2 == (Object)null) && string.Equals(((object)val2).GetType().Name, componentName, StringComparison.Ordinal)) { return true; } } val = val.parent; } return false; } private static string GetMixerText(AudioSource source) { try { if ((Object)(object)source.outputAudioMixerGroup == (Object)null) { return string.Empty; } string text = (((Object)(object)source.outputAudioMixerGroup.audioMixer == (Object)null) ? string.Empty : ((Object)source.outputAudioMixerGroup.audioMixer).name); return ((Object)source.outputAudioMixerGroup).name + " " + text; } catch { return string.Empty; } } private static bool IsAtlyssJukeboxSource(AudioSource source) { if ((Object)(object)((Component)source).GetComponentInParent() != (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; } } 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 _serverTracksLoaded; private static bool _clientLoadStarted; private static bool _loadingLocalSongs; internal static string LocalSongsDirectory => Path.Combine(Paths.BepInExRootPath, "Custom Songs", "ATLYSS Jukebox"); internal static int TrackCount => _playlist.Count; internal static int ServerTrackCount => _bundledTracks.Count; internal static int LocalTrackCount => _localTracks.Count; internal static bool HasTracks => _playlist.Count > 0; internal static bool HasServerTracks => _bundledTracks.Count > 0; internal static bool IsLoadingLocalSongs => _loadingLocalSongs; internal static event Action? OnPlaylistChanged; internal static void Init() { if (!_initialized) { _initialized = true; Plugin.Log.LogDebug("[Jukebox] Deferred song discovery until casino authority or a local casino entry needs it."); } } internal static void EnsureServerTracksLoaded() { if (!_initialized) { Init(); } if (_serverTracksLoaded) { return; } if ((Object)(object)Plugin.AssetsBundle == (Object)null) { bool num; if (!Plugin.IsHeadlessServer && !BJNetcode.AmHostFresh()) { if (!CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino) { return; } num = Plugin.EnsurePresentationAssetsLoaded(); } else { num = Plugin.EnsureAuthorityAssetsLoaded(); } if (!num || (Object)(object)Plugin.AssetsBundle == (Object)null) { return; } } _serverTracksLoaded = true; LoadBundledSongs(); RebuildPlaylist(); CasinoJukeboxManager.OnPlaylistChanged?.Invoke(); } internal static void EnsureClientTracksLoaded() { if (Plugin.IsHeadlessServer || !CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino) { return; } EnsureServerTracksLoaded(); if (_clientLoadStarted) { return; } _clientLoadStarted = true; try { Directory.CreateDirectory(LocalSongsDirectory); Plugin.Log.LogInfo("[Jukebox] Local songs folder: " + LocalSongsDirectory); } catch (Exception ex) { Plugin.Log.LogError("[Jukebox] Failed to create local songs folder: " + ex.Message); _clientLoadStarted = false; return; } try { ((MonoBehaviour)Plugin.Instance).StartCoroutine(LoadLocalSongs()); } catch (Exception ex2) { Plugin.Log.LogError("[Jukebox] Failed to start local song loader: " + ex2.Message); _clientLoadStarted = false; } } internal static AudioClip? GetClip(int playlistStep) { return GetTrack(playlistStep)?.Clip; } internal static string GetTrackName(int playlistStep) { JukeboxTrack track = GetTrack(playlistStep); if (track != null) { return track.Name; } return "No songs loaded"; } internal static AudioClip? GetServerClip(int playlistStep) { return GetTrackFrom(_bundledTracks, playlistStep)?.Clip; } internal static string GetServerTrackName(int playlistStep) { JukeboxTrack trackFrom = GetTrackFrom(_bundledTracks, playlistStep); if (trackFrom != null) { return trackFrom.Name; } return "No server songs loaded"; } internal static int GetServerTrackIndex(int playlistStep) { return GetTrackIndex(_bundledTracks.Count, playlistStep); } internal static AudioClip? GetLocalClip(int localIndex) { return GetTrackFrom(_localTracks, localIndex)?.Clip; } internal static string GetLocalTrackName(int localIndex) { JukeboxTrack trackFrom = GetTrackFrom(_localTracks, localIndex); if (trackFrom != null) { return trackFrom.Name; } return "No local songs loaded"; } internal static void ConfigureAudioSource(AudioSource source, bool spatial) { if ((Object)(object)source == (Object)null) { return; } source.playOnAwake = false; source.loop = false; source.spatialBlend = (spatial ? 1f : 0f); source.dopplerLevel = 0f; if (spatial) { if (source.minDistance <= 0f) { source.minDistance = 2f; } if (source.maxDistance < 20f) { source.maxDistance = 65f; } source.rolloffMode = (AudioRolloffMode)1; } source.outputAudioMixerGroup = null; } private static JukeboxTrack? GetTrack(int playlistStep) { return GetTrackFrom(_playlist, playlistStep); } private static JukeboxTrack? GetTrackFrom(List tracks, int playlistStep) { int trackIndex = GetTrackIndex(tracks.Count, playlistStep); if (trackIndex < 0) { return null; } return tracks[trackIndex]; } private static int GetTrackIndex(int count, int playlistStep) { if (count == 0) { return -1; } int num = playlistStep % count; if (num < 0) { num += count; } return num; } private static void LoadBundledSongs() { _bundledTracks.Clear(); AssetBundle assetsBundle = Plugin.AssetsBundle; if ((Object)(object)assetsBundle == (Object)null) { Plugin.Log.LogWarning("[Jukebox] Casino asset bundle is not loaded; bundled songs unavailable."); return; } string[] allAssetNames; try { allAssetNames = assetsBundle.GetAllAssetNames(); } catch (Exception ex) { Plugin.Log.LogError("[Jukebox] Failed to enumerate asset bundle songs: " + ex.Message); return; } HashSet loadedNumbers = new HashSet(); string[] array = allAssetNames; foreach (string assetName in array) { TryLoadBundledSongAsset(assetsBundle, assetName, requireJukeboxPath: true, loadedNumbers); } array = allAssetNames; foreach (string assetName2 in array) { TryLoadBundledSongAsset(assetsBundle, assetName2, requireJukeboxPath: false, loadedNumbers); } if (_bundledTracks.Count == 0) { TryLoadBundledSongsByClipName(assetsBundle, loadedNumbers); } _bundledTracks.Sort(delegate(JukeboxTrack a, JukeboxTrack b) { int num = a.SortIndex.CompareTo(b.SortIndex); return (num != 0) ? num : string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); }); if (_bundledTracks.Count == 0) { Plugin.Log.LogWarning("[Jukebox] No bundled songs found. Expected AudioClips named Song1, Song2, etc. in atlysscasino_assets."); LogBundleSongDiagnostics(allAssetNames); } else { Plugin.Log.LogInfo($"[Jukebox] Loaded {_bundledTracks.Count} bundled song(s): {DescribeTracks(_bundledTracks)}."); } } private static bool TryLoadBundledSongAsset(AssetBundle bundle, string assetName, bool requireJukeboxPath, HashSet 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)); } private static IEnumerator LoadLocalSongs() { _loadingLocalSongs = true; _localTracks.Clear(); yield return null; if (!CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino) { CancelLocalSongLoad(); yield break; } string[] paths; try { paths = Directory.GetFiles(LocalSongsDirectory); } catch (Exception ex) { Plugin.Log.LogError("[Jukebox] Failed to list local songs: " + ex.Message); _loadingLocalSongs = false; _clientLoadStarted = false; yield break; } Array.Sort(paths, (string a, string b) => string.Compare(Path.GetFileName(a), Path.GetFileName(b), StringComparison.OrdinalIgnoreCase)); for (int i = 0; i < paths.Length; i++) { if (!CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino) { CancelLocalSongLoad(); yield break; } string text = paths[i]; if (IsSupportedAudioFile(text)) { yield return LoadLocalSong(text, i); if (!CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino) { CancelLocalSongLoad(); yield break; } } } _loadingLocalSongs = false; RebuildPlaylist(); Plugin.Log.LogInfo($"[Jukebox] Loaded {_localTracks.Count} local song(s). Total playlist: {_playlist.Count}."); CasinoJukeboxManager.OnPlaylistChanged?.Invoke(); } private static IEnumerator LoadLocalSong(string filePath, int sortIndex) { AudioType audioType = GetAudioType(filePath); if ((int)audioType == 0) { Plugin.Log.LogWarning("[Jukebox] Unsupported local song extension: " + filePath); yield break; } string text; try { text = new Uri(filePath).AbsoluteUri; } catch { text = filePath; } UnityWebRequest loader = UnityWebRequestMultimedia.GetAudioClip(text, audioType); DownloadHandler downloadHandler = loader.downloadHandler; DownloadHandlerAudioClip val = (DownloadHandlerAudioClip)(object)((downloadHandler is DownloadHandlerAudioClip) ? downloadHandler : null); if (val != null) { val.streamAudio = CasinoConfig.StreamLocalJukeboxSongsFromDisk; } UnityWebRequestAsyncOperation operation = loader.SendWebRequest(); while (!((AsyncOperation)operation).isDone) { if (!CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino) { loader.Abort(); yield break; } yield return null; } if ((int)loader.result != 1) { Plugin.Log.LogWarning("[Jukebox] Failed to load local song '" + filePath + "': " + loader.error); yield break; } AudioClip content = DownloadHandlerAudioClip.GetContent(loader); if ((Object)(object)content == (Object)null || (int)content.loadState != 2) { Plugin.Log.LogWarning("[Jukebox] Local song did not produce a loaded AudioClip: " + filePath); yield break; } ((Object)content).name = Path.GetFileNameWithoutExtension(filePath); _localTracks.Add(new JukeboxTrack(((Object)content).name, content, isLocal: true, sortIndex)); } private static void CancelLocalSongLoad() { for (int i = 0; i < _localTracks.Count; i++) { AudioClip clip = _localTracks[i].Clip; if ((Object)(object)clip != (Object)null) { Object.Destroy((Object)(object)clip); } } _localTracks.Clear(); _loadingLocalSongs = false; _clientLoadStarted = false; RebuildPlaylist(); Plugin.Log.LogDebug("[Jukebox] Cancelled local song loading after leaving the casino."); } private static void RebuildPlaylist() { _playlist.Clear(); _playlist.AddRange(_bundledTracks); _playlist.AddRange(_localTracks); } private static bool IsSupportedAudioFile(string path) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 return (int)GetAudioType(path) > 0; } private static AudioType GetAudioType(string path) { return (AudioType)(Path.GetExtension(path).ToLowerInvariant() switch { ".ogg" => 14, ".mp3" => 13, ".wav" => 20, _ => 0, }); } private static bool TryParseSongNumber(string name, out int number) { number = 0; if (string.IsNullOrWhiteSpace(name)) { return false; } Match match = Regex.Match(name.Trim(), "^song(\\d+)$", RegexOptions.IgnoreCase); if (!match.Success) { return false; } return int.TryParse(match.Groups[1].Value, out number); } private static string DescribeTracks(List 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 bool _wasInCasino; private int _playlistStep = -1; private float _trackStartedAt; private float _appliedVolume = -1f; internal static void Init() { } internal static void Play() { if (!Plugin.IsHeadlessServer) { if (!Plugin.IsLocalPlayerConfirmedInCasino()) { ShowCommandMessage("Personal jukebox is available only inside the casino.", error: true); } else { GetOrCreate().PlayInternal(); } } } internal static void Forward() { if (!Plugin.IsHeadlessServer) { if (!Plugin.IsLocalPlayerConfirmedInCasino()) { ShowCommandMessage("Personal jukebox is available only inside the casino.", error: true); } else { GetOrCreate().AdvanceInternal(1, showMessage: true); } } } internal static void Previous() { if (!Plugin.IsHeadlessServer) { if (!Plugin.IsLocalPlayerConfirmedInCasino()) { ShowCommandMessage("Personal jukebox is available only inside the casino.", error: true); } else { GetOrCreate().AdvanceInternal(-1, showMessage: true); } } } internal static void StopPlayback() { if (!Plugin.IsHeadlessServer) { if ((Object)(object)_instance == (Object)null) { ShowCommandMessage("Stopped personal jukebox."); } else { _instance.StopInternal(showMessage: true); } } } internal static void StopForWorldJukebox() { if (!((Object)(object)_instance == (Object)null) && _instance._playing) { _instance.StopInternal(showMessage: false); } } internal static void ShowCommandMessage(string message, bool error = false) { string text = (error ? "#FF6666" : "#FFD700"); string text2 = "[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() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown if ((Object)(object)_instance == (Object)null) { GameObject val = new GameObject("AtlyssCasino_PersonalJukebox"); _instance = val.AddComponent(); Object.DontDestroyOnLoad((Object)val); CasinoJukeboxManager.OnPlaylistChanged += _instance.OnPlaylistChanged; } return _instance; } private void Update() { if (!Plugin.IsLocalPlayerConfirmedInCasino()) { if (_playing) { StopInternal(showMessage: false); } _wasInCasino = false; Object.Destroy((Object)(object)((Component)this).gameObject); return; } if (!_wasInCasino) { _wasInCasino = true; CasinoJukeboxManager.EnsureClientTracksLoaded(); } ApplyVolumeIfChanged(); if (!CasinoConfig.JukeboxEnabled) { StopInternal(showMessage: false); } else if (_playing && !((Object)(object)_source == (Object)null) && !((Object)(object)_source.clip == (Object)null)) { float length = _source.clip.length; if (length > 0.1f && Time.time - _trackStartedAt >= length - 0.05f) { AdvanceInternal(1, showMessage: false); } } } private void PlayInternal() { if (!Plugin.IsLocalPlayerConfirmedInCasino()) { StopInternal(showMessage: false); ShowCommandMessage("Personal jukebox is available only inside the casino.", error: true); return; } CasinoJukeboxManager.EnsureClientTracksLoaded(); if (!CasinoConfig.JukeboxEnabled) { ShowCommandMessage("Jukebox is disabled in Client Audio settings.", error: true); return; } if (CasinoJukebox.IsAnyWorldJukeboxPlaying()) { StopInternal(showMessage: false); ShowCommandMessage("Casino jukebox is already playing here.", error: true); return; } if (!CasinoJukeboxManager.HasTracks) { string text = (CasinoJukeboxManager.IsLoadingLocalSongs ? " Local songs are still loading." : ""); ShowCommandMessage("No jukebox songs loaded." + text, error: true); return; } if (_playlistStep < 0) { _playlistStep = 0; } PlayStep(_playlistStep, showMessage: true); } private void AdvanceInternal(int delta, bool showMessage) { if (!Plugin.IsLocalPlayerConfirmedInCasino()) { StopInternal(showMessage: false); if (showMessage) { ShowCommandMessage("Personal jukebox is available only inside the casino.", error: true); } return; } CasinoJukeboxManager.EnsureClientTracksLoaded(); if (!CasinoConfig.JukeboxEnabled) { if (showMessage) { ShowCommandMessage("Jukebox is disabled in Client Audio settings.", error: true); } return; } if (!CasinoJukeboxManager.HasTracks) { if (showMessage) { ShowCommandMessage("No jukebox songs loaded.", error: true); } return; } if (_playlistStep < 0) { _playlistStep = 0; } else { _playlistStep += delta; } if (_playlistStep < 0) { _playlistStep = CasinoJukeboxManager.TrackCount - 1; } PlayStep(_playlistStep, showMessage); } private void PlayStep(int playlistStep, bool showMessage) { if (!Plugin.IsLocalPlayerConfirmedInCasino()) { StopInternal(showMessage: false); return; } if ((Object)(object)_source == (Object)null) { _source = ((Component)this).gameObject.AddComponent(); } CasinoJukeboxManager.ConfigureAudioSource(_source, spatial: false); _appliedVolume = -1f; ApplyVolumeIfChanged(); AudioClip clip = CasinoJukeboxManager.GetClip(playlistStep); if ((Object)(object)clip == (Object)null) { StopInternal(showMessage: false); if (showMessage) { ShowCommandMessage("No clip found for that jukebox song.", error: true); } return; } _source.Stop(); _source.clip = clip; _source.Play(); CasinoJukeboxAudioGuard.RegisterJukeboxSource(_source); _playing = true; _trackStartedAt = Time.time; if (showMessage) { ShowCommandMessage("Playing " + CasinoJukeboxManager.GetTrackName(playlistStep) + "."); } } private void StopInternal(bool showMessage) { if ((Object)(object)_source != (Object)null) { _source.Stop(); CasinoJukeboxAudioGuard.UnregisterJukeboxSource(_source); } _playing = false; if (showMessage) { ShowCommandMessage("Stopped personal jukebox."); } } private void OnPlaylistChanged() { if (_playing && Plugin.IsLocalPlayerConfirmedInCasino()) { PlayStep(_playlistStep, showMessage: false); } else if (_playing) { StopInternal(showMessage: false); } } private void ApplyVolumeIfChanged() { if (!((Object)(object)_source == (Object)null)) { float effectivePersonalJukeboxVolume = CasinoConfig.EffectivePersonalJukeboxVolume; if (!Mathf.Approximately(_appliedVolume, effectivePersonalJukeboxVolume)) { _source.volume = effectivePersonalJukeboxVolume; _appliedVolume = effectivePersonalJukeboxVolume; } } } private void OnDestroy() { CasinoJukeboxAudioGuard.UnregisterJukeboxSource(_source); CasinoJukeboxManager.OnPlaylistChanged -= OnPlaylistChanged; if (_instance == this) { _instance = null; } } private static string StripColorTags(string message) { if (string.IsNullOrEmpty(message)) { return string.Empty; } return message.Replace("", "").Replace("", "").Replace("", "") .Replace("", ""); } } public sealed class RoomZone : MonoBehaviour { internal BoxCollider Collider { get; private set; } internal string RoomName { get; private set; } = "Room"; internal string ZoneId { get; private set; } = string.Empty; internal string MapScopeId { get; private set; } = string.Empty; internal string SceneName { get; private set; } = string.Empty; internal int SceneHandle { get; private set; } internal Component? MapInstance { get; private set; } internal float Volume { get { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Collider == (Object)null) { return float.MaxValue; } Vector3 size = Collider.size; Vector3 lossyScale = ((Component)Collider).transform.lossyScale; return Mathf.Abs(size.x * lossyScale.x) * Mathf.Abs(size.y * lossyScale.y) * Mathf.Abs(size.z * lossyScale.z); } } internal void Setup(BoxCollider box, string roomName, Scene scene, Component? mapInstance, string mapScopeId, string zoneId) { Collider = box; RoomName = RoomZoneRegistry.SanitizeRoomLabel(roomName); ZoneId = zoneId ?? string.Empty; MapScopeId = mapScopeId ?? string.Empty; SceneName = ((Scene)(ref scene)).name ?? string.Empty; SceneHandle = ((Scene)(ref scene)).handle; MapInstance = mapInstance; ((Collider)Collider).isTrigger = true; RoomZoneRegistry.Register(this); } private void OnTriggerEnter(Collider other) { RoomZoneRegistry.TrackPlayer(other); } private void OnTriggerStay(Collider other) { RoomZoneRegistry.TrackPlayer(other); } private void OnTriggerExit(Collider other) { RoomZoneRegistry.ForgetTrackedCollider(other); } private void OnDestroy() { RoomZoneRegistry.Unregister(this); } } internal static class RoomZoneRegistry { private enum DeliveryDisplayResult { Unresolved, Displayed, Rejected } private const string Prefix = "RoomZone"; private const string ExpectedPrivateRoomName = "Office"; private const int DeliveryResolveAttempts = 12; private const float DeliveryResolveDelaySeconds = 0.1f; private static readonly List Zones = new List(); private static readonly Dictionary KnownPlayers = new Dictionary(); private static readonly Dictionary PlayerColliderCache = new Dictionary(); private static readonly HashSet TrackedTriggerColliderIds = new HashSet(); private static readonly HashSet PendingSceneScans = new HashSet(); private static readonly Regex CloneSuffixRegex = new Regex("\\s*\\(\\d+\\)$", RegexOptions.Compiled); private static bool _initialized; private static FieldInfo? _playerMapInstanceField; private static PropertyInfo? _playerMapInstanceProperty; private static FieldInfo? _playerMapNameField; private static PropertyInfo? _playerMapNameProperty; private static bool _playerReflectionResolved; private static bool _loggedPlayerReflectionFailure; private static MethodInfo? _vanillaReceiveChatMethod; private static MethodInfo? _vanillaTargetNoticeMethod; private static bool _loggedVanillaReceiveFailure; internal static bool IsRoutingHost { get { try { return NetworkServer.active; } catch { return Plugin.IsHeadlessServer; } } } internal static void Init() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) if (_initialized) { return; } _initialized = true; SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); if (IsCasinoScene(sceneAt)) { ScanScene(sceneAt); ScheduleSceneScan(sceneAt); } } Plugin.Log.LogInfo("[RoomZone] Event-driven registry initialized."); } internal static void Register(RoomZone zone) { if (!((Object)(object)zone == (Object)null) && !Zones.Contains(zone)) { Zones.Add(zone); } } internal static void Unregister(RoomZone zone) { if (!((Object)(object)zone == (Object)null)) { Zones.Remove(zone); } } internal static void TrackPlayer(Collider collider) { if ((Object)(object)collider == (Object)null) { return; } int instanceID = ((Object)collider).GetInstanceID(); if (!TrackedTriggerColliderIds.Add(instanceID) || !TryGetPlayer(collider, out Player player)) { return; } ulong steam = GetSteam64(player); if (steam == 0L) { TrackedTriggerColliderIds.Remove(instanceID); return; } Player value; bool num = !KnownPlayers.TryGetValue(steam, out value) || value != player; KnownPlayers[steam] = player; if (num && player == Player._mainPlayer) { RoomZoneChatNetcode.NotifyLocalPlayerReady(player); } } internal static void ForgetTrackedCollider(Collider collider) { if (!((Object)(object)collider == (Object)null)) { TrackedTriggerColliderIds.Remove(((Object)collider).GetInstanceID()); } } internal static bool TryRouteValidatedZoneChat(ChatBehaviour chat, string message, bool sentByServer, ChatChannel channel) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 if ((int)channel != 2) { return false; } if (sentByServer) { return false; } if ((Object)(object)chat == (Object)null || string.IsNullOrWhiteSpace(message)) { return false; } if (IsSingleSlashCommand(message)) { return false; } if (!IsRoutingHost) { return false; } Player player = GetPlayer(chat); if ((Object)(object)player == (Object)null) { return false; } RoomZone roomZone = FindContainingZone(player, refreshColliders: true); if ((Object)(object)roomZone == (Object)null) { return false; } ulong steam = GetSteam64(player); if (steam == 0L) { NotifyRoutingFailure(player, 0uL, roomZone, "Your message stayed private, but your player identity was unavailable."); return true; } KnownPlayers[steam] = player; if (!RoomZoneChatNetcode.IsPrivateOutgoingEnabled(steam)) { return false; } try { RoutePrivateMessage(player, steam, roomZone, message); } catch (Exception arg) { Plugin.Log.LogError($"[RoomZone] Private routing failed closed for {steam}: {arg}"); NotifyRoutingFailure(player, steam, roomZone, "Your message stayed private, but it could not be delivered."); } return true; } internal static bool ShouldFailClosed(ChatBehaviour chat, string message, bool sentByServer, ChatChannel channel) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 if ((int)channel != 2) { return false; } if (sentByServer || (Object)(object)chat == (Object)null) { return false; } if (string.IsNullOrWhiteSpace(message) || IsSingleSlashCommand(message)) { return false; } if (!IsRoutingHost) { return false; } Player player = GetPlayer(chat); if ((Object)(object)player == (Object)null) { return false; } RoomZone roomZone = FindContainingZone(player, refreshColliders: true); if ((Object)(object)roomZone == (Object)null) { return false; } ulong steam = GetSteam64(player); if (steam != 0L && !RoomZoneChatNetcode.IsPrivateOutgoingEnabled(steam)) { return false; } NotifyRoutingFailure(player, steam, roomZone, "Your message stayed private, but it could not be delivered."); return true; } private static void RoutePrivateMessage(Player sender, ulong senderSteam64, RoomZone senderZone, string rawMessage) { List playerCandidates = GetPlayerCandidates(sender); int num = 0; int num2 = 0; foreach (Player item in playerCandidates) { if ((Object)(object)item == (Object)null) { continue; } ulong steam = GetSteam64(item); if (steam == 0L) { continue; } RoomZone roomZone = FindContainingZone(item, item == sender); if (!((Object)(object)roomZone == (Object)null) && string.Equals(roomZone.ZoneId, senderZone.ZoneId, StringComparison.Ordinal)) { num++; if (RoomZoneChatNetcode.SendRoomChatDelivery(steam, senderSteam64, rawMessage, senderZone.RoomName, senderZone.MapScopeId, senderZone.ZoneId)) { num2++; } } } Plugin.Log.LogDebug($"[RoomZone] Private Zone chat sender={senderSteam64} " + $"zone='{senderZone.ZoneId}' eligible={num} queued={num2}."); if (num == 0 || num2 < num) { NotifyRoutingFailure(sender, senderSteam64, senderZone, (num == 0) ? "Your message stayed private, but no room recipients could be resolved." : "Your message stayed private, but delivery failed for one or more room players."); } } private static void NotifyRoutingFailure(Player sender, ulong senderSteam64, RoomZone zone, string message) { if ((senderSteam64 == 0L || !RoomZoneChatNetcode.SendPrivateNotice(senderSteam64, message, zone.MapScopeId, zone.ZoneId)) && !TrySendVanillaTargetNotice(sender, message) && sender == Player._mainPlayer) { DisplayLocalNotice(message); } } private static bool TrySendVanillaTargetNotice(Player sender, string message) { if (!IsRoutingHost || (Object)(object)sender == (Object)null) { return false; } ChatBehaviour chatBehaviour = GetChatBehaviour(sender); if ((Object)(object)chatBehaviour == (Object)null) { return false; } if ((object)_vanillaTargetNoticeMethod == null) { _vanillaTargetNoticeMethod = typeof(ChatBehaviour).GetMethod("Target_RecieveMessage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(string) }, null); } if (_vanillaTargetNoticeMethod == null) { return false; } try { _vanillaTargetNoticeMethod.Invoke(chatBehaviour, new object[1] { "[RoomZone] " + SanitizeNotice(message) }); return true; } catch { return false; } } internal static void ReceiveRoomChatDelivery(RoomZoneChatDelivery delivery) { if (delivery != null && delivery.SenderSteam64 != 0L && !string.IsNullOrWhiteSpace(delivery.RawMessage) && !string.IsNullOrWhiteSpace(delivery.ZoneId)) { if ((Object)(object)Plugin.Instance == (Object)null) { TryDisplayDelivery(delivery, allowUnresolved: false); } else { ((MonoBehaviour)Plugin.Instance).StartCoroutine(ResolveAndDisplayDelivery(delivery)); } } } private static IEnumerator ResolveAndDisplayDelivery(RoomZoneChatDelivery delivery) { for (int attempt = 0; attempt < 12; attempt++) { DeliveryDisplayResult deliveryDisplayResult = TryDisplayDelivery(delivery, allowUnresolved: true); if (deliveryDisplayResult == DeliveryDisplayResult.Displayed || deliveryDisplayResult == DeliveryDisplayResult.Rejected) { yield break; } yield return (object)new WaitForSeconds(0.1f); } Plugin.Log.LogDebug("[RoomZone] Dropped unresolved private delivery " + $"sender={delivery.SenderSteam64} zone='{delivery.ZoneId}'."); } private static DeliveryDisplayResult TryDisplayDelivery(RoomZoneChatDelivery delivery, bool allowUnresolved) { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { if (!allowUnresolved) { return DeliveryDisplayResult.Rejected; } return DeliveryDisplayResult.Unresolved; } RoomZone roomZone = FindContainingZone(mainPlayer, refreshColliders: true); if ((Object)(object)roomZone == (Object)null) { if (!allowUnresolved || HasZoneId(delivery.ZoneId)) { return DeliveryDisplayResult.Rejected; } return DeliveryDisplayResult.Unresolved; } if (!string.Equals(roomZone.MapScopeId, delivery.MapScopeId ?? string.Empty, StringComparison.Ordinal) || !string.Equals(roomZone.ZoneId, delivery.ZoneId ?? string.Empty, StringComparison.Ordinal)) { return DeliveryDisplayResult.Rejected; } Player val = FindPlayerBySteam64(delivery.SenderSteam64); if ((Object)(object)val == (Object)null) { return DeliveryDisplayResult.Unresolved; } ChatBehaviour chatBehaviour = GetChatBehaviour(val); if ((Object)(object)chatBehaviour == (Object)null) { return DeliveryDisplayResult.Unresolved; } MethodInfo vanillaReceiveChatMethod = GetVanillaReceiveChatMethod(); if (vanillaReceiveChatMethod == null) { return DeliveryDisplayResult.Rejected; } string text = SanitizeRoomLabel(delivery.RoomName); string text2 = "[" + text + "] " + delivery.RawMessage; try { vanillaReceiveChatMethod.Invoke(chatBehaviour, new object[3] { text2, false, (object)(ChatChannel)2 }); return DeliveryDisplayResult.Displayed; } catch (Exception ex) { Plugin.Log.LogWarning("[RoomZone] Vanilla private receive failed: " + ex.Message); return DeliveryDisplayResult.Rejected; } } internal static void ReceivePrivateNotice(RoomZoneChatNotice notice) { if (notice == null || string.IsNullOrWhiteSpace(notice.Message)) { return; } Player mainPlayer = Player._mainPlayer; if (!((Object)(object)mainPlayer == (Object)null)) { RoomZone roomZone = FindContainingZone(mainPlayer, refreshColliders: true); if (!((Object)(object)roomZone == (Object)null) && string.Equals(roomZone.MapScopeId, notice.MapScopeId, StringComparison.Ordinal) && string.Equals(roomZone.ZoneId, notice.ZoneId, StringComparison.Ordinal)) { DisplayLocalNotice(notice.Message); } } } private static void DisplayLocalNotice(string message) { Player mainPlayer = Player._mainPlayer; ChatBehaviour val = (((Object)(object)mainPlayer == (Object)null) ? null : GetChatBehaviour(mainPlayer)); if ((Object)(object)val == (Object)null) { return; } try { val.New_ChatMessage("[RoomZone] " + SanitizeNotice(message)); } catch { } } private static MethodInfo? GetVanillaReceiveChatMethod() { if (_vanillaReceiveChatMethod != null) { return _vanillaReceiveChatMethod; } _vanillaReceiveChatMethod = typeof(ChatBehaviour).GetMethod("UserCode_Rpc_RecieveChatMessage__String__Boolean__ChatChannel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[3] { typeof(string), typeof(bool), typeof(ChatChannel) }, null); if (_vanillaReceiveChatMethod == null && !_loggedVanillaReceiveFailure) { _loggedVanillaReceiveFailure = true; Plugin.Log.LogError("[RoomZone] Could not resolve vanilla UserCode_Rpc_RecieveChatMessage. Private delivery will fail closed."); } return _vanillaReceiveChatMethod; } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) RefreshScene(scene); } internal static void RefreshScene(Scene scene) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (IsCasinoScene(scene)) { ScanScene(scene); ScheduleSceneScan(scene); } } internal static int CountRegisteredZones(Scene scene) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if (!IsCasinoScene(scene)) { return 0; } Zones.RemoveAll((RoomZone zone) => (Object)(object)zone == (Object)null); int num = 0; for (int num2 = 0; num2 < Zones.Count; num2++) { RoomZone roomZone = Zones[num2]; if ((Object)(object)roomZone != (Object)null && roomZone.SceneHandle == ((Scene)(ref scene)).handle) { num++; } } return num; } internal static bool HasExpectedPrivateRoom(Scene scene) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if (!IsCasinoScene(scene)) { return false; } Zones.RemoveAll((RoomZone zone) => (Object)(object)zone == (Object)null); for (int num = 0; num < Zones.Count; num++) { RoomZone roomZone = Zones[num]; if (!((Object)(object)roomZone == (Object)null) && roomZone.SceneHandle == ((Scene)(ref scene)).handle && !((Object)(object)roomZone.Collider == (Object)null) && ((Collider)roomZone.Collider).enabled && ((Component)roomZone).gameObject.activeInHierarchy && string.Equals(roomZone.RoomName, "Office", StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static void ScheduleSceneScan(Scene scene) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded || !PendingSceneScans.Add(((Scene)(ref scene)).handle)) { return; } if ((Object)(object)Plugin.Instance == (Object)null) { PendingSceneScans.Remove(((Scene)(ref scene)).handle); return; } try { ((MonoBehaviour)Plugin.Instance).StartCoroutine(CoalescedSceneScan(scene)); } catch (Exception ex) { PendingSceneScans.Remove(((Scene)(ref scene)).handle); Plugin.Log.LogWarning("[RoomZone] Could not start scene discovery; a later refresh can retry: " + ex.Message); } } private static IEnumerator CoalescedSceneScan(Scene scene) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) float retryDelay = 0.5f; try { yield return null; yield return null; while (IsCasinoScene(scene)) { ScanScene(scene); if (!HasExpectedPrivateRoom(scene)) { yield return (object)new WaitForSecondsRealtime(retryDelay); retryDelay = Mathf.Min(retryDelay * 2f, 30f); continue; } break; } } finally { PendingSceneScans.Remove(((Scene)(ref scene)).handle); } } private static void OnSceneUnloaded(Scene scene) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) if (string.Equals(((Scene)(ref scene)).name, "AtlyssCasino", StringComparison.Ordinal)) { Zones.RemoveAll((RoomZone zone) => (Object)(object)zone == (Object)null || zone.SceneHandle == ((Scene)(ref scene)).handle); PendingSceneScans.Remove(((Scene)(ref scene)).handle); PlayerColliderCache.Clear(); TrackedTriggerColliderIds.Clear(); } } private static void ScanScene(Scene scene) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) if (!IsCasinoScene(scene)) { return; } int num = 0; GameObject[] rootGameObjects; try { rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); } catch (Exception ex) { Plugin.Log.LogWarning("[RoomZone] Could not enumerate scene roots; discovery will retry: " + ex.Message); return; } GameObject[] array = rootGameObjects; foreach (GameObject val in array) { if ((Object)(object)val == (Object)null) { continue; } BoxCollider[] componentsInChildren; try { componentsInChildren = val.GetComponentsInChildren(true); } catch (Exception ex2) { Plugin.Log.LogWarning("[RoomZone] Could not scan root '" + ((Object)val).name + "'; discovery will continue: " + ex2.Message); continue; } BoxCollider[] array2 = componentsInChildren; foreach (BoxCollider val2 in array2) { if ((Object)(object)val2 == (Object)null) { continue; } try { bool flag = StartsWithRoomZone(CleanUnityName(((Object)((Component)val2).gameObject).name)); if ((((Collider)val2).isTrigger || flag) && IsRoomZoneCandidate(val2)) { Component mapInstance = FindNearestMapInstance(((Component)val2).transform); string mapScopeId = BuildMapScopeId(scene, mapInstance); string zoneId = BuildZoneId(val2, scene, mapInstance, mapScopeId); RoomZone? roomZone = FindRoomZoneComponent(val2); RoomZone roomZone2 = roomZone ?? ((Component)val2).gameObject.AddComponent(); roomZone2.Setup(val2, ResolveRoomName(val2), scene, mapInstance, mapScopeId, zoneId); if ((Object)(object)roomZone == (Object)null) { num++; Plugin.Log.LogInfo("[RoomZone] Hooked room='" + roomZone2.RoomName + "' zone='" + roomZone2.ZoneId + "' scene='" + roomZone2.SceneName + "'."); } } } catch (Exception ex3) { Plugin.Log.LogWarning("[RoomZone] Collider '" + ((Object)val2).name + "' scan failed; other zones and later retries remain active: " + ex3.Message); } } } if (num > 0) { Plugin.Log.LogInfo($"[RoomZone] Hooked {num} collider zone(s) " + "in scene '" + ((Scene)(ref scene)).name + "'."); } } private static bool IsCasinoScene(Scene scene) { if (((Scene)(ref scene)).IsValid() && ((Scene)(ref scene)).isLoaded) { return string.Equals(((Scene)(ref scene)).name, "AtlyssCasino", StringComparison.Ordinal); } return false; } private static RoomZone? FindRoomZoneComponent(BoxCollider box) { RoomZone[] components = ((Component)box).gameObject.GetComponents(); RoomZone[] array = components; foreach (RoomZone roomZone in array) { if ((Object)(object)roomZone != (Object)null && roomZone.Collider == box) { return roomZone; } } array = components; foreach (RoomZone roomZone2 in array) { if ((Object)(object)roomZone2 != (Object)null && (Object)(object)roomZone2.Collider == (Object)null) { return roomZone2; } } return null; } private static Component? FindNearestMapInstance(Transform transform) { Transform val = transform; while ((Object)(object)val != (Object)null) { Component[] components = ((Component)val).GetComponents(); foreach (Component val2 in components) { if (!((Object)(object)val2 == (Object)null) && string.Equals(((object)val2).GetType().Name, "MapInstance", StringComparison.Ordinal)) { return val2; } } val = val.parent; } return null; } private static string BuildMapScopeId(Scene scene, Component? mapInstance) { string text = NormalizeIdPart(((Scene)(ref scene)).name); if ((Object)(object)mapInstance == (Object)null) { return "scene:" + text; } try { NetworkIdentity val = mapInstance.GetComponent() ?? mapInstance.GetComponentInParent(); if ((Object)(object)val != (Object)null && val.netId != 0) { return $"scene:{text}|mapnet:{val.netId}"; } } catch { } return "scene:" + text + "|map:" + BuildPathToSceneRoot(mapInstance.transform); } private static string BuildZoneId(BoxCollider box, Scene scene, Component? mapInstance, string mapScopeId) { Transform stopExclusive = (((Object)(object)mapInstance == (Object)null) ? null : mapInstance.transform); string arg = BuildRelativePath(((Component)box).transform, stopExclusive); BoxCollider[] components = ((Component)box).gameObject.GetComponents(); int num = 0; for (int i = 0; i < components.Length; i++) { if (components[i] == box) { num = i; break; } } return $"{mapScopeId}|zone:{arg}|box:{num}"; } private static string BuildPathToSceneRoot(Transform transform) { return BuildRelativePath(transform, null); } private static string BuildRelativePath(Transform transform, Transform? stopExclusive) { List list = new List(); Transform val = transform; while ((Object)(object)val != (Object)null && val != stopExclusive) { list.Add($"{NormalizeIdPart(((Object)val).name)}@{val.GetSiblingIndex()}"); val = val.parent; } list.Reverse(); return string.Join("/", list.ToArray()); } private static string NormalizeIdPart(string value) { if (string.IsNullOrEmpty(value)) { return "_"; } string text = value.Trim(); if (text.Length == 0) { return "_"; } StringBuilder stringBuilder = new StringBuilder(text.Length * 4); string text2 = text; foreach (char c in text2) { int num = c; stringBuilder.Append(num.ToString("X4")); } return stringBuilder.ToString(); } private static bool IsRoomZoneCandidate(BoxCollider box) { if (StartsWithRoomZone(CleanUnityName(((Object)((Component)box).gameObject).name))) { return true; } Transform parent = ((Component)box).transform.parent; while ((Object)(object)parent != (Object)null) { if (StartsWithRoomZone(CleanUnityName(((Object)parent).name))) { return true; } parent = parent.parent; } return false; } private static string ResolveRoomName(BoxCollider box) { string text = CleanUnityName(((Object)((Component)box).gameObject).name); if (StartsWithRoomZone(text)) { string text2 = ExtractUsableRoomName(text); if (!string.IsNullOrWhiteSpace(text2)) { return text2; } } Transform parent = ((Component)box).transform.parent; while ((Object)(object)parent != (Object)null) { string text3 = CleanUnityName(((Object)parent).name); if (StartsWithRoomZone(text3)) { string text4 = ExtractUsableRoomName(text3); if (!string.IsNullOrWhiteSpace(text4)) { return text4; } } parent = parent.parent; } string text5 = ExtractUsableRoomName(text); if (!string.IsNullOrWhiteSpace(text5)) { return text5; } return "Room"; } private static string? ExtractUsableRoomName(string rawName) { string text = CleanUnityName(rawName); if (IsDefaultRoomName(text)) { return null; } if (text.StartsWith("RoomZone_", StringComparison.OrdinalIgnoreCase)) { string text2 = text.Substring("RoomZone".Length + 1).Trim(' ', '_', '-', ':'); if (!IsDefaultRoomName(text2)) { return text2; } return null; } if (text.StartsWith("RoomZone", StringComparison.OrdinalIgnoreCase) && text.Length > "RoomZone".Length) { string text3 = text.Substring("RoomZone".Length).Trim(' ', '_', '-', ':'); if (!IsDefaultRoomName(text3)) { return text3; } return null; } return text; } private static bool StartsWithRoomZone(string cleanName) { return cleanName.StartsWith("RoomZone", StringComparison.OrdinalIgnoreCase); } private static bool IsDefaultRoomName(string value) { if (string.IsNullOrWhiteSpace(value)) { return true; } string a = CleanUnityName(value); if (!string.Equals(a, "RoomZone", StringComparison.OrdinalIgnoreCase) && !string.Equals(a, "RoomZone_", StringComparison.OrdinalIgnoreCase) && !string.Equals(a, "GameObject", StringComparison.OrdinalIgnoreCase) && !string.Equals(a, "Cube", StringComparison.OrdinalIgnoreCase) && !string.Equals(a, "Box", StringComparison.OrdinalIgnoreCase) && !string.Equals(a, "BoxCollider", StringComparison.OrdinalIgnoreCase) && !string.Equals(a, "Collider", StringComparison.OrdinalIgnoreCase)) { return string.Equals(a, "Trigger", StringComparison.OrdinalIgnoreCase); } return true; } private static string CleanUnityName(string rawName) { string input = rawName ?? string.Empty; input = CloneSuffixRegex.Replace(input, string.Empty); return input.Trim(); } internal static string SanitizeRoomLabel(string value) { if (string.IsNullOrWhiteSpace(value)) { return "Room"; } StringBuilder stringBuilder = new StringBuilder(Math.Min(value.Length, 48)); foreach (char c in value) { if (stringBuilder.Length >= 48) { break; } if (!char.IsControl(c) && c != '<' && c != '>' && c != '[' && c != ']') { stringBuilder.Append(c); } } string text = stringBuilder.ToString().Trim(); if (!string.IsNullOrWhiteSpace(text)) { return text; } return "Room"; } private static string SanitizeNotice(string value) { if (string.IsNullOrWhiteSpace(value)) { return "Private delivery failed."; } return value.Replace("<", string.Empty).Replace(">", string.Empty).Trim(); } private static RoomZone? FindContainingZone(Player player, bool refreshColliders) { RoomZone result = null; float num = float.MaxValue; for (int num2 = Zones.Count - 1; num2 >= 0; num2--) { RoomZone roomZone = Zones[num2]; if ((Object)(object)roomZone == (Object)null || (Object)(object)roomZone.Collider == (Object)null) { Zones.RemoveAt(num2); } else if (((Collider)roomZone.Collider).enabled && ((Component)roomZone).gameObject.activeInHierarchy && IsPlayerInZoneScope(player, roomZone) && OverlapsZone(player, roomZone, refreshColliders)) { float volume = roomZone.Volume; if (volume < num) { result = roomZone; num = volume; } } } return result; } private static bool OverlapsZone(Player player, RoomZone zone, bool refreshColliders) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) Collider[] playerColliders = GetPlayerColliders(player, refreshColliders); if (ComputeOverlap(zone.Collider, playerColliders)) { return true; } if (!refreshColliders) { playerColliders = GetPlayerColliders(player, refresh: true); if (ComputeOverlap(zone.Collider, playerColliders)) { return true; } } Vector3 val = ((Component)zone.Collider).transform.InverseTransformPoint(GetPlayerZonePosition(player)) - zone.Collider.center; Vector3 val2 = zone.Collider.size * 0.5f; if (Mathf.Abs(val.x) <= val2.x && Mathf.Abs(val.y) <= val2.y) { return Mathf.Abs(val.z) <= val2.z; } return false; } private static bool ComputeOverlap(BoxCollider zoneCollider, Collider[] playerColliders) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) Vector3 val2 = default(Vector3); float num = default(float); foreach (Collider val in playerColliders) { if ((Object)(object)val == (Object)null || !val.enabled || !((Component)val).gameObject.activeInHierarchy || (object)val == zoneCollider) { continue; } try { if (Physics.ComputePenetration((Collider)(object)zoneCollider, ((Component)zoneCollider).transform.position, ((Component)zoneCollider).transform.rotation, val, ((Component)val).transform.position, ((Component)val).transform.rotation, ref val2, ref num)) { return true; } } catch { } } return false; } private static Collider[] GetPlayerColliders(Player player, bool refresh) { int instanceID = ((Object)player).GetInstanceID(); if (!refresh && PlayerColliderCache.TryGetValue(instanceID, out Collider[] value) && value != null) { return value; } Collider[] array; try { array = ((Component)player).GetComponentsInChildren(false); } catch { array = Array.Empty(); } PlayerColliderCache[instanceID] = array; return array; } private static List GetPlayerCandidates(Player sender) { List list = new List(); HashSet seen = new HashSet(); AddCandidate(sender, list, seen); foreach (Player value in KnownPlayers.Values) { AddCandidate(value, list, seen); } Player[] array = Object.FindObjectsOfType(); foreach (Player val in array) { AddCandidate(val, list, seen); ulong steam = GetSteam64(val); if (steam != 0L) { KnownPlayers[steam] = val; } } return list; } private static void AddCandidate(Player player, List players, HashSet seen) { if (!((Object)(object)player == (Object)null) && seen.Add(((Object)player).GetInstanceID())) { players.Add(player); } } private static bool HasZoneId(string zoneId) { foreach (RoomZone zone in Zones) { if ((Object)(object)zone != (Object)null && (Object)(object)zone.Collider != (Object)null && string.Equals(zone.ZoneId, zoneId, StringComparison.Ordinal)) { return true; } } return false; } internal static bool TryGetPlayer(Collider collider, out Player player) { player = null; if ((Object)(object)collider == (Object)null) { return false; } player = ((Component)collider).GetComponentInParent(); return (Object)(object)player != (Object)null; } internal static ulong GetSteam64(Player player) { if ((Object)(object)player == (Object)null) { return 0uL; } try { if (ulong.TryParse(player.Network_steamID, out var result)) { return result; } } catch { } return 0uL; } internal static Vector3 GetPlayerZonePosition(Player player) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return Vector3.zero; } Bounds val; try { CharacterController componentInChildren = ((Component)player).GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null && ((Collider)componentInChildren).enabled) { val = ((Collider)componentInChildren).bounds; return ((Bounds)(ref val)).center; } } catch { } try { Collider[] playerColliders = GetPlayerColliders(player, refresh: false); Bounds? val2 = null; Collider[] array = playerColliders; foreach (Collider val3 in array) { if (!((Object)(object)val3 == (Object)null) && val3.enabled && !val3.isTrigger) { if (val2.HasValue) { Bounds value = val2.Value; ((Bounds)(ref value)).Encapsulate(val3.bounds); val2 = value; } else { val2 = val3.bounds; } } } if (val2.HasValue) { val = val2.Value; return ((Bounds)(ref val)).center; } } catch { } return ((Component)player).transform.position; } private static Player? GetPlayer(ChatBehaviour chat) { if ((Object)(object)chat == (Object)null) { return null; } try { Player component = ((Component)chat).GetComponent(); if ((Object)(object)component != (Object)null) { return component; } } catch { } try { Player componentInParent = ((Component)chat).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { return componentInParent; } } catch { } try { object? obj3 = typeof(ChatBehaviour).GetField("_player", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(chat); return (Player?)((obj3 is Player) ? obj3 : null); } catch { return null; } } private static ChatBehaviour? GetChatBehaviour(Player player) { if ((Object)(object)player == (Object)null) { return null; } try { return ((Component)player).GetComponent() ?? ((Component)player).GetComponentInChildren(); } catch { return null; } } private static Player? FindPlayerBySteam64(ulong steam64) { if (steam64 == 0L) { return null; } if (KnownPlayers.TryGetValue(steam64, out Player value) && (Object)(object)value != (Object)null) { return value; } try { Player val = BJNetcode.FindPlayerBySteam64(steam64); if ((Object)(object)val != (Object)null) { KnownPlayers[steam64] = val; } return val; } catch { return null; } } private static bool IsPlayerInZoneScope(Player player, RoomZone zone) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) Component playerMapInstance = GetPlayerMapInstance(player); if ((Object)(object)playerMapInstance != (Object)null && (Object)(object)zone.MapInstance != (Object)null) { return playerMapInstance == zone.MapInstance; } string playerMapName = GetPlayerMapName(player); if (!string.IsNullOrWhiteSpace(playerMapName) && !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? 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 { if (_playerMapNameProperty?.GetValue(player) is string result) { return result; } } catch { } try { if (_playerMapNameField?.GetValue(player) is string result2) { return result2; } } catch { } return string.Empty; } private static void ResolvePlayerReflection() { if (!_playerReflectionResolved) { _playerReflectionResolved = 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 (!_loggedPlayerReflectionFailure && _playerMapInstanceProperty == null && _playerMapInstanceField == null) { _loggedPlayerReflectionFailure = true; Plugin.Log.LogWarning("[RoomZone] Player MapInstance reflection was unavailable; scope checks will use map and scene names."); } } } private static bool IsSingleSlashCommand(string message) { if (string.IsNullOrWhiteSpace(message)) { return false; } string text = message.TrimStart(); if (text.StartsWith("/", StringComparison.Ordinal)) { return !text.StartsWith("//", StringComparison.Ordinal); } return false; } } [HarmonyPatch(typeof(ChatBehaviour), "Rpc_RecieveChatMessage", new Type[] { typeof(string), typeof(bool), typeof(ChatChannel) })] internal static class RoomZoneChatPatch { [HarmonyPrefix] [HarmonyPriority(0)] public static bool Prefix(ChatBehaviour __instance, string __0, bool __1, ChatChannel __2) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) try { return !RoomZoneRegistry.TryRouteValidatedZoneChat(__instance, __0, __1, __2); } catch (Exception arg) { Plugin.Log.LogError($"[RoomZone] Validated RPC interception failed: {arg}"); return !RoomZoneRegistry.ShouldFailClosed(__instance, __0, __1, __2); } } } 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? CasinoGameUiAutoOpenEntry; internal static ConfigEntry? SuppressRoutineClientLogsEntry; internal static ConfigEntry? RoomZoneChatEnabledEntry; internal static ConfigEntry? BalancedCasinoPerformanceEntry; internal static ConfigEntry? HideDecorativePropsEntry; internal static ConfigEntry? ReduceIdleSlotVisualsEntry; internal static ConfigEntry? ControllerInteractEnabledEntry; internal static ConfigEntry? ControllerInteractButtonEntry; internal static ConfigEntry? CasinoGameUiHotkeyEntry; 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 ?? 100, 0, 10000); internal static int SyncedWalkAwayPenaltyCrowns => Clamp(_syncedWalkAwayPenaltyCrowns ?? 10, 0, 1000); internal static float SyncedBlackjackTurnTimeoutSeconds => Clamp(_syncedBlackjackTurnTimeoutSeconds ?? 60, 15, 300); internal static float SyncedRouletteAfkTimeoutSeconds => Clamp(_syncedRouletteAfkTimeoutSeconds ?? 60, 15, 300); internal static float SyncedSlotsPayoutMultiplier => Clamp(_syncedSlotsPayoutMultiplier ?? 1f, 0f, 10f); internal static float SyncedBlackjackPayoutMultiplier => Clamp(_syncedBlackjackPayoutMultiplier ?? 1f, 0f, 10f); internal static float SyncedRoulettePayoutMultiplier => Clamp(_syncedRoulettePayoutMultiplier ?? 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 CasinoGameUiAutoOpen => CasinoGameUiAutoOpenEntry?.Value ?? true; internal static bool SuppressRoutineClientLogs => SuppressRoutineClientLogsEntry?.Value ?? true; internal static bool RoomZoneChatEnabled => RoomZoneChatEnabledEntry?.Value ?? true; internal static bool BalancedCasinoPerformance => BalancedCasinoPerformanceEntry?.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 string CasinoGameUiHotkey => (CasinoGameUiHotkeyEntry?.Value ?? string.Empty).Trim(); 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_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Expected O, but got Unknown //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Expected O, but got Unknown //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Expected O, but got Unknown //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Expected O, but got Unknown //IL_041c: Unknown result type (might be due to invalid IL or missing references) //IL_0426: Expected O, but got Unknown //IL_0454: Unknown result type (might be due to invalid IL or missing references) //IL_045e: 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."); CasinoGameUiAutoOpenEntry = config.Bind("Client Display", "Casino Game UI Auto Open", true, "Automatically opens the local blackjack/roulette IMGUI panel when you sit at or join a game table."); SuppressRoutineClientLogsEntry = config.Bind("Client Display", "Suppress Routine Client Logs", true, "When true, normal clients suppress casino info/debug/message logs. Warnings and errors still print."); RoomZoneChatEnabledEntry = config.Bind("Client Chat", "Private Room Zone Chat Enabled", true, "Controls outgoing Zone chat while you are inside a private RoomZone. On sends privately to players in the same exact room; off sends publicly through vanilla Zone chat. Incoming private room messages remain enabled either way."); BalancedCasinoPerformanceEntry = config.Bind("Client Performance", "Balanced Casino Performance", true, "Compatibility setting retained for existing configs. Active casino lights now always use their exact authored settings; casino presentation is still suspended while you are outside to protect FPS."); 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."); CasinoGameUiHotkeyEntry = config.Bind("Client Controls", "Casino Game UI Hotkey", "", "Optional keyboard KeyCode used to open or close the casino game UI. Leave empty to disable the hotkey."); 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 suppress casino info/debug/message logs. Warnings and errors still print."); } 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_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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", new Type[] { })] internal static class SettingsOpenPatch { private static void Postfix() { RefreshInteractable(); } } [HarmonyPatch(typeof(SettingsManager), "Set_SettingMenuSelectionIndex", new Type[] { typeof(int) })] internal 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //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_0078: 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_046a: Unknown result type (might be due to invalid IL or missing references) //IL_0474: 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)); TrackLocal(orAddCustomTab.AddToggle("Game UI Auto Open", CasinoConfig.CasinoGameUiAutoOpenEntry)); TrackLocal(orAddCustomTab.AddToggle("Quiet Client Logs", CasinoConfig.SuppressRoutineClientLogsEntry)); 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("Balanced Casino Performance", CasinoConfig.BalancedCasinoPerformanceEntry)); 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)); TrackLocal(orAddCustomTab.AddTextField("Game UI Hotkey", CasinoConfig.CasinoGameUiHotkeyEntry, "")); 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() { CasinoRuntimeActivity.RefreshConfiguration(); 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_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (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) { _interactableSetters.Add(delegate(bool interactable) { if ((Object)(object)slider.Slider != (Object)null) { ((Selectable)slider.Slider).interactable = interactable; } if ((Object)(object)slider.ResetButton != (Object)null) { ((Selectable)slider.ResetButton).interactable = interactable; } }); } private static void TrackLocal(AtlyssAdvancedSlider slider) { _localInteractableSetters.Add(delegate(bool interactable) { if ((Object)(object)slider.Slider != (Object)null) { ((Selectable)slider.Slider).interactable = interactable; } if ((Object)(object)slider.ResetButton != (Object)null) { ((Selectable)slider.ResetButton).interactable = interactable; } }); } private static void Track(AtlyssToggle toggle) { _interactableSetters.Add(delegate(bool interactable) { if ((Object)(object)toggle.Toggle != (Object)null) { ((Selectable)toggle.Toggle).interactable = interactable; } }); } private static void TrackLocal(AtlyssToggle toggle) { _localInteractableSetters.Add(delegate(bool interactable) { if ((Object)(object)toggle.Toggle != (Object)null) { ((Selectable)toggle.Toggle).interactable = interactable; } }); } private static void Track(AtlyssTextField textField) { _interactableSetters.Add(delegate(bool interactable) { if ((Object)(object)textField.InputField != (Object)null) { ((Selectable)textField.InputField).interactable = interactable; } }); } private static void TrackLocal(AtlyssTextField textField) { _localInteractableSetters.Add(delegate(bool interactable) { if ((Object)(object)textField.InputField != (Object)null) { ((Selectable)textField.InputField).interactable = interactable; } }); } private static void Track(AtlyssDropdown dropdown) { _interactableSetters.Add(delegate(bool interactable) { if ((Object)(object)dropdown.Dropdown != (Object)null) { ((Selectable)dropdown.Dropdown).interactable = interactable; } }); } private static void TrackLocal(AtlyssDropdown dropdown) { _localInteractableSetters.Add(delegate(bool interactable) { if ((Object)(object)dropdown.Dropdown != (Object)null) { ((Selectable)dropdown.Dropdown).interactable = interactable; } }); } private static void Track(AtlyssButton button) { _interactableSetters.Add(delegate(bool interactable) { if ((Object)(object)button.Button != (Object)null) { ((Selectable)button.Button).interactable = interactable; } }); } private static void TrackLocal(AtlyssButton button) { _localInteractableSetters.Add(delegate(bool interactable) { if ((Object)(object)button.Button != (Object)null) { ((Selectable)button.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_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) int gamepadButtonIndex = GetGamepadButtonIndex(config.Value, fallback); AtlyssDropdown obj = tab.AddDropdown(label, _gamepadButtonLabels, gamepadButtonIndex); obj.OnValueChanged.AddListener((UnityAction)delegate(int newValue) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (newValue < 0 || newValue >= _gamepadButtons.Length) { newValue = GetGamepadButtonIndex(fallback, fallback); } config.Value = _gamepadButtons[newValue]; }); return obj; } private static int GetGamepadButtonIndex(GamepadButton value, GamepadButton fallback) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between I4 and Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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.10.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { private enum ExpectedHarmonyPatchKind { Prefix, Postfix } public static CasinoLog Log = null; public static AssetBundle? AssetsBundle = null; private static bool _assetBundleLoadAttempted; private static bool _presentationServicesInitialized; private const string HarmonyId = "dev.seth.atlysscasino"; 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); private static ChatBehaviour? _cachedLocalChatBehaviour; private static float _nextChatResolveTime; 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; } if (CasinoRuntimeActivity.CasinoSceneLoaded) { return CasinoRuntimeActivity.IsAuthorityHost; } try { return BJNetcode.AmHost(); } catch { return false; } } } public static int EntryFeeCrowns { get { if (!UseLocalGameplayConfig) { return CasinoConfig.SyncedEntryFeeCrowns; } return CasinoConfig.EntryFeeCrowns; } } public static int WalkAwayPenaltyCrowns { get { if (!UseLocalGameplayConfig) { return CasinoConfig.SyncedWalkAwayPenaltyCrowns; } return CasinoConfig.WalkAwayPenaltyCrowns; } } public static float BlackjackTurnTimeoutSeconds { get { if (!UseLocalGameplayConfig) { return CasinoConfig.SyncedBlackjackTurnTimeoutSeconds; } return CasinoConfig.BlackjackTurnTimeoutSeconds; } } public static float RouletteAfkTimeoutSeconds { get { if (!UseLocalGameplayConfig) { return CasinoConfig.SyncedRouletteAfkTimeoutSeconds; } return CasinoConfig.RouletteAfkTimeoutSeconds; } } public static float SlotsPayoutMultiplier { get { if (!UseLocalGameplayConfig) { return CasinoConfig.SyncedSlotsPayoutMultiplier; } return CasinoConfig.SlotsPayoutMultiplier; } } public static float BlackjackPayoutMultiplier { get { if (!UseLocalGameplayConfig) { return CasinoConfig.SyncedBlackjackPayoutMultiplier; } return CasinoConfig.BlackjackPayoutMultiplier; } } public static float RoulettePayoutMultiplier { get { if (!UseLocalGameplayConfig) { return CasinoConfig.SyncedRoulettePayoutMultiplier; } return CasinoConfig.RoulettePayoutMultiplier; } } public static int[] AllowedBets { get { if (!UseLocalGameplayConfig) { return CasinoConfig.SyncedAllowedBetAmounts; } return CasinoConfig.AllowedBetAmounts; } } 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 BalancedCasinoPerformance => CasinoConfig.BalancedCasinoPerformance; public static bool ReduceIdleSlotVisuals => CasinoConfig.ReduceIdleSlotVisuals; public static bool SuppressRoutineHeadlessLogs => CasinoConfig.SuppressRoutineHeadlessLogs; public static bool SuppressRoutineClientLogs => CasinoConfig.SuppressRoutineClientLogs; public static bool JukeboxEnabled => CasinoConfig.JukeboxEnabled; public static float JukeboxVolume => CasinoConfig.JukeboxVolume; public static float PersonalJukeboxVolume => CasinoConfig.PersonalJukeboxVolume; private void Awake() { Instance = this; Log = new CasinoLog(((BaseUnityPlugin)this).Logger); RunStartupStep("configuration", delegate { CasinoConfig.Bind(((BaseUnityPlugin)this).Config); }, critical: true); RunStartupStep("Harmony patch installation", InstallHarmonyPatches, critical: true); RunStartupStep("EasySettings registration", CasinoEasySettings.Register); RunStartupStep("casino runtime coordinator", HostCasinoVisibilityWatcher.Init); RunStartupStep("casino scene setup", CasinoPatch.Init); RunStartupStep("RoomZone registry", RoomZoneRegistry.Init, critical: true); if (!IsHeadlessServer) { RunStartupStep("self portrait visibility", SelfPortraitVisibility.Init); } RunStartupStep("table leave watcher", BlackjackSceneWatcher.Init); if (!IsHeadlessServer) { RunStartupStep("controller macros", CasinoControllerMacros.Init); } RunStartupStep("casino game UI", CasinoGameUI.Init); RunStartupStep("blackjack and slot netcode", BJNetcode.Initialize, critical: true); RunStartupStep("roulette netcode", RNNetcode.Initialize, critical: true); RunStartupStep("jukebox netcode", JukeboxNetcode.Initialize); RunStartupStep("RoomZone chat netcode", RoomZoneChatNetcode.Initialize, critical: true); RunStartupStep("RoomZone preference readiness", RoomZoneChatNetcode.BeginLocalPlayerReadinessWatch); RunStartupStep("RoomZone scene readiness subscription", delegate { SceneManager.sceneLoaded += OnSceneLoadedForRoomZonePreference; }); RunStartupStep("authority asset bundle handle", delegate { EnsureAuthorityAssetsLoaded(); }, critical: true); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Atlyss Casino loaded!"); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[Casino] Slots: /slotbet | Blackjack: /blackjackbet /ready /start /hit /stand /leave | Roulette: /tbet /rbet /rclearbets /rstandby /rspin /rleave | Game UI: /casinoui | Jukebox: /jukebox /next /prev /stop | Help: /casinohelp"); } private bool RunStartupStep(string label, Action action, bool critical = false) { try { action(); return true; } catch (Exception arg) { ((BaseUnityPlugin)this).Logger.LogError((object)("[CasinoStartup] Isolated " + (critical ? "critical" : "optional") + " service " + $"'{label}' failed without aborting remaining startup: {arg}")); return false; } } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoadedForRoomZonePreference; } private static void OnSceneLoadedForRoomZonePreference(Scene scene, LoadSceneMode mode) { try { RoomZoneChatNetcode.BeginLocalPlayerReadinessWatch(); } catch (Exception arg) { Log.LogError("[CasinoStartup] RoomZone scene readiness refresh " + $"failed without escaping the scene event: {arg}"); } } private void InstallHarmonyPatches() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown Harmony harmony = new Harmony("dev.seth.atlysscasino"); MethodInfo expectedTarget = AccessTools.Method(typeof(ChatBehaviour), "Send_ChatMessage", new Type[1] { typeof(string) }, (Type[])null); bool flag = InstallPatchClass(harmony, typeof(CommandHandler), expectedTarget, "casino commands", critical: true); MethodInfo expectedTarget2 = AccessTools.Method(typeof(ChatBehaviour), "Rpc_RecieveChatMessage", new Type[3] { typeof(string), typeof(bool), typeof(ChatChannel) }, (Type[])null); bool flag2 = InstallPatchClass(harmony, typeof(RoomZoneChatPatch), expectedTarget2, "private room chat", critical: true); bool flag3 = InstallOptionalPatchClass(harmony, typeof(CasinoEasySettings.SettingsOpenPatch), typeof(SettingsManager), "Open_SettingsMenu", Type.EmptyTypes, "casino settings menu open refresh", ExpectedHarmonyPatchKind.Postfix); bool flag4 = InstallOptionalPatchClass(harmony, typeof(CasinoEasySettings.SettingsSelectionPatch), typeof(SettingsManager), "Set_SettingMenuSelectionIndex", new Type[1] { typeof(int) }, "casino settings tab selection refresh", ExpectedHarmonyPatchKind.Postfix); string text = "not compiled"; bool flag5 = flag && flag2; string text2 = "[CasinoStartup] Harmony health: critical=" + (flag5 ? "healthy" : "FAILED") + ", commands=" + (flag ? "healthy" : "failed") + ", privateRoomChat=" + (flag2 ? "healthy" : "failed") + ", settingsOpen=" + (flag3 ? "healthy" : "failed") + ", settingsSelection=" + (flag4 ? "healthy" : "failed") + ", blackjackDebug=" + text + "."; if (flag5) { ((BaseUnityPlugin)this).Logger.LogInfo((object)text2); } else { ((BaseUnityPlugin)this).Logger.LogError((object)text2); } } private bool InstallPatchClass(Harmony harmony, Type patchClass, MethodInfo? expectedTarget, string label, bool critical, ExpectedHarmonyPatchKind expectedKind = ExpectedHarmonyPatchKind.Prefix) { try { if (expectedTarget == null) { ((BaseUnityPlugin)this).Logger.LogError((object)("[CasinoStartup] Cannot resolve target for " + label + ".")); return false; } harmony.CreateClassProcessor(patchClass).Patch(); if (HasExpectedPatch(expectedTarget, patchClass, expectedKind)) { return true; } ((BaseUnityPlugin)this).Logger.LogError((object)("[CasinoStartup] " + label + " patch installed no verified " + expectedKind.ToString().ToLowerInvariant() + " on " + expectedTarget.DeclaringType?.Name + "." + expectedTarget.Name + ".")); return false; } catch (Exception arg) { string text = (critical ? "critical" : "optional"); ((BaseUnityPlugin)this).Logger.LogError((object)("[CasinoStartup] Isolated " + text + " patch '" + label + "' " + $"failed without aborting other patches: {arg}")); return false; } } private bool InstallOptionalPatchClass(Harmony harmony, Type patchClass, Type targetType, string targetMethodName, Type[] targetArgumentTypes, string label, ExpectedHarmonyPatchKind expectedKind) { try { MethodInfo expectedTarget = AccessTools.Method(targetType, targetMethodName, targetArgumentTypes, (Type[])null); return InstallPatchClass(harmony, patchClass, expectedTarget, label, critical: false, expectedKind); } catch (Exception arg) { ((BaseUnityPlugin)this).Logger.LogError((object)("[CasinoStartup] Isolated optional patch '" + label + "' target resolution failed without aborting other patches: " + $"{arg}")); return false; } } private static bool HasExpectedPatch(MethodInfo expectedTarget, Type patchClass, ExpectedHarmonyPatchKind expectedKind) { Patches patchInfo = Harmony.GetPatchInfo((MethodBase)expectedTarget); if (patchInfo == null) { return false; } int num = ((expectedKind == ExpectedHarmonyPatchKind.Prefix) ? patchInfo.Prefixes.Count : patchInfo.Postfixes.Count); for (int i = 0; i < num; i++) { Patch val = ((expectedKind == ExpectedHarmonyPatchKind.Prefix) ? patchInfo.Prefixes[i] : patchInfo.Postfixes[i]); if (val.owner == "dev.seth.atlysscasino" && val.PatchMethod?.DeclaringType == patchClass) { return true; } } return false; } internal static void LogAlwaysInfo(object message) { ((BaseUnityPlugin)Instance).Logger.LogInfo(message); } public static bool IsLocalPlayerInCasino() { return CasinoRuntimeActivity.IsLocalPlayerInCasino; } public static bool IsLocalPlayerConfirmedInCasino() { return CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino; } 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 cachedLocalChatBehaviour = GetCachedLocalChatBehaviour(); if ((Object)(object)cachedLocalChatBehaviour == (Object)null) { return false; } Type type = ((object)cachedLocalChatBehaviour).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(cachedLocalChatBehaviour); 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) { object value = property.GetValue(component); if (value is bool && !(bool)value) { 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 value2 = property2.GetValue(component); bool flag = default(bool); int num; if (value2 is bool) { flag = (bool)value2; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return false; } } public static bool IsCasinoOwner(ulong steam64) { return steam64 == 76561198284196478L; } public static bool ShouldRigOwnerLuck(ulong steam64) { if (OwnerLuckEnabled) { return IsCasinoOwner(steam64); } return false; } public static void ShowGameFeed(string message) { try { ShowGameFeed(GetCachedLocalChatBehaviour(), message); } catch { } } internal static ChatBehaviour? GetCachedLocalChatBehaviour() { if ((Object)(object)_cachedLocalChatBehaviour != (Object)null) { return _cachedLocalChatBehaviour; } if (Time.unscaledTime < _nextChatResolveTime) { return null; } _nextChatResolveTime = Time.unscaledTime + 1f; try { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer != (Object)null) { _cachedLocalChatBehaviour = ((Component)mainPlayer).GetComponent() ?? ((Component)mainPlayer).GetComponentInChildren(true); } if ((Object)(object)_cachedLocalChatBehaviour == (Object)null) { _cachedLocalChatBehaviour = Object.FindObjectOfType(); } } catch { } return _cachedLocalChatBehaviour; } public static void ShowGameFeed(ChatBehaviour? chat, string message) { if (!GameFeedMessagesEnabled || !IsLocalPlayerConfirmedInCasino() || (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(); for (int i = 0; i < assemblies.Length; i++) { Type type = assemblies[i].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 || !IsLocalPlayerConfirmedInCasino()) { 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_0003: 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) ScopeToCasinoScene(go, default(Scene)); } public static void ScopeToCasinoScene(GameObject go, Scene preferredScene) { //IL_000b: 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_002b: 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_0028: 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_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_004a: 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_0084: 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_0079: 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) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_013a: 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_011c: Unknown result type (might be due to invalid IL or missing references) //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_009e: 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) //IL_00b2: 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) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)go == (Object)null || IsLoadedCasinoScene(go.scene)) { return; } Scene val; Scene val2; if (!IsLoadedCasinoScene(preferredScene)) { val = default(Scene); val2 = val; } else { val2 = preferredScene; } Scene val3 = val2; if (!IsLoadedCasinoScene(val3)) { Component cachedCasinoMapInstance = CasinoRuntimeActivity.CachedCasinoMapInstance; if ((Object)(object)cachedCasinoMapInstance != (Object)null && IsLoadedCasinoScene(cachedCasinoMapInstance.gameObject.scene)) { val3 = cachedCasinoMapInstance.gameObject.scene; } } if (!IsLoadedCasinoScene(val3)) { Scene activeScene = SceneManager.GetActiveScene(); if (IsLoadedCasinoScene(activeScene)) { val3 = activeScene; } } if (!IsLoadedCasinoScene(val3)) { Scene val4 = default(Scene); bool flag = false; bool flag2 = false; for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); if (IsLoadedCasinoScene(sceneAt)) { if (flag) { flag2 = true; break; } val4 = sceneAt; flag = true; } } if (flag && !flag2) { val3 = val4; } } if (!IsLoadedCasinoScene(val3)) { CasinoLog log = Log; string[] obj = new string[5] { "[Casino] ScopeToCasinoScene could not resolve one exact casino scene — '", ((Object)go).name, "' will stay in scene '", null, null }; val = go.scene; object obj2; if (!((Scene)(ref val)).IsValid()) { obj2 = ""; } else { val = go.scene; obj2 = ((Scene)(ref val)).name; } obj[3] = (string)obj2; obj[4] = "'."; log.LogWarning(string.Concat(obj)); return; } try { SceneManager.MoveGameObjectToScene(go, val3); } catch (Exception ex) { Log.LogWarning("[Casino] ScopeToCasinoScene failed for '" + ((Object)go).name + "': " + ex.Message); } } private static bool IsLoadedCasinoScene(Scene scene) { if (((Scene)(ref scene)).IsValid() && ((Scene)(ref scene)).isLoaded) { return ((Scene)(ref scene)).name == "AtlyssCasino"; } return false; } 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); } } internal static bool EnsurePresentationAssetsLoaded() { if (IsHeadlessServer) { return false; } if (!EnsureCasinoAssetBundleLoaded()) { return false; } if (!_presentationServicesInitialized) { CasinoJukeboxManager.Init(); CasinoJukeboxPersonalPlayer.Init(); _presentationServicesInitialized = true; } return true; } internal static bool EnsureAuthorityAssetsLoaded() { return EnsureCasinoAssetBundleLoaded(); } private static bool EnsureCasinoAssetBundleLoaded() { if ((Object)(object)AssetsBundle == (Object)null && !_assetBundleLoadAttempted) { _assetBundleLoadAttempted = true; LoadAssetBundle(); } return (Object)(object)AssetsBundle != (Object)null; } private static void LoadAssetBundle() { try { string text = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "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) { _inner.LogWarning(data); } public void LogInfo(object data) { if (!ShouldSuppressRoutine()) { _inner.LogInfo(data); } } public void LogDebug(object data) { if (!ShouldSuppressRoutine()) { _inner.LogDebug(data); } } public void LogMessage(object data) { if (!ShouldSuppressRoutine()) { _inner.LogMessage(data); } } private static bool ShouldSuppressRoutine() { if (!Plugin.IsHeadlessServer) { return Plugin.SuppressRoutineClientLogs; } return Plugin.SuppressRoutineHeadlessLogs; } } internal class CasinoControllerMacros : MonoBehaviour { private const float MACRO_COOLDOWN = 0.25f; private static CasinoControllerMacros? _instance; private float _nextMacroTime; internal static void Init() { //IL_0013: 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_001e: Expected O, but got Unknown //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_instance != (Object)null)) { GameObject val = new GameObject("AtlyssCasino_ControllerMacros"); Object.DontDestroyOnLoad((Object)val); ((Object)val).hideFlags = (HideFlags)61; _instance = val.AddComponent(); } } private void Update() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_004a: 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_0070: Unknown result type (might be due to invalid IL or missing references) if (CasinoConfig.ControllerMacrosEnabled && !Plugin.IsHeadlessServer && !HostCasinoVisibilityWatcher.IsServerMode && Plugin.IsLocalPlayerConfirmedInCasino() && !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_000a: 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_000d: 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_0014: 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 WasGameUiHotkeyPressed() { //IL_001d: 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) string casinoGameUiHotkey = CasinoConfig.CasinoGameUiHotkey; if (string.IsNullOrWhiteSpace(casinoGameUiHotkey)) { return false; } if (!Enum.TryParse(casinoGameUiHotkey, ignoreCase: true, out KeyCode result)) { return false; } if ((int)result == 0) { return false; } return Input.GetKeyDown(result); } internal static bool WasControllerButtonPressed(GamepadButton button) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_000f: 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_0000: Unknown result type (might be due to invalid IL or missing references) //IL_006e: 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; } if (!CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino && !IsGloballyAvailableCasinoCommand(cmd)) { return true; } switch (cmd) { case "/slotbet": case "/sbet": case "/sb": HandleSlotBet(__instance, args); SetChatFocusFalse(__instance); return false; case "/bb": case "/blackjackbet": case "/bjbet": case "/bjb": HandleBlackjackBet(__instance, args); SetChatFocusFalse(__instance); return false; case "/rd": case "/br": case "/ready": case "/rdy": HandleReady(__instance); SetChatFocusFalse(__instance); return false; case "/deal": case "/bd": case "/start": case "/begin": HandleStart(__instance); SetChatFocusFalse(__instance); return false; case "/bh": case "/hit": case "/h": HandleHit(__instance); SetChatFocusFalse(__instance); return false; case "/stay": case "/st": case "/stand": case "/bst": HandleStand(__instance); SetChatFocusFalse(__instance); return false; case "/lv": case "/bl": case "/leave": case "/l": 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 "/rc": case "/rcb": case "/rcl": case "/rclearbets": case "/rclear": HandleRClearBets(__instance); SetChatFocusFalse(__instance); return false; case "/rr": case "/rs": case "/rsb": case "/rready": case "/rstandby": HandleRReady(__instance); SetChatFocusFalse(__instance); return false; case "/rspin": case "/rsp": HandleRSpin(__instance); SetChatFocusFalse(__instance); return false; case "/rl": case "/rlv": case "/rleave": 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 "/skip": case "/next": case "/jn": case "/jf": case "/jnext": case "/fwd": if (!ArgumentEmptyOrAny(args, "song", "jukebox", "music")) { return true; } HandleForwardSong(); SetChatFocusFalse(__instance); return false; case "/prev": case "/back": case "/jprev": case "/rewind": case "/previous": if (!ArgumentEmptyOrAny(args, "song", "jukebox", "music")) { return true; } HandlePreviousSong(); SetChatFocusFalse(__instance); return false; case "/stop": case "/js": case "/jstop": if (!ArgumentEmptyOrAny(args, "jukebox", "song", "music")) { return true; } HandleStopJukebox(); SetChatFocusFalse(__instance); return false; case "/roomzonechat": case "/rzchat": case "/pzchat": case "/roomchat": case "/roomzone": case "/privatezonechat": HandleRoomZoneChat(__instance, args); SetChatFocusFalse(__instance); return false; case "/cui": case "/gameui": case "/casinoui": HandleCasinoUi(__instance, args); SetChatFocusFalse(__instance); return false; case "/ch": case "/chelp": case "/casinohelp": HandleCasinoHelp(__instance); SetChatFocusFalse(__instance); return false; case "/luck": case "/ownerluck": case "/casinocheat": 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); if (flag) { 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.AmHostFresh()) { blackjackTable.SetPlayerReady(num, flag); BJNetcode.BroadcastReadyChanged(((Object)blackjackTable).name, num, flag); bool allSeatedPlayersReady = blackjackTable.AllSeatedPlayersReady; SendLocalMessage(chat, $"[Casino] Seat {num + 1}: " + (flag ? "READY" : "not ready") + ". " + ReadyCountSummary(blackjackTable) + (allSeatedPlayersReady ? " All players ready — use /start to deal!" : "")); } else { BJNetcode.SendReadyToggleRequest(((Object)blackjackTable).name, flag); SendLocalMessage(chat, "[Casino] Ready " + (flag ? "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)) { string text = (((Object)(object)blackjackTable.GetHostPlayer() == (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.AmHostFresh()) { 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.AmHostFresh()) { 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.AmHostFresh()) { 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(); foreach (BlackjackTable blackjackTable in array) { int seatForPlayer = blackjackTable.GetSeatForPlayer(mainPlayer); if (seatForPlayer >= 0) { if (BJNetcode.AmHostFresh()) { 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 && rouletteTable.GetBetsForPlayer(Player._mainPlayer).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.AmHostFresh()) { 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); string text2 = RouletteLogic.FormatBetSummary(rouletteTable.GetBetsForPlayer(mainPlayer)); 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.AmHostFresh()) { 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); if (flag && rouletteTable.GetBetsForPlayer(mainPlayer).Count == 0) { SendLocalMessage(chat, "[Casino] Place at least one bet first. Use /rbet [number]."); return; } if (BJNetcode.AmHostFresh()) { if (!rouletteTable.TrySetReady(mainPlayer, flag, out string errorMsg)) { SendLocalMessage(chat, "[Casino] " + errorMsg); return; } RNNetcode.BroadcastReadyChanged(((Object)rouletteTable).name, BJNetcode.GetLocalSteam64(), flag); } else { RNNetcode.SendReadyToggleRequest(((Object)rouletteTable).name, flag); } int readyCount = rouletteTable.GetReadyCount(); int playerCount = rouletteTable.PlayerCount; bool flag2 = readyCount == playerCount && playerCount > 0; string text = (flag ? "READY" : "not ready"); string text2 = $"({readyCount}/{playerCount} players ready)"; string text3 = ((rouletteTable.IsTableHost(mainPlayer) && flag2) ? " All players ready — use /rspin to spin the wheel!" : ((rouletteTable.IsTableHost(mainPlayer) && flag) ? (" Waiting for other players. " + text2) : (flag ? (" 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.AmHostFresh()) { 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.AmHostFresh()) { 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)) { switch (text) { case "play": case "start": case "on": break; case "forward": case "next": case "skip": case "fwd": HandleForwardSong(); return; case "previous": case "prev": case "back": case "rewind": HandlePreviousSong(); return; case "stop": case "off": HandleStopJukebox(); return; default: SendLocalMessage(chat, "[Jukebox] Use /jukebox, /jukebox next, /jukebox prev, or /jukebox stop."); return; } } HandlePlayJukebox(); } private static void HandleRoomZoneChat(ChatBehaviour chat, string args) { string text = (args ?? string.Empty).Trim().ToLowerInvariant(); if (!string.IsNullOrEmpty(text)) { switch (text) { case "toggle": break; case "on": case "enable": case "enabled": case "true": case "1": goto IL_0079; case "off": case "disable": case "disabled": case "false": case "0": goto IL_00be; case "status": SendRoomZoneChatStatus(chat); return; default: SendLocalMessage(chat, "[Casino] Usage: /roomzonechat [on|off|status]"); return; } } bool value = !CasinoConfig.RoomZoneChatEnabled; goto IL_00e2; IL_0079: value = true; goto IL_00e2; IL_00be: value = false; goto IL_00e2; IL_00e2: 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 ? "Your outgoing Zone messages are private while you are inside a RoomZone. You still receive private messages from players in your current room." : "Your outgoing Zone messages use public vanilla routing. You still receive private messages from players in your current room."); SendLocalMessage(chat, "[Casino] Private zone chat is " + text + ". " + text2); } private static void HandleCasinoUi(ChatBehaviour chat, string args) { SendLocalMessage(chat, CasinoGameUI.RunCommand(args)); } 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) - Choose private or public outgoing Zone chat; private room messages are always received"); SendLocalMessage(chat, "/casinoui [open|close|toggle|status] (/cui, /gameui) - Open or close the blackjack/roulette UI"); 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(); if (!string.IsNullOrEmpty(text)) { switch (text) { case "toggle": break; case "on": case "enable": case "enabled": case "true": case "1": goto IL_0093; case "off": case "disable": case "disabled": case "false": case "0": goto IL_00db; case "status": { string text2 = (Plugin.OwnerLuckEnabled ? "ON" : "OFF"); SendLocalMessage(chat, "[Casino] Owner luck is " + text2 + "."); return; } default: SendLocalMessage(chat, "[Casino] Usage: /ownerluck [on|off|status]"); return; } } bool flag = !Plugin.OwnerLuckEnabled; goto IL_0125; IL_0125: Plugin.OwnerLuckEnabled = flag; if (BJNetcode.AmHostFresh()) { 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)); return; IL_00db: flag = false; goto IL_0125; IL_0093: flag = true; goto IL_0125; } private static bool IsGloballyAvailableCasinoCommand(string cmd) { switch (cmd) { case "/roomchat": case "/roomzone": case "/pzchat": case "/rzchat": case "/roomzonechat": case "/privatezonechat": case "/casinohelp": case "/chelp": case "/ownerluck": case "/casinocheat": return true; default: return false; } } 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; for (int i = 0; i < allowedBets.Length; i++) { if (allowedBets[i] == 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(); foreach (BlackjackTable blackjackTable in array) { int seatForPlayer = blackjackTable.GetSeatForPlayer(mainPlayer); if (seatForPlayer >= 0) { return (blackjackTable, seatForPlayer); } } return (null, -1); } private static bool IsLocalPlayerSeatedAtAnyBlackjackTable() { var (blackjackTable, num) = FindLocalSeat(); if ((Object)(object)blackjackTable != (Object)null) { return num >= 0; } return false; } private static RouletteTable? FindLocalRouletteTable() { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return null; } RouletteTable[] array = Object.FindObjectsOfType(); foreach (RouletteTable rouletteTable in array) { 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[] obj = new string[5] { "Init_ChatFocusFalse", "Set_ChatFocusFalse", "Close_Chat", "Deactivate_Chat", "Exit_Chat" }; Type type = ((object)chat).GetType(); string[] array = obj; foreach (string text in array) { 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 { private const float SMALL_PLAYER_SCALE_THRESHOLD = 0.05f; private const float PROXIMITY_POLL_INTERVAL_SEC = 0.1f; 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 static bool _sceneHooksInstalled; private BoxCollider? _collider; private bool _playerWasInside; private float _nextProximityPollTime; private void Awake() { _collider = ((Component)this).GetComponent(); EnsureSceneHooksInstalled(); Plugin.Log.LogInfo($"[EntryFee] Entry fee trigger active. Fee: {Plugin.EntryFeeCrowns} Crowns."); } private static void EnsureSceneHooksInstalled() { if (!_sceneHooksInstalled) { SceneManager.sceneUnloaded += OnSceneUnloaded; SceneManager.sceneLoaded += OnSceneLoaded; _sceneHooksInstalled = true; } } private static void OnSceneUnloaded(Scene scene) { if (!(((Scene)(ref scene)).name != "AtlyssCasino") && !HasLoadedCasinoScene()) { _paidPlayers.Clear(); _paymentPendingPlayers.Clear(); _pendingBrokeMessage = false; _isBooting = false; Plugin.Log.LogInfo("[EntryFee] Casino unloaded — paid list cleared."); } } private static bool HasLoadedCasinoScene() { //IL_0005: 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) for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); if (((Scene)(ref sceneAt)).IsValid() && ((Scene)(ref sceneAt)).isLoaded && ((Scene)(ref sceneAt)).name == "AtlyssCasino") { return true; } } return false; } 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_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_collider == (Object)null || _isBooting) { return; } if (!Plugin.IsLocalPlayerConfirmedInCasino()) { _playerWasInside = false; } else { if (Time.unscaledTime < _nextProximityPollTime) { return; } _nextProximityPollTime = Time.unscaledTime + 0.1f; 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)); } } } private IEnumerator VerifyAndChargeEntryFee(Player player, PlayerInventory inv, ulong steam64) { _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."); yield break; } int startingBalance = ReadCrowns(inv); int entryFee = Plugin.EntryFeeCrowns; int expectedBalance = startingBalance - entryFee; if (startingBalance < entryFee) { _paymentPendingPlayers.Remove(steam64); Plugin.ShowHUDError($"You need at least {entryFee} Crowns to enter!"); Plugin.Log.LogInfo($"[EntryFee] Player {steam64} had {startingBalance} Crowns " + "at verification time — insufficient. Sending back to Sanctum."); _isBooting = true; _pendingBrokeMessage = true; yield return ((MonoBehaviour)this).StartCoroutine(BootPlayerToSanctum(player)); yield break; } bool paid = false; int actualBalance = startingBalance; for (int attempt = 1; attempt <= 2; attempt++) { inv.Network_heldCurrency = expectedBalance; yield return null; yield return (object)new WaitForSeconds(0.05f); actualBalance = ReadCrowns(inv); if (actualBalance == expectedBalance) { paid = true; break; } Plugin.Log.LogWarning($"[EntryFee] Payment verification attempt {attempt} failed " + $"for {steam64}: started {startingBalance}, expected " + $"{expectedBalance}, actual {actualBalance}."); } _paymentPendingPlayers.Remove(steam64); if (paid) { _paidPlayers.Add(steam64); Plugin.ShowHUDInfo("Welcome to AtlyssCasino! " + $"{entryFee} Crown entry fee charged. Good luck!"); Plugin.Log.LogInfo($"[EntryFee] Player {steam64} paid {entryFee} Crowns " + $"(verified {startingBalance} -> {expectedBalance})."); yield break; } _paidPlayers.Remove(steam64); if (actualBalance != startingBalance) { inv.Network_heldCurrency = startingBalance; yield return null; } Plugin.ShowHUDError("Entry fee payment failed. Returning you to Sanctum."); Plugin.Log.LogWarning($"[EntryFee] Player {steam64} was NOT marked paid. " + $"Started {startingBalance}, expected {expectedBalance}, " + $"actual {actualBalance}. Sending back to Sanctum."); _isBooting = true; _pendingBrokeMessage = false; yield return ((MonoBehaviour)this).StartCoroutine(BootPlayerToSanctum(player)); } private static IEnumerator BootPlayerToSanctum(Player player) { yield return (object)new WaitForSeconds(1.5f); yield return ((MonoBehaviour)Plugin.Instance).StartCoroutine(CasinoExitTrigger.ForceReturnToSanctum(player, "[EntryFee]")); } private static IEnumerator ShowBrokeMessageDelayed() { yield return (object)new WaitForSeconds(3.5f); for (int i = 0; i < 3; i++) { try { Plugin.ShowHUDError("Come back when you're a little... mmm... richer!"); } catch { } yield return (object)new WaitForSeconds(3.8f); } Plugin.Log.LogInfo("[EntryFee] Broke message shown."); } private static void TeleportToStartPoint(Player player) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) GameObject[] array = GameObject.FindGameObjectsWithTag("spawnPoint"); foreach (GameObject val in array) { 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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0283: 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_0224: 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 value = field.GetValue(player); Transform val2 = (Transform)((value is Transform) ? value : null); if ((Object)(object)val2 == (Object)null) { GameObject val3 = (GameObject)((value is GameObject) ? value : null); if (val3 != null) { val2 = val3.transform; } } if ((Object)(object)val2 == (Object)null) { Component val4 = (Component)((value is Component) ? value : null); if (val4 != null) { val2 = val4.transform; } } if (!((Object)(object)val2 == (Object)null)) { Consider(Mathf.Abs(val2.localScale.y), "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)) { Consider(num2 / 0.011f, "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 num4, string source) { float num3 = Mathf.Abs(num4); if (!(num3 <= 0f) && !float.IsNaN(num3) && !float.IsInfinity(num3) && !(num3 >= minScale)) { minScale = num3; minSource = source; } } } } 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; } private const float InputCooldown = 0.5f; private const float ProximityPollInterval = 0.1f; private PortalKind _kind; private Transform? _target; private Collider[] _primitiveColliders = Array.Empty(); private Collider[] _fallbackColliders = Array.Empty(); private Renderer[] _fallbackRenderers = Array.Empty(); private string _label = "Portal"; private bool _playerNearby; private bool _isTransporting; private float _nextInputTime; private float _nextProximityPollTime; private static float _globalNextInputTime; private static int _cachedPlayerInstanceId; private static Collider[] _cachedPlayerColliders = Array.Empty(); internal bool IsUsableReturnPortal { get { if (_kind == PortalKind.ReturnToSanctum) { if (_primitiveColliders.Length == 0 && _fallbackColliders.Length == 0) { return _fallbackRenderers.Length != 0; } return true; } return false; } } internal static int HookScenePortals(GameObject[] allObjects) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); Dictionary dictionary2 = new Dictionary(StringComparer.OrdinalIgnoreCase); GameObject[] array = allObjects; foreach (GameObject val in array) { 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 { (((Component)value2.A).gameObject.GetComponent() ?? ((Component)value2.A).gameObject.AddComponent()).SetupLocal(value2.B, MakeLabel(key)); num++; (((Component)value2.B).gameObject.GetComponent() ?? ((Component)value2.B).gameObject.AddComponent()).SetupLocal(value2.A, MakeLabel(key)); num++; } } array = allObjects; foreach (GameObject val2 in array) { if ((Object)(object)val2 == (Object)null) { continue; } string text = ((Object)val2).name.Trim(); string id3; if (IsReturnToSanctumPortal(text)) { (val2.GetComponent() ?? val2.AddComponent()).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 { (val2.GetComponent() ?? val2.AddComponent()).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_00cd: 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) MakeRootPortalBoxesNonBlocking(); Collider[] componentsInChildren = ((Component)this).GetComponentsInChildren(true); List list = new List(); List list2 = new List(); Collider[] array = componentsInChildren; foreach (Collider val in array) { if (!((Object)(object)val == (Object)null) && val.enabled) { list2.Add(val); if (IsPrimitiveCollider(val)) { list.Add(val); } } } _primitiveColliders = list.ToArray(); _fallbackColliders = ((list.Count == 0) ? list2.ToArray() : Array.Empty()); _fallbackRenderers = ((list.Count == 0 && list2.Count == 0) ? ((Component)this).GetComponentsInChildren(true) : Array.Empty()); string text = (((Object)(object)_target == (Object)null) ? "sanctum" : (((Object)_target).name + " at " + FormatVector(_target.position))); Plugin.Log.LogInfo("[ManualPortal] Registered '" + ((Object)this).name + "' -> " + text + "; " + $"primitiveBounds={_primitiveColliders.Length}, " + $"fallbackColliderBounds={_fallbackColliders.Length}, " + $"fallbackRendererBounds={_fallbackRenderers.Length}, " + "position=" + FormatVector(((Component)this).transform.position) + "."); if (_primitiveColliders.Length == 0 && _fallbackColliders.Length == 0 && _fallbackRenderers.Length == 0) { Plugin.Log.LogWarning("[ManualPortal] '" + ((Object)this).name + "' has no usable collider or renderer bounds. It will not show prompts or teleport."); } } private void MakeRootPortalBoxesNonBlocking() { BoxCollider[] components = ((Component)this).GetComponents(); foreach (BoxCollider val in components) { if (!((Object)(object)val == (Object)null) && !((Collider)val).isTrigger) { ((Collider)val).isTrigger = true; } } } private static bool IsPrimitiveCollider(Collider collider) { if (!(collider is BoxCollider) && !(collider is SphereCollider) && !(collider is CapsuleCollider)) { return collider is CharacterController; } return true; } 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; } if (Time.unscaledTime >= _nextProximityPollTime) { _nextProximityPollTime = Time.unscaledTime + 0.1f; 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; ((MonoBehaviour)this).StartCoroutine(ReturnToSanctum(mainPlayer)); } else { TeleportLocal(mainPlayer); } } } catch (Exception ex) { Plugin.Log.LogWarning("[ManualPortal] '" + ((Object)this).name + "' update threw: " + ex.Message); } } private IEnumerator ReturnToSanctum(Player player) { try { yield return CasinoExitTrigger.ForceReturnToSanctum(player, "[ManualPortal/" + ((Object)this).name + "]"); } finally { _isTransporting = false; } } private void TeleportLocal(Player player) { //IL_003a: 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_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006b: 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_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_0048: 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_005e: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) Vector3 playerZonePosition = RoomZoneRegistry.GetPlayerZonePosition(player); if (TryGetPlayerBounds(player, out var bounds)) { if (IntersectsColliderBounds(_primitiveColliders, bounds, playerZonePosition)) { return true; } if (TryGetFallbackBounds(out var bounds2) && (((Bounds)(ref bounds2)).Intersects(bounds) || ((Bounds)(ref bounds2)).Contains(playerZonePosition))) { return true; } } if (ContainsPoint(_primitiveColliders, playerZonePosition)) { return true; } if (TryGetFallbackBounds(out var bounds3)) { return ((Bounds)(ref bounds3)).Contains(playerZonePosition); } return false; } private static bool IntersectsColliderBounds(Collider[] colliders, Bounds playerBounds, Vector3 probe) { //IL_001c: 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_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) foreach (Collider val in colliders) { if (!((Object)(object)val == (Object)null) && val.enabled) { Bounds bounds = val.bounds; if (((Bounds)(ref bounds)).Intersects(playerBounds) || ((Bounds)(ref bounds)).Contains(probe)) { return true; } } } return false; } private static bool ContainsPoint(Collider[] colliders, Vector3 point) { //IL_001c: 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_0024: Unknown result type (might be due to invalid IL or missing references) foreach (Collider val in colliders) { if ((Object)(object)val != (Object)null && val.enabled) { Bounds bounds = val.bounds; if (((Bounds)(ref bounds)).Contains(point)) { return true; } } } return false; } private bool TryGetFallbackBounds(out Bounds bounds) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) bounds = default(Bounds); bool found = false; Collider[] fallbackColliders = _fallbackColliders; foreach (Collider val in fallbackColliders) { if (!((Object)(object)val == (Object)null) && val.enabled) { Encapsulate(ref bounds, ref found, val.bounds); } } Renderer[] fallbackRenderers = _fallbackRenderers; foreach (Renderer val2 in fallbackRenderers) { if (!((Object)(object)val2 == (Object)null) && val2.enabled) { Encapsulate(ref bounds, ref found, val2.bounds); } } return found; } private static void Encapsulate(ref Bounds aggregate, ref bool found, Bounds value) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: 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) if (found) { ((Bounds)(ref aggregate)).Encapsulate(value); return; } aggregate = value; found = true; } private static bool TryGetPlayerBounds(Player player, out Bounds bounds) { //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_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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0075: 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 { int instanceID = ((Object)player).GetInstanceID(); if (_cachedPlayerInstanceId != instanceID || _cachedPlayerColliders.Length == 0) { _cachedPlayerInstanceId = instanceID; _cachedPlayerColliders = ((Component)player).GetComponentsInChildren(false); } bool flag = false; Collider[] cachedPlayerColliders = _cachedPlayerColliders; foreach (Collider val in cachedPlayerColliders) { if (!((Object)(object)val == (Object)null) && val.enabled) { if (flag) { ((Bounds)(ref bounds)).Encapsulate(val.bounds); continue; } bounds = val.bounds; flag = true; } } if (!flag) { _cachedPlayerColliders = Array.Empty(); } 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) { if (!string.Equals(objectName, "CasinoExitPortal", StringComparison.OrdinalIgnoreCase) && !string.Equals(objectName, "CasinoReturnPortal", StringComparison.OrdinalIgnoreCase) && !string.Equals(objectName, "ManualPortal_Sanctum", StringComparison.OrdinalIgnoreCase)) { return string.Equals(objectName, "ManualPortal_ReturnToSanctum", StringComparison.OrdinalIgnoreCase); } return true; } private static bool TryGetSourceId(string objectName, out string id) { if (!TryStripPrefix(objectName, "ManualPortal_", out id)) { return TryStripPrefix(objectName, "CasinoManualPortal_", out id); } return true; } private static bool TryGetTargetId(string objectName, out string id) { if (!TryStripPrefix(objectName, "ManualPortalTarget_", out id)) { return TryStripPrefix(objectName, "CasinoManualPortalTarget_", out id); } return true; } 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_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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) return $"({value.x:F2}, {value.y:F2}, {value.z:F2})"; } } public static class CasinoPatch { private sealed class SetupPassResult { internal int Slots; internal int ReadySlots; internal int Blackjack; internal int ReadyBlackjack; internal int Roulette; internal int ReadyRoulette; internal bool EntryFeeHooked; internal int ManualPortals; internal int PortalSigns; internal bool ExitReady; internal int Jukeboxes; internal int RoomZones; internal bool PrivateRoomReady; internal bool IsFullyReady { get { if (Slots >= 1 && ReadySlots == Slots && Blackjack >= 1 && ReadyBlackjack == Blackjack && Roulette >= 1 && ReadyRoulette == Roulette && EntryFeeHooked && ExitReady && Jukeboxes > 0 && RoomZones > 0) { return PrivateRoomReady; } return false; } } internal string ReadinessSignature => $"{Slots}:{ReadySlots}|" + $"{Blackjack}:{ReadyBlackjack}|" + $"{Roulette}:{ReadyRoulette}|" + $"{EntryFeeHooked}|{ManualPortals}|{PortalSigns}|" + $"{ExitReady}|{Jukeboxes}|{RoomZones}|{PrivateRoomReady}"; internal string Describe() { return $"slots={ReadySlots}/{Slots}, " + $"blackjack={ReadyBlackjack}/{Blackjack}, " + $"roulette={ReadyRoulette}/{Roulette}, " + $"entryFee={EntryFeeHooked}, " + $"manualPortals={ManualPortals}, " + $"exitReady={ExitReady}, " + $"portalSigns={PortalSigns}, " + $"jukeboxes={Jukeboxes}, " + $"roomZones={RoomZones}, " + $"privateRoomReady={PrivateRoomReady}"; } } private const int MAX_RETRIES = 15; private const int REQUIRED_READY_PASSES = 3; private const float RETRY_INTERVAL_SEC = 1f; private const float RECOVERY_RETRY_INITIAL_SEC = 5f; private const float RECOVERY_RETRY_MAX_SEC = 60f; 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 _initialized; private static readonly Dictionary SetupCoroutines = new Dictionary(); private static readonly HashSet CompletedSetupSceneHandles = new HashSet(); private static readonly Dictionary SharedSlotMaterials = new Dictionary(StringComparer.Ordinal); public static void Init() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) if (_initialized) { return; } SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; _initialized = true; for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); if (((Scene)(ref sceneAt)).IsValid() && ((Scene)(ref sceneAt)).isLoaded && ((Scene)(ref sceneAt)).name == "AtlyssCasino") { OnSceneLoaded(sceneAt, (LoadSceneMode)1); } } } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (((Scene)(ref scene)).name != "AtlyssCasino") { return; } Plugin.Log.LogInfo("[Casino] Casino scene loaded — starting setup retry loop."); BJNetcode.SendCasinoConfigRequest(); if (SetupCoroutines.ContainsKey(((Scene)(ref scene)).handle) || CompletedSetupSceneHandles.Contains(((Scene)(ref scene)).handle)) { Plugin.Log.LogWarning("[Casino] Setup already running or complete for scene handle " + $"{((Scene)(ref scene)).handle} — skipping duplicate event."); return; } try { Coroutine value = ((MonoBehaviour)Plugin.Instance).StartCoroutine(SetupWithRetry(((Scene)(ref scene)).handle)); SetupCoroutines[((Scene)(ref scene)).handle] = value; } catch (Exception arg) { Plugin.Log.LogError("[Casino] Failed to start setup for scene handle " + $"{((Scene)(ref scene)).handle}: {arg}"); } } private static void OnSceneUnloaded(Scene scene) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) if (!(((Scene)(ref scene)).name != "AtlyssCasino")) { CancelSetupCoroutine(((Scene)(ref scene)).handle); CompletedSetupSceneHandles.Remove(((Scene)(ref scene)).handle); if (CasinoRuntimeActivity.TryGetActiveCasinoScene(out var scene2) || TryGetAnyLoadedCasinoScene(out scene2)) { RebindSceneRegistries(scene2); return; } CasinoExitTrigger.ResetTransportState(); ClearSceneRegistries(); } } private static void CancelSetupCoroutine(int sceneHandle) { if (SetupCoroutines.TryGetValue(sceneHandle, out Coroutine value) && value != null && (Object)(object)Plugin.Instance != (Object)null) { try { ((MonoBehaviour)Plugin.Instance).StopCoroutine(value); } catch { } } SetupCoroutines.Remove(sceneHandle); } private static void CompleteSetupCoroutine(int sceneHandle) { SetupCoroutines.Remove(sceneHandle); } private static bool TryGetSetupScene(int expectedHandle, out Scene scene) { //IL_0005: 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_0051: 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_003d: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); if (((Scene)(ref sceneAt)).handle == expectedHandle) { if (!((Scene)(ref sceneAt)).IsValid() || !((Scene)(ref sceneAt)).isLoaded || !string.Equals(((Scene)(ref sceneAt)).name, "AtlyssCasino", StringComparison.Ordinal)) { break; } scene = sceneAt; return true; } } scene = default(Scene); return false; } private static IEnumerator SetupWithRetry(int expectedSceneHandle) { if (!Plugin.IsHeadlessServer) { try { EnsureOverlayShader(); } catch (Exception ex) { Plugin.Log.LogWarning("[Casino] Overlay shader setup failed without stopping gameplay repair: " + ex.Message); } } CasinoLog log = Plugin.Log; Shader? overlayShader = _overlayShader; log.LogInfo("[Casino] Overlay shader: " + (((overlayShader != null) ? ((Object)overlayShader).name : null) ?? "NULL")); yield return null; yield return null; int burstAttempt = 0; int totalAttempt = 0; int consecutiveReadyPasses = 0; float recoveryDelay = 5f; string previousReadySignature = string.Empty; Scene scene; SetupPassResult setupPassResult; while (true) { if (!TryGetSetupScene(expectedSceneHandle, out scene)) { Plugin.Log.LogInfo("[Casino] Setup cancelled because its casino scene unloaded."); CompleteSetupCoroutine(expectedSceneHandle); yield break; } burstAttempt++; totalAttempt++; Plugin.Log.LogDebug($"[Casino] Setup repair pass {burstAttempt}/{15} " + $"(total {totalAttempt})..."); setupPassResult = null; try { setupPassResult = RunSetupPass(CollectSceneObjects(scene)); RoomZoneRegistry.RefreshScene(scene); setupPassResult.RoomZones = RoomZoneRegistry.CountRegisteredZones(scene); setupPassResult.PrivateRoomReady = RoomZoneRegistry.HasExpectedPrivateRoom(scene); } catch (Exception arg) { Plugin.Log.LogError($"[Casino] Setup repair pass {totalAttempt} threw; " + $"the same scene will be retried safely: {arg}"); } if (setupPassResult != null && setupPassResult.IsFullyReady) { if (string.Equals(previousReadySignature, setupPassResult.ReadinessSignature, StringComparison.Ordinal)) { consecutiveReadyPasses++; } else { previousReadySignature = setupPassResult.ReadinessSignature; consecutiveReadyPasses = 1; } if (consecutiveReadyPasses >= 3) { break; } } else { consecutiveReadyPasses = 0; previousReadySignature = string.Empty; } if (setupPassResult != null) { Plugin.Log.LogDebug("[Casino] Setup repair incomplete: " + setupPassResult.Describe() + "; " + $"stableReadyPasses={consecutiveReadyPasses}/" + $"{3}."); } if (burstAttempt >= 15) { Plugin.Log.LogWarning("[Casino] Setup is still incomplete after " + $"{15} repair passes. The loaded scene will " + $"remain recoverable and retry in {recoveryDelay:F0}s."); yield return (object)new WaitForSecondsRealtime(recoveryDelay); burstAttempt = 0; consecutiveReadyPasses = 0; previousReadySignature = string.Empty; recoveryDelay = Mathf.Min(recoveryDelay * 2f, 60f); } else { yield return (object)new WaitForSecondsRealtime(1f); } } Plugin.LogAlwaysInfo($"[Casino] Setup SUCCEEDED after {totalAttempt} " + "repair pass(es) — " + setupPassResult.Describe() + "."); CasinoPerformanceOptimizer.ApplyToCasinoScene(scene); CasinoRuntimeActivity.RefreshSceneCache(scene); RoomZoneRegistry.RefreshScene(scene); BJNetcode.BroadcastCasinoConfigSync(); if (!Plugin.IsHeadlessServer && CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino) { EnsureLocalPresentation(scene); } CompletedSetupSceneHandles.Add(expectedSceneHandle); CompleteSetupCoroutine(expectedSceneHandle); } private static SetupPassResult RunSetupPass(GameObject[] allObjects) { int readyMachineCount; int readyTableCount; int readyTableCount2; SetupPassResult obj = new SetupPassResult { Slots = HookSlotMachines(allObjects, out readyMachineCount), ReadySlots = readyMachineCount, Blackjack = HookBlackjackTables(allObjects, out readyTableCount), ReadyBlackjack = readyTableCount, Roulette = HookRouletteTables(allObjects, out readyTableCount2), ReadyRoulette = readyTableCount2, EntryFeeHooked = HookEntryFee(allObjects), ManualPortals = CasinoManualPortal.HookScenePortals(allObjects), PortalSigns = CasinoPortalSigns.Ensure(allObjects) }; obj.ExitReady = HookExitTrigger(obj.ManualPortals, allObjects); obj.Jukeboxes = HookJukeboxes(allObjects); RebindPreferredSceneRegistries(); return obj; } private static void ClearSceneRegistries() { BJNetcode.ClearSceneRegistries(); RNNetcode.ClearSceneRegistry(); CasinoJukebox.ClearRegistry(); } private static void RebindPreferredSceneRegistries() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (CasinoRuntimeActivity.TryGetActiveCasinoScene(out var scene) || TryGetAnyLoadedCasinoScene(out scene)) { RebindSceneRegistries(scene); } else { ClearSceneRegistries(); } } private static bool TryGetAnyLoadedCasinoScene(out Scene scene) { //IL_0005: 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_0046: 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_0032: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); if (((Scene)(ref sceneAt)).IsValid() && ((Scene)(ref sceneAt)).isLoaded && !(((Scene)(ref sceneAt)).name != "AtlyssCasino")) { scene = sceneAt; return true; } } scene = default(Scene); return false; } internal static void RebindSceneRegistries(Scene scene) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded || ((Scene)(ref scene)).name != "AtlyssCasino") { return; } ClearSceneRegistries(); int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; GameObject[] array = CollectSceneObjects(scene); foreach (GameObject val in array) { if (!((Object)(object)val == (Object)null)) { SlotMachine component = val.GetComponent(); if ((Object)(object)component != (Object)null) { BJNetcode.RegisterSlotMachine(component); num++; } BlackjackTable component2 = val.GetComponent(); if ((Object)(object)component2 != (Object)null) { BJNetcode.RegisterBlackjackTable(component2); num2++; } RouletteTable component3 = val.GetComponent(); if ((Object)(object)component3 != (Object)null) { RNNetcode.RegisterTable(component3); num3++; } CasinoJukebox component4 = val.GetComponent(); if ((Object)(object)component4 != (Object)null) { component4.Setup(); num4++; } } } Plugin.Log.LogDebug($"[Casino] Rebound registries to scene handle {((Scene)(ref scene)).handle}: " + $"slots={num}, blackjack={num2}, roulette={num3}, " + $"jukeboxes={num4}."); } private static GameObject[] CollectSceneObjects(Scene scene) { List list = new List(256); 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) { list.Add(((Component)val2).gameObject); } } } return list.ToArray(); } private static int HookSlotMachines(GameObject[] allObjects, out int readyMachineCount) { int num = 0; readyMachineCount = 0; HashSet hashSet = new HashSet(); 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) && hashSet.Add(((Object)val2).GetInstanceID())) { SlotMachine slotMachine = val2.GetComponent(); if ((Object)(object)slotMachine == (Object)null && !((Object)val2).name.StartsWith("SlotMachine_", StringComparison.Ordinal)) { ((Object)val2).name = "SlotMachine_" + BuildStableObjectId(val2.transform); } if (slotMachine == null) { slotMachine = val2.AddComponent(); } SlotMachine slotMachine2 = slotMachine; slotMachine2.Init(); BJNetcode.RegisterSlotMachine(slotMachine2); (val.GetComponent() ?? val.AddComponent()).Setup(slotMachine2); num++; readyMachineCount++; 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 {readyMachineCount}/{num} " + "slot machine interaction(s)."); return num; } private static int HookBlackjackTables(GameObject[] allObjects, out int readyTableCount) { int num = 0; readyTableCount = 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++; BlackjackTable blackjackTable = val.GetComponent() ?? val.AddComponent(); AttachTableInfo(blackjackTable); BJNetcode.RegisterBlackjackTable(blackjackTable); int num5 = 0; int num6 = 0; bool flag = false; Dictionary dictionary = new Dictionary(); Dictionary dictionary2 = new Dictionary(); Transform[] componentsInChildren = val.GetComponentsInChildren(true); foreach (Transform val2 in componentsInChildren) { if ((Object)(object)val2 == (Object)(object)val.transform) { continue; } string text = ((Object)val2).name.Trim(); if (text == "DealerAnchor") { blackjackTable.SetDealerAnchor(val2); flag = true; num5++; continue; } int num7 = ParseIndexedName(text, "SeatAnchor"); if (num7 >= 0) { if (num7 >= 5) { Plugin.Log.LogWarning($"[Casino] '{text}' index {num7} " + ">= MAX_PLAYERS; skipping."); continue; } blackjackTable.SetSeatAnchor(num7, val2); num6++; num5++; continue; } int num8 = ResolveSeatTriggerIndex(val2); if (num8 >= 0) { if (num8 >= 5) { Plugin.Log.LogWarning($"[Casino] '{text}' index {num8} " + ">= MAX_PLAYERS; skipping."); continue; } BlackjackSeatTrigger blackjackSeatTrigger = ((Component)val2).gameObject.GetComponent() ?? ((Component)val2).gameObject.AddComponent(); blackjackSeatTrigger.Setup(blackjackTable, num8); dictionary[num8] = blackjackSeatTrigger; continue; } int num9 = ParseIndexedName(text, "Stool"); if (num9 >= 0) { if (num9 >= 5) { Plugin.Log.LogWarning($"[Casino] '{text}' index {num9} " + ">= MAX_PLAYERS; skipping."); } else { dictionary2[num9] = val2; } } } int count = dictionary.Count; int num10 = 0; foreach (KeyValuePair item in dictionary) { if (dictionary2.TryGetValue(item.Key, out var value)) { item.Value.SetStool(value); num10++; } else { Plugin.Log.LogInfo($"[Casino] No Stool with index {item.Key} on " + "'" + ((Object)val).name + "' — seat color won't change."); } } if (flag && num6 == 5 && count == 5) { readyTableCount++; } else { Plugin.Log.LogWarning("[Casino] Blackjack table '" + ((Object)val).name + "' is not fully " + $"interactive yet: dealerAnchor={flag}, " + $"seatAnchors={num6}/" + $"{5}, triggers=" + $"{count}/{5}."); } Plugin.Log.LogInfo("[Casino] Hooked Blackjack table '" + ((Object)val).name + "' — " + $"{count} trigger(s), " + $"{num5} anchor(s), " + $"{num10} stool(s)."); num2 += count; num3 += num5; num4 += num10; } Plugin.Log.LogInfo($"[Casino] Hooked {readyTableCount}/{num} ready " + "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); foreach (Transform val in componentsInChildren) { 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, out int readyTableCount) { //IL_0106: Unknown result type (might be due to invalid IL or missing references) int num = 0; readyTableCount = 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"))) { num++; RouletteTable rouletteTable = val.GetComponent() ?? val.AddComponent(); AttachTableInfo(rouletteTable); RNNetcode.RegisterTable(rouletteTable); 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; } (((Component)val2).gameObject.GetComponent() ?? ((Component)val2).gameObject.AddComponent()).Setup(rouletteTable); readyTableCount++; Plugin.Log.LogInfo("[Casino] Hooked roulette table '" + ((Object)val).name + "' " + $"with trigger at {val2.position}."); } } Plugin.Log.LogInfo($"[Casino] Hooked {readyTableCount}/{num} ready " + "roulette table(s)."); return num; } private static void AttachTableInfo(BlackjackTable table) { (((Component)table).gameObject.GetComponent() ?? ((Component)table).gameObject.AddComponent()).Setup(table); } private static void AttachTableInfo(RouletteTable table) { (((Component)table).gameObject.GetComponent() ?? ((Component)table).gameObject.AddComponent()).Setup(table); } private static bool HookEntryFee(GameObject[] allObjects) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) GameObject val = null; foreach (GameObject val2 in allObjects) { if (!((Object)(object)val2 == (Object)null) && !(((Object)val2).name != "EntryFeeTrigger")) { val = val2; break; } } if ((Object)(object)val == (Object)null) { return false; } if ((Object)(object)val.GetComponent() == (Object)null) { val.AddComponent(); Plugin.Log.LogInfo("[Casino] Entry fee trigger hooked at " + $"{val.transform.position}."); } return true; } private static bool HookExitTrigger(int manualPortalCount, GameObject[] allObjects) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) try { foreach (GameObject val in allObjects) { if (!((Object)(object)val == (Object)null)) { Component component = val.GetComponent("Portal"); if (!((Object)(object)component == (Object)null)) { Plugin.Log.LogInfo("[Casino] Vanilla Portal found at " + $"{component.transform.position} on '{((Object)component.gameObject).name}'. " + "F-key handling for clients runs from the cached runtime coordinator."); return true; } } } } catch (Exception ex) { Plugin.Log.LogWarning("[Casino] Portal scan threw: " + ex.Message); return false; } if (HasUsableManualReturnPortal(allObjects)) { Plugin.Log.LogInfo("[Casino] No vanilla Portal component found; using a usable manual return-to-Sanctum portal instead."); return true; } 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. " + $"Interior manual portals currently hooked: {manualPortalCount}."); return false; } private static bool HasUsableManualReturnPortal(GameObject[] allObjects) { foreach (GameObject val in allObjects) { if (!((Object)(object)val == (Object)null)) { CasinoManualPortal component = val.GetComponent(); if ((Object)(object)component != (Object)null && component.IsUsableReturnPortal) { return true; } } } return false; } 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)) { (val.GetComponent() ?? val.AddComponent()).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 string BuildStableObjectId(Transform transform) { List list = new List(); Transform val = transform; while ((Object)(object)val != (Object)null) { list.Add(val); val = val.parent; } uint num = 2166136261u; for (int num2 = list.Count - 1; num2 >= 0; num2--) { Transform val2 = list[num2]; string text = ((Object)val2).name + "@" + $"{val2.GetSiblingIndex()}"; for (int i = 0; i < text.Length; i++) { num ^= text[i]; num *= 16777619; } num ^= 0x2F; num *= 16777619; } return num.ToString("X8"); } 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; } if (!int.TryParse(name.Substring(length, num), 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; } internal static void EnsureLocalPresentation(Scene scene) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) if (Plugin.IsHeadlessServer || !CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino || !((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded || ((Scene)(ref scene)).name != "AtlyssCasino" || !Plugin.EnsurePresentationAssetsLoaded()) { return; } EnsureOverlayShader(); if (!CasinoRuntimeActivity.TryGetCachedSceneObjects(((Scene)(ref scene)).handle, out GameObject[] objects)) { objects = CollectSceneObjects(scene); } foreach (GameObject val in objects) { if (!((Object)(object)val == (Object)null)) { SlotMachine component = val.GetComponent(); if (!((Object)(object)component == (Object)null)) { AssignSlotMaterials(component); component.EnsurePresentationInitialized(); component.ApplyPerformanceSettings(); } } } CasinoJukebox.AutoStartAll(); } private static void EnsureOverlayShader() { if (!((Object)(object)_overlayShader != (Object)null)) { _overlayShader = Shader.Find("Unlit/Texture"); if ((Object)(object)_overlayShader == (Object)null) { _overlayShader = Shader.Find("Standard"); } } } private static void AssignSlotMaterials(SlotMachine slot) { slot.MatIdle = MakeMat("idle"); slot.MatIdleBlank = MakeMat("idle_blank"); slot.MatWin = MakeMat("win"); slot.MatJackpot = MakeMat("jackpot"); slot.MatCherry = MakeMat("cherry"); slot.MatLemon = MakeMat("lemon"); slot.MatOrange = MakeMat("orange"); slot.MatStar = MakeMat("star"); slot.MatDiamond = MakeMat("diamond"); slot.MatSeven = MakeMat("seven"); } private static Material? MakeMat(string textureName) { //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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) if (SharedSlotMaterials.TryGetValue(textureName, out Material value) && (Object)(object)value != (Object)null) { return value; } 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; SharedSlotMaterials[textureName] = val2; return val2; } } internal static class CasinoPerformanceOptimizer { private sealed class RendererVisibilityState { internal readonly Renderer Component; internal readonly bool ForceRenderingOff; internal RendererVisibilityState(Renderer component) { Component = component; ForceRenderingOff = component.forceRenderingOff; } } private sealed class LightVisibilityState { internal readonly Light Component; internal readonly bool Enabled; internal LightVisibilityState(Light component) { Component = component; Enabled = ((Behaviour)component).enabled; } } private static readonly Dictionary HiddenDecorativeRenderers = new Dictionary(); private static readonly Dictionary HiddenDecorativeLights = new Dictionary(); internal static void ApplyToCasinoScene(Scene scene) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) PruneDestroyedState(); if (!Plugin.IsHeadlessServer && ((Scene)(ref scene)).IsValid() && ((Scene)(ref scene)).isLoaded && !(((Scene)(ref scene)).name != "AtlyssCasino")) { int num = ApplyDecorativeVisibility(scene, Plugin.HideDecorativeProps); if (num > 0) { Plugin.Log.LogInfo(Plugin.HideDecorativeProps ? ($"[CasinoPerf] Culled {num} decorative " + "presentation component(s).") : ($"[CasinoPerf] Restored {num} decorative " + "presentation component(s).")); } int num2 = ApplySlotVisualMode(scene); if (Plugin.ReduceIdleSlotVisuals) { Plugin.Log.LogInfo("[CasinoPerf] Applied idle slot screen reduction to " + $"{num2} slot machine(s)."); } } } internal static void CleanupScene(int sceneHandle) { PruneDestroyedState(); RestoreDecorativeVisibility(sceneHandle); } private static int ApplyDecorativeVisibility(Scene scene, bool hide) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) if (!hide) { return RestoreDecorativeVisibility(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) { continue; } try { Renderer[] componentsInChildren2 = ((Component)val3).GetComponentsInChildren(true); foreach (Renderer val4 in componentsInChildren2) { if (!((Object)(object)val4 == (Object)null) && !val4.forceRenderingOff) { int instanceID = ((Object)val4).GetInstanceID(); if (!HiddenDecorativeRenderers.ContainsKey(instanceID)) { HiddenDecorativeRenderers[instanceID] = new RendererVisibilityState(val4); } val4.forceRenderingOff = true; num++; } } Light[] componentsInChildren3 = ((Component)val3).GetComponentsInChildren(true); foreach (Light val5 in componentsInChildren3) { if (!((Object)(object)val5 == (Object)null) && ((Behaviour)val5).enabled) { int instanceID2 = ((Object)val5).GetInstanceID(); if (!HiddenDecorativeLights.ContainsKey(instanceID2)) { HiddenDecorativeLights[instanceID2] = new LightVisibilityState(val5); } ((Behaviour)val5).enabled = false; num++; } } } catch (Exception ex) { Plugin.Log.LogWarning("[CasinoPerf] Failed to cull decorative presentation '" + ((Object)val3).name + "': " + ex.Message); } } return num; } private static int RestoreDecorativeVisibility(Scene scene) { return RestoreDecorativeVisibility(((Scene)(ref scene)).handle); } private static int RestoreDecorativeVisibility(int sceneHandle) { //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_010c: 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) int num = 0; List list = new List(); Scene scene; foreach (KeyValuePair hiddenDecorativeRenderer in HiddenDecorativeRenderers) { Renderer component = hiddenDecorativeRenderer.Value.Component; if ((Object)(object)component == (Object)null) { list.Add(hiddenDecorativeRenderer.Key); continue; } scene = ((Component)component).gameObject.scene; if (((Scene)(ref scene)).handle == sceneHandle) { component.forceRenderingOff = hiddenDecorativeRenderer.Value.ForceRenderingOff; list.Add(hiddenDecorativeRenderer.Key); num++; } } for (int i = 0; i < list.Count; i++) { HiddenDecorativeRenderers.Remove(list[i]); } list.Clear(); foreach (KeyValuePair hiddenDecorativeLight in HiddenDecorativeLights) { Light component2 = hiddenDecorativeLight.Value.Component; if ((Object)(object)component2 == (Object)null) { list.Add(hiddenDecorativeLight.Key); continue; } scene = ((Component)component2).gameObject.scene; if (((Scene)(ref scene)).handle == sceneHandle) { ((Behaviour)component2).enabled = hiddenDecorativeLight.Value.Enabled; list.Add(hiddenDecorativeLight.Key); num++; } } for (int j = 0; j < list.Count; j++) { HiddenDecorativeLights.Remove(list[j]); } return num; } private static void PruneDestroyedState() { List list = new List(); foreach (KeyValuePair hiddenDecorativeRenderer in HiddenDecorativeRenderers) { if ((Object)(object)hiddenDecorativeRenderer.Value.Component == (Object)null) { list.Add(hiddenDecorativeRenderer.Key); } } for (int i = 0; i < list.Count; i++) { HiddenDecorativeRenderers.Remove(list[i]); } list.Clear(); foreach (KeyValuePair hiddenDecorativeLight in HiddenDecorativeLights) { if ((Object)(object)hiddenDecorativeLight.Value.Component == (Object)null) { list.Add(hiddenDecorativeLight.Key); } } for (int j = 0; j < list.Count; j++) { HiddenDecorativeLights.Remove(list[j]); } } 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_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Invalid comparison between Unknown and I4 if (normalizedName.StartsWith("spotlight")) { return true; } Light component = go.GetComponent(); if ((Object)(object)component != (Object)null) { return (int)component.type == 0; } return false; } 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 num2 = casinoTeleportSignDisplay.SignKey == spec.Key && casinoTeleportSignDisplay.HasTextTarget; casinoTeleportSignDisplay.Setup(spec.Key, spec.Text, spec.CharacterSize); num++; if (!num2) { 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; } } exactNames = SignPrefixes; for (int j = 0; j < exactNames.Length; j++) { string text2 = NormalizeKey(exactNames[j]); if (!text.StartsWith(text2, StringComparison.Ordinal)) { continue; } string text3 = text.Substring(text2.Length); string[] aliases = signSpec.Aliases; foreach (string value2 in aliases) { if (text3 == NormalizeKey(value2)) { 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; } if (!int.TryParse(value.Substring(num + 1, num2 - num - 1), out var _)) { return value; } return value.Substring(0, num); } } 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); if ((Object)(object)_textMesh != (Object)null) { ((Behaviour)this).enabled = false; } } 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); ((Behaviour)this).enabled = 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_0045: 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); foreach (Component val in componentsInChildren) { 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_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected O, but got Unknown //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_010d: 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; if ((Object)(object)sharedMaterial != (Object)null && ((Object)sharedMaterial).name.StartsWith("CasinoPortalSign_TextMesh_DepthTested", StringComparison.Ordinal)) { SetDepthTestProperties(sharedMaterial); return; } 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.sharedMaterial = 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_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: 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; } } internal sealed class CasinoRuntimeActivity : MonoBehaviour { private enum PresenceState { Unknown, Inside, Outside } private sealed class ScenePresentationCache { internal readonly Scene Scene; internal readonly GameObject[] SceneObjects; internal readonly Component[] MapInstances; internal readonly Transform? ExitPortal; internal readonly RendererState[] Renderers; internal readonly LightState[] Lights; internal readonly CameraState[] Cameras; internal readonly AudioState[] AudioSources; internal readonly ParticleState[] Particles; private bool _outsideApplied; internal int SceneHandle { get { //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) Scene scene = Scene; return ((Scene)(ref scene)).handle; } } internal ScenePresentationCache(Scene scene, GameObject[] sceneObjects, Component[] mapInstances, Transform? exitPortal, RendererState[] renderers, LightState[] lights, CameraState[] cameras, AudioState[] audioSources, ParticleState[] particles) { //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) Scene = scene; SceneObjects = sceneObjects; MapInstances = mapInstances; ExitPortal = exitPortal; Renderers = renderers; Lights = lights; Cameras = cameras; AudioSources = audioSources; Particles = particles; } internal void ApplyOutside() { if (!_outsideApplied) { for (int i = 0; i < Renderers.Length; i++) { Renderers[i].HideForOutside(); } for (int j = 0; j < Lights.Length; j++) { Lights[j].DisableForOutside(); } for (int k = 0; k < Cameras.Length; k++) { Cameras[k].DisableForOutside(); } for (int l = 0; l < AudioSources.Length; l++) { AudioSources[l].MuteForOutside(); } for (int m = 0; m < Particles.Length; m++) { Particles[m].PauseForOutside(); } _outsideApplied = true; } } internal void Restore() { for (int i = 0; i < Renderers.Length; i++) { Renderers[i].Restore(); } for (int j = 0; j < Lights.Length; j++) { Lights[j].Restore(); } for (int k = 0; k < Cameras.Length; k++) { Cameras[k].Restore(); } for (int l = 0; l < AudioSources.Length; l++) { AudioSources[l].Restore(); } for (int m = 0; m < Particles.Length; m++) { Particles[m].Restore(); } _outsideApplied = false; } } private sealed class RendererState { internal readonly Renderer Component; private bool _managed; private bool _restoreForceRenderingOff; internal RendererState(Renderer component) { Component = component; } internal void HideForOutside() { if ((Object)(object)Component == (Object)null) { return; } if (!_managed) { if (Component.forceRenderingOff) { return; } _restoreForceRenderingOff = Component.forceRenderingOff; _managed = true; } Component.forceRenderingOff = true; } internal void Restore() { if (!((Object)(object)Component == (Object)null) && _managed) { Component.forceRenderingOff = _restoreForceRenderingOff; _managed = false; } } } private sealed class LightState { internal readonly Light Component; private bool _managed; private bool _restoreEnabled; private float _restoreRange; private LightRenderMode _restoreRenderMode; internal bool Enabled { get { if (!_managed) { if ((Object)(object)Component != (Object)null) { return ((Behaviour)Component).enabled; } return false; } return _restoreEnabled; } } internal float Range { get { if (!_managed) { if (!((Object)(object)Component != (Object)null)) { return 0f; } return Component.range; } return _restoreRange; } } internal LightRenderMode RenderMode { get { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (!_managed) { if (!((Object)(object)Component != (Object)null)) { return (LightRenderMode)0; } return Component.renderMode; } return _restoreRenderMode; } } internal LightState(Light component) { Component = component; } private void BeginManagedChange() { //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) if (!_managed && !((Object)(object)Component == (Object)null)) { _restoreEnabled = ((Behaviour)Component).enabled; _restoreRange = Component.range; _restoreRenderMode = Component.renderMode; _managed = true; } } internal void ApplyManaged(bool enabled, float range, LightRenderMode renderMode) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Component == (Object)null)) { BeginManagedChange(); Component.range = range; Component.renderMode = renderMode; ((Behaviour)Component).enabled = enabled; } } internal void DisableForOutside() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) ApplyManaged(enabled: false, Range, RenderMode); } internal void Restore() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Component == (Object)null) && _managed) { Component.range = _restoreRange; Component.renderMode = _restoreRenderMode; ((Behaviour)Component).enabled = _restoreEnabled; _managed = false; } } } private sealed class CameraState { internal readonly Camera Component; private bool _managed; private bool _restoreEnabled; internal CameraState(Camera component) { Component = component; } internal void DisableForOutside() { if ((Object)(object)Component == (Object)null) { return; } if (!_managed) { if (!((Behaviour)Component).enabled) { return; } _restoreEnabled = ((Behaviour)Component).enabled; _managed = true; } ((Behaviour)Component).enabled = false; } internal void Restore() { if (!((Object)(object)Component == (Object)null) && _managed) { ((Behaviour)Component).enabled = _restoreEnabled; _managed = false; } } } private sealed class AudioState { internal readonly AudioSource Component; private bool _managed; private bool _restoreMute; internal AudioState(AudioSource component) { Component = component; } internal void MuteForOutside() { if ((Object)(object)Component == (Object)null) { return; } if (!_managed) { if (Component.mute) { return; } _restoreMute = Component.mute; _managed = true; } Component.mute = true; } internal void Restore() { if (!((Object)(object)Component == (Object)null) && _managed) { Component.mute = _restoreMute; _managed = false; } } } private sealed class ParticleState { private static readonly object[] WithChildren = new object[1] { true }; private readonly Component _component; private readonly PropertyInfo? _isPlaying; private readonly MethodInfo? _pause; private readonly MethodInfo? _play; private bool _resumeOnRestore; private ParticleState(Component component, PropertyInfo? isPlaying, MethodInfo? pause, MethodInfo? play) { _component = component; _isPlaying = isPlaying; _pause = pause; _play = play; } internal static ParticleState? TryCreate(Component component) { try { Type type = ((object)component).GetType(); return new ParticleState(component, type.GetProperty("isPlaying", InstanceFields), type.GetMethod("Pause", new Type[1] { typeof(bool) }), type.GetMethod("Play", new Type[1] { typeof(bool) })); } catch { return null; } } internal void PauseForOutside() { if ((Object)(object)_component == (Object)null) { return; } try { object obj = _isPlaying?.GetValue(_component); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { _resumeOnRestore = true; _pause?.Invoke(_component, WithChildren); } } catch { } } internal void Restore() { if ((Object)(object)_component == (Object)null) { return; } try { if (_resumeOnRestore) { _play?.Invoke(_component, WithChildren); } } catch { } _resumeOnRestore = false; } } private const float SafetyCheckInterval = 0.25f; private const float SceneLoadGraceSeconds = 1.5f; private const int OutsideConfirmationCount = 2; private const float ExitPortalRange = 3f; private const float ExitInputCooldown = 0.5f; private const float ExitProximityPollSeconds = 0.1f; private static readonly BindingFlags InstanceFields = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static CasinoRuntimeActivity? _instance; private static FieldInfo? _playerMapInstanceField; private static PropertyInfo? _playerMapInstanceProperty; private static FieldInfo? _playerMapNameField; private static PropertyInfo? _playerMapNameProperty; private static bool _playerMapInstanceResolved; private static bool _playerMapInstanceWarningLogged; private Scene _casinoScene; private int _casinoSceneHandle = -1; private float _casinoLoadedAt = -1f; private float _nextSafetyCheck; private Component? _activeCasinoMapInstance; private int _matchedCasinoSceneHandle = -1; private readonly Dictionary _mapInstancesBySceneHandle = new Dictionary(); private readonly Dictionary _sceneObjectsByHandle = new Dictionary(); private readonly Dictionary _presentationCachesBySceneHandle = new Dictionary(); private Transform? _exitPortal; private PresenceState _presence; private bool _initialized; private string _presenceEvidence = "not-evaluated"; private PresenceState? _lastReportedPresence; private string _lastReportedPresenceEvidence = string.Empty; private int _outsideConfirmationChecks; private bool _authorityHost; private bool _stateApplied; private PresenceState _appliedPresence; private bool _appliedAuthorityHost; private bool _exitPlayerNearby; private float _exitNextInputTime; private float _nextExitProximityPollTime; internal static bool IsLocalPlayerInCasino { get { if (EnsureInitialized("local-presence-query") && (Object)(object)_instance != (Object)null && _instance.HasLiveCasinoScene && !HostCasinoVisibilityWatcher.IsHeadlessServer && !HostCasinoVisibilityWatcher.IsServerMode) { return _instance._presence != PresenceState.Outside; } return false; } } internal static bool IsLocalPlayerConfirmedInCasino { get { if (EnsureInitialized("confirmed-presence-query") && (Object)(object)_instance != (Object)null && _instance.HasLiveCasinoScene) { return _instance._presence == PresenceState.Inside; } return false; } } internal static bool IsAuthorityHost { get { if ((Object)(object)_instance != (Object)null) { return _instance._authorityHost; } return false; } } internal static bool CasinoSceneLoaded { get { if ((Object)(object)_instance != (Object)null) { return _instance.HasLiveCasinoScene; } return false; } } internal static Component? CachedCasinoMapInstance => _instance?._activeCasinoMapInstance; private bool HasLiveCasinoScene { get { if (((Scene)(ref _casinoScene)).IsValid() && ((Scene)(ref _casinoScene)).isLoaded) { return ((Scene)(ref _casinoScene)).handle == _casinoSceneHandle; } return false; } } internal static bool TryGetActiveCasinoScene(out Scene scene) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_instance != (Object)null && _instance.HasLiveCasinoScene) { scene = _instance._casinoScene; return true; } scene = default(Scene); return false; } internal static void Init() { if ((Object)(object)_instance != (Object)null) { return; } Plugin instance = Plugin.Instance; if ((Object)(object)instance == (Object)null) { throw new InvalidOperationException("AtlyssCasino plugin host is not available."); } GameObject gameObject = ((Component)instance).gameObject; CasinoRuntimeActivity casinoRuntimeActivity = gameObject.GetComponent(); bool flag = (Object)(object)casinoRuntimeActivity == (Object)null; if ((Object)(object)casinoRuntimeActivity == (Object)null) { casinoRuntimeActivity = gameObject.AddComponent(); } _instance = casinoRuntimeActivity; try { casinoRuntimeActivity.Initialize(); } catch { _instance = null; if (flag && (Object)(object)casinoRuntimeActivity != (Object)null) { Object.Destroy((Object)(object)casinoRuntimeActivity); } throw; } } internal static bool EnsureInitialized(string reason) { if ((Object)(object)_instance != (Object)null) { return true; } try { Init(); if ((Object)(object)_instance == (Object)null) { return false; } Plugin.LogAlwaysInfo("[CasinoRuntime] Coordinator ready: reason=" + reason + ", host=plugin-object."); return true; } catch (Exception arg) { Plugin.Log.LogError("[CasinoRuntime] Coordinator recovery failed " + $"(reason={reason}): {arg}"); return false; } } internal static void RefreshConfiguration() { if (!((Object)(object)_instance == (Object)null)) { _instance.RefreshAllConfiguration(); } } internal static void RefreshSceneCache(Scene scene) { //IL_000d: 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_0064: Unknown result type (might be due to invalid IL or missing references) if (!EnsureInitialized("scene-cache-refresh") || !IsExactCasinoScene(scene)) { return; } CasinoRuntimeActivity instance = _instance; if (!((Object)(object)instance == (Object)null)) { if (instance._presentationCachesBySceneHandle.TryGetValue(((Scene)(ref scene)).handle, out ScenePresentationCache value)) { value.Restore(); } CasinoPerformanceOptimizer.ApplyToCasinoScene(scene); bool flag = !instance.HasLiveCasinoScene || instance._casinoSceneHandle == ((Scene)(ref scene)).handle; instance.CaptureScene(scene, flag, flag); if (!flag && instance._presentationCachesBySceneHandle.TryGetValue(((Scene)(ref scene)).handle, out ScenePresentationCache value2)) { value2.ApplyOutside(); } } } internal static bool TryGetCachedSceneObjects(int sceneHandle, out GameObject[] objects) { if ((Object)(object)_instance != (Object)null && _instance._sceneObjectsByHandle.TryGetValue(sceneHandle, out GameObject[] value)) { objects = value; return objects.Length != 0; } objects = Array.Empty(); return false; } private static bool IsExactCasinoScene(Scene scene) { if (((Scene)(ref scene)).IsValid() && ((Scene)(ref scene)).isLoaded) { return string.Equals(((Scene)(ref scene)).name, "AtlyssCasino", StringComparison.Ordinal); } return false; } private void Initialize() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (_initialized) { return; } _initialized = true; SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; for (int i = 0; i < SceneManager.sceneCount; i++) { if (IsExactCasinoScene(SceneManager.GetSceneAt(i))) { _casinoLoadedAt = Time.unscaledTime; CaptureAllLoadedCasinoScenes(); break; } } Plugin.Log.LogInfo("[CasinoRuntime] Initialized (headless=" + $"{HostCasinoVisibilityWatcher.IsHeadlessServer}, " + $"serverMode={HostCasinoVisibilityWatcher.IsServerMode})."); } private void OnDestroy() { if (_initialized) { SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.sceneUnloaded -= OnSceneUnloaded; _initialized = false; } if (HasLiveCasinoScene) { RestoreCachedState(); } List list = new List(_presentationCachesBySceneHandle.Keys); for (int i = 0; i < list.Count; i++) { CasinoPerformanceOptimizer.CleanupScene(list[i]); } if (_instance == this) { _instance = null; if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.LogAlwaysInfo("[CasinoRuntime] Coordinator destroyed; the next presence or scene query will recover it."); } } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_0000: 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) if (IsExactCasinoScene(scene)) { AttachScene(scene); } } private void OnSceneUnloaded(Scene scene) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) if (!string.Equals(((Scene)(ref scene)).name, "AtlyssCasino", StringComparison.Ordinal) && !_presentationCachesBySceneHandle.ContainsKey(((Scene)(ref scene)).handle)) { return; } bool num = ((Scene)(ref scene)).handle == _casinoSceneHandle; RemoveSceneCache(((Scene)(ref scene)).handle, cleanupOptimizer: true); if (!num) { return; } ScenePresentationCache scenePresentationCache = null; foreach (ScenePresentationCache value2 in _presentationCachesBySceneHandle.Values) { if (IsExactCasinoScene(value2.Scene)) { scenePresentationCache = value2; break; } } if (scenePresentationCache == null) { ClearSceneCache(); return; } _casinoLoadedAt = Time.unscaledTime; ActivateSceneCache(scenePresentationCache); _presence = ResolvePresence(DetermineLocalPresence()); if (_presence == PresenceState.Inside && _presentationCachesBySceneHandle.TryGetValue(_matchedCasinoSceneHandle, out ScenePresentationCache value) && value.SceneHandle != _casinoSceneHandle) { Component activeCasinoMapInstance = _activeCasinoMapInstance; ActivateSceneCache(value, activeCasinoMapInstance); } _authorityHost = DetermineAuthorityRole(); _stateApplied = false; ApplyCachedActivityState(force: true); } private void AttachScene(Scene scene) { //IL_0000: 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_0053: 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) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (!IsExactCasinoScene(scene)) { return; } if (HasLiveCasinoScene && _casinoSceneHandle != ((Scene)(ref scene)).handle) { CaptureScene(scene, makeActive: false, applyActivity: false); if (_presentationCachesBySceneHandle.TryGetValue(((Scene)(ref scene)).handle, out ScenePresentationCache value)) { value.ApplyOutside(); } _nextSafetyCheck = 0f; } else { _casinoScene = scene; _casinoSceneHandle = ((Scene)(ref scene)).handle; _casinoLoadedAt = Time.unscaledTime; _nextSafetyCheck = 0f; _stateApplied = false; _exitPlayerNearby = false; CaptureScene(scene); } } private void CaptureScene(Scene scene, bool makeActive = true, bool applyActivity = true) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) if (!IsExactCasinoScene(scene)) { return; } if (_presentationCachesBySceneHandle.TryGetValue(((Scene)(ref scene)).handle, out ScenePresentationCache value)) { value.Restore(); } Transform val = null; List list = new List(256); List list2 = new List(4); List list3 = new List(128); List list4 = new List(64); List list5 = new List(8); List list6 = new List(32); List list7 = new List(32); GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); foreach (GameObject val2 in rootGameObjects) { if ((Object)(object)val2 == (Object)null) { continue; } Component[] componentsInChildren = val2.GetComponentsInChildren(true); foreach (Component val3 in componentsInChildren) { if ((Object)(object)val3 == (Object)null) { continue; } if (val3 is Transform) { list.Add(val3.gameObject); } string name = ((object)val3).GetType().Name; if (name == "MapInstance") { list2.Add(val3); } if ((Object)(object)val == (Object)null && name == "Portal") { val = val3.transform; } if (val3 is Transform || HasPlayerAncestor(val3.transform)) { continue; } Renderer val4 = (Renderer)(object)((val3 is Renderer) ? val3 : null); if (val4 != null) { list3.Add(new RendererState(val4)); continue; } Light val5 = (Light)(object)((val3 is Light) ? val3 : null); if (val5 != null) { list4.Add(new LightState(val5)); continue; } Camera val6 = (Camera)(object)((val3 is Camera) ? val3 : null); if (val6 != null && IsPresentationCamera(val6)) { list5.Add(new CameraState(val6)); continue; } AudioSource val7 = (AudioSource)(object)((val3 is AudioSource) ? val3 : null); if (val7 != null) { list6.Add(new AudioState(val7)); } else if (name == "ParticleSystem") { ParticleState particleState = ParticleState.TryCreate(val3); if (particleState != null) { list7.Add(particleState); } } } } ScenePresentationCache scenePresentationCache = new ScenePresentationCache(scene, list.ToArray(), list2.ToArray(), val, list3.ToArray(), list4.ToArray(), list5.ToArray(), list6.ToArray(), list7.ToArray()); _presentationCachesBySceneHandle[((Scene)(ref scene)).handle] = scenePresentationCache; _sceneObjectsByHandle[((Scene)(ref scene)).handle] = scenePresentationCache.SceneObjects; _mapInstancesBySceneHandle[((Scene)(ref scene)).handle] = scenePresentationCache.MapInstances; if (makeActive) { ActivateSceneCache(scenePresentationCache); } if (!applyActivity) { Plugin.Log.LogInfo($"[CasinoRuntime] Cached scene handle {((Scene)(ref scene)).handle}: " + $"objects={scenePresentationCache.SceneObjects.Length}, " + $"renderers={scenePresentationCache.Renderers.Length}, " + $"lights={scenePresentationCache.Lights.Length}, " + $"cameras={scenePresentationCache.Cameras.Length}, " + $"audio={scenePresentationCache.AudioSources.Length}, " + $"particles={scenePresentationCache.Particles.Length}, " + $"mapInstances={scenePresentationCache.MapInstances.Length}."); return; } _presence = ResolvePresence(DetermineLocalPresence()); if (_presence == PresenceState.Inside && _matchedCasinoSceneHandle != _casinoSceneHandle && _presentationCachesBySceneHandle.TryGetValue(_matchedCasinoSceneHandle, out ScenePresentationCache value2)) { Component activeCasinoMapInstance = _activeCasinoMapInstance; ActivateSceneCache(value2, activeCasinoMapInstance); } _authorityHost = DetermineAuthorityRole(); _stateApplied = false; Plugin.Log.LogInfo($"[CasinoRuntime] Cached scene handle {((Scene)(ref scene)).handle}: " + $"objects={scenePresentationCache.SceneObjects.Length}, " + $"renderers={scenePresentationCache.Renderers.Length}, " + $"lights={scenePresentationCache.Lights.Length}, " + $"cameras={scenePresentationCache.Cameras.Length}, " + $"audio={scenePresentationCache.AudioSources.Length}, " + $"particles={scenePresentationCache.Particles.Length}, " + $"mapInstances={scenePresentationCache.MapInstances.Length}."); ApplyCachedActivityState(force: true); } private void ActivateSceneCache(ScenePresentationCache cache, Component? matchedMapInstance = null) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) bool num = _casinoSceneHandle != cache.SceneHandle; if (num && _presentationCachesBySceneHandle.TryGetValue(_casinoSceneHandle, out ScenePresentationCache value)) { value.Restore(); } _casinoScene = cache.Scene; _casinoSceneHandle = cache.SceneHandle; _activeCasinoMapInstance = matchedMapInstance ?? ((cache.MapInstances.Length != 0) ? cache.MapInstances[0] : null); _exitPortal = cache.ExitPortal; if (num) { _stateApplied = false; CasinoPatch.RebindSceneRegistries(cache.Scene); } } private void CaptureAllLoadedCasinoScenes() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) int casinoSceneHandle = _casinoSceneHandle; RestoreCachedState(); HashSet hashSet = new HashSet(); for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); if (IsExactCasinoScene(sceneAt)) { hashSet.Add(((Scene)(ref sceneAt)).handle); CaptureScene(sceneAt, makeActive: false, applyActivity: false); } } List list = new List(); foreach (KeyValuePair item in _presentationCachesBySceneHandle) { if (!hashSet.Contains(item.Key)) { list.Add(item.Key); } } for (int j = 0; j < list.Count; j++) { RemoveSceneCache(list[j], cleanupOptimizer: true); } ScenePresentationCache value = null; if (!_presentationCachesBySceneHandle.TryGetValue(casinoSceneHandle, out value)) { using Dictionary.ValueCollection.Enumerator enumerator2 = _presentationCachesBySceneHandle.Values.GetEnumerator(); if (enumerator2.MoveNext()) { value = enumerator2.Current; } } if (value == null) { ClearSceneCache(); return; } ActivateSceneCache(value); _presence = ResolvePresence(DetermineLocalPresence()); if (_presence == PresenceState.Inside && _presentationCachesBySceneHandle.TryGetValue(_matchedCasinoSceneHandle, out ScenePresentationCache value2) && value2.SceneHandle != _casinoSceneHandle) { Component activeCasinoMapInstance = _activeCasinoMapInstance; ActivateSceneCache(value2, activeCasinoMapInstance); } _authorityHost = DetermineAuthorityRole(); _stateApplied = false; ApplyCachedActivityState(force: true); } private void RefreshAllConfiguration() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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_0019: Unknown result type (might be due to invalid IL or missing references) RestoreCachedState(); for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); if (IsExactCasinoScene(sceneAt)) { CasinoPerformanceOptimizer.ApplyToCasinoScene(sceneAt); } } CaptureAllLoadedCasinoScenes(); _nextSafetyCheck = 0f; } private void RemoveSceneCache(int sceneHandle, bool cleanupOptimizer) { if (_presentationCachesBySceneHandle.TryGetValue(sceneHandle, out ScenePresentationCache value)) { value.Restore(); _presentationCachesBySceneHandle.Remove(sceneHandle); } _sceneObjectsByHandle.Remove(sceneHandle); _mapInstancesBySceneHandle.Remove(sceneHandle); if (cleanupOptimizer) { CasinoPerformanceOptimizer.CleanupScene(sceneHandle); } } private void ClearSceneCache() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) _casinoScene = default(Scene); _casinoSceneHandle = -1; _casinoLoadedAt = -1f; _activeCasinoMapInstance = null; _matchedCasinoSceneHandle = -1; _mapInstancesBySceneHandle.Clear(); _sceneObjectsByHandle.Clear(); _presentationCachesBySceneHandle.Clear(); _exitPortal = null; _presence = PresenceState.Unknown; _presenceEvidence = "not-evaluated"; _lastReportedPresence = null; _lastReportedPresenceEvidence = string.Empty; _outsideConfirmationChecks = 0; _authorityHost = false; _stateApplied = false; _exitPlayerNearby = false; } private void Update() { if (HasLiveCasinoScene) { if (_presence == PresenceState.Inside) { HandleExitPortalInput(); } else { ResetExitPortalState(); } if (!(Time.unscaledTime < _nextSafetyCheck)) { _nextSafetyCheck = Time.unscaledTime + 0.25f; EvaluateCachedState(forceApply: false); } } } private void EvaluateCachedState(bool forceApply) { //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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) if (!HasLiveCasinoScene) { return; } PresenceState presence = _presence; PresenceState presenceState = ResolvePresence(DetermineLocalPresence()); if (presenceState == PresenceState.Inside && _matchedCasinoSceneHandle != _casinoSceneHandle) { if (_presentationCachesBySceneHandle.TryGetValue(_matchedCasinoSceneHandle, out ScenePresentationCache value)) { ActivateSceneCache(value, _activeCasinoMapInstance); } else if ((Object)(object)_activeCasinoMapInstance != (Object)null) { Scene scene = _activeCasinoMapInstance.gameObject.scene; if (IsExactCasinoScene(scene)) { CaptureScene(scene); return; } } } bool flag = DetermineAuthorityRole(); bool flag2 = presenceState != _presence || flag != _authorityHost; _presence = presenceState; _authorityHost = flag; if (presence != PresenceState.Outside && _presence == PresenceState.Outside) { CaptureAllLoadedCasinoScenes(); } else { ApplyCachedActivityState(forceApply || flag2); } } private void ApplyCachedActivityState(bool force) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (!HasLiveCasinoScene || (!force && _stateApplied && _appliedPresence == _presence && _appliedAuthorityHost == _authorityHost)) { return; } RestoreCachedState(); if (_presence == PresenceState.Inside) { Plugin.EnsurePresentationAssetsLoaded(); CasinoPatch.EnsureLocalPresentation(_casinoScene); BJNetcode.RebuildRegisteredBlackjackVisuals(); SelfPortraitVisibility.Refresh(); ApplyInactiveSceneSuppression(); } else if (_presence == PresenceState.Outside) { ApplyOutsideState(); if (!HostCasinoVisibilityWatcher.IsHeadlessServer) { CasinoJukeboxPersonalPlayer.StopForWorldJukebox(); } } else { ApplyInactiveSceneSuppression(); } _appliedPresence = _presence; _appliedAuthorityHost = _authorityHost; _stateApplied = true; if (!_lastReportedPresence.HasValue || _lastReportedPresence.Value != _presence || !string.Equals(_lastReportedPresenceEvidence, _presenceEvidence, StringComparison.Ordinal)) { Plugin.LogAlwaysInfo($"[CasinoRuntime] Presence: state={_presence}, " + "evidence=" + _presenceEvidence + ", " + $"activeSceneHandle={_casinoSceneHandle}, " + $"matchedSceneHandle={_matchedCasinoSceneHandle}."); _lastReportedPresence = _presence; _lastReportedPresenceEvidence = _presenceEvidence; } Plugin.Log.LogInfo($"[CasinoRuntime] State: presence={_presence}, " + $"authorityHost={_authorityHost}, activeLights=authored."); } private void ApplyOutsideState() { foreach (ScenePresentationCache value in _presentationCachesBySceneHandle.Values) { value.ApplyOutside(); } } private void ApplyInactiveSceneSuppression() { foreach (ScenePresentationCache value in _presentationCachesBySceneHandle.Values) { if (value.SceneHandle != _casinoSceneHandle) { value.ApplyOutside(); } } } private void RestoreCachedState() { foreach (ScenePresentationCache value in _presentationCachesBySceneHandle.Values) { value.Restore(); } } private PresenceState DetermineLocalPresence() { //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015c: 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_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) _matchedCasinoSceneHandle = -1; _presenceEvidence = "evaluating"; if (HostCasinoVisibilityWatcher.IsHeadlessServer || HostCasinoVisibilityWatcher.IsServerMode) { _presenceEvidence = "server-mode"; return PresenceState.Outside; } Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { _presenceEvidence = "player-unavailable"; return PresenceState.Unknown; } ResolvePlayerMapInstanceField(); try { Component val = ReadPlayerMapInstance(mainPlayer); string text = ReadPlayerMapName(mainPlayer); if ((Object)(object)val != (Object)null) { foreach (KeyValuePair item in _mapInstancesBySceneHandle) { Component[] value = item.Value; foreach (Component val2 in value) { if (!((Object)(object)val2 == (Object)null) && (val == val2 || ((Object)val).GetInstanceID() == ((Object)val2).GetInstanceID())) { _activeCasinoMapInstance = val; _matchedCasinoSceneHandle = item.Key; _presenceEvidence = "cached-map-instance"; return PresenceState.Inside; } } } } Scene scene = ((Component)mainPlayer).gameObject.scene; if (IsExactCasinoScene(scene)) { _activeCasinoMapInstance = val; _matchedCasinoSceneHandle = ((Scene)(ref scene)).handle; _presenceEvidence = "player-scene"; return PresenceState.Inside; } string text2 = string.Empty; if ((Object)(object)val != (Object)null) { Scene scene2 = val.gameObject.scene; if (IsExactCasinoScene(scene2)) { _activeCasinoMapInstance = val; _matchedCasinoSceneHandle = ((Scene)(ref scene2)).handle; _presenceEvidence = "player-map-scene"; return PresenceState.Inside; } MapInstance val3 = (MapInstance)(object)((val is MapInstance) ? val : null); if ((Object)(object)val3 != (Object)null) { Scene loadedScene = val3._loadedScene; if (IsExactCasinoScene(loadedScene)) { _activeCasinoMapInstance = val; _matchedCasinoSceneHandle = ((Scene)(ref loadedScene)).handle; _presenceEvidence = "player-map-loaded-scene"; return PresenceState.Inside; } text2 = val3._mapName ?? string.Empty; if (IsCasinoMapName(text2)) { _activeCasinoMapInstance = val; _presenceEvidence = "map-instance-name"; return PresenceState.Inside; } } if (((Object)val.gameObject).name.IndexOf("AtlyssCasino", StringComparison.OrdinalIgnoreCase) >= 0) { _activeCasinoMapInstance = val; _presenceEvidence = "map-object-name"; return PresenceState.Inside; } } if (IsCasinoMapName(text)) { _activeCasinoMapInstance = val; _presenceEvidence = "player-map-name"; return PresenceState.Inside; } if (!string.IsNullOrWhiteSpace(text)) { _presenceEvidence = "noncasino-player-map-name"; return PresenceState.Outside; } if (!string.IsNullOrWhiteSpace(text2)) { _presenceEvidence = "noncasino-map-instance-name"; return PresenceState.Outside; } _presenceEvidence = "map-identity-unavailable"; return PresenceState.Unknown; } catch (Exception ex) { _presenceEvidence = "presence-read-failed"; if (!_playerMapInstanceWarningLogged) { _playerMapInstanceWarningLogged = true; Plugin.Log.LogWarning("[CasinoRuntime] Player MapInstance read failed: " + ex.GetType().Name + ": " + ex.Message); } return PresenceState.Unknown; } } private static Component? ReadPlayerMapInstance(Player player) { try { object? obj = _playerMapInstanceField?.GetValue(player); Component val = (Component)((obj is Component) ? obj : null); if (val != null && (Object)(object)val != (Object)null) { return val; } } catch { } try { object? obj3 = _playerMapInstanceProperty?.GetValue(player); Component val2 = (Component)((obj3 is Component) ? obj3 : null); if (val2 != null && (Object)(object)val2 != (Object)null) { return val2; } } catch { } return null; } private static string ReadPlayerMapName(Player player) { try { if (_playerMapNameField?.GetValue(player) is string result) { return result; } } catch { } try { if (_playerMapNameProperty?.GetValue(player) is string result2) { return result2; } } catch { } return string.Empty; } private static bool IsCasinoMapName(string? mapName) { if (!string.IsNullOrWhiteSpace(mapName)) { return string.Equals(mapName.Trim(), "AtlyssCasino", StringComparison.OrdinalIgnoreCase); } return false; } private PresenceState ResolvePresence(PresenceState observation) { switch (observation) { case PresenceState.Inside: _outsideConfirmationChecks = 0; return PresenceState.Inside; case PresenceState.Unknown: _outsideConfirmationChecks = 0; return PresenceState.Unknown; default: if (Time.unscaledTime - _casinoLoadedAt < 1.5f) { _outsideConfirmationChecks = 0; return PresenceState.Unknown; } if (_presence == PresenceState.Outside) { _outsideConfirmationChecks = 2; return PresenceState.Outside; } _outsideConfirmationChecks++; if (_outsideConfirmationChecks < 2) { return PresenceState.Unknown; } return PresenceState.Outside; } } private static void ResolvePlayerMapInstanceField() { if (!_playerMapInstanceResolved) { _playerMapInstanceResolved = true; Type typeFromHandle = typeof(Player); _playerMapInstanceProperty = typeFromHandle.GetProperty("Network_playerMapInstance", InstanceFields); _playerMapInstanceField = typeFromHandle.GetField("_playerMapInstance", InstanceFields) ?? typeFromHandle.GetField("playerMapInstance", InstanceFields); _playerMapNameProperty = typeFromHandle.GetProperty("Network_mapName", InstanceFields); _playerMapNameField = typeFromHandle.GetField("_mapName", InstanceFields) ?? typeFromHandle.GetField("mapName", InstanceFields); if (_playerMapInstanceField == null && _playerMapInstanceProperty == null && _playerMapNameField == null && _playerMapNameProperty == null && !_playerMapInstanceWarningLogged) { _playerMapInstanceWarningLogged = true; Plugin.Log.LogWarning("[CasinoRuntime] Player map identity fields were not found; presence will use scene-only fallbacks."); } } } private static bool DetermineAuthorityRole() { if (HostCasinoVisibilityWatcher.IsHeadlessServer || HostCasinoVisibilityWatcher.IsServerMode) { return true; } try { if (NetworkServer.active) { return true; } } catch { } try { return BJNetcode.AmHost(); } catch { return false; } } private void HandleExitPortalInput() { //IL_0069: 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_0074: 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 (HostCasinoVisibilityWatcher.IsHeadlessServer || HostCasinoVisibilityWatcher.IsServerMode || _authorityHost || CasinoExitTrigger.IsTransporting) { _exitPlayerNearby = false; return; } Transform exitPortal = _exitPortal; Player mainPlayer = Player._mainPlayer; if ((Object)(object)exitPortal == (Object)null || (Object)(object)mainPlayer == (Object)null) { return; } if (Time.unscaledTime >= _nextExitProximityPollTime) { _nextExitProximityPollTime = Time.unscaledTime + 0.1f; Vector3 val = ((Component)mainPlayer).transform.position - exitPortal.position; bool flag = ((Vector3)(ref val)).sqrMagnitude < 9f; if (flag && !_exitPlayerNearby) { _exitPlayerNearby = true; Plugin.ShowHUDInfo("Press " + CasinoInput.InteractPrompt + " to return to Sanctum."); } else if (!flag) { _exitPlayerNearby = false; } } if (_exitPlayerNearby && !Plugin.IsTypingInUI() && CasinoInput.WasInteractPressed() && !(Time.time < _exitNextInputTime)) { _exitNextInputTime = Time.time + 0.5f; ((MonoBehaviour)this).StartCoroutine(CasinoExitTrigger.ForceReturnToSanctum(mainPlayer, "[ExitPortal/CasinoRuntime]")); } } private void ResetExitPortalState() { _exitPlayerNearby = false; } private static bool HasPlayerAncestor(Transform transform) { Transform val = transform; while ((Object)(object)val != (Object)null) { if ((Object)(object)((Component)val).GetComponent() != (Object)null) { return true; } val = val.parent; } return false; } private static bool IsPresentationCamera(Camera camera) { Transform val = ((Component)camera).transform; bool result = false; while ((Object)(object)val != (Object)null) { string name = ((Object)val).name; if (name.IndexOf("skybox", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("map camera", StringComparison.OrdinalIgnoreCase) >= 0) { return false; } if (name.IndexOf("presentation", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("security", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("monitor", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("render camera", StringComparison.OrdinalIgnoreCase) >= 0) { result = true; } if ((Object)(object)((Component)val).GetComponent("MapInstance") != (Object)null) { break; } val = val.parent; } return result; } } 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 (!Plugin.IsLocalPlayerConfirmedInCasino()) { SetTextVisible(visible: false); return; } 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; if (!roundInProgress) { return Time.time < _blackjackRoundCompleteUntil; } return false; } private static bool IsBlackjackActive(BlackjackTable table, bool showRoundComplete) { if (!(table.RoundInProgress || showRoundComplete)) { return table.OccupiedSeatCount > 0; } return true; } 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; if (!isSpinning) { return Time.time < _rouletteRoundCompleteUntil; } return false; } private static bool IsRouletteActive(RouletteTable table, bool showRoundComplete) { if (!(table.IsSpinning || showRoundComplete)) { return table.PlayerCount > 0; } return true; } 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--; } if (num <= 0) { return "??"; } return 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 text2 = NormalizePlayerName(((Object)player).name ?? string.Empty, fallback); if (!string.IsNullOrWhiteSpace(text2)) { return text2; } return fallback; } 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(); } if (!string.IsNullOrWhiteSpace(text)) { return text; } return fallback; } 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); foreach (Component val in componentsInChildren) { 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_0083: Unknown result type (might be due to invalid IL or missing references) Type type = ResolveType("UnityEngine.TextMesh"); if (type == null) { return null; } Component obj = ((Component)target).gameObject.AddComponent(type); SetProperty(obj, "anchor", "MiddleCenter"); SetProperty(obj, "alignment", "Center"); SetProperty(obj, "fontSize", 42); SetProperty(obj, "characterSize", 0.08f); SetProperty(obj, "color", (object)new Color(1f, 0.86f, 0.55f, 1f)); ApplyDepthTestedMaterial(obj); return obj; } private static void ApplyDepthTestedMaterial(Component textComponent) { //IL_00d6: 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_00f4: Expected O, but got Unknown Renderer[] componentsInChildren = textComponent.GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } Material[] sharedMaterials = val.sharedMaterials; bool flag = false; for (int j = 0; j < sharedMaterials.Length; j++) { Material val2 = sharedMaterials[j]; if ((Object)(object)val2 == (Object)null || ((Object)val2).name.EndsWith("_DepthTested", StringComparison.Ordinal)) { continue; } 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) { sharedMaterials[j] = val3; flag = true; } } else if (val2.HasProperty("_ZTest") || val2.HasProperty("_ZTestMode") || val2.HasProperty("unity_GUIZTestMode")) { Material val4 = new Material(val2) { name = ((Object)val2).name + "_DepthTested" }; SetDepthTestProperties(val4); sharedMaterials[j] = val4; flag = true; } } if (flag) { val.sharedMaterials = sharedMaterials; } } } 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; } if (!mat.HasProperty("_ZTest") && !mat.HasProperty("_ZTestMode") && !mat.HasProperty("unity_GUIZTestMode")) { return shaderName.IndexOf("Text", StringComparison.OrdinalIgnoreCase) >= 0; } return false; } 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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //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_00ca: 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) 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(); for (int i = 0; i < assemblies.Length; i++) { Type type = assemblies[i].GetType(fullName); if (type != null) { return type; } } return null; } private static Transform? FindDeepChild(Transform root, string childName) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: 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 { private const string SANCTUM_PATH = "Assets/Scenes/00_zone_forest/_zone00_sanctum.unity"; public static bool IsTransporting; public static IEnumerator ForceReturnToSanctum(Player player, string caller = "[ExitTrigger]") { IsTransporting = true; try { Plugin.ShowHUDInfo("Returning to Sanctum..."); yield return (object)new WaitForSeconds(0.5f); ConfigurePortal(caller); yield return null; 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."); yield break; } yield return null; Plugin.Log.LogInfo(caller + " Strategy 2: Cmd_SceneTransport (short name)..."); if (TrySceneTransport(player, "Sanctum", "startPoint")) { Plugin.Log.LogInfo(caller + " Cmd_SceneTransport (short) fired."); yield break; } yield return null; Plugin.Log.LogInfo(caller + " Strategy 3: InteractQueue_RecallPortal (fallback)..."); if (TryInteractQueue(caller)) { Plugin.Log.LogInfo(caller + " InteractQueue_RecallPortal fired."); yield break; } Plugin.Log.LogInfo(caller + " Strategy 4: _requestedRecall..."); if (TrySetRecallFlag(player)) { Plugin.Log.LogInfo(caller + " _requestedRecall set."); yield break; } Plugin.Log.LogWarning(caller + " Strategy 5: SceneManager.LoadScene..."); SceneManager.LoadScene("Assets/Scenes/00_zone_forest/_zone00_sanctum.unity"); } finally { IsTransporting = false; } } internal static void ResetTransportState() { IsTransporting = false; } private static void ConfigurePortal(string caller) { GameObject val = GameObject.Find("_entity_recallPortal"); if ((Object)(object)val == (Object)null) { return; } MonoBehaviour[] componentsInChildren = val.GetComponentsInChildren(true); foreach (MonoBehaviour val2 in componentsInChildren) { 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); } } } } array2 = componentsInChildren; foreach (MonoBehaviour val3 in array2) { 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[] array4 = methods; foreach (MethodInfo methodInfo in array4) { 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 static class HostCasinoVisibilityWatcher { private static bool? _isHeadlessCached; private static bool? _isServerModeCached; public static bool IsHeadlessServer { get { if (_isHeadlessCached.HasValue) { return _isHeadlessCached.Value; } bool flag = Application.isBatchMode; try { string[] commandLineArgs = Environment.GetCommandLineArgs(); for (int i = 0; i < commandLineArgs.Length; i++) { if (string.Equals(commandLineArgs[i], "-nographics", StringComparison.OrdinalIgnoreCase) || string.Equals(commandLineArgs[i], "-server", StringComparison.OrdinalIgnoreCase)) { flag = true; break; } } } catch { } _isHeadlessCached = flag; return flag; } } public static bool IsServerMode { get { if (_isServerModeCached.HasValue) { return _isServerModeCached.Value; } bool flag = false; try { string[] commandLineArgs = Environment.GetCommandLineArgs(); for (int i = 0; i < commandLineArgs.Length; i++) { if (string.Equals(commandLineArgs[i], "-server", StringComparison.OrdinalIgnoreCase)) { flag = true; break; } } } catch { } _isServerModeCached = flag; return flag; } } public static void Init() { if (!CasinoRuntimeActivity.EnsureInitialized("plugin-startup")) { throw new InvalidOperationException("Casino runtime coordinator could not initialize."); } } public static bool IsLocalPlayerInsideCasinoBounds() { return CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino; } } 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; internal string Name = string.Empty; internal int SceneHandle; internal string SceneName = string.Empty; internal Component? MapInstance; } 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_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0080: 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) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) if (_initialized) { return; } _initialized = true; SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; GameObject val = new GameObject("AtlyssCasino_SelfPortraitVisibility"); Object.DontDestroyOnLoad((Object)val); ((Object)val).hideFlags = (HideFlags)61; _driver = val.AddComponent(); ((Behaviour)_driver).enabled = false; for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); if (IsCasinoScene(sceneAt)) { ScanScene(sceneAt); } } Plugin.Log.LogInfo("[SelfPortrait] Visibility controller initialized."); } internal static void Refresh() { if (_targets.Count == 0) { if ((Object)(object)_driver != (Object)null) { ((Behaviour)_driver).enabled = false; } return; } Player[] players = (Plugin.IsLocalPlayerConfirmedInCasino() ? Object.FindObjectsOfType() : Array.Empty()); 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, players); 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_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (IsCasinoScene(scene)) { ScanScene(scene); if ((Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(DelayedScan(scene)); } } } 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 null; yield return null; ScanScene(scene); yield return (object)new WaitForSeconds(1f); ScanScene(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) if (string.Equals(((Scene)(ref scene)).name, "AtlyssCasino", StringComparison.Ordinal)) { _targets.RemoveAll((PortraitTarget t) => t.SceneHandle == ((Scene)(ref scene)).handle); if (_targets.Count == 0 && (Object)(object)_driver != (Object)null) { ((Behaviour)_driver).enabled = false; } } } private static void ScanScene(Scene scene) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if (!IsCasinoScene(scene)) { return; } int num = 0; 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) && 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) { if ((Object)(object)_driver != (Object)null) { ((Behaviour)_driver).enabled = true; } Plugin.Log.LogInfo($"[SelfPortrait] Found {num} owner portrait object(s) in scene '{((Scene)(ref scene)).name}'."); Refresh(); } } private static bool IsCasinoScene(Scene scene) { if (((Scene)(ref scene)).IsValid() && ((Scene)(ref scene)).isLoaded) { return string.Equals(((Scene)(ref scene)).name, "AtlyssCasino", StringComparison.Ordinal); } return false; } private static 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[] players) { foreach (Player val in players) { 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_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) Component playerMapInstance = GetPlayerMapInstance(player); if ((Object)(object)playerMapInstance != (Object)null && (Object)(object)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_0002: 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_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0041: 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(); foreach (GameObject val in rootGameObjects) { if ((Object)(object)val == (Object)null) { continue; } MonoBehaviour[] componentsInChildren = val.GetComponentsInChildren(true); foreach (MonoBehaviour val2 in componentsInChildren) { 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 { if (_playerMapNameProperty?.GetValue(player) is string result) { return result; } } catch { } try { if (_playerMapNameField?.GetValue(player) 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; } } } } internal sealed class CasinoGameUI : MonoBehaviour { private struct GameContext { public BlackjackTable? BlackjackTable; public int BlackjackSeat; public RouletteTable? RouletteTable; public string Key; public bool HasBlackjack { get { if ((Object)(object)BlackjackTable != (Object)null) { return BlackjackSeat >= 0; } return false; } } public bool HasRoulette => (Object)(object)RouletteTable != (Object)null; public bool HasAny { get { if (!HasBlackjack) { return HasRoulette; } return true; } } } private const int WindowId = 991904; private const float MinWidth = 460f; private const float MinHeight = 360f; private const float ContextRefreshInterval = 0.75f; private static CasinoGameUI? _instance; private Rect _windowRect = new Rect(80f, 80f, 640f, 560f); private Vector2 _scroll; private bool _visible; private bool _wasInCasino; private float _nextContextRefreshTime; private string _lastContextKey = string.Empty; private GameContext _cachedContext = EmptyContext(); private GUIStyle? _titleStyle; private GUIStyle? _headerStyle; private GUIStyle? _boxStyle; internal static void Init() { //IL_001b: 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) //IL_0026: Expected O, but got Unknown if (!((Object)(object)_instance != (Object)null) && !Plugin.IsHeadlessServer) { GameObject val = new GameObject("AtlyssCasino_GameUI"); Object.DontDestroyOnLoad((Object)val); _instance = val.AddComponent(); Plugin.Log.LogInfo("[CasinoUI] IMGUI game UI initialized."); } } internal static string RunCommand(string args) { if ((Object)(object)_instance == (Object)null) { Init(); } if ((Object)(object)_instance == (Object)null) { return "[Casino] Game UI is unavailable."; } return _instance.RunCommandInternal(args); } private string RunCommandInternal(string args) { string text = (args ?? string.Empty).Trim().ToLowerInvariant(); if (text.Length != 0) { switch (text) { case "toggle": break; case "open": case "show": case "on": if (!RefreshContextForCommand().HasAny) { _visible = false; return "[Casino] Sit at blackjack or join roulette first."; } _visible = true; return "[Casino] Game UI opened."; case "close": case "hide": case "off": _visible = false; return "[Casino] Game UI closed."; case "status": { string text2 = (_visible ? "open" : "closed"); string text3 = ((_lastContextKey.Length == 0) ? "no active table" : _lastContextKey); return "[Casino] Game UI is " + text2 + " (" + text3 + ")."; } default: return "[Casino] Usage: /casinoui [open|close|toggle|status]"; } } if (!RefreshContextForCommand().HasAny) { _visible = false; return "[Casino] Sit at blackjack or join roulette first."; } _visible = !_visible; if (!_visible) { return "[Casino] Game UI closed."; } return "[Casino] Game UI opened."; } private void Update() { if (Plugin.IsHeadlessServer) { return; } if (!Plugin.IsLocalPlayerConfirmedInCasino()) { ClearContext(); return; } bool num = !_wasInCasino; _wasInCasino = true; if (num || Time.unscaledTime >= _nextContextRefreshTime) { RefreshContext(); } GameContext cachedContext = _cachedContext; if (!cachedContext.HasAny) { _visible = false; _lastContextKey = string.Empty; return; } if (!string.Equals(_lastContextKey, cachedContext.Key, StringComparison.Ordinal)) { _lastContextKey = cachedContext.Key; _visible = CasinoConfig.CasinoGameUiAutoOpen; } if (!Plugin.IsTypingInUI() && CasinoInput.WasGameUiHotkeyPressed()) { _visible = !_visible; } } private void OnGUI() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown //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) if (!_visible || Plugin.IsHeadlessServer || !_wasInCasino) { return; } GameContext context = _cachedContext; if (context.HasAny) { EnsureStyles(); ClampWindowToScreen(); _windowRect = GUI.Window(991904, _windowRect, (WindowFunction)delegate { DrawWindow(context); }, "Casino"); } } private void DrawWindow(GameContext context) { //IL_0068: 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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginVertical(Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Casino Game UI", _titleStyle, Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button("X", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(28f), GUILayout.Height(24f) })) { _visible = false; } GUILayout.EndHorizontal(); _scroll = GUILayout.BeginScrollView(_scroll, Array.Empty()); if (context.HasBlackjack) { DrawBlackjack(context.BlackjackTable, context.BlackjackSeat); } else if (context.HasRoulette) { DrawRoulette(context.RouletteTable); } GUILayout.EndScrollView(); GUILayout.EndVertical(); GUI.DragWindow(new Rect(0f, 0f, 10000f, 28f)); } private void DrawBlackjack(BlackjackTable table, int seat) { GUILayout.Label("Blackjack", _headerStyle, Array.Empty()); GUILayout.BeginVertical(_boxStyle, Array.Empty()); DrawInfoLine("Table", ((Object)table).name); DrawInfoLine("Host", FormatBlackjackHost(table)); DrawInfoLine("Your Seat", $"Seat {seat + 1}"); DrawInfoLine("Current Turn", FormatBlackjackTurn(table)); DrawInfoLine("Turn Ended", FormatBlackjackTurnEnded(table, seat)); DrawInfoLine("Round", FormatBlackjackRound(table, seat)); DrawInfoLine("Ready", table.IsPlayerReady(seat) ? "Yes" : "No"); DrawInfoLine("Bet", $"{table.Hands[seat].Bet} Crowns"); DrawInfoLine("Players", $"{CountBlackjackPlayers(table)}/{5}"); GUILayout.EndVertical(); GUILayout.Space(6f); GUILayout.BeginVertical(_boxStyle, Array.Empty()); GUILayout.Label("Your Hand", _headerStyle, Array.Empty()); GUILayout.Label((table.Hands[seat].Cards.Count == 0) ? "(no cards)" : table.Hands[seat].Describe(), Array.Empty()); GUILayout.Space(4f); GUILayout.Label("Dealer", _headerStyle, Array.Empty()); GUILayout.Label((table.DealerHand.Cards.Count == 0) ? "(no cards)" : table.DescribeDealerForView(), Array.Empty()); GUILayout.EndVertical(); GUILayout.Space(6f); GUILayout.Label("Bet", _headerStyle, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); int[] allowedBets = Plugin.AllowedBets; for (int i = 0; i < allowedBets.Length; i++) { int num = allowedBets[i]; bool enabled = !table.RoundInProgress; if (ActionButton(num.ToString(), $"/blackjackbet {num}", enabled)) { break; } } GUILayout.EndHorizontal(); GUILayout.Space(6f); GUILayout.Label("Actions", _headerStyle, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); ActionButton(table.IsPlayerReady(seat) ? "Unready" : "Ready", "/ready", !table.RoundInProgress); ActionButton("Deal", "/start", !table.RoundInProgress && table.IsHost(seat) && table.AllSeatedPlayersReady); ActionButton("Hit", "/hit", table.RoundInProgress && table.CurrentTurnSeat == seat); ActionButton("Stand", "/stand", table.RoundInProgress && table.CurrentTurnSeat == seat); ActionButton("Leave", "/leave", true); GUILayout.EndHorizontal(); } private void DrawRoulette(RouletteTable table) { //IL_02f0: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Unknown result type (might be due to invalid IL or missing references) Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } GUILayout.Label("Roulette", _headerStyle, Array.Empty()); GUILayout.BeginVertical(_boxStyle, Array.Empty()); DrawInfoLine("Table", ((Object)table).name); DrawInfoLine("Host", FormatPlayerName(table.GetHostPlayer(), "(none)")); DrawInfoLine("Current Turn", FormatRouletteTurn(table, mainPlayer)); DrawInfoLine("Turn Ended", table.IsSpinning ? "No" : "Yes"); DrawInfoLine("Round", FormatRouletteRound(table)); DrawInfoLine("Stake", $"{Plugin.RouletteBet} Crowns per bet"); DrawInfoLine("Ready", table.IsPlayerReady(mainPlayer) ? "Yes" : "No"); DrawInfoLine("Ready Count", $"{table.GetReadyCount()}/{table.PlayerCount}"); DrawInfoLine("Players", $"{table.PlayerCount}/{8}"); DrawInfoLine("Last Result", FormatRouletteResult(table.LastWinningNumber)); GUILayout.EndVertical(); GUILayout.Space(6f); GUILayout.BeginVertical(_boxStyle, Array.Empty()); GUILayout.Label("Your Bets", _headerStyle, Array.Empty()); GUILayout.Label(RouletteLogic.FormatBetSummary(table.GetBetsForPlayer(mainPlayer)), Array.Empty()); GUILayout.EndVertical(); GUILayout.Space(6f); GUILayout.Label("Stake", _headerStyle, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); int[] allowedBets = Plugin.AllowedBets; for (int i = 0; i < allowedBets.Length; i++) { int num = allowedBets[i]; if (ActionButton(num.ToString(), $"/tbet {num}", !table.BetsLocked && !table.IsSpinning)) { break; } } GUILayout.EndHorizontal(); GUILayout.Space(6f); GUILayout.Label("Inside Bets", _headerStyle, Array.Empty()); DrawRouletteNumberGrid(table); GUILayout.Space(6f); GUILayout.Label("Outside Bets", _headerStyle, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); RouletteBetButton("1-18", "low", !table.BetsLocked && !table.IsSpinning, null); RouletteBetButton("Even", "even", !table.BetsLocked && !table.IsSpinning, null); RouletteBetButton("Red", "red", !table.BetsLocked && !table.IsSpinning, (Color?)new Color(0.75f, 0.12f, 0.12f), Array.Empty()); RouletteBetButton("Black", "black", !table.BetsLocked && !table.IsSpinning, (Color?)new Color(0.05f, 0.05f, 0.05f), Array.Empty()); RouletteBetButton("Odd", "odd", !table.BetsLocked && !table.IsSpinning, null); RouletteBetButton("19-36", "high", !table.BetsLocked && !table.IsSpinning, null); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); RouletteBetButton("1st 12", "dozen 1", !table.BetsLocked && !table.IsSpinning, null); RouletteBetButton("2nd 12", "dozen 2", !table.BetsLocked && !table.IsSpinning, null); RouletteBetButton("3rd 12", "dozen 3", !table.BetsLocked && !table.IsSpinning, null); RouletteBetButton("Column 1", "column 1", !table.BetsLocked && !table.IsSpinning, null); RouletteBetButton("Column 2", "column 2", !table.BetsLocked && !table.IsSpinning, null); RouletteBetButton("Column 3", "column 3", !table.BetsLocked && !table.IsSpinning, null); GUILayout.EndHorizontal(); GUILayout.Space(6f); GUILayout.Label("Actions", _headerStyle, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); ActionButton("Clear Bets", "/rclearbets", !table.BetsLocked && !table.IsSpinning); ActionButton(table.IsPlayerReady(mainPlayer) ? "Unready" : "Ready", "/rstandby", !table.IsSpinning); ActionButton("Spin", "/rspin", !table.IsSpinning && table.IsTableHost(mainPlayer)); ActionButton("Leave", "/rleave", !table.IsSpinning); GUILayout.EndHorizontal(); } private void DrawRouletteNumberGrid(RouletteTable table) { bool enabled = !table.BetsLocked && !table.IsSpinning; GUILayout.BeginHorizontal(Array.Empty()); RouletteNumberButton(0, enabled, GUILayout.Width(42f), GUILayout.Height(82f)); GUILayout.BeginVertical(Array.Empty()); for (int i = 0; i < 3; i++) { GUILayout.BeginHorizontal(Array.Empty()); for (int j = 0; j < 12; j++) { int number = 3 * j + (3 - i); RouletteNumberButton(number, enabled, GUILayout.Width(42f), GUILayout.Height(26f)); } GUILayout.EndHorizontal(); } GUILayout.EndVertical(); GUILayout.EndHorizontal(); } private void RouletteNumberButton(int number, bool enabled, params GUILayoutOption[] options) { //IL_0046: 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_005c: 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_001a: Unknown result type (might be due to invalid IL or missing references) Color value = ((number == 0) ? new Color(0f, 0.45f, 0.15f) : (RouletteLogic.IsRed(number) ? new Color(0.75f, 0.12f, 0.12f) : new Color(0.05f, 0.05f, 0.05f))); RouletteBetButton(number.ToString(), number.ToString(), enabled, value, options); } private bool RouletteBetButton(string label, string commandArg, bool enabled, Color? color = null, params GUILayoutOption[] options) { //IL_0000: 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_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_003f: 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_0017: 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) Color backgroundColor = GUI.backgroundColor; Color contentColor = GUI.contentColor; if (color.HasValue) { GUI.backgroundColor = color.Value; GUI.contentColor = Color.white; } bool result = ActionButton(label, "/rbet " + commandArg, enabled, options); GUI.backgroundColor = backgroundColor; GUI.contentColor = contentColor; return result; } private static bool ActionButton(string label, string command, bool enabled, params GUILayoutOption[] options) { bool enabled2 = GUI.enabled; GUI.enabled = enabled2 && enabled; bool num = GUILayout.Button(label, options); GUI.enabled = enabled2; if (!num) { return false; } CommandHandler.TryRunLocalCasinoMacro(command); return true; } private static void DrawInfoLine(string label, string value) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(label + ":", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }); GUILayout.Label(value, Array.Empty()); GUILayout.EndHorizontal(); } private GameContext RefreshContextForCommand() { if (!Plugin.IsLocalPlayerConfirmedInCasino()) { ClearContext(); return _cachedContext; } _wasInCasino = true; RefreshContext(); return _cachedContext; } private void RefreshContext() { _cachedContext = FindContextInCasino(); _nextContextRefreshTime = Time.unscaledTime + 0.75f; } private void ClearContext() { _wasInCasino = false; _visible = false; _lastContextKey = string.Empty; _cachedContext = EmptyContext(); _nextContextRefreshTime = 0f; } private static GameContext EmptyContext() { return new GameContext { BlackjackSeat = -1, Key = string.Empty }; } private static GameContext FindContextInCasino() { GameContext result = EmptyContext(); Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return result; } BlackjackTable[] array = Object.FindObjectsOfType(); foreach (BlackjackTable blackjackTable in array) { if (!((Object)(object)blackjackTable == (Object)null)) { int seatForPlayer = blackjackTable.GetSeatForPlayer(mainPlayer); if (seatForPlayer >= 0) { result.BlackjackTable = blackjackTable; result.BlackjackSeat = seatForPlayer; result.Key = "blackjack:" + ((Object)blackjackTable).name + ":" + seatForPlayer; return result; } } } RouletteTable[] array2 = Object.FindObjectsOfType(); foreach (RouletteTable rouletteTable in array2) { if (!((Object)(object)rouletteTable == (Object)null) && rouletteTable.IsPlayerAtTable(mainPlayer)) { result.RouletteTable = rouletteTable; result.Key = "roulette:" + ((Object)rouletteTable).name; return result; } } return result; } private static string FormatBlackjackHost(BlackjackTable table) { Player hostPlayer = table.GetHostPlayer(); if ((Object)(object)hostPlayer == (Object)null) { return "(none)"; } if (table.HostSeatIndex < 0) { return FormatPlayerName(hostPlayer, "Host"); } return string.Format("{0} (Seat {1})", FormatPlayerName(hostPlayer, "Host"), table.HostSeatIndex + 1); } private static string FormatBlackjackTurn(BlackjackTable table) { if (!table.RoundInProgress) { return "None"; } int currentTurnSeat = table.CurrentTurnSeat; if (currentTurnSeat >= 0) { Player seatedPlayer = table.GetSeatedPlayer(currentTurnSeat); return $"{FormatPlayerName(seatedPlayer, $"Seat {currentTurnSeat + 1}")} (Seat {currentTurnSeat + 1})"; } return "Dealer"; } private static string FormatBlackjackTurnEnded(BlackjackTable table, int seat) { BlackjackHand blackjackHand = table.Hands[seat]; if (!table.RoundInProgress && blackjackHand.Cards.Count > 0) { return "Yes"; } if (table.CurrentTurnSeat == seat) { return "No"; } if (blackjackHand.IsDone) { return "Yes"; } if (!table.RoundInProgress) { return "No"; } return "Waiting"; } private static string FormatBlackjackRound(BlackjackTable table, int seat) { if (table.RoundInProgress) { return "In Progress"; } if (table.DealerHand.Cards.Count > 0 || table.Hands[seat].Cards.Count > 0) { return "Complete"; } if (!table.AllSeatedPlayersReady) { return "Waiting"; } return "Ready To Deal"; } private static int CountBlackjackPlayers(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 string FormatRouletteTurn(RouletteTable table, Player me) { if (table.IsSpinning) { return "Wheel"; } if (table.AllPlayersReady()) { return "Host Spin"; } if (!table.IsPlayerReady(me)) { return "Place Bets"; } return "Waiting For Players"; } private static string FormatRouletteRound(RouletteTable table) { if (table.IsSpinning) { return "Spinning"; } if (table.LastWinningNumber >= 0) { return "Complete"; } return "Betting"; } private static string FormatRouletteResult(int number) { if (number < 0) { return "(none)"; } 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 network_nickname = player.Network_nickname; if (!string.IsNullOrWhiteSpace(network_nickname)) { return network_nickname; } return fallback; } private void EnsureStyles() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) //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_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown //IL_0080: 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_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown //IL_00ae: Expected O, but got Unknown if (_titleStyle == null) { _titleStyle = new GUIStyle(GUI.skin.label) { fontSize = 18, fontStyle = (FontStyle)1 }; _titleStyle.normal.textColor = Color.white; _headerStyle = new GUIStyle(GUI.skin.label) { fontSize = 14, fontStyle = (FontStyle)1 }; _headerStyle.normal.textColor = new Color(1f, 0.86f, 0.38f); _boxStyle = new GUIStyle(GUI.skin.box) { padding = new RectOffset(8, 8, 8, 8) }; } } private void ClampWindowToScreen() { float num = Mathf.Max((float)Screen.width, 476f); float num2 = Mathf.Max((float)Screen.height, 376f); ((Rect)(ref _windowRect)).width = Mathf.Max(460f, Mathf.Min(((Rect)(ref _windowRect)).width, num - 16f)); ((Rect)(ref _windowRect)).height = Mathf.Max(360f, Mathf.Min(((Rect)(ref _windowRect)).height, num2 - 16f)); ((Rect)(ref _windowRect)).x = Mathf.Clamp(((Rect)(ref _windowRect)).x, 0f, Mathf.Max(0f, num - ((Rect)(ref _windowRect)).width)); ((Rect)(ref _windowRect)).y = Mathf.Clamp(((Rect)(ref _windowRect)).y, 0f, Mathf.Max(0f, num2 - ((Rect)(ref _windowRect)).height)); } } public class SlotBetConfirm : MonoBehaviour { private const float FALLBACK_RADIUS = 2.5f; private const float PROXIMITY_POLL_SECONDS = 0.1f; private SlotMachine? _machine; private BoxCollider? _collider; private bool _playerNearby; private float _nextProximityPollTime; private static bool _warnedAboutMissingCollider; public void Setup(SlotMachine machine) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: 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) _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.IsLocalPlayerConfirmedInCasino()) { if (_playerNearby) { _playerNearby = false; } return; } Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } if (Time.time >= _nextProximityPollTime) { _nextProximityPollTime = Time.time + 0.1f; 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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_collider != (Object)null) { Bounds bounds = ((Collider)_collider).bounds; return ((Bounds)(ref bounds)).Contains(((Component)player).transform.position); } return Vector3.Distance(((Component)this).transform.position, ((Component)player).transform.position) < 2.5f; } } public class SlotMachine : MonoBehaviour { public bool InUse; public float NextInputTime; 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 bool _logicInitialized; private bool _presentationInitialized; private bool _hasPendingRemotePresentation; private int _pendingRemoteReel0; private int _pendingRemoteReel1; private int _pendingRemoteReel2; 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() { if (!_logicInitialized) { _logicInitialized = true; _lever = ((Component)this).transform.Find("Lever"); } } internal void EnsurePresentationInitialized() { if (Plugin.IsHeadlessServer || !CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino) { return; } Init(); bool flag = false; if (!_presentationInitialized) { 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!"); return; } BuildBackdrop(val); BuildThreeReels(val); if ((Object)(object)val2 != (Object)null) { BuildTopScreen(val2); } _presentationInitialized = true; flag = true; } _symbolMaterials = (Material?[]?)(object)new Material[6] { MatCherry, MatLemon, MatOrange, MatStar, MatDiamond, MatSeven }; if (flag) { SetBackdrop(MatIdleBlank); for (int i = 0; i < 3; i++) { SetReel(i, MatIdleBlank); } SetTopScreen(MatIdle); } ApplyPerformanceSettings(); } private void BuildBackdrop(Transform anchor) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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) 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_007a: 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) //IL_00a5: 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_003b: 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_005b: 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 (!CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino) { return; } EnsurePresentationInitialized(); 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 (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; } _pendingRemoteReel0 = r1; _pendingRemoteReel1 = r2; _pendingRemoteReel2 = r3; _hasPendingRemotePresentation = true; if (InUse) { Plugin.Log.LogDebug("[Slots] Retained newest remote result for '" + ((Object)this).name + "' until the current presentation finishes."); } else if (Plugin.IsHeadlessServer || !CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino) { Plugin.Log.LogDebug("[Slots] Recorded remote result for '" + ((Object)this).name + "' outside the casino without starting presentation coroutines."); } else { StartRemotePresentation(); } } internal void RestoreRetainedPresentation() { if (_hasPendingRemotePresentation && !InUse && !Plugin.IsHeadlessServer && CasinoRuntimeActivity.IsLocalPlayerConfirmedInCasino) { EnsurePresentationInitialized(); if (_presentationInitialized && _symbolMaterials != null) { int pendingRemoteReel = _pendingRemoteReel0; int pendingRemoteReel2 = _pendingRemoteReel1; int pendingRemoteReel3 = _pendingRemoteReel2; _hasPendingRemotePresentation = false; SetReel(0, _symbolMaterials[pendingRemoteReel]); SetReel(1, _symbolMaterials[pendingRemoteReel2]); SetReel(2, _symbolMaterials[pendingRemoteReel3]); (float, string, bool) tuple = CalculatePayout(pendingRemoteReel, pendingRemoteReel2, pendingRemoteReel3); bool flag = pendingRemoteReel == 4 && pendingRemoteReel2 == 4 && pendingRemoteReel3 == 4; SetTopScreen(flag ? MatJackpot : ((tuple.Item1 > 0f) ? MatWin : MatIdle)); ApplyPerformanceSettings(); Plugin.Log.LogDebug("[Slots] Restored retained result for '" + ((Object)this).name + "' without " + $"locking gameplay -> [{pendingRemoteReel}, {pendingRemoteReel2}, {pendingRemoteReel3}]."); } } } private void StartRemotePresentation() { if (_hasPendingRemotePresentation && !InUse) { EnsurePresentationInitialized(); if (_presentationInitialized) { int pendingRemoteReel = _pendingRemoteReel0; int pendingRemoteReel2 = _pendingRemoteReel1; int pendingRemoteReel3 = _pendingRemoteReel2; _hasPendingRemotePresentation = false; InUse = true; ApplyPerformanceSettings(); Plugin.Log.LogInfo($"[Slots] Mirroring remote spin on '{((Object)this).name}' -> [{pendingRemoteReel}, {pendingRemoteReel2}, {pendingRemoteReel3}]."); ((MonoBehaviour)this).StartCoroutine(PlaySlotsRemote(pendingRemoteReel, pendingRemoteReel2, pendingRemoteReel3)); } } } private static bool IsFruit(int idx) { if (idx != 0 && idx != 1) { return idx == 2; } return true; } private static bool IsStarOrSeven(int idx) { if (idx != 3) { return idx == 5; } return true; } private (float multiplier, string tier, bool bigWin) CalculatePayout(int r1, int r2, int r3) { if (r1 == r2 && r2 == r3) { if (r1 == 4) { return (multiplier: 500f, tier: "JACKPOT!", bigWin: true); } if (IsStarOrSeven(r1)) { return (multiplier: 4f, tier: "Triple Match!", bigWin: false); } return (multiplier: 2f, tier: "Triple Fruit!", bigWin: false); } int num = 0; int[] array = new int[3] { r1, r2, r3 }; for (int i = 0; i < array.Length; i++) { if (array[i] == 4) { num++; } } if (num == 2) { return (multiplier: 4f, tier: "Two Diamonds!", bigWin: false); } if (HasExactlyTwoOfSame(r1, r2, r3, out var matchedIdx)) { if (IsStarOrSeven(matchedIdx)) { return (multiplier: 1f, tier: "Near Miss!", bigWin: false); } if (IsFruit(matchedIdx)) { return (multiplier: 0.5f, tier: "Fruit Pair", bigWin: false); } } return (multiplier: 0f, tier: "No match", bigWin: 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; } private IEnumerator PlaySlotsLocal(int wager, PlayerInventory inventory, int r1, int r2, int r3) { (float, string, bool) tuple = CalculatePayout(r1, r2, r3); float multiplier = tuple.Item1; string tier = tuple.Item2; bool item = tuple.Item3; bool isJackpot = r1 == 4 && r2 == 4 && r3 == 4; yield return ((MonoBehaviour)this).StartCoroutine(RunSpinVisuals(r1, r2, r3, isJackpot, item, multiplier > 0f)); string arg = "[ " + _symbols[r1] + " | " + _symbols[r2] + " | " + _symbols[r3] + " ]"; if (multiplier > 0f) { float slotsPayoutMultiplier = Plugin.SlotsPayoutMultiplier; int num = ((wager > 0) ? Mathf.FloorToInt((float)wager * multiplier * slotsPayoutMultiplier) : 0); if (num > 0) { inventory.Network_heldCurrency += num; } Plugin.ShowHUDInfo($"{arg}\n{tier} Won {num} Crowns!"); Plugin.Log.LogInfo($"[Slots] {tier} — bet {wager}, payout {num} " + $"({multiplier:F2}x base, config {slotsPayoutMultiplier:F2}x)."); } else { Plugin.ShowHUDError($"{arg}\nNo match. Lost {wager} Crowns."); Plugin.Log.LogInfo($"[Slots] Loss — bet {wager}."); } yield return (object)new WaitForSeconds(1f); for (int i = 0; i < 3; i++) { SetReel(i, MatIdleBlank); } SetTopScreen(MatIdle); InUse = false; _localPlayerSlotBusyUntil = 0f; ApplyPerformanceSettings(); RestoreRetainedPresentation(); } private IEnumerator PlaySlotsRemote(int r1, int r2, int r3) { (float multiplier, string tier, bool bigWin) tuple = CalculatePayout(r1, r2, r3); float item = tuple.multiplier; bool item2 = tuple.bigWin; bool isJackpot = r1 == 4 && r2 == 4 && r3 == 4; yield return ((MonoBehaviour)this).StartCoroutine(RunSpinVisuals(r1, r2, r3, isJackpot, item2, item > 0f)); yield return (object)new WaitForSeconds(1f); for (int i = 0; i < 3; i++) { SetReel(i, MatIdleBlank); } SetTopScreen(MatIdle); InUse = false; ApplyPerformanceSettings(); RestoreRetainedPresentation(); } internal void ApplyPerformanceSettings() { if (_presentationInitialized) { bool screenRenderersEnabled = !Plugin.ReduceIdleSlotVisuals || InUse; SetScreenRenderersEnabled(screenRenderersEnabled); } } private IEnumerator RunSpinVisuals(int r1, int r2, int r3, bool isJackpot, bool bigWin, bool isWin) { if ((Object)(object)_lever != (Object)null) { ((MonoBehaviour)this).StartCoroutine(AnimateLever()); } SetTopScreen(MatIdleBlank); int[] results = new int[3] { r1, r2, r3 }; float[] array = new float[3] { 1.5f, 2f, 2.5f }; for (int i = 0; i < 3; i++) { ((MonoBehaviour)this).StartCoroutine(SpinReel(i, array[i], results[i])); } yield return (object)new WaitForSeconds(array[2] + 0.3f); if (isWin) { Material topScreen = (isJackpot ? MatJackpot : MatWin); SetTopScreen(topScreen); if (isJackpot) { yield return ((MonoBehaviour)this).StartCoroutine(FlashAllReels(MatJackpot, 6, 0.15f, results)); } else if (bigWin) { yield return ((MonoBehaviour)this).StartCoroutine(FlashAllReels(MatWin, 4, 0.2f, results)); } else { yield return (object)new WaitForSeconds(1.5f); } } else { yield return (object)new WaitForSeconds(1.5f); } } private IEnumerator SpinReel(int reelIndex, float duration, int finalResult) { if (_symbolMaterials == null) { yield break; } float elapsed = 0f; float nextFlicker = 0f; while (elapsed < duration) { if (elapsed >= nextFlicker) { SetReel(reelIndex, _symbolMaterials[Random.Range(0, _symbolMaterials.Length)]); float num = elapsed / duration; float num2 = Mathf.Lerp(0.05f, 0.35f, num); nextFlicker = elapsed + num2; } elapsed += Time.deltaTime; yield return null; } SetReel(reelIndex, _symbolMaterials[finalResult]); } private IEnumerator FlashAllReels(Material? flashMat, int times, float interval, int[] results) { if (_symbolMaterials == null) { yield break; } for (int t = 0; t < times; t++) { for (int i = 0; i < 3; i++) { SetReel(i, flashMat); } yield return (object)new WaitForSeconds(interval); for (int j = 0; j < 3; j++) { SetReel(j, _symbolMaterials[results[j]]); } yield return (object)new WaitForSeconds(interval); } for (int k = 0; k < 3; k++) { SetReel(k, flashMat); } } 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]).sharedMaterial = mat; } } private void SetBackdrop(Material? mat) { if (!((Object)(object)_backdropRenderer == (Object)null) && !((Object)(object)mat == (Object)null)) { ((Renderer)_backdropRenderer).sharedMaterial = mat; } } private void SetTopScreen(Material? mat) { if (!((Object)(object)_topScreenRenderer == (Object)null) && !((Object)(object)mat == (Object)null)) { ((Renderer)_topScreenRenderer).sharedMaterial = 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_00cc: 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_00d4: 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_00da: 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_00e6: 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_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0102: 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)); if (num <= 2.5f) { return num2 <= 0.25f; } return false; } private static bool IsNearAnchor(Renderer renderer, Transform? anchor, float maxDistance) { //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_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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) && ContainsAnyScreenName(NormalizeScreenName(((Object)val).name), "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; } private IEnumerator AnimateLever() { if (!((Object)(object)_lever == (Object)null)) { float elapsed = 0f; float duration = 0.3f; Quaternion start = _lever.localRotation; Quaternion pulled = start * Quaternion.Euler(-30f, 0f, 0f); while (elapsed < duration) { elapsed += Time.deltaTime; _lever.localRotation = Quaternion.Lerp(start, pulled, elapsed / duration); yield return null; } yield return (object)new WaitForSeconds(0.3f); elapsed = 0f; while (elapsed < duration) { elapsed += Time.deltaTime; _lever.localRotation = Quaternion.Lerp(pulled, start, elapsed / duration); yield return null; } _lever.localRotation = start; } } } } 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) { if (number > 0) { return RedNumbers.Contains(number); } return false; } public static bool IsBlack(int number) { if (number > 0) { return !RedNumbers.Contains(number); } return false; } public static bool IsEven(int number) { if (number > 0) { return number % 2 == 0; } return false; } public static bool IsOdd(int number) { if (number > 0) { return number % 2 != 0; } return false; } public static bool IsLow(int number) { if (number >= 1) { return number <= 18; } return false; } public static bool IsHigh(int number) { if (number >= 19) { return number <= 36; } return false; } 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) { switch (betType) { case BetType.Number: if (betTarget != winningNumber) { return 0; } return 36; case BetType.Red: if (!IsRed(winningNumber)) { return 0; } return 2; case BetType.Black: if (!IsBlack(winningNumber)) { return 0; } return 2; case BetType.Even: if (!IsEven(winningNumber)) { return 0; } return 2; case BetType.Odd: if (!IsOdd(winningNumber)) { return 0; } return 2; case BetType.Low: if (!IsLow(winningNumber)) { return 0; } return 2; case BetType.High: if (!IsHigh(winningNumber)) { return 0; } return 2; case BetType.Dozen: if (DozenOf(winningNumber) != betTarget) { return 0; } return 3; case BetType.Column: if (ColumnOf(winningNumber) != betTarget) { return 0; } return 3; default: return 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 { 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; private bool _isSpinning; private bool _betsLocked; private int _lastWinningNumber = -1; private Transform? _spinner; private float _ghostCheckTimer; private readonly Dictionary _lastActivityTime = new Dictionary(); private readonly Dictionary _afkWarningShown = new Dictionary(); private readonly Dictionary _afkFinalWarningShown = new Dictionary(); private bool _coinFlipSpin; 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 == 0L) { return null; } if (_players.TryGetValue(_tableHostSteam64, out Player value)) { return value; } return BJNetcode.FindPlayerBySteam64(_tableHostSteam64); } public void Init() { RNNetcode.RegisterTable(this); _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 == 0L) { _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 != 0L) { 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) { if (_tableHostSteam64 != 0L) { return GetSteam64(player) == _tableHostSteam64; } return false; } 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); if (!_bets.TryGetValue(steam, out List value)) { return new List(); } return value; } 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 (_players.Count == 0) { return; } _ghostCheckTimer += Time.deltaTime; if (_ghostCheckTimer < 1f) { return; } _ghostCheckTimer = 0f; if (!BJNetcode.AmHostFresh()) { return; } 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 { } } private IEnumerator RunRound(int winningNumber) { _lastWinningNumber = winningNumber; _isSpinning = true; _betsLocked = true; bool showHud = LocalPlayerIsParticipant(); if (showHud) { Plugin.ShowHUDInfo("Wheel spinning! No more bets!"); } yield return ((MonoBehaviour)Plugin.Instance).StartCoroutine(RunSpinAnimation(winningNumber)); yield return (object)new WaitForSeconds(1.5f); string text = ((winningNumber == 0) ? "Green" : (RouletteLogic.IsRed(winningNumber) ? "Red" : "Black")); if (showHud) { Plugin.ShowHUDInfo($"Result: {winningNumber} ({text})!"); } Plugin.Log.LogInfo($"[Roulette] '{((Object)this).name}': result = {winningNumber} ({text})."); yield return (object)new WaitForSeconds(2f); ResolvePayouts(winningNumber); yield return (object)new WaitForSeconds(3f); foreach (ulong item in new List(_readyState.Keys)) { _readyState[item] = false; } foreach (ulong item2 in new List(_bets.Keys)) { _bets[item2].Clear(); } _betsLocked = false; _isSpinning = false; if (showHud) { Plugin.ShowHUD("Round over! Place new bets with /rbet and /rstandby to play again."); } Plugin.Log.LogInfo("[Roulette] '" + ((Object)this).name + "': round complete."); } 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 result = 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) { result = placedBet.BetTarget; flag = true; } } if (!flag) { return fallback; } return result; } private IEnumerator RunSpinAnimation(int winningNumber) { if ((Object)(object)_spinner == (Object)null) { Plugin.Log.LogWarning("[Roulette] No spinner transform — skipping animation."); yield return (object)new WaitForSeconds(10f); yield break; } if (!Plugin.IsLocalPlayerConfirmedInCasino() || HostCasinoVisibilityWatcher.IsHeadlessServer) { yield return (object)new WaitForSeconds(10f); if (Plugin.IsLocalPlayerConfirmedInCasino() && (Object)(object)_spinner != (Object)null) { int num = RouletteLogic.WheelSlotIndexOf(winningNumber); Vector3 eulerAngles = _spinner.eulerAngles; eulerAngles.y = (float)num * 9.72973f + 0f; _spinner.eulerAngles = eulerAngles; } yield break; } _coinFlipSpin = Random.Range(0, 10000) == 0; if (_coinFlipSpin) { Plugin.Log.LogInfo("[Roulette] COIN FLIP SPIN activated (0.01% easter egg)!"); } int slotIndex = RouletteLogic.WheelSlotIndexOf(winningNumber); float landAngle = (float)slotIndex * 9.72973f + 0f; float elapsed = 0f; while (elapsed < 1.5f) { elapsed += Time.deltaTime; float num2 = elapsed / 1.5f; float num3 = Mathf.Lerp(0f, 360f, num2); ApplySpinRotation(num3 * Time.deltaTime); yield return null; } elapsed = 0f; while (elapsed < 5f) { elapsed += Time.deltaTime; ApplySpinRotation(360f * Time.deltaTime); yield return null; } elapsed = 0f; while (elapsed < 3.5f) { elapsed += Time.deltaTime; float num4 = elapsed / 3.5f; float num3 = Mathf.Lerp(360f, 0f, num4); ApplySpinRotation(num3 * Time.deltaTime); yield return null; } if (!_coinFlipSpin) { Vector3 eulerAngles2 = _spinner.eulerAngles; eulerAngles2.y = landAngle; _spinner.eulerAngles = eulerAngles2; } _coinFlipSpin = false; Plugin.Log.LogInfo($"[Roulette] Spinner landed on slot {slotIndex} " + $"(number {winningNumber}, angle {landAngle:F1} deg)."); } private void ApplySpinRotation(float degrees) { //IL_0035: 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 RebuildResultPresentation() { //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_005a: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_spinner == (Object)null) && _lastWinningNumber >= 0 && !HostCasinoVisibilityWatcher.IsHeadlessServer && Plugin.IsLocalPlayerConfirmedInCasino()) { int num = RouletteLogic.WheelSlotIndexOf(_lastWinningNumber); Vector3 eulerAngles = _spinner.eulerAngles; eulerAngles.y = (float)num * 9.72973f + 0f; _spinner.eulerAngles = eulerAngles; } } 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 == 0L) { _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 != 0L) { _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(); } } public IEnumerator RunRoundRemote(int winningNumber) { _lastWinningNumber = winningNumber; _isSpinning = true; _betsLocked = true; bool showHud = LocalPlayerIsParticipant(); if (showHud) { Plugin.ShowHUDInfo("Wheel spinning! No more bets!"); } yield return ((MonoBehaviour)Plugin.Instance).StartCoroutine(RunSpinAnimation(winningNumber)); yield return (object)new WaitForSeconds(1.5f); string arg = ((winningNumber == 0) ? "Green" : (RouletteLogic.IsRed(winningNumber) ? "Red" : "Black")); if (showHud) { Plugin.ShowHUDInfo($"Result: {winningNumber} ({arg})!"); } yield return (object)new WaitForSeconds(5f); foreach (ulong item in new List(_readyState.Keys)) { _readyState[item] = false; } foreach (ulong item2 in new List(_bets.Keys)) { _bets[item2].Clear(); } _betsLocked = false; _isSpinning = false; if (showHud) { Plugin.ShowHUD("Round over! Place new bets with /rbet and /rstandby to play again."); } } 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 OnDestroy() { RNNetcode.UnregisterTable(this); } 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 const float PROXIMITY_POLL_SECONDS = 0.1f; private RouletteTable? _table; private BoxCollider? _collider; private bool _playerNearby; private float _nextInputTime; private float _nextProximityPollTime; public void Setup(RouletteTable table) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007d: 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.IsLocalPlayerConfirmedInCasino()) { if (_playerNearby) { _playerNearby = false; } return; } Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } if (Time.time >= _nextProximityPollTime) { _nextProximityPollTime = Time.time + 0.1f; 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.AmHostFresh()) { _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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_collider != (Object)null) { Bounds bounds = ((Collider)_collider).bounds; return ((Bounds)(ref bounds)).Contains(((Component)player).transform.position); } return Vector3.Distance(((Component)this).transform.position, ((Component)player).transform.position) < 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.AmHostFresh()) { 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 { Plugin.ShowGameFeed(Object.FindObjectOfType(), 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 = false; private static readonly Dictionary _tablesByName = new Dictionary(StringComparer.Ordinal); private static ChatBehaviour? _cachedChatBehaviour; public static void RegisterTable(RouletteTable table) { if (!((Object)(object)table == (Object)null) && !string.IsNullOrEmpty(((Object)table).name)) { _tablesByName[((Object)table).name] = table; } } public static void UnregisterTable(RouletteTable table) { if (!((Object)(object)table == (Object)null) && !string.IsNullOrEmpty(((Object)table).name) && _tablesByName.TryGetValue(((Object)table).name, out RouletteTable value) && value == table) { _tablesByName.Remove(((Object)table).name); } } public static void ClearSceneRegistry() { _tablesByName.Clear(); _cachedChatBehaviour = null; } public static void RebuildRegisteredTableVisuals() { foreach (RouletteTable value in _tablesByName.Values) { if ((Object)(object)value != (Object)null) { value.RebuildResultPresentation(); } } } public static void Initialize() { //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_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_008a: 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_0095: Expected O, but got Unknown //IL_00ab: 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) //IL_00b6: Expected O, but got Unknown //IL_00cc: 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_00d7: Expected O, but got Unknown //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_00f8: Expected O, but got Unknown //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Expected O, but got Unknown //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Expected O, but got Unknown //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Expected O, but got Unknown //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Expected O, but got Unknown //IL_0192: 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_019d: Expected O, but got Unknown //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Expected O, but got Unknown //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01df: 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) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RJoinTableRequest { TableName = tableName, StakeAmount = stakeAmount }); }, "SendJoinTableRequest"); } public static void SendLeaveTableRequest(string tableName) { SendLeaveTableRequest(tableName, forfeit: false); } public static void SendLeaveTableRequest(string tableName, bool forfeit) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RLeaveTableRequest { TableName = tableName, Forfeit = forfeit }); }, "SendLeaveTableRequest"); } public static void SendReadyToggleRequest(string tableName, bool ready) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RReadyToggleRequest { TableName = tableName, Ready = ready }); }, "SendReadyToggleRequest"); } public static void SendSpinRequest(string tableName) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RSpinRequest { TableName = tableName }); }, "SendSpinRequest"); } public static void SendPlaceBetRequest(string tableName, BetType betType, int betTarget, int amount) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RPlaceBetRequest { TableName = tableName, BetType = (int)betType, BetTarget = betTarget, Amount = amount }); }, "SendPlaceBetRequest"); } public static void SendClearBetsRequest(string tableName) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RClearBetsRequest { TableName = tableName }); }, "SendClearBetsRequest"); } public static void BroadcastPlayerJoined(string tableName, ulong steam64, int stake, bool becameHost, int count) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RPlayerJoined { TableName = tableName, PlayerSteam64 = steam64, StakeAmount = stake, BecameHost = becameHost, PlayerCount = count }); }, "BroadcastPlayerJoined"); } public static void BroadcastPlayerLeft(string tableName, ulong steam64, ulong newHostSteam64, int count) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RPlayerLeft { TableName = tableName, PlayerSteam64 = steam64, NewHostSteam64 = newHostSteam64, PlayerCount = count }); }, "BroadcastPlayerLeft"); } public static void BroadcastReadyChanged(string tableName, ulong steam64, bool ready) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RReadyChanged { TableName = tableName, PlayerSteam64 = steam64, Ready = ready }); }, "BroadcastReadyChanged"); } public static void BroadcastBetPlaced(string tableName, ulong steam64, BetType betType, int betTarget, int amount) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RBetPlaced { TableName = tableName, PlayerSteam64 = steam64, BetType = (int)betType, BetTarget = betTarget, Amount = amount }); }, "BroadcastBetPlaced"); } public static void BroadcastBetsCleared(string tableName, ulong steam64) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RBetsCleared { TableName = tableName, PlayerSteam64 = steam64 }); }, "BroadcastBetsCleared"); } public static void BroadcastSpinStarted(string tableName, int winningNumber) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RSpinStarted { TableName = tableName, WinningNumber = winningNumber }); }, "BroadcastSpinStarted"); } public static void BroadcastPayoutsResolved(string tableName, int winningNumber, List results) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RPayoutsResolved { TableName = tableName, WinningNumber = winningNumber, Results = (results ?? new List()) }); }, "BroadcastPayoutsResolved"); } public static void SendActionRejected(ulong targetSteam64, string tableName, string action, string reason) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket(targetSteam64, (PacketBase)(object)new RActionRejected { TableName = tableName, TargetSteam64 = targetSteam64, Action = (action ?? "action"), Reason = (reason ?? "rejected") }, (CompressionType)0, CompressionLevel.Fastest); }, "SendActionRejected"); } private static void OnJoinTableRequest(PacketHeader header, PacketBase packet) { if (!BJNetcode.AmHostFresh() || !(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.AmHostFresh() || !(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.AmHostFresh() || !(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.AmHostFresh() || !(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.AmHostFresh() || !(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.AmHostFresh() || !(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.AmHostFresh()) { 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.AmHostFresh()) { 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.AmHostFresh()) { 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 chatBehaviour = GetChatBehaviour(); if ((Object)(object)chatBehaviour != (Object)null) { string text3 = ((rReadyChanged.PlayerSteam64 == localSteam) ? "You are" : "A player is"); Plugin.ShowGameFeed(chatBehaviour, "[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.AmHostFresh()) { 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.AmHostFresh()) { 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.AmHostFresh()) { 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; } if (_tablesByName.TryGetValue(tableName, out RouletteTable value)) { if ((Object)(object)value != (Object)null) { return value; } _tablesByName.Remove(tableName); } RouletteTable[] array = Object.FindObjectsOfType(); foreach (RouletteTable rouletteTable in array) { if (!(((Object)rouletteTable).name != tableName)) { _tablesByName[tableName] = rouletteTable; return rouletteTable; } } return null; } private static ChatBehaviour? GetChatBehaviour() { if ((Object)(object)_cachedChatBehaviour != (Object)null) { return _cachedChatBehaviour; } _cachedChatBehaviour = Object.FindObjectOfType(); return _cachedChatBehaviour; } 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; } } 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 { [CompilerGenerated] private static class <>O { public static PacketListener <0>__OnRoomZoneChatDelivery; public static PacketListener <1>__OnRoomZoneChatNotice; public static PacketListener <2>__OnRoomZoneChatPreferenceUpdate; public static PacketListener <3>__OnRoomZoneChatPreferenceAck; } public const string PLUGIN_GUID = "dev.seth.atlysscasino"; private const int PreferenceSyncAttempts = 30; private const float PreferenceSyncRetrySeconds = 1f; private const int LocalPlayerReadinessAttempts = 120; private const float LocalPlayerReadinessRetrySeconds = 0.5f; private const int SeenDeliveryLimit = 128; private static readonly Dictionary PrivateOutgoingBySteam64 = new Dictionary(); private static readonly HashSet SeenDeliveryIds = new HashSet(); private static readonly Queue SeenDeliveryOrder = new Queue(); private static bool _initialized; private static ulong _deliverySequence; private static int _preferenceRevision; private static int _acknowledgedRevision = -1; private static bool _requestedPreference = true; private static Coroutine? _preferenceSyncCoroutine; private static Coroutine? _localPlayerReadinessCoroutine; private static ulong _lastReadySteam64; private static int _lastReadyPlayerInstanceId; public static void Initialize() { //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_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_008a: 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_0095: Expected O, but got Unknown if (_initialized) { Plugin.Log.LogWarning("[RoomZoneNet] Initialize() called twice - ignoring."); return; } object obj = <>O.<0>__OnRoomZoneChatDelivery; if (obj == null) { PacketListener val = OnRoomZoneChatDelivery; <>O.<0>__OnRoomZoneChatDelivery = val; obj = (object)val; } CodeTalkerNetwork.RegisterListener((PacketListener)obj); object obj2 = <>O.<1>__OnRoomZoneChatNotice; if (obj2 == null) { PacketListener val2 = OnRoomZoneChatNotice; <>O.<1>__OnRoomZoneChatNotice = val2; obj2 = (object)val2; } CodeTalkerNetwork.RegisterListener((PacketListener)obj2); object obj3 = <>O.<2>__OnRoomZoneChatPreferenceUpdate; if (obj3 == null) { PacketListener val3 = OnRoomZoneChatPreferenceUpdate; <>O.<2>__OnRoomZoneChatPreferenceUpdate = val3; obj3 = (object)val3; } CodeTalkerNetwork.RegisterListener((PacketListener)obj3); object obj4 = <>O.<3>__OnRoomZoneChatPreferenceAck; if (obj4 == null) { PacketListener val4 = OnRoomZoneChatPreferenceAck; <>O.<3>__OnRoomZoneChatPreferenceAck = val4; obj4 = (object)val4; } CodeTalkerNetwork.RegisterListener((PacketListener)obj4); _initialized = true; Plugin.Log.LogInfo("[RoomZoneNet] Authenticated room-chat listeners registered."); BeginPreferenceSync(forceNewRevision: true); } internal static bool IsPrivateOutgoingEnabled(ulong steam64) { if (steam64 == 0L) { return true; } ulong localSteam = BJNetcode.GetLocalSteam64(); if (localSteam != 0L && steam64 == localSteam) { return CasinoConfig.RoomZoneChatEnabled; } if (PrivateOutgoingBySteam64.TryGetValue(steam64, out var value)) { return value; } return true; } internal static void PublishLocalPreference() { BeginPreferenceSync(CasinoConfig.RoomZoneChatEnabled != _requestedPreference); } internal static void BeginLocalPlayerReadinessWatch() { if (_initialized && !Plugin.IsHeadlessServer && !((Object)(object)Plugin.Instance == (Object)null)) { if (_localPlayerReadinessCoroutine != null) { ((MonoBehaviour)Plugin.Instance).StopCoroutine(_localPlayerReadinessCoroutine); _localPlayerReadinessCoroutine = null; } _localPlayerReadinessCoroutine = ((MonoBehaviour)Plugin.Instance).StartCoroutine(LocalPlayerReadinessRoutine()); } } internal static void NotifyLocalPlayerReady(Player player) { if (!_initialized || (Object)(object)player == (Object)null || Plugin.IsHeadlessServer) { return; } ulong localSteam = BJNetcode.GetLocalSteam64(); ulong steam = RoomZoneRegistry.GetSteam64(player); if (player != Player._mainPlayer && (localSteam == 0L || steam != localSteam)) { return; } int instanceID = ((Object)player).GetInstanceID(); bool flag = _lastReadyPlayerInstanceId != instanceID; if (localSteam == 0L || _lastReadySteam64 != localSteam || flag || _acknowledgedRevision != _preferenceRevision) { _lastReadySteam64 = localSteam; _lastReadyPlayerInstanceId = instanceID; if (flag) { BeginPreferenceSync(forceNewRevision: true); } else if (_preferenceSyncCoroutine == null) { BeginPreferenceSync(forceNewRevision: false); } } } private static IEnumerator LocalPlayerReadinessRoutine() { yield return null; for (int attempt = 0; attempt < 120; attempt++) { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer != (Object)null) { NotifyLocalPlayerReady(mainPlayer); } if (_preferenceRevision > 0 && _acknowledgedRevision == _preferenceRevision) { break; } yield return (object)new WaitForSecondsRealtime(0.5f); } _localPlayerReadinessCoroutine = null; } internal static bool SendRoomChatDelivery(ulong targetSteam64, ulong senderSteam64, string rawMessage, string roomName, string mapScopeId, string zoneId) { if (!_initialized || !RoomZoneRegistry.IsRoutingHost) { return false; } if (targetSteam64 == 0L || senderSteam64 == 0L) { return false; } if (string.IsNullOrWhiteSpace(rawMessage)) { return false; } if (string.IsNullOrWhiteSpace(zoneId)) { return false; } RoomZoneChatDelivery delivery = new RoomZoneChatDelivery { DeliveryId = NextDeliveryId(), SenderSteam64 = senderSteam64, RawMessage = rawMessage, RoomName = RoomZoneRegistry.SanitizeRoomLabel(roomName), MapScopeId = (mapScopeId ?? string.Empty), ZoneId = (zoneId ?? string.Empty) }; ulong localSteam = BJNetcode.GetLocalSteam64(); if (localSteam != 0L && targetSteam64 == localSteam) { HandleTrustedDelivery(delivery); return true; } return SafeSend(delegate { CodeTalkerNetwork.SendNetworkPacket(targetSteam64, (PacketBase)(object)delivery, (CompressionType)0, CompressionLevel.Fastest); }, "SendRoomChatDelivery"); } internal static bool SendPrivateNotice(ulong targetSteam64, string message, string mapScopeId, string zoneId) { if (!_initialized || !RoomZoneRegistry.IsRoutingHost) { return false; } if (targetSteam64 == 0L || string.IsNullOrWhiteSpace(message)) { return false; } RoomZoneChatNotice notice = new RoomZoneChatNotice { Message = message, MapScopeId = (mapScopeId ?? string.Empty), ZoneId = (zoneId ?? string.Empty) }; ulong localSteam = BJNetcode.GetLocalSteam64(); if (localSteam != 0L && targetSteam64 == localSteam) { RoomZoneRegistry.ReceivePrivateNotice(notice); return true; } return SafeSend(delegate { CodeTalkerNetwork.SendNetworkPacket(targetSteam64, (PacketBase)(object)notice, (CompressionType)0, CompressionLevel.Fastest); }, "SendPrivateNotice"); } private static void OnRoomZoneChatDelivery(PacketHeader header, PacketBase packet) { if (packet is RoomZoneChatDelivery delivery) { if (!header.SenderIsLobbyOwner) { Plugin.Log.LogWarning($"[RoomZoneNet] Ignoring delivery from non-host sender={header.SenderID}."); } else { HandleTrustedDelivery(delivery); } } } private static void HandleTrustedDelivery(RoomZoneChatDelivery delivery) { if (delivery.DeliveryId != 0L && RememberDelivery(delivery.DeliveryId)) { RoomZoneRegistry.ReceiveRoomChatDelivery(delivery); } } private static void OnRoomZoneChatNotice(PacketHeader header, PacketBase packet) { if (packet is RoomZoneChatNotice notice) { if (!header.SenderIsLobbyOwner) { Plugin.Log.LogWarning($"[RoomZoneNet] Ignoring notice from non-host sender={header.SenderID}."); } else { RoomZoneRegistry.ReceivePrivateNotice(notice); } } } private static void OnRoomZoneChatPreferenceUpdate(PacketHeader header, PacketBase packet) { if (packet is RoomZoneChatPreferenceUpdate roomZoneChatPreferenceUpdate && RoomZoneRegistry.IsRoutingHost && header.SenderID != 0L) { PrivateOutgoingBySteam64[header.SenderID] = roomZoneChatPreferenceUpdate.PrivateOutgoingEnabled; RoomZoneChatPreferenceAck ack = new RoomZoneChatPreferenceAck { PrivateOutgoingEnabled = roomZoneChatPreferenceUpdate.PrivateOutgoingEnabled, Revision = roomZoneChatPreferenceUpdate.Revision }; SafeSend(delegate { CodeTalkerNetwork.SendNetworkPacket(header.SenderID, (PacketBase)(object)ack, (CompressionType)0, CompressionLevel.Fastest); }, "SendPreferenceAck"); } } private static void OnRoomZoneChatPreferenceAck(PacketHeader header, PacketBase packet) { if (packet is RoomZoneChatPreferenceAck roomZoneChatPreferenceAck) { if (!header.SenderIsLobbyOwner) { Plugin.Log.LogWarning($"[RoomZoneNet] Ignoring preference ack from non-host sender={header.SenderID}."); } else if (roomZoneChatPreferenceAck.Revision == _preferenceRevision && roomZoneChatPreferenceAck.PrivateOutgoingEnabled == _requestedPreference) { _acknowledgedRevision = roomZoneChatPreferenceAck.Revision; } } } private static void BeginPreferenceSync(bool forceNewRevision) { if (!_initialized || Plugin.IsHeadlessServer) { return; } bool roomZoneChatEnabled = CasinoConfig.RoomZoneChatEnabled; if (!forceNewRevision && roomZoneChatEnabled == _requestedPreference && _acknowledgedRevision == _preferenceRevision) { return; } bool flag = forceNewRevision || roomZoneChatEnabled != _requestedPreference || _preferenceRevision == 0; if (flag) { _requestedPreference = roomZoneChatEnabled; _preferenceRevision++; if (_preferenceRevision <= 0) { _preferenceRevision = 1; } _acknowledgedRevision = -1; } if (RoomZoneRegistry.IsRoutingHost) { ulong localSteam = BJNetcode.GetLocalSteam64(); if (localSteam != 0L) { PrivateOutgoingBySteam64[localSteam] = roomZoneChatEnabled; } _acknowledgedRevision = _preferenceRevision; _preferenceSyncCoroutine = null; } else if ((Object)(object)Plugin.Instance == (Object)null) { TrySendPreferenceUpdate(_preferenceRevision, roomZoneChatEnabled); } else if (_preferenceSyncCoroutine == null || flag) { int preferenceRevision = _preferenceRevision; _preferenceSyncCoroutine = ((MonoBehaviour)Plugin.Instance).StartCoroutine(PreferenceSyncRoutine(preferenceRevision, roomZoneChatEnabled)); } } private static IEnumerator PreferenceSyncRoutine(int revision, bool enabled) { for (int attempt = 0; attempt < 30; attempt++) { if (revision != _preferenceRevision) { yield break; } if (_acknowledgedRevision == revision) { break; } if (RoomZoneRegistry.IsRoutingHost) { ulong localSteam = BJNetcode.GetLocalSteam64(); if (localSteam != 0L) { PrivateOutgoingBySteam64[localSteam] = enabled; } _acknowledgedRevision = revision; break; } TrySendPreferenceUpdate(revision, enabled); yield return (object)new WaitForSecondsRealtime(1f); } if (revision == _preferenceRevision) { _preferenceSyncCoroutine = null; } } private static bool TrySendPreferenceUpdate(int revision, bool enabled) { if ((Object)(object)Player._mainPlayer == (Object)null) { return false; } if (BJNetcode.GetLocalSteam64() == 0L) { return false; } return SafeSend(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RoomZoneChatPreferenceUpdate { PrivateOutgoingEnabled = enabled, Revision = revision }); }, "SendPreferenceUpdate"); } private static ulong NextDeliveryId() { _deliverySequence++; if (_deliverySequence == 0L) { _deliverySequence = 1uL; } return _deliverySequence; } private static bool RememberDelivery(ulong deliveryId) { if (!SeenDeliveryIds.Add(deliveryId)) { return false; } SeenDeliveryOrder.Enqueue(deliveryId); while (SeenDeliveryOrder.Count > 128) { ulong item = SeenDeliveryOrder.Dequeue(); SeenDeliveryIds.Remove(item); } return true; } private static bool SafeSend(Action action, string label) { try { action(); return true; } catch (Exception ex) { Plugin.Log.LogError("[RoomZoneNet] " + label + " failed: " + ex.Message); return false; } } } public sealed class RoomZoneChatDelivery : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public ulong DeliveryId { get; set; } [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 MapScopeId { get; set; } = string.Empty; [JsonProperty] public string ZoneId { get; set; } = string.Empty; } public sealed class RoomZoneChatNotice : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public string Message { get; set; } = string.Empty; [JsonProperty] public string MapScopeId { get; set; } = string.Empty; [JsonProperty] public string ZoneId { get; set; } = string.Empty; } public sealed class RoomZoneChatPreferenceUpdate : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public bool PrivateOutgoingEnabled { get; set; } = true; [JsonProperty] public int Revision { get; set; } } public sealed class RoomZoneChatPreferenceAck : PacketBase { public override string PacketSourceGUID => "dev.seth.atlysscasino"; [JsonProperty] public bool PrivateOutgoingEnabled { get; set; } = true; [JsonProperty] public int Revision { get; set; } } } 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_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_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: 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) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new JukeboxAdvanceRequest { ObjectName = (objectName ?? string.Empty), Delta = delta }); }, "SendAdvanceRequest"); } internal static void SendStateRequest(string objectName) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new JukeboxStateRequest { ObjectName = (objectName ?? 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.AmHostFresh() || 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.AmHostFresh() || 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 state = new JukeboxWorldState { ObjectName = (jukeboxStateSync.ObjectName ?? string.Empty), PlaylistStep = jukeboxStateSync.PlaylistStep, Playing = jukeboxStateSync.Playing, ElapsedSeconds = jukeboxStateSync.ElapsedSeconds }; CacheState(state); if (!BJNetcode.AmHostFresh()) { 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 List FaceUpStates { get; } = new List(); public int Bet { get; set; } public bool HasStood { get; set; } public bool HasBusted => BestValue > 21; public bool IsNaturalBlackjack { get { if (Cards.Count == 2) { return BestValue == 21; } return false; } } 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++; } } if (num2 > 0) { return num + 10 <= 21; } return false; } } public bool IsDone { get { if (!HasStood) { return HasBusted; } return true; } } public void Reset() { Cards.Clear(); CardObjects.Clear(); FaceUpStates.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 Renderer Renderer; public int MaterialIndex; public int ColorPropertyId; public Color DefaultColor; public MaterialPropertyBlock PropertyBlock; } private enum StoolState { Vacant, Claimed, ActiveTurn } private const float FALLBACK_RADIUS = 1.5f; private const float PROXIMITY_POLL_SECONDS = 0.1f; 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; private float _nextProximityPollTime; private BlackjackTable? _table; private int _seatIndex = -1; private bool _playerNearby; private BoxCollider? _collider; private readonly List _tintTargets = new List(); private StoolState _lastVisualState; private static bool _warnedAboutMissingCollider = false; public void Setup(BlackjackTable table, int seatIndex) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0091: 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_0098: 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) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_0172: 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_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Expected O, but got Unknown //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_0205: 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) 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[] sharedMaterials = val.sharedMaterials; for (int j = 0; j < sharedMaterials.Length; j++) { Material val2 = sharedMaterials[j]; 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 { Renderer = val, MaterialIndex = j, ColorPropertyId = Shader.PropertyToID(text), DefaultColor = val3, PropertyBlock = new MaterialPropertyBlock() }); 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.IsLocalPlayerConfirmedInCasino()) { if (_playerNearby) { _playerNearby = false; } } else { if ((Object)(object)_table == (Object)null) { return; } Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { return; } if (Time.time >= _nextProximityPollTime) { _nextProximityPollTime = Time.time + 0.1f; 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) && _table.GetSeatForPlayer(player) == _seatIndex) { string name = ((Object)_table).name; if (BJNetcode.AmHostFresh()) { _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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_collider != (Object)null) { Bounds bounds = ((Collider)_collider).bounds; return ((Bounds)(ref bounds)).Contains(((Component)player).transform.position); } return Vector3.Distance(((Component)this).transform.position, ((Component)player).transform.position) < 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_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: 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_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: 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.Renderer == (Object)null)) { Color val = (Color)(_lastVisualState switch { StoolState.ActiveTurn => TURN_STOOL_COLOR, StoolState.Claimed => CLAIMED_STOOL_COLOR, _ => tintTarget.DefaultColor, }); tintTarget.Renderer.GetPropertyBlock(tintTarget.PropertyBlock, tintTarget.MaterialIndex); tintTarget.PropertyBlock.SetColor(tintTarget.ColorPropertyId, val); tintTarget.Renderer.SetPropertyBlock(tintTarget.PropertyBlock, tintTarget.MaterialIndex); } } } 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.AmHostFresh()) { 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(); foreach (BlackjackTable blackjackTable in array) { 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() { return new StartRoundResult { KickedSeats = new List() }; } } 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; private float _currentTurnStartTime; private bool _turnWarningShown; private bool _turnFinalWarningShown; 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; } 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.AmHostFresh()) { 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() { BJNetcode.RegisterBlackjackTable(this); for (int i = 0; i < 5; i++) { Hands[i] = new BlackjackHand(); } Deck.Shuffle(); } private void Update() { if ((RoundInProgress || OccupiedSeatCount != 0) && !(Time.time < _nextGhostPollTime)) { _nextGhostPollTime = Time.time + 1f; if (BJNetcode.AmHostFresh()) { 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? obj = _claimedSeats[i]; bool flag = obj == null; bool flag2 = (Object)(object)obj == (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 anchor = _seatAnchors[seatIndex]; return HostDrawAndDeal(Hands[seatIndex], anchor, faceUp, GetEffectiveCardScale(0.5f), $"seat{seatIndex}", seatIndex); } public Card? DealCardToDealer(bool faceUp) { return HostDrawAndDeal(DealerHand, DealerAnchor, faceUp, GetEffectiveCardScale(0.85f), "dealer", -1); } private float GetEffectiveCardScale(float baseScale) { //IL_0006: 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 (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.AmHostFresh()) { 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) { int count = hand.Cards.Count; hand.Cards.Add(card); hand.FaceUpStates.Add(faceUp); hand.CardObjects.Add(null); if (!ShouldCreateCardPresentation() || (Object)(object)anchor == (Object)null) { Plugin.Log.LogDebug("[Blackjack] Recorded " + card.DisplayName() + " for " + label + " without creating an unavailable/outside card visual."); return true; } GameObject val = CreateCardVisual(anchor, card, faceUp, uniformScale, label, count); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning("[Blackjack] Recorded " + card.DisplayName() + " for " + label + ", but its visual could not be created."); return true; } hand.CardObjects[count] = val; Plugin.Log.LogInfo("[Blackjack] Dealt " + card.DisplayName() + " to " + label + " " + $"(faceUp={faceUp}, scale={uniformScale:F3}, " + $"handValue={hand.BestValue})."); return true; } private static bool ShouldCreateCardPresentation() { if (!Plugin.IsHeadlessServer) { return Plugin.IsLocalPlayerConfirmedInCasino(); } return false; } private GameObject? CreateCardVisual(Transform anchor, Card card, bool faceUp, float uniformScale, string label, int indexInHand) { //IL_0043: 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_0052: 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_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: 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_007d: 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_0087: Unknown result type (might be due to invalid IL or missing references) //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_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: 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_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Plugin.AssetsBundle == (Object)null) { return null; } string prefabName = card.PrefabName; GameObject val = Plugin.AssetsBundle.LoadAsset(prefabName); if ((Object)(object)val == (Object)null) { return null; } float num = uniformScale * 1.3f * 1.6f * 0.2f; Vector3 val2 = -anchor.right * (num * (float)indexInHand); Vector3 val3 = anchor.up * (0.015f * uniformScale * (float)indexInHand); 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}_{indexInHand}_{card.Suit}_{card.Rank}"; Plugin.ScopeToCasinoScene(val7, ((Component)anchor).gameObject.scene); val7.transform.localScale = Vector3.one * uniformScale; (val7.GetComponent() ?? val7.AddComponent()).Init(card, faceUp); return val7; } public void RebuildMissingCardVisuals() { if (!ShouldCreateCardPresentation()) { return; } for (int i = 0; i < 5; i++) { Transform val = _seatAnchors[i]; if (!((Object)(object)val == (Object)null)) { RebuildHandVisuals(Hands[i], val, GetEffectiveCardScale(0.5f), $"seat{i}"); } } if ((Object)(object)DealerAnchor != (Object)null) { RebuildHandVisuals(DealerHand, DealerAnchor, GetEffectiveCardScale(0.85f), "dealer"); } } private void RebuildHandVisuals(BlackjackHand hand, Transform anchor, float scale, string label) { while (hand.CardObjects.Count < hand.Cards.Count) { hand.CardObjects.Add(null); } while (hand.FaceUpStates.Count < hand.Cards.Count) { hand.FaceUpStates.Add(item: true); } for (int i = 0; i < hand.Cards.Count; i++) { if (!((Object)(object)hand.CardObjects[i] != (Object)null)) { GameObject val = CreateCardVisual(anchor, hand.Cards[i], hand.FaceUpStates[i], scale, label, i); if ((Object)(object)val != (Object)null) { hand.CardObjects[i] = val; } } } } 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.AmHostFresh()) { 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() { while (DealerHand.FaceUpStates.Count < DealerHand.Cards.Count) { DealerHand.FaceUpStates.Add(item: true); } while (DealerHand.CardObjects.Count < DealerHand.Cards.Count) { DealerHand.CardObjects.Add(null); } for (int i = 0; i < DealerHand.Cards.Count; i++) { if (DealerHand.FaceUpStates[i]) { continue; } DealerHand.FaceUpStates[i] = true; GameObject val = DealerHand.CardObjects[i]; if ((Object)(object)val != (Object)null) { CardVisual component = val.GetComponent(); if ((Object)(object)component != (Object)null) { component.SetFaceUp(faceUp: true); } } if (BJNetcode.AmHostFresh()) { 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 && DealCardToDealer(faceUp: true) != null) { } 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]; if (!((Object)(object)val == (Object)null)) { return GetSteam64(val); } return 0uL; } 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.AmHostFresh()) { 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.AmHostFresh()) { int heldCurrency = component._heldCurrency; int num = (component.Network_heldCurrency = heldCurrency + amount); Plugin.Log.LogInfo($"[Blackjack] Seat {seatIndex} paid +{amount} Crowns " + $"(direct SyncVar write, {heldCurrency} -> {num})."); } 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(", "); } if (IsCardFaceUp(DealerHand, i)) { 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++) { if (IsCardFaceUp(DealerHand, i)) { 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--; } if (num <= 0) { return "??"; } return num.ToString(); } string text = DealerHand.BestValue.ToString(); if (DealerHand.IsNaturalBlackjack) { text += " Blackjack"; } else if (DealerHand.HasBusted) { text += " BUST"; } return text; } private static bool IsCardFaceUp(BlackjackHand hand, int index) { if (index >= 0 && index < hand.FaceUpStates.Count) { return hand.FaceUpStates[index]; } if (index >= 0 && index < hand.CardObjects.Count && (Object)(object)hand.CardObjects[index] != (Object)null) { CardVisual component = hand.CardObjects[index].GetComponent(); if ((Object)(object)component != (Object)null) { return component.IsFaceUp; } } return true; } 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 anchor; float effectiveCardScale; string text; switch (seatIndex) { case -1: blackjackHand = DealerHand; anchor = DealerAnchor; effectiveCardScale = GetEffectiveCardScale(0.85f); text = "dealer"; break; case 0: case 1: case 2: case 3: case 4: blackjackHand = Hands[seatIndex]; anchor = _seatAnchors[seatIndex]; effectiveCardScale = GetEffectiveCardScale(0.5f); text = $"seat{seatIndex}"; break; default: Plugin.Log.LogError($"[Blackjack] ApplyCardDealt: invalid seat index {seatIndex} 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, anchor, card, faceUp, effectiveCardScale, text); } } public void ApplyCardFlipped(int seatIndex, int indexInHand, bool faceUp) { BlackjackHand blackjackHand; string arg; switch (seatIndex) { case -1: blackjackHand = DealerHand; arg = "dealer"; break; case 0: case 1: case 2: case 3: case 4: blackjackHand = Hands[seatIndex]; arg = $"seat{seatIndex}"; break; default: Plugin.Log.LogError($"[Blackjack] ApplyCardFlipped: invalid seat index {seatIndex} at '{((Object)this).name}'."); return; } if (indexInHand < 0 || indexInHand >= blackjackHand.Cards.Count) { Plugin.Log.LogError($"[Blackjack] ApplyCardFlipped: index {indexInHand} out of range on " + $"{arg} at '{((Object)this).name}' (hand has {blackjackHand.Cards.Count} cards)."); return; } while (blackjackHand.FaceUpStates.Count < blackjackHand.Cards.Count) { blackjackHand.FaceUpStates.Add(item: true); } while (blackjackHand.CardObjects.Count < blackjackHand.Cards.Count) { blackjackHand.CardObjects.Add(null); } blackjackHand.FaceUpStates[indexInHand] = faceUp; 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 num2 = (HostSeatIndex = array[Random.Range(0, num)]); Plugin.Log.LogInfo($"[Blackjack] Host reassigned to seat {num2} 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) { if (seatIndex >= 0 && seatIndex < 5) { return HostSeatIndex == seatIndex; } return false; } 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 { get { if (Rank != Rank.Ace) { return MinValue; } return 11; } } 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_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: 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_014a: 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_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown //IL_005f: 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_00a7: 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) 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).sharedMaterial = ((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_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown string b = name.Trim(); foreach (Transform item in parent) { Transform val = item; if (string.Equals(((Object)val).name.Trim(), b, StringComparison.OrdinalIgnoreCase)) { return val; } } return null; } private string ListChildren() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown List list = new List(); foreach (Transform item in ((Component)this).transform) { Transform val = item; list.Add(((Object)val).name); } if (list.Count <= 0) { return "(none)"; } return string.Join(", ", list); } } 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; if (!(num > 0f)) { return 0f; } return num; } } [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 = false; private const float HostRoleCacheSeconds = 2f; private static bool _cachedAmHost; private static bool _hostRoleCacheValid; private static float _hostRoleCacheExpiresAt; private static ulong _cachedLocalSteam64; private static int _cachedLocalPlayerInstanceId; private static readonly Dictionary _playersBySteam64 = new Dictionary(); private static readonly Dictionary _tablesByName = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary _slotsByName = new Dictionary(StringComparer.Ordinal); private static PropertyInfo? _playerSteamIdProp; private static FieldInfo? _playerSteamIdField; private static PropertyInfo? _playerIsHostProp; private static bool _reflectionResolved = false; 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 = false; public static void Initialize() { //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_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_008a: 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_0095: Expected O, but got Unknown //IL_00ab: 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) //IL_00b6: Expected O, but got Unknown //IL_00cc: 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_00d7: Expected O, but got Unknown //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_00f8: Expected O, but got Unknown //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Expected O, but got Unknown //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Expected O, but got Unknown //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Expected O, but got Unknown //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Expected O, but got Unknown //IL_0192: 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_019d: Expected O, but got Unknown //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Expected O, but got Unknown //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Expected O, but got Unknown //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Expected O, but got Unknown //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Expected O, but got Unknown //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Expected O, but got Unknown //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Expected O, but got Unknown //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Expected O, but got Unknown //IL_029a: Unknown result type (might be due to invalid IL or missing references) //IL_029f: Unknown result type (might be due to invalid IL or missing references) //IL_02a5: Expected O, but got Unknown //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: 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() { if (HasImmediateServerAuthority()) { return true; } if (_hostRoleCacheValid && Time.unscaledTime < _hostRoleCacheExpiresAt) { return _cachedAmHost; } return RefreshHostRoleCache(); } public static bool AmHostFresh() { return RefreshHostRoleCache(); } private static bool RefreshHostRoleCache() { bool result = (_cachedAmHost = ResolveHostAuthority()); _hostRoleCacheValid = true; _hostRoleCacheExpiresAt = Time.unscaledTime + 2f; return result; } private static bool ResolveHostAuthority() { if (HasImmediateServerAuthority()) { return true; } ResolveReflection(); ulong num = TryGetLobbyOwnerSteam64(); ulong num2 = TryGetMyOwnSteam64(); if (num2 == 0L) { num2 = GetLocalSteam64(); } if (num != 0L && num2 != 0L) { 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) { return (bool)value; } } catch { } } return false; } private static bool HasImmediateServerAuthority() { if (Plugin.IsHeadlessServer) { return true; } try { if (NetworkServer.active) { return true; } } catch { } return false; } public static ulong GetLocalSteam64() { Player mainPlayer = Player._mainPlayer; if ((Object)(object)mainPlayer == (Object)null) { _cachedLocalSteam64 = 0uL; _cachedLocalPlayerInstanceId = 0; return 0uL; } int instanceID = ((Object)mainPlayer).GetInstanceID(); if (_cachedLocalPlayerInstanceId == instanceID && _cachedLocalSteam64 != 0L) { return _cachedLocalSteam64; } _cachedLocalPlayerInstanceId = instanceID; _cachedLocalSteam64 = GetSteam64Of(mainPlayer); if (_cachedLocalSteam64 != 0L) { _playersBySteam64[_cachedLocalSteam64] = mainPlayer; } return _cachedLocalSteam64; } public static Player? FindPlayerBySteam64(ulong steam64) { if (steam64 == 0L) { return null; } if (_playersBySteam64.TryGetValue(steam64, out Player value)) { if ((Object)(object)value != (Object)null && GetSteam64Of(value) == steam64) { return value; } _playersBySteam64.Remove(steam64); } Player[] array = Object.FindObjectsOfType(); foreach (Player val in array) { ulong steam64Of = GetSteam64Of(val); if (steam64Of != 0L) { _playersBySteam64[steam64Of] = val; if (steam64Of == steam64) { return val; } } } return null; } public static void RegisterBlackjackTable(BlackjackTable table) { if (!((Object)(object)table == (Object)null) && !string.IsNullOrEmpty(((Object)table).name)) { _tablesByName[((Object)table).name] = table; } } public static void RegisterSlotMachine(SlotMachine machine) { if (!((Object)(object)machine == (Object)null) && !string.IsNullOrEmpty(((Object)machine).name)) { _slotsByName[((Object)machine).name] = machine; } } public static void ClearSceneRegistries() { _tablesByName.Clear(); _slotsByName.Clear(); } public static void RebuildRegisteredBlackjackVisuals() { foreach (BlackjackTable value in _tablesByName.Values) { if ((Object)(object)value != (Object)null) { value.RebuildMissingCardVisuals(); } } foreach (SlotMachine value2 in _slotsByName.Values) { if ((Object)(object)value2 != (Object)null) { value2.RestoreRetainedPresentation(); } } RNNetcode.RebuildRegisteredTableVisuals(); } public static void SendClaimSeatRequest(string tableName, int seatIndex, int bet) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new ClaimSeatRequest { TableName = tableName, SeatIndex = seatIndex, Bet = bet }); }, "SendClaimSeatRequest"); } public static void SendReleaseSeatRequest(string tableName) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new ReleaseSeatRequest { TableName = tableName }); }, "SendReleaseSeatRequest"); } public static void BroadcastSeatClaimed(string tableName, int seatIndex, ulong steam64, int bet, bool becameHost) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new SeatClaimed { TableName = tableName, SeatIndex = seatIndex, SeatedPlayerSteam64 = steam64, Bet = bet, BecameHost = becameHost }); }, "BroadcastSeatClaimed"); } public static void BroadcastSeatReleased(string tableName, int seatIndex, int newHostSeatIndex) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new SeatReleased { TableName = tableName, SeatIndex = seatIndex, NewHostSeatIndex = newHostSeatIndex }); }, "BroadcastSeatReleased"); } public static void SendReadyToggleRequest(string tableName, bool ready) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new ReadyToggleRequest { TableName = tableName, Ready = ready }); }, "SendReadyToggleRequest"); } public static void BroadcastReadyChanged(string tableName, int seatIndex, bool ready) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new ReadyChanged { TableName = tableName, SeatIndex = seatIndex, Ready = ready }); }, "BroadcastReadyChanged"); } public static void SendReadyRejected(ulong targetSteam64, string tableName, string reason) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket(targetSteam64, (PacketBase)(object)new ReadyRejected { TableName = tableName, TargetSteam64 = targetSteam64, Reason = (reason ?? "rejected") }, (CompressionType)0, CompressionLevel.Fastest); }, "SendReadyRejected"); } public static void BroadcastCardDealt(string tableName, int seatIndex, Card card, bool faceUp, int indexInHand) { if (card == null) { Plugin.Log.LogError("[BJNet] BroadcastCardDealt: card is null — aborting."); return; } Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new CardDealt { TableName = tableName, SeatIndex = seatIndex, Suit = (int)card.Suit, Rank = (int)card.Rank, PrefabName = card.PrefabName, FaceUp = faceUp, IndexInHand = indexInHand }); }, "BroadcastCardDealt"); } public static void BroadcastCardFlipped(string tableName, int seatIndex, int indexInHand, bool faceUp) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new CardFlipped { TableName = tableName, SeatIndex = seatIndex, IndexInHand = indexInHand, FaceUp = faceUp }); }, "BroadcastCardFlipped"); } public static void BroadcastRoundCleared(string tableName) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new RoundCleared { TableName = tableName }); }, "BroadcastRoundCleared"); } public static void BroadcastTurnChanged(string tableName, int seatIndex) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new TurnChanged { TableName = tableName, SeatIndex = seatIndex }); }, "BroadcastTurnChanged"); } public static void BroadcastSlotSpinResult(string machineName, int reel0, int reel1, int reel2) { SlotMachineLocks.Lock(machineName); Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new SlotSpinResult { MachineName = machineName, SpinnerSteam64 = GetLocalSteam64(), Reel0 = reel0, Reel1 = reel1, Reel2 = reel2 }); }, "BroadcastSlotSpinResult"); } public static void SendStartRoundRequest(string tableName) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new StartRoundRequest { TableName = tableName }); }, "SendStartRoundRequest"); } public static void SendHitRequest(string tableName) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new HitRequest { TableName = tableName }); }, "SendHitRequest"); } public static void SendStandRequest(string tableName) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new StandRequest { TableName = tableName }); }, "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 (!AmHostFresh()) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new CasinoConfigRequest()); }, "SendCasinoConfigRequest"); } } public static void BroadcastCasinoConfigSync() { if (Plugin.IsHeadlessServer || AmHostFresh()) { 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) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket(targetSteam64, (PacketBase)(object)new ActionRejected { TableName = tableName, TargetSteam64 = targetSteam64, Action = (action ?? "action"), Reason = (reason ?? "rejected") }, (CompressionType)0, CompressionLevel.Fastest); }, "SendActionRejected"); } public static void SendPing(string note) { Safe(delegate { CodeTalkerNetwork.SendNetworkPacket((PacketBase)(object)new BJPingPacket { Note = (note ?? string.Empty) }); }, "SendPing"); Plugin.Log.LogInfo("[BJNet] Sent ping packet (note='" + note + "')."); } 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 || AmHostFresh())) { 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 (!AmHostFresh()) { 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 (AmHostFresh() && 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 (!AmHostFresh() || !(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 (!AmHostFresh() || !(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 (AmHostFresh() && !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 (AmHostFresh() && 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 (!AmHostFresh() || !(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 (AmHostFresh() && 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 (!AmHostFresh()) { 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 (!AmHostFresh()) { 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 (!AmHostFresh()) { 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 (!AmHostFresh()) { 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 (!AmHostFresh() || !(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 (!AmHostFresh() || !(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 (!AmHostFresh() || !(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; } if (_slotsByName.TryGetValue(machineName, out SlotMachine value)) { if ((Object)(object)value != (Object)null) { return value; } _slotsByName.Remove(machineName); } SlotMachine[] array = Object.FindObjectsOfType(); foreach (SlotMachine slotMachine in array) { if (!((Object)(object)slotMachine == (Object)null) && !string.IsNullOrEmpty(((Object)slotMachine).name)) { _slotsByName[((Object)slotMachine).name] = slotMachine; 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; } if (_tablesByName.TryGetValue(tableName, out BlackjackTable value)) { if ((Object)(object)value != (Object)null) { return value; } _tablesByName.Remove(tableName); } BlackjackTable[] array = Object.FindObjectsOfType(); foreach (BlackjackTable blackjackTable in array) { if (!((Object)(object)blackjackTable == (Object)null) && !string.IsNullOrEmpty(((Object)blackjackTable).name)) { _tablesByName[((Object)blackjackTable).name] = blackjackTable; 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 { ulong num = TryExtractSteam64(_playerSteamIdProp.GetValue(p)); if (num != 0L) { return num; } } catch { } } if (_playerSteamIdField != null) { try { ulong num2 = TryExtractSteam64(_playerSteamIdField.GetValue(p)); if (num2 != 0L) { return num2; } } catch { } } return 0uL; } private static ulong TryExtractSteam64(object? val) { if (val == null) { return 0uL; } if (val is ulong) { return (ulong)val; } 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; } FieldInfo field = val.GetType().GetField("m_SteamID", BindingFlags.Instance | BindingFlags.Public); if (field != null) { try { object value = field.GetValue(val); if (value is ulong) { return (ulong)value; } } catch { } } if (val is string s && ulong.TryParse(s, out var result)) { return result; } 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 == 0L) { 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" }; foreach (string text in array) { 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) { array = new string[3] { "_steamID", "steamID", "_cachedSteamID" }; foreach (string text2 in array) { 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."); } array = new string[3] { "Network_isHostPlayer", "isHostPlayer", "IsHostPlayer" }; foreach (string text3 in array) { 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[] array2 = types; foreach (Type type in array2) { 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; } }