using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("Zichen-LateJoinNow-1.0.1")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+91fe42e22f71e843e8fdae1615fbf0b1d6b512da")] [assembly: AssemblyProduct("Zichen-LateJoinNow-1.0.1")] [assembly: AssemblyTitle("Zichen-LateJoinNow-1.0.1")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Zichen_LateJoinNow { public enum DisplayLanguage { 中文, English } [BepInPlugin("zichen.latejoinnow", "LateJoinNow", "1.0.1")] public class Plugin : BaseUnityPlugin { private sealed class StateSyncEntry { public string TypeName; public string FieldName; public string RpcName; public bool CastToInt; public Type ResolvedType; public FieldInfo ResolvedField; public bool ResolveAttempted; } [CompilerGenerated] private sealed class d__124 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Player target; private int 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__124(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__2 = 0; break; case 1: <>1__state = -1; 5__2++; break; } if (5__2 < 3) { if (!IsTargetStillOnline(target)) { return false; } int num = 0; try { IEnumerable enumerable2; if (!((Object)(object)GameDirector.instance != (Object)null) || GameDirector.instance.PlayerList == null) { IEnumerable enumerable = Object.FindObjectsOfType(); enumerable2 = enumerable; } else { IEnumerable enumerable = GameDirector.instance.PlayerList; enumerable2 = enumerable; } foreach (PlayerAvatar item in enumerable2) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item.photonView == (Object)null)) { try { item.photonView.RPC("LoadingLevelAnimationCompletedRPC", (RpcTarget)0, Array.Empty()); num++; } catch { } } } } catch (Exception ex) { LogWarning("[LateJoin] 发 LoadingLevelAnimationCompletedRPC 失败:" + ex.Message); } if (5__2 == 0) { LogInfo($"[LateJoin] 阶段 3/3 → {target.NickName}: LoadingLevelAnimationCompletedRPC 已对 {num} 个 PlayerAvatar 首发(RpcTarget.All)。"); } else { Verbose($"[LateJoin] LoadingLevelAnimationCompletedRPC 第 {5__2 + 1} 次重发 → {num} 个 PlayerAvatar。"); } <>2__current = (object)new WaitForSecondsRealtime(0.5f); <>1__state = 1; return true; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__113 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Player target; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__113(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; EnsurePlayerCorpseObjects(target); <>2__current = ((MonoBehaviour)Instance).StartCoroutine(SendCatchupSnapshotStaged(target)); <>1__state = 2; return true; case 2: <>1__state = -1; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__125 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Player target; private string 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__125(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Expected O, but got Unknown //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Expected O, but got Unknown //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Expected O, but got Unknown //IL_02f1: Unknown result type (might be due to invalid IL or missing references) //IL_02fb: Expected O, but got Unknown //IL_0353: Unknown result type (might be due to invalid IL or missing references) //IL_035d: Expected O, but got Unknown //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__2 = CurrentSceneName(); if (VerboseLogEnabled) { int num = 0; try { num = Object.FindObjectsOfType().Length; } catch { } LogInfo($"[LateJoin] 启动快照协程 → {target.NickName} | 关卡#{LateJoinNowHooks.ChangeLevelCounter} startScene={5__2} 当前PhotonView总数={num}"); } else { LogInfo($"[LateJoin] 启动快照协程 → {target.NickName} | 关卡#{LateJoinNowHooks.ChangeLevelCounter} startScene={5__2}"); } <>2__current = (object)new WaitForSecondsRealtime(0.5f); <>1__state = 1; return true; case 1: <>1__state = -1; if (ShouldAbortSnapshot(target, 5__2, "阶段 2 前")) { return false; } try { if ((Object)(object)NetworkManager.instance != (Object)null && (Object)(object)((MonoBehaviourPun)NetworkManager.instance).photonView != (Object)null) { ((MonoBehaviourPun)NetworkManager.instance).photonView.RPC("AllPlayerSpawnedRPC", target, Array.Empty()); LogInfo("[LateJoin] 阶段 1/3 → " + target.NickName + ": AllPlayerSpawnedRPC 已发送。"); } } catch (Exception ex2) { LogWarning("[LateJoin] 发 AllPlayerSpawnedRPC 失败:" + ex2.Message); } <>2__current = (object)new WaitForSecondsRealtime(0.6f); <>1__state = 2; return true; case 2: <>1__state = -1; if (ShouldAbortSnapshot(target, 5__2, "阶段 3.5 前")) { return false; } <>2__current = ((MonoBehaviour)Instance).StartCoroutine(WaitHostGenerated(target)); <>1__state = 3; return true; case 3: <>1__state = -1; if (ShouldAbortSnapshot(target, 5__2, "阶段 4 前")) { return false; } <>2__current = ((MonoBehaviour)Instance).StartCoroutine(SendGenerateDoneRetried(target)); <>1__state = 4; return true; case 4: <>1__state = -1; <>2__current = (object)new WaitForSecondsRealtime(0.3f); <>1__state = 5; return true; case 5: <>1__state = -1; if (ShouldAbortSnapshot(target, 5__2, "阶段 5.5 前")) { return false; } try { SendModuleSyncToLateJoiner(target); } catch (Exception ex4) { LogWarning("[LateJoin] 重发 Module 状态整体失败:" + ex4.Message); } <>2__current = (object)new WaitForSecondsRealtime(0.2f); <>1__state = 6; return true; case 6: <>1__state = -1; if (ShouldAbortSnapshot(target, 5__2, "阶段 5.7 前")) { return false; } try { SendValuableObjectSyncToLateJoiner(target); } catch (Exception ex3) { LogWarning("[LateJoin] 重发 ValuableObject 状态整体失败:" + ex3.Message); } <>2__current = (object)new WaitForSecondsRealtime(0.1f); <>1__state = 7; return true; case 7: <>1__state = -1; if (ShouldAbortSnapshot(target, 5__2, "阶段 5.9 前")) { return false; } try { SendStateSyncToLateJoiner(target); } catch (Exception ex) { LogWarning("[LateJoin] SendStateSyncToLateJoiner 整体失败:" + ex.Message); } <>2__current = (object)new WaitForSecondsRealtime(0.1f); <>1__state = 8; return true; case 8: <>1__state = -1; <>2__current = ((MonoBehaviour)Instance).StartCoroutine(BroadcastLoadingAnimationCompleted(target)); <>1__state = 9; return true; case 9: <>1__state = -1; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__121 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Player target; private int 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__121(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Expected O, but got Unknown int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; goto IL_0138; } <>1__state = -1; 5__2 = 0; goto IL_0148; IL_0138: 5__2++; goto IL_0148; IL_0148: if (5__2 < 3) { if (!IsTargetStillOnline(target)) { LogInfo("[LateJoin] 玩家 " + target.NickName + " 已离线,取消 GenerateDone 重发。"); return false; } bool flag = false; try { if ((Object)(object)LevelGenerator.Instance != (Object)null && (Object)(object)LevelGenerator.Instance.PhotonView != (Object)null) { LevelGenerator.Instance.PhotonView.RPC("GenerateDone", target, Array.Empty()); flag = true; } } catch (Exception ex) { LogWarning("[LateJoin] 发 GenerateDone 失败:" + ex.Message); } if (!flag) { LogWarning("[LateJoin] LevelGenerator.Instance 不可用,跳过 GenerateDone。"); return false; } if (5__2 == 0) { LogInfo("[LateJoin] 阶段 2/3 → " + target.NickName + ": GenerateDone 首发。"); } else { Verbose($"[LateJoin] GenerateDone 第 {5__2 + 1} 次重发 → {target.NickName}"); } if (5__2 < 2) { <>2__current = (object)new WaitForSecondsRealtime(1f); <>1__state = 1; return true; } goto IL_0138; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__162 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Plugin <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__162(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; Plugin plugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = plugin.WaitForMenuReady(15f, delegate(MenuManager mm) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) TryShowMenuPopup(mm, BuildCompatTitle(), new Color(1f, 0.78f, 0.2f), BuildCompatBody(), "兼容提示"); }); <>1__state = 1; return true; case 1: <>1__state = -1; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__165 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Plugin <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__165(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; Plugin plugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = plugin.WaitForMenuReady(15f, delegate(MenuManager mm) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) TryShowMenuPopup(mm, BuildConflictTitle(), new Color(1f, 0.35f, 0.35f), BuildConflictBody(), "冲突警告"); }); <>1__state = 1; return true; case 1: <>1__state = -1; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__168 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public string playerName; private float 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__168(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0101: Unknown result type (might be due to invalid IL or missing references) switch (<>1__state) { default: return false; case 0: <>1__state = -1; if (!PhotonNetwork.IsMasterClient) { return false; } if (string.IsNullOrEmpty(playerName)) { playerName = (UseChinese() ? "未知玩家" : "Unknown"); } <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; if (TryShowInGameJoinMessage(playerName)) { return false; } 5__2 = Time.unscaledTime + 8f; goto IL_0134; case 2: { <>1__state = -1; MenuManager instance = MenuManager.instance; if ((Object)(object)instance != (Object)null && !IsPopupSlotBusy(instance)) { TryShowMenuPopup(instance, BuildJoinNotifyTitle(), new Color(0.4f, 1f, 0.55f), BuildJoinNotifyBody(playerName), "玩家加入提示"); return false; } goto IL_011d; } case 3: { <>1__state = -1; goto IL_0134; } IL_0134: if (Time.unscaledTime < 5__2) { if (TryShowInGameJoinMessage(playerName)) { return false; } MenuManager instance = MenuManager.instance; if ((Object)(object)instance != (Object)null && !IsPopupSlotBusy(instance) && CompatibilityReport.CanShowMenuPopup) { <>2__current = null; <>1__state = 2; return true; } goto IL_011d; } return false; IL_011d: <>2__current = null; <>1__state = 3; return true; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__140 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; private float 5__2; private bool 5__3; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__140(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <>1__state = -2; } private bool MoveNext() { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown bool result; try { switch (<>1__state) { default: result = false; goto end_IL_0000; case 0: <>1__state = -1; <>1__state = -3; 5__2 = Time.unscaledTime; 5__3 = false; break; case 1: <>1__state = -3; break; } if (!(Time.unscaledTime - 5__2 < 60f)) { goto IL_00ae; } if (!IsCooldownStillRelevant()) { Verbose("[Enforce] 等 Generated 期间主机身份/房间已变化,放弃等待。"); result = false; goto IL_0134; } if ((Object)(object)LevelGenerator.Instance != (Object)null && LevelGenerator.Instance.Generated) { 5__3 = true; goto IL_00ae; } <>2__current = (object)new WaitForSecondsRealtime(0.2f); <>1__state = 1; result = true; goto end_IL_0000; IL_00ae: if (!IsCooldownStillRelevant()) { Verbose("[Enforce] 进入固定冷却前主机身份/房间已变化,放弃。"); result = false; goto IL_0134; } float num = Time.unscaledTime - 5__2; if (5__3) { LogInfo($"[Enforce] 主机 Generated=true(等了 {num:F1}s),进入 {5f:F0}s 固定冷却。"); } else { LogWarning($"[Enforce] 等待主机 Generated=true 超时({60f:F0}s),跳过等待直接进入 {5f:F0}s 固定冷却。"); } _changeLevelCooldownUntil = Time.unscaledTime + 5f; <>m__Finally1(); result = false; goto end_IL_0000; IL_0134: <>m__Finally1(); end_IL_0000:; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } return result; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; _changeLevelCooldownCoroutineRunning = false; } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__160 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public float deadlineSeconds; public Action onReady; private float 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__160(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 = Time.unscaledTime + deadlineSeconds; goto IL_00ad; case 1: { <>1__state = -1; MenuManager instance = MenuManager.instance; if ((Object)(object)instance != (Object)null && !IsPopupSlotBusy(instance)) { onReady?.Invoke(instance); return false; } goto IL_0096; } case 2: { <>1__state = -1; goto IL_00ad; } IL_00ad: if (Time.unscaledTime < 5__2) { MenuManager instance = MenuManager.instance; if ((Object)(object)instance != (Object)null && !IsPopupSlotBusy(instance) && CompatibilityReport.CanShowMenuPopup) { <>2__current = null; <>1__state = 1; return true; } goto IL_0096; } return false; IL_0096: <>2__current = null; <>1__state = 2; return true; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__120 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Player target; private float 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__120(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__2 = Time.unscaledTime + 30f; break; case 1: <>1__state = -1; break; } if (Time.unscaledTime < 5__2) { if (!IsTargetStillOnline(target)) { LogInfo("[LateJoin] 玩家 " + target.NickName + " 已离线,取消等待 Generated。"); return false; } if ((Object)(object)LevelGenerator.Instance != (Object)null && LevelGenerator.Instance.Generated) { Verbose("[LateJoin] 主机已 Generated=true,开始发 GenerateDone 兜底。"); return false; } <>2__current = (object)new WaitForSecondsRealtime(0.2f); <>1__state = 1; return true; } LogWarning("[LateJoin] 等待主机 Generated=true 超时(30s),仍按当前状态发送 GenerateDone。"); 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 PluginGuid = "zichen.latejoinnow"; public const string PluginName = "LateJoinNow"; public const string PluginVersion = "1.0.1"; internal static Plugin Instance; internal static Harmony harmony; private static ConfigEntry _cfgModEnabled; private static ConfigEntry _cfgLanguage; private static ConfigEntry _cfgJoinNotify; private static ConfigEntry _cfgPublicRoomPrefix; private static ConfigEntry _cfgHudEnabled; private static ConfigEntry _cfgHudShowRoomType; private static ConfigEntry _cfgHudShowRoomName; private static ConfigEntry _cfgHudShowPlayerCount; private static ConfigEntry _cfgHudFontSize; private static ConfigEntry _cfgHudOpacity; private static ConfigEntry _cfgVerboseLog; private static ConfigEntry _cfgShowConflictWarning; private static ConfigEntry _cfgAllowLobby; private static ConfigEntry _cfgAllowShop; private static ConfigEntry _cfgAllowLevel; private static ConfigEntry _cfgPublicVisible; private float _enforceTimer; private const float EnforceInterval = 1f; private static GUIStyle _hudLabelStyle; private static GUIStyle _hudShadowStyle; private static int _cachedFontSize = -1; private static int _cachedOpacity = -1; private static readonly List _hudLines = new List(3); private const float HudRefreshInterval = 0.5f; private static float _hudNextRefreshAt; private static bool _hudCacheUseChinese; private static bool _hudCacheShowPlayerCount; private static bool _hudCacheShowRoomType; private static bool _hudCacheShowRoomName; private static int _hudCachePlayerCount; private static int _hudCacheMaxPlayers; private static LobbyTypes _hudCacheLobbyType; private static bool _hudCacheLobbyTypeKnown; private static string _hudCacheServerName; private static readonly StateSyncEntry[] _stateSyncEntries = new StateSyncEntry[22] { S("ExtractionPoint", "StateSetRPC", castToInt: false), S("ShopKeycardDoor", "StateSetRPC"), S("UpgradeStand", "StateSetRPC"), S("ItemValuableBox", "StateSetRPC"), S("ItemMine", "StateSetRPC", castToInt: true, "state"), S("ItemMelee", "StateSetRPC"), S("ItemGun", "StateSetRPC", castToInt: true, "stateCurrent"), S("ItemDrone", "StateSetRPC"), S("ItemVehicle", "SetStateRPC"), S("ItemCartCannonMain", "StateSetRPC", castToInt: true, "stateCurrent"), S("FanTrap", "SetStateRPC", castToInt: false), S("CrystalBallValuable", "SetStateRPC", castToInt: false), S("BlenderValuable", "SetStateRPC", castToInt: false), S("FlamethrowerValuable", "SetStateRPC", castToInt: false), S("IceSawValuable", "SetStateRPC", castToInt: false), S("FireExtinguisherValuable", "SetStateRPC", castToInt: false), S("ScreamDollValuable", "SetStateRPC", castToInt: false), S("JackhammerValuable", "SetStateRPC", castToInt: false), S("TrafficLightValuable", "SetStateRPC", castToInt: false), S("ValuableWizardTimeGlass", "SetStateRPC", castToInt: false), S("ValuableArcticSnowBike", "SetStateRPC"), S("ValuableEgg", "SetStateRPC", castToInt: false) }; private static readonly HashSet _pendingLateJoiners = new HashSet(); private static readonly FieldRef _playerDeathHeadRef = AccessTools.FieldRefAccess("playerDeathHead"); private static readonly FieldRef _playerTumbleRef = AccessTools.FieldRefAccess("tumble"); private static readonly FieldRef _moduleConnectingTopRef = AccessTools.FieldRefAccess("ConnectingTop"); private static readonly FieldRef _moduleConnectingBottomRef = AccessTools.FieldRefAccess("ConnectingBottom"); private static readonly FieldRef _moduleConnectingRightRef = AccessTools.FieldRefAccess("ConnectingRight"); private static readonly FieldRef _moduleConnectingLeftRef = AccessTools.FieldRefAccess("ConnectingLeft"); private static readonly FieldRef _moduleFirstRef = AccessTools.FieldRefAccess("First"); private static readonly FieldRef _moduleSetupDoneRef = AccessTools.FieldRefAccess("SetupDone"); private static readonly FieldRef _valuableDollarValueSetRef = AccessTools.FieldRefAccess("dollarValueSet"); private static readonly FieldRef _valuableDollarValueCurrentRef = AccessTools.FieldRefAccess("dollarValueCurrent"); private static readonly FieldRef _valuableDiscoveredRef = AccessTools.FieldRefAccess("discovered"); private static readonly FieldRef _valuableInStartRoomRef = AccessTools.FieldRefAccess("inStartRoom"); private const float ChangeLevelCooldownSeconds = 5f; private const float WaitGeneratedTimeoutSeconds = 60f; private const float WaitGeneratedPollInterval = 0.2f; private const float CooldownLockedSentinelSeconds = 600f; private const string RoomNamePrefixCN = "[游戏中] "; private const string RoomNamePrefixEN = "[In Game] "; private static readonly FieldInfo _networkServerNameField = AccessTools.Field(typeof(DataDirector), "networkServerName"); private static float _changeLevelCooldownUntil; private static bool _changeLevelCooldownCoroutineRunning; private static bool? _lastSceneAllowed; public static bool JoinNotifyEnabled { get { if (_cfgJoinNotify != null) { return _cfgJoinNotify.Value; } return true; } } public static bool PublicRoomPrefixEnabled { get { if (_cfgPublicRoomPrefix != null) { return _cfgPublicRoomPrefix.Value; } return true; } } public static bool HudEnabled { get { if (_cfgHudEnabled != null) { return _cfgHudEnabled.Value; } return true; } } public static bool HudShowRoomType { get { if (_cfgHudShowRoomType != null) { return _cfgHudShowRoomType.Value; } return true; } } public static bool HudShowRoomName { get { if (_cfgHudShowRoomName != null) { return _cfgHudShowRoomName.Value; } return true; } } public static bool HudShowPlayerCount { get { if (_cfgHudShowPlayerCount != null) { return _cfgHudShowPlayerCount.Value; } return true; } } public static int HudFontSize { get { if (_cfgHudFontSize != null) { return _cfgHudFontSize.Value; } return 16; } } public static int HudOpacityPercent { get { if (_cfgHudOpacity != null) { return _cfgHudOpacity.Value; } return 80; } } public static bool VerboseLogEnabled { get { if (_cfgVerboseLog != null) { return _cfgVerboseLog.Value; } return true; } } public static bool ShowConflictWarningEnabled { get { if (_cfgShowConflictWarning != null) { return _cfgShowConflictWarning.Value; } return true; } } public static bool AllowLobby { get { if (_cfgAllowLobby != null) { return _cfgAllowLobby.Value; } return true; } } public static bool AllowShop { get { if (_cfgAllowShop != null) { return _cfgAllowShop.Value; } return true; } } public static bool AllowLevel { get { if (_cfgAllowLevel != null) { return _cfgAllowLevel.Value; } return true; } } public static bool PublicVisibleSync { get { if (_cfgPublicVisible != null) { return _cfgPublicVisible.Value; } return true; } } internal static int PendingLateJoinerCount => _pendingLateJoiners.Count; internal static bool IsInChangeLevelCooldown => Time.unscaledTime < _changeLevelCooldownUntil; private static float CooldownSecondsLeft => Mathf.Max(0f, _changeLevelCooldownUntil - Time.unscaledTime); public static bool IsStaticModEnabled() { if (_cfgModEnabled != null) { return _cfgModEnabled.Value; } return false; } public static bool UseChinese() { if (_cfgLanguage != null) { return _cfgLanguage.Value == DisplayLanguage.中文; } return true; } internal static void Verbose(string msg) { if (VerboseLogEnabled) { Plugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogInfo((object)("[Verbose] " + msg)); } } } public static bool IsCurrentSceneAllowed() { if (!IsStaticModEnabled()) { return false; } if (!PhotonNetwork.IsMasterClient) { return false; } if ((Object)(object)RunManager.instance == (Object)null) { return false; } try { if (SemiFunc.RunIsLobby()) { return AllowLobby; } if (SemiFunc.RunIsShop()) { return AllowShop; } if (SemiFunc.RunIsArena()) { return false; } if (SemiFunc.RunIsLevel()) { return AllowLevel; } } catch (Exception ex) { Plugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)("判定当前场景类型失败:" + ex.Message)); } return false; } return false; } private void Awake() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown DetachFromManager(); Instance = this; ResetConfigIfVersionChanged(); BindConfig(); if (!IsStaticModEnabled()) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"LateJoinNow v1.0.1 已禁用,跳过 Harmony 安装。"); return; } CompatibilityReport.Build(); try { harmony = new Harmony("zichen.latejoinnow"); InstallPatchesSafely(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"LateJoinNow v1.0.1 loaded."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("LateJoinNow 启动失败:" + ex.Message)); } if (CompatibilityReport.HasIssues) { ((MonoBehaviour)this).StartCoroutine(WaitAndShowCompatPopup()); } if (ShowConflictWarningEnabled) { ConflictDetect.Detect(); if (ConflictDetect.HasConflict) { ((MonoBehaviour)this).StartCoroutine(WaitAndShowConflictPopup()); } } } private void Update() { if (IsHookActive() && PhotonNetwork.IsMasterClient) { _enforceTimer -= Time.unscaledDeltaTime; if (!(_enforceTimer > 0f)) { _enforceTimer = 1f; EnforceLobbyState(); } } } private void BindConfig() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Expected O, but got Unknown //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Expected O, but got Unknown //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Expected O, but got Unknown ((BaseUnityPlugin)this).Config.Bind("模组信息", "模组名称", "中途随时加入", new ConfigDescription("当前模组的中文名称。此处只是提示,不影响功能。", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { ReadOnly = true, HideDefaultButton = true } })); ((BaseUnityPlugin)this).Config.Bind("模组信息", "模组版本号", "1.0.1", new ConfigDescription("当前模组版本号。此处只是提示,不影响功能。", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { ReadOnly = true, HideDefaultButton = true } })); ((BaseUnityPlugin)this).Config.Bind("模组信息", "REPO交流QQ群", "824639225", new ConfigDescription("REPO 游戏交流、BUG 反馈、优化建议、功能请求请加 QQ 群。此处只是提示,不影响功能。", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { ReadOnly = true, HideDefaultButton = true } })); _cfgLanguage = ((BaseUnityPlugin)this).Config.Bind("模组信息", "语言/Language", DisplayLanguage.中文, "公开文档和配置说明语言切换。仅该项保留中英并列格式。"); _cfgModEnabled = ((BaseUnityPlugin)this).Config.Bind("A.全局设置", "模组启用", true, "关闭后整个模组全部功能彻底失效,零性能开销。"); _cfgPublicRoomPrefix = ((BaseUnityPlugin)this).Config.Bind("A.全局设置", "公开房名加前缀", true, "开启后,创建自定义名字的公开房时,服务器列表里的房间名会自动加上\"[游戏中]\"前缀,让其他玩家知道这个房间支持中途加入。前缀只在房间创建时一次性写入,房间存在期间无法修改。"); _cfgPublicVisible = ((BaseUnityPlugin)this).Config.Bind("A.全局设置", "游戏中房间在公开服务器", true, "若你的房间是公共房,开启后允许中途加入时房间也会出现在公共列表,可被陌生人随机加入;关闭则只接受邀请加入。"); _cfgJoinNotify = ((BaseUnityPlugin)this).Config.Bind("A.全局设置", "新玩家加入弹窗通知", true, "开启后,每次有玩家中途加入房间,主机会收到一次菜单弹窗提示。"); _cfgAllowLobby = ((BaseUnityPlugin)this).Config.Bind("B.场景白名单", "允许在卡车阶段加入", true, "卡车阶段(游戏内 Lobby,玩家在卡车里休整)允许中途加入。"); _cfgAllowShop = ((BaseUnityPlugin)this).Config.Bind("B.场景白名单", "允许在商店阶段加入", true, "商店阶段允许中途加入。"); _cfgAllowLevel = ((BaseUnityPlugin)this).Config.Bind("B.场景白名单", "允许在关卡进行中加入", true, "普通运行关卡(搜刮、跑路过程中)允许中途加入。"); _cfgHudEnabled = ((BaseUnityPlugin)this).Config.Bind("C.房间信息显示", "启用", true, "开启后,当前场景允许中途加入时,屏幕左下角显示一个小信息框。关闭则任何场景都不显示。"); _cfgHudShowPlayerCount = ((BaseUnityPlugin)this).Config.Bind("C.房间信息显示", "显示房间人数", true, "在 HUD 上显示当前人数 / 最大人数(例如 3/6)。"); _cfgHudShowRoomType = ((BaseUnityPlugin)this).Config.Bind("C.房间信息显示", "显示房间类型", true, "在 HUD 上显示当前房间类型:公开服务器 / 私人服务器 / 单人模式。"); _cfgHudShowRoomName = ((BaseUnityPlugin)this).Config.Bind("C.房间信息显示", "显示房间名称", true, "在 HUD 上显示当前房间名称:公开房显示房间名,私人房显示\"仅限好友加入\"。"); _cfgHudFontSize = ((BaseUnityPlugin)this).Config.Bind("C.房间信息显示", "字体大小", 16, new ConfigDescription("HUD 文字大小,默认 16。", (AcceptableValueBase)(object)new AcceptableValueRange(10, 28), Array.Empty())); _cfgHudOpacity = ((BaseUnityPlugin)this).Config.Bind("C.房间信息显示", "文字透明度/%", 80, new ConfigDescription("HUD 文字整体透明度,默认 80%。", (AcceptableValueBase)(object)new AcceptableValueRange(20, 100), Array.Empty())); _cfgVerboseLog = ((BaseUnityPlugin)this).Config.Bind("D.调试", "详细日志", false, "开启后会在控制台输出更多关键路径日志(关卡切换、玩家加入、缓存清理、触发等),便于排查问题。"); _cfgShowConflictWarning = ((BaseUnityPlugin)this).Config.Bind("D.调试", "同类模组冲突警告", true, "开启后,启动时若检测到本地还安装了其他「中途加入/晚加入」类同类 mod,会在主菜单弹一次红色警告。强烈建议保持开启。"); } private void ResetConfigIfVersionChanged() { try { string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; string text = ReadConfigPluginVersion(configFilePath); if (!string.IsNullOrWhiteSpace(text) && !(text == "1.0.1")) { ((BaseUnityPlugin)this).Config.Clear(); if (File.Exists(configFilePath)) { File.Delete(configFilePath); } ((BaseUnityPlugin)this).Config.Reload(); ((BaseUnityPlugin)this).Logger.LogWarning((object)"配置文件版本不一致,已重置为当前版本默认值。"); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("按版本重置配置失败:" + ex.Message)); } } private static string ReadConfigPluginVersion(string configPath) { if (!File.Exists(configPath)) { return null; } Match match = Regex.Match(File.ReadAllText(configPath), "(?m)^模组版本号\\s*=\\s*(.+?)\\s*$"); if (!match.Success) { return null; } return match.Groups[1].Value.Trim(); } private void DetachFromManager() { ((Component)this).gameObject.transform.parent = null; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)52; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); } internal static void LogInfo(string msg) { Plugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogInfo((object)msg); } } internal static void LogWarning(string msg) { Plugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)msg); } } internal static void LogError(string msg) { Plugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogError((object)msg); } } internal static bool IsHookActive() { if (IsStaticModEnabled()) { return CompatibilityReport.RuntimeReady; } return false; } private void OnGUI() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Invalid comparison between Unknown and I4 if (IsHookActive() && PhotonNetwork.IsMasterClient && HudEnabled && IsCurrentSceneAllowed() && (Event.current == null || (int)Event.current.type == 7)) { EnsureHudStyles(); RefreshHudLinesIfNeeded(); DrawHud(); } } private static void EnsureHudStyles() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown //IL_00c5: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.Clamp(HudFontSize, 10, 28); int num2 = Mathf.Clamp(HudOpacityPercent, 20, 100); if (_hudLabelStyle == null || _cachedFontSize != num || _cachedOpacity != num2) { float num3 = (float)num2 / 100f; _hudLabelStyle = new GUIStyle(); _hudLabelStyle.fontSize = num; _hudLabelStyle.richText = true; _hudLabelStyle.normal.textColor = new Color(1f, 1f, 1f, num3); _hudShadowStyle = new GUIStyle(); _hudShadowStyle.fontSize = num; _hudShadowStyle.richText = true; _hudShadowStyle.normal.textColor = new Color(0f, 0f, 0f, num3 * 0.7f); _cachedFontSize = num; _cachedOpacity = num2; } } private static void RefreshHudLinesIfNeeded() { //IL_00f9: 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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) bool flag = UseChinese(); bool hudShowPlayerCount = HudShowPlayerCount; bool hudShowRoomType = HudShowRoomType; bool hudShowRoomName = HudShowRoomName; int num = 0; int num2 = 0; try { if (PhotonNetwork.CurrentRoom != null) { num = PhotonNetwork.CurrentRoom.PlayerCount; num2 = PhotonNetwork.CurrentRoom.MaxPlayers; } } catch { } LobbyTypes? lobbyTypeOrNull = GetLobbyTypeOrNull(); string text = ReadServerName(); if (flag != _hudCacheUseChinese || hudShowPlayerCount != _hudCacheShowPlayerCount || hudShowRoomType != _hudCacheShowRoomType || hudShowRoomName != _hudCacheShowRoomName || num != _hudCachePlayerCount || num2 != _hudCacheMaxPlayers || lobbyTypeOrNull.HasValue != _hudCacheLobbyTypeKnown || (lobbyTypeOrNull.HasValue && lobbyTypeOrNull.Value != _hudCacheLobbyType) || text != _hudCacheServerName || !(Time.unscaledTime < _hudNextRefreshAt)) { _hudCacheUseChinese = flag; _hudCacheShowPlayerCount = hudShowPlayerCount; _hudCacheShowRoomType = hudShowRoomType; _hudCacheShowRoomName = hudShowRoomName; _hudCachePlayerCount = num; _hudCacheMaxPlayers = num2; _hudCacheLobbyTypeKnown = lobbyTypeOrNull.HasValue; _hudCacheLobbyType = lobbyTypeOrNull.GetValueOrDefault(); _hudCacheServerName = text; _hudNextRefreshAt = Time.unscaledTime + 0.5f; _hudLines.Clear(); if (hudShowPlayerCount) { _hudLines.Add((flag ? "房间人数:" : "Players: ") + GetPlayerCountText(num, num2, flag)); } if (hudShowRoomType) { _hudLines.Add((flag ? "房间类型:" : "Room: ") + GetRoomTypeText(lobbyTypeOrNull, flag)); } if (hudShowRoomName) { _hudLines.Add((flag ? "房间名称:" : "Name: ") + GetRoomNameText(lobbyTypeOrNull, text, flag)); } } } private static void DrawHud() { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) if (_hudLines.Count != 0) { float num = (float)((_cachedFontSize > 0) ? _cachedFontSize : 16) * 1.35f; float num2 = num * (float)_hudLines.Count; float num3 = 12f; float num4 = (float)Screen.height - num2 - 12f; for (int i = 0; i < _hudLines.Count; i++) { float num5 = num4 + (float)i * num; GUI.Label(new Rect(num3 + 1f, num5 + 1f, 480f, num), _hudLines[i], _hudShadowStyle); GUI.Label(new Rect(num3, num5, 480f, num), _hudLines[i], _hudLabelStyle); } } } private static LobbyTypes? GetLobbyTypeOrNull() { //IL_005d: 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_0063: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)GameManager.instance == (Object)null) { return null; } try { if (CompatibilityReport.GameManagerLobbyTypeRef != null) { return CompatibilityReport.GameManagerLobbyTypeRef.Invoke(GameManager.instance); } if (CompatibilityReport.GameManagerLobbyTypeField != null) { object value = CompatibilityReport.GameManagerLobbyTypeField.GetValue(GameManager.instance); if (value is LobbyTypes) { return (LobbyTypes)value; } } } catch { } return null; } private static string ReadServerName() { try { if (PhotonNetwork.CurrentRoom == null) { return null; } Hashtable customProperties = ((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties; string text = null; if (customProperties != null && ((Dictionary)(object)customProperties).ContainsKey((object)"server_name")) { text = customProperties[(object)"server_name"] as string; } if (string.IsNullOrWhiteSpace(text)) { text = PhotonNetwork.CurrentRoom.Name; } return string.IsNullOrEmpty(text) ? null : text; } catch { return null; } } private static string GetRoomTypeText(LobbyTypes? lobbyType, bool useChinese) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected I4, but got Unknown try { if (!SemiFunc.IsMultiplayer()) { return useChinese ? "单人模式" : "Singleplayer"; } } catch { } if (lobbyType.HasValue) { LobbyTypes value = lobbyType.Value; switch ((int)value) { case 1: if (!useChinese) { return "Public"; } return "公开服务器"; case 0: if (!useChinese) { return "Private"; } return "私人服务器"; case 2: if (!useChinese) { return "Matchmaking"; } return "匹配房间"; } } try { if (PhotonNetwork.CurrentRoom != null) { return (!PhotonNetwork.CurrentRoom.IsVisible) ? (useChinese ? "私人服务器" : "Private") : (useChinese ? "公开服务器" : "Public"); } } catch { } if (!useChinese) { return "Unknown"; } return "未知"; } private static string GetRoomNameText(LobbyTypes? lobbyType, string serverName, bool useChinese) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Invalid comparison between Unknown and I4 try { if (!SemiFunc.IsMultiplayer()) { return useChinese ? "无" : "N/A"; } } catch { } bool flag = false; if (lobbyType.HasValue) { flag = (int)lobbyType.Value == 0; } else { try { if (PhotonNetwork.CurrentRoom != null && !PhotonNetwork.CurrentRoom.IsVisible) { flag = true; } } catch { } } if (flag) { if (!useChinese) { return "Friends Only"; } return "仅限好友加入"; } if (string.IsNullOrEmpty(serverName)) { if (!useChinese) { return "Unknown"; } return "未知"; } return serverName; } private static string GetPlayerCountText(int playerCount, int maxPlayers, bool useChinese) { try { if (!SemiFunc.IsMultiplayer()) { return "1/1"; } } catch { } if (playerCount > 0) { int num = ((maxPlayers > 0) ? maxPlayers : playerCount); return playerCount + "/" + num; } if (!useChinese) { return "Unknown"; } return "未知"; } private static StateSyncEntry S(string type, string rpc, bool castToInt = true, string field = "currentState") { return new StateSyncEntry { TypeName = type, FieldName = field, RpcName = rpc, CastToInt = castToInt }; } internal static void SendStateSyncToLateJoiner(Player target) { if (target == null) { return; } int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; StateSyncEntry[] stateSyncEntries = _stateSyncEntries; foreach (StateSyncEntry stateSyncEntry in stateSyncEntries) { ResolveEntryReflection(stateSyncEntry); if (stateSyncEntry.ResolvedType == null || stateSyncEntry.ResolvedField == null) { continue; } num++; int num5 = 0; int num6 = 0; try { Object[] array = Object.FindObjectsOfType(stateSyncEntry.ResolvedType); if (array == null || array.Length == 0) { continue; } Object[] array2 = array; foreach (Object obj in array2) { MonoBehaviour val = (MonoBehaviour)(object)((obj is MonoBehaviour) ? obj : null); if ((Object)(object)val == (Object)null) { continue; } PhotonView component = ((Component)val).GetComponent(); if ((Object)(object)component == (Object)null || component.ViewID == 0) { num6++; num3++; continue; } try { object value = stateSyncEntry.ResolvedField.GetValue(val); if (value == null) { num6++; num3++; continue; } object obj2 = (stateSyncEntry.CastToInt ? ((object)Convert.ToInt32(value)) : value); component.RPC(stateSyncEntry.RpcName, target, new object[1] { obj2 }); num5++; num2++; } catch (Exception ex) { num4++; Verbose($"[StateSync] {stateSyncEntry.TypeName} 实例重发 {stateSyncEntry.RpcName} 失败 viewID={component.ViewID}: {ex.Message}"); } } if (num5 > 0 || num6 > 0) { Verbose($"[StateSync] {stateSyncEntry.TypeName} → {target.NickName}: 已发 {num5}(跳过 {num6})"); } } catch (Exception ex2) { num4++; LogWarning("[StateSync] 处理 " + stateSyncEntry.TypeName + " 整体失败:" + ex2.Message); } } LogInfo($"[StateSync] → {target.NickName}: 共处理 {num} 类组件,发 RPC {num2} 次,跳过 {num3},错误 {num4}。"); } private static void ResolveEntryReflection(StateSyncEntry entry) { if (entry.ResolveAttempted) { return; } entry.ResolveAttempted = true; try { Type type = typeof(PlayerAvatar).Assembly.GetType(entry.TypeName, throwOnError: false); if (type == null) { Verbose("[StateSync] 未找到类型 " + entry.TypeName + ",跳过该项配置(游戏版本可能已变化)。"); return; } FieldInfo fieldInfo = AccessTools.Field(type, entry.FieldName); if (fieldInfo == null) { Verbose("[StateSync] " + entry.TypeName + " 上没有字段 " + entry.FieldName + ",跳过该项配置。"); } else { entry.ResolvedType = type; entry.ResolvedField = fieldInfo; } } catch (Exception ex) { LogWarning("[StateSync] 解析 " + entry.TypeName + "." + entry.FieldName + " 反射失败:" + ex.Message); } } internal static void NetworkManagerOnPlayerEnteredRoomPostfix(Player newPlayer) { if (!IsHookActive()) { return; } Verbose(string.Format("[Photon] NetworkManager.OnPlayerEnteredRoom Postfix | name={0} actor={1} isMaster={2}", ((newPlayer != null) ? newPlayer.NickName : null) ?? "", (newPlayer != null) ? new int?(newPlayer.ActorNumber) : null, PhotonNetwork.IsMasterClient)); if (PhotonNetwork.IsMasterClient && newPlayer != null && PhotonNetwork.CurrentRoom != null) { bool flag = IsSnapshotNeededForCurrentScene(); string currentSceneTag = GetCurrentSceneTag(); Verbose($"[LateJoin] 当前场景标签={currentSceneTag} 需要快照={flag} 弹窗通知开关={JoinNotifyEnabled}"); if (JoinNotifyEnabled && (Object)(object)Instance != (Object)null) { ((MonoBehaviour)Instance).StartCoroutine(WaitAndShowJoinNotifyPopup(newPlayer.NickName)); } if (flag) { _pendingLateJoiners.Add(newPlayer.ActorNumber); LogInfo($"[LateJoin] 玩家 {newPlayer.NickName} (Actor={newPlayer.ActorNumber}) 中途加入到 {currentSceneTag},等待他发 PlayerSpawnedRPC 后再发送快照。已登记为待发快照(pending {_pendingLateJoiners.Count} 人)。"); } } } internal static void NetworkManagerOnPlayerLeftRoomPostfix(Player otherPlayer) { if (otherPlayer != null && _pendingLateJoiners.Remove(otherPlayer.ActorNumber)) { Verbose($"[LateJoin] 玩家 {otherPlayer.NickName} (Actor={otherPlayer.ActorNumber}) 离开房间,从待发快照集合中移除。"); } } internal static void NetworkManagerPlayerSpawnedRPCPostfix(PhotonMessageInfo _info) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (!IsHookActive() || !PhotonNetwork.IsMasterClient || PhotonNetwork.CurrentRoom == null) { return; } Player sender = _info.Sender; if (sender == null) { Verbose("[LateJoin] PlayerSpawnedRPC postfix: sender 为 null,跳过。"); return; } if (sender.ActorNumber == PhotonNetwork.LocalPlayer.ActorNumber) { Verbose("[LateJoin] PlayerSpawnedRPC postfix: sender 是主机自己(" + sender.NickName + "),跳过。"); return; } if (!_pendingLateJoiners.Contains(sender.ActorNumber)) { Verbose($"[LateJoin] PlayerSpawnedRPC postfix: {sender.NickName} (Actor={sender.ActorNumber}) 不在待发快照集合中,跳过(多半是切关卡后的重新 spawn)。"); return; } if (!IsSnapshotNeededForCurrentScene()) { Verbose("[LateJoin] PlayerSpawnedRPC postfix: 当前场景不需要补快照,跳过 sender=" + sender.NickName + "。"); return; } _pendingLateJoiners.Remove(sender.ActorNumber); LogInfo($"[LateJoin] 收到 {sender.NickName} (Actor={sender.ActorNumber}) 的 PlayerSpawnedRPC,启动延迟分阶段快照(保证客户端原版协程有时间设 Level)。"); if ((Object)(object)Instance != (Object)null) { ((MonoBehaviour)Instance).StartCoroutine(EnsurePlayerCorpseObjectsAndSnapshot(sender)); } } [IteratorStateMachine(typeof(d__113))] private static IEnumerator EnsurePlayerCorpseObjectsAndSnapshot(Player target) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__113(0) { target = target }; } private static bool IsTargetStillOnline(Player target) { if (target == null) { return false; } if (PhotonNetwork.CurrentRoom == null) { return false; } return PhotonNetwork.CurrentRoom.Players.ContainsKey(target.ActorNumber); } private static string CurrentSceneName() { RunManager instance = RunManager.instance; object obj; if (instance == null) { obj = null; } else { Level levelCurrent = instance.levelCurrent; obj = ((levelCurrent != null) ? ((Object)levelCurrent).name : null); } if (obj == null) { obj = ""; } return (string)obj; } private static bool ShouldAbortSnapshot(Player target, string startScene, string stageHint) { if (!IsStaticModEnabled()) { return true; } if (!PhotonNetwork.IsMasterClient) { return true; } if (!IsTargetStillOnline(target)) { LogInfo("[LateJoin] 玩家 " + target.NickName + " 在 " + stageHint + " 已离线,取消快照。"); return true; } string text = CurrentSceneName(); if (text != startScene) { LogInfo("[LateJoin] " + stageHint + " 场景已切换(" + startScene + " → " + text + "),取消 " + target.NickName + " 的快照。"); return true; } return false; } private static bool IsSnapshotNeededForCurrentScene() { try { if ((Object)(object)RunManager.instance == (Object)null) { return false; } return SemiFunc.RunIsLobby() || SemiFunc.RunIsShop() || SemiFunc.RunIsLevel(); } catch { return false; } } private static string GetCurrentSceneTag() { try { if ((Object)(object)RunManager.instance == (Object)null) { return ""; } if (SemiFunc.RunIsLobby()) { return "Lobby"; } if (SemiFunc.RunIsLobbyMenu()) { return "LobbyMenu"; } if (SemiFunc.IsMainMenu()) { return "MainMenu"; } if (SemiFunc.RunIsShop()) { return "Shop"; } if (SemiFunc.RunIsArena()) { return "Arena"; } if (SemiFunc.RunIsLevel()) { return "Level"; } return ""; } catch { return ""; } } private static void EnsurePlayerCorpseObjects(Player target) { //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_0236: Unknown result type (might be due to invalid IL or missing references) if (!PhotonNetwork.IsMasterClient || target == null) { return; } try { if ((Object)(object)LevelGenerator.Instance == (Object)null) { Verbose("[EnsureCorpse] LevelGenerator.Instance 为 null,跳过补创。"); return; } if ((Object)(object)LevelGenerator.Instance.PlayerDeathHeadPrefab == (Object)null || (Object)(object)LevelGenerator.Instance.PlayerTumblePrefab == (Object)null) { Verbose("[EnsureCorpse] PlayerDeathHeadPrefab 或 PlayerTumblePrefab 为 null,跳过补创。"); return; } if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null) { Verbose("[EnsureCorpse] GameDirector.instance.PlayerList 为 null,跳过补创。"); return; } if (SemiFunc.RunIsLobby()) { Verbose("[EnsureCorpse] 当前在 Lobby,跳过补创(关卡才需要)。"); return; } Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(0f, 3000f, 0f); PlayerAvatar val2 = null; foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if (!((Object)(object)player == (Object)null) && !((Object)(object)player.photonView == (Object)null) && player.photonView.Owner != null && player.photonView.Owner.ActorNumber == target.ActorNumber) { val2 = player; break; } } if ((Object)(object)val2 == (Object)null) { LogWarning("[EnsureCorpse] 在 GameDirector.PlayerList 里找不到 " + target.NickName + " 的 PlayerAvatar,跳过补创。"); return; } if (_playerDeathHeadRef == null || _playerTumbleRef == null) { LogWarning("[EnsureCorpse] FieldRef PlayerAvatar.playerDeathHead/tumble 缓存失败,跳过补创。"); return; } if ((Object)(object)_playerDeathHeadRef.Invoke(val2) == (Object)null) { GameObject val3 = PhotonNetwork.Instantiate(((Object)LevelGenerator.Instance.PlayerDeathHeadPrefab).name, val, Quaternion.identity, (byte)0, (object[])null); PlayerDeathHead component = val3.GetComponent(); component.playerAvatar = val2; _playerDeathHeadRef.Invoke(val2) = component; string nickName = target.NickName; PhotonView component2 = val3.GetComponent(); LogInfo($"[EnsureCorpse] 已为 {nickName} 补创 PlayerDeathHead | viewID={((component2 != null) ? new int?(component2.ViewID) : null)}"); } else { Verbose("[EnsureCorpse] " + target.NickName + " 已经有 PlayerDeathHead,跳过。"); } if ((Object)(object)_playerTumbleRef.Invoke(val2) == (Object)null) { GameObject val4 = PhotonNetwork.Instantiate(((Object)LevelGenerator.Instance.PlayerTumblePrefab).name, val, Quaternion.identity, (byte)0, (object[])null); PlayerTumble component3 = val4.GetComponent(); component3.playerAvatar = val2; _playerTumbleRef.Invoke(val2) = component3; string nickName2 = target.NickName; PhotonView component4 = val4.GetComponent(); LogInfo($"[EnsureCorpse] 已为 {nickName2} 补创 PlayerTumble | viewID={((component4 != null) ? new int?(component4.ViewID) : null)}"); } else { Verbose("[EnsureCorpse] " + target.NickName + " 已经有 PlayerTumble,跳过。"); } } catch (Exception ex) { LogWarning("[EnsureCorpse] 补创 PlayerDeathHead/PlayerTumble 失败:" + ex.Message + "\n" + ex.StackTrace); } } [IteratorStateMachine(typeof(d__120))] private static IEnumerator WaitHostGenerated(Player target) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__120(0) { target = target }; } [IteratorStateMachine(typeof(d__121))] private static IEnumerator SendGenerateDoneRetried(Player target) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__121(0) { target = target }; } private static void SendModuleSyncToLateJoiner(Player target) { if (_moduleConnectingTopRef == null || _moduleConnectingBottomRef == null || _moduleConnectingRightRef == null || _moduleConnectingLeftRef == null || _moduleFirstRef == null || _moduleSetupDoneRef == null) { LogWarning("[LateJoin] Module 内部字段反射缓存失败,跳过 ModuleConnectionSetRPC 重发。"); return; } int num = 0; int num2 = 0; Module[] array = Object.FindObjectsOfType(); foreach (Module val in array) { if ((Object)(object)val == (Object)null) { continue; } PhotonView component = ((Component)val).GetComponent(); if ((Object)(object)component == (Object)null) { num2++; continue; } if (!_moduleSetupDoneRef.Invoke(val)) { num2++; continue; } try { component.RPC("ModuleConnectionSetRPC", target, new object[5] { _moduleConnectingTopRef.Invoke(val), _moduleConnectingBottomRef.Invoke(val), _moduleConnectingRightRef.Invoke(val), _moduleConnectingLeftRef.Invoke(val), _moduleFirstRef.Invoke(val) }); num++; } catch (Exception ex) { Verbose($"[LateJoin] 重发 ModuleConnectionSetRPC 单个失败 viewID={component.ViewID}: {ex.Message}"); } } LogInfo($"[LateJoin] 阶段 2.5/3 → {target.NickName}: 已重发 {num} 个 Module 的 ModuleConnectionSetRPC(跳过 {num2} 个未 SetupDone)。"); } private static void SendValuableObjectSyncToLateJoiner(Player target) { if (_valuableDollarValueSetRef == null || _valuableDollarValueCurrentRef == null || _valuableDiscoveredRef == null || _valuableInStartRoomRef == null) { LogWarning("[LateJoin] ValuableObject 内部字段反射缓存失败,跳过 ValuableObject 状态重发。"); return; } if ((Object)(object)ValuableDirector.instance == (Object)null || ValuableDirector.instance.valuableList == null) { Verbose("[LateJoin] ValuableDirector 未就绪,跳过 ValuableObject 状态重发。"); return; } int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; foreach (ValuableObject valuable in ValuableDirector.instance.valuableList) { if ((Object)(object)valuable == (Object)null) { continue; } num4++; PhotonView component = ((Component)valuable).GetComponent(); if ((Object)(object)component == (Object)null || component.ViewID == 0) { num5++; continue; } try { if (_valuableDollarValueSetRef.Invoke(valuable)) { component.RPC("DollarValueSetRPC", target, new object[1] { _valuableDollarValueCurrentRef.Invoke(valuable) }); num++; } if (_valuableDiscoveredRef.Invoke(valuable)) { component.RPC("DiscoverRPC", target, Array.Empty()); num2++; } if (_valuableInStartRoomRef.Invoke(valuable)) { component.RPC("AddToDollarHaulListRPC", target, Array.Empty()); num3++; } } catch (Exception ex) { Verbose($"[LateJoin] 重发 ValuableObject RPC 单个失败 viewID={component.ViewID}: {ex.Message}"); } } LogInfo($"[LateJoin] 阶段 2.7/3 → {target.NickName}: ValuableObject 状态重发 总计={num4} value={num} discover={num2} haul={num3} 无ViewID={num5}。"); } [IteratorStateMachine(typeof(d__124))] private static IEnumerator BroadcastLoadingAnimationCompleted(Player target) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__124(0) { target = target }; } [IteratorStateMachine(typeof(d__125))] private static IEnumerator SendCatchupSnapshotStaged(Player target) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__125(0) { target = target }; } internal static void HideRoomForChangeLevelCooldown() { if (PhotonNetwork.IsMasterClient && PhotonNetwork.CurrentRoom != null && !((Object)(object)Instance == (Object)null)) { bool isOpen = PhotonNetwork.CurrentRoom.IsOpen; bool isVisible = PhotonNetwork.CurrentRoom.IsVisible; LockRoomNow(); _changeLevelCooldownUntil = Time.unscaledTime + 600f; LogInfo($"[Enforce] 切关锁住中途加入 | IsOpen {isOpen}→false IsVisible {isVisible}→false | 等待主机 Generated=true 后再延迟 {5f:F0}s 解锁"); if (!_changeLevelCooldownCoroutineRunning) { _changeLevelCooldownCoroutineRunning = true; ((MonoBehaviour)Instance).StartCoroutine(WaitForHostGeneratedThenCooldownCoroutine()); } } } [IteratorStateMachine(typeof(d__140))] private static IEnumerator WaitForHostGeneratedThenCooldownCoroutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__140(0); } private static bool IsCooldownStillRelevant() { if (PhotonNetwork.IsMasterClient) { return PhotonNetwork.CurrentRoom != null; } return false; } private static bool LockRoomNow() { if (PhotonNetwork.CurrentRoom == null) { return false; } bool flag = false; if (PhotonNetwork.CurrentRoom.IsOpen) { PhotonNetwork.CurrentRoom.IsOpen = false; flag = true; } if (PhotonNetwork.CurrentRoom.IsVisible) { PhotonNetwork.CurrentRoom.IsVisible = false; flag = true; } if (flag) { try { SteamManager instance = SteamManager.instance; if (instance != null) { instance.LockLobby(); } } catch (Exception ex) { Verbose("[Enforce] LockLobby 失败:" + ex.Message); } } return flag; } internal static void EnforceLobbyState() { if (!PhotonNetwork.IsMasterClient || PhotonNetwork.CurrentRoom == null || (Object)(object)SteamManager.instance == (Object)null) { return; } try { if (IsInChangeLevelCooldown) { EnforceCooldownState(); } else if (IsCurrentSceneAllowed()) { EnforceAllowedSceneState(); _lastSceneAllowed = true; } else if (_lastSceneAllowed != false) { Verbose($"[Enforce] 当前场景不允许加入,保持原版行为 | IsOpen={PhotonNetwork.CurrentRoom.IsOpen} IsVisible={PhotonNetwork.CurrentRoom.IsVisible}"); _lastSceneAllowed = false; } } catch (Exception ex) { LogWarning("维护房间开放状态失败:" + ex.Message + "\n" + ex.StackTrace); } } private static void EnforceCooldownState() { if (LockRoomNow()) { Verbose($"[Enforce] 冷却期强制锁房 | 剩 {CooldownSecondsLeft:F1}s"); } } private static void EnforceAllowedSceneState() { bool isOpen = PhotonNetwork.CurrentRoom.IsOpen; bool isVisible = PhotonNetwork.CurrentRoom.IsVisible; bool flag = false; if (!PhotonNetwork.CurrentRoom.IsOpen) { PhotonNetwork.CurrentRoom.IsOpen = true; flag = true; SteamManager.instance.UnlockLobby(true); } if (PublicVisibleSync && IsPublicGame()) { if (!PhotonNetwork.CurrentRoom.IsVisible) { PhotonNetwork.CurrentRoom.IsVisible = true; flag = true; } if ((Object)(object)GameManager.instance != (Object)null) { GameManager.instance.SetConnectRandom(true); } } if (flag) { Verbose($"[Enforce] 房间状态已更新 | IsOpen {isOpen}→{PhotonNetwork.CurrentRoom.IsOpen} IsVisible {isVisible}→{PhotonNetwork.CurrentRoom.IsVisible} sceneAllowed=true"); } } internal static void NetworkConnectOnConnectedToMasterPrefix() { if (ShouldAddRoomNamePrefix()) { string text = _networkServerNameField.GetValue(DataDirector.instance) as string; if (!string.IsNullOrEmpty(text) && !HasRoomNamePrefix(text)) { string text2 = (UseChinese() ? "[游戏中] " : "[In Game] ") + text; _networkServerNameField.SetValue(DataDirector.instance, text2); LogInfo("[Enforce] 公开房创建时加前缀 | '" + text + "' → '" + text2 + "'"); } } } private static bool ShouldAddRoomNamePrefix() { if (!IsStaticModEnabled()) { return false; } if (!CompatibilityReport.RuntimeReady) { return false; } if (!PublicRoomPrefixEnabled) { return false; } if ((Object)(object)DataDirector.instance == (Object)null || (Object)(object)GameManager.instance == (Object)null) { return false; } if (_networkServerNameField == null) { return false; } if (!IsPublicGame()) { return false; } return true; } private static bool HasRoomNamePrefix(string name) { if (name != null) { if (!name.StartsWith("[游戏中] ", StringComparison.Ordinal)) { return name.StartsWith("[In Game] ", StringComparison.Ordinal); } return true; } return false; } private static bool IsPublicGame() { if ((Object)(object)GameManager.instance == (Object)null) { return false; } try { if (CompatibilityReport.GameManagerConnectRandomRef != null) { return CompatibilityReport.GameManagerConnectRandomRef.Invoke(GameManager.instance); } if (CompatibilityReport.GameManagerConnectRandomField != null) { object value = CompatibilityReport.GameManagerConnectRandomField.GetValue(GameManager.instance); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } } catch { } return false; } internal static bool ClearInstantiationCacheById(int instantiationId) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (instantiationId == 0) { return false; } if (!CompatibilityReport.CanCleanPhotonCache) { return false; } try { Hashtable val = (Hashtable)CompatibilityReport.PhotonRemoveFilter.GetValue(null); object value = CompatibilityReport.PhotonKeyByteSeven.GetValue(null); RaiseEventOptions val2 = (RaiseEventOptions)CompatibilityReport.PhotonServerCleanOptions.GetValue(null); val[value] = instantiationId; val2.CachingOption = (EventCaching)6; CompatibilityReport.PhotonRaiseEventInternal.Invoke(null, new object[4] { (byte)202, val, val2, SendOptions.SendReliable }); return true; } catch (Exception ex) { Plugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)$"清理 Photon 实例化缓存失败 (id={instantiationId}): {ex.Message}"); } return false; } } internal static void ClearPhotonInstantiationCache(PhotonView photonView) { if (!((Object)(object)photonView == (Object)null)) { ClearInstantiationCacheById(photonView.InstantiationId); } } internal static void ClearAllScenePhotonCache() { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) try { HashSet hashSet = BuildPlayerPersistentViewIdSet(); PhotonView[] array = Object.FindObjectsOfType(); int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; PhotonView[] array2 = array; foreach (PhotonView val in array2) { if ((Object)(object)val == (Object)null) { num2++; continue; } Scene scene = ((Component)val).gameObject.scene; if (((Scene)(ref scene)).buildIndex == -1) { num2++; } else if (hashSet.Contains(val.ViewID) || IsPlayerPersistentByName(val)) { num3++; Verbose(string.Format("[ClearCache] 跳过玩家持久组件 | viewID={0} name={1} owner={2}", val.ViewID, ((Object)((Component)val).gameObject).name, (val.Owner != null) ? val.Owner.NickName : "")); } else if (val.InstantiationId == 0) { num4++; } else { ClearPhotonInstantiationCache(val); num++; } } Verbose($"[ClearCache] 已清理 {num} 个 PhotonView 缓存 | 跳过:玩家持久组件 {num3} / SceneView {num4} / 无效对象 {num2}"); } catch (Exception ex) { LogWarning("批量清理 PhotonView 缓存失败:" + ex.Message + "\n" + ex.StackTrace); } } private static HashSet BuildPlayerPersistentViewIdSet() { HashSet hashSet = new HashSet(); if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null) { return hashSet; } try { foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if ((Object)(object)player == (Object)null) { continue; } if ((Object)(object)player.photonView != (Object)null) { hashSet.Add(player.photonView.ViewID); } if (_playerTumbleRef == null) { continue; } try { PlayerTumble val = _playerTumbleRef.Invoke(player); if ((Object)(object)val != (Object)null) { PhotonView component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { hashSet.Add(component.ViewID); } } } catch { } } } catch (Exception ex) { Verbose("[ClearCache] 构建玩家持久 ViewID 集合时异常:" + ex.Message); } return hashSet; } private static bool IsPlayerPersistentByName(PhotonView view) { if ((Object)(object)view == (Object)null || (Object)(object)((Component)view).gameObject == (Object)null) { return false; } string name = ((Object)((Component)view).gameObject).name; if (string.IsNullOrEmpty(name)) { return false; } if (name.StartsWith("Voice", StringComparison.OrdinalIgnoreCase)) { return true; } if (name.StartsWith("Player Avatar", StringComparison.OrdinalIgnoreCase)) { return true; } if (name.StartsWith("Player Tumble", StringComparison.OrdinalIgnoreCase)) { return true; } return false; } internal static void ClearRunManagerPunBufferedRpcs() { if (!CompatibilityReport.CanReadRunManagerPun) { Verbose("[ClearCache] CanReadRunManagerPun=false,跳过 RunManagerPUN 清理。"); return; } if ((Object)(object)RunManager.instance == (Object)null) { Verbose("[ClearCache] RunManager.instance 为 null,跳过 RunManagerPUN 清理。"); return; } try { RunManagerPUN val = CompatibilityReport.RunManagerPunRef.Invoke(RunManager.instance); if ((Object)(object)val == (Object)null) { Verbose("[ClearCache] RunManagerPUN 引用为 null,跳过。"); return; } PhotonView val2 = CompatibilityReport.RunManagerPunPhotonViewRef.Invoke(val); if ((Object)(object)val2 == (Object)null) { Verbose("[ClearCache] RunManagerPUN.photonView 为 null,跳过。"); return; } PhotonNetwork.RemoveBufferedRPCs(val2.ViewID, (string)null, (int[])null); Verbose($"[ClearCache] RunManagerPUN buffered RPC 已清理 | viewID={val2.ViewID}"); } catch (Exception ex) { LogWarning("清理 RunManagerPUN buffered RPC 失败:" + ex.Message + "\n" + ex.StackTrace); } } private void InstallPatchesSafely() { PatchPrefix("RunManager.ChangeLevel Prefix", typeof(RunManager), "ChangeLevel", "RunManagerChangeLevelPrefix"); PatchPostfix("RunManager.ChangeLevel Postfix", typeof(RunManager), "ChangeLevel", "RunManagerChangeLevelPostfix"); PatchPrefix("LevelGenerator.Start Prefix", typeof(LevelGenerator), "Start", "LevelGeneratorStartPrefix"); PatchPostfix("PlayerAvatar.Start Postfix", typeof(PlayerAvatar), "Start", "PlayerAvatarStartPostfix"); PatchPrefix("PlayerAvatar.Spawn Prefix", typeof(PlayerAvatar), "Spawn", "PlayerAvatarSpawnPrefix"); PatchPrefix("LoadingUI.LevelAnimationStart Prefix", typeof(LoadingUI), "LevelAnimationStart", "LoadingUILevelAnimationStartPrefix"); PatchPrefix("PlayerAvatar.LoadingLevelAnimationCompletedRPC Prefix", typeof(PlayerAvatar), "LoadingLevelAnimationCompletedRPC", "PlayerAvatarLoadingLevelAnimationCompletedRPCPrefix"); PatchPrefix("SemiFunc.OwnerOnlyRPC Prefix", typeof(SemiFunc), "OwnerOnlyRPC", "SemiFuncOwnerOnlyRPCPrefix"); PatchPostfix("NetworkManager.OnPlayerEnteredRoom Postfix", typeof(NetworkManager), "OnPlayerEnteredRoom", "NetworkManagerOnPlayerEnteredRoomPostfix"); PatchPostfix("NetworkManager.OnPlayerLeftRoom Postfix", typeof(NetworkManager), "OnPlayerLeftRoom", "NetworkManagerOnPlayerLeftRoomPostfix"); PatchPrefix("NetworkConnect.OnConnectedToMaster Prefix", typeof(NetworkConnect), "OnConnectedToMaster", "NetworkConnectOnConnectedToMasterPrefix"); PatchPostfix("NetworkManager.PlayerSpawnedRPC Postfix", typeof(NetworkManager), "PlayerSpawnedRPC", "NetworkManagerPlayerSpawnedRPCPostfix"); PatchPrefix("GumballValuable.UpdateHypnosisLinesList Prefix", typeof(GumballValuable), "UpdateHypnosisLinesList", "GumballValuableUpdateHypnosisLinesListPrefix"); PatchPostfix("ItemAttributes.Awake Postfix", typeof(ItemAttributes), "Awake", "ItemAttributesAwakePostfix"); void PatchPostfix(string label, Type targetType, string methodName, string hookMethod) { TryPatch(label, delegate { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown harmony.Patch((MethodBase)AccessTools.Method(targetType, methodName, (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(LateJoinNowHooks), hookMethod, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); }); } void PatchPrefix(string label, Type targetType, string methodName, string hookMethod) { TryPatch(label, delegate { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown harmony.Patch((MethodBase)AccessTools.Method(targetType, methodName, (Type[])null, (Type[])null), new HarmonyMethod(typeof(LateJoinNowHooks), hookMethod, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); }); } } private void TryPatch(string label, Action action) { try { action(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("补丁安装:" + label + " ✅")); } catch (Exception ex) { CompatibilityReport.AffectedFeatures.Add(UseChinese() ? ("补丁失败:" + label) : ("Patch failed: " + label)); CompatibilityReport.Details.Add(label + " → " + ex.Message); ((BaseUnityPlugin)this).Logger.LogWarning((object)("补丁安装失败:" + label + " → " + ex.Message)); } } private static void TryShowMenuPopup(MenuManager menuManager, string title, Color color, string body, string warnLabel) { //IL_0002: 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) try { menuManager.PagePopUp(title, color, body, "OK", true); return; } catch { } try { menuManager.PagePopUpScheduled(title, color, body, "OK", true); } catch { Plugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)("无法显示" + warnLabel + "弹窗。")); } } } [IteratorStateMachine(typeof(d__160))] private IEnumerator WaitForMenuReady(float deadlineSeconds, Action onReady) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__160(0) { deadlineSeconds = deadlineSeconds, onReady = onReady }; } private static bool IsPopupSlotBusy(MenuManager menuManager) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Invalid comparison between Unknown and I4 //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Invalid comparison between Unknown and I4 if ((Object)(object)menuManager == (Object)null) { return true; } if (!CompatibilityReport.CanShowMenuPopup) { return true; } try { if ((Object)(object)CompatibilityReport.MenuCurrentMenuPageRef.Invoke(menuManager) == (Object)null) { return true; } if (CompatibilityReport.MenuPagePopUpScheduledRef.Invoke(menuManager)) { return true; } MenuPageIndex val = CompatibilityReport.MenuCurrentMenuPageIndexRef.Invoke(menuManager); return (int)val == 9 || (int)val == 7; } catch { return true; } } [IteratorStateMachine(typeof(d__162))] private IEnumerator WaitAndShowCompatPopup() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__162(0) { <>4__this = this }; } private static string BuildCompatTitle() { if (!UseChinese()) { return "LateJoinNow Compatibility"; } return "LateJoinNow 兼容模式"; } private static string BuildCompatBody() { StringBuilder stringBuilder = new StringBuilder(); if (UseChinese()) { stringBuilder.AppendLine("检测到游戏接口可能已变化,以下功能进入兼容模式:"); } else { stringBuilder.AppendLine("Some game APIs may have changed. The following features may be unavailable:"); } stringBuilder.AppendLine(); stringBuilder.AppendLine(""); foreach (string affectedFeature in CompatibilityReport.AffectedFeatures) { stringBuilder.Append(" • ").AppendLine(affectedFeature); } stringBuilder.AppendLine(""); stringBuilder.AppendLine(); stringBuilder.Append(""); stringBuilder.Append(UseChinese() ? "如需使用这些功能,请等待作者适配最新版本。" : "Please wait for the mod to be updated for the latest game version."); stringBuilder.Append(""); return stringBuilder.ToString(); } [IteratorStateMachine(typeof(d__165))] private IEnumerator WaitAndShowConflictPopup() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__165(0) { <>4__this = this }; } private static string BuildConflictTitle() { if (!UseChinese()) { return "LateJoinNow Conflict Warning"; } return "LateJoinNow 冲突警告"; } private static string BuildConflictBody() { StringBuilder stringBuilder = new StringBuilder(); if (UseChinese()) { stringBuilder.AppendLine("检测到本地启用了其他「中途加入」类模组:"); } else { stringBuilder.AppendLine("Detected other late-join style mods alongside LateJoinNow:"); } stringBuilder.AppendLine(); stringBuilder.AppendLine(""); foreach (string detectedConflictMod in ConflictDetect.DetectedConflictMods) { stringBuilder.Append(" • ").AppendLine(detectedConflictMod); } stringBuilder.AppendLine(""); stringBuilder.AppendLine(); stringBuilder.Append(""); if (UseChinese()) { stringBuilder.Append("建议在模组管理器中禁用上述模组,仅保留 LateJoinNow。"); } else { stringBuilder.Append("Please disable those mods and keep only LateJoinNow."); } stringBuilder.AppendLine(""); stringBuilder.AppendLine(); stringBuilder.Append(""); stringBuilder.Append(UseChinese() ? "同时启用会出现状态污染、卡加载等问题。" : "Running them together causes state corruption and loading hangs."); stringBuilder.Append(""); return stringBuilder.ToString(); } [IteratorStateMachine(typeof(d__168))] internal static IEnumerator WaitAndShowJoinNotifyPopup(string playerName) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__168(0) { playerName = playerName }; } private static bool TryShowInGameJoinMessage(string playerName) { //IL_0086: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)RunManager.instance == (Object)null) { return false; } if (!SemiFunc.RunIsLobby() && !SemiFunc.RunIsShop() && !SemiFunc.RunIsLevel()) { return false; } if ((Object)(object)ChatManager.instance == (Object)null) { return false; } string text = (UseChinese() ? ("[LateJoinNow] " + playerName + " 已加入房间") : ("[LateJoinNow] " + playerName + " joined the room")); ChatManager.instance.PossessChat((PossessChatID)0, text, 0.05f, new Color(0.4f, 1f, 0.55f), 0f, false, 0, (UnityEvent)null); return true; } catch { return false; } } private static string BuildJoinNotifyTitle() { if (!UseChinese()) { return "Player Joined"; } return "新玩家加入"; } private static string BuildJoinNotifyBody(string playerName) { if (!UseChinese()) { return "" + playerName + " has joined your room."; } return "玩家 " + playerName + " 已加入你的房间。"; } } internal sealed class ConfigurationManagerAttributes { public int? Order; public bool? ReadOnly; public bool? Browsable; public bool? HideDefaultButton; } internal static class CompatibilityReport { public static FieldInfo PhotonRemoveFilter; public static FieldInfo PhotonKeyByteSeven; public static FieldInfo PhotonServerCleanOptions; public static MethodInfo PhotonRaiseEventInternal; public static FieldInfo GameManagerConnectRandomField; public static FieldInfo GameManagerLobbyTypeField; public static FieldRef GameManagerLobbyTypeRef; public static FieldRef GameManagerConnectRandomRef; public static FieldRef RunManagerPunRef; public static FieldRef RunManagerPunPhotonViewRef; public static FieldRef PlayerAvatarSpawnedRef; public static FieldRef MenuPagePopUpScheduledRef; public static FieldRef MenuCurrentMenuPageIndexRef; public static FieldRef MenuCurrentMenuPageRef; public static bool CanCleanPhotonCache; public static bool CanRefreshLevelGenerator; public static bool CanCatchupLateJoiner; public static bool CanShowMenuPopup; public static bool CanReadRunManagerPun; public static bool CanReadGameManagerLobbyState; public static bool CanAddRoomNamePrefix; public static bool CanFixGumballOverflow; public static bool CanGuardLoadingAnimation; public static bool CanEnsurePlayerCorpse; public static bool CanSyncModuleState; public static bool CanSyncValuableObjectState; public static bool HasIssues { get; private set; } public static bool RuntimeReady { get; private set; } public static List AffectedFeatures { get; } = new List(); public static List Details { get; } = new List(); public static void Build() { HasIssues = false; RuntimeReady = false; AffectedFeatures.Clear(); Details.Clear(); CanCleanPhotonCache = TryAll(() => RequireField(typeof(PhotonNetwork), "removeFilter", BindingFlags.Static | BindingFlags.NonPublic, ref PhotonRemoveFilter), () => RequireField(typeof(PhotonNetwork), "keyByteSeven", BindingFlags.Static | BindingFlags.NonPublic, ref PhotonKeyByteSeven), () => RequireField(typeof(PhotonNetwork), "ServerCleanOptions", BindingFlags.Static | BindingFlags.NonPublic, ref PhotonServerCleanOptions), () => RequireMethod(typeof(PhotonNetwork), "RaiseEventInternal", new Type[4] { typeof(byte), typeof(object), typeof(RaiseEventOptions), typeof(SendOptions) }, BindingFlags.Static | BindingFlags.NonPublic, ref PhotonRaiseEventInternal)); if (!CanCleanPhotonCache) { AffectedFeatures.Add(Plugin.UseChinese() ? "清理 Photon 实例化缓存(晚加入者会看到陈旧物体)" : "Photon instantiation cache cleanup (late joiners may see stale objects)"); } RequireField(typeof(GameManager), "connectRandom", BindingFlags.Instance | BindingFlags.NonPublic, ref GameManagerConnectRandomField); RequireField(typeof(GameManager), "lobbyType", BindingFlags.Instance | BindingFlags.NonPublic, ref GameManagerLobbyTypeField); try { if (GameManagerLobbyTypeField != null) { GameManagerLobbyTypeRef = AccessTools.FieldRefAccess("lobbyType"); } if (GameManagerConnectRandomField != null) { GameManagerConnectRandomRef = AccessTools.FieldRefAccess("connectRandom"); } } catch (Exception ex) { Details.Add("GameManager FieldRef 缓存失败:" + ex.Message); } CanReadRunManagerPun = TryAll(() => RequireFieldRef("runManagerPUN", out RunManagerPunRef), () => RequireFieldRef("photonView", out RunManagerPunPhotonViewRef)); if (!CanReadRunManagerPun) { AffectedFeatures.Add(Plugin.UseChinese() ? "清理 RunManager 缓存" : "RunManager buffered RPC cleanup"); } CanRefreshLevelGenerator = typeof(LevelGenerator) != null; if (!CanRefreshLevelGenerator) { AffectedFeatures.Add(Plugin.UseChinese() ? "Lobby 阶段刷新关卡生成器缓存" : "LevelGenerator buffered RPC refresh in Lobby"); } CanCatchupLateJoiner = TryAll(() => RequireFieldRef("spawned", out PlayerAvatarSpawnedRef), () => RequireMethodExists(typeof(PlayerAvatar), "LoadingLevelAnimationCompletedRPC")); if (!CanCatchupLateJoiner) { AffectedFeatures.Add(Plugin.UseChinese() ? "晚加入玩家自动跳过加载动画" : "Late joiner load-animation catch-up"); } CanShowMenuPopup = TryAll(() => RequireFieldRef("pagePopUpScheduled", out MenuPagePopUpScheduledRef), () => RequireFieldRef("currentMenuPageIndex", out MenuCurrentMenuPageIndexRef), () => RequireFieldRef("currentMenuPage", out MenuCurrentMenuPageRef)); if (!CanShowMenuPopup) { AffectedFeatures.Add(Plugin.UseChinese() ? "菜单弹窗提示" : "Menu popup notifications"); } CanReadGameManagerLobbyState = GameManagerConnectRandomField != null && GameManagerLobbyTypeField != null; if (!CanReadGameManagerLobbyState) { AffectedFeatures.Add(Plugin.UseChinese() ? "房间类型判定(HUD / 公开房名前缀可能失效)" : "Room type detection (HUD / public room prefix may not work)"); } CanAddRoomNamePrefix = AccessTools.Field(typeof(DataDirector), "networkServerName") != null; if (!CanAddRoomNamePrefix) { Details.Add("缺失字段:DataDirector.networkServerName"); AffectedFeatures.Add(Plugin.UseChinese() ? "公开房名加[游戏中]前缀" : "Public room name [In Game] prefix"); } CanFixGumballOverflow = AccessTools.Field(typeof(GumballValuable), "hypnosisLines") != null && AccessTools.Field(typeof(GumballValuable), "hypnosisLine") != null; if (!CanFixGumballOverflow) { Details.Add("缺失字段:GumballValuable.hypnosisLines / hypnosisLine"); AffectedFeatures.Add(Plugin.UseChinese() ? "修复 GumballValuable 玩家增加时越界崩溃" : "GumballValuable hypnosis lines overflow fix"); } CanGuardLoadingAnimation = AccessTools.Field(typeof(LoadingUI), "levelAnimationStarted") != null && AccessTools.Field(typeof(LoadingUI), "levelAnimationCompleted") != null && AccessTools.Field(typeof(PlayerAvatar), "levelAnimationCompleted") != null; if (!CanGuardLoadingAnimation) { Details.Add("缺失字段:LoadingUI/PlayerAvatar.levelAnimationStarted/Completed"); AffectedFeatures.Add(Plugin.UseChinese() ? "晚加入加载界面守门(避免卡黑屏)" : "Late join loading screen guard"); } CanEnsurePlayerCorpse = AccessTools.Field(typeof(PlayerAvatar), "playerDeathHead") != null && AccessTools.Field(typeof(PlayerAvatar), "tumble") != null; if (!CanEnsurePlayerCorpse) { Details.Add("缺失字段:PlayerAvatar.playerDeathHead / tumble"); AffectedFeatures.Add(Plugin.UseChinese() ? "晚加入者尸体/翻滚组件补创(避免出口区 NullRef)" : "Late joiner death head / tumble fallback"); } CanSyncModuleState = AccessTools.Field(typeof(Module), "ConnectingTop") != null && AccessTools.Field(typeof(Module), "ConnectingBottom") != null && AccessTools.Field(typeof(Module), "ConnectingRight") != null && AccessTools.Field(typeof(Module), "ConnectingLeft") != null && AccessTools.Field(typeof(Module), "First") != null && AccessTools.Field(typeof(Module), "SetupDone") != null; if (!CanSyncModuleState) { Details.Add("缺失字段:Module.ConnectingTop/Bottom/Right/Left/First/SetupDone"); AffectedFeatures.Add(Plugin.UseChinese() ? "晚加入者墙体/房间内饰同步" : "Late joiner walls / room interior sync"); } CanSyncValuableObjectState = AccessTools.Field(typeof(ValuableObject), "dollarValueSet") != null && AccessTools.Field(typeof(ValuableObject), "dollarValueCurrent") != null && AccessTools.Field(typeof(ValuableObject), "discovered") != null && AccessTools.Field(typeof(ValuableObject), "inStartRoom") != null; if (!CanSyncValuableObjectState) { Details.Add("缺失字段:ValuableObject.dollarValueSet/dollarValueCurrent/discovered/inStartRoom"); AffectedFeatures.Add(Plugin.UseChinese() ? "晚加入者宝物数值/地图标记/出口区清单同步" : "Late joiner valuable value / map marker / haul list sync"); } HasIssues = AffectedFeatures.Count > 0; RuntimeReady = true; } private static bool TryAll(params Func[] checks) { bool result = true; foreach (Func func in checks) { try { if (!func()) { result = false; } } catch (Exception ex) { Details.Add(ex.Message); result = false; } } return result; } private static bool RequireField(Type type, string name, BindingFlags flags, ref FieldInfo into) { into = type.GetField(name, flags); if (into != null) { return true; } Details.Add("缺失字段:" + type.Name + "." + name); return false; } private static bool RequireMethod(Type type, string name, Type[] paramTypes, BindingFlags flags, ref MethodInfo into) { into = type.GetMethod(name, flags, null, paramTypes, null); if (into != null) { return true; } Details.Add("缺失方法:" + type.Name + "." + name + "(" + string.Join(", ", Array.ConvertAll(paramTypes, (Type p) => p.Name)) + ")"); return false; } private static bool RequireMethodExists(Type type, string name) { if (type.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) != null) { return true; } Details.Add("缺失方法:" + type.Name + "." + name); return false; } private static bool RequireFieldRef(string fieldName, out FieldRef fieldRef) { fieldRef = null; if (AccessTools.Field(typeof(TType), fieldName) == null) { Details.Add("缺失字段:" + typeof(TType).Name + "." + fieldName); return false; } try { fieldRef = AccessTools.FieldRefAccess(fieldName); return true; } catch (Exception ex) { Details.Add("无法绑定字段访问器 " + typeof(TType).Name + "." + fieldName + ":" + ex.Message); return false; } } } internal static class ConflictDetect { internal static readonly List DetectedConflictMods = new List(); private static readonly string[] ConflictKeywords = new string[2] { "late", "join" }; private static readonly string[] SelfKeywords = new string[2] { "latejoinnow", "zichenlatejoinnow" }; internal static bool HasConflict => DetectedConflictMods.Count > 0; internal static void Detect() { DetectedConflictMods.Clear(); try { foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { string text = pluginInfo.Key ?? string.Empty; PluginInfo value = pluginInfo.Value; object obj; if (value == null) { obj = null; } else { BepInPlugin metadata = value.Metadata; obj = ((metadata != null) ? metadata.Name : null); } if (obj == null) { obj = string.Empty; } string text2 = (string)obj; if (!string.Equals(text, "zichen.latejoinnow", StringComparison.OrdinalIgnoreCase) && !LooksLikeSelf(text) && !LooksLikeSelf(text2) && (LooksLikeOtherLateJoinMod(text) || LooksLikeOtherLateJoinMod(text2))) { string item = ((!string.IsNullOrWhiteSpace(text2)) ? text2 : text); if (!DetectedConflictMods.Contains(item)) { DetectedConflictMods.Add(item); Plugin.LogWarning("[ConflictDetect] 检测到同类晚加入 mod:" + text + " / " + text2); } } } } catch (Exception ex) { Plugin.LogWarning("[ConflictDetect] 扫描插件失败:" + ex.Message); } } private static string Normalize(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } return value.Replace("_", string.Empty).Replace("-", string.Empty).Replace(" ", string.Empty) .Replace(".", string.Empty) .ToLowerInvariant(); } private static bool LooksLikeSelf(string value) { string text = Normalize(value); if (text.Length == 0) { return false; } for (int i = 0; i < SelfKeywords.Length; i++) { if (text.IndexOf(SelfKeywords[i], StringComparison.Ordinal) >= 0) { return true; } } return false; } private static bool LooksLikeOtherLateJoinMod(string value) { string text = Normalize(value); if (text.Length == 0) { return false; } for (int i = 0; i < ConflictKeywords.Length; i++) { if (text.IndexOf(ConflictKeywords[i], StringComparison.Ordinal) >= 0) { return true; } } return false; } } internal static class LateJoinNowHooks { private static bool _changeLevelInProgress; private static int _changeLevelCounter; private static readonly Dictionary _trackedItemInstantiationIds = new Dictionary(); private static readonly FieldRef> _hypnosisLinesRef = AccessTools.FieldRefAccess>("hypnosisLines"); private static readonly FieldRef _hypnosisLinePrefabRef = TryFieldRef("hypnosisLine"); private static readonly FieldRef _hypnosisLinesParentRef = TryFieldRef("HypnosisLinesParenTransform"); private static readonly FieldRef _levelAnimationCompletedRef = AccessTools.FieldRefAccess("levelAnimationCompleted"); private static readonly FieldRef _playerNameRef = AccessTools.FieldRefAccess("playerName"); private static readonly FieldRef _loadingUIStartedRef = TryFieldRef("levelAnimationStarted"); private static readonly FieldRef _loadingUICompletedRef = TryFieldRef("levelAnimationCompleted"); internal static int ChangeLevelCounter => _changeLevelCounter; internal static int TrackedItemCount => _trackedItemInstantiationIds.Count; internal static void ItemAttributesAwakePostfix(ItemAttributes __instance) { if (!Plugin.IsHookActive() || !PhotonNetwork.IsMasterClient || (Object)(object)__instance == (Object)null) { return; } try { PhotonView component = ((Component)__instance).GetComponent(); if (!((Object)(object)component == (Object)null) && component.InstantiationId != 0) { _trackedItemInstantiationIds[component.ViewID] = component.InstantiationId; } } catch (Exception ex) { Plugin.Verbose("[TrackItem] 记录 ItemAttributes InstantiationId 失败:" + ex.Message); } } internal static void ClearTrackedItemInstantiationCaches() { if (_trackedItemInstantiationIds.Count == 0) { return; } int num = 0; foreach (KeyValuePair trackedItemInstantiationId in _trackedItemInstantiationIds) { if (Plugin.ClearInstantiationCacheById(trackedItemInstantiationId.Value)) { num++; } } Plugin.LogInfo($"[TrackItem] 切关清理:已按 InstantiationId 清掉 {num} 个 ItemAttributes 物品的 buffered Instantiate 事件(含已销毁/已购买的)。"); _trackedItemInstantiationIds.Clear(); } internal static void RunManagerChangeLevelPrefix(bool _completedLevel, bool _levelFailed, ChangeLevelType _changeLevelType) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.IsHookActive()) { return; } if (_changeLevelInProgress) { Plugin.Verbose($"[Hook] ChangeLevel Prefix: 检测到重入,跳过(type={_changeLevelType})。"); return; } Plugin.Verbose($"[Hook] RunManager.ChangeLevel Prefix 被触发 | type={_changeLevelType} completed={_completedLevel} failed={_levelFailed} prevScene={GetCurrentLevelName()} isMaster={PhotonNetwork.IsMasterClient}"); if (!PhotonNetwork.IsMasterClient) { Plugin.Verbose("[Hook] ChangeLevel Prefix: 非主机,跳过清缓存。"); return; } _changeLevelInProgress = true; try { Plugin.ClearRunManagerPunBufferedRpcs(); Plugin.ClearAllScenePhotonCache(); ClearTrackedItemInstantiationCaches(); Plugin.HideRoomForChangeLevelCooldown(); } catch (Exception ex) { Plugin.LogWarning("ChangeLevel prefix 清缓存失败:" + ex.Message + "\n" + ex.StackTrace); } finally { _changeLevelInProgress = false; } } internal static void RunManagerChangeLevelPostfix(bool _completedLevel, bool _levelFailed, ChangeLevelType _changeLevelType) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.IsHookActive()) { return; } if (!PhotonNetwork.IsMasterClient) { Plugin.Verbose($"[Hook] ChangeLevel Postfix 非主机 | type={_changeLevelType} newScene={GetCurrentLevelName()}"); return; } _changeLevelCounter++; Plugin.LogInfo($"[Hook] ChangeLevel #{_changeLevelCounter} | type={_changeLevelType} newScene={GetCurrentLevelName()} pendingLateJoiners={Plugin.PendingLateJoinerCount}"); Plugin.LogInfo($"[Hook] 当前配置 | 模组启用={Plugin.IsStaticModEnabled()} " + $"卡车={Plugin.AllowLobby} 商店={Plugin.AllowShop} 关卡={Plugin.AllowLevel} " + $"公共房可见={Plugin.PublicVisibleSync} 房名前缀={Plugin.PublicRoomPrefixEnabled} " + $"加入弹窗={Plugin.JoinNotifyEnabled}"); try { Plugin.EnforceLobbyState(); } catch (Exception ex) { Plugin.LogWarning("ChangeLevel postfix 失败:" + ex.Message + "\n" + ex.StackTrace); } } private static string GetCurrentLevelName() { try { RunManager instance = RunManager.instance; object obj; if (instance == null) { obj = null; } else { Level levelCurrent = instance.levelCurrent; obj = ((levelCurrent != null) ? ((Object)levelCurrent).name : null); } if (obj == null) { obj = ""; } return (string)obj; } catch { return ""; } } internal static void LevelGeneratorStartPrefix(LevelGenerator __instance) { if (!Plugin.IsHookActive() || !CompatibilityReport.CanRefreshLevelGenerator || !PhotonNetwork.IsMasterClient) { return; } bool flag = false; try { flag = SemiFunc.RunIsLobby(); } catch { } Plugin.Verbose($"[Hook] LevelGenerator.Start Prefix 被触发 | isMaster=true isLobby={flag} hasView={(Object)(object)__instance != (Object)null && (Object)(object)__instance.PhotonView != (Object)null}"); if (!flag || (Object)(object)__instance == (Object)null || (Object)(object)__instance.PhotonView == (Object)null) { return; } try { Plugin.Verbose($"[Hook] Lobby 启动,清理 LevelGenerator buffered RPC | viewID={__instance.PhotonView.ViewID}"); PhotonNetwork.RemoveBufferedRPCs(__instance.PhotonView.ViewID, (string)null, (int[])null); } catch (Exception ex) { Plugin.LogWarning("LevelGenerator prefix 失败:" + ex.Message + "\n" + ex.StackTrace); } } internal static void PlayerAvatarStartPostfix(PlayerAvatar __instance) { if (!Plugin.IsHookActive() || !CompatibilityReport.CanCatchupLateJoiner || !PhotonNetwork.IsMasterClient || (Object)(object)__instance == (Object)null || (Object)(object)__instance.photonView == (Object)null) { return; } bool flag = false; try { flag = SemiFunc.RunIsLobby(); } catch { } Plugin.Verbose($"[Hook] PlayerAvatar.Start Postfix | playerName={SafePlayerName(__instance)} isMaster=true isLobby={flag}"); try { __instance.photonView.RPC("LoadingLevelAnimationCompletedRPC", (RpcTarget)0, Array.Empty()); Plugin.Verbose($"[Hook] 已对全员广播 LoadingLevelAnimationCompletedRPC | playerName={SafePlayerName(__instance)} isLobby={flag}"); } catch (Exception ex) { Plugin.LogWarning("催加载动画 RPC 失败:" + ex.Message + "\n" + ex.StackTrace); } try { int viewID = __instance.photonView.ViewID; EnemyOnScreen[] array = Object.FindObjectsOfType(); foreach (EnemyOnScreen val in array) { if (!((Object)(object)val == (Object)null)) { val.PlayerAdded(viewID); } } } catch (Exception ex2) { Plugin.LogWarning("EnemyOnScreen.PlayerAdded 补充调用失败:" + ex2.Message); } } internal static bool PlayerAvatarSpawnPrefix(PlayerAvatar __instance) { if (!Plugin.IsHookActive()) { return true; } if (!CompatibilityReport.CanCatchupLateJoiner) { return true; } if ((Object)(object)__instance == (Object)null) { return true; } try { if (CompatibilityReport.PlayerAvatarSpawnedRef.Invoke(__instance)) { Plugin.Verbose("[Hook] PlayerAvatar.Spawn Prefix: " + SafePlayerName(__instance) + " 已 spawn,跳过。"); return false; } Plugin.Verbose("[Hook] PlayerAvatar.Spawn Prefix: " + SafePlayerName(__instance) + " 未 spawn,放行。"); } catch (Exception ex) { Plugin.LogWarning("读取 PlayerAvatar.spawned 失败:" + ex.Message); } return true; } internal static void NetworkManagerOnPlayerEnteredRoomPostfix(Player newPlayer) { Plugin.NetworkManagerOnPlayerEnteredRoomPostfix(newPlayer); } internal static void NetworkManagerOnPlayerLeftRoomPostfix(Player otherPlayer) { Plugin.NetworkManagerOnPlayerLeftRoomPostfix(otherPlayer); } internal static void NetworkConnectOnConnectedToMasterPrefix() { Plugin.NetworkConnectOnConnectedToMasterPrefix(); } internal static void NetworkManagerPlayerSpawnedRPCPostfix(PhotonMessageInfo _info) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Plugin.NetworkManagerPlayerSpawnedRPCPostfix(_info); } private static FieldRef TryFieldRef(string fieldName) { try { if (AccessTools.Field(typeof(TType), fieldName) == null) { return null; } return AccessTools.FieldRefAccess(fieldName); } catch { return null; } } internal static bool GumballValuableUpdateHypnosisLinesListPrefix(GumballValuable __instance) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.IsHookActive()) { return true; } if ((Object)(object)__instance == (Object)null) { return true; } if (_hypnosisLinesRef == null || _hypnosisLinePrefabRef == null) { return true; } try { List list = _hypnosisLinesRef.Invoke(__instance); List list2 = SemiFunc.PlayerGetList(); if (list == null || list2 == null) { return true; } if (list2.Count > list.Count) { GameObject val = _hypnosisLinePrefabRef.Invoke(__instance); Transform val2 = ((_hypnosisLinesParentRef != null) ? _hypnosisLinesParentRef.Invoke(__instance) : null); if ((Object)(object)val == (Object)null) { return true; } for (int i = list.Count; i < list2.Count; i++) { GameObject val3 = Object.Instantiate(val, ((Component)__instance).transform.position, Quaternion.identity); if ((Object)(object)val2 != (Object)null) { val3.transform.SetParent(val2); } val3.SetActive(false); list.Add(val3); Plugin.Verbose($"[GumballFix] 为玩家 {i} 补创 hypnosisLine"); } } } catch (Exception ex) { Plugin.LogWarning("[GumballFix] 补创 hypnosisLine 失败:" + ex.Message); } return true; } internal static bool SemiFuncOwnerOnlyRPCPrefix(PhotonMessageInfo _info, PhotonView _photonView, ref bool __result) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.IsHookActive()) { return true; } try { if (_info.Sender != null && PhotonNetwork.MasterClient != null && _info.Sender.ActorNumber == PhotonNetwork.MasterClient.ActorNumber) { __result = true; return false; } } catch { } return true; } internal static bool PlayerAvatarLoadingLevelAnimationCompletedRPCPrefix(PlayerAvatar __instance) { if (!Plugin.IsHookActive()) { return true; } if ((Object)(object)__instance == (Object)null) { return true; } if (_levelAnimationCompletedRef == null) { return true; } try { if (!_levelAnimationCompletedRef.Invoke(__instance)) { _levelAnimationCompletedRef.Invoke(__instance) = true; string text = ((_playerNameRef != null) ? _playerNameRef.Invoke(__instance) : "?"); Plugin.Verbose("[Hook] PlayerAvatar.LoadingLevelAnimationCompletedRPC: 强制设置 " + text + " levelAnimationCompleted=true(绕过 OwnerOnlyRPC 守门)"); } } catch (Exception ex) { Plugin.LogWarning("[Hook] PlayerAvatar.LoadingLevelAnimationCompletedRPC prefix 失败:" + ex.Message); } return false; } internal static bool LoadingUILevelAnimationStartPrefix(LoadingUI __instance) { if (!Plugin.IsHookActive()) { return true; } if ((Object)(object)__instance == (Object)null) { return true; } if ((Object)(object)LevelGenerator.Instance == (Object)null) { return true; } try { if ((Object)(object)LevelGenerator.Instance.Level == (Object)null) { Plugin.LogWarning("[Hook] LoadingUI.LevelAnimationStart: LevelGenerator.Level 为 null,跳过关卡介绍动画并直接进入游戏。"); if (_loadingUIStartedRef != null) { _loadingUIStartedRef.Invoke(__instance) = true; } if (_loadingUICompletedRef != null) { _loadingUICompletedRef.Invoke(__instance) = true; } try { if ((Object)(object)PlayerController.instance != (Object)null && (Object)(object)PlayerController.instance.playerAvatarScript != (Object)null) { PlayerController.instance.playerAvatarScript.LoadingLevelAnimationCompleted(); } } catch (Exception ex) { Plugin.LogWarning("[Hook] LevelAnimationComplete fallback 失败:" + ex.Message); } return false; } } catch (Exception ex2) { Plugin.LogWarning("[Hook] LoadingUI prefix 守门失败:" + ex2.Message); } return true; } private static string SafePlayerName(PlayerAvatar avatar) { try { if ((Object)(object)avatar == (Object)null) { return ""; } if ((Object)(object)avatar.photonView == (Object)null) { return ""; } Player owner = avatar.photonView.Owner; return (owner != null) ? (owner.NickName + "#" + owner.ActorNumber) : ""; } catch { return ""; } } } }