Decompiled source of Gungeon Online v0.101.0
plugins/GungeonOnline.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Dungeonator; using HarmonyLib; using HutongGames.PlayMaker; using HutongGames.PlayMaker.Actions; using InControl; using Steamworks; using UnityEngine; using UnityEngine.SceneManagement; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: CompilationRelaxations(8)] [assembly: AssemblyVersion("0.0.0.0")] namespace GungeonOnline; public static class AllocProfiler { public static bool Enabled; private static readonly Dictionary<string, long> totals = new Dictionary<string, long>(); private static float nextReportAt; private static long startTotal = -1L; public static void Init() { string[] commandLineArgs = Environment.GetCommandLineArgs(); foreach (string text in commandLineArgs) { if (text == "--gungeon-online-allocprof") { Enabled = true; } } if (Enabled) { Plugin.Log.LogInfo((object)"Allocation profiler on."); } } public static long Begin() { if (!Enabled) { return 0L; } return GC.GetTotalMemory(forceFullCollection: false); } public static void End(string name, long before) { if (Enabled) { long num = GC.GetTotalMemory(forceFullCollection: false) - before; if (num > 0) { totals.TryGetValue(name, out var value); totals[name] = value + num; } } } public static void Report() { if (!Enabled) { return; } long totalMemory = GC.GetTotalMemory(forceFullCollection: false); if (startTotal < 0) { startTotal = totalMemory; nextReportAt = Time.realtimeSinceStartup + 30f; } else if (!(Time.realtimeSinceStartup < nextReportAt)) { nextReportAt = Time.realtimeSinceStartup + 30f; List<KeyValuePair<string, long>> list = new List<KeyValuePair<string, long>>(totals); list.Sort((KeyValuePair<string, long> a, KeyValuePair<string, long> b) => b.Value.CompareTo(a.Value)); string text = "Alloc report (last 30 s): heap " + totalMemory / 1048576 + " MB (+" + (totalMemory - startTotal) / 1024 + " KB);"; for (int num = 0; num < list.Count && num < 12; num++) { object obj = text; text = string.Concat(obj, " ", list[num].Key, "=", list[num].Value / 1024, "KB"); } Plugin.Log.LogInfo((object)text); totals.Clear(); startTotal = totalMemory; } } } public static class BeamSync { private class Shown { public string Prefab; public BeamController Beam; public Vector2 Origin; public Vector2 Direction; public float LastHeard; } private const float SendInterval = 1f / 15f; private static NetSession session; private static readonly FieldInfo moduleDataField = AccessTools.Field(typeof(Gun), "m_moduleData"); private static float nextSendAt; private static int lastSentCount; private static readonly List<Shown> shown = new List<Shown>(); public static void Init(NetSession netSession) { session = netSession; } private static List<BeamController> OurBeams(PlayerController me) { List<BeamController> list = new List<BeamController>(); Gun currentGun = ((GameActor)me).CurrentGun; if ((Object)(object)currentGun == (Object)null || (object)moduleDataField == null) { return list; } if (!(moduleDataField.GetValue(currentGun) is IDictionary dictionary)) { return list; } foreach (object value in dictionary.Values) { ModuleShootData val = (ModuleShootData)((value is ModuleShootData) ? value : null); if (val != null && (Object)(object)val.beam != (Object)null && ((Behaviour)val.beam).isActiveAndEnabled) { list.Add(val.beam); } } return list; } public static void Update() { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) if (session == null || session.Status != NetSession.State.Connected) { Clear(); return; } float realtimeSinceStartup = Time.realtimeSinceStartup; for (int num = BeamSync.shown.Count - 1; num >= 0; num--) { Shown shown = BeamSync.shown[num]; if ((Object)(object)shown.Beam == (Object)null || realtimeSinceStartup - shown.LastHeard > 0.3f) { Stop(shown); BeamSync.shown.RemoveAt(num); } else { shown.Beam.Origin = shown.Origin; shown.Beam.Direction = shown.Direction; shown.Beam.LateUpdatePosition(new Vector3(shown.Origin.x, shown.Origin.y, 0f)); } } if (realtimeSinceStartup < nextSendAt) { return; } nextSendAt = realtimeSinceStartup + 1f / 15f; PlayerController val = (GameManager.HasInstance ? GameManager.Instance.PrimaryPlayer : null); if ((Object)(object)val == (Object)null) { return; } List<BeamController> beams = OurBeams(val); if (beams.Count == 0 && lastSentCount == 0) { return; } lastSentCount = beams.Count; string level = LevelSync.CurrentLevelKey(); session.SendUnreliable(MsgType.Beams, delegate(BinaryWriter w) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) w.Write(level); w.Write((byte)Mathf.Min(beams.Count, 8)); for (int i = 0; i < beams.Count && i < 8; i++) { BeamController val2 = beams[i]; w.Write(((Object)((Component)val2).gameObject).name.Replace("(Clone)", "").Trim()); w.Write(val2.Origin.x); w.Write(val2.Origin.y); w.Write(val2.Direction.x); w.Write(val2.Direction.y); } }); } public static void OnBeams(BinaryReader r) { //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) string text = r.ReadString(); int num = r.ReadByte(); PlayerController val = (GameManager.HasInstance ? GameManager.Instance.PrimaryPlayer : null); bool flag = (Object)(object)val != (Object)null && text == LevelSync.CurrentLevelKey() && !GameManager.Instance.IsLoadingLevel; float realtimeSinceStartup = Time.realtimeSinceStartup; Vector2 origin = default(Vector2); Vector2 val2 = default(Vector2); for (int i = 0; i < num; i++) { string text2 = r.ReadString(); ((Vector2)(ref origin))..ctor(r.ReadSingle(), r.ReadSingle()); ((Vector2)(ref val2))..ctor(r.ReadSingle(), r.ReadSingle()); if (!flag) { continue; } Shown shown = ((i < BeamSync.shown.Count) ? BeamSync.shown[i] : null); if (shown != null && shown.Prefab != text2) { Stop(shown); BeamSync.shown.RemoveAt(i); shown = null; } if (shown == null) { BeamController val3 = Start(text2, origin, val2, val); if ((Object)(object)val3 == (Object)null) { continue; } shown = new Shown(); shown.Prefab = text2; shown.Beam = val3; BeamSync.shown.Insert(Mathf.Min(i, BeamSync.shown.Count), shown); } shown.Origin = origin; shown.Direction = val2; shown.LastHeard = realtimeSinceStartup; } for (int num2 = BeamSync.shown.Count - 1; num2 >= num; num2--) { Stop(BeamSync.shown[num2]); BeamSync.shown.RemoveAt(num2); } } private static BeamController Start(string prefabName, Vector2 origin, Vector2 dir, PlayerController me) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) GameObject val = BulletSync.FindPrefab(prefabName); if ((Object)(object)val == (Object)null || (Object)(object)val.GetComponent<BeamController>() == (Object)null) { return null; } try { GameObject val2 = SpawnManager.SpawnProjectile(val, new Vector3(origin.x, origin.y, 0f), Quaternion.identity, true); Projectile val3 = (((Object)(object)val2 != (Object)null) ? val2.GetComponent<Projectile>() : null); BeamController val4 = (((Object)(object)val2 != (Object)null) ? val2.GetComponent<BeamController>() : null); if ((Object)(object)val3 == (Object)null || (Object)(object)val4 == (Object)null) { return null; } val3.Owner = (GameActor)(object)me; val3.baseData.damage = 0f; val3.collidesWithEnemies = false; val3.collidesWithPlayer = false; val4.Owner = (GameActor)(object)me; val4.HitsPlayers = false; val4.HitsEnemies = false; val4.DamageModifier = 0f; val4.Direction = dir; val4.Origin = origin; return val4; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Couldn't show the partner's beam " + prefabName + ": " + ex.Message)); return null; } } private static void Stop(Shown s) { if (s == null || (Object)(object)s.Beam == (Object)null) { return; } try { s.Beam.CeaseAttack(); } catch { Object.Destroy((Object)(object)((Component)s.Beam).gameObject); } } private static void Clear() { foreach (Shown item in shown) { Stop(item); } shown.Clear(); } } public static class BlankSync { private static NetSession session; private static bool triggeringRemote; public static void Init(NetSession netSession, Harmony harmony) { session = netSession; harmony.PatchAll(typeof(BlankSync)); } [HarmonyPatch(typeof(SilencerInstance), "TriggerSilencer")] [HarmonyPostfix] private static void AfterTriggerSilencer(Vector2 centerPoint, float expandSpeed, float maxRadius, GameObject silencerVFX, float distIntensity, float distRadius, float pushForce, float pushRadius, float knockbackForce, float knockbackRadius, float additionalTimeAtMaxRadius, PlayerController user, bool breaksWalls, bool skipBreakables) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) if (!triggeringRemote && session != null && session.Status == NetSession.State.Connected && !((Object)(object)user == (Object)null) && GameManager.HasInstance && !((Object)(object)user != (Object)(object)GameManager.Instance.PrimaryPlayer)) { bool ghostVfx = (Object)(object)silencerVFX != (Object)null && ((Object)silencerVFX).name.Contains("Ghost"); bool anyVfx = (Object)(object)silencerVFX != (Object)null; string level = LevelSync.CurrentLevelKey(); session.SendReliable(MsgType.Blank, delegate(BinaryWriter w) { w.Write(level); w.Write(centerPoint.x); w.Write(centerPoint.y); w.Write(expandSpeed); w.Write(maxRadius); w.Write(anyVfx); w.Write(ghostVfx); w.Write(distIntensity); w.Write(distRadius); w.Write(pushForce); w.Write(pushRadius); w.Write(knockbackForce); w.Write(knockbackRadius); w.Write(additionalTimeAtMaxRadius); w.Write(breaksWalls); w.Write(skipBreakables); }); } } public static void OnBlank(BinaryReader r) { //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) string text = r.ReadString(); Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(r.ReadSingle(), r.ReadSingle()); float num = r.ReadSingle(); float num2 = r.ReadSingle(); bool flag = r.ReadBoolean(); bool flag2 = r.ReadBoolean(); float num3 = r.ReadSingle(); float num4 = r.ReadSingle(); float num5 = r.ReadSingle(); float num6 = r.ReadSingle(); float num7 = r.ReadSingle(); float num8 = r.ReadSingle(); float num9 = r.ReadSingle(); bool flag3 = r.ReadBoolean(); bool flag4 = r.ReadBoolean(); PlayerController val2 = (GameManager.HasInstance ? GameManager.Instance.PrimaryPlayer : null); if ((Object)(object)val2 == (Object)null || text != LevelSync.CurrentLevelKey()) { return; } GameObject val3 = (GameObject)(flag ? /*isinst with value type is only supported in some contexts*/: null); AkSoundEngine.PostEvent(flag2 ? "Play_OBJ_silenceblank_small_01" : "Play_OBJ_silenceblank_use_01", ((Component)val2).gameObject); triggeringRemote = true; try { SilencerInstance val4 = new GameObject("silencer").AddComponent<SilencerInstance>(); val4.TriggerSilencer(val, num, num2, val3, num3, num4, num5, num6, num7, num8, num9, val2, flag3, flag4); } finally { triggeringRemote = false; } } } public static class BreachCultist { private static NetSession session; private static readonly MethodInfo toggleSelf = AccessTools.Method(typeof(FoyerCharacterSelectFlag), "ToggleSelf", (Type[])null, (Type[])null); private static readonly FieldInfo activeField = AccessTools.Field(typeof(FoyerCharacterSelectFlag), "m_active"); private static FoyerCharacterSelectFlag cultistFlag; private static bool addingStandIn; private static readonly HashSet<CharacterSelectIdleDoer> standIns = new HashSet<CharacterSelectIdleDoer>(); private static readonly FieldInfo extantCardField = AccessTools.Field(typeof(FoyerCharacterSelectFlag), "m_extantOverheadUIElement"); private static bool Online { get { if (session != null) { return session.Link != null; } return false; } } public static void Init(NetSession netSession, Harmony harmony) { session = netSession; harmony.PatchAll(typeof(BreachCultist)); } [HarmonyPatch(typeof(FoyerCharacterSelectFlag), "Update")] [HarmonyPrefix] private static bool BeforeFlagUpdate(FoyerCharacterSelectFlag __instance) { if (!Online || !__instance.IsCoopCharacter) { return true; } if (!(bool)activeField.GetValue(__instance)) { toggleSelf.Invoke(__instance, new object[1] { true }); } return false; } [HarmonyPatch(typeof(Foyer), "SetUpCharacterCallbacks")] [HarmonyPrefix] private static void BeforeCharacterSetup() { if (!Online) { return; } FoyerCharacterSelectFlag[] array = Object.FindObjectsOfType<FoyerCharacterSelectFlag>(); foreach (FoyerCharacterSelectFlag val in array) { if ((Object)(object)val == (Object)null || !val.IsCoopCharacter) { continue; } val.IsCoopCharacter = false; if (string.IsNullOrEmpty(val.CharacterPrefabPath)) { val.CharacterPrefabPath = CharacterSelectController.GetCharacterPathFromIdentity((PlayableCharacters)7); } if (!(bool)activeField.GetValue(val)) { toggleSelf.Invoke(val, new object[1] { true }); } if ((Object)(object)((Component)val).GetComponent<CharacterSelectIdleDoer>() == (Object)null) { addingStandIn = true; try { CharacterSelectIdleDoer val2 = ((Component)val).gameObject.AddComponent<CharacterSelectIdleDoer>(); standIns.Add(val2); ((Behaviour)val2).enabled = false; } finally { addingStandIn = false; } } if ((Object)(object)val.OverheadElement == (Object)null) { FoyerInfoPanelController[] array2 = Resources.FindObjectsOfTypeAll<FoyerInfoPanelController>(); foreach (FoyerInfoPanelController val3 in array2) { if ((Object)(object)val3 != (Object)null && ((Object)val3).name == "CHR_CultistPanel") { val.OverheadElement = ((Component)val3).gameObject; break; } } } if ((Object)(object)val.OverheadElement == (Object)null) { FoyerCharacterSelectFlag[] array3 = Object.FindObjectsOfType<FoyerCharacterSelectFlag>(); foreach (FoyerCharacterSelectFlag val4 in array3) { if ((Object)(object)val4 != (Object)null && (Object)(object)val4 != (Object)(object)val && (Object)(object)val4.OverheadElement != (Object)null) { val.OverheadElement = val4.OverheadElement; cultistFlag = val; break; } } } Plugin.Log.LogInfo((object)("Online: the Cultist (" + val.CharacterPrefabPath + ") can be picked as a character. overhead " + ((Object)(object)val.OverheadElement != (Object)null) + ", sprite " + ((Object)(object)((BraveBehaviour)val).sprite != (Object)null) + ", talkDoer " + ((Object)(object)((BraveBehaviour)val).talkDoer != (Object)null) + ", object " + ((Object)val).name)); } } [HarmonyPrefix] [HarmonyPatch(typeof(CharacterSelectIdleDoer), "OnEnable")] private static bool BeforeIdleEnable(CharacterSelectIdleDoer __instance) { if (!addingStandIn) { return !standIns.Contains(__instance); } return false; } [HarmonyPatch(typeof(CharacterSelectIdleDoer), "Update")] [HarmonyPrefix] private static bool BeforeIdleUpdate(CharacterSelectIdleDoer __instance) { return !standIns.Contains(__instance); } [HarmonyPostfix] [HarmonyPatch(typeof(FoyerCharacterSelectFlag), "CreateOverheadElement")] private static void AfterCreateCard(FoyerCharacterSelectFlag __instance) { if ((Object)(object)__instance != (Object)(object)cultistFlag || (Object)(object)cultistFlag == (Object)null) { return; } object? value = extantCardField.GetValue(__instance); dfControl val = (dfControl)((value is dfControl) ? value : null); if ((Object)(object)val == (Object)null) { return; } bool flag = false; dfLabel[] componentsInChildren = ((Component)val).GetComponentsInChildren<dfLabel>(true); foreach (dfLabel val2 in componentsInChildren) { if (!flag) { val2.Text = "The Cultist"; flag = true; } else { ((dfControl)val2).IsVisible = false; } } dfSprite[] componentsInChildren2 = ((Component)val).GetComponentsInChildren<dfSprite>(true); foreach (dfSprite val3 in componentsInChildren2) { ((dfControl)val3).IsVisible = false; } } [HarmonyPatch(typeof(TalkDoerLite), "Interact")] [HarmonyPrefix] private static bool BeforeTalkToCultist(TalkDoerLite __instance) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Expected O, but got Unknown if (!Online || (Object)(object)__instance == (Object)null || !((Object)__instance).name.Contains("FoyerCharacter_Cultist") || !GameManager.HasInstance || !GameManager.Instance.IsFoyer) { return true; } FoyerCharacterSelectFlag component = ((Component)__instance).GetComponent<FoyerCharacterSelectFlag>(); string playerPrefabPath = (((Object)(object)component != (Object)null && !string.IsNullOrEmpty(component.CharacterPrefabPath)) ? component.CharacterPrefabPath : CharacterSelectController.GetCharacterPathFromIdentity((PlayableCharacters)7)); PlayerController primaryPlayer = GameManager.Instance.PrimaryPlayer; if ((Object)(object)primaryPlayer != (Object)null && (int)primaryPlayer.characterIdentity == 7) { return false; } PlayMakerFSM component2 = ((Component)__instance).GetComponent<PlayMakerFSM>(); FsmState val = (((Object)(object)component2 != (Object)null && component2.Fsm != null) ? (component2.Fsm.ActiveState ?? ((component2.Fsm.States.Length > 0) ? component2.Fsm.States[0] : null)) : null); if (val == null) { return true; } Plugin.Log.LogInfo((object)"Online: talking to the Cultist picks him as our character."); ChangeToNewCharacter val2 = new ChangeToNewCharacter(); val2.PlayerPrefabPath = playerPrefabPath; ((FsmStateAction)val2).Init(val); ((FsmStateAction)val2).OnEnter(); return false; } [HarmonyPatch(typeof(ChangeCoopMode), "OnEnter")] [HarmonyPrefix] private static bool BeforeChangeCoopMode(ChangeCoopMode __instance) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown if (!Online) { return true; } if (__instance.IsTestCoopValid) { ((FsmStateAction)__instance).Fsm.Event(__instance.IfCoopValidEvent); ((FsmStateAction)__instance).Finish(); return false; } if (__instance.TargetCoopMode && !string.IsNullOrEmpty(__instance.PlayerPrefabPath)) { Plugin.Log.LogInfo((object)"Online: picking the Cultist as our character instead of starting local co-op."); ChangeToNewCharacter val = new ChangeToNewCharacter(); val.PlayerPrefabPath = __instance.PlayerPrefabPath; ((FsmStateAction)val).Init(((FsmStateAction)__instance).State); ((FsmStateAction)val).OnEnter(); return false; } ((FsmStateAction)__instance).Finish(); return false; } } public static class BulletSync { private class Tracked { public int Id; public float StartAngle; public Tracked(int id, float angle) { Id = id; StartAngle = angle; } } private const int MaxCoursesPerSend = 24; private static NetSession session; private static GameObject lastPrefab; private static GameObject lastSpawned; private static bool spawningRemote; private static Dictionary<string, GameObject> prefabsByName; private static readonly HashSet<string> missingPrefabs = new HashSet<string>(); private static readonly FieldInfo ownerField = AccessTools.Field(typeof(Projectile), "m_owner"); private static readonly FieldInfo shooterField = AccessTools.Field(typeof(Projectile), "m_shooter"); private static readonly HashSet<Projectile> remoteProjectiles = new HashSet<Projectile>(); private static readonly Dictionary<Projectile, Tracked> ours = new Dictionary<Projectile, Tracked>(); private static readonly Dictionary<int, Projectile> theirs = new Dictionary<int, Projectile>(); private static int nextNetId; private static float nextCourseSendAt; private static GameObject soundEmitter; private static float lastShotSoundAt; public static void Init(NetSession netSession, Harmony harmony) { session = netSession; harmony.PatchAll(typeof(BulletSync)); } [HarmonyPatch(typeof(SpawnManager), "SpawnProjectile", new Type[] { typeof(GameObject), typeof(Vector3), typeof(Quaternion), typeof(bool) })] [HarmonyPostfix] private static void AfterSpawnProjectile(GameObject __0, GameObject __result) { lastPrefab = __0; lastSpawned = __result; if (!spawningRemote && (Object)(object)__result != (Object)null) { Projectile component = __result.GetComponent<Projectile>(); if ((Object)(object)component != (Object)null) { remoteProjectiles.Remove(component); } } } public static void Update() { //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) if (session == null || session.Status != NetSession.State.Connected) { ours.Clear(); theirs.Clear(); } else { if (Time.realtimeSinceStartup < nextCourseSendAt) { return; } nextCourseSendAt = Time.realtimeSinceStartup + 0.1f; if (theirs.Count > 0) { List<int> list = null; foreach (KeyValuePair<int, Projectile> their in theirs) { if ((Object)(object)their.Value == (Object)null || !((Behaviour)their.Value).isActiveAndEnabled || !remoteProjectiles.Contains(their.Value)) { if (list == null) { list = new List<int>(); } list.Add(their.Key); } } if (list != null) { foreach (int item in list) { theirs.Remove(item); } } } if (ours.Count == 0) { return; } List<int> ids = new List<int>(); List<Vector2> positions = new List<Vector2>(); List<float> angles = new List<float>(); List<float> speeds = new List<float>(); List<Projectile> list2 = null; foreach (KeyValuePair<Projectile, Tracked> our in ours) { Projectile key = our.Key; if ((Object)(object)key == (Object)null || !((Behaviour)key).isActiveAndEnabled) { if (list2 == null) { list2 = new List<Projectile>(); } list2.Add(key); continue; } float num = Mathf.Atan2(key.Direction.y, key.Direction.x) * 57.29578f; if (!(Mathf.Abs(Mathf.DeltaAngle(num, our.Value.StartAngle)) < 6f) && ids.Count < 24) { ids.Add(our.Value.Id); positions.Add(Vector2.op_Implicit(((BraveBehaviour)key).transform.position)); angles.Add(num); speeds.Add(key.Speed); } } if (list2 != null) { foreach (Projectile item2 in list2) { ours.Remove(item2); } } if (ids.Count == 0) { return; } string level = LevelSync.CurrentLevelKey(); session.SendUnreliable(MsgType.Courses, delegate(BinaryWriter w) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) w.Write(level); w.Write((byte)ids.Count); for (int i = 0; i < ids.Count; i++) { w.Write(ids[i]); w.Write(positions[i].x); w.Write(positions[i].y); w.Write(angles[i]); w.Write(speeds[i]); } }); } } public static void OnCourses(BinaryReader r) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) string text = r.ReadString(); int num = r.ReadByte(); bool flag = GameManager.HasInstance && text == LevelSync.CurrentLevelKey(); Vector2 val = default(Vector2); for (int i = 0; i < num; i++) { int key = r.ReadInt32(); ((Vector2)(ref val))..ctor(r.ReadSingle(), r.ReadSingle()); float num2 = r.ReadSingle(); float speed = r.ReadSingle(); if (flag && theirs.TryGetValue(key, out var value) && !((Object)(object)value == (Object)null) && ((Behaviour)value).isActiveAndEnabled) { Vector3 position = ((BraveBehaviour)value).transform.position; ((BraveBehaviour)value).transform.position = new Vector3(val.x, val.y, position.z); if ((Object)(object)((BraveBehaviour)value).specRigidbody != (Object)null) { ((BraveBehaviour)value).specRigidbody.Reinitialize(); } float num3 = num2 * ((float)Math.PI / 180f); value.baseData.speed = speed; value.SendInDirection(new Vector2(Mathf.Cos(num3), Mathf.Sin(num3)), true, false); } } } public static bool IsRemote(Projectile p) { if ((Object)(object)p != (Object)null) { return remoteProjectiles.Contains(p); } return false; } [HarmonyPatch(typeof(Projectile), "HandleDamage")] [HarmonyPrefix] private static void BeforeHandleDamage(Projectile __instance) { EnemySync.LocalBulletHitting = !(EnemySync.RemoteBulletHitting = remoteProjectiles.Contains(__instance)); } [HarmonyPatch(typeof(Projectile), "HandleDamage")] [HarmonyPostfix] private static void AfterHandleDamage() { EnemySync.RemoteBulletHitting = false; EnemySync.LocalBulletHitting = false; } [HarmonyPostfix] [HarmonyPatch(typeof(Gun), "ShootSingleProjectile")] private static void AfterShootSingleProjectile(Gun __instance) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) if (spawningRemote || session == null || session.Status != NetSession.State.Connected || !GameManager.HasInstance || (Object)(object)__instance.CurrentOwner == (Object)null || (Object)(object)__instance.CurrentOwner != (Object)(object)GameManager.Instance.PrimaryPlayer) { return; } Projectile lastProjectile = __instance.LastProjectile; if (!((Object)(object)lastProjectile == (Object)null) && !((Object)(object)lastPrefab == (Object)null) && !((Object)(object)((Component)lastProjectile).gameObject != (Object)(object)lastSpawned)) { string prefabName = ((Object)lastPrefab).name; Vector2 pos = Vector2.op_Implicit(((BraveBehaviour)lastProjectile).transform.position); float angle = ((BraveBehaviour)lastProjectile).transform.eulerAngles.z; float speed = lastProjectile.baseData.speed; float damage = lastProjectile.baseData.damage; float range = lastProjectile.baseData.range; float scale = lastProjectile.AdditionalScaleMultiplier; int gunId = ((PickupObject)__instance).PickupObjectId; int netId = ++nextNetId; ours[lastProjectile] = new Tracked(netId, angle); string level = LevelSync.CurrentLevelKey(); session.SendUnreliable(MsgType.Shot, delegate(BinaryWriter w) { w.Write(level); w.Write(prefabName); w.Write(pos.x); w.Write(pos.y); w.Write(angle); w.Write(speed); w.Write(damage); w.Write(range); w.Write(scale); w.Write(gunId); w.Write(netId); }); } } private static GameObject Emitter(Vector2 pos) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown if ((Object)(object)soundEmitter == (Object)null) { soundEmitter = new GameObject("GungeonOnline Partner Sounds"); } soundEmitter.transform.position = new Vector3(pos.x, pos.y, 0f); return soundEmitter; } private static void PlayShotEffects(int gunId, Vector2 pos) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) if (Time.realtimeSinceStartup - lastShotSoundAt < 0.06f) { return; } lastShotSoundAt = Time.realtimeSinceStartup; PickupObject byId = PickupObjectDatabase.GetById(gunId); Gun val = (Gun)(object)((byId is Gun) ? byId : null); if ((Object)(object)val == (Object)null) { return; } try { GameObject val2 = Emitter(pos); if (!string.IsNullOrEmpty(val.gunSwitchGroup)) { AkSoundEngine.SetSwitch("WPN_Guns", val.gunSwitchGroup, val2); } AkSoundEngine.PostEvent(string.IsNullOrEmpty(val.OverrideNormalFireAudioEvent) ? "Play_WPN_gun_shot_01" : val.OverrideNormalFireAudioEvent, val2); if ((Object)(object)val.shellCasing != (Object)null) { GameObject val3 = SpawnManager.SpawnDebris(val.shellCasing, new Vector3(pos.x, pos.y, pos.y), Quaternion.identity); ShellCasing val4 = (((Object)(object)val3 != (Object)null) ? val3.GetComponent<ShellCasing>() : null); if ((Object)(object)val4 != (Object)null) { val4.Trigger(); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Partner gun sound: " + ex.Message)); } } [HarmonyPostfix] [HarmonyPatch(typeof(Gun), "Reload")] private static void AfterReload(Gun __instance, bool __result) { //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_0067: Unknown result type (might be due to invalid IL or missing references) if (__result && session != null && session.Status == NetSession.State.Connected && GameManager.HasInstance && !((Object)(object)__instance.CurrentOwner == (Object)null) && !((Object)(object)__instance.CurrentOwner != (Object)(object)GameManager.Instance.PrimaryPlayer)) { int gunId = ((PickupObject)__instance).PickupObjectId; Vector2 pos = Vector2.op_Implicit(((BraveBehaviour)__instance).transform.position); string level = LevelSync.CurrentLevelKey(); session.SendUnreliable(MsgType.Reload, delegate(BinaryWriter w) { w.Write(level); w.Write(gunId); w.Write(pos.x); w.Write(pos.y); }); } } public static void OnReload(BinaryReader r) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) string text = r.ReadString(); int num = r.ReadInt32(); Vector2 pos = default(Vector2); ((Vector2)(ref pos))..ctor(r.ReadSingle(), r.ReadSingle()); if (GameManager.HasInstance && !(text != LevelSync.CurrentLevelKey())) { PickupObject byId = PickupObjectDatabase.GetById(num); Gun val = (Gun)(object)((byId is Gun) ? byId : null); GameObject val2 = Emitter(pos); if ((Object)(object)val != (Object)null && !string.IsNullOrEmpty(val.gunSwitchGroup)) { AkSoundEngine.SetSwitch("WPN_Guns", val.gunSwitchGroup, val2); } AkSoundEngine.PostEvent("Play_WPN_gun_reload_01", val2); } } public static void OnShot(BinaryReader r) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) string text = r.ReadString(); string name = r.ReadString(); Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(r.ReadSingle(), r.ReadSingle()); float num = r.ReadSingle(); float speed = r.ReadSingle(); float damage = r.ReadSingle(); float range = r.ReadSingle(); float additionalScaleMultiplier = r.ReadSingle(); int gunId = r.ReadInt32(); int key = r.ReadInt32(); if (!GameManager.HasInstance || (Object)(object)GameManager.Instance.PrimaryPlayer == (Object)null || text != LevelSync.CurrentLevelKey()) { return; } PlayShotEffects(gunId, val); GameObject val2 = FindPrefab(name); if (!((Object)(object)val2 == (Object)null)) { spawningRemote = true; GameObject val3; try { val3 = SpawnManager.SpawnProjectile(val2, Vector2.op_Implicit(val), Quaternion.Euler(0f, 0f, num), true); } finally { spawningRemote = false; } Projectile val4 = (((Object)(object)val3 != (Object)null) ? val3.GetComponent<Projectile>() : null); if (!((Object)(object)val4 == (Object)null)) { remoteProjectiles.Add(val4); theirs[key] = val4; ownerField.SetValue(val4, null); shooterField.SetValue(val4, null); val4.collidesWithPlayer = session.PvpEnabled; val4.collidesWithEnemies = true; val4.baseData.speed = speed; val4.baseData.damage = damage; val4.baseData.range = range; val4.AdditionalScaleMultiplier = additionalScaleMultiplier; float num2 = num * ((float)Math.PI / 180f); val4.SendInDirection(new Vector2(Mathf.Cos(num2), Mathf.Sin(num2)), true, true); } } } public static GameObject FindPrefab(string name) { if (prefabsByName == null) { BuildPrefabIndex(); } if (prefabsByName.TryGetValue(name, out var value) && (Object)(object)value != (Object)null) { return value; } if (missingPrefabs.Add(name)) { Plugin.Log.LogWarning((object)("Partner fired projectile '" + name + "', which isn't a known gun projectile; not shown.")); } return null; } private static void BuildPrefabIndex() { prefabsByName = new Dictionary<string, GameObject>(); int num = 0; foreach (PickupObject @object in ((ObjectDatabase<PickupObject>)(object)PickupObjectDatabase.Instance).Objects) { Gun val = (Gun)(object)((@object is Gun) ? @object : null); if (!((Object)(object)val == (Object)null)) { num++; AddModule(val.singleModule); AddVolley(val.RawSourceVolley); AddVolley(val.alternateVolley); AddVolley(val.rawOptionalReloadVolley); AddProjectile(val.CriticalReplacementProjectile); } } Plugin.Log.LogInfo((object)("Indexed " + prefabsByName.Count + " projectile types from " + num + " guns.")); } private static void AddVolley(ProjectileVolleyData volley) { if ((Object)(object)volley == (Object)null || volley.projectiles == null) { return; } foreach (ProjectileModule projectile in volley.projectiles) { AddModule(projectile); } } private static void AddModule(ProjectileModule module) { if (module == null) { return; } if (module.projectiles != null) { foreach (Projectile projectile in module.projectiles) { AddProjectile(projectile); } } if (module.chargeProjectiles == null) { return; } foreach (ChargeProjectile chargeProjectile in module.chargeProjectiles) { if (chargeProjectile != null) { AddProjectile(chargeProjectile.Projectile); } } } private static void AddProjectile(Projectile p) { if ((Object)(object)p != (Object)null && !prefabsByName.ContainsKey(((Object)((Component)p).gameObject).name)) { prefabsByName[((Object)((Component)p).gameObject).name] = ((Component)p).gameObject; } } } public static class CustomCharacterSupport { private static bool looked; private static IDictionary stored; private static Type customCharacterType; private static FieldInfo dataField; private static FieldInfo nameInternalField; private static FieldInfo nameField; private static string lastLocalName = ""; private static void Find() { if (looked) { return; } looked = true; try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type = assembly.GetType("CustomCharacters.CharacterBuilder", throwOnError: false); if ((object)type != null) { FieldInfo field = type.GetField("storedCharacters", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); stored = (((object)field != null) ? (field.GetValue(null) as IDictionary) : null); customCharacterType = assembly.GetType("CustomCharacters.CustomCharacter", throwOnError: false); Type type2 = assembly.GetType("CustomCharacters.CustomCharacterData", throwOnError: false); if ((object)customCharacterType != null) { dataField = customCharacterType.GetField("data", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if ((object)type2 != null) { nameInternalField = type2.GetField("nameInternal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); nameField = type2.GetField("name", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } Plugin.Log.LogInfo((object)("Custom Characters mod found: " + ((stored != null) ? stored.Count : 0) + " custom characters.")); break; } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Custom Characters support: " + ex.Message)); stored = null; } } public static string LocalName(PlayerController player) { Find(); if ((Object)(object)player == (Object)null || (object)customCharacterType == null || (object)dataField == null) { return ""; } Component component = ((Component)player).GetComponent(customCharacterType); if ((Object)(object)component == (Object)null) { return ""; } object value = dataField.GetValue(component); string text = NameOf(value); if (text != lastLocalName) { lastLocalName = text; Plugin.Log.LogInfo((object)("Playing custom character '" + text + "': the partner will see it if they have it too" + (((Object)(object)FindPrefab(text) != (Object)null) ? "." : " (couldn't find it in our own list!)."))); } return text; } private static string NameOf(object data) { if (data == null) { return ""; } string text = (((object)nameInternalField != null) ? (nameInternalField.GetValue(data) as string) : null); if (string.IsNullOrEmpty(text) && (object)nameField != null) { text = nameField.GetValue(data) as string; } return text ?? ""; } public static GameObject FindPrefab(string name) { Find(); if (string.IsNullOrEmpty(name) || stored == null) { return null; } foreach (DictionaryEntry item in stored) { object value = item.Value; if (value != null) { object data = Member(value, "First"); string a = item.Key as string; if (string.Equals(a, name, StringComparison.OrdinalIgnoreCase) || string.Equals(NameOf(data), name, StringComparison.OrdinalIgnoreCase)) { object obj = Member(value, "Second"); return (GameObject)((obj is GameObject) ? obj : null); } } } return null; } private static object Member(object o, string name) { Type type = o.GetType(); FieldInfo field = type.GetField(name); if ((object)field != null) { return field.GetValue(o); } return type.GetProperty(name)?.GetValue(o, null); } public static GameObject PartnerPrefab(RemotePlayerState remote, out string description) { GameObject val = FindPrefab(remote.CustomCharacter); if ((Object)(object)val != (Object)null) { description = "custom character " + remote.CustomCharacter; return val; } string characterPathFromIdentity = CharacterSelectController.GetCharacterPathFromIdentity((PlayableCharacters)remote.Identity); description = characterPathFromIdentity + ((remote.CustomCharacter.Length > 0) ? (" (their custom character " + remote.CustomCharacter + " isn't installed here)") : ""); Object obj = BraveResources.Load(characterPathFromIdentity, ".prefab"); return (GameObject)(object)((obj is GameObject) ? obj : null); } } public static class DeathSync { private const float AllDeadGrace = 1f; private const float PartnerGoneGrace = 3f; private static NetSession session; private static readonly FieldInfo isFallingField = AccessTools.Field(typeof(GameActor), "m_isFalling"); private static readonly MethodInfo handleDeathMethod = AccessTools.Method(typeof(PlayerController), "HandleDeath_CR", (Type[])null, (Type[])null); private static float allDeadSince = -1f; private static bool gameOverStarted; public static Chest ReviveChest; private static bool Connected { get { if (session != null) { return session.Status == NetSession.State.Connected; } return false; } } private static bool PartnerAliveHere { get { RemotePlayerState remote = session.Remote; if (remote.HasPlayer && !remote.IsDead) { return remote.Scene == LevelSync.CurrentLevelKey(); } return false; } } public static bool PartnerIsGhost { get { if (Connected && session.Remote.HasPlayer) { return session.Remote.IsDead; } return false; } } public static void Init(NetSession netSession, Harmony harmony) { session = netSession; harmony.PatchAll(typeof(DeathSync)); } [HarmonyPrefix] [HarmonyPatch(typeof(PlayerController), "Die")] private static bool BeforeDie(PlayerController __instance) { if (!Connected || !GameManager.HasInstance || (Object)(object)__instance != (Object)(object)GameManager.Instance.PrimaryPlayer || __instance.IsGhost) { return true; } if (GameManager.Instance.IsFoyer) { return true; } if (PartnerAliveHere) { Plugin.Log.LogInfo((object)"We died; partner is alive, so we become a ghost until they open a chest."); bool flag = (bool)isFallingField.GetValue(__instance); ((MonoBehaviour)__instance).StartCoroutine(__instance.HandleCoopDeath(flag)); session.SendReliable(MsgType.PlayerDied, null); return false; } if (session.Remote.HasPlayer && session.Remote.IsDead) { Plugin.Log.LogInfo((object)"We died while our partner is a ghost: game over for both."); session.SendReliable(MsgType.TeamWiped, null); } return true; } public static void OnPartnerDied() { Plugin.Log.LogInfo((object)(session.PeerName + " died and is a ghost. Open a chest to bring them back.")); } public static void Update() { PlayerController val = (GameManager.HasInstance ? GameManager.Instance.PrimaryPlayer : null); if ((Object)(object)val == (Object)null || !val.IsGhost || gameOverStarted || GameManager.Instance.IsLoadingLevel) { allDeadSince = -1f; return; } if (Connected && (!session.Remote.HasPlayer || !session.Remote.IsDead)) { allDeadSince = -1f; return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (allDeadSince < 0f) { allDeadSince = realtimeSinceStartup; } else if (realtimeSinceStartup - allDeadSince >= (Connected ? 1f : 3f)) { Plugin.Log.LogInfo((object)(Connected ? "Both players are down: game over." : "We're a ghost and our partner is gone: game over.")); ForceGameOver(); } } public static void OnTeamWiped() { Plugin.Log.LogInfo((object)"Partner died too: game over."); ForceGameOver(); } public static void ForceGameOver() { if (!GameManager.HasInstance) { return; } PlayerController primaryPlayer = GameManager.Instance.PrimaryPlayer; if ((Object)(object)primaryPlayer == (Object)null || (!primaryPlayer.IsGhost && !((BraveBehaviour)primaryPlayer).healthHaver.IsDead)) { Plugin.Log.LogWarning((object)"Game over requested but we're alive; ignoring."); } else if (!gameOverStarted) { gameOverStarted = true; try { ((MonoBehaviour)primaryPlayer).StartCoroutine((IEnumerator)handleDeathMethod.Invoke(primaryPlayer, null)); } catch (Exception ex) { Plugin.Log.LogError((object)("Couldn't show the death screen: " + ex)); gameOverStarted = false; } } } public static void ResetGameOver() { gameOverStarted = false; } [HarmonyPrefix] [HarmonyPatch(typeof(Chest), "Open")] private static bool BeforeChestOpen(Chest __instance, PlayerController player) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) if (!PartnerIsGhost || (Object)(object)player == (Object)null || player.IsGhost || ((BraveBehaviour)player).healthHaver.IsDead) { return true; } if ((int)__instance.ChestIdentifier != 0) { return true; } __instance.IsOpen = true; ReviveChest = __instance; if ((Object)(object)((BraveBehaviour)__instance).spriteAnimator != (Object)null && !string.IsNullOrEmpty(__instance.openAnimName)) { ((BraveBehaviour)__instance).spriteAnimator.Play(__instance.openAnimName); } Vector2 val = (((Object)(object)((BraveBehaviour)__instance).sprite != (Object)null) ? ((BraveBehaviour)__instance).sprite.WorldBottomCenter : Vector2.op_Implicit(((BraveBehaviour)__instance).transform.position)); Plugin.Log.LogInfo((object)("Opened a chest while our partner is a ghost: bringing them back at " + val)); SendRevive(val, fromBoss: false); return false; } [HarmonyPostfix] [HarmonyPatch(typeof(RoomHandler), "HandleBossClearReward")] private static void AfterBossClearReward() { //IL_004b: Unknown result type (might be due to invalid IL or missing references) PlayerController val = (GameManager.HasInstance ? GameManager.Instance.PrimaryPlayer : null); if (PartnerIsGhost && !((Object)(object)val == (Object)null) && !val.IsGhost && !((BraveBehaviour)val).healthHaver.IsDead) { Plugin.Log.LogInfo((object)"Boss beaten while our partner is a ghost: bringing them back."); SendRevive(((GameActor)val).CenterPosition, fromBoss: true); } } private static void SendRevive(Vector2 at, bool fromBoss) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) session.SendReliable(MsgType.Revive, delegate(BinaryWriter w) { w.Write(at.x); w.Write(at.y); w.Write(fromBoss); }); } public static void OnRevive(BinaryReader r) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(r.ReadSingle(), r.ReadSingle()); bool flag = r.ReadBoolean(); if (!GameManager.HasInstance) { return; } PlayerController primaryPlayer = GameManager.Instance.PrimaryPlayer; if (!((Object)(object)primaryPlayer == (Object)null) && primaryPlayer.IsGhost) { Plugin.Log.LogInfo((object)(flag ? "Partner beat the boss: coming back to life." : "Partner opened a chest: coming back to life.")); if (!flag) { OpenChestNear(val); } primaryPlayer.ResurrectFromChest(val); ((MonoBehaviour)primaryPlayer).StartCoroutine(FinishRevive(primaryPlayer)); } } private static IEnumerator FinishRevive(PlayerController p) { float waited = 0f; while (waited < 4f && (Object)(object)p != (Object)null && (p.IsCurrentlyCoopReviving || ((BraveBehaviour)p).spriteAnimator.IsPlaying("ghost_revive") || waited < 1.5f)) { waited += Time.unscaledDeltaTime; yield return null; } if (!((Object)(object)p == (Object)null) && !p.IsGhost) { p.CurrentInputState = (PlayerInputState)0; ((BraveBehaviour)p).healthHaver.IsVulnerable = true; p.ClearInputOverride("revivepause"); p.IsCurrentlyCoopReviving = false; Plugin.Log.LogInfo((object)"Revive finished: control restored."); } } [HarmonyPatch(typeof(PlayerController), "ReuniteWithOtherPlayer")] [HarmonyPrefix] private static bool BeforeReunite(PlayerController other) { if ((Object)(object)other != (Object)null) { return true; } if (Connected) { PlayerWarp.TeleportToPartner(); } return false; } private static void OpenChestNear(Vector2 at) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) Chest val = null; float num = 3f; Object[] array = Object.FindObjectsOfType(typeof(Chest)); for (int i = 0; i < array.Length; i++) { Chest val2 = (Chest)array[i]; if (!((Object)(object)val2 == (Object)null) && !val2.IsOpen && !((Object)(object)((BraveBehaviour)val2).sprite == (Object)null)) { float num2 = Vector2.Distance(((BraveBehaviour)val2).sprite.WorldBottomCenter, at); if (num2 < num) { val = val2; num = num2; } } } if (!((Object)(object)val == (Object)null)) { val.IsOpen = true; if ((Object)(object)((BraveBehaviour)val).spriteAnimator != (Object)null && !string.IsNullOrEmpty(val.openAnimName)) { ((BraveBehaviour)val).spriteAnimator.Play(val.openAnimName); } } } } public class DebugWindow { public bool Visible; private readonly NetSession session; private readonly RemotePuppet puppet; private Rect windowRect = new Rect(20f, 20f, 320f, 10f); private string nameField; private string addressField; private string message = ""; private GUIStyle nameStyle; private GUIStyle nameShadow; private string nameText; private string nameFor; public DebugWindow(NetSession session, RemotePuppet puppet) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) this.session = session; this.puppet = puppet; nameField = Plugin.DisplayName(); addressField = Plugin.LastLanAddress.Value; } public void Draw() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected O, but got Unknown //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) DrawRemoteMarker(); if (PauseSync.PartnerPaused) { GUIStyle val = new GUIStyle(GUI.skin.label); val.fontSize = 28; val.alignment = (TextAnchor)4; val.normal.textColor = Color.white; GUI.Label(new Rect(0f, (float)Screen.height * 0.35f - 30f, (float)Screen.width, 60f), session.PeerName + " paused the game", val); } if (LevelSync.WaitingForHost) { GUIStyle val2 = new GUIStyle(GUI.skin.label); val2.fontSize = 28; val2.alignment = (TextAnchor)4; val2.normal.textColor = Color.white; GUI.Label(new Rect(0f, (float)Screen.height * 0.5f - 30f, (float)Screen.width, 60f), "Waiting for the host to restart...", val2); } if (Visible) { windowRect = GUILayout.Window(7239169, windowRect, new WindowFunction(DrawContents), "Gungeon Online 0.101.0", (GUILayoutOption[])(object)new GUILayoutOption[0]); } } private void DrawContents(int id) { DrawNameRow(); GUILayout.Space(6f); if (session.Link == null) { DrawOfflineOptions(); } else { DrawSessionInfo(); } if (message.Length > 0) { GUILayout.Label(message, (GUILayoutOption[])(object)new GUILayoutOption[0]); } GUILayout.Label("F8 hides this window.", (GUILayoutOption[])(object)new GUILayoutOption[0]); GUI.DragWindow(); } private void DrawNameRow() { GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Label("Name:", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(45f) }); nameField = GUILayout.TextField(nameField, 24, (GUILayoutOption[])(object)new GUILayoutOption[0]); if (GUILayout.Button("Save", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) })) { Plugin.PlayerName.Value = nameField.Trim(); message = "Name saved. Others will see: " + Plugin.DisplayName(); } GUILayout.EndHorizontal(); if (nameField.Trim().Length == 0) { GUILayout.Label("(empty = your Steam name)", (GUILayoutOption[])(object)new GUILayoutOption[0]); } bool flag = GUILayout.Toggle(Plugin.ShowPartnerName.Value, " Show partner's name (only on your screen)", (GUILayoutOption[])(object)new GUILayoutOption[0]); if (flag != Plugin.ShowPartnerName.Value) { Plugin.ShowPartnerName.Value = flag; } } private void DrawOfflineOptions() { GUILayout.Label("-- Steam --", (GUILayoutOption[])(object)new GUILayoutOption[0]); if (session.SteamAvailable) { if (GUILayout.Button("Host on Steam", (GUILayoutOption[])(object)new GUILayoutOption[0])) { session.HostSteam(); } GUILayout.Label("To join: accept a friend's Steam invite.", (GUILayoutOption[])(object)new GUILayoutOption[0]); } else { GUILayout.Label("Steam isn't available (game not started through Steam).", (GUILayoutOption[])(object)new GUILayoutOption[0]); } GUILayout.Space(6f); GUILayout.Label("-- LAN (same network) --", (GUILayoutOption[])(object)new GUILayoutOption[0]); if (GUILayout.Button("Host on LAN (port " + Plugin.LanPort.Value + ")", (GUILayoutOption[])(object)new GUILayoutOption[0]) && !session.HostLan(Plugin.LanPort.Value)) { message = "Couldn't host. Is the port already in use? See the log."; } GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]); addressField = GUILayout.TextField(addressField, (GUILayoutOption[])(object)new GUILayoutOption[0]); if (GUILayout.Button("Join", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) })) { JoinAddress(addressField); } GUILayout.EndHorizontal(); GUILayout.Label("Type the host's address, e.g. 192.168.1.20", (GUILayoutOption[])(object)new GUILayoutOption[0]); if (GUILayout.Button("Find LAN games", (GUILayoutOption[])(object)new GUILayoutOption[0])) { session.Discovery.Search(Plugin.LanPort.Value); } foreach (LanDiscovery.FoundHost item in session.Discovery.Found) { string text = string.Concat(item.Name, " (", item.Address, ")", item.SameVersion ? "" : " [different version]"); if (GUILayout.Button(text, (GUILayoutOption[])(object)new GUILayoutOption[0]) && item.SameVersion) { session.Discovery.Stop(); Join(item.Address); } } if (session.Discovery.Searching && session.Discovery.Found.Count == 0) { GUILayout.Label("Searching...", (GUILayoutOption[])(object)new GUILayoutOption[0]); } } private void DrawSessionInfo() { Transport link = session.Link; GUILayout.Label("Mode: " + link.Kind + (link.IsHost ? " (host)" : ""), (GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Label(link.Info, (GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Label("Status: " + session.Status, (GUILayoutOption[])(object)new GUILayoutOption[0]); if (session.PeerName.Length > 0) { GUILayout.Label("Partner: " + session.PeerName, (GUILayoutOption[])(object)new GUILayoutOption[0]); } if (session.PingMs >= 0f) { GUILayout.Label("Ping: " + Mathf.RoundToInt(session.PingMs) + " ms", (GUILayoutOption[])(object)new GUILayoutOption[0]); } RemotePlayerState remote = session.Remote; if (session.Status == NetSession.State.Connected) { if (remote.HasPlayer) { GUILayout.Label("Partner at " + ((Vector2)(ref remote.Position)).ToString("F1") + " in " + remote.Scene, (GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Label(string.Concat("Partner is ", (object)(PlayableCharacters)remote.Identity, remote.AltCostume ? " (alt costume)" : "", ", anim ", remote.Clip, " #", remote.Frame), (GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Label("Partner sprite: " + puppet.Details(), (GUILayoutOption[])(object)new GUILayoutOption[0]); if (LevelSync.WaitingToJoin) { GUILayout.Label("Host started a new run: Quick Restart to join it.", (GUILayoutOption[])(object)new GUILayoutOption[0]); } if (remote.IsDead) { GUILayout.Label("PARTNER IS A GHOST: open a chest to bring them back!", (GUILayoutOption[])(object)new GUILayoutOption[0]); } PlayerController val = (GameManager.HasInstance ? GameManager.Instance.PrimaryPlayer : null); if ((Object)(object)val != (Object)null && val.IsGhost) { GUILayout.Label("You are a ghost: your partner can bring you back by opening a chest.", (GUILayoutOption[])(object)new GUILayoutOption[0]); } } else { GUILayout.Label("Partner is in menus (" + remote.Scene + ")", (GUILayoutOption[])(object)new GUILayoutOption[0]); } } if (session.Status == NetSession.State.Connected) { string reason; bool flag = (GUI.enabled = PlayerWarp.CanTeleportToPartner(out reason)); if (GUILayout.Button(flag ? "Teleport to partner" : ("Teleport to partner (" + reason + ")"), (GUILayoutOption[])(object)new GUILayoutOption[0])) { PlayerWarp.TeleportToPartner(); } GUI.enabled = true; } if (link.IsHost) { bool flag3 = GUILayout.Toggle(Plugin.Pvp.Value, " PvP: bullets hurt each other", (GUILayoutOption[])(object)new GUILayoutOption[0]); if (flag3 != Plugin.Pvp.Value) { Plugin.Pvp.Value = flag3; session.SendRules(); } } else { GUILayout.Label("PvP: " + (session.PvpEnabled ? "ON (host's choice)" : "off (host's choice)"), (GUILayoutOption[])(object)new GUILayoutOption[0]); } if (link == session.Steam && link.IsHost && GUILayout.Button("Invite friend (Steam overlay)", (GUILayoutOption[])(object)new GUILayoutOption[0])) { session.Steam.Lobby.OpenInviteOverlay(); } if (GUILayout.Button("Leave", (GUILayoutOption[])(object)new GUILayoutOption[0])) { session.Leave(); message = ""; } } private void JoinAddress(string text) { text = text.Trim(); int result = Plugin.LanPort.Value; string ipString = text; int num = text.LastIndexOf(':'); if (num > 0) { ipString = text.Substring(0, num); if (!int.TryParse(text.Substring(num + 1), out result)) { message = "That port isn't a number."; return; } } if (!IPAddress.TryParse(ipString, out IPAddress address)) { message = "That isn't an IP address (like 192.168.1.20)."; return; } Plugin.LastLanAddress.Value = text; Join(new IPEndPoint(address, result)); } private void Join(IPEndPoint host) { message = (session.JoinLan(host) ? "" : "Couldn't start joining. See the log."); } private void DrawRemoteMarker() { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected O, but got Unknown //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Expected O, but got Unknown //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) RemotePlayerState remote = session.Remote; if (session.Status != NetSession.State.Connected || !remote.HasPlayer || remote.Scene != LevelSync.CurrentLevelKey() || !GameManager.HasInstance || (Object)(object)GameManager.Instance.MainCameraController == (Object)null) { return; } Camera camera = GameManager.Instance.MainCameraController.Camera; if ((Object)(object)camera == (Object)null || !Plugin.ShowPartnerName.Value || !puppet.TryGetHeadTop(out var world)) { return; } Vector3 val = camera.WorldToScreenPoint(world); if (!(val.z < 0f)) { if (nameStyle == null) { nameStyle = new GUIStyle(GUI.skin.label); nameStyle.alignment = (TextAnchor)7; nameStyle.fontStyle = (FontStyle)1; nameShadow = new GUIStyle(nameStyle); nameShadow.normal.textColor = new Color(0f, 0f, 0f, 0.85f); } if (nameText == null || nameFor != session.PeerName) { nameFor = session.PeerName; nameText = session.PeerName; } Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(val.x - 100f, (float)Screen.height - val.y - 26f, 200f, 24f); GUI.Label(new Rect(((Rect)(ref val2)).x + 1f, ((Rect)(ref val2)).y + 1f, ((Rect)(ref val2)).width, ((Rect)(ref val2)).height), nameText, nameShadow); GUI.Label(val2, nameText, nameStyle); } } } public static class EnemySync { private class Correction { public Vector2 Target; public float ReceivedAt; } private const float StateInterval = 0.1f; private const float KillHealth = 1000000f; private const float SnapDistance = 4f; private const float SettleDistance = 0.25f; private const float PullSpeed = 6f; private static NetSession session; private static Dungeon currentDungeon; private static readonly Dictionary<AIActor, int> idOf = new Dictionary<AIActor, int>(); private static readonly Dictionary<int, AIActor> byId = new Dictionary<int, AIActor>(); private static readonly Dictionary<RoomHandler, int> roomCounters = new Dictionary<RoomHandler, int>(); private static readonly HashSet<int> deadIds = new HashSet<int>(); private static readonly List<AIActor> killQueue = new List<AIActor>(); private static readonly Dictionary<AIActor, float> queuedAt = new Dictionary<AIActor, float>(); private static float nextStateAt; private static bool applyingRemote; public static bool RemoteBulletHitting; public static bool LocalBulletHitting; private static float healthBefore; private static readonly HashSet<AIActor> remotelyKilled = new HashSet<AIActor>(); private static readonly Dictionary<AIActor, Correction> corrections = new Dictionary<AIActor, Correction>(); private static float sealedEmptySince = -1f; private static bool applyingRemoteRoom; private static readonly HashSet<int> clearedSent = new HashSet<int>(); private static bool Connected { get { if (session != null) { return session.Status == NetSession.State.Connected; } return false; } } private static bool LocalIsGhost { get { PlayerController val = (GameManager.HasInstance ? GameManager.Instance.PrimaryPlayer : null); if ((Object)(object)val != (Object)null) { if (!val.IsGhost) { return ((BraveBehaviour)val).healthHaver.IsDead; } return true; } return false; } } private static PlayerController Me { get { if (!GameManager.HasInstance) { return null; } return GameManager.Instance.PrimaryPlayer; } } public static void Init(NetSession netSession, Harmony harmony) { session = netSession; harmony.PatchAll(typeof(EnemySync)); } private static void CheckFloor() { Dungeon val = (GameManager.HasInstance ? GameManager.Instance.Dungeon : null); if (!((Object)(object)val == (Object)(object)currentDungeon)) { currentDungeon = val; idOf.Clear(); byId.Clear(); roomCounters.Clear(); deadIds.Clear(); killQueue.Clear(); queuedAt.Clear(); corrections.Clear(); clearedSent.Clear(); } } [HarmonyPatch(typeof(RoomHandler), "RegisterEnemy")] [HarmonyPostfix] private static void AfterRegisterEnemy(RoomHandler __instance, AIActor enemy) { if ((Object)(object)enemy == (Object)null || !GameManager.HasInstance || (Object)(object)GameManager.Instance.Dungeon == (Object)null) { return; } CheckFloor(); if (idOf.ContainsKey(enemy)) { return; } int num = GameManager.Instance.Dungeon.data.rooms.IndexOf(__instance); if (num >= 0) { roomCounters.TryGetValue(__instance, out var value); roomCounters[__instance] = value + 1; int num2 = num * 1000 + value; idOf[enemy] = num2; byId[num2] = enemy; if (deadIds.Contains(num2)) { killQueue.Add(enemy); queuedAt[enemy] = Time.realtimeSinceStartup; } } } private static AIActor Find(int id, string guid) { if (!byId.TryGetValue(id, out var value) || (Object)(object)value == (Object)null || (Object)(object)((BraveBehaviour)value).healthHaver == (Object)null || ((BraveBehaviour)value).healthHaver.IsDead) { return null; } if (!(value.EnemyGuid == guid)) { return null; } return value; } [HarmonyPatch(typeof(HealthHaver), "ApplyDamage")] [HarmonyPrefix] private static bool BeforeApplyDamage(HealthHaver __instance) { if ((Object)(object)((BraveBehaviour)__instance).aiActor != (Object)null && Connected && !applyingRemote) { if (RemoteBulletHitting) { return false; } if (LocalIsGhost) { return false; } } healthBefore = __instance.GetCurrentHealth(); return true; } [HarmonyPatch(typeof(HealthHaver), "ApplyDamage")] [HarmonyPostfix] private static void AfterApplyDamage(HealthHaver __instance) { if (applyingRemote || !Connected || (Object)(object)((BraveBehaviour)__instance).aiActor == (Object)null || !idOf.TryGetValue(((BraveBehaviour)__instance).aiActor, out var id)) { return; } float dealt = healthBefore - __instance.GetCurrentHealth(); string level = LevelSync.CurrentLevelKey(); string guid = ((BraveBehaviour)__instance).aiActor.EnemyGuid; if (__instance.IsDead) { deadIds.Add(id); session.SendReliable(MsgType.EnemyDied, delegate(BinaryWriter w) { w.Write(level); w.Write(id); w.Write(guid); }); } else if (dealt > 0f && LocalBulletHitting) { session.SendReliable(MsgType.EnemyDamage, delegate(BinaryWriter w) { w.Write(level); w.Write(id); w.Write(guid); w.Write(dealt); }); } } public static void OnEnemyDamage(BinaryReader r) { string text = r.ReadString(); int id = r.ReadInt32(); string guid = r.ReadString(); float amount = r.ReadSingle(); if (!(text != LevelSync.CurrentLevelKey())) { CheckFloor(); AIActor val = Find(id, guid); if ((Object)(object)val != (Object)null) { ApplyRemoteDamage(val, amount); } } } public static void OnEnemyDied(BinaryReader r) { string text = r.ReadString(); int num = r.ReadInt32(); string guid = r.ReadString(); if (!(text != LevelSync.CurrentLevelKey())) { CheckFloor(); deadIds.Add(num); AIActor val = Find(num, guid); if ((Object)(object)val != (Object)null) { KillFromPartner(val); } } } [HarmonyPrefix] [HarmonyPatch(typeof(HealthHaver), "BossHealthSanityCheck")] private static bool BeforeBossHealthSanityCheck(ref bool __result) { if (!applyingRemote) { return true; } __result = true; return false; } [HarmonyPrefix] [HarmonyPatch(typeof(AIActor), "HandleLootPinata")] private static bool BeforeLootDrop(AIActor __instance) { if (!remotelyKilled.Remove(__instance)) { return true; } if ((Object)(object)((BraveBehaviour)__instance).healthHaver != (Object)null) { return ((BraveBehaviour)__instance).healthHaver.IsBoss; } return false; } private static void KillFromPartner(AIActor a) { if (remotelyKilled.Count > 500) { remotelyKilled.Clear(); } remotelyKilled.Add(a); ApplyRemoteDamage(a, 1000000f); if ((Object)(object)a == (Object)null || (Object)(object)((BraveBehaviour)a).healthHaver == (Object)null || !((BraveBehaviour)a).healthHaver.IsDead) { remotelyKilled.Remove(a); } } private static void ApplyRemoteDamage(AIActor a, float amount) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) applyingRemote = true; try { ((BraveBehaviour)a).healthHaver.ApplyDamage(amount, Vector2.zero, "Partner", (CoreDamageTypes)0, (DamageCategory)0, true, (PixelCollider)null, true); } finally { applyingRemote = false; } } private static bool InCharge(RoomHandler room) { PlayerController me = Me; if ((Object)(object)me != (Object)null && !LocalIsGhost && room != null) { return me.CurrentRoom == room; } return false; } public static void Update() { if (!Connected || !GameManager.HasInstance) { return; } CheckFloor(); float realtimeSinceStartup = Time.realtimeSinceStartup; for (int num = killQueue.Count - 1; num >= 0; num--) { AIActor val = killQueue[num]; if ((Object)(object)val == (Object)null || (Object)(object)((BraveBehaviour)val).healthHaver == (Object)null || ((BraveBehaviour)val).healthHaver.IsDead) { killQueue.RemoveAt(num); } else { queuedAt.TryGetValue(val, out var value); if ((val.ParentRoom != null && val.ParentRoom.IsSealed) || realtimeSinceStartup - value > 1.5f) { killQueue.RemoveAt(num); KillFromPartner(val); } } } ApplyCorrections(); UnsealIfStuck(realtimeSinceStartup); if (realtimeSinceStartup < nextStateAt) { return; } nextStateAt = realtimeSinceStartup + 0.1f; List<int> ids = new List<int>(); List<AIActor> actors = new List<AIActor>(); foreach (KeyValuePair<int, AIActor> item in byId) { AIActor value2 = item.Value; if ((Object)(object)value2 != (Object)null && (Object)(object)((BraveBehaviour)value2).healthHaver != (Object)null && !((BraveBehaviour)value2).healthHaver.IsDead && ((Component)value2).gameObject.activeInHierarchy && InCharge(value2.ParentRoom)) { ids.Add(item.Key); actors.Add(value2); } } if (ids.Count == 0) { return; } string level = LevelSync.CurrentLevelKey(); session.SendUnreliable(MsgType.EnemyStates, delegate(BinaryWriter w) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) w.Write(level); w.Write((ushort)ids.Count); for (int i = 0; i < ids.Count; i++) { Vector2 val2 = (((Object)(object)((BraveBehaviour)actors[i]).specRigidbody != (Object)null) ? ((BraveBehaviour)actors[i]).specRigidbody.UnitCenter : Vector2.op_Implicit(((BraveBehaviour)actors[i]).transform.position)); w.Write(ids[i]); w.Write(val2.x); w.Write(val2.y); w.Write(((BraveBehaviour)actors[i]).healthHaver.GetCurrentHealth()); } }); } public static void OnEnemyStates(BinaryReader r) { //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) string text = r.ReadString(); int num = r.ReadUInt16(); if (text != LevelSync.CurrentLevelKey()) { return; } CheckFloor(); float realtimeSinceStartup = Time.realtimeSinceStartup; Vector2 target = default(Vector2); for (int i = 0; i < num; i++) { int key = r.ReadInt32(); ((Vector2)(ref target))..ctor(r.ReadSingle(), r.ReadSingle()); float num2 = r.ReadSingle(); if (byId.TryGetValue(key, out var value) && !((Object)(object)value == (Object)null) && !((Object)(object)((BraveBehaviour)value).healthHaver == (Object)null) && !((BraveBehaviour)value).healthHaver.IsDead && (!InCharge(value.ParentRoom) || !session.Link.IsHost)) { if (num2 < ((BraveBehaviour)value).healthHaver.GetCurrentHealth()) { ((BraveBehaviour)value).healthHaver.ForceSetCurrentHealth(num2); } if (!corrections.TryGetValue(value, out var value2)) { value2 = new Correction(); corrections[value] = value2; } value2.Target = target; value2.ReceivedAt = realtimeSinceStartup; } } } private static void ApplyCorrections() { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: 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_00eb: 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) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) if (corrections.Count == 0) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; List<AIActor> list = null; foreach (KeyValuePair<AIActor, Correction> correction in corrections) { AIActor key = correction.Key; if ((Object)(object)key == (Object)null || (Object)(object)((BraveBehaviour)key).healthHaver == (Object)null || ((BraveBehaviour)key).healthHaver.IsDead || (Object)(object)((BraveBehaviour)key).specRigidbody == (Object)null || realtimeSinceStartup - correction.Value.ReceivedAt > 0.5f) { if (list == null) { list = new List<AIActor>(); } list.Add(key); continue; } Vector2 val = correction.Value.Target - ((BraveBehaviour)key).specRigidbody.UnitCenter; float magnitude = ((Vector2)(ref val)).magnitude; if (!(magnitude < 0.25f)) { Vector2 val2 = ((magnitude > 4f) ? val : (val * Mathf.Min(1f, Time.deltaTime * 6f))); Transform transform = ((BraveBehaviour)key).transform; transform.position += new Vector3(val2.x, val2.y, 0f); ((BraveBehaviour)key).specRigidbody.Reinitialize(); } } if (list == null) { return; } foreach (AIActor item in list) { corrections.Remove(item); } } private static void UnsealIfStuck(float now) { PlayerController me = Me; RoomHandler val = (((Object)(object)me != (Object)null) ? me.CurrentRoom : null); if (val == null || !val.IsSealed || val.HasActiveEnemies((ActiveEnemyType)1)) { sealedEmptySince = -1f; } else if (sealedEmptySince < 0f) { sealedEmptySince = now; } else if (now - sealedEmptySince > 2f) { Plugin.Log.LogInfo((object)"Room is sealed with no enemies left (partner cleared it); unsealing."); val.UnsealRoom(); sealedEmptySince = -1f; } } [HarmonyPostfix] [HarmonyPatch(typeof(RoomHandler), "UnsealRoom")] private static void AfterUnsealRoom(RoomHandler __instance) { if (applyingRemoteRoom || !Connected || LocalIsGhost || !GameManager.HasInstance || (Object)(object)GameManager.Instance.Dungeon == (Object)null || __instance.HasActiveEnemies((ActiveEnemyType)1)) { return; } CheckFloor(); int roomIndex = GameManager.Instance.Dungeon.data.rooms.IndexOf(__instance); if (roomIndex >= 0 && clearedSent.Add(roomIndex)) { string level = LevelSync.CurrentLevelKey(); session.SendReliable(MsgType.RoomCleared, delegate(BinaryWriter w) { w.Write(level); w.Write(roomIndex); }); } } public static void OnRoomCleared(BinaryReader r) { string text = r.ReadString(); int num = r.ReadInt32(); if (text != LevelSync.CurrentLevelKey() || !GameManager.HasInstance || (Object)(object)GameManager.Instance.Dungeon == (Object)null) { return; } List<RoomHandler> rooms = GameManager.Instance.Dungeon.data.rooms; if (num < 0 || num >= rooms.Count) { return; } RoomHandler val = rooms[num]; CheckFloor(); foreach (KeyValuePair<AIActor, int> item in idOf) { if ((Object)(object)item.Key != (Object)null && item.Key.ParentRoom == val) { deadIds.Add(item.Value); } } List<AIActor> activeEnemies = val.GetActiveEnemies((ActiveEnemyType)1); int num2 = 0; if (activeEnemies != null) { foreach (AIActor item2 in new List<AIActor>(activeEnemies)) { if ((Object)(object)item2 != (Object)null && (Object)(object)((BraveBehaviour)item2).healthHaver != (Object)null && !((BraveBehaviour)item2).healthHaver.IsDead) { KillFromPartner(item2); num2++; } } } if (num2 > 0) { Plugin.Log.LogInfo((object)("Partner cleared room " + val.GetRoomName() + "; removed " + num2 + " enemies left in our copy.")); } if (!val.IsSealed) { return; } applyingRemoteRoom = true; try { val.UnsealRoom(); } finally { applyingRemoteRoom = false; } } [HarmonyPostfix] [HarmonyPatch(typeof(RoomHandler), "SealRoom")] private static void AfterSealRoom(RoomHandler __instance) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Invalid comparison between Unknown and I4 //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) PlayerController me = Me; if (!Connected || (Object)(object)me == (Object)null || LocalIsGhost || me.CurrentRoom != __instance) { return; } bool flag = __instance.area != null && (int)__instance.area.PrototypeRoomCategory == 3; if (DeathSync.PartnerIsGhost || flag) { Vector2 pos = ((GameActor)me).CenterPosition; string level = LevelSync.CurrentLevelKey(); session.SendReliable(MsgType.PullGhost, delegate(BinaryWriter w) { w.Write(level); w.Write(pos.x); w.Write(pos.y); }); } } public static void OnPullGhost(BinaryReader r) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Invalid comparison between Unknown and I4 string text = r.ReadString(); new Vector2(r.ReadSingle(), r.ReadSingle()); PlayerController me = Me; if ((Object)(object)me == (Object)null || text != LevelSync.CurrentLevelKey()) { return; } if (!me.IsGhost) { if (((BraveBehaviour)me).healthHaver.IsDead || (me.CurrentRoom != null && me.CurrentRoom.area != null && (int)me.CurrentRoom.area.PrototypeRoomCategory == 3)) { return; } Toast.Show("Boss fight! Bringing you to your partner.", 3f); } PlayerWarp.ScheduleGhostPull(); } } public static class ExpandSync { private static NetSession session; private static Harmony harmony; private static bool setUp; private static int tries; private static bool Online { get { if (session != null) { return session.Status == NetSession.State.Connected; } return false; } } public static void Init(NetSession netSession, Harmony h) { session = netSession; harmony = h; } public static void Update() { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Expected O, but got Unknown //IL_00b4: Expected O, but got Unknown if (setUp || tries > 600) { return; } tries++; if (tries % 60 != 1) { return; } Type type = AccessTools.TypeByName("ExpandTheGungeon.ExpandComponents.ExpandJungleTreeController"); if ((object)type == null) { return; } setUp = true; Type[] nestedTypes = type.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic); foreach (Type type2 in nestedTypes) { if (!type2.Name.StartsWith("<Start>")) { continue; } MethodInfo methodInfo = AccessTools.Method(type2, "MoveNext", (Type[])null, (Type[])null); if ((object)methodInfo != null) { try { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ExpandSync), "BeforeStep", (Type[])null), new HarmonyMethod(typeof(ExpandSync), "AfterStep", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"Expand the Gungeon found: its jungle elevator room now matches between players."); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Couldn't patch Expand the Gungeon's jungle tree: " + ex.Message)); } } } } private static void BeforeStep(object __instance, out object __state) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) __state = null; if (Online && GameManager.HasInstance && GameManager.Instance.CurrentRunSeed != 0) { int num = GameManager.Instance.CurrentRunSeed; object value = Traverse.Create(__instance).Field("<>4__this").GetValue(); Component val = (Component)((value is Component) ? value : null); if ((Object)(object)val != (Object)null) { Vector3 position = val.transform.position; num = num * 31 + Mathf.RoundToInt(position.x) * 7919 + Mathf.RoundToInt(position.y); } __state = Random.state; Random.InitState(num); } } private static void AfterStep(object __state) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) if (__state != null) { Random.state = (State)__state; } } } public static class FloorCheck { private const int MaxRetries = 2; private static NetSession session; private static Dungeon printed; private static string ourKey = ""; private static uint ourHash; private static List<string> ourRooms = new List<string>(); private static float loadedAt = -1f; private static string hostKey = ""; private static uint hostHash; private static List<string> hostRooms = new List<string>(); private static bool compared; private static int retries; private static string retriesForKey = ""; public static bool Mismatch { get; private set; } private static bool Connected { get { if (session != null && session.Status == NetSession.State.Connected) { return session.Link != null; } return false; } } public static void Init(NetSession netSession) { session = netSession; } public static void Update() { if (!Connected || !GameManager.HasInstance) { return; } GameManager instance = GameManager.Instance; if (instance.IsLoadingLevel || (Object)(object)instance.Dungeon == (Object)null || instance.IsFoyer || (Object)(object)instance.PrimaryPlayer == (Object)null) { loadedAt = -1f; } else if ((Object)(object)instance.Dungeon == (Object)(object)printed) { if (!session.Link.IsHost && !compared) { Compare(); } } else if (loadedAt < 0f) { loadedAt = Time.realtimeSinceStartup; } else if (!(Time.realtimeSinceStartup - loadedAt < 1f)) { TakePrint(instance.Dungeon); compared = false; if (session.Link.IsHost) { SendPrint(); } } } public static void OnConnected() { if (Connected && session.Link.IsHost && (Object)(object)printed != (Object)null && (Object)(object)printed == (Object)(object)(GameManager.HasInstance ? GameManager.Instance.Dungeon : null)) { SendPrint(); } } private static void TakePrint(Dungeon d) { printed = d; ourKey = LevelSync.CurrentLevelKey() + "|" + GameManager.Instance.CurrentRunSeed; ourRooms = new List<string>(); foreach (RoomHandler room in d.data.rooms) { if (room == null || room.area == null) { ourRooms.Add("?"); continue; } CellArea area = room.area; ourRooms.Add((area.PrototypeRoomName ?? "procedural") + "@" + area.basePosition.x + "," + area.basePosition.y + "/" + area.dimensions.x + "x" + area.dimensions.y); } ourHash = Hash(ourRooms); Plugin.Log.LogInfo((object)("Floor fingerprint: " + ourRooms.Count + " rooms, " + ourHash.ToString("X8") + " (" + ourKey + ")")); } private static uint Hash(List<string> rooms) { uint num = 2166136261u; foreach (string room in rooms) { string text = room; foreach (char c in text) { num ^= c; num *= 16777619; } num ^= 0x7C; num *= 16777619; } return num; } private static void SendPrint() { string key = ourKey; uint hash = ourHash; List<string> rooms = ourRooms; session.SendReliable(MsgType.FloorPrint, delegate(BinaryWriter w) { w.Write(key); w.Write(hash); w.Write(rooms.Count); foreach (string item in rooms) { w.Write(item); } }); } public static void OnFloorPrint(BinaryReader r) { hostKey = r.ReadString(); hostHash = r.ReadUInt32(); int num = r.ReadInt32(); hostRooms = new List<string>(); for (int i = 0; i < num && i < 2000; i++) { hostRooms.Add(r.ReadString()); } compared = false; } private static void Compare() { if (hostKey.Length == 0 || hostKey != ourKey) { return; } compared = true; if (hostHash == ourHash && hostRooms.Count == ourRooms.Count) { if (Mismatch) { Toast.Show("Floor now matches the host's.", 4f); } Mismatch = false; Plugin.Log.LogInfo((object)("Floor matches the host's (" + ourHash.ToString("X8") + ").")); return; } Mismatch = true; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Floor does NOT match the host's: ours " + ourRooms.Count + " rooms " + ourHash.ToString("X8") + ", host " + hostRooms.Count + " rooms " + hostHash.ToString("X8") + ". First differences:"); int num = 0; for (int i = 0; i < Mathf.Max(ourRooms.Count, hostRooms.Count); i++) { if (num >= 8) { break; } string text = ((i < ourRooms.Count) ? ourRooms[i] : "-"); string text2 = ((i < hostRooms.Count) ? hostRooms[i] : "-"); if (!(text == text2)) { stringBuilder.Append("\n room " + i + ": ours " + text + " | host " + text2); num++; } } Plugin.Log.LogWarning((object)stringBuilder.ToString()); if (retriesForKey != ourKey) { retriesForKey = ourKey; retries = 0; } if (retries < 2 && LevelSync.ReloadHostFloor()) { retries++; Toast.Show("This floor didn't match the host's. Reloading it (try " + retries + " of " + 2 + ")...", 6f); printed = null; } else { Toast.Show("This floor still doesn't match the host's. Enemies and items may be out of sync. Please send your LogOutput.log.", 15f); } } } public static class InventorySync { private class Snapshot { public int Seed; public string Character = ""; public List<int> GunIds = new List<int>(); public List<int> GunAmmo = new List<int>(); public List<int> Passives = new List<int>(); public List<int> Actives = new List<int>(); public float Health; public float MaxHealth; public float Armor; public int Blanks; public void Write(BinaryWriter w) { w.Write(Seed); w.Write(Character ?? ""); w.Write(GunIds.Count); for (int i = 0; i < GunIds.Count; i++) { w.Write(GunIds[i]); w.Write(GunAmmo[i]); } WriteList(w, Passives); WriteList(w, Actives); w.Write(Health); w.Write(MaxHealth); w.Write(Armor); w.Write(Blanks); } public static Snapshot Read(BinaryReader r) { Snapshot snapshot = new Snapshot(); snapshot.Seed = r.ReadInt32(); snapshot.Character = r.ReadString(); int num = r.ReadInt32(); for (int i = 0; i < num; i++) { snapshot.GunIds.Add(r.ReadInt32()); snapshot.GunAmmo.Add(r.ReadInt32()); } snapshot.Passives = ReadList(r); snapshot.Actives = ReadList(r); snapshot.Health = r.ReadSingle(); snapshot.MaxHealth = r.ReadSingle(); snapshot.Armor = r.ReadSingle(); snapshot.Blanks = r.ReadInt32(); return snapshot; } public string Key() { MemoryStream memoryStream = new MemoryStream(); BinaryWriter binaryWriter = new BinaryWriter(memoryStream); Write(binaryWriter); binaryWriter.Flush(); return Convert.ToBase64String(memoryStream.ToArray()); } } private const float CheckInterval = 2f; private static NetSession session; private static float nextCheckAt; private static string lastSentKey = ""; private static readonly Dictionary<string, Snapshot> remembered = new Dictionary<string, Snapshot>(); private static bool askWhenReady; private static float answerPendingUntil = -1f; private static string rejoinCharacter; private static float rejoinOfferedAt; private static bool rejoinPicked; public static bool HasRejoinOffer => rejoinCharacter != null; private static bool Connected { get { if (session != null && session.Status == NetSession.State.Connected) { return session.Link != null; } return false; } } private static void WriteList(BinaryWriter w, List<int> list) { w.Write(list.Count); foreach (int item in list) { w.Write(item); } } private static List<int> ReadList(BinaryReader r) { int num = r.ReadInt32(); List<int> list = new List<int>(); for (int i = 0; i < num && i < 1000; i++) { list.Add(r.ReadInt32()); } return list; } public static void Init(NetSession netSession) { session = netSession; } public static void OnJoinedHostRun(bool savedRun) { askWhenReady = !savedRun; lastSentKey = ""; } private static Snapshot Take(PlayerController me) { Snapshot snapshot = new Snapshot(); snapshot.Seed = GameManager.Instance.CurrentRunSeed; snapshot.Character = ((Object)((Component)me).gameObject).name.Replace("(Clone)", "").Trim(); if (me.inventory != null) { foreach (Gun allGun in me.inventory.AllGuns) { if (!((Object)(object)allGun == (Object)null)) { snapshot.GunIds.Add(((PickupObject)allGun).PickupObjectId); snapshot.GunAmmo.Add(allGun.CurrentAmmo); } } } foreach (PassiveItem passiveItem in me.passiveItems) { if ((Object)(object)passiveItem != (Object)null) { snapshot.Passives.Add(((PickupObject)passiveItem).PickupObjectId); } } foreach (PlayerItem activeItem in me.activeItems) { if ((Object)(object)activeItem != (Object)null) { snapshot.Actives.Add(((PickupObject)activeItem).PickupObjectId); } } snapshot.Health = ((BraveBehaviour)me).healthHaver.GetCurrentHealth(); snapshot.MaxHealth = ((BraveBehaviour)me).healthHaver.GetMaxHealth(); snapshot.Armor = ((BraveBehaviour)me).healthHaver.Armor; snapshot.Blanks = me.Blanks; return snapshot; } public static void Update() { if (!Connected || !GameManager.HasInstance) { return; } GameManager instance = GameManager.Instance; PlayerController primaryPlayer = instance.PrimaryPlayer; bool flag = (Object)(object)primaryPlayer != (Object)null && !instance.IsFoyer && !instance.IsLoadingLevel && (Object)(object)instance.Dungeon != (Object)null && instance.CurrentRunSeed != 0; if (session.Link.IsHost || !flag) { return; } if (askWhenReady) { askWhenReady = false; answerPendingUntil = Time.realtimeSinceStartup + 6f; int seed = instance.CurrentRunSeed; session.SendReliable(MsgType.InventoryRequest, delegate(BinaryWriter w) { w.Write(seed); }); } if (Time.realtimeSinceStartup < answerPendingUntil || Time.realtimeSinceStartup < nextCheckAt || primaryPlayer.IsGhost || ((BraveBehaviour)primaryPlayer).healthHaver.IsDead) { return; } nextCheckAt = Time.realtimeSinceStartup + 2f; Snapshot s = Take(primaryPlayer); string text = s.Key(); if (!(text == lastSentKey)) { lastSentKey = text; session.SendReliable(MsgType.Inventory, delegate(BinaryWriter w) { s.Write(w); }); } } public static void OnConnected() { if (session == null || session.Link == null || !session.Link.IsHost || !GameManager.HasInstance) { return; } GameManager instance = GameManager.Instance; if (!instance.IsFoyer && instance.CurrentRunSeed != 0 && remembered.TryGetValue(session.PeerName ?? "", out var value) && value.Seed == instance.CurrentRunSeed) { string character = value.Character; Plugin.Log.LogInfo((object)(session.PeerName + " is back: offering their place in the run as " + character + ".")); session.SendReliable(MsgType.RejoinOffer, delegate(BinaryWriter w) { w.Write(character); }); } } public static void OnRejoinOffer(BinaryReader r) { rejoinCharacter = r.ReadString(); rejoinOfferedAt = Time.realtimeSinceStartup; rejoinPicked = false; Plugin.Log.LogInfo((object)("The host remembers us in its run (" + rejoinCharacter + "): rejoining automatically.")); } public static void UpdateRejoin() { if (rejoinCharacter == null || session == null || session.Link == null || session.Link.IsHost) { return; } if (Time.realtimeSinceStartup - rejoinOfferedAt > 120f || !LevelSync.HostInRun) { rejoinCharacter = null; return; } GameManager val = (GameManager.HasInstance ? GameManager.Instance : null); if ((Object)(object)val == (Object)null || !val.IsFoyer || val.IsLoadingLevel || (Object)(object)Foyer.Instance == (Object)null) { return; } if (!rejoinPicked && val.IsSelectingCharacter && !Foyer.IsCurrentlyPlayingCharacterSelect) { FoyerCharacterSelectFlag[] array = Object.FindObjectsOfType<FoyerCharacterSelectFlag>(); foreach (FoyerCharacterSelectFlag val2 in array) { if (!((Object)(object)val2 == (Object)null) && !string.IsNullOrEmpty(val2.CharacterPrefabPath)) { string text = val2.CharacterPrefabPath; int num = text.LastIndexOf('/'); if (num >= 0) { text = text.Substring(num + 1); } if (string.Equals(text, rejoinCharacter, StringComparison.OrdinalIgnoreCase)) { rejoinPicked = true; Plugin.Log.LogInfo((object)("Picking our old character " + rejoinCharacter + ".")); ((MonoBehaviour)Foyer.Instance).StartCoroutine(Foyer.Instance.OnSelectedCharacter(0.25f, val2)); return; } } } rejoinPicked = true; Plugin.Log.LogWarning((object)("Couldn't find our old character " + rejoinCharacter + " to pick; pick any character.")); } else if (!val.IsSelectingCharacter && (Object)(object)val.PrimaryPlayer != (Object)null && !Foyer.IsCurrentlyPlayingCharacterSelect) { rejoinCharacter = null; Plugin.Log.LogInfo((object)"Rejoining the host's run."); Toast.Show("Rejoining the run...", 3f); if ((Object)(object)Pixelator.Instance != (Object)null) { Pixelator.Instance.FadeToBlack(0.5f, false, 0f); } Foyer.Instance.OnDepartedFoyer(); val.DelayedLoadNextLevel(0.5f); } } public static void OnInventory(BinaryReader r) { Snapshot value = Snapshot.Read(r); remembered[session.PeerName ?? ""] = value; } public static void OnInventoryRequest(BinaryReader r) { int num = r.ReadInt32(); if (!remembered.TryGetValue(session.PeerName ?? "", out var s) || s.Seed != num || !GameManager.HasInstance || GameManager.Instance.CurrentRunSeed != num) { session.SendReliable(MsgType.InventoryRestore, delegate(BinaryWriter w) { w.Write(value: false); }); return; } Plugin.Log.LogInfo((object)(session.PeerName + " rejoined our run: sending back their items.")); session.SendReliable(MsgType.InventoryRestore, delegate(BinaryWriter w) { w.Write(value: true); s.Write(w); }); } public static void OnInventoryRestore(BinaryReader r) { answerPendingUntil = -1f; rejoinCharacter = null; if (!r.ReadBoolean()) { Plugin.Log.LogInfo((object)"The host has no items saved for us in this run."); return; } Snapshot snapshot = Snapshot.Read(r); PlayerController val = (GameManager.HasInstance ? GameManager.Instance.PrimaryPlayer : null); if ((Object)(object)val == (Object)null || GameManager.Instance.CurrentRunSeed != snapshot.Seed) { return; } int num = 0; for (int i = 0; i < snapshot.GunIds.Count; i++) { Gun val2 = null; foreach (Gun allGun in val.inventory.AllGuns) { if ((Object)(object)allGun != (Object)null && ((PickupObject)allGun).PickupObjectId == snapshot.GunIds[i]) { val2 = allGun; } } if ((Object)(object)val2 == (Object)null) { PickupObject byId = PickupObjectDatabase.GetById(snapshot.GunIds[i]); Gun val3 = (Gun)(object)((byId is Gun) ? byId : null); if ((Object)(object)val3 == (Object)null) { continue; } val2 = val.inventory.AddGunToInventory(val3, false); num++; } if ((Object)(object)val2 != (Object)null) { val2.CurrentAmmo = snapshot.GunAmmo[i]; } } num += GiveMissing(val, snapshot.Passives); num += GiveMissing(val, snapshot.Actives); if (snapshot.MaxHealth > 0f) { ((BraveBehaviour)val).healthHaver.SetHealthMaximum(snapshot.MaxHealth, (float?)null, false); ((BraveBehaviour)val).healthHaver.ForceSetCurrentHealth(Mathf.Max(0.5f, snapshot.Health)); } ((BraveBehaviour)val).healthHaver.Armor = snapshot.Armor; val.Blanks = snapshot.Blanks; Plugin.Log.LogInfo((object)("The host remembered our items: got " + num + " back.")); Toast.Show("Welcome back! Your items and health were restored.", 5f); } private static int GiveMissing(PlayerController me, List<int> ids) { Dictionary<int, int> dictionary = new Dictionary<int, int>(); foreach (PassiveItem passiveItem in me.passiveItems) { if ((Object)(object)passiveItem != (Object)null) { dictionary[((PickupObject)passiveItem).PickupObjectId] = (dictionary.ContainsKey(((PickupObject)passiveItem).PickupObjectId) ? dictionary[((PickupObject)passiveItem).PickupObjectId] : 0) + 1; } } foreach (PlayerItem activeItem in me.activeItems) { if ((Object)(object)activeItem != (Object)null) { dictionary[((PickupObject)activeItem).PickupObjectId] = (dictionary.ContainsKey(((PickupObject)activeItem).PickupObjectId) ? dictionary[((PickupObject)activeItem).PickupObjectId] : 0) + 1; } } int num = 0; foreach (int id in ids) { if (dictionary.TryGetValue(id, out var value) && value > 0) { dictionary[id] = value - 1; continue; } PickupObject byId = PickupObjectDatabase.GetById(id); if (!((Object)(object)byId == (Object)null)) { LootEngine.GivePrefabToPlayer(((Component)byId).gameObject, me); num++; } } return num; } } public class LanDiscovery { public class FoundHost { public string Name; public IPEndPoint Address; public bool SameVersion; } public readonly List<FoundHost> Found = new List<FoundHost>(); private UdpClient udp; private readonly byte[] receiveBuffer = new byte[65536]; public bool Searching => udp != null; public void Search(int port) { Stop(); Found.Clear(); try { udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); udp.EnableBroadcast = true; LanLink.IgnoreConnectionResets(udp); byte[] array = LanLink.Wrap(LanLink.PacketKind.Discover, null, null); udp.Send(array, array.Length, new IPEndPoint(IPAddress.Broadcast, port)); udp.Send(array, array.Length, new IPEndPoint(IPAddress.Loopback, port)); } catch (SocketException ex) { Plugin.Log.LogWarning((object)("LAN search failed: " + ex.SocketErrorCode)); Stop(); } } public void Poll() { if (udp == null) { return; } try { while (udp.Available > 0) { EndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0); int num = udp.Client.ReceiveFrom(receiveBuffer, ref remoteEP); byte[] array = new byte[num]; Buffer.BlockCopy(receiveBuffer, 0, array, 0, num); HandleReply(array, (IPEndPoint)remoteEP); } } catch (SocketException) { } } public void Stop() { if (udp != null) { udp.Close(); } udp = null; } private void HandleReply(byte[] data, IPEndPoint from) { if (data.Length < 3 || data[0] != 71 || data[1] != 79 || data[2] != 5) { return; } try { using BinaryReader binaryReader = new BinaryReader(new MemoryStream(data, 3, data.Lengt