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 LateRepo Fix v1.1.4
LateRepoFix.dll
Decompiled 2 weeks agousing 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<bool> DebugLogging; internal static ConfigEntry<bool> AllowActiveRoundJoin; internal static ConfigEntry<bool> KeepPhotonRoomOpen; internal static ConfigEntry<bool> SyncModuleConnectionsForLateJoiners; internal static ConfigEntry<bool> SyncItemBatteryStateForLateJoiners; internal static ConfigEntry<bool> 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<bool>("General", "DebugLogging", false, "Enable extra logs for LateRepo Fix."); AllowActiveRoundJoin = ((BaseUnityPlugin)this).Config.Bind<bool>("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<bool>("Host", "KeepPhotonRoomOpenDuringActiveRound", true, "Keep the Photon room open and visible while active round joining is enabled."); SyncModuleConnectionsForLateJoiners = ((BaseUnityPlugin)this).Config.Bind<bool>("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<bool>("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<bool>("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<int, ModuleConnectionState> States = new Dictionary<int, ModuleConnectionState>(); private static readonly List<PendingReplay> Pending = new List<PendingReplay>(); 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<string, Module> dictionary = new Dictionary<string, Module>(); 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<PhotonView>(); } 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<ModulePropSwitch>(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<StartRoom>() != (Object)null || (Object)(object)((Component)module).GetComponentInChildren<StartRoom>(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<Module>(true); } catch { return Object.FindObjectsOfType<Module>(); } } 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+<Logic>d__19"); private static readonly Type EnemyVisionCoroutineType = AccessTools.TypeByName("EnemyVision+<Vision>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<int> playerViewIds = GetPlayerViewIds(); if (playerViewIds.Count != 0) { EnemyOnScreen[] array = Object.FindObjectsOfType<EnemyOnScreen>(); foreach (EnemyOnScreen enemyOnScreen in array) { Sync(enemyOnScreen, playerViewIds); } EnemyVision[] array2 = Object.FindObjectsOfType<EnemyVision>(); 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<int> 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<int> 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<int> 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<int> GetPlayerViewIds() { List<int> list = new List<int>(); 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<PendingSync> Pending = new List<PendingSync>(); 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<ItemBattery>(); 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<ItemBattery>(); if ((Object)(object)component2 != (Object)null) { return component2; } component2 = component.GetComponentInChildren<ItemBattery>(true); if ((Object)(object)component2 != (Object)null) { return component2; } return component.GetComponentInParent<ItemBattery>(true); } private static ItemBattery[] FindBatteries() { try { return Object.FindObjectsOfType<ItemBattery>(true); } catch { return Object.FindObjectsOfType<ItemBattery>(); } } 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<PhotonView>(); } 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+<Logic>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+<Vision>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<LevelGenerator> __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); } } }