using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Photon.Pun; using Photon.Realtime; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace LateRepoFix; [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("laterepo.fix.runtime", "LateRepo Fix", "1.1.4")] public sealed class Plugin : BaseUnityPlugin { internal static ManualLogSource Log; internal static ConfigEntry DebugLogging; internal static ConfigEntry AllowActiveRoundJoin; internal static ConfigEntry KeepPhotonRoomOpen; internal static ConfigEntry SyncModuleConnectionsForLateJoiners; internal static ConfigEntry SyncItemBatteryStateForLateJoiners; internal static ConfigEntry RepairInventoryBatteryUiForLateJoiners; private void Awake() { //IL_00d1: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; DebugLogging = ((BaseUnityPlugin)this).Config.Bind("General", "DebugLogging", false, "Enable extra logs for LateRepo Fix."); AllowActiveRoundJoin = ((BaseUnityPlugin)this).Config.Bind("Host", "AllowJoiningDuringActiveRound", true, "Keep the Steam lobby joinable for late joins while the host is already in an active run."); KeepPhotonRoomOpen = ((BaseUnityPlugin)this).Config.Bind("Host", "KeepPhotonRoomOpenDuringActiveRound", true, "Keep the Photon room open and visible while active round joining is enabled."); SyncModuleConnectionsForLateJoiners = ((BaseUnityPlugin)this).Config.Bind("Level", "SyncModuleConnectionsForLateJoiners", true, "Repair module connection state for late joiners before client block generation and replay the host state after their avatar is ready."); SyncItemBatteryStateForLateJoiners = ((BaseUnityPlugin)this).Config.Bind("Items", "SyncItemBatteryStateForLateJoiners", true, "Send current ItemBattery charge state only to newly joined clients so vanilla battery UI starts with the correct bar count."); RepairInventoryBatteryUiForLateJoiners = ((BaseUnityPlugin)this).Config.Bind("Items", "RepairInventoryBatteryUiForLateJoiners", true, "Repair vanilla inventory battery visuals when a late-joined client has batteryLife but a stale private battery bar counter."); new Harmony("laterepo.fix.runtime").PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched LateRepo LevelGenerator hook guard, Steam/Photon access, enemy late-join state, early client module connection repair, targeted module replay, and item battery late-join UI sync."); } private void Update() { LateJoinAccess.Update(); EnemyLateJoinState.Update(); ModuleConnectionBuffer.Update(); LateJoinItemBatteryState.Update(); } } internal static class ModLog { private static bool loggedSuppressedException; private static bool loggedPreventedLobbyLock; private static bool loggedForcedLobbyOpen; private static bool loggedPhotonRoomOpen; private static bool loggedBufferedModuleConnections; private static bool loggedItemBatterySync; public static void Info(string message) { if (Plugin.Log != null) { Plugin.Log.LogInfo((object)message); } } public static void Debug(string message) { if (Plugin.DebugLogging != null && Plugin.DebugLogging.Value && Plugin.Log != null) { Plugin.Log.LogInfo((object)("[Debug] " + message)); } } public static void SuppressedSingleplayerPool() { if (loggedSuppressedException) { Debug("Suppressed another outdated RunManager.singleplayerPool access."); return; } Info("Suppressed outdated RunManager.singleplayerPool access during LevelGenerator.Start."); loggedSuppressedException = true; } public static void PreventedLobbyLock() { if (loggedPreventedLobbyLock) { Debug("Prevented another Steam lobby lock while active round joining is enabled."); return; } Info("Prevented Steam lobby lock while active round joining is enabled."); loggedPreventedLobbyLock = true; } public static void ForcedLobbyOpen(string reason) { if (loggedForcedLobbyOpen) { Debug("Kept Steam lobby joinable after " + reason + "."); return; } Info("Kept Steam lobby joinable after " + reason + "."); loggedForcedLobbyOpen = true; } public static void PhotonRoomOpen(string reason) { if (loggedPhotonRoomOpen) { Debug("Kept Photon room open after " + reason + "."); return; } Info("Kept Photon room open and visible for active round joining."); loggedPhotonRoomOpen = true; } public static void BufferedModuleConnection(int viewId) { if (loggedBufferedModuleConnections) { Debug("Stored module connection state for PhotonView " + viewId + "."); return; } Info("Stored module connection states for active-round late joiners."); loggedBufferedModuleConnections = true; } public static void ItemBatterySync(Player target, int count) { string text = ((target == null) ? "unknown" : (string.IsNullOrEmpty(target.NickName) ? target.ActorNumber.ToString() : target.NickName)); if (loggedItemBatterySync) { Debug("Sent " + count + " item battery state(s) to late joiner " + text + "."); } else { Info("Sent item battery charge states to late joiner " + text + "."); loggedItemBatterySync = true; } } } internal static class ExceptionFilter { public static bool IsSingleplayerPoolMissing(Exception ex) { while (ex != null) { if (ex is MissingFieldException && ex.ToString().IndexOf("RunManager.singleplayerPool", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } ex = ex.InnerException; } return false; } } internal static class ModuleConnectionBuffer { private sealed class ModuleConnectionState { public PhotonView PhotonView; public int ViewId; public bool Top; public bool Bottom; public bool Right; public bool Left; public bool First; } private sealed class PendingReplay { public Player Target; public float NextTime; public int Attempts; } private const int LocalReplayAttemptsMax = 10; private static readonly FieldInfo ModulePhotonViewField = AccessTools.Field(typeof(Module), "photonView"); private static readonly FieldInfo ModuleConnectingTopField = AccessTools.Field(typeof(Module), "ConnectingTop"); private static readonly FieldInfo ModuleConnectingBottomField = AccessTools.Field(typeof(Module), "ConnectingBottom"); private static readonly FieldInfo ModuleConnectingRightField = AccessTools.Field(typeof(Module), "ConnectingRight"); private static readonly FieldInfo ModuleConnectingLeftField = AccessTools.Field(typeof(Module), "ConnectingLeft"); private static readonly FieldInfo ModuleSetupDoneField = AccessTools.Field(typeof(Module), "SetupDone"); private static readonly FieldInfo ModuleFirstField = AccessTools.Field(typeof(Module), "First"); private static readonly FieldInfo ModuleGridXField = AccessTools.Field(typeof(Module), "GridX"); private static readonly FieldInfo ModuleGridYField = AccessTools.Field(typeof(Module), "GridY"); private static readonly FieldInfo ModuleStartRoomField = AccessTools.Field(typeof(Module), "StartRoom"); private static readonly FieldInfo ModulePropSwitchModuleField = AccessTools.Field(typeof(ModulePropSwitch), "Module"); private static readonly FieldInfo LevelGeneratorModulesSpawnedField = AccessTools.Field(typeof(LevelGenerator), "ModulesSpawned"); private static readonly Dictionary States = new Dictionary(); private static readonly List Pending = new List(); private static int localReplayAttempts; public static bool TrySendBuffered(Module module, bool top, bool bottom, bool right, bool left, bool first) { if (Plugin.SyncModuleConnectionsForLateJoiners == null || !Plugin.SyncModuleConnectionsForLateJoiners.Value) { return false; } if ((Object)(object)module == (Object)null || !SemiFunc.IsMultiplayer() || !PhotonNetwork.IsMasterClient) { return false; } PhotonView photonView = GetPhotonView(module); if ((Object)(object)photonView == (Object)null || photonView.ViewID == 0) { return false; } try { States[photonView.ViewID] = new ModuleConnectionState { PhotonView = photonView, ViewId = photonView.ViewID, Top = top, Bottom = bottom, Right = right, Left = left, First = first }; ModLog.BufferedModuleConnection(photonView.ViewID); } catch (Exception ex) { ModLog.Debug("Could not store module connection state: " + ex.GetType().Name); } return false; } public static void Queue(PlayerAvatar playerAvatar) { if (Plugin.SyncModuleConnectionsForLateJoiners != null && Plugin.SyncModuleConnectionsForLateJoiners.Value && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && !((Object)(object)playerAvatar == (Object)null) && !((Object)(object)playerAvatar.photonView == (Object)null)) { Queue(playerAvatar.photonView.Owner, 3f); } } public static void ApplyLocalClientConnections(LevelGenerator levelGenerator) { if (Plugin.SyncModuleConnectionsForLateJoiners == null || !Plugin.SyncModuleConnectionsForLateJoiners.Value || !SemiFunc.IsMultiplayer() || PhotonNetwork.IsMasterClient || (Object)(object)levelGenerator == (Object)null) { return; } Module[] array = FindModules(); if (array == null || array.Length == 0) { return; } Dictionary dictionary = new Dictionary(); Module[] array2 = array; foreach (Module val in array2) { if (!((Object)(object)val == (Object)null) && !IsStartRoom(val)) { int @int = GetInt(ModuleGridXField, val); int int2 = GetInt(ModuleGridYField, val); string gridKey = GetGridKey(@int, int2); if (!dictionary.ContainsKey(gridKey)) { dictionary.Add(gridKey, val); } } } int num = 0; array2 = array; foreach (Module val in array2) { if (!((Object)(object)val == (Object)null) && !IsStartRoom(val)) { int @int = GetInt(ModuleGridXField, val); int int2 = GetInt(ModuleGridYField, val); bool flag = IsFirstModule(val); bool top = dictionary.ContainsKey(GetGridKey(@int, int2 + 1)); bool bottom = dictionary.ContainsKey(GetGridKey(@int, int2 - 1)) || flag; bool right = dictionary.ContainsKey(GetGridKey(@int + 1, int2)); bool left = dictionary.ContainsKey(GetGridKey(@int - 1, int2)); if (ApplyState(val, top, bottom, right, left, flag, countModuleReady: true)) { num++; } } } if (num > 0) { localReplayAttempts = 0; ModLog.Debug("Applied " + num + " local module connection state(s) before client block generation."); } else if (localReplayAttempts < 10) { localReplayAttempts++; ModLog.Debug("Local module connection prepass found no unapplied modules."); } } public static bool ApplyIncomingRpc(Module module, bool top, bool bottom, bool right, bool left, bool first) { if ((Object)(object)module == (Object)null || !SemiFunc.IsMultiplayer() || PhotonNetwork.IsMasterClient) { return true; } bool @bool = GetBool(ModuleSetupDoneField, module); if (@bool && GetBool(ModuleConnectingTopField, module) == top && GetBool(ModuleConnectingBottomField, module) == bottom && GetBool(ModuleConnectingRightField, module) == right && GetBool(ModuleConnectingLeftField, module) == left && GetBool(ModuleFirstField, module) == first) { return false; } ApplyState(module, top, bottom, right, left, first, !@bool); return false; } private static void Queue(Player target, float delay) { if (target == null || PhotonNetwork.LocalPlayer == null || target.ActorNumber == PhotonNetwork.LocalPlayer.ActorNumber) { return; } for (int num = Pending.Count - 1; num >= 0; num--) { if (Pending[num].Target != null && Pending[num].Target.ActorNumber == target.ActorNumber) { Pending.RemoveAt(num); } } Pending.Add(new PendingReplay { Target = target, NextTime = Time.unscaledTime + Mathf.Max(0.1f, delay), Attempts = 0 }); } public static void Update() { if (Pending.Count == 0) { return; } for (int num = Pending.Count - 1; num >= 0; num--) { PendingReplay pendingReplay = Pending[num]; if (pendingReplay != null && pendingReplay.Target != null && !(Time.unscaledTime < pendingReplay.NextTime)) { int num2 = SendCurrentState(pendingReplay.Target); pendingReplay.Attempts++; if (pendingReplay.Attempts >= 16) { Pending.RemoveAt(num); ModLog.Debug("Sent " + num2 + " module connection state(s) to late joiner."); } else { pendingReplay.NextTime = Time.unscaledTime + 5f; } } } } private static int SendCurrentState(Player target) { if (States.Count == 0) { return 0; } int num = 0; foreach (ModuleConnectionState value in States.Values) { if (value == null) { continue; } PhotonView val = (((Object)(object)value.PhotonView != (Object)null) ? value.PhotonView : PhotonNetwork.GetPhotonView(value.ViewId)); if (!((Object)(object)val == (Object)null) && val.ViewID != 0) { try { val.RPC("ModuleConnectionSetRPC", target, new object[5] { value.Top, value.Bottom, value.Right, value.Left, value.First }); num++; } catch (Exception ex) { ModLog.Debug("Could not replay module connection state for PhotonView " + value.ViewId + ": " + ex.GetType().Name); } } } return num; } private static PhotonView GetPhotonView(Module module) { PhotonView val = (PhotonView)((ModulePhotonViewField == null) ? null : /*isinst with value type is only supported in some contexts*/); return ((Object)(object)val != (Object)null) ? val : ((Component)module).GetComponent(); } private static bool ApplyState(Module module, bool top, bool bottom, bool right, bool left, bool first, bool countModuleReady) { if ((Object)(object)module == (Object)null) { return false; } bool @bool = GetBool(ModuleSetupDoneField, module); SetBool(ModuleConnectingTopField, module, top); SetBool(ModuleConnectingBottomField, module, bottom); SetBool(ModuleConnectingRightField, module, right); SetBool(ModuleConnectingLeftField, module, left); SetBool(ModuleFirstField, module, first); SetBool(ModuleSetupDoneField, module, value: true); try { ModulePropSwitch[] componentsInChildren = ((Component)module).GetComponentsInChildren(true); ModulePropSwitch[] array = componentsInChildren; foreach (ModulePropSwitch val in array) { if (!((Object)(object)val == (Object)null)) { if (ModulePropSwitchModuleField != null) { ModulePropSwitchModuleField.SetValue(val, module); } val.Setup(); } } } catch (Exception ex) { ModLog.Debug("Could not setup module prop switches for " + ((Object)module).name + ": " + ex.GetType().Name); } if (countModuleReady && !@bool && (Object)(object)LevelGenerator.Instance != (Object)null && LevelGeneratorModulesSpawnedField != null) { int @int = GetInt(LevelGeneratorModulesSpawnedField, LevelGenerator.Instance); LevelGeneratorModulesSpawnedField.SetValue(LevelGenerator.Instance, @int + 1); } return !@bool; } private static bool IsFirstModule(Module module) { return (Object)(object)module != (Object)null && (GetBool(ModuleFirstField, module) || IsStartRoom(module) || (Object)(object)((Component)module).GetComponent() != (Object)null || (Object)(object)((Component)module).GetComponentInChildren(true) != (Object)null); } private static bool IsStartRoom(Module module) { return (Object)(object)module != (Object)null && GetBool(ModuleStartRoomField, module); } private static Module[] FindModules() { try { return Object.FindObjectsOfType(true); } catch { return Object.FindObjectsOfType(); } } private static string GetGridKey(int x, int y) { return x + ":" + y; } private static bool GetBool(FieldInfo field, object instance) { if (field == null || instance == null) { return false; } object value = field.GetValue(instance); return value is bool && (bool)value; } private static void SetBool(FieldInfo field, object instance, bool value) { if (field != null && instance != null) { field.SetValue(instance, value); } } private static int GetInt(FieldInfo field, object instance) { if (field == null || instance == null) { return 0; } object value = field.GetValue(instance); return (value is int) ? ((int)value) : 0; } } [HarmonyPatch(typeof(LevelGenerator), "Start")] internal static class LevelGeneratorStartPatch { private static void Postfix() { LateJoinAccess.TryUnlock("LevelGenerator.Start"); } private static Exception Finalizer(Exception __exception) { if (__exception != null && ExceptionFilter.IsSingleplayerPoolMissing(__exception)) { ModLog.SuppressedSingleplayerPool(); return null; } return __exception; } } [HarmonyPatch(typeof(LevelGenerator), "GenerateDone")] internal static class LevelGeneratorGenerateDonePatch { private static void Postfix() { LateJoinItemBatteryState.QueueAllRemote(2f); } } [HarmonyPatch(typeof(LevelGenerator), "ItemSetup")] internal static class LevelGeneratorItemSetupPatch { private static void Postfix() { LateJoinItemBatteryState.QueueAllRemote(2f); } } [HarmonyPatch(typeof(LevelGenerator), "GenerateConnectObjects")] internal static class LevelGeneratorGenerateConnectObjectsPatch { private static void Prefix(LevelGenerator __instance) { ModuleConnectionBuffer.ApplyLocalClientConnections(__instance); } } [HarmonyPatch(typeof(LevelGenerator), "GenerateBlockObjects")] internal static class LevelGeneratorGenerateBlockObjectsPatch { private static void Prefix(LevelGenerator __instance) { ModuleConnectionBuffer.ApplyLocalClientConnections(__instance); } } internal static class LateJoinAccess { private static readonly FieldInfo PrivateLobbyField = AccessTools.Field(typeof(SteamManager), "privateLobby"); private static float nextAccessRefreshTime; public static bool ShouldKeepJoinable() { return Plugin.AllowActiveRoundJoin != null && Plugin.AllowActiveRoundJoin.Value && PhotonNetwork.IsMasterClient; } public static void Update() { if (!(Time.unscaledTime < nextAccessRefreshTime)) { nextAccessRefreshTime = Time.unscaledTime + 2f; RefreshPhotonRoom("periodic host access refresh"); } } public static bool PreventLock(SteamManager manager) { if (!ShouldKeepJoinable() || (Object)(object)manager == (Object)null) { return true; } Unlock(manager, "SteamManager.LockLobby"); OpenPhotonRoom("SteamManager.LockLobby"); ModLog.PreventedLobbyLock(); return false; } public static void TryUnlock(string reason) { if (ShouldKeepJoinable() && !((Object)(object)SteamManager.instance == (Object)null)) { Unlock(SteamManager.instance, reason); OpenPhotonRoom(reason); ModLog.ForcedLobbyOpen(reason); } } private static void RefreshPhotonRoom(string reason) { if (ShouldKeepJoinable()) { OpenPhotonRoom(reason); } } private static void Unlock(SteamManager manager, string reason) { try { bool flag = !IsPrivateLobby(manager); manager.UnlockLobby(flag); ModLog.Debug("Unlocked Steam lobby from " + reason + " with open=" + flag + "."); } catch (Exception ex) { ModLog.Debug("Could not unlock Steam lobby from " + reason + ": " + ex.GetType().Name); } } private static bool IsPrivateLobby(SteamManager manager) { if (PrivateLobbyField == null || (Object)(object)manager == (Object)null) { return false; } return (bool)PrivateLobbyField.GetValue(manager); } private static void OpenPhotonRoom(string reason) { if (Plugin.KeepPhotonRoomOpen == null || !Plugin.KeepPhotonRoomOpen.Value || !PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null) { return; } try { bool flag = false; if (!PhotonNetwork.CurrentRoom.IsOpen) { PhotonNetwork.CurrentRoom.IsOpen = true; flag = true; } if (!PhotonNetwork.CurrentRoom.IsVisible) { PhotonNetwork.CurrentRoom.IsVisible = true; flag = true; } if (flag) { ModLog.PhotonRoomOpen(reason); } } catch (Exception ex) { ModLog.Debug("Could not keep Photon room open after " + reason + ": " + ex.GetType().Name); } } } internal static class EnemyLateJoinState { private static readonly Type EnemyOnScreenCoroutineType = AccessTools.TypeByName("EnemyOnScreen+d__19"); private static readonly Type EnemyVisionCoroutineType = AccessTools.TypeByName("EnemyVision+d__34"); private static readonly FieldInfo EnemyOnScreenCoroutineOwner = ((EnemyOnScreenCoroutineType == null) ? null : AccessTools.Field(EnemyOnScreenCoroutineType, "<>4__this")); private static readonly FieldInfo EnemyVisionCoroutineOwner = ((EnemyVisionCoroutineType == null) ? null : AccessTools.Field(EnemyVisionCoroutineType, "<>4__this")); private static float nextSyncTime; private static int lastPlayerSignature; public static void Update() { if (!(Time.unscaledTime < nextSyncTime)) { nextSyncTime = Time.unscaledTime + 1f; int playerSignature = GetPlayerSignature(); if (playerSignature != 0 && playerSignature != lastPlayerSignature) { lastPlayerSignature = playerSignature; SyncAll(); } } } public static void SyncAll() { List playerViewIds = GetPlayerViewIds(); if (playerViewIds.Count != 0) { EnemyOnScreen[] array = Object.FindObjectsOfType(); foreach (EnemyOnScreen enemyOnScreen in array) { Sync(enemyOnScreen, playerViewIds); } EnemyVision[] array2 = Object.FindObjectsOfType(); foreach (EnemyVision enemyVision in array2) { Sync(enemyVision, playerViewIds); } } } public static void Sync(EnemyOnScreen enemyOnScreen) { if (!((Object)(object)enemyOnScreen == (Object)null)) { Sync(enemyOnScreen, GetPlayerViewIds()); } } public static void Sync(EnemyVision enemyVision) { if (!((Object)(object)enemyVision == (Object)null)) { Sync(enemyVision, GetPlayerViewIds()); } } public static void SyncEnemyOnScreenCoroutine(object stateMachine) { EnemyOnScreen enemyOnScreen = (EnemyOnScreen)((EnemyOnScreenCoroutineOwner == null) ? null : /*isinst with value type is only supported in some contexts*/); Sync(enemyOnScreen); } public static void SyncEnemyVisionCoroutine(object stateMachine) { EnemyVision enemyVision = (EnemyVision)((EnemyVisionCoroutineOwner == null) ? null : /*isinst with value type is only supported in some contexts*/); Sync(enemyVision); } private static void Sync(EnemyOnScreen enemyOnScreen, List playerIds) { if ((Object)(object)enemyOnScreen == (Object)null || playerIds.Count == 0) { return; } try { foreach (int playerId in playerIds) { enemyOnScreen.PlayerAdded(playerId); } } catch (Exception ex) { ModLog.Debug("Could not sync EnemyOnScreen late-join state: " + ex.GetType().Name); } } private static void Sync(EnemyVision enemyVision, List playerIds) { if ((Object)(object)enemyVision == (Object)null || playerIds.Count == 0) { return; } try { foreach (int playerId in playerIds) { enemyVision.PlayerAdded(playerId); } } catch (Exception ex) { ModLog.Debug("Could not sync EnemyVision late-join state: " + ex.GetType().Name); } } private static int GetPlayerSignature() { List playerViewIds = GetPlayerViewIds(); if (playerViewIds.Count == 0) { return 0; } int num = 17; foreach (int item in playerViewIds) { num = num * 31 + item; } return num; } private static List GetPlayerViewIds() { List list = new List(); if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null) { return list; } foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if (!((Object)(object)player == (Object)null) && !((Object)(object)player.photonView == (Object)null) && player.photonView.ViewID != 0) { list.Add(player.photonView.ViewID); } } return list; } } internal static class LateJoinItemBatteryState { private sealed class PendingSync { public Player Target; public float NextTime; public int Attempts; } private static readonly FieldInfo BatteryPhotonViewField = AccessTools.Field(typeof(ItemBattery), "photonView"); private static readonly FieldInfo BatteryLifeIntField = AccessTools.Field(typeof(ItemBattery), "batteryLifeInt"); private static readonly FieldInfo BatteryCurrentBarsField = AccessTools.Field(typeof(ItemBattery), "currentBars"); private static readonly FieldInfo InventorySpotBatteryVisualLogicField = AccessTools.Field(typeof(InventorySpot), "batteryVisualLogic"); private static readonly List Pending = new List(); public static void Queue(PlayerAvatar playerAvatar) { if (Plugin.SyncItemBatteryStateForLateJoiners != null && Plugin.SyncItemBatteryStateForLateJoiners.Value && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && !((Object)(object)playerAvatar == (Object)null) && !((Object)(object)playerAvatar.photonView == (Object)null)) { Player owner = playerAvatar.photonView.Owner; if (owner != null && PhotonNetwork.LocalPlayer != null && owner.ActorNumber != PhotonNetwork.LocalPlayer.ActorNumber) { Queue(owner, 2f); } } } public static void QueueAllRemote(float delay) { if (Plugin.SyncItemBatteryStateForLateJoiners != null && Plugin.SyncItemBatteryStateForLateJoiners.Value && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && PhotonNetwork.PlayerListOthers != null) { Player[] playerListOthers = PhotonNetwork.PlayerListOthers; foreach (Player target in playerListOthers) { Queue(target, delay); } } } private static void Queue(Player target, float delay) { if (target == null || PhotonNetwork.LocalPlayer == null || target.ActorNumber == PhotonNetwork.LocalPlayer.ActorNumber) { return; } for (int num = Pending.Count - 1; num >= 0; num--) { if (Pending[num].Target != null && Pending[num].Target.ActorNumber == target.ActorNumber) { Pending.RemoveAt(num); } } Pending.Add(new PendingSync { Target = target, NextTime = Time.unscaledTime + Mathf.Max(0.1f, delay), Attempts = 0 }); } public static void Update() { if (Pending.Count == 0) { return; } for (int num = Pending.Count - 1; num >= 0; num--) { PendingSync pendingSync = Pending[num]; if (pendingSync != null && pendingSync.Target != null && !(Time.unscaledTime < pendingSync.NextTime)) { int count = SendCurrentState(pendingSync.Target); pendingSync.Attempts++; if (pendingSync.Attempts >= 12) { Pending.RemoveAt(num); ModLog.ItemBatterySync(pendingSync.Target, count); } else { pendingSync.NextTime = Time.unscaledTime + 5f; } } } } private static int SendCurrentState(Player target) { int num = 0; ItemBattery[] array = FindBatteries(); ItemBattery[] array2 = array; foreach (ItemBattery val in array2) { if ((Object)(object)val == (Object)null) { continue; } PhotonView photonView = GetPhotonView(val); if (!((Object)(object)photonView == (Object)null) && photonView.ViewID != 0 && CanReceiveBatteryRpc(photonView, val)) { try { int batteryLevel = GetBatteryLevel(val); bool batteryActive = val.batteryActive; photonView.RPC("BatteryToggleRPC", target, new object[1] { false }); photonView.RPC("BatteryFullPercentChangeRPC", target, new object[2] { batteryLevel, false }); photonView.RPC("BatteryToggleRPC", target, new object[1] { batteryActive }); num++; } catch (Exception ex) { ModLog.Debug("Could not sync ItemBattery state for " + ((Object)val).name + ": " + ex.GetType().Name); } } } return num; } private static bool CanReceiveBatteryRpc(PhotonView photonView, ItemBattery battery) { if ((Object)(object)photonView == (Object)null || (Object)(object)battery == (Object)null) { return false; } ItemBattery component = ((Component)photonView).GetComponent(); return (Object)(object)component != (Object)null && object.ReferenceEquals(component, battery); } public static void RepairInventorySpot(InventorySpot spot) { if (!ShouldRepairInventoryBatteryUi() || (Object)(object)spot == (Object)null || !spot.IsOccupied()) { return; } ItemBattery val = FindBattery((Component)(object)spot.CurrentItem); if ((Object)(object)val == (Object)null) { return; } int num = RepairBatteryCounters(val); BatteryVisualLogic val2 = (BatteryVisualLogic)((InventorySpotBatteryVisualLogicField == null) ? null : /*isinst with value type is only supported in some contexts*/); if ((Object)(object)val2 == (Object)null) { return; } try { if (!((Component)val2).gameObject.activeSelf) { ((Component)val2).gameObject.SetActive(true); } if ((Object)(object)val2.itemBattery != (Object)(object)val) { val2.itemBattery = val; val2.BatteryBarsSet(); } else if (val2.batteryBars != val.batteryBars) { val2.BatteryBarsSet(); } val2.BatteryBarsUpdate(num, true); } catch (Exception ex) { ModLog.Debug("Could not repair inventory battery UI for " + ((Object)val).name + ": " + ex.GetType().Name); } } public static void RepairInventoryBatteryResult(Inventory inventory, int index, ref int result) { if (!ShouldRepairInventoryBatteryUi() || (Object)(object)inventory == (Object)null) { return; } InventorySpot spotByIndex = inventory.GetSpotByIndex(index); if ((Object)(object)spotByIndex == (Object)null || !spotByIndex.IsOccupied()) { return; } ItemBattery val = FindBattery((Component)(object)spotByIndex.CurrentItem); if (!((Object)(object)val == (Object)null)) { int num = RepairBatteryCounters(val); if (result < 0 || (result == 0 && val.batteryLife > 0f) || result > Mathf.Max(1, val.batteryBars)) { result = num; } } } private static bool ShouldRepairInventoryBatteryUi() { return Plugin.RepairInventoryBatteryUiForLateJoiners != null && Plugin.RepairInventoryBatteryUiForLateJoiners.Value; } private static int RepairBatteryCounters(ItemBattery battery) { int batteryLevel = GetBatteryLevel(battery); SetIntField(BatteryLifeIntField, battery, batteryLevel); SetIntField(BatteryCurrentBarsField, battery, batteryLevel); return batteryLevel; } private static ItemBattery FindBattery(Component component) { if ((Object)(object)component == (Object)null) { return null; } ItemBattery component2 = component.GetComponent(); if ((Object)(object)component2 != (Object)null) { return component2; } component2 = component.GetComponentInChildren(true); if ((Object)(object)component2 != (Object)null) { return component2; } return component.GetComponentInParent(true); } private static ItemBattery[] FindBatteries() { try { return Object.FindObjectsOfType(true); } catch { return Object.FindObjectsOfType(); } } private static PhotonView GetPhotonView(ItemBattery battery) { PhotonView val = (PhotonView)((BatteryPhotonViewField == null) ? null : /*isinst with value type is only supported in some contexts*/); return ((Object)(object)val != (Object)null) ? val : ((Component)battery).GetComponent(); } private static int GetBatteryLevel(ItemBattery battery) { int num = Mathf.Max(1, battery.batteryBars); int num2 = 0; if (BatteryLifeIntField != null) { object value = BatteryLifeIntField.GetValue(battery); if (value is int) { num2 = (int)value; } } if (num2 < 0 || num2 > num || (num2 == 0 && battery.batteryLife > 0f)) { num2 = Mathf.RoundToInt(battery.batteryLife / (100f / (float)num)); } return Mathf.Clamp(num2, 0, num); } private static void SetIntField(FieldInfo field, ItemBattery battery, int value) { if (field != null && (Object)(object)battery != (Object)null) { field.SetValue(battery, value); } } } [HarmonyPatch(typeof(SteamManager), "LockLobby")] internal static class SteamManagerLockLobbyPatch { private static bool Prefix(SteamManager __instance) { return LateJoinAccess.PreventLock(__instance); } } [HarmonyPatch(typeof(RunManager), "ChangeLevel")] internal static class RunManagerChangeLevelPatch { private static void Postfix() { LateJoinAccess.TryUnlock("RunManager.ChangeLevel"); } } [HarmonyPatch(typeof(MenuPageLobby), "ButtonStart")] internal static class MenuPageLobbyButtonStartPatch { private static void Postfix() { LateJoinAccess.TryUnlock("MenuPageLobby.ButtonStart"); } } [HarmonyPatch(typeof(EnemyOnScreen), "OnEnable")] internal static class EnemyOnScreenOnEnablePatch { private static void Prefix(EnemyOnScreen __instance) { EnemyLateJoinState.Sync(__instance); } } [HarmonyPatch(typeof(EnemyVision), "OnEnable")] internal static class EnemyVisionOnEnablePatch { private static void Prefix(EnemyVision __instance) { EnemyLateJoinState.Sync(__instance); } } [HarmonyPatch(typeof(PlayerAvatar), "Start")] internal static class PlayerAvatarStartPatch { private static void Postfix(PlayerAvatar __instance) { EnemyLateJoinState.SyncAll(); ModuleConnectionBuffer.Queue(__instance); LateJoinItemBatteryState.Queue(__instance); } } [HarmonyPatch(typeof(InventorySpot), "StateOccupied")] internal static class InventorySpotStateOccupiedPatch { private static void Postfix(InventorySpot __instance) { LateJoinItemBatteryState.RepairInventorySpot(__instance); } } [HarmonyPatch(typeof(Inventory), "GetBatteryStateFromInventorySpot")] internal static class InventoryGetBatteryStateFromInventorySpotPatch { private static void Postfix(Inventory __instance, int index, ref int __result) { LateJoinItemBatteryState.RepairInventoryBatteryResult(__instance, index, ref __result); } } [HarmonyPatch(typeof(Module), "ModuleConnectionSet")] internal static class ModuleConnectionSetPatch { private static bool Prefix(Module __instance, bool _top, bool _bottom, bool _right, bool _left, bool _first) { return !ModuleConnectionBuffer.TrySendBuffered(__instance, _top, _bottom, _right, _left, _first); } } [HarmonyPatch(typeof(Module), "ModuleConnectionSetRPC")] internal static class ModuleConnectionSetRpcPatch { private static bool Prefix(Module __instance, bool _top, bool _bottom, bool _right, bool _left, bool _first) { return ModuleConnectionBuffer.ApplyIncomingRpc(__instance, _top, _bottom, _right, _left, _first); } } [HarmonyPatch] internal static class EnemyOnScreenLogicMoveNextPatch { private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("EnemyOnScreen+d__19"); return (type == null) ? null : AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null); } private static void Prefix(object __instance) { EnemyLateJoinState.SyncEnemyOnScreenCoroutine(__instance); } } [HarmonyPatch] internal static class EnemyVisionMoveNextPatch { private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("EnemyVision+d__34"); return (type == null) ? null : AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null); } private static void Prefix(object __instance) { EnemyLateJoinState.SyncEnemyVisionCoroutine(__instance); } } [HarmonyPatch] internal static class LateRepoLevelGeneratorHookPatch { private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("LateRepo.Patches.LateJoinPatch"); if (type == null) { ModLog.Info("LateRepo hook type was not found."); return null; } return AccessTools.Method(type, "LevelGeneratorHook", (Type[])null, (Type[])null); } private static bool Prefix(Action __0, LevelGenerator __1) { try { ClearLobbyBufferedRpcs(__1); ModLog.Debug("Calling original LevelGenerator.Start through LateRepo hook."); __0(__1); } catch (Exception ex) { if (ExceptionFilter.IsSingleplayerPoolMissing(ex)) { ModLog.SuppressedSingleplayerPool(); return false; } throw; } return false; } private static void ClearLobbyBufferedRpcs(LevelGenerator levelGenerator) { if (PhotonNetwork.IsMasterClient && SemiFunc.RunIsLobby() && !((Object)(object)levelGenerator == (Object)null) && !((Object)(object)levelGenerator.PhotonView == (Object)null)) { PhotonNetwork.RemoveBufferedRPCs(levelGenerator.PhotonView.ViewID, (string)null, (int[])null); } } }