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 LateJoinFixed v1.3.0
LateJoinFixed.dll
Decompiled 13 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.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; using Zichen_LateJoinNow.Common; using Zichen_LateJoinNow.I18N; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LateJoinFixed")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.3.0.0")] [assembly: AssemblyInformationalVersion("1.3.0")] [assembly: AssemblyProduct("LateJoinFixed")] [assembly: AssemblyTitle("LateJoinFixed")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.3.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Zichen_LateJoinNow { 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; private static readonly BindingFlags NPS = BindingFlags.Static | BindingFlags.NonPublic; private static readonly BindingFlags NPI = BindingFlags.Instance | BindingFlags.NonPublic; private static readonly BindingFlags ALL = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; 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(() => RF(typeof(PhotonNetwork), "removeFilter", NPS, ref PhotonRemoveFilter), () => RF(typeof(PhotonNetwork), "keyByteSeven", NPS, ref PhotonKeyByteSeven), () => RF(typeof(PhotonNetwork), "ServerCleanOptions", NPS, ref PhotonServerCleanOptions), () => RM(typeof(PhotonNetwork), "RaiseEventInternal", new Type[4] { typeof(byte), typeof(object), typeof(RaiseEventOptions), typeof(SendOptions) }, NPS, ref PhotonRaiseEventInternal)); if (!CanCleanPhotonCache) { AffectedFeatures.Add("Photon instantiation cache cleanup"); } RF(typeof(GameManager), "connectRandom", NPI, ref GameManagerConnectRandomField); RF(typeof(GameManager), "lobbyType", NPI, 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 failed: " + ex.Message); } CanReadRunManagerPun = TryAll(() => RFR<RunManager, RunManagerPUN>("runManagerPUN", out RunManagerPunRef), () => RFR<RunManagerPUN, PhotonView>("photonView", out RunManagerPunPhotonViewRef)); if (!CanReadRunManagerPun) { AffectedFeatures.Add("RunManager buffered RPC cleanup"); } CanRefreshLevelGenerator = typeof(LevelGenerator) != null; CanCatchupLateJoiner = TryAll(() => RFR<PlayerAvatar, bool>("spawned", out PlayerAvatarSpawnedRef), () => RME(typeof(PlayerAvatar), "LoadingLevelAnimationCompletedRPC")); if (!CanCatchupLateJoiner) { AffectedFeatures.Add("Late joiner load-animation catch-up"); } CanShowMenuPopup = TryAll(() => RFR<MenuManager, bool>("pagePopUpScheduled", out MenuPagePopUpScheduledRef), () => RFR<MenuManager, MenuPageIndex>("currentMenuPageIndex", out MenuCurrentMenuPageIndexRef), () => RFR<MenuManager, MenuPage>("currentMenuPage", out MenuCurrentMenuPageRef)); if (!CanShowMenuPopup) { AffectedFeatures.Add("Menu popup notifications"); } CanReadGameManagerLobbyState = GameManagerConnectRandomField != null && GameManagerLobbyTypeField != null; if (!CanReadGameManagerLobbyState) { AffectedFeatures.Add("Room type detection"); } CanAddRoomNamePrefix = Check(typeof(DataDirector), "networkServerName", "Public room name [Late Join] prefix"); CanFixGumballOverflow = HasField(typeof(GumballValuable), "hypnosisLines") && HasField(typeof(GumballValuable), "hypnosisLine"); if (!CanFixGumballOverflow) { AffectedFeatures.Add("GumballValuable overflow fix"); } CanGuardLoadingAnimation = HasField(typeof(LoadingUI), "levelAnimationStarted") && HasField(typeof(LoadingUI), "levelAnimationCompleted") && HasField(typeof(PlayerAvatar), "levelAnimationCompleted"); if (!CanGuardLoadingAnimation) { AffectedFeatures.Add("Late join loading screen guard"); } CanEnsurePlayerCorpse = HasField(typeof(PlayerAvatar), "playerDeathHead") && HasField(typeof(PlayerAvatar), "tumble"); if (!CanEnsurePlayerCorpse) { AffectedFeatures.Add("Late joiner death head / tumble fallback"); } CanSyncModuleState = HasField(typeof(Module), "ConnectingTop") && HasField(typeof(Module), "ConnectingBottom") && HasField(typeof(Module), "ConnectingRight") && HasField(typeof(Module), "ConnectingLeft") && HasField(typeof(Module), "First") && HasField(typeof(Module), "SetupDone"); if (!CanSyncModuleState) { AffectedFeatures.Add("Late joiner walls / room interior sync"); } CanSyncValuableObjectState = HasField(typeof(ValuableObject), "dollarValueSet") && HasField(typeof(ValuableObject), "dollarValueCurrent") && HasField(typeof(ValuableObject), "discovered") && HasField(typeof(ValuableObject), "inStartRoom"); if (!CanSyncValuableObjectState) { AffectedFeatures.Add("Late joiner valuable state sync"); } HasIssues = AffectedFeatures.Count > 0; RuntimeReady = true; } private static bool HasField(Type t, string name) { return AccessTools.Field(t, name) != null; } private static bool Check(Type t, string name, string feature) { if (HasField(t, name)) { return true; } Details.Add("Missing field: " + t.Name + "." + name); AffectedFeatures.Add(feature); return false; } private static bool TryAll(params Func<bool>[] checks) { bool result = true; foreach (Func<bool> func in checks) { try { if (!func()) { result = false; } } catch (Exception ex) { Details.Add(ex.Message); result = false; } } return result; } private static bool RF(Type t, string name, BindingFlags flags, ref FieldInfo into) { into = t.GetField(name, flags); if (into != null) { return true; } Details.Add("Missing field: " + t.Name + "." + name); return false; } private static bool RM(Type t, string name, Type[] pTypes, BindingFlags flags, ref MethodInfo into) { into = t.GetMethod(name, flags, null, pTypes, null); if (into != null) { return true; } Details.Add("Missing method: " + t.Name + "." + name); return false; } private static bool RME(Type t, string name) { if (t.GetMethod(name, ALL) != null) { return true; } Details.Add("Missing method: " + t.Name + "." + name); return false; } private static bool RFR<TType, TField>(string name, out FieldRef<TType, TField> fieldRef) { fieldRef = null; if (!HasField(typeof(TType), name)) { Details.Add("Missing field: " + typeof(TType).Name + "." + name); return false; } try { fieldRef = AccessTools.FieldRefAccess<TType, TField>(name); return true; } catch (Exception ex) { Details.Add("FieldRef binding failed " + typeof(TType).Name + "." + name + ": " + ex.Message); return false; } } } internal static class ConflictDetect { internal static readonly List<string> DetectedConflictMods = new List<string>(); private static readonly string[] ConflictKeywords = new string[2] { "late", "join" }; private static readonly string[] SelfKeywords = new string[3] { "latejoinnow", "zichenlatejoinnow", "latejoinfixed" }; internal static bool HasConflict => DetectedConflictMods.Count > 0; internal static void Detect() { DetectedConflictMods.Clear(); try { foreach (KeyValuePair<string, PluginInfo> 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) && !IsSelf(text) && !IsSelf(text2) && (IsConflict(text) || IsConflict(text2))) { string text3 = ((!string.IsNullOrWhiteSpace(text2)) ? text2 : text); if (!DetectedConflictMods.Contains(text3)) { DetectedConflictMods.Add(text3); Plugin.LogWarning("Detected similar/conflicting mod: " + text3); } } } } catch (Exception ex) { Plugin.Verbose("Mod conflict detection failed: " + ex.Message); } } private static string Normalize(string v) { if (!string.IsNullOrWhiteSpace(v)) { return v.Replace("_", "").Replace("-", "").Replace(" ", "") .Replace(".", "") .ToLowerInvariant(); } return string.Empty; } private static bool IsSelf(string v) { string text = Normalize(v); string[] selfKeywords = SelfKeywords; foreach (string value in selfKeywords) { if (text.IndexOf(value, StringComparison.Ordinal) >= 0) { return true; } } return false; } private static bool IsConflict(string v) { string text = Normalize(v); string[] conflictKeywords = ConflictKeywords; foreach (string value in conflictKeywords) { if (text.IndexOf(value, StringComparison.Ordinal) >= 0) { return true; } } return false; } } internal static class EnemySync { internal static bool Enabled = true; private static FieldRef<EnemyParent, bool> _spawned; private static FieldRef<EnemyParent, bool> SpawnedRef => _spawned ?? (_spawned = AccessTools.FieldRefAccess<EnemyParent, bool>("Spawned")); internal static void OnRemotePlayerAvatarStart(PlayerAvatar avatar) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown try { if (Enabled && PhotonNetwork.IsMasterClient && SemiFunc.RunIsLevel() && (Object)avatar != (Object)null && (Object)avatar.photonView != (Object)null && !avatar.photonView.IsMine && avatar.photonView.Owner != null && (Object)LevelGenerator.Instance != (Object)null && LevelGenerator.Instance.Generated) { GameDirector instance = GameDirector.instance; if ((Object)instance != (Object)null) { ((MonoBehaviour)instance).StartCoroutine(HostSend(avatar.photonView.Owner.ActorNumber)); } } } catch (Exception e) { Suppressed.Report("Late-join enemy sync (host)", e); } } private static IEnumerator HostSend(int actor) { float[] array = new float[2] { 4f, 6f }; float[] array2 = array; foreach (float delay in array2) { yield return (object)new WaitForSeconds(delay); if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient || !SemiFunc.RunIsLevel()) { break; } Player val = ((PhotonNetwork.CurrentRoom != null) ? PhotonNetwork.CurrentRoom.GetPlayer(actor, false) : null); if (val == null || (Object)EnemyDirector.instance == (Object)null) { break; } int num = 0; int num2 = 0; if (EnemyDirector.instance.enemiesSpawned != null) { EnemyParent[] array3 = EnemyDirector.instance.enemiesSpawned.ToArray(); foreach (EnemyParent val2 in array3) { try { if (!((Object)val2 == (Object)null) && !((Object)((MonoBehaviourPun)val2).photonView == (Object)null)) { num2++; if (SpawnedRef != null && !SpawnedRef.Invoke(val2)) { ((MonoBehaviourPun)val2).photonView.RPC("DespawnRPC", val, Array.Empty<object>()); num++; } } } catch (Exception e) { Suppressed.Report("Late-join enemy sync (send)", e); } } } if (delay == 4f) { Plugin.LogInfo($"[Late-join enemy sync] {num2} enemies in the level; told a player who joined mid-level that {num} of them are resting (otherwise they'd see them frozen in place)."); } } } } internal static class LateJoinNowHooks { private class InitialSide { public Connection Side; public InitialSide(Connection s) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) Side = s; } } private static bool _changeLevelInProgress; private static readonly Dictionary<int, int> _trackedItems = new Dictionary<int, int>(); private static readonly List<GameObject> _itemsToTrack = new List<GameObject>(128); private static bool _itemTrackingCoroutineRunning; internal static readonly FieldRef<PlayerAvatar, bool> _avDisabledRef = AccessTools.FieldRefAccess<PlayerAvatar, bool>("isDisabled"); internal static readonly FieldRef<PlayerAvatar, bool> _avDeadSetRef = AccessTools.FieldRefAccess<PlayerAvatar, bool>("deadSet"); private static readonly FieldRef<GumballValuable, List<GameObject>> _gumLines = AccessTools.FieldRefAccess<GumballValuable, List<GameObject>>("hypnosisLines"); private static readonly FieldRef<GumballValuable, GameObject> _gumPrefab = TFR<GumballValuable, GameObject>("hypnosisLine"); private static readonly FieldRef<GumballValuable, Transform> _gumParent = TFR<GumballValuable, Transform>("HypnosisLinesParenTransform"); private static readonly FieldRef<GumballValuable, List<bool>> _gumPlayerInSight = TFR<GumballValuable, List<bool>>("playerInSight"); private static readonly FieldRef<GumballValuable, List<bool>> _gumPreviouslySeen = TFR<GumballValuable, List<bool>>("previouslySeenList"); private static readonly FieldRef<GumballValuable, int> _gumPlayerCount = TFR<GumballValuable, int>("playerCount"); private static readonly FieldRef<PlayerAvatar, bool> _animCompleted = AccessTools.FieldRefAccess<PlayerAvatar, bool>("levelAnimationCompleted"); private static readonly FieldRef<LoadingUI, bool> _uiStarted = TFR<LoadingUI, bool>("levelAnimationStarted"); private static readonly FieldRef<LoadingUI, bool> _uiCompleted = TFR<LoadingUI, bool>("levelAnimationCompleted"); private static readonly FieldRef<PlayerAvatarTalkAnimation, PlayerAvatarVisuals> _talkAnimVisualsRef = TFR<PlayerAvatarTalkAnimation, PlayerAvatarVisuals>("playerAvatarVisuals"); private static readonly FieldRef<PlayerAvatarOverchargeVisuals, PlayerAvatar> _overchargePlayerAvatarRef = TFR<PlayerAvatarOverchargeVisuals, PlayerAvatar>("playerAvatar"); private static readonly FieldRef<PlayerAvatarOverchargeVisuals, Transform> _overchargeBeamOriginRef = TFR<PlayerAvatarOverchargeVisuals, Transform>("physGrabBeamOrigin"); private static readonly FieldRef<PlayerAvatarGrabDisableVisuals, PlayerAvatar> _grabDisablePlayerAvatarRef = TFR<PlayerAvatarGrabDisableVisuals, PlayerAvatar>("playerAvatar"); private static readonly FieldRef<PlayerAvatarGrabDisableVisuals, Transform> _grabDisableBeamOriginRef = TFR<PlayerAvatarGrabDisableVisuals, Transform>("physGrabBeamOrigin"); private static readonly FieldRef<PlayerAvatarLeftArm, PlayerAvatarVisuals> _leftArmVisualsRef = TFR<PlayerAvatarLeftArm, PlayerAvatarVisuals>("playerAvatarVisuals"); private static readonly FieldRef<Inventory, bool> _inventorySpotsFeched = TFR<Inventory, bool>("spotsFeched"); private static readonly FieldRef<Inventory, List<InventorySpot>> _inventorySpotsRef = TFR<Inventory, List<InventorySpot>>("inventorySpots"); private static readonly FieldRef<Inventory, PhysGrabber> _inventoryPhysGrabberRef = TFR<Inventory, PhysGrabber>("physGrabber"); private static readonly FieldRef<Inventory, PlayerAvatar> _inventoryPlayerAvatarRef = TFR<Inventory, PlayerAvatar>("playerAvatar"); internal static readonly FieldRef<ItemEquippable, InventorySpot> _itemEquippedSpotRef = TFR<ItemEquippable, InventorySpot>("equippedSpot"); internal static readonly FieldRef<ItemEquippable, int> _itemEquippableOwnerIdRef = TFR<ItemEquippable, int>("ownerPlayerId"); internal static readonly FieldRef<ItemEquippable, int> _itemInventorySpotIndexRef = TFR<ItemEquippable, int>("inventorySpotIndex"); private static readonly FieldRef<PhysGrabber, PhysGrabObject> _physGrabbedObjectRef = TFR<PhysGrabber, PhysGrabObject>("grabbedPhysGrabObject"); private static readonly FieldRef<ItemEquippable, ItemState> _itemEquippableStateRef = TFR<ItemEquippable, ItemState>("currentState"); private static readonly FieldRef<InventorySpot, SpotState> _spotStateRef = TFR<InventorySpot, SpotState>("currentState"); private static readonly MethodInfo _spotSetCurrentItemProp = AccessTools.PropertySetter(typeof(InventorySpot), "CurrentItem"); private static MethodInfo _rpcUpdateItemStateMethod; private static readonly Dictionary<int, bool> _epLastLockedState = new Dictionary<int, bool>(8); private static readonly FieldRef<ExtractionPoint, bool> _epIsLockedRef = TFR<ExtractionPoint, bool>("isLocked"); private static float _restartSceneTimer = 0f; private static readonly FieldRef<RunManager, bool> _rmRestartingRef = TFR<RunManager, bool>("restarting"); private static readonly FieldRef<RunManager, bool> _rmRestartingDoneRef = TFR<RunManager, bool>("restartingDone"); private static readonly FieldRef<RunManager, bool> _rmGameOverRef = TFR<RunManager, bool>("gameOver"); private static readonly FieldRef<RunManager, bool> _rmLobbyJoinRef = TFR<RunManager, bool>("lobbyJoin"); private static readonly FieldRef<RunManager, bool> _rmWaitToChangeSceneRef = TFR<RunManager, bool>("waitToChangeScene"); private static readonly FieldRef<PlayerAvatar, bool> _avatarOutroDoneRef = TFR<PlayerAvatar, bool>("outroDone"); private static readonly ConditionalWeakTable<ModulePropSwitch, InitialSide> _initialPropSwitchSides = new ConditionalWeakTable<ModulePropSwitch, InitialSide>(); internal static void SetSpotItem(InventorySpot spot, ItemEquippable item) { if ((Object)(object)spot == (Object)null) { return; } try { if (_spotSetCurrentItemProp != null) { _spotSetCurrentItemProp.Invoke(spot, new object[1] { item }); } if (_spotStateRef != null) { _spotStateRef.Invoke(spot) = (SpotState)(((Object)(object)item != (Object)null) ? 1 : 0); } spot.UpdateUI(); } catch (Exception ex) { Plugin.Verbose("[SetSpotItem] Error: " + ex.Message); } } private static bool IsMainState() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 GameDirector instance = GameDirector.instance; if (instance == null) { return false; } return (int)instance.currentState == 2; } internal static void ClearTrackedItemInstantiationCaches() { if (_trackedItems.Count == 0) { return; } int num = 0; List<int> list = new List<int>(_trackedItems.Values); _trackedItems.Clear(); foreach (int item in list) { if (Plugin.ClearInstantiationCacheById(item)) { num++; } } Plugin.Verbose($"Level transition cleaned {num} item cache entries."); } internal static void RunManagerChangeLevelPrefix(bool _completedLevel, bool _levelFailed, ChangeLevelType _changeLevelType) { if (!Plugin.IsHookActive() || _changeLevelInProgress || !PhotonNetwork.IsMasterClient) { return; } _changeLevelInProgress = true; try { if (_animCompleted != null && GameDirector.instance?.PlayerList != null) { foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if (!((Object)(object)player == (Object)null)) { try { _animCompleted.Invoke(player) = true; } catch { } } } } Plugin.ClearRunManagerPunBufferedRpcs(); HashSet<int> keep = Plugin.BuildPlayerPersistentViewIdSet(); Plugin.DestroyAllSceneValuablesAndItemsWithKeepPublic(keep); Plugin.ClearAllScenePhotonCacheWithKeepPublic(keep, clearPersistent: true); ClearTrackedItemInstantiationCaches(); Plugin.ClearLateJoinQueue(); Plugin.HideRoomForChangeLevelCooldown(); _epLastLockedState.Clear(); } catch { } finally { _changeLevelInProgress = false; } } internal static void RunManagerChangeLevelPostfix(bool _completedLevel, bool _levelFailed, ChangeLevelType _changeLevelType) { if (!Plugin.IsHookActive() || !PhotonNetwork.IsMasterClient) { return; } try { Plugin.EnforceLobbyState(); } catch { } } internal static void LevelGeneratorStartPrefix(LevelGenerator __instance) { if (!PhotonNetwork.IsMasterClient) { try { Plugin.ResetClientCoopState(); return; } catch { return; } } if (!Plugin.IsHookActive() || !CompatibilityReport.CanRefreshLevelGenerator) { return; } bool flag = false; try { flag = SemiFunc.RunIsLobby(); } catch { } if (!flag || (Object)(object)__instance?.PhotonView == (Object)null) { return; } try { PhotonNetwork.RemoveBufferedRPCs(__instance.PhotonView.ViewID, (string)null, (int[])null); } catch { } } internal static void PlayerAvatarStartPostfix(PlayerAvatar __instance) { if (!Plugin.IsHookActive() || !CompatibilityReport.CanCatchupLateJoiner || (Object)(object)__instance?.photonView == (Object)null || !IsMainState()) { return; } if (!PhotonNetwork.IsMasterClient) { if (__instance.photonView.IsMine) { Plugin.TryRunClientLateJoinFixes(__instance); } return; } if (!__instance.photonView.IsMine && __instance.photonView.Owner != null) { MapSync.OnRemotePlayerAvatarStart(__instance); EnemySync.OnRemotePlayerAvatarStart(__instance); } if (_animCompleted != null) { try { _animCompleted.Invoke(__instance) = true; } catch { } } try { __instance.photonView.RPC("LoadingLevelAnimationCompletedRPC", (RpcTarget)0, Array.Empty<object>()); } catch { } try { int viewID = __instance.photonView.ViewID; EnemyOnScreen[] array = Object.FindObjectsOfType<EnemyOnScreen>(); foreach (EnemyOnScreen val in array) { if ((Object)(object)val != (Object)null) { val.PlayerAdded(viewID); } } } catch { } } internal static bool LevelGeneratorPlayerSpawnPrefix(LevelGenerator __instance) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.IsHookActive() || (Object)(object)__instance == (Object)null) { return true; } if (!PhotonNetwork.IsMasterClient) { return true; } if (!__instance.Generated) { return true; } try { if (GameDirector.instance?.PlayerList == null) { return false; } bool teleportToHostOnJoinEnabled = Plugin.TeleportToHostOnJoinEnabled; Vector3 val = Vector3.zero; Quaternion val2 = Quaternion.identity; bool flag = false; if (teleportToHostOnJoinEnabled) { PlayerAvatar val3 = PickAliveAnchorAvatar(); if ((Object)(object)val3 != (Object)null) { Transform val4 = (((Object)(object)val3.playerTransform != (Object)null) ? val3.playerTransform : ((Component)val3).transform); if ((Object)(object)val4 != (Object)null) { Vector3 forward = val4.forward; forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude < 0.001f) { forward = Vector3.forward; } else { ((Vector3)(ref forward)).Normalize(); } val = val4.position + forward * 3f; val2 = Quaternion.LookRotation(-forward); flag = true; } } } SpawnPoint[] array = null; int num = 0; int num2 = 0; foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { object obj; if (player == null) { obj = null; } else { PhotonView photonView = player.photonView; obj = ((photonView != null) ? photonView.Owner : null); } if (obj == null || !Plugin.TryGetLateJoinEntryByActor(player.photonView.Owner.ActorNumber, out var entry)) { continue; } Plugin.LateJoinState state = entry.State; if (state != Plugin.LateJoinState.Pending && state != Plugin.LateJoinState.Queued && state != Plugin.LateJoinState.Processing) { continue; } Vector3 val5; Quaternion val6; if (flag) { val5 = val; val6 = val2; } else { if (array == null) { array = Object.FindObjectsOfType<SpawnPoint>(); } if (array.Length == 0) { return false; } SpawnPoint obj2 = array[num % array.Length]; val5 = ((Component)obj2).transform.position; val6 = ((Component)obj2).transform.rotation; num++; } try { player.Spawn(val5, val6); entry.SpawnPosition = val5; num2++; } catch { } } Plugin.Verbose(string.Format("Late joiner spawn: {0} player(s), {1}.", num2, flag ? "teleported to anchor" : "spawn point")); } catch { } return false; } private static bool IsAvatarAlive(PlayerAvatar av) { if ((Object)(object)av == (Object)null) { return false; } return !Plugin.IsAvatarReallyDead(av); } private static PlayerAvatar PickAliveAnchorAvatar() { if (GameDirector.instance?.PlayerList == null) { return null; } Player masterClient = PhotonNetwork.MasterClient; int num = ((masterClient != null) ? masterClient.ActorNumber : (-1)); PlayerAvatar val = null; PlayerAvatar val2 = null; foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { object obj; if (player == null) { obj = null; } else { PhotonView photonView = player.photonView; obj = ((photonView != null) ? photonView.Owner : null); } if (obj != null && IsAvatarAlive(player)) { if (player.photonView.Owner.ActorNumber == num) { val = player; } else if ((Object)(object)val2 == (Object)null) { val2 = player; } } } return val ?? val2; } 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); } internal static void NetworkConnectOnJoinedRoomPostfix() { try { MapSync.OnJoinedRoom(); Plugin.AnnouncePresenceAndRescan(); } catch { } Plugin.TryJoinSteamLobbyFromPhotonRoom(); } internal static void NetworkManagerOnDisconnectedPostfix() { try { Plugin.ResetCoopState(); } catch { } } private static FieldRef<T, F> TFR<T, F>(string name) { try { return (AccessTools.Field(typeof(T), name) != null) ? AccessTools.FieldRefAccess<T, F>(name) : null; } catch { return null; } } internal static bool GumballValuableUpdateHypnosisLinesListPrefix(GumballValuable __instance) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.IsHookActive() || (Object)(object)__instance == (Object)null || _gumLines == null || _gumPrefab == null) { return true; } try { List<GameObject> list = _gumLines.Invoke(__instance); List<PlayerAvatar> list2 = SemiFunc.PlayerGetList(); if (list == null || list2 == null) { return true; } int count = list2.Count; int count2 = list.Count; if (count <= count2) { return true; } GameObject val = _gumPrefab.Invoke(__instance); if ((Object)(object)val == (Object)null) { return false; } Transform val2 = ((_gumParent != null) ? _gumParent.Invoke(__instance) : null); for (int i = count2; i < count; i++) { GameObject val3 = Object.Instantiate<GameObject>(val, ((Component)__instance).transform.position, Quaternion.identity); if ((Object)(object)val2 != (Object)null) { val3.transform.SetParent(val2); } val3.SetActive(false); list.Add(val3); } if (_gumPlayerInSight != null) { List<bool> list3 = _gumPlayerInSight.Invoke(__instance); if (list3 != null) { while (list3.Count < count) { list3.Add(item: false); } } } if (_gumPreviouslySeen != null) { List<bool> list4 = _gumPreviouslySeen.Invoke(__instance); if (list4 != null) { while (list4.Count < count) { list4.Add(item: false); } } } } catch { } return true; } internal static bool GumballValuableClientEffectsLoopPrefix(GumballValuable __instance) { if ((Object)(object)__instance == (Object)null) { return true; } try { if (_gumLines == null || _gumPlayerCount == null) { return true; } List<GameObject> list = _gumLines.Invoke(__instance); int num = _gumPlayerCount.Invoke(__instance); if (list != null && num > list.Count) { return false; } if (_gumPlayerInSight != null) { List<bool> list2 = _gumPlayerInSight.Invoke(__instance); if (list2 != null && num > list2.Count) { return false; } } } catch { } 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) if (!Plugin.IsHookActive()) { return true; } try { Player sender = _info.Sender; if (sender != null) { if (sender.IsMasterClient) { __result = true; return false; } if ((Object)(object)_photonView != (Object)null && (_photonView.OwnerActorNr == sender.ActorNumber || _photonView.CreatorActorNr == sender.ActorNumber)) { __result = true; return false; } } } catch { } return true; } internal static bool PlayerAvatarLoadingLevelAnimationCompletedRPCPrefix(PlayerAvatar __instance) { if (!Plugin.IsHookActive() || (Object)(object)__instance == (Object)null || _animCompleted == null) { return true; } if (!PhotonNetwork.IsMasterClient) { return true; } if (!IsMainState()) { return true; } try { if (!_animCompleted.Invoke(__instance)) { _animCompleted.Invoke(__instance) = true; } } catch { } return false; } internal static bool LoadingUILevelAnimationStartPrefix(LoadingUI __instance) { if (!Plugin.IsHookActive() || (Object)(object)__instance == (Object)null || (Object)(object)LevelGenerator.Instance?.Level != (Object)null) { return true; } if (PhotonNetwork.CurrentRoom == null) { return true; } try { if (_uiStarted != null) { _uiStarted.Invoke(__instance) = true; } if (_uiCompleted != null) { _uiCompleted.Invoke(__instance) = true; } try { PlayerController instance = PlayerController.instance; if (instance != null) { PlayerAvatar playerAvatarScript = instance.playerAvatarScript; if (playerAvatarScript != null) { playerAvatarScript.LoadingLevelAnimationCompleted(); } } } catch { } return false; } catch { } return true; } internal static void PlayerAvatarAwakeSetOwnershipPostfix(PlayerAvatar __instance) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance?.photonView == (Object)null || !PhotonNetwork.IsMasterClient) { return; } try { __instance.photonView.OwnershipTransfer = (OwnershipOption)1; } catch { } } internal static void PlayerAvatarOnDestroyPostfix(PlayerAvatar __instance) { try { if ((Object)(object)PlayerAvatar.instance == (Object)(object)__instance) { PlayerAvatar.instance = null; } } catch { } if (!PhotonNetwork.IsMasterClient || !PhotonNetwork.InRoom || !((Object)(object)__instance != (Object)null)) { return; } try { if (Plugin._playerDeathHeadRef != null) { PlayerDeathHead val = Plugin._playerDeathHeadRef.Invoke(__instance); if ((Object)(object)val != (Object)null) { PhotonView component = ((Component)val).GetComponent<PhotonView>(); if ((Object)(object)component != (Object)null) { if (component.IsMine) { PhotonNetwork.Destroy(((Component)val).gameObject); Plugin.Verbose("[AvatarOnDestroy] Physically destroyed PlayerDeathHead associated with Avatar."); } else { PhotonNetwork.RemoveBufferedRPCs(component.ViewID, (string)null, (int[])null); } } } } if (Plugin._playerTumbleRef != null) { PlayerTumble val2 = Plugin._playerTumbleRef.Invoke(__instance); if ((Object)(object)val2 != (Object)null) { PhotonView component2 = ((Component)val2).GetComponent<PhotonView>(); if ((Object)(object)component2 != (Object)null) { if (component2.IsMine) { PhotonNetwork.Destroy(((Component)val2).gameObject); Plugin.Verbose("[AvatarOnDestroy] Physically destroyed PlayerTumble associated with Avatar."); } else { PhotonNetwork.RemoveBufferedRPCs(component2.ViewID, (string)null, (int[])null); } } } } } catch (Exception ex) { Plugin.Verbose("[AvatarOnDestroy] Failed to destroy fallback corpse component: " + ex.Message); } try { PhotonView photonView = __instance.photonView; if ((Object)(object)photonView != (Object)null && photonView.ViewID != 0) { PhotonNetwork.RemoveBufferedRPCs(photonView.ViewID, (string)null, (int[])null); Plugin.Verbose($"[AvatarOnDestroy] Cleared buffered RPCs for Avatar ViewID {photonView.ViewID}."); } PlayerCosmetics componentInChildren = ((Component)__instance).GetComponentInChildren<PlayerCosmetics>(); PhotonView val3 = ((componentInChildren != null) ? ((Component)componentInChildren).GetComponent<PhotonView>() : null); if ((Object)(object)val3 != (Object)null && val3.ViewID != 0) { PhotonNetwork.RemoveBufferedRPCs(val3.ViewID, (string)null, (int[])null); Plugin.Verbose($"[AvatarOnDestroy] Cleared buffered RPCs for Cosmetics ViewID {val3.ViewID}."); } } catch (Exception ex2) { Plugin.Verbose("[AvatarOnDestroy] Failed to clear buffered RPCs: " + ex2.Message); } } internal static bool EnergyUIUpdatePrefix() { if ((Object)(object)PlayerController.instance != (Object)null) { return (Object)(object)PlayerAvatar.instance != (Object)null; } return false; } internal static bool PlayerAvatarVisualsUpdatePrefix(PlayerAvatarVisuals __instance) { if ((Object)(object)__instance == (Object)null) { return false; } if (!__instance.isMenuAvatar && (Object)(object)LevelGenerator.Instance == (Object)null) { return false; } if (__instance.isMenuAvatar && (Object)(object)__instance.playerAvatar == (Object)null && (Object)(object)PlayerAvatar.instance == (Object)null) { return false; } if (!__instance.isMenuAvatar && (Object)(object)__instance.playerAvatar == (Object)null) { return false; } PlayerAvatar val = (((Object)(object)__instance.playerAvatar != (Object)null) ? __instance.playerAvatar : PlayerAvatar.instance); if ((Object)(object)val != (Object)null && (Object)(object)val.localCamera == (Object)null) { return false; } return true; } internal static bool PlayerExpressionUpdatePrefix(PlayerExpression __instance) { if ((Object)(object)__instance == (Object)null) { return false; } if ((Object)(object)LevelGenerator.Instance == (Object)null) { bool flag = false; try { flag = SemiFunc.MenuLevel(); } catch { } if (!flag) { return false; } } if (!Object.op_Implicit((Object)(object)__instance.playerAvatar) && (Object)(object)PlayerAvatar.instance == (Object)null) { return false; } return true; } internal static bool PlayerAvatarTalkAnimationUpdatePrefix(PlayerAvatarTalkAnimation __instance) { if ((Object)(object)__instance == (Object)null) { return false; } if ((Object)(object)__instance.playerAvatar == (Object)null && (Object)(object)PlayerAvatar.instance == (Object)null) { return false; } if (_talkAnimVisualsRef != null) { try { if ((Object)(object)_talkAnimVisualsRef.Invoke(__instance) == (Object)null) { return false; } } catch { return false; } } return true; } internal static bool PlayerLocalCameraGetOverrideTransformPrefix(PlayerLocalCamera __instance, ref Transform __result) { if ((Object)(object)__instance == (Object)null) { __result = null; return false; } if ((Object)(object)__instance.playerAvatar == (Object)null) { __result = ((Component)__instance).transform; return false; } if ((Object)(object)CameraPosition.instance == (Object)null) { __result = ((Component)__instance).transform; return false; } if ((Object)(object)__instance.playerAvatar.PlayerVisionTarget == (Object)null) { __result = ((Component)__instance).transform; return false; } return true; } internal static bool PlayerAvatarOverchargeVisualsUpdatePrefix(PlayerAvatarOverchargeVisuals __instance) { if ((Object)(object)__instance == (Object)null) { return false; } try { if (_overchargePlayerAvatarRef != null && (Object)(object)_overchargePlayerAvatarRef.Invoke(__instance) == (Object)null) { return false; } if (_overchargeBeamOriginRef != null && (Object)(object)_overchargeBeamOriginRef.Invoke(__instance) == (Object)null) { return false; } } catch { return false; } return true; } internal static bool PlayerAvatarGrabDisableVisualsUpdatePrefix(PlayerAvatarGrabDisableVisuals __instance) { if ((Object)(object)__instance == (Object)null) { return false; } try { if (_grabDisablePlayerAvatarRef != null && (Object)(object)_grabDisablePlayerAvatarRef.Invoke(__instance) == (Object)null) { return false; } if (_grabDisableBeamOriginRef != null && (Object)(object)_grabDisableBeamOriginRef.Invoke(__instance) == (Object)null) { return false; } } catch { return false; } return true; } internal static bool PhysGrabberUpdatePrefix(PhysGrabber __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.playerAvatar == (Object)null) { return false; } if ((Object)(object)__instance.playerAvatar.healthGrab == (Object)null) { return false; } if ((Object)(object)__instance.playerAvatar.localCamera == (Object)null) { return false; } if (__instance.isLocal) { if ((Object)(object)PlayerController.instance == (Object)null) { return false; } if ((Object)(object)LevelGenerator.Instance == (Object)null) { return false; } } return true; } internal static bool PlayerAvatarLeftArmUpdatePrefix(PlayerAvatarLeftArm __instance) { if ((Object)(object)__instance == (Object)null) { return false; } if ((Object)(object)__instance.playerAvatar == (Object)null) { return false; } if (_leftArmVisualsRef != null) { try { if ((Object)(object)_leftArmVisualsRef.Invoke(__instance) == (Object)null) { return false; } } catch { return false; } } if ((Object)(object)__instance.flashlightController == (Object)null) { return false; } if ((Object)(object)__instance.playerAvatar.localCamera == (Object)null) { return false; } if ((Object)(object)__instance.playerAvatar.playerHealth == (Object)null) { return false; } if ((Object)(object)__instance.playerAvatar.playerAvatarVisuals == (Object)null) { return false; } return true; } internal static bool EnsureInventoryReady() { try { Inventory val = Inventory.instance; if ((Object)(object)val == (Object)null) { val = Object.FindObjectOfType<Inventory>(true); if ((Object)(object)val != (Object)null) { Inventory.instance = val; } } if ((Object)(object)val == (Object)null) { return false; } List<InventorySpot> list = ((_inventorySpotsRef != null) ? _inventorySpotsRef.Invoke(val) : val.GetAllSpots()); if (list == null) { list = new List<InventorySpot>(); if (_inventorySpotsRef != null) { _inventorySpotsRef.Invoke(val) = list; } } while (list.Count < 3) { list.Add(null); } bool flag = false; for (int i = 0; i < 3; i++) { if ((Object)(object)list[i] == (Object)null) { flag = true; break; } } if (flag) { InventorySpot[] array = Object.FindObjectsOfType<InventorySpot>(true); if (array != null && array.Length != 0) { InventorySpot[] array2 = array; foreach (InventorySpot val2 in array2) { if ((Object)(object)val2 != (Object)null && val2.inventorySpotIndex >= 0 && val2.inventorySpotIndex < 3) { list[val2.inventorySpotIndex] = val2; } } } } if (_inventoryPhysGrabberRef != null && (Object)(object)_inventoryPhysGrabberRef.Invoke(val) == (Object)null && (Object)(object)PhysGrabber.instance != (Object)null) { _inventoryPhysGrabberRef.Invoke(val) = PhysGrabber.instance; } if (_inventoryPlayerAvatarRef != null && (Object)(object)_inventoryPlayerAvatarRef.Invoke(val) == (Object)null && (Object)(object)PlayerAvatar.instance != (Object)null) { _inventoryPlayerAvatarRef.Invoke(val) = PlayerAvatar.instance; } bool flag2 = true; for (int k = 0; k < 3; k++) { if ((Object)(object)list[k] == (Object)null) { flag2 = false; break; } } if (flag2 && _inventorySpotsFeched != null) { _inventorySpotsFeched.Invoke(val) = true; } return flag2; } catch (Exception ex) { Plugin.Verbose("[EnsureInventoryReady] Error: " + ex.Message); return false; } } private static bool IsInventoryReady() { if ((Object)(object)Inventory.instance == (Object)null) { return false; } return EnsureInventoryReady(); } internal static bool ItemEquippableUpdateItemStatePrefix(ItemEquippable __instance, int state, int spotIndex, int ownerId, PhotonMessageInfo _info) { if ((Object)(object)__instance == (Object)null || !SemiFunc.IsMultiplayer()) { return true; } EnsureInventoryReady(); if (state == 2 && (Object)(object)PhysGrabber.instance != (Object)null && (Object)(object)PhysGrabber.instance.photonView != (Object)null && PhysGrabber.instance.photonView.ViewID == ownerId && spotIndex >= 0 && spotIndex < 3) { Inventory instance = Inventory.instance; InventorySpot val = ((instance != null) ? instance.GetSpotByIndex(spotIndex) : null); if ((Object)(object)val == (Object)null) { InventorySpot[] array = Object.FindObjectsOfType<InventorySpot>(true); if (array != null) { InventorySpot[] array2 = array; foreach (InventorySpot val2 in array2) { if (!((Object)(object)val2 != (Object)null) || val2.inventorySpotIndex != spotIndex) { continue; } val = val2; if (!((Object)(object)Inventory.instance != (Object)null)) { break; } List<InventorySpot> list = ((_inventorySpotsRef != null) ? _inventorySpotsRef.Invoke(Inventory.instance) : null); if (list != null) { while (list.Count < 3) { list.Add(null); } list[spotIndex] = val; } break; } } } if ((Object)(object)val != (Object)null && (Object)(object)val.CurrentItem != (Object)(object)__instance) { if (_spotStateRef != null) { _spotStateRef.Invoke(val) = (SpotState)0; } if (_spotSetCurrentItemProp != null) { _spotSetCurrentItemProp.Invoke(val, new object[1]); } } } return true; } internal static void ItemEquippableUpdateItemStatePostfix(ItemEquippable __instance, int state, int spotIndex, int ownerId) { //IL_0201: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || !SemiFunc.IsMultiplayer() || !((Object)(object)PhysGrabber.instance != (Object)null) || !((Object)(object)PhysGrabber.instance.photonView != (Object)null) || PhysGrabber.instance.photonView.ViewID != ownerId || state != 2 || spotIndex < 0 || spotIndex >= 3) { return; } EnsureInventoryReady(); Inventory instance = Inventory.instance; InventorySpot val = ((instance != null) ? instance.GetSpotByIndex(spotIndex) : null); if ((Object)(object)val == (Object)null) { InventorySpot[] array = Object.FindObjectsOfType<InventorySpot>(true); if (array != null) { InventorySpot[] array2 = array; foreach (InventorySpot val2 in array2) { if ((Object)(object)val2 != (Object)null && val2.inventorySpotIndex == spotIndex) { val = val2; break; } } } } InventorySpot val3 = ((_itemEquippedSpotRef != null) ? _itemEquippedSpotRef.Invoke(__instance) : null); if ((Object)(object)val != (Object)null) { if ((Object)(object)val3 == (Object)null || (Object)(object)val.CurrentItem != (Object)(object)__instance) { Plugin.LogInfo($"[ItemEquippable] Safety net: Binding equippedSpot for {((Object)__instance).name} on slot {spotIndex}!"); if (_itemEquippedSpotRef != null) { _itemEquippedSpotRef.Invoke(__instance) = val; } SetSpotItem(val, __instance); } PlayerAvatar instance2 = PlayerAvatar.instance; if ((Object)(object)instance2 != (Object)null && (Object)(object)instance2.physGrabber != (Object)null && _physGrabbedObjectRef != null) { PhysGrabObject component = ((Component)__instance).GetComponent<PhysGrabObject>(); if ((Object)(object)component != (Object)null && (Object)(object)_physGrabbedObjectRef.Invoke(instance2.physGrabber) == (Object)(object)component) { instance2.physGrabber.OverrideGrab((PhysGrabObject)null, 0f, false); instance2.physGrabber.ReleaseObject(-1, 0.1f); } } } else { Plugin.LogError($"[ItemEquippable] CRITICAL: Spot {spotIndex} not found! Recovering item {((Object)__instance).name} so it does not vanish!"); if (_itemEquippableStateRef != null) { _itemEquippableStateRef.Invoke(__instance) = (ItemState)0; } PhysGrabObject component2 = ((Component)__instance).GetComponent<PhysGrabObject>(); if ((Object)(object)component2 != (Object)null) { component2.OverrideDeactivateReset(); ((Component)component2).transform.localScale = Vector3.one; } } } internal static bool ItemEquippableCompleteUnequipPrefix(ItemEquippable __instance, int physGrabberPhotonViewID) { if (!SemiFunc.IsMultiplayer()) { return true; } if (physGrabberPhotonViewID <= 0) { return true; } PhotonView val = PhotonView.Find(physGrabberPhotonViewID); if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).GetComponent<PhysGrabber>() == (Object)null) { Plugin.Verbose($"[ItemEquippable] CompleteUnequip skipped: invalid physGrabberPhotonViewID {physGrabberPhotonViewID}"); return false; } return true; } internal static bool InventoryUIUpdatePrefix(InventoryUI __instance) { if ((Object)(object)LevelGenerator.Instance == (Object)null || !LevelGenerator.Instance.Generated) { return false; } return true; } internal static void InventorySpotHandleInputPrefix(InventorySpot __instance) { EnsureInventoryReady(); } internal static void InventorySpotAttemptEquipItemPrefix(InventorySpot __instance) { EnsureInventoryReady(); } internal static void InventorySpotStartPostfix(InventorySpot __instance) { EnsureInventoryReady(); } internal static void PlayerVoiceChatStartPostfix(PlayerVoiceChat __instance) { if (!Plugin.IsHookActive() || (Object)(object)__instance == (Object)null) { return; } try { PhotonView component = ((Component)__instance).GetComponent<PhotonView>(); if (!((Object)(object)component == (Object)null) && !component.IsMine) { bool flag = true; try { flag = SemiFunc.MenuLevel(); } catch { } bool flag2 = false; try { flag2 = SemiFunc.RunIsLobby() || SemiFunc.RunIsShop() || SemiFunc.RunIsLevel(); } catch { } if (!flag && flag2) { __instance.ToggleMixer(false, false); } } } catch { } } internal static void EnemyOnScreenAwakePostfix(EnemyOnScreen __instance) { if (!Plugin.IsHookActive() || (Object)(object)__instance == (Object)null || GameDirector.instance?.PlayerList == null || !PhotonNetwork.IsMasterClient) { return; } try { foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if ((Object)(object)player?.photonView != (Object)null) { __instance.PlayerAdded(player.photonView.ViewID); } } } catch { } } internal static void ExtractionPointUpdateLockPostfix(ExtractionPoint __instance, bool locked) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown if (!Plugin.IsHookActive() || (Object)(object)__instance == (Object)null || !PhotonNetwork.IsMasterClient || PhotonNetwork.CurrentRoom == null) { return; } try { PhotonView component = ((Component)__instance).GetComponent<PhotonView>(); if (!((Object)(object)component == (Object)null) && component.ViewID != 0) { int viewID = component.ViewID; if (!_epLastLockedState.TryGetValue(viewID, out var value) || value != locked) { _epLastLockedState[viewID] = locked; object[] array = new object[2] { viewID, locked }; PhotonNetwork.RaiseEvent((byte)171, (object)array, new RaiseEventOptions { Receivers = (ReceiverGroup)0 }, SendOptions.SendReliable); } } } catch (Exception ex) { Plugin.Verbose("ExtractionPoint lock broadcast failed: " + ex.Message); } } internal static void ModuleAwakePostfix(Module __instance) { } internal static bool ModuleStartPrefix(Module __instance) { if ((Object)(object)__instance == (Object)null) { return false; } try { if ((Object)(object)((Component)__instance).GetComponent<StartRoom>() != (Object)null) { Plugin.SetModuleStartRoom(__instance, val: true); return false; } if ((Object)(object)LevelGenerator.Instance != (Object)null && (Object)(object)LevelGenerator.Instance.LevelParent != (Object)null) { ((Component)__instance).transform.parent = LevelGenerator.Instance.LevelParent.transform; } } catch { } return false; } internal static void ModuleStartPostfix(Module __instance) { if (PhotonNetwork.IsMasterClient || (Object)(object)__instance == (Object)null) { return; } try { Plugin.OnClientModuleSpawned(__instance); } catch { } } internal static bool ModulePropSwitchSetupPrefix(ModulePropSwitch __instance) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Invalid comparison between Unknown and I4 //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Invalid comparison between Unknown and I4 //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Invalid comparison between Unknown and I4 //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Expected O, but got Unknown //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Invalid comparison between Unknown and I4 //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Expected O, but got Unknown if ((Object)(object)__instance == (Object)null) { return false; } try { Module val = ((Plugin._propSwitchModuleRef != null) ? Plugin._propSwitchModuleRef.Invoke(__instance) : null); if ((Object)val == (Object)null) { val = ((Component)__instance).GetComponentInParent<Module>(); if (Plugin._propSwitchModuleRef != null && (Object)val != (Object)null) { Plugin._propSwitchModuleRef.Invoke(__instance) = val; } } if ((Object)val == (Object)null) { return false; } if (!_initialPropSwitchSides.TryGetValue(__instance, out var value)) { value = new InitialSide(__instance.ConnectionSide); _initialPropSwitchSides.Add(__instance, value); } Quaternion localRotation = ((Component)val).transform.localRotation; int num = Mathf.RoundToInt(((Quaternion)(ref localRotation)).eulerAngles.y) % 360; if (num < 0) { num += 360; } int num2 = (num + 45) / 90; Connection val2 = (__instance.ConnectionSide = (Connection)((value.Side + num2) % 4)); bool flag = Plugin._modTop != null && Plugin._modTop.Invoke(val); bool flag2 = Plugin._modRight != null && Plugin._modRight.Invoke(val); bool flag3 = Plugin._modBottom != null && Plugin._modBottom.Invoke(val); bool flag4 = Plugin._modLeft != null && Plugin._modLeft.Invoke(val); bool flag5 = false; if ((int)val2 == 0 && flag) { flag5 = true; } else if ((int)val2 == 1 && flag2) { flag5 = true; } else if ((int)val2 == 2 && flag3) { flag5 = true; } else if ((int)val2 == 3 && flag4) { flag5 = true; } if ((Object)__instance.NotConnectedParent != (Object)null) { __instance.NotConnectedParent.SetActive(!flag5); } if ((Object)__instance.ConnectedParent != (Object)null) { __instance.ConnectedParent.SetActive(flag5); } } catch (Exception ex) { Plugin.Verbose("[ModuleFix] ModulePropSwitch.SetupPrefix error: " + ex.Message); } return false; } internal static void PhysGrabObjectAwakePostfix(PhysGrabObject __instance) { if ((Object)(object)__instance == (Object)null) { return; } if (PhotonNetwork.IsMasterClient && Plugin.IsHookActive()) { _itemsToTrack.Add(((Component)__instance).gameObject); if (!_itemTrackingCoroutineRunning) { _itemTrackingCoroutineRunning = true; try { ((MonoBehaviour)Plugin.Instance).StartCoroutine(TrackItemsDelayedBatch()); } catch { } } } try { Plugin.TryForceSpawnSingle(__instance); } catch { } } internal static bool PlayerAvatarUpdateMyPlayerVoiceChatPrefix(PlayerAvatar __instance, int photonViewID) { if (!Plugin.IsHookActive()) { return true; } try { PhotonView val = PhotonView.Find(photonViewID); if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).GetComponent<PlayerVoiceChat>() == (Object)null) { Plugin.Verbose($"[UpdateMyPlayerVoiceChat] Local Voice ViewID {photonViewID} not found, intercepting and waiting for retry."); return false; } } catch { return false; } return true; } internal static bool RunManagerRestartScenePrefix(RunManager __instance) { if ((Object)(object)__instance == (Object)null || !Plugin.IsHookActive()) { return true; } if (!PhotonNetwork.IsMasterClient || !GameManager.Multiplayer()) { return true; } bool num = _rmRestartingRef.Invoke(__instance); bool flag = _rmRestartingDoneRef.Invoke(__instance); if (!num) { _rmRestartingRef.Invoke(__instance) = true; _restartSceneTimer = 0f; if ((Object)(object)GameDirector.instance == (Object)null) { return true; } foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if ((Object)(object)player != (Object)null) { try { player.OutroStart(); } catch { } } } return false; } if (flag) { return false; } _restartSceneTimer += Time.deltaTime; bool flag2 = true; if ((Object)(object)GameDirector.instance == (Object)null) { flag2 = false; } else { foreach (PlayerAvatar player2 in GameDirector.instance.PlayerList) { if (!((Object)(object)player2 == (Object)null) && (!((Object)(object)player2.photonView != (Object)null) || player2.photonView.Owner == null || !Plugin.TryGetLateJoinEntryByActor(player2.photonView.Owner.ActorNumber, out var entry) || entry.State == Plugin.LateJoinState.Done) && !_avatarOutroDoneRef.Invoke(player2)) { flag2 = false; break; } } } if (!flag2 && _restartSceneTimer >= 15f) { Plugin.LogInfo($"Stuck in Outro state for {_restartSceneTimer:F1}s, forcing level transition!"); flag2 = true; } if (!flag2) { return false; } _rmRestartingDoneRef.Invoke(__instance) = true; bool flag3 = false; if (_rmGameOverRef.Invoke(__instance)) { try { NetworkManager.instance.DestroyAll(); } catch { } _rmGameOverRef.Invoke(__instance) = false; flag3 = true; } bool num2 = _rmLobbyJoinRef.Invoke(__instance); bool flag4 = _rmWaitToChangeSceneRef.Invoke(__instance); if (num2) { _rmLobbyJoinRef.Invoke(__instance) = false; try { SceneManager.LoadSceneAsync("LobbyJoin"); } catch { } } else if (!flag4) { if (!flag3 && SemiFunc.RunIsLobbyMenu()) { try { NetworkManager.instance.DestroyAll(); } catch { } } if (!GameManager.Multiplayer()) { try { SceneManager.LoadSceneAsync("Main"); } catch { } } else if (PhotonNetwork.IsMasterClient) { try { PhotonNetwork.LoadLevel("Reload"); } catch { } } } return false; } internal static IEnumerable<CodeInstruction> SlowProjectileDestroyRPCTranspiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); MethodInfo methodInfo = AccessTools.Method(typeof(Object), "Destroy", new Type[1] { typeof(Object) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(Plugin), "SafeDestroySlowProjectile", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { Plugin.Verbose("SlowProjectile.DestroyRPC Transpiler: Target or replacement method not found!"); return list; } bool flag = false; for (int i = 0; i < list.Count; i++) { if (list[i].opcode == OpCodes.Call && list[i].operand is MethodInfo methodInfo3 && methodInfo3 == methodInfo) { list[i].operand = methodInfo2; flag = true; Plugin.Verbose("SlowProjectile.DestroyRPC Transpiler: Replaced Object.Destroy call at index " + i); } } if (!flag) { Plugin.Verbose("SlowProjectile.DestroyRPC Transpiler: Target call not found!"); } return list; } internal static void ItemAttributesAwakePostfix(ItemAttributes __instance) { if ((Object)(object)__instance == (Object)null || !Plugin.IsHookActive() || !PhotonNetwork.IsMasterClient) { return; } _itemsToTrack.Add(((Component)__instance).gameObject); if (_itemTrackingCoroutineRunning) { return; } _itemTrackingCoroutineRunning = true; try { ((MonoBehaviour)Plugin.Instance).StartCoroutine(TrackItemsDelayedBatch()); } catch { } } internal static void ValuableObjectAwakePostfix(ValuableObject __instance) { if ((Object)(object)__instance == (Object)null || !Plugin.IsHookActive() || !PhotonNetwork.IsMasterClient) { return; } _itemsToTrack.Add(((Component)__instance).gameObject); if (_itemTrackingCoroutineRunning) { return; } _itemTrackingCoroutineRunning = true; try { ((MonoBehaviour)Plugin.Instance).StartCoroutine(TrackItemsDelayedBatch()); } catch { } } internal static IEnumerator TrackItemsDelayedBatch() { yield return null; _itemTrackingCoroutineRunning = false; if (!Plugin.IsHookActive() || !PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient) { _itemsToTrack.Clear(); yield break; } int num = 0; try { for (int i = 0; i < _itemsToTrack.Count; i++) { GameObject val = _itemsToTrack[i]; if ((Object)(object)val == (Object)null) { continue; } try { PhotonView component = val.GetComponent<PhotonView>(); if ((Object)(object)component != (Object)null && component.InstantiationId != 0) { _trackedItems[component.ViewID] = component.InstantiationId; num++; } } catch { } } } catch { } finally { _itemsToTrack.Clear(); } if (num > 0) { Plugin.Verbose($"[TrackItemsDelayedBatch] Batch registered tracking for {num} scene item(s)."); } } internal static void PhysGrabObjectDestroyPhysGrabObjectRPCPrefix(PhysGrabObject __instance, PhotonMessageInfo _info) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)RunManager.instance == (Object)null) && !((Object)(object)__instance == (Object)null) && Plugin.IsHookActive() && PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient && SemiFunc.MasterOnlyRPC(_info)) { PhotonView component = ((Component)__instance).GetComponent<PhotonView>(); if ((Object)(object)component != (Object)null && component.InstantiationId != 0) { _trackedItems.Remove(component.ViewID); Plugin.ClearInstantiationCacheById(component.InstantiationId); Plugin.Verbose($"[DestroyPhysGrabObjectRPC] Cleared cache on destroy: ViewID={component.ViewID}, InstantiationId={component.InstantiationId}, Name={((Object)((Component)__instance).gameObject).name}"); } } } catch (Exception ex) { Plugin.Verbose("[DestroyPhysGrabObjectRPC] Failed to clean up cache on destroy: " + ex.Message); } } internal static void PlayerAvatarJumpRPCPrefix(PlayerAvatar __instance) { if ((Object)(object)__instance == (Object)null || !Plugin.IsHookActive() || !PhotonNetwork.IsMasterClient) { return; } try { PhotonView photonView = __instance.photonView; if ((Object)(object)photonView != (Object)null && photonView.Owner != null) { Plugin.NotifyPlayerJumped(photonView.Owner.ActorNumber); } } catch { } } internal static void RunManagerUpdateLevelPostfix() { MapSync.OnUpdateLevel(); } internal static void LevelGeneratorGenerateDonePostfix(LevelGenerator __instance) { MapSync.OnGenerateDone(__instance); } internal static bool ModuleConnectionSetRPCPrefix(Module __instance) { return LevelRepair.ModuleConnectionSetRPCPrefix(__instance); } } internal static class LevelRepair { internal static bool Enabled = true; private const float Spacing = 15f; private const float Tolerance = 2f; internal static bool ModuleConnectionSetRPCPrefix(Module __instance) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown if (!Enabled) { return true; } try { return (Object)__instance == (Object)null || Plugin._modDone == null || !Plugin._modDone.Invoke(__instance); } catch { return true; } } internal static IEnumerator Routine() { yield return (object)new WaitForSeconds(6f); int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; try { List<Module> list = new List<Module>(); List<Vector3> list2 = new List<Vector3>(); Module[] array = Object.FindObjectsOfType<Module>(true); foreach (Module val in array) { if (!((Object)val == (Object)null)) { if ((Object)((Component)val).GetComponent<StartRoom>() != (Object)null) { list2.Add(((Component)val).transform.position); } else { list.Add(val); } } } foreach (Module item in list) { num++; if (Plugin._modDone != null && Plugin._modDone.Invoke(item)) { num2++; continue; } Vector3 position = ((Component)item).transform.position; bool flag = Mathf.Abs(position.x) < 2f && Mathf.Abs(position.z - 7.5f) < 2f; foreach (Vector3 item2 in list2) { if (Mathf.Abs(item2.x - position.x) < 2f && item2.z < position.z && position.z - item2.z < 23f) { flag = true; } } if (Plugin._modTop != null) { Plugin._modTop.Invoke(item) = HasNeighbour(list, item, position + new Vector3(0f, 0f, 15f)); } if (Plugin._modBottom != null) { Plugin._modBottom.Invoke(item) = HasNeighbour(list, item, position - new Vector3(0f, 0f, 15f)) || flag; } if (Plugin._modRight != null) { Plugin._modRight.Invoke(item) = HasNeighbour(list, item, position + new Vector3(15f, 0f, 0f)); } if (Plugin._modLeft != null) { Plugin._modLeft.Invoke(item) = HasNeighbour(list, item, position - new Vector3(15f, 0f, 0f)); } if (Plugin._modFirst != null) { Plugin._modFirst.Invoke(item) = flag; } if (Plugin._modDone != null) { Plugin._modDone.Invoke(item) = true; } ModulePropSwitch[] componentsInChildren = ((Component)item).GetComponentsInChildren<ModulePropSwitch>(true); foreach (ModulePropSwitch val2 in componentsInChildren) { if (Plugin._propSwitchModuleRef != null) { Plugin._propSwitchModuleRef.Invoke(val2) = item; } val2.Setup(); num4++; } num3++; } } catch (Exception e) { Suppressed.Report("Late-join level repair", e); } Plugin.LogInfo((num3 > 0) ? $"[Late-join level repair] Set up {num3} of {num} level pieces the host's setup never reached ({num4} wall/floor switches); {num2} were already set up." : $"[Late-join level repair] All {num} level pieces were already set up; nothing to repair."); } private static bool HasNeighbour(List<Module> all, Module self, Vector3 at) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_0022: Expected O, but got Unknown //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) foreach (Module item in all) { if (!((Object)item == (Object)self)) { Vector3 position = ((Component)item).transform.position; if (Mathf.Abs(position.x - at.x) < 2f && Mathf.Abs(position.z - at.z) < 2f) { return true; } } } return false; } } internal static class MapSync { internal static bool RevealAllFallback; internal static bool MapSyncEnabled = true; internal static bool LevelRepairEnabled = true; private const byte EventCode = 163; private const string Tag = "RW.MapSync"; private const float MatchDistance = 0.25f; private static readonly FieldRef<RoomVolume, bool> Explored = AccessTools.FieldRefAccess<RoomVolume, bool>("Explored"); private static bool _subscribed; private static string _lastRoomName; private static LevelGenerator _lastLevel; private static bool _lateJoinPending; private static float _joinedRoomAt = -1000f; private static bool _receivedThisLevel; private static bool _joinerRoutinesStarted; private static readonly List<Vector3> Pending = new List<Vector3>(); internal static void OnJoinedRoom() { _joinedRoomAt = Time.realtimeSinceStartup; } internal static void OnUpdateLevel() { try { if (PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient) { Room currentRoom = PhotonNetwork.CurrentRoom; string obj = ((currentRoom != null) ? currentRoom.Name : null); bool flag = obj != _lastRoomName; _lastRoomName = obj; if (flag && SemiFunc.RunIsLevel() && Time.realtimeSinceStartup - _joinedRoomAt < 15f) { _lateJoinPending = true; } } } catch (Exception e) { Suppressed.Report("Late-join map sync (join detect)", e); } } internal static void OnGenerateDone(LevelGenerator __instance) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_0023: Expected O, but got Unknown try { if (PhotonNetwork.InRoom && SemiFunc.IsMultiplayer() && !((Object)__instance == (Object)_lastLevel)) { _lastLevel = __instance; _joinerRoutinesStarted = false; EnsureSubscribed(); _receivedThisLevel = false; Pending.Clear(); if (_lateJoinPending && !PhotonNetwork.IsMasterClient && SemiFunc.RunIsLevel()) { _lateJoinPending = false; StartJoinerRoutines((MonoBehaviour)(object)__instance); } } } catch (Exception e) { Suppressed.Report("Late-join map sync (level done)", e); } } internal static void EnsureJoinerRoutinesStarted(MonoBehaviour runner) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown if (!_joinerRoutinesStarted && !PhotonNetwork.IsMasterClient && SemiFunc.RunIsLevel() && (Object)runner != (Object)null) { _lateJoinPending = false; StartJoinerRoutines(runner); } } private static void StartJoinerRoutines(MonoBehaviour runner) { if (!_joinerRoutinesStarted) { _joinerRoutinesStarted = true; EnsureSubscribed(); Plugin.LogInfo("[Late-join map sync] Joined mid-level" + (MapSyncEnabled ? "; waiting for the host's explored rooms." : ".")); if (MapSyncEnabled) { runner.StartCoroutine(JoinerRoutine()); } if (LevelRepairEnabled) { runner.StartCoroutine(LevelRepair.Routine()); } } } private static void EnsureSubscribed() { if (!_subscribed) { PhotonNetwork.NetworkingClient.EventReceived += OnEvent; _subscribed = true; } } internal static void OnRemotePlayerAvatarStart(PlayerAvatar avatar) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown try { if (MapSyncEnabled && PhotonNetwork.IsMasterClient && SemiFunc.RunIsLevel() && (Object)avatar != (Object)null && (Object)avatar.photonView != (Object)null && !avatar.photonView.IsMine && avatar.photonView.Owner != null && (Object)LevelGenerator.Instance != (Object)null && LevelGenerator.Instance.Generated) { GameDirector instance = GameDirector.instance; if ((Object)instance != (Object)null) { ((MonoBehaviour)instance).StartCoroutine(HostSend(avatar.photonView.Owner.ActorNumber)); } } } catch (Exception e) { Suppressed.Report("Late-join map sync (host)", e); } } private static IEnumerator HostSend(int actor) { float[] array = new float[2] { 4f, 8f }; float[] array2 = array; foreach (float delay in array2) { yield return (object)new WaitForSeconds(delay); if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient || PhotonNetwork.CurrentRoom == null || PhotonNetwork.CurrentRoom.GetPlayer(actor, false) == null) { break; } try { List<float> list = new List<float>(); RoomVolume[] array3 = Object.FindObjectsOfType<RoomVolume>(true); foreach (RoomVolume val in array3) { if ((Object)val != (Object)null && Explored != null && Explored.Invoke(val)) { Vector3 position = ((Component)val).transform.position; list.Add(position.x); list.Add(position.y); list.Add(position.z); } } object[] array4 = new object[2] { "RW.MapSync", list.ToArray() }; RaiseEventOptions val2 = new RaiseEventOptions(); val2.TargetActors = new int[1] { actor }; PhotonNetwork.RaiseEvent((byte)163, (object)array4, val2, SendOptions.SendReliable); if (delay == 4f) { Plugin.LogInfo($"[Late-join map sync] Sent {list.Count / 3} explored rooms to a player who joined mid-level."); } } catch (Exception e) { Suppressed.Report("Late-join map sync (send)", e); } } } private static void OnEvent(EventData ev) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) try { if (ev.Code != 163 || PhotonNetwork.IsMasterClient) { return; } Player masterClient = PhotonNetwork.MasterClient; if (masterClient != null && ev.Sender == masterClient.ActorNumber && ev.CustomData is object[] array && array.Length == 2 && array[0] is string text && !(text != "RW.MapSync") && array[1] is float[] array2) { _receivedThisLevel = true; for (int i = 0; i + 2 < array2.Length; i += 3) { Pending.Add(new Vector3(array2[i], array2[i + 1], array2[i + 2])); } } } catch (Exception e) { Suppressed.Report("Late-join map sync (receive)", e); } } private static IEnumerator JoinerRoutine() { float waited = 0f; while (((Object)LevelGenerator.Instance == (Object)null || !LevelGenerator.Instance.Generated) && waited < 60f) { yield return (object)new WaitForSeconds(0.5f); waited += 0.5f; } yield return (object)new WaitForSeconds(3f); int uncovered = 0; for (float t = 0f; t < 30f; t += 1f) { uncovered += ApplyPending(); if (!_receivedThisLevel && t >= 15f) { break; } yield return (object)new WaitForSeconds(1f); } if (_receivedThisLevel) { Plugin.LogInfo($"[Late-join map sync] Joined mid-level; the host's map was synced ({uncovered} rooms uncovered)."); yield break; } if (!RevealAllFallback) { Plugin.LogInfo("[Late-join map sync] Joined mid-level, but the host doesn't run Map Sync Fix, so its explored rooms can't be synced (RevealAllRoomsWhenHostHasNoMod is off)."); yield break; } int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; RoomVolume[] array = Object.FindObjectsOfType<RoomVolume>(true); foreach (RoomVolume val in array) { if (!((Object)val == (Object)null)) { num++; if (Explored != null && Explored.Invoke(val)) { num2++; } else if ((Object)val.MapModule == (Object)null) { num3++; } else if (val.SetExplored()) { num4++; } } } Plugin.LogInfo($"[Late-join map sync] Joined mid-level and the host doesn't run Map Sync Fix; uncovered the rest of the map (RevealAllRoomsWhenHostHasNoMod): {num4} rooms uncovered now, {num2} already uncovered, {num3} with no map cover (truck/extraction/start), {num} room volumes in total."); } private static int ApplyPending() { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown if (Pending.Count == 0) { return 0; } int num = 0; RoomVolume[] array = Object.FindObjectsOfType<RoomVolume>(true); for (int num2 = Pending.Count - 1; num2 >= 0; num2--) { bool flag = false; bool flag2 = true; RoomVolume[] array2 = array; foreach (RoomVolume val in array2) { if ((Object)val == (Object)null || Vector3.Distance(((Component)val).transform.position, Pending[num2]) > 0.25f) { continue; } flag = true; if (Explored != null && !Explored.Invoke(val)) { if ((Object)val.MapModule == (Object)null) { flag2 = false; } else if (val.SetExplored()) { num++; } } } if (flag && flag2) { Pending.RemoveAt(num2); } } return num; } } internal sealed class PeerCoopCallbackListener : IInRoomCallbacks, IOnEventCallback { private const byte ExtractionLockEventCode = 171; private const byte DeadPlayersSyncEventCode = 172; private const byte ModuleSyncEventCode = 173; public void OnEvent(EventData photonEvent) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Expected O, but got Unknown if (photonEvent.Code == 173 && !PhotonNetwork.IsMasterClient) { try { if (photonEvent.CustomData is int[] data) { Plugin.HandleModuleSyncEventClient(data); } return; } catch (Exception ex) { Plugin.Verbose("Failed to synchronize module connection data: " + ex.Message); return; } } if (photonEvent.Code == 172 && !PhotonNetwork.IsMasterClient) { try { if (photonEvent.CustomData is int[] array) { for (int i = 0; i < array.Length; i++) { PhotonView val = PhotonView.Find(array[i]); if ((Object)val != (Object)null) { PlayerAvatar component = ((Component)val).GetComponent<PlayerAvatar>(); if ((Object)component != (Object)null) { Plugin.MarkPlayerAvatarDeadClient(component); } } } } return; } catch (Exception ex2) { Plugin.Verbose("Failed to synchronize dead players: " + ex2.Message); return; } } if (photonEvent.Code != 171 || PhotonNetwork.IsMasterClient) { return; } try { if (!(photonEvent.CustomData is object[] array2) || array2.Length < 2) { return; } int num = (int)array2[0]; bool flag = (bool)array2[1]; PhotonView val2 = PhotonView.Find(num); if (!((Object)val2 == (Object)null)) { ExtractionPoint component2 = ((Component)val2).GetComponent<ExtractionPoint>(); if (!((Object)component2 == (Object)null)) { component2.UpdateLock(flag); Plugin.Verbose($"ExtractionPoint {num} lock state sync: {flag}"); } } } catch (Exception ex3) { Plugin.Verbose("ExtractionPoint lock sync failed: " + ex3.Message); } } public void OnPlayerEnteredRoom(Player newPlayer) { if (newPlayer != null) { Plugin.UpdateCoopStateFor(newPlayer); } } public void OnPlayerLeftRoom(Player otherPlayer) { if (otherPlayer != null) { Plugin.RemoveCoopActor(otherPlayer.ActorNumber); } } public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps) { if (targetPlayer == null || changedProps == null) { return; } bool flag = ((Dictionary<object, object>)(object)changedProps).ContainsKey("LateJoinNow.Version") || ((Dictionary<object, object>)(object)changedProps).ContainsKey("LateJoinNow.Protocol"); bool flag2 = ((Dictionary<object, object>)(object)changedProps).ContainsKey("LateJoinNow.Ready") || ((Dictionary<object, object>)(object)changedProps).ContainsKey("LateJoinNow.ModuleCount"); if (flag || flag2) { if (flag) { Plugin.UpdateCoopStateFor(targetPlayer); } if (flag2 && PhotonNetwork.IsMasterClient && targetPlayer.ActorNumber != PhotonNetwork.LocalPlayer.ActorNumber) { Plugin.RecordCoopReadyForListener(targetPlayer); } } } public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged) { } public void OnMasterClientSwitched(Player newMasterClient) { try { Plugin.HandleMasterClientSwitched(newMasterClient); } catch { } } } [BepInPlugin("com.repo.latejoinfixed", "LateJoinFixed", "1.3.0")] 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; } internal enum LateJoinState { Pending, Queued, Processing, Finalizing, Done, Aborted } internal sealed class LateJoinEntry { public Player Target; public int ActorNumber; public string NickName; public LateJoinState State; public string StartScene; public Vector3 SpawnPosition; } private delegate void OwnershipUpdateDelegate(int[] viewOwnerPairs, int targetActor); internal struct ModuleSyncData { public bool top; public bool bottom; public bool right; public bool left; public bool first; public ModuleSyncData(bool top, bool bottom, bool right, bool left, bool first) { this.top = top; this.bottom = bottom; this.right = right; this.left = left; this.first = first; } } public const string PluginGuid = "com.repo.latejoinfixed"; public const string PluginName = "LateJoinFixed"; public const string PluginVersion = "1.3.0"; internal static Plugin Instance; internal static Harmony harmony; private I18NRegistry _i18n; private I18NSwitcher _i18nSwitcher; private static ConfigEntry<bool> _cfgModEnabled; private static ConfigEntry<bool> _cfgPublicRoomPrefix; private static ConfigEntry<bool> _cfgTeleportToHostOnJoin; private static ConfigEntry<bool> _cfgCoopMode; private static ConfigEntry<bool> _cfgAllowLobby; private static ConfigEntry<bool> _cfgAllowShop; private static ConfigEntry<bool> _cfgAllowLevel; private static ConfigEntry<bool> _cfgHudEnabled; private static ConfigEntry<bool> _cfgShowJoinStatus; 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> _cfgMapSync; private static ConfigEntry<bool> _cfgRevealAllRoomsWhenHostHasNoMod; private static ConfigEntry<bool> _cfgLevelRepair; private static ConfigEntry<bool> _cfgEnemySync; private float _enforceTimer; private float _clientDeadCheckTimer; private const float EnforceInterval = 1f; private static readonly FieldRef<RoundDirector, bool> _roundAllExtractionPointsCompletedRef = TryFieldRef<RoundDirector, bool>("allExtractionPointsCompleted"); private const float SceneAllowedCacheSeconds = 0.5f; private static float _sceneAllowedCacheUntil; private static bool _sceneAllowedCachedValue; private static float _inGameCacheUntil; private static bool _inGameCachedValue; private static bool _clientLateJoinFixesDone; private static bool _clientLateJoinCoroutineRunning; private static FieldInfo _physGrabSpawnedField; private static Type _physGrabHingeType; 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 = 1f; 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 bool _hudCacheLobbyTypeKnown; private static LobbyTypes _hudCacheLobbyType; private static string _hudCacheServerName; private static bool _hudCacheRoomOpen; private static readonly List<string> _emptyList = new List<string>(0); private static readonly List<string> _lateJoinStatusLines = new List<string>(4); private static readonly List<LateJoinEntry> _hudEntriesScratch = new List<LateJoinEntry>(8); private static readonly Comparison<LateJoinEntry> _entrySortDesc = (LateJoinEntry a, LateJoinEntry b) => b.ActorNumber.CompareTo(a.ActorNumber); private const float StatusLinesRefreshSeconds = 0.25f; private static float _statusLinesNextRefreshAt; private static int _statusLinesLastEntryCount = -1; private static int _statusLinesLastCoopMask; private static readonly FieldRef<PlayerAvatar, string> _avatarPlayerNameRef = TryAvatarPlayerNameRef(); 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 FieldInfo _epIsLockedFieldInfo; private static readonly Dictionary<int, LateJoinEntry> _lateJoinEntries = new Dictionary<int, LateJoinEntry>(); private static readonly Queue<int> _lateJoinQueue = new Queue<int>(); private static bool _schedulerRunning; private static readonly Dictionary<int, bool> _playerJumpedFlags = new Dictionary<int, bool>(); internal static readonly FieldRef<PlayerAvatar, PlayerDeathHead> _playerDeathHeadRef = AccessTools.FieldRefAccess<PlayerAvatar, PlayerDeathHead>("playerDeathHead"); internal static readonly FieldRef<PlayerAvatar, PlayerTumble> _playerTumbleRef = AccessTools.FieldRefAccess<PlayerAvatar, PlayerTumble>("tumble"); internal static readonly FieldRef<PlayerDeathHead, bool> _deathHeadTriggeredRef = AccessTools.FieldRefAccess<PlayerDeathHead, bool>("triggered"); internal static readonly FieldRef<PlayerHealth, int> _playerHealthRef = AccessTools.FieldRefAccess<PlayerHealth, int>("health"); internal static readonly FieldRef<PlayerDeathHead, PhysGrabObject> _deathHeadPhysGrabRef = AccessTools.FieldRefAccess<PlayerDeathHead, PhysGrabObject>("physGrabObject"); private static readonly FieldRef<PlayerAvatar, bool> _avatarIsMovingRef = AccessTools.FieldRefAccess<PlayerAvatar, bool>("isMoving"); private static readonly FieldRef<PlayerAvatar, Vector3> _avatarInputDirectionRawRef = AccessTools.FieldRefAccess<PlayerAvatar, Vector3>("InputDirectionRaw"); private static readonly FieldRef<PlayerAvatar, bool> _avatarAnimCompleted = TryAvatarAnimCompletedRef(); internal static readonly FieldRef<Module, bool> _modTop = AccessTools.FieldRefAccess<Module, bool>("ConnectingTop"); internal static readonly FieldRef<Module, bool> _modBottom = AccessTools.FieldRefAccess<Module, bool>("ConnectingBottom"); internal static readonly FieldRef<Module, bool> _modRight = AccessTools.FieldRefAccess<Module, bool>("ConnectingRight"); internal static readonly FieldRef<Module, bool> _modLeft = AccessTools.FieldRefAccess<Module, bool>("ConnectingLeft"); internal static readonly FieldRef<Module, bool> _modFirst = AccessTools.FieldRefAccess<Module, bool>("First"); internal static readonly FieldRef<Module, bool> _modDone = AccessTools.FieldRefAccess<Module, bool>("SetupDone"); private static readonly Dictionary<int, ModuleSyncData> _cachedModuleSync = new Dictionary<int, ModuleSyncData>(64); private static readonly FieldRef<LevelGenerator, int> _lgModulesReadyPlayersRef = AccessTools.FieldRefAccess<LevelGenerator, int>("ModulesReadyPlayers"); private static readonly FieldRef<LevelGenerator, int> _lgModulesSpawnedRef = AccessTools.FieldRefAccess<LevelGenerator, int>("ModulesSpawned"); private static readonly FieldRef<LevelGenerator, bool> _lgAllPlayersReadyRef = AccessTools.FieldRefAccess<LevelGenerator, bool>("AllPlayersReady"); private static readonly FieldRef<LevelGenerator, int> _lgModuleAmountRef = AccessTools.FieldRefAccess<LevelGenerator, int>("ModuleAmount"); internal static readonly FieldRef<Module, bool> _modStartRoomRef = AccessTools.FieldRefAccess<Module, bool>("StartRoom"); internal static readonly FieldRef<ModulePropSwitch, Module> _propSwitchModuleRef = AccessTools.FieldRefAccess<ModulePropSwitch, Module>("Module"); private static readonly FieldRef<ValuableObject, bool> _valSet = AccessTools.FieldRefAccess<ValuableObject, bool>("dollarValueSet"); private static readonly FieldRef<ValuableObject, float> _valCurrent = AccessTools.FieldRefAccess<ValuableObject, float>("dollarValueCurrent"); private static readonly FieldRef<ValuableObject, bool> _valDisc = AccessTools.FieldRefAccess<ValuableObject, bool>("discovered"); private static readonly FieldRef<ValuableObject, bool> _valHaul = AccessTools.FieldRefAccess<ValuableObject, bool>("inStartRoom"); private static readonly FieldRef<MenuPageLobby, List<string>> _menuLobbyJoiningPlayersRef = TryMenuLobbyListRef("joiningPlayers"); private static readonly FieldRef<MenuPageLobby, float> _menuLobbyJoiningTimerRef = TryMenuLobbyFloatRef("joiningPlayersTimer"); private static readonly FieldRef<MenuPageLobby, float> _menuLobbyJoiningEndTimerRef = TryMenuLobbyFloatRef("joiningPlayersEndTimer"); private static readonly MethodInfo _ownershipUpdateMethod = typeof(PhotonNetwork).GetMethod("OwnershipUpdate", BindingFlags.Static | BindingFlags.NonPublic); private static readonly OwnershipUpdateDelegate _ownershipUpdate = ((_ownershipUpdateMethod != null) ? ((OwnershipUpdateDelegate)Delegate.CreateDelegate(typeof(OwnershipUpdateDelegate), _ownershipUpdateMethod)) : null); private static readonly int[] _ownershipScratch = new int[2]; private static bool _refreshOldPlayersBusy; private static readonly List<(PlayerAvatar av, int ownerActor)> _oldAvatarsScratch = new List<(PlayerAvatar, int)>(8); private static readonly WaitForSecondsRealtime _wait02 = new WaitForSecondsRealtime(0.2f); private static readonly WaitForSecondsRealtime _wait05 = new WaitForSecondsRealtime(0.5f); private static readonly WaitForSecondsRealtime _wait1 = new WaitForSecondsRealtime(1f); private static readonly WaitForSecondsRealtime _wait2 = new WaitForSecondsRealtime(2f); private static readonly WaitForSecondsRealtime _wait25 = new WaitForSecondsRealtime(2.5f); private static readonly WaitForSecondsRealtime _wait15 = new WaitForSecondsRealtime(1.5f); private const int ModuleBatchSize = 8; private const int ValuableBatchSize = 6; private static readonly FieldRef<ItemAttributes, int> _itemValueRef = TryItemValueRef(); private const int ItemBatchSize = 8; private static readonly int[] _projectileOwnershipScratch = new int[2]; private const float ChangeLevelCooldownSeconds = 0f; private const float WaitGeneratedTimeoutSeconds = 60f; private const float WaitGeneratedPollInterval = 0.2f; private const float CooldownLockedSentinelSeconds = 600f; private const string RoomNamePrefixCN = "[中途加入 (fix by Scorpio)] "; private const string RoomNamePrefixEN = "[Late Join (fix by Scorpio)] "; private static readonly FieldRef<DataDirector, string> _networkServerNameRef = TryFieldRefRoom(); private static readonly FieldRef<RunManager, List<PlayerVoiceChat>> _runManagerVoiceChatsRef = TryFieldRefRM(); private static readonly HashSet<int> _scenePersistentViewIds = new HashSet<int>(64); private static float _changeLevelCooldownUntil; private static bool _changeLevelCooldownRunning; private static bool? _lastSceneAllowed; private static readonly WaitForSecondsRealtime _waitGeneratedPoll = new WaitForSecondsRealtime(0.2f); private static bool _steamLobbyLocked; private static float _steamUnlockEnsureNextAt; private const float SteamUnlockEnsureInter