using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using GhostSpectator.Patches; using GhostSpectator.Runtime; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using Steamworks; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using Zorro.Core; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("SisyphusMD.GhostSpectator")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.0.0")] [assembly: AssemblyInformationalVersion("0.2.0+4f459ec7fe975aba9e7341a951a4458802675bb3")] [assembly: AssemblyProduct("SisyphusMD.GhostSpectator")] [assembly: AssemblyTitle("GhostSpectator")] [assembly: AssemblyMetadata("RepositoryUrl", "https://forgejo.bryantserver.com/SisyphusMD/GhostSpectator")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.2.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace BepInEx { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] [Microsoft.CodeAnalysis.Embedded] internal sealed class BepInAutoPluginAttribute : Attribute { public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace BepInEx.Preloader.Core.Patching { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] [Microsoft.CodeAnalysis.Embedded] internal sealed class PatcherAutoPluginAttribute : Attribute { public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace Microsoft.CodeAnalysis { [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace GhostSpectator { public static class PatchValidator { public static (int ok, int missing) Validate(Action logError) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Invalid comparison between Unknown and I4 //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Invalid comparison between Unknown and I4 int num = 0; int num2 = 0; Type[] types = typeof(Plugin).Assembly.GetTypes(); foreach (Type type in types) { object[] customAttributes = type.GetCustomAttributes(typeof(HarmonyPatch), inherit: false); if (customAttributes.Length == 0) { continue; } HarmonyPatch val = (HarmonyPatch)customAttributes[0]; HarmonyMethod info = ((HarmonyAttribute)val).info; if (info.declaringType == null || info.methodName == null) { MethodInfo methodInfo = AccessTools.Method(type, "TargetMethod", (Type[])null, (Type[])null); if (methodInfo == null) { logError("[validate] " + type.Name + ": [HarmonyPatch] has no target info AND no TargetMethod() dispatcher"); num2++; continue; } try { MethodBase methodBase = (MethodBase)methodInfo.Invoke(null, null); if (methodBase != null) { num++; continue; } logError("[validate] " + type.Name + ": TargetMethod() returned null"); num2++; } catch (Exception ex) { logError("[validate] " + type.Name + ": TargetMethod() threw " + ex.GetType().Name + ": " + ex.Message); num2++; } } else { MethodBase methodBase2 = (((int)info.methodType.GetValueOrDefault() == 1) ? AccessTools.PropertyGetter(info.declaringType, info.methodName) : (((int)info.methodType.GetValueOrDefault() != 2) ? AccessTools.Method(info.declaringType, info.methodName, info.argumentTypes, (Type[])null) : AccessTools.PropertySetter(info.declaringType, info.methodName))); if (methodBase2 == null) { logError($"[validate] {type.Name}: TARGET NOT FOUND: {info.declaringType.FullName}.{info.methodName} (methodType={info.methodType})"); num2++; } else { num++; } } } (string, Type, string)[] array = new(string, Type, string)[18] { ("Character.AllCharacters (static field)", typeof(Character), "AllCharacters"), ("Character.localCharacter (static field)", typeof(Character), "localCharacter"), ("Character.refs (field)", typeof(Character), "refs"), ("Character.CharacterRefs.ragdoll (field)", typeof(CharacterRefs), "ragdoll"), ("CharacterAfflictions.character (field)", typeof(CharacterAfflictions), "character"), ("CharacterData.fullyPassedOut (field)", typeof(CharacterData), "fullyPassedOut"), ("CharacterData.deathTimer (field)", typeof(CharacterData), "deathTimer"), ("CharacterData.character (field)", typeof(CharacterData), "character"), ("GUIManager.dyingBarObject (field)", typeof(GUIManager), "dyingBarObject"), ("GUIManager.staminaCanvasGroup (field)", typeof(GUIManager), "staminaCanvasGroup"), ("GUIManager.mushroomsCanvasGroup (field)", typeof(GUIManager), "mushroomsCanvasGroup"), ("GUIManager.items (field)", typeof(GUIManager), "items"), ("GUIManager.backpack (field)", typeof(GUIManager), "backpack"), ("PointPinger.character (field)", typeof(PointPinger), "character"), ("PointPinger.pingInstance (field)", typeof(PointPinger), "pingInstance"), ("PointPinger.pointPrefab (field)", typeof(PointPinger), "pointPrefab"), ("PointPinger.coolDown (field)", typeof(PointPinger), "coolDown"), ("PointPinger._timeLastPinged (field)", typeof(PointPinger), "_timeLastPinged") }; (string, Type, string)[] array2 = array; for (int j = 0; j < array2.Length; j++) { var (text, type2, text2) = array2[j]; if (AccessTools.Field(type2, text2) != null) { num++; continue; } logError("[validate] " + text + ": NOT FOUND"); num2++; } (string, Type, string)[] array3 = new(string, Type, string)[4] { ("CharacterData.dead (property)", typeof(CharacterData), "dead"), ("CharacterData.canBeSpectated (property)", typeof(CharacterData), "canBeSpectated"), ("MainCameraMovement.specCharacter (static property)", typeof(MainCameraMovement), "specCharacter"), ("PhotonNetwork.PlayerList (property)", typeof(PhotonNetwork), "PlayerList") }; (string, Type, string)[] array4 = array3; for (int k = 0; k < array4.Length; k++) { var (text3, type3, text4) = array4[k]; if (AccessTools.Property(type3, text4) != null) { num++; continue; } logError("[validate] " + text3 + ": NOT FOUND"); num2++; } return (num, num2); } } [BepInPlugin("SisyphusMD.GhostSpectator", "GhostSpectator", "0.2.0")] public class Plugin : BaseUnityPlugin { public const string Id = "SisyphusMD.GhostSpectator"; internal static ManualLogSource Log { get; private set; } internal static ConfigEntry SpectatorEnabled { get; private set; } public static string Name => "GhostSpectator"; public static string Version => "0.2.0"; internal static void Trace(string msg) { Log.LogInfo((object)msg); Debug.Log((object)("[GhostSpectator] " + msg)); } internal static void TraceWarn(string msg) { Log.LogWarning((object)msg); Debug.LogWarning((object)("[GhostSpectator] " + msg)); } internal static void TraceDebug(string msg) { Log.LogDebug((object)msg); } private void Awake() { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; SpectatorEnabled = ((BaseUnityPlugin)this).Config.Bind("Spectator", "Enabled", false, "Toggled in-game via the 'Ghost Spectator' button in the airport lobby. When true, this player spawns into every match as a permanent ghost: dead from the start, with the vanilla ghost camera and voice chat, and never revived by Scout Effigies, Ancient Statues, or Respawn Chests. Other players are unaffected. Don't edit this directly, use the in-game button so the change broadcasts to your lobby properly."); (int, int) tuple = PatchValidator.Validate((Action)Log.LogError); Log.LogInfo((object)$"[validate] {tuple.Item1} targets resolved, {tuple.Item2} missing"); Harmony val = new Harmony("SisyphusMD.GhostSpectator"); int num = 0; int num2 = 0; Type[] types = typeof(Plugin).Assembly.GetTypes(); foreach (Type type in types) { if (type.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length == 0) { continue; } try { val.CreateClassProcessor(type).Patch(); Log.LogInfo((object)("[trace] attached: " + type.Name)); num++; } catch (Exception ex) { Log.LogError((object)("[trace] FAILED to attach " + type.Name + ": " + ex.GetType().Name + ": " + ex.Message)); if (ex.InnerException != null) { Log.LogError((object)("[trace] inner: " + ex.InnerException.GetType().Name + ": " + ex.InnerException.Message)); } num2++; } } Log.LogInfo((object)$"[trace] patches attached: {num}, failed: {num2}"); if (num == 0) { Log.LogError((object)"GhostSpectator: no patches attached. Mod is non-functional."); return; } GameObject val2 = new GameObject("GhostSpectator.Runtime"); Object.DontDestroyOnLoad((Object)(object)val2); val2.AddComponent(); val2.AddComponent(); val2.AddComponent(); Log.LogInfo((object)$"{Name} {Version} loaded, spectator={SpectatorEnabled.Value}"); } } } namespace GhostSpectator.Runtime { internal static class GuiHelpers { internal static Texture2D BuildSolidTexture(Color color) { //IL_0004: 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_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown //IL_001c: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false) { wrapMode = (TextureWrapMode)1, hideFlags = (HideFlags)61 }; val.SetPixel(0, 0, color); val.Apply(); return val; } internal static GUIStyle MakeLabelStyle(int fontSize, FontStyle fontStyle, TextAnchor alignment, Color textColor, bool wordWrap = false) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: 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_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_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = fontSize, fontStyle = fontStyle, alignment = alignment, wordWrap = wordWrap }; val.normal.textColor = textColor; return val; } internal static Texture2D BuildGhostTexture(int size) { //IL_0004: 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_0010: 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_0020: Expected O, but got Unknown //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_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(size, size, (TextureFormat)4, false) { filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1, hideFlags = (HideFlags)61 }; Color[] array = (Color[])(object)new Color[size * size]; for (int i = 0; i < array.Length; i++) { array[i] = Color.clear; } float num = (float)size / 2f; float num2 = (float)size * 0.42f; float num3 = (float)size * 0.45f; float num4 = (float)size * 0.82f; float num5 = num2 / 2.6f; for (int j = 0; j < size; j++) { float num6 = (float)j + 0.5f; int num7 = size - 1 - j; for (int k = 0; k < size; k++) { float num8 = (float)k + 0.5f; float num9 = num8 - num; bool flag = false; if (num6 < num3) { float num10 = (num6 - num3) / num3; float num11 = num9 / num2; if (num11 * num11 + num10 * num10 <= 1f) { flag = true; } } else if (num6 < num4) { if (Mathf.Abs(num9) <= num2) { flag = true; } } else if (Mathf.Abs(num9) <= num2) { int num12 = 3; float num13 = num2 * 2f / (float)num12; for (int l = 0; l < num12; l++) { float num14 = 0f - num2 + ((float)l + 0.5f) * num13; float num15 = num9 - num14; float num16 = num6 - num4; if (num15 * num15 + num16 * num16 <= num5 * num5) { flag = true; break; } } } if (flag) { array[num7 * size + k] = Color.white; } } } DrawDisk(array, size, (int)((float)size * 0.34f), (int)((float)size * 0.42f), Mathf.Max(2, size / 14), Color.black); DrawDisk(array, size, (int)((float)size * 0.66f), (int)((float)size * 0.42f), Mathf.Max(2, size / 14), Color.black); val.SetPixels(array); val.Apply(); return val; } private static void DrawDisk(Color[] pixels, int size, int cx, int cy, int r, Color color) { //IL_0042: 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) int num = size - 1 - cy; for (int i = -r; i <= r; i++) { for (int j = -r; j <= r; j++) { if (j * j + i * i <= r * r) { int num2 = cx + j; int num3 = num - i; if (num2 >= 0 && num2 < size && num3 >= 0 && num3 < size) { pixels[num3 * size + num2] = color; } } } } } } internal class MidRunJoinPopup : MonoBehaviour { private bool _isShowing; private bool _hasChoice; private bool _choiceIsSpectate; private float _shownAtUnscaledTime; private const float SelfHideTimeoutSeconds = 300f; private GUIStyle? _titleStyle; private GUIStyle? _bodyStyle; private GUIStyle? _buttonStyle; private GUIStyle? _boxStyle; private Texture2D? _backgroundTexture; private Texture2D? _scrimTexture; private const int PanelWidth = 520; private const int PanelHeight = 240; private const int ButtonHeight = 56; private const int ButtonGap = 16; private const int PaddingX = 32; private const int PaddingY = 28; internal static MidRunJoinPopup? Instance { get; private set; } internal bool IsShowing => _isShowing; internal bool HasChoice => _hasChoice; internal bool ChoiceIsSpectate => _choiceIsSpectate; private void Awake() { Instance = this; } private void OnDestroy() { if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } internal void Show() { _hasChoice = false; _choiceIsSpectate = false; _isShowing = true; _shownAtUnscaledTime = Time.realtimeSinceStartup; Plugin.TraceDebug("[trace] MidRunJoinPopup shown"); } private void Update() { if (_isShowing && Time.realtimeSinceStartup - _shownAtUnscaledTime > 300f) { Plugin.TraceWarn($"[trace] MidRunJoinPopup self-hiding after {300f:0}s timeout (waiter coroutine likely orphaned)"); Hide(); } } internal void Hide() { _isShowing = false; Plugin.TraceDebug("[trace] MidRunJoinPopup hidden."); } private void OnGUI() { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: 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_01b5: 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_01d0: Unknown result type (might be due to invalid IL or missing references) if (_isShowing) { EnsureStyles(); if (_boxStyle != null && (Object)(object)_backgroundTexture != (Object)null) { _boxStyle.normal.background = _backgroundTexture; } int depth = GUI.depth; GUI.depth = -1000; if ((Object)(object)_scrimTexture != (Object)null) { GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)_scrimTexture); } Rect val = default(Rect); ((Rect)(ref val))..ctor((float)(Screen.width - 520) / 2f, (float)(Screen.height - 240) / 2f, 520f, 240f); GUI.Box(val, GUIContent.none, _boxStyle); float num = ((Rect)(ref val)).y + 28f; Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(((Rect)(ref val)).x + 32f, num, ((Rect)(ref val)).width - 64f, 30f); GUI.Label(val2, "Join this run as…", _titleStyle); num += 38f; Rect val3 = default(Rect); ((Rect)(ref val3))..ctor(((Rect)(ref val)).x + 32f, num, ((Rect)(ref val)).width - 64f, 50f); GUI.Label(val3, "Pick how to join. Live climbers play the run; spectators watch.", _bodyStyle); num += 58f; float num2 = num; float num3 = (((Rect)(ref val)).width - 64f - 16f) / 2f; Rect val4 = default(Rect); ((Rect)(ref val4))..ctor(((Rect)(ref val)).x + 32f, num2, num3, 56f); Rect val5 = default(Rect); ((Rect)(ref val5))..ctor(((Rect)(ref val)).x + 32f + num3 + 16f, num2, num3, 56f); if (GUI.Button(val4, "Play", _buttonStyle)) { Commit(spectate: false); } if (GUI.Button(val5, "Spectate", _buttonStyle)) { Commit(spectate: true); } GUI.depth = depth; } } private void Commit(bool spectate) { _choiceIsSpectate = spectate; _hasChoice = true; Plugin.SpectatorEnabled.Value = spectate; Plugin.Trace($"[trace] MidRunJoinPopup committed: spectate={spectate}"); } private void EnsureStyles() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_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_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Expected O, but got Unknown if (_titleStyle == null) { _titleStyle = GuiHelpers.MakeLabelStyle(22, (FontStyle)1, (TextAnchor)3, Color.white); _bodyStyle = GuiHelpers.MakeLabelStyle(14, (FontStyle)0, (TextAnchor)0, new Color(0.88f, 0.88f, 0.92f), wordWrap: true); _buttonStyle = new GUIStyle(GUI.skin.button) { fontSize = 18, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; _backgroundTexture = GuiHelpers.BuildSolidTexture(new Color(0.08f, 0.08f, 0.12f, 0.96f)); _scrimTexture = GuiHelpers.BuildSolidTexture(new Color(0f, 0f, 0f, 0.65f)); _boxStyle = new GUIStyle(GUI.skin.box); _boxStyle.normal.background = _backgroundTexture; } } } internal class RoomCallbackHandler : MonoBehaviourPunCallbacks { [CompilerGenerated] private sealed class d__7 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Player targetPlayer; private int 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__7(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__2 = 0; break; case 1: <>1__state = -1; if (targetPlayer == null) { return false; } if (!string.IsNullOrEmpty(targetPlayer.UserId)) { if (PhotonNetwork.IsMasterClient) { RecordRoleIfNew(targetPlayer); } return false; } 5__2++; break; } if (5__2 < 120) { <>2__current = null; <>1__state = 1; return true; } object arg = 120; Player obj = targetPlayer; Plugin.TraceWarn($"[trace] DeferredRecordRole gave up after {arg}f waiting for UserId on {((obj != null) ? obj.NickName : null)}"); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public const string IsSpectatorKey = "GhostSpectator.IsSpectator"; private EventHandler? _settingChangedHandler; private static Player[] _sortedPlayers = (Player[])(object)new Player[0]; private static readonly string[] _emptyLabels = new string[0]; private static string[] _sortedPlayerLabels = _emptyLabels; private static readonly Player[] _empty = (Player[])(object)new Player[0]; public static Player[] SortedPlayers => _sortedPlayers; public static string[] SortedPlayerLabels => _sortedPlayerLabels; private void Awake() { if (PhotonNetwork.InRoom) { PublishSpectatorStatus(); } _settingChangedHandler = delegate { PublishSpectatorStatus(); }; Plugin.SpectatorEnabled.SettingChanged += _settingChangedHandler; } private void OnDestroy() { if (_settingChangedHandler != null) { Plugin.SpectatorEnabled.SettingChanged -= _settingChangedHandler; _settingChangedHandler = null; } } public override void OnJoinedRoom() { Room currentRoom = PhotonNetwork.CurrentRoom; Plugin.TraceDebug("[trace] OnJoinedRoom fired. RoomName=" + ((currentRoom != null) ? currentRoom.Name : null)); SpectatorState.HasPublishedLocalCustomizationData = false; SpectatorState.KioskRefusedTimestamp = -1f; SpectatorState.KioskRefusedMessage = string.Empty; PublishSpectatorStatus(); RebuildSortedPlayers(); Room currentRoom2 = PhotonNetwork.CurrentRoom; if (((currentRoom2 != null) ? ((RoomInfo)currentRoom2).CustomProperties : null) != null && ((Dictionary)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"GhostSpectator.RunRoles", out object value)) { RoleLock.DeserializeRunRolesFromHashtable(value); Plugin.TraceDebug($"[trace] RunRoles seeded from room property at join: {RoleLock.RunRoles.Count} entries"); } } public override void OnPlayerEnteredRoom(Player newPlayer) { Plugin.TraceDebug("[trace] OnPlayerEnteredRoom fired for " + ((newPlayer != null) ? newPlayer.NickName : null)); PublishSpectatorStatus(); RebuildSortedPlayers(); } public override void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps) { //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 (!PhotonNetwork.IsMasterClient || targetPlayer == null || changedProps == null || !((Dictionary)(object)changedProps).ContainsKey((object)"GhostSpectator.IsSpectator")) { return; } Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; if (!(name == "Airport")) { if (string.IsNullOrEmpty(targetPlayer.UserId)) { ((MonoBehaviour)this).StartCoroutine(DeferredRecordRole(targetPlayer)); } else { RecordRoleIfNew(targetPlayer); } } } [IteratorStateMachine(typeof(d__7))] private IEnumerator DeferredRecordRole(Player targetPlayer) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__7(0) { targetPlayer = targetPlayer }; } private static void RecordRoleIfNew(Player targetPlayer) { if (string.IsNullOrEmpty(targetPlayer.UserId)) { return; } bool flag = SpectatorState.ClaimsSpectator(targetPlayer); bool flag2 = false; lock (RoleLock.RunRoles) { if (!RoleLock.RunRoles.ContainsKey(targetPlayer.UserId)) { RoleLock.RunRoles[targetPlayer.UserId] = flag; flag2 = true; Plugin.Trace($"[trace] recorded run role for {targetPlayer.NickName} ({targetPlayer.UserId}): isSpec={flag}"); } } if (flag2) { RoleLock.PublishRunRolesToNetwork(); } } public override void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged) { if (propertiesThatChanged == null || !((Dictionary)(object)propertiesThatChanged).ContainsKey((object)"GhostSpectator.RunRoles")) { return; } if (PhotonNetwork.IsMasterClient) { object obj = propertiesThatChanged[(object)"GhostSpectator.RunRoles"]; Hashtable inbound = (Hashtable)((obj is Hashtable) ? obj : null); if (!RoleLock.RunRolesHashtableMatchesInMemory(inbound)) { Plugin.TraceWarn("[trace] non-master overwrote RunRoles room property; republishing authoritative value"); RoleLock.PublishRunRolesToNetwork(); return; } } RoleLock.DeserializeRunRolesFromHashtable(propertiesThatChanged[(object)"GhostSpectator.RunRoles"]); Plugin.TraceDebug($"[trace] RunRoles synced from room property: {RoleLock.RunRoles.Count} entries"); } public override void OnPlayerLeftRoom(Player otherPlayer) { Plugin.TraceDebug("[trace] OnPlayerLeftRoom fired for " + ((otherPlayer != null) ? otherPlayer.NickName : null)); RebuildSortedPlayers(); } public override void OnLeftRoom() { _sortedPlayers = _empty; _sortedPlayerLabels = _emptyLabels; } public override void OnMasterClientSwitched(Player newMasterClient) { Plugin.TraceDebug($"[trace] OnMasterClientSwitched fired. newMaster=#{((newMasterClient != null) ? new int?(newMasterClient.ActorNumber) : null)}"); SpectatorState.RunHasEnded = false; SpectatorState.BannersOverriddenThisRun = false; if (PhotonNetwork.IsMasterClient) { RoleLock.TryWriteRunRolesToSteamLobby(); } } private static void RebuildSortedPlayers() { if (!PhotonNetwork.InRoom) { _sortedPlayers = _empty; _sortedPlayerLabels = _emptyLabels; return; } _sortedPlayers = PhotonNetwork.PlayerList.OrderBy((Player p) => p.ActorNumber).ToArray(); _sortedPlayerLabels = new string[_sortedPlayers.Length]; for (int i = 0; i < _sortedPlayers.Length; i++) { Player val = _sortedPlayers[i]; string text = (string.IsNullOrEmpty((val != null) ? val.NickName : null) ? "(unnamed)" : val.NickName); _sortedPlayerLabels[i] = ((val != null && val.IsLocal) ? (text + " (you)") : text); } } public static void PublishSpectatorStatus() { //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_0031: Expected O, but got Unknown //IL_0032: Expected O, but got Unknown if (!PhotonNetwork.InRoom) { Plugin.TraceDebug("[trace] PublishSpectatorStatus skipped, not InRoom"); return; } Hashtable val = new Hashtable(); ((Dictionary)val).Add((object)"GhostSpectator.IsSpectator", (object)Plugin.SpectatorEnabled.Value); Hashtable val2 = val; PhotonNetwork.LocalPlayer.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null); Plugin.TraceDebug($"[trace] published IsSpectator={Plugin.SpectatorEnabled.Value}"); } public static int CountNonSpectators() { if (!PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) { return 0; } int num = 0; foreach (Player value in PhotonNetwork.CurrentRoom.Players.Values) { if (!SpectatorState.ClaimsSpectator(value)) { num++; } } return num; } public static Player[] GetNonSpectatorPlayerList() { if (!PhotonNetwork.InRoom) { return _empty; } List list = new List(PhotonNetwork.PlayerList.Length); Player[] playerList = PhotonNetwork.PlayerList; foreach (Player val in playerList) { if (val != null && !SpectatorState.IsTrustedSpectator(val) && (val.IsLocal || SpectatorState.HasGhostSpectatorMod(val))) { list.Add(val); } } return list.ToArray(); } } internal class SpectatorMenuUI : MonoBehaviour { private GUIStyle? _titleStyle; private GUIStyle? _sectionHeaderStyle; private GUIStyle? _rowStyle; private GUIStyle? _boxStyle; private GUIStyle? _powerButtonStyle; private Texture2D? _ghostTexture; private Texture2D? _climberTexture; private Texture2D? _powerTexture; private Texture2D? _panelBackground; private const int PanelWidth = 240; private const int ScreenMargin = 20; private const int PanelPaddingX = 12; private const int PanelPaddingY = 10; private const int RowHeight = 24; private const int TitleHeight = 28; private const int SectionHeaderHeight = 20; private const int SectionGap = 4; private const int IconSize = 18; private const int IconPadding = 2; private const int PowerButtonSize = 26; private static readonly Color FallbackGhostColor = new Color(0.78f, 0.66f, 1f, 1f); private static readonly Color PowerOnColor = new Color(0.3f, 0.95f, 0.4f, 1f); private static readonly Color PowerOffColor = new Color(0.62f, 0.62f, 0.68f, 1f); private static readonly Color EmptySlotColor = new Color(0.6f, 0.6f, 0.65f, 0.45f); private GUIStyle? _kioskBannerStyle; private Texture2D? _kioskBannerBackground; private void OnGUI() { if (PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null) { EnsureTextures(); EnsureStyles(); if (_boxStyle != null && (Object)(object)_panelBackground != (Object)null) { _boxStyle.normal.background = _panelBackground; } DrawPlayerPanel(20f); DrawKioskRefusedMessage(); } } private void DrawKioskRefusedMessage() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: 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_0049: 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_0057: 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: Expected O, but got Unknown //IL_006f: Expected O, but got Unknown //IL_007a: 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_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Expected O, but got Unknown //IL_0159: Unknown result type (might be due to invalid IL or missing references) if (SpectatorState.KioskRefusedTimestamp < 0f) { return; } float num = Time.realtimeSinceStartup - SpectatorState.KioskRefusedTimestamp; if (!(num > 5f)) { if (_kioskBannerStyle == null) { _kioskBannerStyle = new GUIStyle(GUI.skin.label) { fontSize = 18, fontStyle = (FontStyle)1, alignment = (TextAnchor)4, wordWrap = true, padding = new RectOffset(20, 20, 14, 14) }; _kioskBannerStyle.normal.textColor = Color.white; } if ((Object)(object)_kioskBannerBackground == (Object)null) { _kioskBannerBackground = GuiHelpers.BuildSolidTexture(new Color(0.2f, 0.06f, 0.06f, 0.92f)); _kioskBannerStyle.normal.background = _kioskBannerBackground; } else if ((Object)(object)_kioskBannerStyle.normal.background == (Object)null) { _kioskBannerStyle.normal.background = _kioskBannerBackground; } string text = (string.IsNullOrEmpty(SpectatorState.KioskRefusedMessage) ? "Can't start the run." : SpectatorState.KioskRefusedMessage); GUIContent val = new GUIContent(text); float num2 = _kioskBannerStyle.CalcHeight(val, 540f); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor((float)(Screen.width - 540) / 2f, (float)Screen.height * 0.18f, 540f, num2); GUI.Label(val2, val, _kioskBannerStyle); } } private void DrawPlayerPanel(float topY) { //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_008c: 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_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_0338: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); bool flag = ((Scene)(ref activeScene)).name == "Airport"; Player[] sortedPlayers = RoomCallbackHandler.SortedPlayers; string[] sortedPlayerLabels = RoomCallbackHandler.SortedPlayerLabels; int num = 0; int num2 = 0; foreach (Player val in sortedPlayers) { if (val != null) { if (SpectatorState.ClaimsSpectator(val)) { num2++; } else { num++; } } } int num3 = 4; int num4 = num2; int num5 = 68 + num3 * 24 + 4 + 20 + num4 * 24; Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(20f, topY, 240f, (float)num5); GUI.Box(val2, GUIContent.none, _boxStyle); float num6 = ((Rect)(ref val2)).y + 10f; Rect val3 = default(Rect); ((Rect)(ref val3))..ctor(((Rect)(ref val2)).x + 12f, num6, ((Rect)(ref val2)).width - 24f, 28f); Rect val4 = (Rect)(flag ? new Rect(((Rect)(ref val3)).x, ((Rect)(ref val3)).y, ((Rect)(ref val3)).width - 26f - 6f, ((Rect)(ref val3)).height) : val3); GUI.Label(val4, "Ghost Spectator", _titleStyle); if (flag) { Rect rect = default(Rect); ((Rect)(ref rect))..ctor(((Rect)(ref val3)).xMax - 26f, ((Rect)(ref val3)).y + 1f, 26f, 26f); DrawPowerToggle(rect); } num6 += 28f; DrawSectionHeader(val2, num6, $"LIVE {num}/{4}"); num6 += 20f; int j = 0; for (int k = 0; k < sortedPlayers.Length; k++) { if (j >= num3) { break; } Player val5 = sortedPlayers[k]; if (val5 != null && !SpectatorState.ClaimsSpectator(val5)) { string label = ((k < sortedPlayerLabels.Length) ? sortedPlayerLabels[k] : (string.IsNullOrEmpty(val5.NickName) ? "(unnamed)" : val5.NickName)); Texture2D icon = (flag ? _climberTexture : (SpectatorState.IsPlayerCharacterDead(val5) ? _ghostTexture : _climberTexture)); DrawPlayerRow(val5, label, new Rect(((Rect)(ref val2)).x + 12f, num6, ((Rect)(ref val2)).width - 24f, 24f), icon); num6 += 24f; j++; } } for (; j < num3; j++) { DrawEmptySlot(new Rect(((Rect)(ref val2)).x + 12f, num6, ((Rect)(ref val2)).width - 24f, 24f)); num6 += 24f; } num6 += 4f; DrawSectionHeader(val2, num6, $"SPECTATORS {num2}/{16}"); num6 += 20f; for (int l = 0; l < sortedPlayers.Length; l++) { Player val6 = sortedPlayers[l]; if (val6 != null && SpectatorState.ClaimsSpectator(val6)) { string label2 = ((l < sortedPlayerLabels.Length) ? sortedPlayerLabels[l] : (string.IsNullOrEmpty(val6.NickName) ? "(unnamed)" : val6.NickName)); DrawPlayerRow(val6, label2, new Rect(((Rect)(ref val2)).x + 12f, num6, ((Rect)(ref val2)).width - 24f, 24f), _ghostTexture); num6 += 24f; } } } private void DrawSectionHeader(Rect panelRect, float y, string text) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) Rect val = default(Rect); ((Rect)(ref val))..ctor(((Rect)(ref panelRect)).x + 12f, y, ((Rect)(ref panelRect)).width - 24f, 20f); GUI.Label(val, text, _sectionHeaderStyle); } private void DrawEmptySlot(Rect row) { //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_0010: 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 color = GUI.color; GUI.color = EmptySlotColor; GUI.Label(row, "(open)", _rowStyle); GUI.color = color; } private void DrawPowerToggle(Rect rect) { //IL_0015: 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_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) bool value = Plugin.SpectatorEnabled.Value; Color color = (value ? PowerOnColor : PowerOffColor); if (GUI.Button(rect, GUIContent.none, _powerButtonStyle)) { Plugin.SpectatorEnabled.Value = !value; Plugin.Trace($"GhostSpectator: toggled via menu to {Plugin.SpectatorEnabled.Value}"); } Rect val = default(Rect); ((Rect)(ref val))..ctor(((Rect)(ref rect)).x + 4f, ((Rect)(ref rect)).y + 4f, ((Rect)(ref rect)).width - 8f, ((Rect)(ref rect)).height - 8f); Color color2 = GUI.color; GUI.color = color; if ((Object)(object)_powerTexture != (Object)null) { GUI.DrawTexture(val, (Texture)(object)_powerTexture, (ScaleMode)2); } GUI.color = color2; } private void DrawPlayerRow(Player player, string label, Rect row, Texture2D? icon) { //IL_0044: 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_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) float num = (((Object)(object)icon != (Object)null) ? (((Rect)(ref row)).width - 18f - 2f) : ((Rect)(ref row)).width); Rect val = default(Rect); ((Rect)(ref val))..ctor(((Rect)(ref row)).x, ((Rect)(ref row)).y, num, ((Rect)(ref row)).height); GUI.Label(val, label, _rowStyle); if (!((Object)(object)icon == (Object)null)) { Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(((Rect)(ref row)).xMax - 18f, ((Rect)(ref row)).y + (((Rect)(ref row)).height - 18f) / 2f, 18f, 18f); Color color = GUI.color; GUI.color = (Color)(((??)GetPlayerSkinColor(player)) ?? FallbackGhostColor); GUI.DrawTexture(val2, (Texture)(object)icon); GUI.color = color; } } private static Color? GetPlayerSkinColor(Player player) { //IL_00ba: Unknown result type (might be due to invalid IL or missing references) try { PersistentPlayerDataService service = GameHandler.GetService(); if (service == null) { return null; } PersistentPlayerData playerData = service.GetPlayerData(player); if (playerData?.customizationData == null) { return null; } Customization instance = Singleton.Instance; if ((Object)(object)instance == (Object)null || instance.skins == null || instance.skins.Length == 0) { return null; } int currentSkin = playerData.customizationData.currentSkin; if (currentSkin < 0 || currentSkin >= instance.skins.Length) { return null; } CustomizationOption val = instance.skins[currentSkin]; return ((Object)(object)val != (Object)null) ? new Color?(val.color) : null; } catch (NullReferenceException) { return null; } catch (IndexOutOfRangeException) { return null; } } private void EnsureStyles() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0047: 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_006c: Expected O, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown //IL_0090: Expected O, but got Unknown if (_titleStyle == null) { _titleStyle = GuiHelpers.MakeLabelStyle(18, (FontStyle)1, (TextAnchor)3, Color.white); _sectionHeaderStyle = GuiHelpers.MakeLabelStyle(11, (FontStyle)1, (TextAnchor)3, new Color(0.72f, 0.74f, 0.8f)); _rowStyle = GuiHelpers.MakeLabelStyle(14, (FontStyle)0, (TextAnchor)3, Color.white); _boxStyle = new GUIStyle(GUI.skin.box); _powerButtonStyle = new GUIStyle(GUI.skin.button) { padding = new RectOffset(2, 2, 2, 2) }; } } private void EnsureTextures() { //IL_0074: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_ghostTexture == (Object)null) { _ghostTexture = GuiHelpers.BuildGhostTexture(40); } if ((Object)(object)_climberTexture == (Object)null) { _climberTexture = BuildClimberTexture(40); } if ((Object)(object)_powerTexture == (Object)null) { _powerTexture = BuildPowerTexture(40); } if ((Object)(object)_panelBackground == (Object)null) { _panelBackground = GuiHelpers.BuildSolidTexture(new Color(0.06f, 0.06f, 0.09f, 0.45f)); } } private static Texture2D BuildClimberTexture(int size) { //IL_0004: 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_0010: 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_0020: Expected O, but got Unknown //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_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(size, size, (TextureFormat)4, false) { filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1, hideFlags = (HideFlags)61 }; Color[] array = (Color[])(object)new Color[size * size]; for (int i = 0; i < array.Length; i++) { array[i] = Color.clear; } float num = (float)size / 2f; float num2 = (float)size * 0.22f; float num3 = (float)size * 0.16f; float num4 = (float)size * 0.4f; float num5 = (float)size * 0.92f; float num6 = (float)size * 0.34f; float num7 = (float)size * 0.24f; for (int j = 0; j < size; j++) { float num8 = (float)j + 0.5f; int num9 = size - 1 - j; for (int k = 0; k < size; k++) { float num10 = (float)k + 0.5f; float num11 = num10 - num; bool flag = false; float num12 = num8 - num2; if (num11 * num11 + num12 * num12 <= num3 * num3) { flag = true; } else if (num8 >= num4 && num8 <= num5) { float num13 = (num8 - num4) / (num5 - num4); float num14 = Mathf.Lerp(num6, num7, num13); if (Mathf.Abs(num11) <= num14) { flag = true; } } if (flag) { array[num9 * size + k] = Color.white; } } } val.SetPixels(array); val.Apply(); return val; } private static Texture2D BuildPowerTexture(int size) { //IL_0004: 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_0010: 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_0020: Expected O, but got Unknown //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_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(size, size, (TextureFormat)4, false) { filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1, hideFlags = (HideFlags)61 }; Color[] array = (Color[])(object)new Color[size * size]; for (int i = 0; i < array.Length; i++) { array[i] = Color.clear; } float num = (float)size / 2f; float num2 = (float)size / 2f; float num3 = (float)size * 0.42f; float num4 = (float)size * 0.3f; float num5 = (float)size * 0.07f; float num6 = (float)size * 0.1f; float num7 = (float)size * 0.52f; float num8 = MathF.PI / 10f; for (int j = 0; j < size; j++) { float num9 = (float)j + 0.5f; int num10 = size - 1 - j; float num11 = num9 - num2; for (int k = 0; k < size; k++) { float num12 = (float)k + 0.5f; float num13 = num12 - num; bool flag = false; float num14 = num13 * num13 + num11 * num11; if (num14 >= num4 * num4 && num14 <= num3 * num3) { bool flag2 = false; if (num11 < 0f) { float num15 = Mathf.Atan2(Mathf.Abs(num13), 0f - num11); if (num15 < num8) { flag2 = true; } } if (!flag2) { flag = true; } } if (num9 >= num6 && num9 <= num7 && Mathf.Abs(num13) <= num5) { flag = true; } if (flag) { array[num10 * size + k] = Color.white; } } } val.SetPixels(array); val.Apply(); return val; } } } namespace GhostSpectator.Patches { [HarmonyPatch(typeof(MainCameraMovement), "Spectate")] internal static class Patch_MainCameraMovement_Spectate { private static int _fallbackLogCount; [HarmonyPostfix] private static void Postfix(MainCameraMovement __instance) { //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_0073: 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_0045: 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_0055: 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_00a6: 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_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_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_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: 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_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.SpectatorEnabled.Value) { return; } Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name == "Airport") { return; } Character specCharacter = MainCameraMovement.specCharacter; if ((Object)(object)specCharacter != (Object)null && !SpectatorState.IsTrustedSpectator(specCharacter)) { SpectatorState.LastSpectateCameraPosition = ((Component)__instance).transform.position; SpectatorState.LastSpectateCameraRotation = ((Component)__instance).transform.rotation; SpectatorState.LastSpectateCameraValid = true; return; } if (SpectatorState.LastSpectateCameraValid) { ((Component)__instance).transform.position = SpectatorState.LastSpectateCameraPosition; ((Component)__instance).transform.rotation = SpectatorState.LastSpectateCameraRotation; return; } Character localCharacter = Character.localCharacter; if (!((Object)(object)localCharacter == (Object)null) && SpectatorState.SpectatorSpawnPositionValid) { Vector3 spectatorSpawnPosition = SpectatorState.SpectatorSpawnPosition; Vector3 val = -localCharacter.data.lookDirection; Vector3 val2 = spectatorSpawnPosition + val * 3f + Vector3.up * 2f; Vector3 val3 = spectatorSpawnPosition + Vector3.up * 1.5f; ((Component)__instance).transform.position = val2; Transform transform = ((Component)__instance).transform; Vector3 val4 = val3 - val2; transform.rotation = Quaternion.LookRotation(((Vector3)(ref val4)).normalized); if (_fallbackLogCount < 3) { Plugin.Trace($"[trace] camera spawn-anchor fallback active (no live target ever this run). anchor={spectatorSpawnPosition}, lookTarget={val3}, camPos={val2}"); _fallbackLogCount++; } } } } [HarmonyPatch(typeof(PersistentPlayerDataService), "OnSyncReceived")] internal static class Patch_PersistentPlayerDataService_OnSyncReceived { [HarmonyPrefix] private static bool Prefix(SyncPersistentPlayerDataPackage package) { if (!PhotonNetwork.InRoom) { return true; } Player localPlayer = PhotonNetwork.LocalPlayer; if (localPlayer == null) { return true; } if (package.ActorNumber != localPlayer.ActorNumber) { return true; } if (!SpectatorState.HasPublishedLocalCustomizationData) { return true; } Plugin.Trace($"[trace] ignored echoed self-sync (actor #{package.ActorNumber}); local is authoritative."); return false; } } [HarmonyPatch(typeof(PersistentPlayerDataService), "SetPlayerData")] internal static class Patch_PersistentPlayerDataService_SetPlayerData { [HarmonyPostfix] private static void Postfix(Player player) { if (player != null && PhotonNetwork.InRoom) { Player localPlayer = PhotonNetwork.LocalPlayer; if (localPlayer != null && player.ActorNumber == localPlayer.ActorNumber) { SpectatorState.HasPublishedLocalCustomizationData = true; } } } } [HarmonyPatch(typeof(Character), "Start")] internal static class Patch_Character_Start { [HarmonyPostfix] private static void Postfix(Character __instance) { //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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; bool flag = (Object)(object)Character.localCharacter != (Object)null && (Object)(object)__instance == (Object)(object)Character.localCharacter; Plugin.TraceDebug($"[trace] Character.Start postfix fired. scene={name}, isLocal={flag}, SpectatorEnabled={Plugin.SpectatorEnabled.Value}"); if (flag) { SpectatorState.SpectatorSpawnPositionValid = false; SpectatorState.LastSpectateCameraValid = false; SpectatorState.RunHasEnded = false; SpectatorState.BannersOverriddenThisRun = false; } if (SpectatorState.IsLocalSpectator(__instance)) { activeScene = SceneManager.GetActiveScene(); if (!(((Scene)(ref activeScene)).name == "Airport")) { SpectatorState.SpectatorSpawnPosition = ((Component)__instance).transform.position; SpectatorState.SpectatorSpawnPositionValid = true; ((MonoBehaviourPun)__instance).photonView.RPC("RPCA_SetDead", (RpcTarget)0, Array.Empty()); __instance.refs.ragdoll.ToggleCollision(false); Plugin.Trace("GhostSpectator: local character forced into ghost state at spawn (RPCA_SetDead broadcast)."); } } } } [HarmonyPatch(typeof(CharacterData), "RPC_SyncOnJoin")] internal static class Patch_CharacterData_RPC_SyncOnJoin { [HarmonyPostfix] private static void Postfix(CharacterData __instance) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (SpectatorState.IsLocalSpectator(__instance.character)) { Scene activeScene = SceneManager.GetActiveScene(); if (!(((Scene)(ref activeScene)).name == "Airport")) { __instance.fullyPassedOut = true; __instance.dead = true; } } } } [HarmonyPatch(typeof(Character), "RPCA_Die")] internal static class Patch_Character_RPCA_Die { [HarmonyPrefix] private static bool Prefix(Character __instance) { if (SpectatorState.IsTrustedSpectator(__instance)) { Plugin.TraceDebug("[trace] blocked RPCA_Die on spectator (already dead via RPCA_SetDead, no skeleton/items needed)."); return false; } if (SpectatorState.TryGetOwner(__instance, out Player owner) && SpectatorState.ClaimsSpectator(owner)) { Plugin.TraceDebug("[trace] blocked RPCA_Die on owner-claims-spectator (pre-corroboration race)."); return false; } return true; } } [HarmonyPatch(typeof(Character), "EndGame")] internal static class Patch_Character_EndGame { private static float? _firstSuppressUnscaledTime; private const float EndGameSuppressionTimeoutSeconds = 10f; [HarmonyPrefix] private static bool Prefix() { if (SpectatorState.RunHasEnded) { Plugin.Trace("[trace] EndGame called again in same run, suppressing duplicate to keep EndScreen's Next button intact."); return false; } if (!SpectatorState.AllExpectedCharactersSpawned(out var expectedPlayers, out var actualCharacters)) { if (!_firstSuppressUnscaledTime.HasValue) { _firstSuppressUnscaledTime = Time.realtimeSinceStartup; } float num = Time.realtimeSinceStartup - _firstSuppressUnscaledTime.Value; if (num < 10f) { Plugin.Trace($"[trace] EndGame suppressed: {actualCharacters} of {expectedPlayers} Characters spawned, waiting for sync (suppressed {num:0.0}s)."); return false; } Plugin.TraceWarn($"[trace] EndGame suppression timeout ({num:0.0}s); allowing EndGame despite {expectedPlayers - actualCharacters} missing Character(s)."); } _firstSuppressUnscaledTime = null; SpectatorState.RunHasEnded = true; Plugin.Trace("[trace] EndGame firing for the first time this run."); return true; } internal static void ResetSuppressionTimer() { _firstSuppressUnscaledTime = null; } } [HarmonyPatch(typeof(OrbFogHandler), "PlayersHaveMovedOn")] internal static class Patch_OrbFogHandler_PlayersHaveMovedOn { [HarmonyPrefix] private static bool Prefix(ref bool __result) { if (!SpectatorState.AllExpectedCharactersSpawned(out var _, out var _)) { __result = false; return false; } bool flag = false; if (Character.AllCharacters != null) { for (int i = 0; i < Character.AllCharacters.Count; i++) { Character val = Character.AllCharacters[i]; if ((Object)(object)val != (Object)null && !SpectatorState.IsTrustedSpectator(val)) { flag = true; break; } } } if (!flag) { __result = false; return false; } return true; } } [HarmonyPatch] internal static class Patch_EndScreen_EndSequenceRoutine_Banners { private static readonly string[] DeadBannerMessages = new string[3] { "NO BODIES.
NOT EVEN YOURS", "A WITNESS
TO NOTHING", "THE MOUNTAIN
KEPT THEM ALL" }; private static readonly string[] FriendsWonBannerMessages = new string[3] { "GLORY BY
PROXY", "YOU HAUNTED THEM
TO THE TOP", "THEY SUMMITED
YOU WITNESSED" }; private static MethodBase TargetMethod() { return SpectatorState.GetCoroutineMoveNext(typeof(EndScreen), "", "Patch_EndScreen_EndSequenceRoutine_Banners"); } [HarmonyPostfix] private static void Postfix() { if (SpectatorState.BannersOverriddenThisRun || !Plugin.SpectatorEnabled.Value) { return; } Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null || !SpectatorState.IsLocalSpectator(localCharacter)) { return; } EndScreen instance = EndScreen.instance; if ((Object)(object)instance == (Object)null) { return; } GameObject val = null; string[] array = null; if ((Object)(object)instance.deadBanner != (Object)null && instance.deadBanner.activeSelf) { val = instance.deadBanner; array = DeadBannerMessages; } else if ((Object)(object)instance.yourFriendsWonBanner != (Object)null && instance.yourFriendsWonBanner.activeSelf) { val = instance.yourFriendsWonBanner; array = FriendsWonBannerMessages; } if ((Object)(object)val == (Object)null || array == null) { return; } string text = array[Random.Range(0, array.Length)]; LocalizedText componentInChildren = val.GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.SetText(text); SpectatorState.BannersOverriddenThisRun = true; Plugin.TraceDebug("[trace] EndScreen banner overridden via LocalizedText: \"" + text + "\""); return; } TMP_Text componentInChildren2 = val.GetComponentInChildren(true); if (!((Object)(object)componentInChildren2 == (Object)null)) { componentInChildren2.text = text; SpectatorState.BannersOverriddenThisRun = true; Plugin.TraceDebug("[trace] EndScreen banner overridden via direct TMP_Text (no LocalizedText found): \"" + text + "\""); } } } [HarmonyPatch(typeof(WaitingForPlayersUI), "Update")] internal static class Patch_WaitingForPlayersUI_Update { private static Sprite? _vanillaScoutSprite; private static Sprite? _ghostSprite; [HarmonyPrefix] private static void Prefix(WaitingForPlayersUI __instance) { if (__instance.scoutImages == null || __instance.scoutImages.Length == 0 || __instance.scoutImages.Length >= Character.AllCharacters.Count) { return; } Image val = __instance.scoutImages[0]; if ((Object)(object)val == (Object)null) { return; } Image[] array = (Image[])(object)new Image[Character.AllCharacters.Count]; for (int i = 0; i < Character.AllCharacters.Count; i++) { if (i < __instance.scoutImages.Length) { array[i] = __instance.scoutImages[i]; } else { array[i] = Object.Instantiate(val, ((Component)val).transform.parent); } } __instance.scoutImages = array; } [HarmonyPostfix] private static void Postfix(WaitingForPlayersUI __instance) { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) if (__instance.scoutImages == null || __instance.scoutImages.Length == 0) { return; } if ((Object)(object)_vanillaScoutSprite == (Object)null) { for (int i = 0; i < __instance.scoutImages.Length; i++) { if ((Object)(object)__instance.scoutImages[i] != (Object)null && (Object)(object)__instance.scoutImages[i].sprite != (Object)null) { _vanillaScoutSprite = __instance.scoutImages[i].sprite; break; } } } if ((Object)(object)_ghostSprite == (Object)null) { Texture2D val = GuiHelpers.BuildGhostTexture(64); _ghostSprite = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f)); } int num = 0; foreach (Player allPlayer in PlayerHandler.GetAllPlayers()) { if (num >= __instance.scoutImages.Length) { break; } Image val2 = __instance.scoutImages[num]; if ((Object)(object)val2 != (Object)null && (Object)(object)allPlayer != (Object)null && (Object)(object)((MonoBehaviourPun)allPlayer).photonView != (Object)null && ((MonoBehaviourPun)allPlayer).photonView.Owner != null) { Sprite val3 = (SpectatorState.IsTrustedSpectator(((MonoBehaviourPun)allPlayer).photonView.Owner) ? _ghostSprite : _vanillaScoutSprite); if ((Object)(object)val3 != (Object)null && (Object)(object)val2.sprite != (Object)(object)val3) { val2.sprite = val3; } } num++; } } } [HarmonyPatch(typeof(PointPinger), "DoPing")] internal static class Patch_PointPinger_DoPing { [HarmonyPrefix] private static void Prefix(PointPinger __instance) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown //IL_003f: Expected O, but got Unknown if (SpectatorState.IsGhost(__instance.character)) { Camera main = Camera.main; if (!((Object)(object)main == (Object)null)) { Hashtable val = new Hashtable(); ((Dictionary)val).Add((object)"GhostSpec.PingerCamera", (object)((Component)main).transform.position); Hashtable val2 = val; PhotonNetwork.LocalPlayer.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null); } } } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] internal static class Patch_PointPinger_get_canPing { [HarmonyPrefix] private static bool Prefix(PointPinger __instance, ref bool __result) { if (!SpectatorState.IsGhost(__instance.character)) { return true; } __result = Time.time - __instance._timeLastPinged >= __instance.coolDown; return false; } } [HarmonyPatch(typeof(PointPinger), "ReceivePoint_Rpc")] internal static class Patch_PointPinger_ReceivePoint_Rpc { [HarmonyPrefix] private static bool Prefix(PointPinger __instance, Vector3 point, Vector3 hitNormal) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: 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_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_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_0106: 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_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011a: 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_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) //IL_00d2: 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) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance.character == (Object)null) { return true; } if (!SpectatorState.IsFiniteVec(point) || !SpectatorState.IsFiniteVec(hitNormal)) { Plugin.TraceWarn($"GhostSpectator: dropped ReceivePoint_Rpc with non-finite payload (point={point}, hitNormal={hitNormal})."); return false; } Character localCharacter = Character.localCharacter; bool flag = SpectatorState.IsGhost(__instance.character); bool flag2 = SpectatorState.IsGhost(localCharacter); if (!flag && !flag2) { return true; } Camera main = Camera.main; Vector3 val; if (flag) { if ((Object)(object)__instance.character == (Object)(object)localCharacter && (Object)(object)main != (Object)null) { val = ((Component)main).transform.position; } else { val = __instance.character.Head; if (SpectatorState.TryGetOwner(__instance.character, out Player owner) && ((Dictionary)(object)owner.CustomProperties).TryGetValue((object)"GhostSpec.PingerCamera", out object value) && value is Vector3 val2) { val = val2; } } } else { val = __instance.character.Head; } Vector3 val3 = ((flag2 && (Object)(object)main != (Object)null) ? ((Component)main).transform.position : (((Object)(object)localCharacter != (Object)null) ? localCharacter.Head : __instance.character.Head)); if (!SpectatorState.IsFiniteVec(val) || !SpectatorState.IsFiniteVec(val3)) { Plugin.TraceWarn($"GhostSpectator: dropped ReceivePoint_Rpc with non-finite head position (pingerHead={val}, receiverHead={val3})."); return false; } Vector3 val4 = point - val; if (((Vector3)(ref val4)).sqrMagnitude < 1E-08f) { return false; } bool flag3 = Physics.Linecast(val, val3, LayerMask.op_Implicit(HelperFunctions.terrainMapMask)); float num = Vector3.Distance(val, val3); if ((Object)(object)__instance.pointPrefab == (Object)null) { return false; } PointPing component = __instance.pointPrefab.GetComponent(); if ((Object)(object)component == (Object)null) { return false; } Vector2 visibilityFullNoneNoLos = component.visibilityFullNoneNoLos; float num2 = 1f - Mathf.InverseLerp(visibilityFullNoneNoLos.x, visibilityFullNoneNoLos.x + (visibilityFullNoneNoLos.y - visibilityFullNoneNoLos.x) * (flag3 ? component.NoLosVisibilityMul : 1f), num); if (num2 <= 0f) { return false; } if ((Object)(object)__instance.pingInstance != (Object)null) { Object.DestroyImmediate((Object)(object)__instance.pingInstance); } GameObject pointPrefab = __instance.pointPrefab; val4 = point - val; __instance.pingInstance = Object.Instantiate(pointPrefab, point, Quaternion.LookRotation(((Vector3)(ref val4)).normalized, Vector3.up)); PointPing component2 = __instance.pingInstance.GetComponent(); component2.hitNormal = hitNormal; component2.Init(__instance.character); component2.pointPinger = __instance; if (__instance.character.refs != null && (Object)(object)__instance.character.refs.mainRenderer != (Object)null) { ((Renderer)component2.renderer).material = Object.Instantiate(((Renderer)__instance.character.refs.mainRenderer).sharedMaterial); } component2.material.SetFloat("_Opacity", num2); Object.Destroy((Object)(object)__instance.pingInstance, 2f); return false; } } [HarmonyPatch(typeof(GUIManager), "UpdateDyingBar")] internal static class Patch_GUIManager_UpdateDyingBar { private static GUIManager? _lastInstance; private static bool _lastObservedVisible; private static void SetObservedHudVisible(GUIManager m, bool visible) { if (m == _lastInstance && _lastObservedVisible == visible) { return; } _lastInstance = m; _lastObservedVisible = visible; float alpha = (visible ? 1f : 0f); if ((Object)(object)m.staminaCanvasGroup != (Object)null) { m.staminaCanvasGroup.alpha = alpha; } if ((Object)(object)m.mushroomsCanvasGroup != (Object)null) { m.mushroomsCanvasGroup.alpha = alpha; } if (m.items != null) { for (int i = 0; i < m.items.Length; i++) { if ((Object)(object)m.items[i] != (Object)null) { ((Component)m.items[i]).gameObject.SetActive(visible); } } } if ((Object)(object)m.backpack != (Object)null) { ((Component)m.backpack).gameObject.SetActive(visible); } } [HarmonyPrefix] private static bool Prefix(GUIManager __instance) { //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.SpectatorEnabled.Value) { return true; } if ((Object)(object)Character.localCharacter == (Object)null) { return true; } Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name == "Airport") { return true; } if ((Object)(object)__instance.dyingBarObject != (Object)null && __instance.dyingBarObject.activeSelf) { __instance.dyingBarObject.SetActive(false); } Character observedCharacter = Character.observedCharacter; bool flag = (Object)(object)observedCharacter == (Object)null || (Object)(object)observedCharacter == (Object)(object)Character.localCharacter; bool flag2 = (Object)(object)observedCharacter != (Object)null && !flag && SpectatorState.IsTrustedSpectator(observedCharacter); SetObservedHudVisible(__instance, !(flag || flag2)); return false; } } [HarmonyPatch(typeof(CharacterAfflictions), "AddStatus")] internal static class Patch_CharacterAfflictions_AddStatus { [HarmonyPrefix] private static bool Prefix(CharacterAfflictions __instance, ref bool __result) { if (SpectatorState.IsLocalSpectator(__instance.character)) { __result = false; return false; } return true; } } [HarmonyPatch(typeof(CharacterStats), "GetFinalTimelineInfo")] internal static class Patch_CharacterStats_GetFinalTimelineInfo { [HarmonyPrefix] private static bool Prefix(CharacterStats __instance, ref TimelineInfo __result) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: 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 (__instance.timelineInfo != null && __instance.timelineInfo.Count > 0) { return true; } if (SpectatorState.TryBorrowTimelineInfo(first: false, out var result)) { __result = result; return false; } __result = default(TimelineInfo); return false; } } [HarmonyPatch(typeof(CharacterStats), "GetFirstTimelineInfo")] internal static class Patch_CharacterStats_GetFirstTimelineInfo { [HarmonyPrefix] private static bool Prefix(CharacterStats __instance, ref TimelineInfo __result) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: 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 (__instance.timelineInfo != null && __instance.timelineInfo.Count > 0) { return true; } if (SpectatorState.TryBorrowTimelineInfo(first: true, out var result)) { __result = result; return false; } __result = default(TimelineInfo); return false; } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] internal static class Patch_NetworkingUtilities_MAX_PLAYERS { [HarmonyPrefix] private static bool Prefix(ref int __result) { __result = 20; return false; } } internal static class MidRunJoinGate { internal static string? PendingSceneName; internal static bool? LockedRoleForJoiner; internal static int LiveCountAtJoin = -1; } [HarmonyPatch(typeof(SteamLobbyHandler), "HandleMessage")] internal static class Patch_SteamLobbyHandler_HandleMessage_CaptureScene { [HarmonyPrefix] private static void Prefix(MessageType messageType, CSteamID lobbyID) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) if ((int)messageType != 2) { return; } string text; try { text = SteamMatchmaking.GetLobbyData(lobbyID, "CurrentScene"); } catch (Exception ex) { Plugin.Log.LogError((object)("[trace] reading Steam lobby CurrentScene threw: " + ex.GetType().Name + ": " + ex.Message)); return; } if (string.IsNullOrEmpty(text)) { text = "Airport"; } MidRunJoinGate.PendingSceneName = text; Plugin.TraceDebug("[trace] HandleMessage RoomID: captured target scene '" + text + "' for upcoming Load"); MidRunJoinGate.LockedRoleForJoiner = null; MidRunJoinGate.LiveCountAtJoin = -1; if (text == "Airport") { return; } try { string lobbyData = SteamMatchmaking.GetLobbyData(lobbyID, "GhostSpectator.RunRoles"); if (string.IsNullOrEmpty(lobbyData)) { return; } Dictionary dictionary = new Dictionary(); RoleLock.DeserializeRunRolesFromString(lobbyData, dictionary); string text2 = SteamUser.GetSteamID().m_SteamID.ToString(); if (dictionary.TryGetValue(text2, out var value)) { MidRunJoinGate.LockedRoleForJoiner = value; Plugin.Trace($"[trace] HandleMessage RoomID: rejoiner detected (userId={text2}); locked role isSpec={value}"); } int num = 0; foreach (KeyValuePair item in dictionary) { if (!item.Value) { num++; } } MidRunJoinGate.LiveCountAtJoin = num; Plugin.TraceDebug($"[trace] HandleMessage RoomID: counted {num} live player(s) already in run"); } catch (Exception ex2) { Plugin.TraceDebug("[trace] reading Steam lobby RunRoles failed: " + ex2.GetType().Name + ": " + ex2.Message); } } } [HarmonyPatch(typeof(LoadingScreenHandler), "Load")] internal static class Patch_LoadingScreenHandler_Load_InjectPopupWait { [CompilerGenerated] private sealed class d__1 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private MidRunJoinPopup 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__1(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__2 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; bool? lockedRoleForJoiner = MidRunJoinGate.LockedRoleForJoiner; MidRunJoinGate.LockedRoleForJoiner = null; if (lockedRoleForJoiner.HasValue) { Plugin.Trace($"[trace] mid-run rejoiner: applying locked role spectate={lockedRoleForJoiner.Value}, skipping popup"); Plugin.SpectatorEnabled.Value = lockedRoleForJoiner.Value; return false; } int liveCountAtJoin = MidRunJoinGate.LiveCountAtJoin; MidRunJoinGate.LiveCountAtJoin = -1; if (liveCountAtJoin >= 0 && liveCountAtJoin >= 4) { Plugin.Trace($"[trace] mid-run joiner: live cap saturated ({liveCountAtJoin}/{4}); auto-spectating, skipping popup"); Plugin.SpectatorEnabled.Value = true; return false; } 5__2 = MidRunJoinPopup.Instance; if ((Object)(object)5__2 == (Object)null) { Plugin.TraceWarn("[trace] mid-run wait coroutine: MidRunJoinPopup.Instance missing; defaulting to live join"); Plugin.SpectatorEnabled.Value = false; return false; } 5__2.Show(); Plugin.Trace("[trace] mid-run wait coroutine: popup shown, waiting for click"); break; } case 1: <>1__state = -1; break; } if (5__2.IsShowing && !5__2.HasChoice) { <>2__current = null; <>1__state = 1; return true; } if (!5__2.HasChoice) { Plugin.TraceWarn("[trace] mid-run wait coroutine: popup hid without a click; defaulting to live join"); Plugin.SpectatorEnabled.Value = false; return false; } 5__2.Hide(); Plugin.Trace($"[trace] mid-run popup choice received: spectate={5__2.ChoiceIsSpectate}"); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [HarmonyPrefix] private static void Prefix(ref IEnumerator[] processes) { string pendingSceneName = MidRunJoinGate.PendingSceneName; MidRunJoinGate.PendingSceneName = null; bool? lockedRoleForJoiner = MidRunJoinGate.LockedRoleForJoiner; int liveCountAtJoin = MidRunJoinGate.LiveCountAtJoin; MidRunJoinGate.LockedRoleForJoiner = null; MidRunJoinGate.LiveCountAtJoin = -1; if (pendingSceneName != null && !(pendingSceneName == "Airport") && processes != null) { MidRunJoinGate.LockedRoleForJoiner = lockedRoleForJoiner; MidRunJoinGate.LiveCountAtJoin = liveCountAtJoin; Plugin.Trace("[trace] injecting mid-run popup wait into Load() processes for scene '" + pendingSceneName + "'"); IEnumerator[] array = new IEnumerator[processes.Length + 1]; array[0] = WaitForMidRunChoice(); for (int i = 0; i < processes.Length; i++) { array[i + 1] = processes[i]; } processes = array; } } [IteratorStateMachine(typeof(d__1))] private static IEnumerator WaitForMidRunChoice() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__1(0); } } [HarmonyPatch(typeof(Character), "Start")] internal static class Patch_Character_Start_ValidateOwner { [CompilerGenerated] private sealed class d__1 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Character c; public Player owner; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__1(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = (object)new WaitForEndOfFrame(); <>1__state = 2; return true; case 2: <>1__state = -1; if ((Object)(object)c == (Object)null || owner == null) { return false; } if (SpectatorState.HasGhostSpectatorMod(owner)) { Plugin.TraceDebug($"[trace] deferred validate: {owner.NickName} (#{owner.ActorNumber}) property arrived in deferral window, no action"); return false; } Plugin.Trace($"[trace] unmoded joiner confirmed at Character.Start+1frame: {owner.NickName} (#{owner.ActorNumber}); hiding visuals"); SpectatorState.HideCharacterVisuals(c); if (PhotonNetwork.IsMasterClient) { Plugin.Trace($"[trace] kicking {owner.NickName} (#{owner.ActorNumber}): GhostSpectator mod not installed"); SpectatorState.TryAddConnectionLogMessage(owner.NickName + " was disconnected: GhostSpectator mod required"); try { PlayerHandler.Kick(owner.ActorNumber); } catch (Exception ex) { Plugin.Log.LogError((object)("[trace] PlayerHandler.Kick threw on " + owner.NickName + ": " + ex.GetType().Name + ": " + ex.Message)); } } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [HarmonyPostfix] private static void Postfix(Character __instance) { if (!((Object)(object)__instance == (Object)null) && (!((Object)(object)Character.localCharacter != (Object)null) || !((Object)(object)__instance == (Object)(object)Character.localCharacter)) && SpectatorState.TryGetOwner(__instance, out Player owner) && !SpectatorState.HasGhostSpectatorMod(owner)) { ((MonoBehaviour)__instance).StartCoroutine(DeferredValidate(__instance, owner)); } } [IteratorStateMachine(typeof(d__1))] private static IEnumerator DeferredValidate(Character c, Player owner) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__1(0) { c = c, owner = owner }; } } [HarmonyPatch(typeof(Character), "RPCA_Revive")] internal static class Patch_Character_RPCA_Revive { [HarmonyPrefix] private static bool Prefix(Character __instance) { if (SpectatorState.IsTrustedSpectator(__instance)) { Plugin.TraceDebug("GhostSpectator: blocked RPCA_Revive on spectator."); return false; } return true; } } [HarmonyPatch(typeof(Character), "RPCA_ReviveAtPosition")] internal static class Patch_Character_RPCA_ReviveAtPosition { [HarmonyPrefix] private static bool Prefix(Character __instance) { if (SpectatorState.IsTrustedSpectator(__instance)) { Plugin.TraceDebug("GhostSpectator: blocked RPCA_ReviveAtPosition on spectator."); return false; } return true; } } [HarmonyPatch(typeof(RespawnRandomScout), "Start")] internal static class Patch_RespawnRandomScout_Start { [HarmonyPrefix] private static bool Prefix(RespawnRandomScout __instance) { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.IsMasterClient) { List list = new List(); foreach (Character allCharacter in Character.AllCharacters) { if ((allCharacter.data.dead || allCharacter.data.fullyPassedOut) && !SpectatorState.IsTrustedSpectator(allCharacter)) { list.Add(allCharacter); } } if (list.Count > 0) { ((MonoBehaviourPun)Util.RandomSelection((IEnumerable)list, (Func)((Character _) => 1))).photonView.RPC("RPCA_ReviveAtPosition", (RpcTarget)0, new object[3] { ((Component)__instance).transform.position, false, -1 }); } } Object.Destroy((Object)(object)((Component)__instance).gameObject); return false; } } internal static class RoleLock { internal const string RoomRunRolesKey = "GhostSpectator.RunRoles"; internal static readonly Dictionary RunRoles = new Dictionary(); internal static Hashtable SerializeRunRolesToHashtable() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown Hashtable val = new Hashtable(); lock (RunRoles) { foreach (KeyValuePair runRole in RunRoles) { val[(object)runRole.Key] = runRole.Value; } return val; } } internal static void DeserializeRunRolesFromHashtable(object? raw) { lock (RunRoles) { RunRoles.Clear(); Hashtable val = (Hashtable)((raw is Hashtable) ? raw : null); if (val == null) { return; } foreach (object key2 in ((Dictionary)(object)val).Keys) { if (key2 is string key && val[key2] is bool value) { RunRoles[key] = value; } } } } internal static string SerializeRunRolesToString() { StringBuilder stringBuilder = new StringBuilder(); lock (RunRoles) { bool flag = true; foreach (KeyValuePair runRole in RunRoles) { if (!flag) { stringBuilder.Append(';'); } stringBuilder.Append(runRole.Key); stringBuilder.Append(':'); stringBuilder.Append(runRole.Value ? '1' : '0'); flag = false; } } return stringBuilder.ToString(); } internal static void DeserializeRunRolesFromString(string? s, Dictionary target) { target.Clear(); if (string.IsNullOrEmpty(s)) { return; } string[] array = s.Split(';'); string[] array2 = array; foreach (string text in array2) { int num = text.IndexOf(':'); if (num > 0 && num != text.Length - 1) { string key = text.Substring(0, num); string text2 = text.Substring(num + 1); if (text2 == "1") { target[key] = true; } else if (text2 == "0") { target[key] = false; } } } } internal static void PublishRunRolesToNetwork() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown //IL_0029: Expected O, but got Unknown if (PhotonNetwork.IsMasterClient && PhotonNetwork.CurrentRoom != null) { try { Hashtable value = SerializeRunRolesToHashtable(); Hashtable val = new Hashtable(); ((Dictionary)val).Add((object)"GhostSpectator.RunRoles", (object)value); Hashtable val2 = val; PhotonNetwork.CurrentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null); } catch (Exception ex) { Plugin.Log.LogError((object)("[trace] PublishRunRolesToNetwork SetCustomProperties failed: " + ex.GetType().Name + ": " + ex.Message)); } TryWriteRunRolesToSteamLobby(); } } internal static void TryWriteRunRolesToSteamLobby() { //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_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) try { SteamLobbyHandler service = GameHandler.GetService(); if (service != null) { CSteamID lobbySteamId = service.LobbySteamId; if (!(lobbySteamId == CSteamID.Nil)) { string text = SerializeRunRolesToString(); SteamMatchmaking.SetLobbyData(lobbySteamId, "GhostSpectator.RunRoles", text); Plugin.TraceDebug($"[trace] mirrored RunRoles to Steam lobby data ({text.Length} chars)"); } } } catch (Exception ex) { Plugin.TraceDebug("[trace] TryWriteRunRolesToSteamLobby failed: " + ex.GetType().Name + ": " + ex.Message); } } internal static bool RunRolesHashtableMatchesInMemory(Hashtable? inbound) { if (inbound == null) { return RunRoles.Count == 0; } lock (RunRoles) { if (((Dictionary)(object)inbound).Count != RunRoles.Count) { return false; } foreach (object key2 in ((Dictionary)(object)inbound).Keys) { if (!(key2 is string key)) { return false; } if (!RunRoles.TryGetValue(key, out var value)) { return false; } if (!(inbound[key2] is bool flag)) { return false; } if (flag != value) { return false; } } return true; } } } internal static class KioskGate { internal static (bool passed, string? refuseMessage) CheckRunStartPreconditions() { if (PhotonNetwork.CurrentRoom == null) { return (true, null); } foreach (Player value in PhotonNetwork.CurrentRoom.Players.Values) { if (value != null && !SpectatorState.HasGhostSpectatorMod(value)) { string text = (string.IsNullOrEmpty(value.NickName) ? "(unnamed)" : value.NickName); return (false, "Can't start: " + text + " doesn't have GhostSpectator installed. All players must install the mod from Thunderstore."); } } if (RoomCallbackHandler.CountNonSpectators() == 0) { return (false, "Can't start: every player is set to Ghost Spectator. Toggle off in the airport panel, or wait for a non-spectator to join."); } return (true, null); } internal static void OnRunStartedMaster() { SpectatorState.RunHasEnded = false; Patch_Character_EndGame.ResetSuppressionTimer(); lock (RoleLock.RunRoles) { RoleLock.RunRoles.Clear(); foreach (Player value in PhotonNetwork.CurrentRoom.Players.Values) { if (value != null && !string.IsNullOrEmpty(value.UserId)) { RoleLock.RunRoles[value.UserId] = SpectatorState.ClaimsSpectator(value); } } } Plugin.Trace($"[trace] run started: snapshotted {RoleLock.RunRoles.Count} player(s) into RunRoles"); RoleLock.PublishRunRolesToNetwork(); } } [HarmonyPatch(typeof(AirportCheckInKiosk), "StartGame")] internal static class Patch_AirportCheckInKiosk_StartGame { [HarmonyPrefix] private static bool Prefix() { string[] obj = new string[6] { $"[trace] StartGame prefix fired. InRoom={PhotonNetwork.InRoom}, ", $"SpectatorEnabled={Plugin.SpectatorEnabled.Value}, ", "CurrentRoomPlayerCount=", null, null, null }; Room currentRoom = PhotonNetwork.CurrentRoom; obj[3] = ((currentRoom != null) ? currentRoom.PlayerCount.ToString() : null) ?? "n/a"; obj[4] = ", "; obj[5] = $"NonSpectators={RoomCallbackHandler.CountNonSpectators()}"; Plugin.TraceDebug(string.Concat(obj)); var (flag, text) = KioskGate.CheckRunStartPreconditions(); if (!flag) { Plugin.TraceWarn("GhostSpectator: clicker-side gate refused: " + text); SpectatorState.RefuseKiosk(text); return false; } SpectatorState.RunHasEnded = false; return true; } } [HarmonyPatch(typeof(AirportCheckInKiosk), "LoadIslandMaster")] internal static class Patch_AirportCheckInKiosk_LoadIslandMaster { [HarmonyPrefix] private static bool Prefix() { Plugin.TraceDebug($"[trace] LoadIslandMaster prefix fired. IsMasterClient={PhotonNetwork.IsMasterClient}"); if (!PhotonNetwork.IsMasterClient) { return true; } var (flag, text) = KioskGate.CheckRunStartPreconditions(); if (!flag) { Plugin.TraceWarn("GhostSpectator: master-side gate refused: " + text); SpectatorState.RefuseKiosk(text); return false; } KioskGate.OnRunStartedMaster(); return true; } } [HarmonyPatch(typeof(PeakHandler), "SetCosmetics")] internal static class Patch_PeakHandler_SetCosmetics { [HarmonyPrefix] private static bool Prefix(List characters) { if (characters == null) { return true; } for (int i = 0; i < characters.Count; i++) { Character val = characters[i]; if ((Object)(object)val != (Object)null && val.refs != null && (Object)(object)val.refs.stats != (Object)null && val.refs.stats.won) { return true; } } Plugin.Trace("[trace] SetCosmetics: nobody won this run, skipping cosmetic setup to keep EndCutscene from crashing."); return false; } } [HarmonyPatch(typeof(AchievementManager), "ThrowAchievement")] internal static class Patch_AchievementManager_ThrowAchievement { [HarmonyPrefix] private static bool Prefix(ACHIEVEMENTTYPE type) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.SpectatorEnabled.Value) { return true; } Plugin.TraceDebug($"[trace] blocked achievement {type} on spectator"); return false; } } [HarmonyPatch(typeof(AchievementManager), "TryCompleteAscent")] internal static class Patch_AchievementManager_TryCompleteAscent { [HarmonyPrefix] private static bool Prefix() { if (!Plugin.SpectatorEnabled.Value) { return true; } Plugin.TraceDebug("[trace] blocked TryCompleteAscent on spectator"); return false; } } internal static class SpectatorState { [CompilerGenerated] private sealed class d__32 : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable { private int <>1__state; private CodeInstruction <>2__current; private int <>l__initialThreadId; private IEnumerable instructions; public IEnumerable <>3__instructions; private FieldInfo 5__2; private MethodInfo 5__3; private IEnumerator <>7__wrap3; CodeInstruction IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__32(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } 5__2 = null; 5__3 = null; <>7__wrap3 = null; <>1__state = -2; } private bool MoveNext() { try { switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__2 = AccessTools.Field(typeof(Character), "AllCharacters"); 5__3 = AccessTools.Method(typeof(SpectatorState), "GetCharactersIgnoringSpectator", (Type[])null, (Type[])null); <>7__wrap3 = instructions.GetEnumerator(); <>1__state = -3; break; case 1: <>1__state = -3; break; } if (<>7__wrap3.MoveNext()) { CodeInstruction current = <>7__wrap3.Current; if (CodeInstructionExtensions.LoadsField(current, 5__2, false)) { current.opcode = OpCodes.Call; current.operand = 5__3; } <>2__current = current; <>1__state = 1; return true; } <>m__Finally1(); <>7__wrap3 = null; return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>7__wrap3 != null) { <>7__wrap3.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { d__32 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__32(0); } d__.instructions = <>3__instructions; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } [CompilerGenerated] private sealed class d__33 : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable { private int <>1__state; private CodeInstruction <>2__current; private int <>l__initialThreadId; private IEnumerable instructions; public IEnumerable <>3__instructions; private MethodInfo 5__2; private MethodInfo 5__3; private IEnumerator <>7__wrap3; CodeInstruction IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__33(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } 5__2 = null; 5__3 = null; <>7__wrap3 = null; <>1__state = -2; } private bool MoveNext() { try { switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__2 = AccessTools.PropertyGetter(typeof(PhotonNetwork), "PlayerList"); 5__3 = AccessTools.Method(typeof(RoomCallbackHandler), "GetNonSpectatorPlayerList", (Type[])null, (Type[])null); <>7__wrap3 = instructions.GetEnumerator(); <>1__state = -3; break; case 1: <>1__state = -3; break; } if (<>7__wrap3.MoveNext()) { CodeInstruction current = <>7__wrap3.Current; if (CodeInstructionExtensions.Calls(current, 5__2)) { current.opcode = OpCodes.Call; current.operand = 5__3; } <>2__current = current; <>1__state = 1; return true; } <>m__Finally1(); <>7__wrap3 = null; return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>7__wrap3 != null) { <>7__wrap3.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { d__33 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__33(0); } d__.instructions = <>3__instructions; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } internal const int LiveCap = 4; internal const int SpectatorCap = 16; internal const int TotalRoomCap = 20; internal static Vector3 SpectatorSpawnPosition = Vector3.zero; internal static bool SpectatorSpawnPositionValid = false; internal static Vector3 LastSpectateCameraPosition = Vector3.zero; internal static Quaternion LastSpectateCameraRotation = Quaternion.identity; internal static bool LastSpectateCameraValid = false; internal static float KioskRefusedTimestamp = -1f; internal static string KioskRefusedMessage = string.Empty; internal const float KioskRefusedDisplayDuration = 5f; internal const string GhostPingerCameraKey = "GhostSpec.PingerCamera"; internal static bool RunHasEnded = false; internal static bool HasPublishedLocalCustomizationData = false; internal static bool BannersOverriddenThisRun = false; internal static bool IsLocalSpectator(Character character) { if (Plugin.SpectatorEnabled.Value && (Object)(object)Character.localCharacter != (Object)null) { return (Object)(object)character == (Object)(object)Character.localCharacter; } return false; } internal static void RefuseKiosk(string message) { KioskRefusedTimestamp = Time.realtimeSinceStartup; KioskRefusedMessage = message; } internal static bool IsGhost(Character c) { if ((Object)(object)c == (Object)null) { return false; } CharacterData data = c.data; if ((Object)(object)data != (Object)null) { if (!data.dead) { return data.fullyPassedOut; } return true; } return false; } internal static bool IsFiniteVec(Vector3 v) { //IL_0000: 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_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0034: 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) if (!float.IsNaN(v.x) && !float.IsNaN(v.y) && !float.IsNaN(v.z) && !float.IsInfinity(v.x) && !float.IsInfinity(v.y)) { return !float.IsInfinity(v.z); } return false; } internal static bool TryGetOwner(Character? character, [NotNullWhen(true)] out Player? owner) { owner = null; if ((Object)(object)character == (Object)null) { return false; } try { PhotonView photonView = ((MonoBehaviourPun)character).photonView; if ((Object)(object)photonView == (Object)null) { return false; } owner = photonView.Owner; return owner != null; } catch (NullReferenceException) { return false; } } internal static MethodBase? GetCoroutineMoveNext(Type outer, string namePrefix, string patchLabel) { string namePrefix2 = namePrefix; Type type = AccessTools.FirstInner(outer, (Func)((Type t) => t.Name.Contains(namePrefix2))); if (type == null) { Plugin.Log.LogError((object)("[trace] " + patchLabel + ": '" + namePrefix2 + "' state machine type not found on " + outer.Name + ", skipping patch")); return null; } return AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null); } internal static bool ClaimsSpectator(Player player) { if (player == null) { return false; } try { bool flag = default(bool); int num; if (player.CustomProperties != null && ((Dictionary)(object)player.CustomProperties).TryGetValue((object)"GhostSpectator.IsSpectator", out object value)) { if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch (NullReferenceException) { return false; } } internal static bool HasGhostSpectatorMod(Player player) { if (player == null) { return false; } try { object value; return player.CustomProperties != null && ((Dictionary)(object)player.CustomProperties).TryGetValue((object)"GhostSpectator.IsSpectator", out value) && value is bool; } catch (NullReferenceException) { return false; } } internal static void HideCharacterVisuals(Character c) { if ((Object)(object)c == (Object)null) { return; } try { Renderer[] componentsInChildren = ((Component)c).GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { if ((Object)(object)componentsInChildren[i] != (Object)null) { componentsInChildren[i].enabled = false; } } Graphic[] componentsInChildren2 = ((Component)c).GetComponentsInChildren(true); for (int j = 0; j < componentsInChildren2.Length; j++) { if ((Object)(object)componentsInChildren2[j] != (Object)null) { ((Behaviour)componentsInChildren2[j]).enabled = false; } } } catch (Exception ex) { Plugin.TraceDebug("[trace] HideCharacterVisuals failed: " + ex.GetType().Name + ": " + ex.Message); } } internal static void TryAddConnectionLogMessage(string message) { try { PlayerConnectionLog val = Object.FindAnyObjectByType(); if (!((Object)(object)val == (Object)null)) { MethodInfo methodInfo = AccessTools.Method(typeof(PlayerConnectionLog), "AddMessage", (Type[])null, (Type[])null); if (!(methodInfo == null)) { methodInfo.Invoke(val, new object[1] { message }); } } } catch (Exception ex) { Plugin.TraceDebug("[trace] TryAddConnectionLogMessage failed: " + ex.GetType().Name + ": " + ex.Message); } } private static bool TryGetLockedRole(Player player, out bool isSpectator) { isSpectator = false; if (player == null || string.IsNullOrEmpty(player.UserId)) { return false; } lock (RoleLock.RunRoles) { return RoleLock.RunRoles.TryGetValue(player.UserId, out isSpectator); } } internal static bool IsTrustedSpectator(Character character) { if ((Object)(object)character == (Object)null) { return false; } if (IsLocalSpectator(character)) { return true; } if (!TryGetOwner(character, out Player owner)) { return false; } if (TryGetLockedRole(owner, out var isSpectator)) { return isSpectator; } if (!ClaimsSpectator(owner)) { return false; } return IsGhost(character); } internal static bool IsPlayerCharacterDead(Player? player) { if (player == null) { return false; } List allCharacters = Character.AllCharacters; if (allCharacters == null) { return false; } for (int i = 0; i < allCharacters.Count; i++) { Character val = allCharacters[i]; if ((Object)(object)val == (Object)null || (Object)(object)((MonoBehaviourPun)val).photonView == (Object)null || ((MonoBehaviourPun)val).photonView.Owner == null || ((MonoBehaviourPun)val).photonView.Owner.ActorNumber != player.ActorNumber) { continue; } if ((Object)(object)val.data != (Object)null) { if (!val.data.dead) { return val.data.fullyPassedOut; } return true; } return false; } return false; } internal static bool IsTrustedSpectator(Player player) { if (player == null) { return false; } if (TryGetLockedRole(player, out var isSpectator)) { return isSpectator; } if (!ClaimsSpectator(player)) { return false; } List allCharacters = Character.AllCharacters; if (allCharacters == null) { return true; } for (int i = 0; i < allCharacters.Count; i++) { Character val = allCharacters[i]; if (!((Object)(object)val == (Object)null)) { PhotonView photonView = ((MonoBehaviourPun)val).photonView; if ((Object)(object)photonView != (Object)null && photonView.Owner != null && photonView.Owner.ActorNumber == player.ActorNumber) { return IsGhost(val); } } } return true; } internal static bool TryBorrowTimelineInfo(bool first, out TimelineInfo result) { //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_006e: 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) foreach (Character allCharacter in Character.AllCharacters) { if (!((Object)(object)allCharacter == (Object)null) && !IsTrustedSpectator(allCharacter)) { CharacterStats val = allCharacter.refs?.stats; if (!((Object)(object)val == (Object)null) && val.timelineInfo != null && val.timelineInfo.Count != 0) { result = (first ? val.timelineInfo[0] : val.timelineInfo[val.timelineInfo.Count - 1]); return true; } } } result = default(TimelineInfo); return false; } internal static bool AllExpectedCharactersSpawned(out int expectedPlayers, out int actualCharacters) { Room currentRoom = PhotonNetwork.CurrentRoom; expectedPlayers = ((currentRoom != null) ? currentRoom.PlayerCount : 0); actualCharacters = ((Character.AllCharacters != null) ? Character.AllCharacters.Count : 0); if (expectedPlayers > 0) { return actualCharacters >= expectedPlayers; } return true; } internal static List GetCharactersIgnoringSpectator() { List list = new List(Character.AllCharacters.Count); foreach (Character allCharacter in Character.AllCharacters) { if (!((Object)(object)allCharacter == (Object)null) && !IsTrustedSpectator(allCharacter) && (!((Object)(object)Character.localCharacter != (Object)(object)allCharacter) || !TryGetOwner(allCharacter, out Player owner) || HasGhostSpectatorMod(owner))) { list.Add(allCharacter); } } return list; } [IteratorStateMachine(typeof(d__32))] internal static IEnumerable ReplaceAllCharactersTranspiler(IEnumerable instructions) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__32(-2) { <>3__instructions = instructions }; } [IteratorStateMachine(typeof(d__33))] internal static IEnumerable ReplacePlayerListTranspiler(IEnumerable instructions) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__33(-2) { <>3__instructions = instructions }; } } [HarmonyPatch(typeof(Character), "PlayerIsDeadOrDown")] internal static class Patch_Character_PlayerIsDeadOrDown { [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { return SpectatorState.ReplaceAllCharactersTranspiler(instructions); } } [HarmonyPatch(typeof(ScoutEffigy), "FinishConstruction")] internal static class Patch_ScoutEffigy_FinishConstruction { [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { return SpectatorState.ReplaceAllCharactersTranspiler(instructions); } } [HarmonyPatch(typeof(RespawnChest), "RespawnAllPlayersHere")] internal static class Patch_RespawnChest_RespawnAllPlayersHere { [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { return SpectatorState.ReplaceAllCharactersTranspiler(instructions); } } [HarmonyPatch(typeof(Fog), "PlayersHaveMovedOn")] internal static class Patch_Fog_PlayersHaveMovedOn { [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { return SpectatorState.ReplaceAllCharactersTranspiler(instructions); } } [HarmonyPatch(typeof(MovingLava), "PlayersHaveMovedOn")] internal static class Patch_MovingLava_PlayersHaveMovedOn { [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { return SpectatorState.ReplaceAllCharactersTranspiler(instructions); } } [HarmonyPatch(typeof(SingleItemSpawner), "TrySpawnItems")] internal static class Patch_SingleItemSpawner_TrySpawnItems { [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { return SpectatorState.ReplacePlayerListTranspiler(instructions); } } [HarmonyPatch(typeof(Spawner), "TrySpawnItems")] internal static class Patch_Spawner_TrySpawnItems { [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { return SpectatorState.ReplacePlayerListTranspiler(instructions); } } [HarmonyPatch] internal static class Patch_DestroyBasedOnPlayerCount_StateMachine { private static MethodBase TargetMethod() { return SpectatorState.GetCoroutineMoveNext(typeof(DestroyBasedOnPlayerCount), "", "Patch_DestroyBasedOnPlayerCount_StateMachine"); } [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { return SpectatorState.ReplacePlayerListTranspiler(instructions); } } [HarmonyPatch] internal static class Patch_EndScreen_EndSequenceRoutine_StateMachine { private static MethodBase TargetMethod() { return SpectatorState.GetCoroutineMoveNext(typeof(EndScreen), "", "Patch_EndScreen_EndSequenceRoutine_StateMachine"); } [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { return SpectatorState.ReplaceAllCharactersTranspiler(instructions); } } } namespace System.Diagnostics.CodeAnalysis { [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class ConstantExpectedAttribute : Attribute { public object? Min { get; set; } public object? Max { get; set; } } [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class ExperimentalAttribute : Attribute { public string DiagnosticId { get; } public string? UrlFormat { get; set; } public ExperimentalAttribute(string diagnosticId) { DiagnosticId = diagnosticId; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] [ExcludeFromCodeCoverage] internal sealed class MemberNotNullAttribute : Attribute { public string[] Members { get; } public MemberNotNullAttribute(string member) { Members = new string[1] { member }; } public MemberNotNullAttribute(params string[] members) { Members = members; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] [ExcludeFromCodeCoverage] internal sealed class MemberNotNullWhenAttribute : Attribute { public bool ReturnValue { get; } public string[] Members { get; } public MemberNotNullWhenAttribute(bool returnValue, string member) { ReturnValue = returnValue; Members = new string[1] { member }; } public MemberNotNullWhenAttribute(bool returnValue, params string[] members) { ReturnValue = returnValue; Members = members; } } [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class SetsRequiredMembersAttribute : Attribute { } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class StringSyntaxAttribute : Attribute { public const string CompositeFormat = "CompositeFormat"; public const string DateOnlyFormat = "DateOnlyFormat"; public const string DateTimeFormat = "DateTimeFormat"; public const string EnumFormat = "EnumFormat"; public const string GuidFormat = "GuidFormat"; public const string Json = "Json"; public const string NumericFormat = "NumericFormat"; public const string Regex = "Regex"; public const string TimeOnlyFormat = "TimeOnlyFormat"; public const string TimeSpanFormat = "TimeSpanFormat"; public const string Uri = "Uri"; public const string Xml = "Xml"; public string Syntax { get; } public object?[] Arguments { get; } public StringSyntaxAttribute(string syntax) { Syntax = syntax; Arguments = new object[0]; } public StringSyntaxAttribute(string syntax, params object?[] arguments) { Syntax = syntax; Arguments = arguments; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class UnscopedRefAttribute : Attribute { } } namespace System.Runtime.Versioning { [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class RequiresPreviewFeaturesAttribute : Attribute { public string? Message { get; } public string? Url { get; set; } public RequiresPreviewFeaturesAttribute() { } public RequiresPreviewFeaturesAttribute(string? message) { Message = message; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class CallerArgumentExpressionAttribute : Attribute { public string ParameterName { get; } public CallerArgumentExpressionAttribute(string parameterName) { ParameterName = parameterName; } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class CollectionBuilderAttribute : Attribute { public Type BuilderType { get; } public string MethodName { get; } public CollectionBuilderAttribute(Type builderType, string methodName) { BuilderType = builderType; MethodName = methodName; } } [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class CompilerFeatureRequiredAttribute : Attribute { public const string RefStructs = "RefStructs"; public const string RequiredMembers = "RequiredMembers"; public string FeatureName { get; } public bool IsOptional { get; set; } public CompilerFeatureRequiredAttribute(string featureName) { FeatureName = featureName; } } [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class InterpolatedStringHandlerArgumentAttribute : Attribute { public string[] Arguments { get; } public InterpolatedStringHandlerArgumentAttribute(string argument) { Arguments = new string[1] { argument }; } public InterpolatedStringHandlerArgumentAttribute(params string[] arguments) { Arguments = arguments; } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class InterpolatedStringHandlerAttribute : Attribute { } [EditorBrowsable(EditorBrowsableState.Never)] [ExcludeFromCodeCoverage] internal static class IsExternalInit { } [AttributeUsage(AttributeTargets.Method, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class ModuleInitializerAttribute : Attribute { } [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class OverloadResolutionPriorityAttribute : Attribute { public int Priority { get; } public OverloadResolutionPriorityAttribute(int priority) { Priority = priority; } } [AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)] [ExcludeFromCodeCoverage] internal sealed class ParamCollectionAttribute : Attribute { } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class RequiredMemberAttribute : Attribute { } [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] [EditorBrowsable(EditorBrowsableState.Never)] [ExcludeFromCodeCoverage] internal sealed class RequiresLocationAttribute : Attribute { } [AttributeUsage(AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Interface, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class SkipLocalsInitAttribute : Attribute { } }