Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of LateJoinNow v1.0.1
Zichen-LateJoinNow-1.0.1.dll
Decompiled 18 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.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 <BroadcastLoadingAnimationCompleted>d__124 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Player target; private int <retry>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <BroadcastLoadingAnimationCompleted>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; <retry>5__2 = 0; break; case 1: <>1__state = -1; <retry>5__2++; break; } if (<retry>5__2 < 3) { if (!IsTargetStillOnline(target)) { return false; } int num = 0; try { IEnumerable<PlayerAvatar> enumerable2; if (!((Object)(object)GameDirector.instance != (Object)null) || GameDirector.instance.PlayerList == null) { IEnumerable<PlayerAvatar> enumerable = Object.FindObjectsOfType<PlayerAvatar>(); enumerable2 = enumerable; } else { IEnumerable<PlayerAvatar> 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<object>()); num++; } catch { } } } } catch (Exception ex) { LogWarning("[LateJoin] 发 LoadingLevelAnimationCompletedRPC 失败:" + ex.Message); } if (<retry>5__2 == 0) { LogInfo($"[LateJoin] 阶段 3/3 → {target.NickName}: LoadingLevelAnimationCompletedRPC 已对 {num} 个 PlayerAvatar 首发(RpcTarget.All)。"); } else { Verbose($"[LateJoin] LoadingLevelAnimationCompletedRPC 第 {<retry>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 <EnsurePlayerCorpseObjectsAndSnapshot>d__113 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Player target; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <EnsurePlayerCorpseObjectsAndSnapshot>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 <SendCatchupSnapshotStaged>d__125 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Player target; private string <startScene>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <SendCatchupSnapshotStaged>d__125(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <startScene>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; <startScene>5__2 = CurrentSceneName(); if (VerboseLogEnabled) { int num = 0; try { num = Object.FindObjectsOfType<PhotonView>().Length; } catch { } LogInfo($"[LateJoin] 启动快照协程 → {target.NickName} | 关卡#{LateJoinNowHooks.ChangeLevelCounter} startScene={<startScene>5__2} 当前PhotonView总数={num}"); } else { LogInfo($"[LateJoin] 启动快照协程 → {target.NickName} | 关卡#{LateJoinNowHooks.ChangeLevelCounter} startScene={<startScene>5__2}"); } <>2__current = (object)new WaitForSecondsRealtime(0.5f); <>1__state = 1; return true; case 1: <>1__state = -1; if (ShouldAbortSnapshot(target, <startScene>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<object>()); 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, <startScene>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, <startScene>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, <startScene>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, <startScene>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, <startScene>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 <SendGenerateDoneRetried>d__121 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Player target; private int <retry>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <SendGenerateDoneRetried>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; <retry>5__2 = 0; goto IL_0148; IL_0138: <retry>5__2++; goto IL_0148; IL_0148: if (<retry>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<object>()); flag = true; } } catch (Exception ex) { LogWarning("[LateJoin] 发 GenerateDone 失败:" + ex.Message); } if (!flag) { LogWarning("[LateJoin] LevelGenerator.Instance 不可用,跳过 GenerateDone。"); return false; } if (<retry>5__2 == 0) { LogInfo("[LateJoin] 阶段 2/3 → " + target.NickName + ": GenerateDone 首发。"); } else { Verbose($"[LateJoin] GenerateDone 第 {<retry>5__2 + 1} 次重发 → {target.NickName}"); } if (<retry>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 <WaitAndShowCompatPopup>d__162 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Plugin <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <WaitAndShowCompatPopup>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 <WaitAndShowConflictPopup>d__165 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Plugin <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <WaitAndShowConflictPopup>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 <WaitAndShowJoinNotifyPopup>d__168 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public string playerName; private float <deadline>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <WaitAndShowJoinNotifyPopup>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; } <deadline>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 < <deadline>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 <WaitForHostGeneratedThenCooldownCoroutine>d__140 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; private float <startedAt>5__2; private bool <generatedSeen>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <WaitForHostGeneratedThenCooldownCoroutine>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; <startedAt>5__2 = Time.unscaledTime; <generatedSeen>5__3 = false; break; case 1: <>1__state = -3; break; } if (!(Time.unscaledTime - <startedAt>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) { <generatedSeen>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 - <startedAt>5__2; if (<generatedSeen>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 <WaitForMenuReady>d__160 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public float deadlineSeconds; public Action<MenuManager> onReady; private float <deadline>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <WaitForMenuReady>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; <deadline>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 < <deadline>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 <WaitHostGenerated>d__120 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Player target; private float <deadline>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <WaitHostGenerated>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; <deadline>5__2 = Time.unscaledTime + 30f; break; case 1: <>1__state = -1; break; } if (Time.unscaledTime < <deadline>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<bool> _cfgModEnabled; private static ConfigEntry<DisplayLanguage> _cfgLanguage; private static ConfigEntry<bool> _cfgJoinNotify; private static ConfigEntry<bool> _cfgPublicRoomPrefix; private static ConfigEntry<bool> _cfgHudEnabled; private static ConfigEntry<bool> _cfgHudShowRoomType; private static ConfigEntry<bool> _cfgHudShowRoomName; private static ConfigEntry<bool> _cfgHudShowPlayerCount; private static ConfigEntry<int> _cfgHudFontSize; private static ConfigEntry<int> _cfgHudOpacity; private static ConfigEntry<bool> _cfgVerboseLog; private static ConfigEntry<bool> _cfgShowConflictWarning; private static ConfigEntry<bool> _cfgAllowLobby; private static ConfigEntry<bool> _cfgAllowShop; private static ConfigEntry<bool> _cfgAllowLevel; private static ConfigEntry<bool> _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<string> _hudLines = new List<string>(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<int> _pendingLateJoiners = new HashSet<int>(); private static readonly FieldRef<PlayerAvatar, PlayerDeathHead> _playerDeathHeadRef = AccessTools.FieldRefAccess<PlayerAvatar, PlayerDeathHead>("playerDeathHead"); private static readonly FieldRef<PlayerAvatar, PlayerTumble> _playerTumbleRef = AccessTools.FieldRefAccess<PlayerAvatar, PlayerTumble>("tumble"); private static readonly FieldRef<Module, bool> _moduleConnectingTopRef = AccessTools.FieldRefAccess<Module, bool>("ConnectingTop"); private static readonly FieldRef<Module, bool> _moduleConnectingBottomRef = AccessTools.FieldRefAccess<Module, bool>("ConnectingBottom"); private static readonly FieldRef<Module, bool> _moduleConnectingRightRef = AccessTools.FieldRefAccess<Module, bool>("ConnectingRight"); private static readonly FieldRef<Module, bool> _moduleConnectingLeftRef = AccessTools.FieldRefAccess<Module, bool>("ConnectingLeft"); private static readonly FieldRef<Module, bool> _moduleFirstRef = AccessTools.FieldRefAccess<Module, bool>("First"); private static readonly FieldRef<Module, bool> _moduleSetupDoneRef = AccessTools.FieldRefAccess<Module, bool>("SetupDone"); private static readonly FieldRef<ValuableObject, bool> _valuableDollarValueSetRef = AccessTools.FieldRefAccess<ValuableObject, bool>("dollarValueSet"); private static readonly FieldRef<ValuableObject, float> _valuableDollarValueCurrentRef = AccessTools.FieldRefAccess<ValuableObject, float>("dollarValueCurrent"); private static readonly FieldRef<ValuableObject, bool> _valuableDiscoveredRef = AccessTools.FieldRefAccess<ValuableObject, bool>("discovered"); private static readonly FieldRef<ValuableObject, bool> _valuableInStartRoomRef = AccessTools.FieldRefAccess<ValuableObject, bool>("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<string>("模组信息", "模组名称", "中途随时加入", new ConfigDescription("当前模组的中文名称。此处只是提示,不影响功能。", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { ReadOnly = true, HideDefaultButton = true } })); ((BaseUnityPlugin)this).Config.Bind<string>("模组信息", "模组版本号", "1.0.1", new ConfigDescription("当前模组版本号。此处只是提示,不影响功能。", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { ReadOnly = true, HideDefaultButton = true } })); ((BaseUnityPlugin)this).Config.Bind<string>("模组信息", "REPO交流QQ群", "824639225", new ConfigDescription("REPO 游戏交流、BUG 反馈、优化建议、功能请求请加 QQ 群。此处只是提示,不影响功能。", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { ReadOnly = true, HideDefaultButton = true } })); _cfgLanguage = ((BaseUnityPlugin)this).Config.Bind<DisplayLanguage>("模组信息", "语言/Language", DisplayLanguage.中文, "公开文档和配置说明语言切换。仅该项保留中英并列格式。"); _cfgModEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("A.全局设置", "模组启用", true, "关闭后整个模组全部功能彻底失效,零性能开销。"); _cfgPublicRoomPrefix = ((BaseUnityPlugin)this).Config.Bind<bool>("A.全局设置", "公开房名加前缀", true, "开启后,创建自定义名字的公开房时,服务器列表里的房间名会自动加上\"[游戏中]\"前缀,让其他玩家知道这个房间支持中途加入。前缀只在房间创建时一次性写入,房间存在期间无法修改。"); _cfgPublicVisible = ((BaseUnityPlugin)this).Config.Bind<bool>("A.全局设置", "游戏中房间在公开服务器", true, "若你的房间是公共房,开启后允许中途加入时房间也会出现在公共列表,可被陌生人随机加入;关闭则只接受邀请加入。"); _cfgJoinNotify = ((BaseUnityPlugin)this).Config.Bind<bool>("A.全局设置", "新玩家加入弹窗通知", true, "开启后,每次有玩家中途加入房间,主机会收到一次菜单弹窗提示。"); _cfgAllowLobby = ((BaseUnityPlugin)this).Config.Bind<bool>("B.场景白名单", "允许在卡车阶段加入", true, "卡车阶段(游戏内 Lobby,玩家在卡车里休整)允许中途加入。"); _cfgAllowShop = ((BaseUnityPlugin)this).Config.Bind<bool>("B.场景白名单", "允许在商店阶段加入", true, "商店阶段允许中途加入。"); _cfgAllowLevel = ((BaseUnityPlugin)this).Config.Bind<bool>("B.场景白名单", "允许在关卡进行中加入", true, "普通运行关卡(搜刮、跑路过程中)允许中途加入。"); _cfgHudEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("C.房间信息显示", "启用", true, "开启后,当前场景允许中途加入时,屏幕左下角显示一个小信息框。关闭则任何场景都不显示。"); _cfgHudShowPlayerCount = ((BaseUnityPlugin)this).Config.Bind<bool>("C.房间信息显示", "显示房间人数", true, "在 HUD 上显示当前人数 / 最大人数(例如 3/6)。"); _cfgHudShowRoomType = ((BaseUnityPlugin)this).Config.Bind<bool>("C.房间信息显示", "显示房间类型", true, "在 HUD 上显示当前房间类型:公开服务器 / 私人服务器 / 单人模式。"); _cfgHudShowRoomName = ((BaseUnityPlugin)this).Config.Bind<bool>("C.房间信息显示", "显示房间名称", true, "在 HUD 上显示当前房间名称:公开房显示房间名,私人房显示\"仅限好友加入\"。"); _cfgHudFontSize = ((BaseUnityPlugin)this).Config.Bind<int>("C.房间信息显示", "字体大小", 16, new ConfigDescription("HUD 文字大小,默认 16。", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 28), Array.Empty<object>())); _cfgHudOpacity = ((BaseUnityPlugin)this).Config.Bind<int>("C.房间信息显示", "文字透明度/%", 80, new ConfigDescription("HUD 文字整体透明度,默认 80%。", (AcceptableValueBase)(object)new AcceptableValueRange<int>(20, 100), Array.Empty<object>())); _cfgVerboseLog = ((BaseUnityPlugin)this).Config.Bind<bool>("D.调试", "详细日志", false, "开启后会在控制台输出更多关键路径日志(关卡切换、玩家加入、缓存清理、触发等),便于排查问题。"); _cfgShowConflictWarning = ((BaseUnityPlugin)this).Config.Bind<bool>("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, object>)(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<PhotonView>(); 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) ?? "<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(<EnsurePlayerCorpseObjectsAndSnapshot>d__113))] private static IEnumerator EnsurePlayerCorpseObjectsAndSnapshot(Player target) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <EnsurePlayerCorpseObjectsAndSnapshot>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 = "<null>"; } 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 "<null>"; } 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 "<unknown>"; } catch { return "<error>"; } } 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<PlayerDeathHead>(); component.playerAvatar = val2; _playerDeathHeadRef.Invoke(val2) = component; string nickName = target.NickName; PhotonView component2 = val3.GetComponent<PhotonView>(); 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<PlayerTumble>(); component3.playerAvatar = val2; _playerTumbleRef.Invoke(val2) = component3; string nickName2 = target.NickName; PhotonView component4 = val4.GetComponent<PhotonView>(); 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(<WaitHostGenerated>d__120))] private static IEnumerator WaitHostGenerated(Player target) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <WaitHostGenerated>d__120(0) { target = target }; } [IteratorStateMachine(typeof(<SendGenerateDoneRetried>d__121))] private static IEnumerator SendGenerateDoneRetried(Player target) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <SendGenerateDoneRetried>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<Module>(); foreach (Module val in array) { if ((Object)(object)val == (Object)null) { continue; } PhotonView component = ((Component)val).GetComponent<PhotonView>(); 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<PhotonView>(); 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<object>()); num2++; } if (_valuableInStartRoomRef.Invoke(valuable)) { component.RPC("AddToDollarHaulListRPC", target, Array.Empty<object>()); 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(<BroadcastLoadingAnimationCompleted>d__124))] private static IEnumerator BroadcastLoadingAnimationCompleted(Player target) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <BroadcastLoadingAnimationCompleted>d__124(0) { target = target }; } [IteratorStateMachine(typeof(<SendCatchupSnapshotStaged>d__125))] private static IEnumerator SendCatchupSnapshotStaged(Player target) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <SendCatchupSnapshotStaged>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(<WaitForHostGeneratedThenCooldownCoroutine>d__140))] private static IEnumerator WaitForHostGeneratedThenCooldownCoroutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <WaitForHostGeneratedThenCooldownCoroutine>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<int> hashSet = BuildPlayerPersistentViewIdSet(); PhotonView[] array = Object.FindObjectsOfType<PhotonView>(); 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 : "<null>")); } 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<int> BuildPlayerPersistentViewIdSet() { HashSet<int> hashSet = new HashSet<int>(); 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<PhotonView>(); 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(<WaitForMenuReady>d__160))] private IEnumerator WaitForMenuReady(float deadlineSeconds, Action<MenuManager> onReady) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <WaitForMenuReady>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(<WaitAndShowCompatPopup>d__162))] private IEnumerator WaitAndShowCompatPopup() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <WaitAndShowCompatPopup>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("<size=85%>"); foreach (string affectedFeature in CompatibilityReport.AffectedFeatures) { stringBuilder.Append(" • ").AppendLine(affectedFeature); } stringBuilder.AppendLine("</size>"); stringBuilder.AppendLine(); stringBuilder.Append("<size=90%><i>"); stringBuilder.Append(UseChinese() ? "如需使用这些功能,请等待作者适配最新版本。" : "Please wait for the mod to be updated for the latest game version."); stringBuilder.Append("</i></size>"); return stringBuilder.ToString(); } [IteratorStateMachine(typeof(<WaitAndShowConflictPopup>d__165))] private IEnumerator WaitAndShowConflictPopup() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <WaitAndShowConflictPopup>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("<size=85%>"); foreach (string detectedConflictMod in ConflictDetect.DetectedConflictMods) { stringBuilder.Append(" • ").AppendLine(detectedConflictMod); } stringBuilder.AppendLine("</size>"); stringBuilder.AppendLine(); stringBuilder.Append("<b>"); if (UseChinese()) { stringBuilder.Append("建议在模组管理器中禁用上述模组,仅保留 LateJoinNow。"); } else { stringBuilder.Append("Please disable those mods and keep only LateJoinNow."); } stringBuilder.AppendLine("</b>"); stringBuilder.AppendLine(); stringBuilder.Append("<size=85%><i>"); stringBuilder.Append(UseChinese() ? "同时启用会出现状态污染、卡加载等问题。" : "Running them together causes state corruption and loading hangs."); stringBuilder.Append("</i></size>"); return stringBuilder.ToString(); } [IteratorStateMachine(typeof(<WaitAndShowJoinNotifyPopup>d__168))] internal static IEnumerator WaitAndShowJoinNotifyPopup(string playerName) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <WaitAndShowJoinNotifyPopup>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 "<b>" + playerName + "</b> has joined your room."; } return "玩家 <b>" + playerName + "</b> 已加入你的房间。"; } } 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<GameManager, LobbyTypes> GameManagerLobbyTypeRef; public static FieldRef<GameManager, bool> GameManagerConnectRandomRef; public static FieldRef<RunManager, RunManagerPUN> RunManagerPunRef; public static FieldRef<RunManagerPUN, PhotonView> RunManagerPunPhotonViewRef; public static FieldRef<PlayerAvatar, bool> PlayerAvatarSpawnedRef; public static FieldRef<MenuManager, bool> MenuPagePopUpScheduledRef; public static FieldRef<MenuManager, MenuPageIndex> MenuCurrentMenuPageIndexRef; public static FieldRef<MenuManager, MenuPage> 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<string> AffectedFeatures { get; } = new List<string>(); public static List<string> Details { get; } = new List<string>(); 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<GameManager, LobbyTypes>("lobbyType"); } if (GameManagerConnectRandomField != null) { GameManagerConnectRandomRef = AccessTools.FieldRefAccess<GameManager, bool>("connectRandom"); } } catch (Exception ex) { Details.Add("GameManager FieldRef 缓存失败:" + ex.Message); } CanReadRunManagerPun = TryAll(() => RequireFieldRef<RunManager, RunManagerPUN>("runManagerPUN", out RunManagerPunRef), () => RequireFieldRef<RunManagerPUN, PhotonView>("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<PlayerAvatar, bool>("spawned", out PlayerAvatarSpawnedRef), () => RequireMethodExists(typeof(PlayerAvatar), "LoadingLevelAnimationCompletedRPC")); if (!CanCatchupLateJoiner) { AffectedFeatures.Add(Plugin.UseChinese() ? "晚加入玩家自动跳过加载动画" : "Late joiner load-animation catch-up"); } CanShowMenuPopup = TryAll(() => RequireFieldRef<MenuManager, bool>("pagePopUpScheduled", out MenuPagePopUpScheduledRef), () => RequireFieldRef<MenuManager, MenuPageIndex>("currentMenuPageIndex", out MenuCurrentMenuPageIndexRef), () => RequireFieldRef<MenuManager, MenuPage>("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<bool>[] checks) { bool