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 LateJoin v1.1.1
LateJoin.dll
Decompiled 17 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using ExitGames.Client.Photon; using HarmonyLib; using LateJoin.Patches; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using UnityEngine; using UnityEngine.SceneManagement; [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("LateJoin")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Allows players to join lobbies that are already in-progress.")] [assembly: AssemblyFileVersion("1.1.1.0")] [assembly: AssemblyInformationalVersion("1.1.1+66f6370566b62a8b3f3b94e484e4c6ebf9d8e9f9")] [assembly: AssemblyProduct("LateJoin")] [assembly: AssemblyTitle("LateJoin")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.1.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 LateJoin { [BepInPlugin("semimodder-latejoin", "Late Join", "1.1.1")] public class Plugin : BaseUnityPlugin { public static ConfigEntry<bool> CfgEnabled; public static Plugin Instance { get; private set; } private void Awake() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) Instance = this; CfgEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable mid-game join support (Host keeps lobby open and syncs basic state)."); new Harmony("semimodder-latejoin").PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Late Join Mod loaded successfully."); } private void Update() { if (CfgEnabled.Value) { LobbyPatches.Update(); LevelPatches.Update(); ItemPatches.Update(); EnemyPatches.Update(); } } } } namespace LateJoin.Patches { public static class EnemyPatches { private static readonly FieldInfo EnemyOnScreenCoroutineOwner = AccessTools.Field(AccessTools.TypeByName("EnemyOnScreen+<Logic>d__19"), "4__this") ?? AccessTools.Field(AccessTools.TypeByName("EnemyOnScreen+<Logic>d__19"), "<>4__this"); private static readonly FieldInfo EnemyVisionCoroutineOwner = AccessTools.Field(AccessTools.TypeByName("EnemyVision+<Vision>d__34"), "4__this") ?? AccessTools.Field(AccessTools.TypeByName("EnemyVision+<Vision>d__34"), "<>4__this"); private static int lastPlayerSignature = 0; public static void Update() { if (Plugin.CfgEnabled.Value) { int playerSignature = GetPlayerSignature(); if (playerSignature != lastPlayerSignature) { lastPlayerSignature = playerSignature; SyncAll(); } } } public static void SyncAll() { if (!Plugin.CfgEnabled.Value) { return; } List<int> playerViewIds = GetPlayerViewIds(); if (playerViewIds.Count == 0) { return; } try { EnemyOnScreen[] array = Object.FindObjectsOfType<EnemyOnScreen>(); foreach (EnemyOnScreen val in array) { if ((Object)(object)val != (Object)null) { Sync(val, playerViewIds); } } EnemyVision[] array2 = Object.FindObjectsOfType<EnemyVision>(); foreach (EnemyVision val2 in array2) { if ((Object)(object)val2 != (Object)null) { Sync(val2, playerViewIds); } } } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] SyncAll enemies failed: " + ex.Message)); } } 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) { if (EnemyOnScreenCoroutineOwner != null && stateMachine != null) { object? value = EnemyOnScreenCoroutineOwner.GetValue(stateMachine); Sync((EnemyOnScreen)((value is EnemyOnScreen) ? value : null)); } } public static void SyncEnemyVisionCoroutine(object stateMachine) { if (EnemyVisionCoroutineOwner != null && stateMachine != null) { object? value = EnemyVisionCoroutineOwner.GetValue(stateMachine); Sync((EnemyVision)((value is EnemyVision) ? value : null)); } } 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) { Debug.LogWarning((object)("[LateJoin] Error syncing PlayerAdded on EnemyOnScreen: " + ex.Message)); } } 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) { Debug.LogWarning((object)("[LateJoin] Error syncing PlayerAdded on EnemyVision: " + ex.Message)); } } 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; } } [HarmonyPatch(typeof(EnemyOnScreen), "OnEnable")] public static class EnemyOnScreenOnEnablePatch { [HarmonyPrefix] public static void Prefix(EnemyOnScreen __instance) { if (Plugin.CfgEnabled.Value) { EnemyPatches.Sync(__instance); } } } [HarmonyPatch(typeof(EnemyVision), "OnEnable")] public static class EnemyVisionOnEnablePatch { [HarmonyPrefix] public static void Prefix(EnemyVision __instance) { if (Plugin.CfgEnabled.Value) { EnemyPatches.Sync(__instance); } } } [HarmonyPatch(typeof(PlayerAvatar), "Start")] public static class PlayerAvatarStartEnemyPatch { [HarmonyPostfix] public static void Postfix() { if (Plugin.CfgEnabled.Value) { EnemyPatches.SyncAll(); } } } [HarmonyPatch] public static class EnemyOnScreenLogicMoveNextPatch { public static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("EnemyOnScreen+<Logic>d__19"); if (!(type == null)) { return AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null); } return null; } [HarmonyPrefix] public static void Prefix(object __instance) { if (Plugin.CfgEnabled.Value) { EnemyPatches.SyncEnemyOnScreenCoroutine(__instance); } } } [HarmonyPatch] public static class EnemyVisionMoveNextPatch { public static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("EnemyVision+<Vision>d__34"); if (!(type == null)) { return AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null); } return null; } [HarmonyPrefix] public static void Prefix(object __instance) { if (Plugin.CfgEnabled.Value) { EnemyPatches.SyncEnemyVisionCoroutine(__instance); } } } public static class InteractivePatches { private sealed class StateSyncEntry { public string TypeName; public string RpcName; public string FieldName; public bool CastToInt; public Type ResolvedType; public FieldInfo ResolvedField; } private static readonly List<StateSyncEntry> entries = new List<StateSyncEntry> { 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) }; private static StateSyncEntry S(string type, string rpc, bool castToInt = true, string field = "currentState") { return new StateSyncEntry { TypeName = type, RpcName = rpc, FieldName = field, CastToInt = castToInt }; } public static void SyncInteractiveObjectsToPlayer(Player target) { if (Plugin.CfgEnabled.Value && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && target != null) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(SyncCoroutine(target)); } } private static IEnumerator SyncCoroutine(Player target) { yield return (object)new WaitForSeconds(3.5f); if (target == null || PhotonNetwork.CurrentRoom == null) { yield break; } int count = 0; foreach (StateSyncEntry entry in entries) { if (entry.ResolvedType == null) { entry.ResolvedType = AccessTools.TypeByName(entry.TypeName); if (entry.ResolvedType != null) { entry.ResolvedField = AccessTools.Field(entry.ResolvedType, entry.FieldName); } } if (entry.ResolvedType == null || entry.ResolvedField == null) { continue; } Object[] array = Object.FindObjectsOfType(entry.ResolvedType); foreach (Object val in array) { if (val == (Object)null) { continue; } Object obj = ((val is Component) ? val : null); PhotonView val2 = ((obj != null) ? ((Component)obj).GetComponent<PhotonView>() : null); if (!((Object)(object)val2 != (Object)null) || val2.ViewID == 0) { continue; } try { object value = entry.ResolvedField.GetValue(val); if (value != null) { object obj2 = (entry.CastToInt ? ((object)Convert.ToInt32(value)) : value); val2.RPC(entry.RpcName, target, new object[1] { obj2 }); count++; } } catch (Exception ex) { Debug.LogWarning((object)$"[LateJoin] Failed to sync state for {entry.TypeName} (ID: {val2.ViewID}): {ex.Message}"); } } yield return (object)new WaitForSeconds(0.05f); } Debug.Log((object)$"[LateJoin] Finished syncing {count} interactive object states to {target.NickName}."); } } [HarmonyPatch(typeof(NetworkManager), "OnPlayerEnteredRoom")] public static class NetworkManagerOnPlayerEnteredRoomInteractivePatch { [HarmonyPostfix] public static void Postfix(Player newPlayer) { if (Plugin.CfgEnabled.Value) { InteractivePatches.SyncInteractiveObjectsToPlayer(newPlayer); } } } public static class ItemPatches { 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 ClearBatteryPending() { Pending.Clear(); } public static void QueueBatterySync(PlayerAvatar playerAvatar) { if (Plugin.CfgEnabled.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) { QueueBatterySync(owner, 2f); } } } public static void QueueAllBatterySync(float delay) { if (Plugin.CfgEnabled.Value && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && PhotonNetwork.PlayerListOthers != null) { Player[] playerListOthers = PhotonNetwork.PlayerListOthers; for (int i = 0; i < playerListOthers.Length; i++) { QueueBatterySync(playerListOthers[i], delay); } } } private static void QueueBatterySync(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 num2 = SendCurrentBatteryState(pendingSync.Target); pendingSync.Attempts++; if (pendingSync.Attempts >= 12) { Pending.RemoveAt(num); Debug.Log((object)$"[LateJoin] Battery state sync completed for {pendingSync.Target.NickName}. Synced {num2} batteries."); } else { pendingSync.NextTime = Time.unscaledTime + 5f; } } } } private static int SendCurrentBatteryState(Player target) { int num = 0; ItemBattery[] array = FindBatteries(); foreach (ItemBattery val in array) { 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) { Debug.LogWarning((object)("[LateJoin] Could not sync battery: " + ex.Message)); } } } return num; } private static ItemBattery[] FindBatteries() { try { return Object.FindObjectsOfType<ItemBattery>(true); } catch { return Object.FindObjectsOfType<ItemBattery>(); } } private static PhotonView GetPhotonView(ItemBattery battery) { if (BatteryPhotonViewField != null) { object? value = BatteryPhotonViewField.GetValue(battery); PhotonView val = (PhotonView)((value is PhotonView) ? value : null); if ((Object)(object)val != (Object)null) { return val; } } return ((Component)battery).GetComponent<PhotonView>(); } private static bool CanReceiveBatteryRpc(PhotonView pv, ItemBattery battery) { if ((Object)(object)pv == (Object)null || (Object)(object)battery == (Object)null) { return false; } ItemBattery component = ((Component)pv).GetComponent<ItemBattery>(); if ((Object)(object)component != (Object)null) { return component == battery; } return false; } private static int GetBatteryLevel(ItemBattery battery) { int num = Mathf.Max(1, battery.batteryBars); int num2 = 0; if (BatteryLifeIntField != null && BatteryLifeIntField.GetValue(battery) is int num3) { num2 = num3; } if (num2 < 0 || num2 > num || (num2 == 0 && battery.batteryLife > 0f)) { num2 = Mathf.RoundToInt(battery.batteryLife / (100f / (float)num)); } return Mathf.Clamp(num2, 0, num); } public static void RepairInventorySpot(InventorySpot spot) { if (!Plugin.CfgEnabled.Value || (Object)(object)spot == (Object)null || !spot.IsOccupied()) { return; } ItemBattery val = FindBatteryInComponent((Component)(object)spot.CurrentItem); if ((Object)(object)val == (Object)null) { return; } int num = RepairBatteryCounters(val); if (!(InventorySpotBatteryVisualLogicField != null)) { return; } object? value = InventorySpotBatteryVisualLogicField.GetValue(spot); BatteryVisualLogic val2 = (BatteryVisualLogic)((value is BatteryVisualLogic) ? value : null); 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) { Debug.LogWarning((object)("[LateJoin] Could not repair inventory battery UI: " + ex.Message)); } } public static void RepairInventoryBatteryResult(Inventory inventory, int index, ref int result) { if (!Plugin.CfgEnabled.Value || (Object)(object)inventory == (Object)null) { return; } InventorySpot spotByIndex = inventory.GetSpotByIndex(index); if ((Object)(object)spotByIndex == (Object)null || !spotByIndex.IsOccupied()) { return; } ItemBattery val = FindBatteryInComponent((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 ItemBattery FindBatteryInComponent(Component c) { if ((Object)(object)c == (Object)null) { return null; } return c.GetComponent<ItemBattery>() ?? c.GetComponentInChildren<ItemBattery>(true) ?? c.GetComponentInParent<ItemBattery>(true); } private static int RepairBatteryCounters(ItemBattery battery) { int batteryLevel = GetBatteryLevel(battery); if (BatteryLifeIntField != null) { BatteryLifeIntField.SetValue(battery, batteryLevel); } if (BatteryCurrentBarsField != null) { BatteryCurrentBarsField.SetValue(battery, batteryLevel); } return batteryLevel; } private static int GetBatteryBars(ItemBattery battery) { if (BatteryCurrentBarsField != null) { object value = BatteryCurrentBarsField.GetValue(battery); if (value is int) { return (int)value; } } return 0; } public static void TakeoverOrphanedPhysGrabObjects(int leftActor) { if (!SemiFunc.IsMasterClient()) { return; } PhysGrabObject[] array = Object.FindObjectsOfType<PhysGrabObject>(); if (array == null) { return; } int num = 0; PhysGrabObject[] array2 = array; foreach (PhysGrabObject val in array2) { if ((Object)(object)val == (Object)null) { continue; } PhotonView component = ((Component)val).GetComponent<PhotonView>(); if ((Object)(object)component != (Object)null && component.ViewID != 0 && (component.Owner == null || component.Owner.ActorNumber == leftActor)) { try { component.TransferOwnership(PhotonNetwork.LocalPlayer); num++; } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] Ownership transfer failed: " + ex.Message)); } } } if (num > 0) { Debug.Log((object)$"[LateJoin] Took over {num} orphaned grabbed objects from leaving player {leftActor}."); } } } [HarmonyPatch(typeof(NetworkManager), "OnPlayerEnteredRoom")] public static class NetworkManagerOnPlayerEnteredRoomPatch { [HarmonyPostfix] public static void Postfix(Player newPlayer) { //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.CfgEnabled.Value || (Object)(object)LevelGenerator.Instance == (Object)null || !LevelGenerator.Instance.Generated) { return; } if (SemiFunc.IsMasterClient()) { Debug.Log((object)("[LateJoin] Mid-game join detected: " + newPlayer.NickName + ". Syncing stats...")); PunManager.instance.SyncAllDictionaries(); PhysGrabObject[] array = Object.FindObjectsOfType<PhysGrabObject>(); foreach (PhysGrabObject val in array) { if (!((Object)(object)val != (Object)null) || val.playerGrabbing == null || val.playerGrabbing.Count <= 0) { continue; } PhotonView component = ((Component)val).GetComponent<PhotonView>(); if (!((Object)(object)component != (Object)null)) { continue; } foreach (PhysGrabber item in val.playerGrabbing) { if ((Object)(object)item != (Object)null && (Object)(object)item.photonView != (Object)null) { component.RPC("GrabPlayerAddRPC", newPlayer, new object[1] { item.photonView.ViewID }); } } } ItemPatches.QueueAllBatterySync(2f); } if (!((Object)(object)PhysGrabber.instance != (Object)null) || !PhysGrabber.instance.grabbed) { return; } PhysGrabber instance = PhysGrabber.instance; PhysGrabObject fieldValue = instance.GetFieldValue<PhysGrabObject>("grabbedPhysGrabObject"); if ((Object)(object)fieldValue != (Object)null && (Object)(object)instance.photonView != (Object)null && instance.photonView.IsMine) { PhotonView component2 = ((Component)fieldValue).GetComponent<PhotonView>(); if ((Object)(object)component2 != (Object)null) { int fieldValue2 = instance.GetFieldValue<int>("grabbedPhysGrabObjectColliderID"); component2.RPC("GrabLinkRPC", newPlayer, new object[5] { instance.photonView.ViewID, fieldValue2, instance.physGrabPoint.position, instance.cameraRelativeGrabbedForward, instance.cameraRelativeGrabbedUp }); } } } } [HarmonyPatch(typeof(NetworkManager), "OnPlayerLeftRoom")] public static class NetworkManagerOnPlayerLeftRoomPatch { [HarmonyPostfix] public static void Postfix(Player otherPlayer) { if (Plugin.CfgEnabled.Value && otherPlayer != null) { ItemPatches.TakeoverOrphanedPhysGrabObjects(otherPlayer.ActorNumber); } } } [HarmonyPatch(typeof(PlayerAvatar), "Start")] public static class PlayerAvatarStartBatteryPatch { [HarmonyPostfix] public static void Postfix(PlayerAvatar __instance) { if (Plugin.CfgEnabled.Value) { ItemPatches.QueueBatterySync(__instance); } } } [HarmonyPatch(typeof(InventorySpot), "StateOccupied")] public static class InventorySpotStateOccupiedPatch { [HarmonyPostfix] public static void Postfix(InventorySpot __instance) { if (Plugin.CfgEnabled.Value) { ItemPatches.RepairInventorySpot(__instance); } } } [HarmonyPatch(typeof(Inventory), "GetBatteryStateFromInventorySpot")] public static class InventoryGetBatteryStateFromInventorySpotPatch { [HarmonyPostfix] public static void Postfix(Inventory __instance, int index, ref int __result) { if (Plugin.CfgEnabled.Value) { ItemPatches.RepairInventoryBatteryResult(__instance, index, ref __result); } } } public static class LevelPatches { 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 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>(); public static void ClearStates() { States.Clear(); Pending.Clear(); } public static bool TrySendBuffered(Module module, bool top, bool bottom, bool right, bool left, bool first) { if (!Plugin.CfgEnabled.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 }; } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] Could not store module connection state: " + ex.Message)); } return false; } public static void Queue(PlayerAvatar playerAvatar) { if (Plugin.CfgEnabled.Value && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && (Object)(object)playerAvatar != (Object)null && (Object)(object)playerAvatar.photonView != (Object)null) { Queue(playerAvatar.photonView.Owner, 3f); } } public static void QueueAllRemote(float delay) { if (Plugin.CfgEnabled.Value && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && PhotonNetwork.PlayerListOthers != null) { Player[] playerListOthers = PhotonNetwork.PlayerListOthers; for (int i = 0; i < playerListOthers.Length; i++) { Queue(playerListOthers[i], 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 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); Debug.Log((object)$"[LateJoin] Sent {num2} module connection states to player {pendingReplay.Target.NickName}."); } 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) { Debug.LogWarning((object)("[LateJoin] Could not replay module connection: " + ex.Message)); } } } return num; } public static void ApplyLocalClientConnections(LevelGenerator levelGenerator) { if (!Plugin.CfgEnabled.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 num = GetInt(ModuleGridXField, val); int num2 = GetInt(ModuleGridYField, val); string key = $"{num}:{num2}"; if (!dictionary.ContainsKey(key)) { dictionary.Add(key, val); } } } int num3 = 0; array2 = array; foreach (Module val2 in array2) { if ((Object)(object)val2 != (Object)null && !IsStartRoom(val2)) { int num4 = GetInt(ModuleGridXField, val2); int num5 = GetInt(ModuleGridYField, val2); bool flag = IsFirstModule(val2); bool top = dictionary.ContainsKey($"{num4}:{num5 + 1}"); bool bottom = dictionary.ContainsKey($"{num4}:{num5 - 1}") || flag; bool right = dictionary.ContainsKey($"{num4 + 1}:{num5}"); bool left = dictionary.ContainsKey($"{num4 - 1}:{num5}"); if (ApplyState(val2, top, bottom, right, left, flag, countModuleReady: true)) { num3++; } } } } 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 flag = GetBool(ModuleSetupDoneField, module); if (flag && 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, !flag); return false; } private static PhotonView GetPhotonView(Module module) { if (ModulePhotonViewField != null) { object? value = ModulePhotonViewField.GetValue(module); PhotonView val = (PhotonView)((value is PhotonView) ? value : null); if ((Object)(object)val != (Object)null) { return val; } } return ((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 flag = 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); foreach (ModulePropSwitch val in componentsInChildren) { if ((Object)(object)val != (Object)null) { if (ModulePropSwitchModuleField != null) { ModulePropSwitchModuleField.SetValue(val, module); } val.Setup(); } } } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] ModulePropSwitch setup failed: " + ex.Message)); } if (countModuleReady && !flag && (Object)(object)LevelGenerator.Instance != (Object)null && LevelGeneratorModulesSpawnedField != null) { int num = GetInt(LevelGeneratorModulesSpawnedField, LevelGenerator.Instance); LevelGeneratorModulesSpawnedField.SetValue(LevelGenerator.Instance, num + 1); } return !flag; } private static bool IsFirstModule(Module module) { if ((Object)(object)module != (Object)null) { if (!GetBool(ModuleFirstField, module) && !IsStartRoom(module) && !((Object)(object)((Component)module).GetComponent<StartRoom>() != (Object)null)) { return (Object)(object)((Component)module).GetComponentInChildren<StartRoom>(true) != (Object)null; } return true; } return false; } private static bool IsStartRoom(Module module) { if ((Object)(object)module != (Object)null) { return GetBool(ModuleStartRoomField, module); } return false; } private static Module[] FindModules() { try { return Object.FindObjectsOfType<Module>(true); } catch { return Object.FindObjectsOfType<Module>(); } } private static bool GetBool(FieldInfo field, object instance) { if (field == null || instance == null) { return false; } object value = field.GetValue(instance); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } 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); if (value is int) { return (int)value; } return 0; } } [HarmonyPatch(typeof(LevelGenerator), "Start")] public static class LevelGeneratorStartPatch { [HarmonyPostfix] public static void Postfix() { if (Plugin.CfgEnabled.Value) { LevelPatches.ClearStates(); } } } [HarmonyPatch(typeof(LevelGenerator), "GenerateDone")] public static class LevelGeneratorGenerateDonePatch { [HarmonyPostfix] public static void Postfix() { if (Plugin.CfgEnabled.Value) { LevelPatches.QueueAllRemote(2f); } } } [HarmonyPatch(typeof(LevelGenerator), "GenerateConnectObjects")] public static class LevelGeneratorGenerateConnectObjectsPatch { [HarmonyPrefix] public static void Prefix(LevelGenerator __instance) { if (Plugin.CfgEnabled.Value) { LevelPatches.ApplyLocalClientConnections(__instance); } } } [HarmonyPatch(typeof(LevelGenerator), "GenerateBlockObjects")] public static class LevelGeneratorGenerateBlockObjectsPatch { [HarmonyPrefix] public static void Prefix(LevelGenerator __instance) { if (Plugin.CfgEnabled.Value) { LevelPatches.ApplyLocalClientConnections(__instance); } } } [HarmonyPatch(typeof(Module), "ModuleConnectionSet")] public static class ModuleConnectionSetPatch { [HarmonyPrefix] public static bool Prefix(Module __instance, bool _top, bool _bottom, bool _right, bool _left, bool _first) { if (Plugin.CfgEnabled.Value) { return !LevelPatches.TrySendBuffered(__instance, _top, _bottom, _right, _left, _first); } return true; } } [HarmonyPatch(typeof(Module), "ModuleConnectionSetRPC")] public static class ModuleConnectionSetRpcPatch { [HarmonyPrefix] public static bool Prefix(Module __instance, bool _top, bool _bottom, bool _right, bool _left, bool _first) { if (Plugin.CfgEnabled.Value) { return LevelPatches.ApplyIncomingRpc(__instance, _top, _bottom, _right, _left, _first); } return true; } } public static class ReflectionHelper { public static T GetFieldValue<T>(this object obj, string fieldName) { FieldInfo fieldInfo = AccessTools.Field(obj.GetType(), fieldName); if (fieldInfo != null) { return (T)fieldInfo.GetValue(obj); } return default(T); } public static void SetFieldValue(this object obj, string fieldName, object value) { FieldInfo fieldInfo = AccessTools.Field(obj.GetType(), fieldName); if (fieldInfo != null) { fieldInfo.SetValue(obj, value); } } } [HarmonyPatch(typeof(MenuPageLobby), "ButtonStart")] public static class MenuPageLobbyButtonStartPatch { [HarmonyPostfix] public static void Postfix() { if (!Plugin.CfgEnabled.Value || !PhotonNetwork.IsMasterClient) { return; } try { if ((Object)(object)SteamManager.instance != (Object)null) { bool fieldValue = SteamManager.instance.GetFieldValue<bool>("privateLobby"); SteamManager.instance.UnlockLobby(!fieldValue); } if (PhotonNetwork.CurrentRoom != null) { PhotonNetwork.CurrentRoom.IsOpen = true; PhotonNetwork.CurrentRoom.IsVisible = true; } } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] ButtonStart Postfix unlock failed: " + ex.Message)); } } } [HarmonyPatch(typeof(SteamManager), "LockLobby")] public static class SteamManagerLockLobbyPatch { [HarmonyPrefix] public static bool Prefix() { if (Plugin.CfgEnabled.Value) { Debug.Log((object)"[LateJoin] Suppressing SteamManager.LockLobby()"); return false; } return true; } } [HarmonyPatch(typeof(RunManager), "ChangeLevel")] public static class RunManagerChangeLevelPatch { private static readonly FieldInfo removeFilterFieldInfo = AccessTools.Field(typeof(PhotonNetwork), "removeFilter"); private static readonly FieldInfo keyByteSevenFieldInfo = AccessTools.Field(typeof(PhotonNetwork), "keyByteSeven"); private static readonly FieldInfo serverCleanOptionsFieldInfo = AccessTools.Field(typeof(PhotonNetwork), "ServerCleanOptions"); private static readonly MethodInfo raiseEventInternalMethodInfo = AccessTools.Method(typeof(PhotonNetwork), "RaiseEventInternal", (Type[])null, (Type[])null); [HarmonyPrefix] public static void Prefix(RunManager __instance, bool _completedLevel, bool _levelFailed, ChangeLevelType _changeLevelType) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.CfgEnabled.Value || _levelFailed || !PhotonNetwork.IsMasterClient) { return; } try { object value = AccessTools.Field(typeof(RunManager), "runManagerPUN").GetValue(__instance); object? value2 = AccessTools.Field(value.GetType(), "photonView").GetValue(value); PhotonView val = (PhotonView)((value2 is PhotonView) ? value2 : null); if (val != null) { PhotonNetwork.RemoveBufferedRPCs(val.ViewID, (string)null, (int[])null); } PhotonView[] array = Object.FindObjectsOfType<PhotonView>(); foreach (PhotonView val2 in array) { if ((Object)(object)val2 != (Object)null) { Scene scene = ((Component)val2).gameObject.scene; if (((Scene)(ref scene)).buildIndex != -1) { ClearPhotonCache(val2); } } } Debug.Log((object)"[LateJoin] Cleared Photon RPC cache for level change."); } catch (Exception ex) { Debug.LogError((object)("[LateJoin] Error in ChangeLevel Prefix: " + ex.Message)); } } [HarmonyPostfix] public static void Postfix() { if (Plugin.CfgEnabled.Value && PhotonNetwork.IsMasterClient) { SteamManager.instance.UnlockLobby(true); if (PhotonNetwork.CurrentRoom != null) { PhotonNetwork.CurrentRoom.IsOpen = true; PhotonNetwork.CurrentRoom.IsVisible = true; } } } private static void ClearPhotonCache(PhotonView pv) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) try { object? value = removeFilterFieldInfo.GetValue(null); Hashtable val = (Hashtable)((value is Hashtable) ? value : null); object value2 = keyByteSevenFieldInfo.GetValue(null); object? value3 = serverCleanOptionsFieldInfo.GetValue(null); RaiseEventOptions val2 = (RaiseEventOptions)((value3 is RaiseEventOptions) ? value3 : null); if (val != null && val2 != null) { val[value2] = pv.InstantiationId; val2.CachingOption = (EventCaching)6; raiseEventInternalMethodInfo.Invoke(null, new object[4] { (byte)202, val, val2, SendOptions.SendReliable }); } } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] ClearPhotonCache failed: " + ex.Message)); } } } public static class LobbyPatches { private static float nextUnlockTime; public static void Update() { if (!Plugin.CfgEnabled.Value || !PhotonNetwork.IsMasterClient || !(Time.unscaledTime >= nextUnlockTime)) { return; } nextUnlockTime = Time.unscaledTime + 2f; try { if ((Object)(object)SteamManager.instance != (Object)null) { bool fieldValue = SteamManager.instance.GetFieldValue<bool>("privateLobby"); SteamManager.instance.UnlockLobby(!fieldValue); } if (PhotonNetwork.CurrentRoom != null) { PhotonNetwork.CurrentRoom.IsOpen = true; PhotonNetwork.CurrentRoom.IsVisible = true; } } catch (Exception ex) { Debug.LogWarning((object)("[LateJoin] Periodic unlock failed: " + ex.Message)); } } } [HarmonyPatch(typeof(PlayerAvatar), "Spawn")] public static class PlayerAvatarSpawnPatch { [HarmonyPrefix] public static bool Prefix(PlayerAvatar __instance) { if (!Plugin.CfgEnabled.Value) { return true; } return !__instance.GetFieldValue<bool>("spawned"); } } [HarmonyPatch(typeof(PlayerAvatar), "Start")] public static class PlayerAvatarStartPatch { private static bool originalGeneratedState; [HarmonyPrefix] public static void Prefix(PlayerAvatar __instance) { if (Plugin.CfgEnabled.Value && (Object)(object)LevelGenerator.Instance != (Object)null) { originalGeneratedState = LevelGenerator.Instance.Generated; if (originalGeneratedState) { LevelGenerator.Instance.Generated = false; } } } [HarmonyPostfix] public static void Postfix(PlayerAvatar __instance) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: 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_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.CfgEnabled.Value || !((Object)(object)LevelGenerator.Instance != (Object)null)) { return; } if (originalGeneratedState) { LevelGenerator.Instance.Generated = true; if (SemiFunc.IsMasterClient() && (Object)(object)__instance.photonView != (Object)null && !__instance.photonView.IsMine) { Vector3 val = Vector3.zero; Quaternion val2 = Quaternion.identity; bool flag = false; if ((Object)(object)TruckSafetySpawnPoint.instance != (Object)null) { val = ((Component)TruckSafetySpawnPoint.instance).transform.position; val2 = ((Component)TruckSafetySpawnPoint.instance).transform.rotation; flag = true; } else { SpawnPoint[] array = Object.FindObjectsOfType<SpawnPoint>(); if (array.Length != 0) { SpawnPoint obj = array[Random.Range(0, array.Length)]; val = ((Component)obj).transform.position; val2 = ((Component)obj).transform.rotation; flag = true; } } if (flag) { Debug.Log((object)$"[LateJoin] Teleporting late-joining player {__instance.photonView.Owner.NickName} to Safety Spawnpoint: {val}"); __instance.Spawn(val, val2); } } } if (PhotonNetwork.IsMasterClient && !SemiFunc.RunIsLobby() && (Object)(object)__instance.photonView != (Object)null) { __instance.photonView.RPC("LoadingLevelAnimationCompletedRPC", (RpcTarget)4, Array.Empty<object>()); } ((MonoBehaviour)__instance).StartCoroutine(InitLateJoinPlayer(__instance)); } private static IEnumerator InitLateJoinPlayer(PlayerAvatar player) { while ((Object)(object)LevelGenerator.Instance == (Object)null || !LevelGenerator.Instance.Generated) { yield return null; } yield return (object)new WaitForSeconds(0.5f); if (!((Object)(object)player == (Object)null) && !((Object)(object)((Component)player).gameObject == (Object)null) && player.photonView.IsMine && SemiFunc.IsMultiplayer() && !SemiFunc.MenuLevel()) { PlayerDeathHead fieldValue = player.GetFieldValue<PlayerDeathHead>("playerDeathHead"); Vector3 fieldValue2 = AssetManager.instance.GetFieldValue<Vector3>("physDisabledPosition"); if (!Object.op_Implicit((Object)(object)fieldValue)) { Debug.Log((object)"[LateJoin] Spawning local DeathHead for self."); PlayerDeathHead component = PhotonNetwork.Instantiate(((Object)LevelGenerator.Instance.PlayerDeathHeadPrefab).name, fieldValue2, Quaternion.identity, (byte)0, (object[])null).GetComponent<PlayerDeathHead>(); component.playerAvatar = player; player.SetFieldValue("playerDeathHead", component); } if (!Object.op_Implicit((Object)(object)player.GetFieldValue<PlayerTumble>("tumble"))) { Debug.Log((object)"[LateJoin] Spawning local Tumble for self."); PlayerTumble component2 = PhotonNetwork.Instantiate(((Object)LevelGenerator.Instance.PlayerTumblePrefab).name, fieldValue2, Quaternion.identity, (byte)0, (object[])null).GetComponent<PlayerTumble>(); component2.playerAvatar = player; player.SetFieldValue("tumble", component2); } } } } }