using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.WebSockets; using System.Numerics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using System.Threading.Tasks; using Archipelago.MultiClient.Net; using Archipelago.MultiClient.Net.BounceFeatures.DeathLink; using Archipelago.MultiClient.Net.Colors; using Archipelago.MultiClient.Net.ConcurrentCollection; using Archipelago.MultiClient.Net.Converters; using Archipelago.MultiClient.Net.DataPackage; using Archipelago.MultiClient.Net.Enums; using Archipelago.MultiClient.Net.Exceptions; using Archipelago.MultiClient.Net.Extensions; using Archipelago.MultiClient.Net.Helpers; using Archipelago.MultiClient.Net.MessageLog.Messages; using Archipelago.MultiClient.Net.MessageLog.Parts; using Archipelago.MultiClient.Net.Models; using Archipelago.MultiClient.Net.Packets; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using MenuLib; using MenuLib.MonoBehaviors; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using Photon.Pun; using REPOLib.Modules; using RepoAP.Core; using UnityEngine; using UnityEngine.Events; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("RepoAP")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.4.0.0")] [assembly: AssemblyInformationalVersion("0.4.0+2bb3b0b2691b1d1e54686193fd3850ce9f105f71")] [assembly: AssemblyProduct("Archipelago Randomizer")] [assembly: AssemblyTitle("RepoAP")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.4.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace RepoAP { [HarmonyPatch(typeof(MainMenuOpen), "Start")] internal class APConnectMenu { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static Action <>9__0_1; public static BuilderDelegate <>9__0_0; internal void b__0_0(Transform parent) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOButton("Archipelago", (Action)delegate { MenuBuilder.BuildPopup(); }, parent, new Vector2(145f, 27f)); } internal void b__0_1() { MenuBuilder.BuildPopup(); } } [HarmonyPrefix] private static void Prefix() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown object obj = <>c.<>9__0_0; if (obj == null) { BuilderDelegate val = delegate(Transform parent) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOButton("Archipelago", (Action)delegate { MenuBuilder.BuildPopup(); }, parent, new Vector2(145f, 27f)); }; <>c.<>9__0_0 = val; obj = (object)val; } MenuAPI.AddElementToMainMenu((BuilderDelegate)obj); } } public static class MenuBuilder { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static BuilderDelegate <>9__0_0; public static BuilderDelegate <>9__0_1; public static Action <>9__0_8; public static BuilderDelegate <>9__0_2; public static Action <>9__0_9; public static BuilderDelegate <>9__0_3; public static Action <>9__0_10; public static BuilderDelegate <>9__0_4; public static Action <>9__0_11; public static BuilderDelegate <>9__0_5; internal void b__0_0(Transform parent) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOLabel("Only host player must be connected to AP Server.", parent, new Vector2(380f, 275f)); } internal void b__0_1(Transform parent) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOLabel(Plugin.connection.connected ? "Connected" : "Not Connected", parent, new Vector2(400f, 225f)); } internal void b__0_2(Transform parent) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOInputField("Address", (Action)delegate(string input) { Plugin.apAddress = input; }, parent, new Vector2(400f, 200f), false, Plugin.apAddress, ""); } internal void b__0_8(string input) { Plugin.apAddress = input; } internal void b__0_3(Transform parent) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOInputField("Port", (Action)delegate(string input) { Plugin.apPort = input; }, parent, new Vector2(400f, 175f), false, Plugin.apPort, ""); } internal void b__0_9(string input) { Plugin.apPort = input; } internal void b__0_4(Transform parent) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOInputField("Password", (Action)delegate(string input) { Plugin.apPassword = input; }, parent, new Vector2(400f, 150f), false, Plugin.apPassword, ""); } internal void b__0_10(string input) { Plugin.apPassword = input; } internal void b__0_5(Transform parent) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOInputField("Player Slot", (Action)delegate(string input) { Plugin.apSlot = input; }, parent, new Vector2(400f, 125f), false, Plugin.apSlot, ""); } internal void b__0_11(string input) { Plugin.apSlot = input; } } public static void BuildPopup() { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //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_00a6: Expected O, but got Unknown //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown //IL_00ef: 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_00fa: Expected O, but got Unknown //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Expected O, but got Unknown //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Expected O, but got Unknown //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Expected O, but got Unknown Plugin.Logger.LogInfo((object)"Building Popup"); REPOPopupPage repoPage = MenuAPI.CreateREPOPopupPage("Archipelago", (PresetSide)1, false, true, 1.5f); REPOPopupPage obj = repoPage; object obj2 = <>c.<>9__0_0; if (obj2 == null) { BuilderDelegate val = delegate(Transform parent) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOLabel("Only host player must be connected to AP Server.", parent, new Vector2(380f, 275f)); }; <>c.<>9__0_0 = val; obj2 = (object)val; } obj.AddElement((BuilderDelegate)obj2); REPOPopupPage obj3 = repoPage; object obj4 = <>c.<>9__0_1; if (obj4 == null) { BuilderDelegate val2 = delegate(Transform parent) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOLabel(Plugin.connection.connected ? "Connected" : "Not Connected", parent, new Vector2(400f, 225f)); }; <>c.<>9__0_1 = val2; obj4 = (object)val2; } obj3.AddElement((BuilderDelegate)obj4); REPOPopupPage obj5 = repoPage; object obj6 = <>c.<>9__0_2; if (obj6 == null) { BuilderDelegate val3 = delegate(Transform parent) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOInputField("Address", (Action)delegate(string input) { Plugin.apAddress = input; }, parent, new Vector2(400f, 200f), false, Plugin.apAddress, ""); }; <>c.<>9__0_2 = val3; obj6 = (object)val3; } obj5.AddElement((BuilderDelegate)obj6); REPOPopupPage obj7 = repoPage; object obj8 = <>c.<>9__0_3; if (obj8 == null) { BuilderDelegate val4 = delegate(Transform parent) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOInputField("Port", (Action)delegate(string input) { Plugin.apPort = input; }, parent, new Vector2(400f, 175f), false, Plugin.apPort, ""); }; <>c.<>9__0_3 = val4; obj8 = (object)val4; } obj7.AddElement((BuilderDelegate)obj8); REPOPopupPage obj9 = repoPage; object obj10 = <>c.<>9__0_4; if (obj10 == null) { BuilderDelegate val5 = delegate(Transform parent) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOInputField("Password", (Action)delegate(string input) { Plugin.apPassword = input; }, parent, new Vector2(400f, 150f), false, Plugin.apPassword, ""); }; <>c.<>9__0_4 = val5; obj10 = (object)val5; } obj9.AddElement((BuilderDelegate)obj10); REPOPopupPage obj11 = repoPage; object obj12 = <>c.<>9__0_5; if (obj12 == null) { BuilderDelegate val6 = delegate(Transform parent) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOInputField("Player Slot", (Action)delegate(string input) { Plugin.apSlot = input; }, parent, new Vector2(400f, 125f), false, Plugin.apSlot, ""); }; <>c.<>9__0_5 = val6; obj12 = (object)val6; } obj11.AddElement((BuilderDelegate)obj12); repoPage.AddElement((BuilderDelegate)delegate(Transform parent) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOButton("Connect", (Action)delegate { Plugin.connection.TryConnect(Plugin.apAddress, int.Parse(Plugin.apPort), Plugin.apPassword, Plugin.apSlot); repoPage.ClosePage(false); BuildConnectingPopUp(); }, parent, new Vector2(378f, 25f)); }); repoPage.AddElement((BuilderDelegate)delegate(Transform parent) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOButton("Close", (Action)delegate { repoPage.ClosePage(true); }, parent, new Vector2(590f, 25f)); }); repoPage.OpenPage(true); } public static void BuildConnectingPopUp() { REPOPopupPage val = MenuAPI.CreateREPOPopupPage("Connecting to Server...", (PresetSide)1, false, true, 0f); Plugin.connection.connectingPage = val; val.OpenPage(true); } } internal class APSaveData { public List locationsChecked = new List(); public List pellysGathered = new List(); public List valuablesGathered = new List(); public List monsterSoulsGathered = new List(); public long shopStockSlotData; public int shopStockReceived; public Dictionary itemsReceived = new Dictionary(); public Dictionary levelsUnlocked = new Dictionary(); public int itemReceivedIndex; public Dictionary locationsScouted = new Dictionary(); public long pellysRequired; public bool pellySpawning; public long levelQuota; public long levelsCompleted; public long upgradeLocations; public bool valuableHunt; public bool monsterHunt; public Dictionary trapsUsed = new Dictionary(); } internal static class APSave { public static ES3Settings es3Settings; public static APSaveData saveData; public static string fileName; private static string saveKey = "archipelago"; public static void Init() { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown if (SemiFunc.IsMasterClientOrSingleplayer()) { string text = Application.persistentDataPath + "/archipelago"; fileName = BuildFileName(); if (!Directory.Exists(text)) { Directory.CreateDirectory(text); } text += "/saves"; if (!Directory.Exists(text)) { Directory.CreateDirectory(text); } text += fileName; if (!File.Exists(text)) { es3Settings = new ES3Settings(text, new Enum[1] { (Enum)(object)(EncryptionType)0 }); saveData = new APSaveData(); SaveSlotDataToFile(); ES3.Save(saveKey, saveData, es3Settings); } else { es3Settings = new ES3Settings(text, new Enum[1] { (Enum)(object)(EncryptionType)0 }); Plugin.Logger.LogInfo((object)("Loading save data from " + text)); saveData = ES3.Load(saveKey, saveData, es3Settings); SaveSlotDataToFile(); ES3.Save(saveKey, saveData, es3Settings); } } } private static string BuildFileName() { if (Plugin.connection.session == null) { return null; } return "/" + Plugin.connection.session.Players.ActivePlayer.Name + "___" + Plugin.connection.session.RoomState.Seed + ".es3"; } private static void SaveSlotDataToFile() { if (Plugin.connection.session == null) { Plugin.Logger.LogWarning((object)"Not connected. Cannot save slot data."); return; } saveData.levelQuota = (long)Plugin.connection.slotData["level_quota"]; saveData.pellysRequired = (long)Plugin.connection.slotData["pellys_required"]; saveData.pellySpawning = saveData.pellysRequired > 0; saveData.upgradeLocations = (long)Plugin.connection.slotData["upgrade_locations"]; saveData.shopStockSlotData = (long)Plugin.connection.slotData["shop_stock"]; saveData.valuableHunt = (bool)Plugin.connection.slotData["valuable_hunt"]; saveData.monsterHunt = (bool)Plugin.connection.slotData["monster_hunt"]; } public static void SyncServerLocationsToSave() { if (!Plugin.connection.connected) { return; } ArchipelagoSession session = Plugin.connection.session; foreach (long item in session.Locations.AllLocationsChecked) { if (!GetLocationsChecked().Contains(item)) { AddLocationChecked(item); } string locationNameFromId = session.Locations.GetLocationNameFromId(item); if (locationNameFromId.Contains("Pelly")) { AddPellyGathered(locationNameFromId); } if (locationNameFromId.Contains("Valuable")) { AddValuableGathered(LocationData.ValuableIDToName(item)); } if (locationNameFromId.Contains("Soul")) { AddMonsterSoulGathered(LocationData.MonsterSoulIDToName(item)); } } } public static void AddLocationChecked(long locToAdd) { if (Plugin.connection.session != null && SemiFunc.IsMasterClientOrSingleplayer()) { saveData.locationsChecked.Add(locToAdd); ES3.Save(saveKey, saveData, es3Settings); } } public static List GetLocationsChecked() { if (Plugin.connection.session == null || !SemiFunc.IsMasterClientOrSingleplayer()) { return null; } return ES3.Load(saveKey, es3Settings).locationsChecked; } public static List GetShopLocationsChecked() { if (Plugin.connection.session == null) { return null; } List locationsChecked = GetLocationsChecked(); List list = new List(); foreach (long item in locationsChecked) { if (LocationData.RemoveBaseId(item) <= 100) { list.Add((int)LocationData.RemoveBaseId(item)); } } return list; } public static void AddItemReceived(long itemId) { if (Plugin.connection.session != null && SemiFunc.IsMasterClientOrSingleplayer()) { if (saveData.itemsReceived.ContainsKey(itemId)) { saveData.itemsReceived[itemId]++; } else { saveData.itemsReceived.Add(itemId, 1); } saveData.itemReceivedIndex++; ES3.Save(saveKey, saveData, es3Settings); } } public static int GetItemReceivedIndex() { if (Plugin.connection.session == null || !SemiFunc.IsMasterClientOrSingleplayer()) { return 0; } return ES3.Load(saveKey, es3Settings).itemReceivedIndex; } public static void AddStockReceived() { if (Plugin.connection.session != null && SemiFunc.IsMasterClientOrSingleplayer()) { if (saveData.itemsReceived.ContainsKey(ItemData.AddBaseId(10L))) { saveData.shopStockReceived = saveData.itemsReceived[ItemData.AddBaseId(10L)]; } else { saveData.shopStockReceived = 0; } ES3.Save(saveKey, saveData, es3Settings); } } public static void UpdateAvailableItems() { if (Plugin.connection.session == null) { return; } Plugin.ShopItemsAvailable = new List(); Plugin.ShopItemsBought = GetShopLocationsChecked(); for (int i = 1; i <= saveData.shopStockSlotData * (saveData.shopStockReceived + 1); i++) { if (!Plugin.ShopItemsBought.Contains(i) && Plugin.connection.session.Locations.AllMissingLocations.Contains(ItemData.AddBaseId(i))) { Plugin.ShopItemsAvailable.Add(i); } } } public static Dictionary GetItemsReceived() { if (Plugin.connection.session == null || !SemiFunc.IsMasterClientOrSingleplayer()) { return null; } return ES3.Load(saveKey, es3Settings).itemsReceived; } public static bool IsItemReceived(long id, int count = 1) { if (!Plugin.connection.connected || !SemiFunc.IsMasterClientOrSingleplayer()) { return false; } Dictionary itemsReceived = ES3.Load(saveKey, es3Settings).itemsReceived; bool result = false; if (itemsReceived.ContainsKey(id) && itemsReceived[id] >= count) { result = true; } return result; } public static void AddLevelReceived(string levelName) { if (Plugin.connection.session != null && SemiFunc.IsMasterClientOrSingleplayer()) { if (!saveData.levelsUnlocked.ContainsKey(levelName) || !saveData.levelsUnlocked[levelName]) { saveData.levelsUnlocked.Add(levelName, value: true); } else { Plugin.Logger.LogDebug((object)(levelName + " has already been received!")); } ES3.Save(saveKey, saveData, es3Settings); } } public static Dictionary GetLevelsReceived() { if (Plugin.connection.session == null || !SemiFunc.IsMasterClientOrSingleplayer()) { return null; } return ES3.Load(saveKey, es3Settings).levelsUnlocked; } public static async void ScoutLocations() { if (Plugin.connection.session == null || !SemiFunc.IsMasterClientOrSingleplayer()) { return; } Plugin.Logger.LogInfo((object)"Scouting Locations..."); int num = 100; int num2 = LocationNames.all_pellys.Count * LocationNames.all_levels.Count; int count = LocationNames.all_valuables.Count; int count2 = LocationNames.all_monster_souls.Count; Plugin.Logger.LogInfo((object)$"Checking {num} shop items..."); Plugin.Logger.LogInfo((object)$"Checking {num2} pelly statues..."); Plugin.Logger.LogInfo((object)$"Checking {count} valuables..."); Plugin.Logger.LogInfo((object)$"Checking {count2} monster souls..."); long[] array = new long[num + num2 + count + count2]; int num3 = 0; for (int i = 1; i <= num; i++) { array[num3++] = LocationData.AddBaseId(i); } for (int j = 1; j <= num2; j++) { array[num3++] = LocationData.AddBaseId(100 + j); } for (int k = 0; k < count; k++) { array[num3++] = LocationData.AddBaseId(200 + k); } for (int l = 0; l < count2; l++) { array[num3++] = LocationData.AddBaseId(500 + l); } try { Dictionary obj = await Plugin.connection.session.Locations.ScoutLocationsAsync(array); saveData.locationsScouted = new Dictionary(); foreach (ScoutedItemInfo value in obj.Values) { saveData.locationsScouted.Add(value.LocationId, value.ToSerializable()); } } catch (Exception ex) { Debug.LogError((object)ex); } ES3.Save(saveKey, saveData, es3Settings); } public static SerializableItemInfo GetScoutedLocation(long id) { if ((Plugin.connection.session != null || !SemiFunc.IsMasterClientOrSingleplayer()) && saveData.locationsScouted.ContainsKey(id)) { return saveData.locationsScouted[id]; } return null; } public static SerializableItemInfo GetScoutedShopItem(long id) { return GetScoutedLocation(id); } public static void AddPellyGathered(string name) { if (Plugin.connection.session == null || !SemiFunc.IsMasterClientOrSingleplayer()) { return; } if (LocationNames.all_levels.Any((string x) => name.Contains(x))) { string text = LocationNames.all_levels.FirstOrDefault((string x) => name.Contains(x)); int index = LocationNames.all_levels.IndexOf(text); name.Replace(text, LocationNames.all_levels_short[index]); } name = name.Replace("Valuable", "").Replace("(Clone)", ""); if (!saveData.pellysGathered.Contains(name)) { saveData.pellysGathered.Add(name); } ES3.Save(saveKey, saveData, es3Settings); } public static void AddValuableGathered(string name) { name = LocationData.GetBaseName(name); if (Plugin.connection.session != null && SemiFunc.IsMasterClientOrSingleplayer()) { if (!saveData.valuablesGathered.Contains(name)) { saveData.valuablesGathered.Add(name); } ES3.Save(saveKey, saveData, es3Settings); } } public static void AddMonsterSoulGathered(string name) { name = LocationData.GetBaseName(name); if (Plugin.connection.session != null && SemiFunc.IsMasterClientOrSingleplayer()) { if (!saveData.monsterSoulsGathered.Contains(name)) { saveData.monsterSoulsGathered.Add(name); } ES3.Save(saveKey, saveData, es3Settings); } } public static bool WasValuableGathered(string name) { name = LocationData.GetBaseName(name); return saveData.valuablesGathered.Contains(name); } public static bool WasMonsterSoulGathered(string name) { name = LocationData.GetBaseName(name); return saveData.monsterSoulsGathered.Contains(name); } public static bool WasPellyGathered(string pelly, string level) { pelly = LocationData.GetBaseName(pelly); return saveData.pellysGathered.Exists((string x) => x.Contains(level) && x.Contains(pelly)); } public static bool CheckCompletion(out string status) { if (Plugin.connection?.session == null) { status = string.Empty; return false; } _ = Plugin.connection.session.Locations.AllLocationsChecked; ReadOnlyCollection allMissingLocations = Plugin.connection.session.Locations.AllMissingLocations; bool flag = true; long num = Math.Max(StatsManager.instance.GetRunStatLevel(), saveData.levelsCompleted); saveData.levelsCompleted = num; status = ""; Plugin.Logger.LogInfo((object)"CheckComplete"); Plugin.Logger.LogInfo((object)$"Current Level: {num}\nQuota: {saveData.levelQuota}"); if (num < saveData.levelQuota) { Plugin.Logger.LogInfo((object)"Level Quota not met"); flag = false; } status = string.Format("{{truck}} Levels - {0}/{1}{2}", num, saveData.levelQuota, (num >= saveData.levelQuota) ? " {check}" : " {X}"); long pellysRequired = saveData.pellysRequired; int num2 = 0; Plugin.Logger.LogInfo((object)$"Pellys Required: {saveData.pellysRequired}"); if (saveData.pellySpawning) { Plugin.Logger.LogInfo((object)"Pellys Gathered:"); foreach (string item in saveData.pellysGathered) { Plugin.Logger.LogInfo((object)("-" + item)); num2++; } Plugin.Logger.LogInfo((object)$"Pellys Collected: {num2}/{pellysRequired}"); if (num2 < pellysRequired) { Plugin.Logger.LogInfo((object)"Pelly hunt not complete."); flag = false; } foreach (long item2 in allMissingLocations) { string locationNameFromId = Plugin.connection.session.Locations.GetLocationNameFromId(item2); if (locationNameFromId.Contains("Pelly")) { Plugin.Logger.LogInfo((object)("Missing " + locationNameFromId)); } } if (RunManager.instance.levels.Contains(RunManager.instance.levelCurrent)) { status = status + "
{?} " + RunManager.instance.levelCurrent.NarrativeName + " Pellys:
"; string[] pellys = LocationNames.pellys; foreach (string pelly in pellys) { status += (saveData.pellysGathered.Any((string x) => x.Contains(pelly.ToString()) && x.Contains(((Object)RunManager.instance.levelCurrent).name)) ? (pelly.ToString().Replace(" Pelly", "") + " {check} | ") : (pelly.ToString().Replace(" Pelly", "") + " {X} | ")); } status += ""; } else { status += string.Format("
{{?}} Pellys - {0}/{1}{2}", num2, pellysRequired, (num2 == pellysRequired) ? " {check}" : " {X}"); } } if (saveData.monsterHunt) { pellysRequired = LocationNames.all_monster_souls.Count; num2 = 0; Plugin.Logger.LogDebug((object)"Monster Hunt"); foreach (string all_monster_soul in LocationNames.all_monster_souls) { if (!saveData.monsterSoulsGathered.Contains(all_monster_soul)) { Plugin.Logger.LogDebug((object)(all_monster_soul + " has not been extracted")); flag = false; } else { Plugin.Logger.LogDebug((object)(all_monster_soul + " hunted")); num2++; } } status += string.Format("
{{ghost}} Souls - {0}/{1}{2}", num2, pellysRequired, (num2 == pellysRequired) ? " {check}" : " {X}"); } if (saveData.valuableHunt) { pellysRequired = LocationNames.all_valuables.Count; num2 = 0; Plugin.Logger.LogDebug((object)"Valuable Hunt"); foreach (string all_valuable in LocationNames.all_valuables) { if (!saveData.valuablesGathered.Contains(all_valuable)) { Plugin.Logger.LogDebug((object)(all_valuable + " has not been extracted")); flag = false; } else { Plugin.Logger.LogDebug((object)(all_valuable + " extracted")); num2++; } } if (RunManager.instance.levels.Contains(RunManager.instance.levelCurrent)) { List list = new List(); foreach (LevelValuables valuablePreset in RunManager.instance.levelCurrent.ValuablePresets) { List list2 = new List(); list2.AddRange(valuablePreset.tiny); list2.AddRange(valuablePreset.small); list2.AddRange(valuablePreset.medium); list2.AddRange(valuablePreset.big); list2.AddRange(valuablePreset.wide); list2.AddRange(valuablePreset.tall); list2.AddRange(valuablePreset.veryTall); List source = list2; list = list.Union(source.Select((PrefabRef prefab) => LocationData.GetBaseName(prefab.PrefabName))).ToList(); } num2 = 0; pellysRequired = list.Intersect(LocationNames.all_valuables).Count(); foreach (string item3 in list) { if (saveData.valuablesGathered.Contains(item3)) { num2++; } } status += string.Format("
{{$$$}} {0} Valuables - {1}/{2}{3}", RunManager.instance.levelCurrent.NarrativeName, num2, pellysRequired, (num2 == pellysRequired) ? " {check}" : " {X}"); } else { status += string.Format("
{{$$$}} Valuables - {0}/{1}{2}", num2, pellysRequired, (num2 == pellysRequired) ? " {check}" : " {X}"); } } if (flag) { Plugin.Logger.LogInfo((object)"All Goals Complete."); } Plugin.connection.SyncCompletionProgress(num, saveData.pellysGathered, saveData.valuablesGathered, saveData.monsterSoulsGathered); return flag; } } public class ArchipelagoConnection { private struct messageData { public string message { get; } public Color flashCol { get; } public Color mainCol { get; } public float time { get; } public messageData(string m, Color fc, Color mc, float t) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) message = m; flashCol = fc; mainCol = mc; time = t; } } [CompilerGenerated] private sealed class d__33 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private bool <>2__current; public ArchipelagoConnection <>4__this; bool IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__33(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; ArchipelagoConnection archipelagoConnection = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; case 2: <>1__state = -1; break; } if (archipelagoConnection.connected) { if (archipelagoConnection.session.Items.AllItemsReceived.Count > archipelagoConnection.ItemIndex) { ItemInfo itemInfo = archipelagoConnection.session.Items.AllItemsReceived[archipelagoConnection.ItemIndex]; string itemName = itemInfo.ItemName; Plugin.Logger.LogDebug((object)("Placing item " + itemName + " with index " + archipelagoConnection.ItemIndex + " in queue.")); archipelagoConnection.incomingItems.Enqueue((itemInfo, archipelagoConnection.ItemIndex)); archipelagoConnection.ItemIndex++; <>2__current = true; <>1__state = 1; return true; } <>2__current = true; <>1__state = 2; return true; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__36 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private bool <>2__current; public ArchipelagoConnection <>4__this; bool IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__36(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) int num = <>1__state; ArchipelagoConnection archipelagoConnection = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; case 2: <>1__state = -1; break; case 3: <>1__state = -1; break; } if (archipelagoConnection.connected) { if (!archipelagoConnection.incomingItems.TryPeek(out (ItemInfo, int) result)) { <>2__current = true; <>1__state = 1; return true; } ItemInfo item = result.Item1; string itemName = item.ItemName; _ = itemName + " (" + item.ItemName + ") at index " + result.Item2; (ItemInfo, int) result2; if (APSave.GetItemReceivedIndex() > result.Item2) { archipelagoConnection.incomingItems.TryDequeue(out result2); Plugin.Logger.LogDebug((object)("Skipping item " + itemName + " at index " + result.Item2 + " as it has already been processed.")); <>2__current = true; <>1__state = 2; return true; } Plugin.Logger.LogInfo((object)("ItemHandler " + item.ItemId)); APSave.AddItemReceived(item.ItemId); if (!new List { RunManager.instance.levelMainMenu, RunManager.instance.levelLobby, RunManager.instance.levelLobbyMenu }.Contains(RunManager.instance.levelCurrent)) { ItemData.AddItemToInventory(item.ItemId, repeatedAdditions: false); messageData item2 = new messageData("Received " + itemName, Color.green, Color.white, 3f); archipelagoConnection.messageItems.Enqueue(item2); } archipelagoConnection.incomingItems.TryDequeue(out result2); <>2__current = true; <>1__state = 3; return true; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__34 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private bool <>2__current; public ArchipelagoConnection <>4__this; bool IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__34(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_00ab: 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) int num = <>1__state; ArchipelagoConnection archipelagoConnection = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; case 2: <>1__state = -1; break; case 3: <>1__state = -1; break; } if (!SemiFunc.MenuLevel()) { archipelagoConnection.messageDelay -= Time.deltaTime; if (archipelagoConnection.messageDelay > 0f) { <>2__current = true; <>1__state = 1; return true; } if (!archipelagoConnection.messageItems.TryDequeue(out var result)) { <>2__current = true; <>1__state = 2; return true; } archipelagoConnection.messageDelay = 3.5f; Plugin.customRPCManager.CallFocusTextRPC(result.message, result.mainCol, result.flashCol, result.time, Plugin.customRPCManagerObject); <>2__current = true; <>1__state = 3; return true; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__35 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private bool <>2__current; public ArchipelagoConnection <>4__this; bool IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__35(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; ArchipelagoConnection archipelagoConnection = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; case 2: <>1__state = -1; break; } if (archipelagoConnection.connected) { if (!archipelagoConnection.outgoingItems.TryDequeue(out var result)) { <>2__current = true; <>1__state = 1; return true; } string itemName = result.ItemName; string locationName = result.LocationName; _ = result.LocationId; string playerName = archipelagoConnection.session.Players.GetPlayerName(result.Player); Plugin.Logger.LogInfo((object)("Sent " + itemName + " at " + locationName + " for " + playerName)); _ = (int)result.Player; _ = archipelagoConnection.session.ConnectionInfo.Slot; <>2__current = true; <>1__state = 2; return true; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public REPOPopupPage connectingPage; public ArchipelagoSession session; public IEnumerator incomingItemHandler; public IEnumerator outgoingItemHandler; public IEnumerator checkItemsReceived; public IEnumerator messageHandler; private float messageDelay; public bool sentCompletion; public bool sentRelease; public bool sentCollect; public Dictionary slotData; public DeathLinkService deathLinkService; public int ItemIndex; private ConcurrentQueue<(ItemInfo NetworkItem, int index)> incomingItems; private ConcurrentQueue outgoingItems; private ConcurrentQueue messageItems; public bool connected { get { if (session == null) { return false; } return session.Socket.Connected; } } public event TickEventHandler TickInLevel; public async Task TryConnect(string address, int port, string pass, string player) { Plugin.Logger.LogDebug((object)"TryConnect"); if (connected) { Plugin.Logger.LogDebug((object)"Already connected. Returning"); return; } TryDisconnect(); if (session == null) { try { session = ArchipelagoSessionFactory.CreateSession(address, port); Plugin.Logger.LogInfo((object)("Session at " + session.ToString())); if (Plugin.BoundConfig.DisplayAPMessagesOnTruckScreen.Value) { session.MessageLog.OnMessageReceived += MessageLog_OnMessageReceived; } } catch { Plugin.Logger.LogError((object)"Failed to create archipelago session!"); } } messageHandler = MessageHandler(); incomingItems = new ConcurrentQueue<(ItemInfo, int)>(); outgoingItems = new ConcurrentQueue(); messageItems = new ConcurrentQueue(); LoginResult loginResult; try { await session.ConnectAsync(); loginResult = await session.LoginAsync("R.E.P.O", player, ItemsHandlingFlags.AllItems, null, null, null, pass); } catch (Exception ex) { loginResult = new LoginFailure(ex.GetBaseException().Message); } if (loginResult is LoginSuccessful loginSuccessful) { slotData = loginSuccessful.SlotData; Plugin.Logger.LogInfo((object)"Successfully connected to Archipelago Multiworld server!"); APSave.Init(); APSave.ScoutLocations(); if (!SemiFunc.MenuLevel()) { messageData item = new messageData("Successfully Connected!", Color.white, Color.green, 3f); messageItems.Enqueue(item); } deathLinkService = session.CreateDeathLinkService(); deathLinkService.OnDeathLinkReceived += HandleIncomingDeathlink; TickInLevel += DeathLinkPatch.ReceiveDeathLinkPatch; TickInLevel += TrapHandler.UseTrapPatch; if (DeathLinkEnabled()) { deathLinkService.EnableDeathLink(); } session.DataStorage["REPO-" + session.Players.GetPlayerName(session.ConnectionInfo.Slot) + "-levelsCompleted"].Initialize(JToken.op_Implicit(0)); session.DataStorage["REPO-" + session.Players.GetPlayerName(session.ConnectionInfo.Slot) + "-pellysGathered"].Initialize((IEnumerable)new List()); session.DataStorage["REPO-" + session.Players.GetPlayerName(session.ConnectionInfo.Slot) + "-valuablesGathered"].Initialize((IEnumerable)new List()); session.DataStorage["REPO-" + session.Players.GetPlayerName(session.ConnectionInfo.Slot) + "-monsterSoulsGathered"].Initialize((IEnumerable)new List()); APSave.saveData.trapsUsed[ItemNames.monster_trap] = 0; APSave.saveData.trapsUsed[ItemNames.audit_trap] = 0; APSave.saveData.trapsUsed[ItemNames.lure_trap] = 0; APSave.saveData.trapsUsed[ItemNames.moon_phase_trap] = 0; session.DataStorage["REPO-" + Plugin.connection.session.Players.GetPlayerName(Plugin.connection.session.ConnectionInfo.Slot) + "-trapsUsed"].Initialize((JToken)(object)JObject.FromObject((object)APSave.saveData.trapsUsed)); Plugin.connection.SyncCompletionProgress(APSave.saveData.levelsCompleted, APSave.saveData.pellysGathered, APSave.saveData.valuablesGathered, APSave.saveData.monsterSoulsGathered); } else { LoginFailure loginFailure = (LoginFailure)loginResult; string text = "Unable to connect to Archipelago Multiworld server:\n"; string[] errors = loginFailure.Errors; foreach (string text2 in errors) { text = text + text2 + "\n"; } text += "\n"; ConnectionRefusedError[] errorCodes = loginFailure.ErrorCodes; for (int i = 0; i < errorCodes.Length; i++) { ConnectionRefusedError connectionRefusedError = errorCodes[i]; text = text + connectionRefusedError.ToString() + "\n"; } Plugin.Logger.LogWarning((object)text); TryDisconnect(); } incomingItemHandler = IncomingItemHandler(); outgoingItemHandler = OutgoingItemHandler(); checkItemsReceived = CheckItemsReceived(); if (SemiFunc.MenuLevel()) { if (loginResult is LoginSuccessful) { Plugin.BoundConfig.APServerAddress.Value = Plugin.apAddress; Plugin.BoundConfig.APServerPort.Value = Plugin.apPort; Plugin.BoundConfig.APPassword.Value = Plugin.apPassword; Plugin.BoundConfig.APSlotName.Value = Plugin.apSlot; } connectingPage.ClosePage(false); MenuBuilder.BuildPopup(); } } private string RGBtoHtmlStr(Color col) { return BitConverter.ToString(new byte[3] { col.R, col.G, col.B }).Replace("-", string.Empty); } private void MessageLog_OnMessageReceived(LogMessage message) { string text = string.Empty; MessagePart[] parts = message.Parts; foreach (MessagePart messagePart in parts) { _ = string.Empty; string empty = string.Empty; empty = RGBtoHtmlStr(messagePart.Color); text = ((!(empty != string.Empty)) ? (text + messagePart.Text) : (text + "" + messagePart.Text + "")); } HandleAPTruckScreenMessages.TruckScreenChatPatch.AddMessage("AP", text); } private void OnItemReceived(ReceivedItemsHelper helper) { ItemInfo itemInfo = helper.DequeueItem(); Plugin.Logger.LogInfo((object)("OnItemReceived: " + itemInfo.ToString())); } public void TryDisconnect() { try { if (session != null) { session.Socket.DisconnectAsync(); session = null; } incomingItems = new ConcurrentQueue<(ItemInfo, int)>(); outgoingItems = new ConcurrentQueue(); deathLinkService = null; slotData = null; ItemIndex = 0; TickInLevel -= DeathLinkPatch.ReceiveDeathLinkPatch; TickInLevel -= TrapHandler.UseTrapPatch; Plugin.Logger.LogInfo((object)"Disconnected from Archipelago"); } catch { Plugin.Logger.LogError((object)"Encountered an error disconnecting from Archipelago!"); } } public async Task ClientDisconnected() { try { messageData item = new messageData("Client Disconnected! Trying to Reconnect...", Color.white, Color.red, 4f); messageItems.Enqueue(item); await TryConnect(Plugin.apAddress, int.Parse(Plugin.apPort), Plugin.apPassword, Plugin.apSlot); } catch (Exception ex) { Plugin.Logger.LogWarning((object)("Failure in reconnecting: " + ex.Message)); } } public void ActivateCheck(long locationID) { if (APSave.saveData.locationsChecked.Contains(locationID)) { return; } Plugin.Logger.LogInfo((object)("Checked Location " + locationID)); session.Locations.CompleteLocationChecksAsync(locationID); APSave.AddLocationChecked(locationID); if (APSave.saveData.locationsScouted.ContainsKey(locationID)) { outgoingItems.Enqueue(APSave.saveData.locationsScouted[locationID]); return; } session.Locations.ScoutLocationsAsync(locationID).ContinueWith(delegate(Task> locationInfoPacket) { foreach (ScoutedItemInfo value in locationInfoPacket.Result.Values) { outgoingItems.Enqueue(value.ToSerializable()); } }); } public void SyncLocations() { int count = session.Locations.AllLocationsChecked.Count; Dictionary dictionary = StatsManager.instance.dictionaryOfDictionaries["archipelago items sent to other players"]; if (count != dictionary.Count) { Plugin.Logger.LogWarning((object)"Locations Unsynced, resyncing..."); Dictionary dictionary2 = StatsManager.instance.dictionaryOfDictionaries["Locations Obtained"]; Plugin.Logger.LogInfo((object)("Server: " + count + "\nClient Count: " + dictionary?.ToString() + "\nClient Raw: " + dictionary2.Count)); } } public string GetLocationName(long id) { return session.Locations.GetLocationNameFromId(id); } public long GetLocationID(string name) { return session.Locations.GetLocationIdFromName("R.E.P.O", name); } public string GetItemName(long id) { return session.Items.GetItemName(id) ?? $"Item: {id}"; } [IteratorStateMachine(typeof(d__33))] private IEnumerator CheckItemsReceived() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__33(0) { <>4__this = this }; } [IteratorStateMachine(typeof(d__34))] private IEnumerator MessageHandler() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__34(0) { <>4__this = this }; } [IteratorStateMachine(typeof(d__35))] private IEnumerator OutgoingItemHandler() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__35(0) { <>4__this = this }; } [IteratorStateMachine(typeof(d__36))] private IEnumerator IncomingItemHandler() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__36(0) { <>4__this = this }; } public void SendCompletion() { StatusUpdatePacket statusUpdatePacket = new StatusUpdatePacket(); statusUpdatePacket.Status = ArchipelagoClientState.ClientGoal; session.Socket.SendPacket(statusUpdatePacket); } public void Release() { if (connected && sentCompletion && !sentRelease) { session.Socket.SendPacket(new SayPacket { Text = "!release" }); sentRelease = true; Plugin.Logger.LogInfo((object)"Released remaining checks."); } } public void Collect() { if (connected && sentCompletion && !sentCollect) { session.Socket.SendPacket(new SayPacket { Text = "!collect" }); sentCollect = true; Plugin.Logger.LogInfo((object)"Collected remaining items."); } } public async Task SyncCompletionProgress(long levels_completed, List pellys_gathered, List valuables_gathered, List monster_souls_gathered) { if (connected) { Plugin.Logger.LogInfo((object)"Syncing completion progress to Archipelago data storage..."); Task getLevelsCompleted = SyncAPClientServerData("REPO-" + session.Players.GetPlayerName(session.ConnectionInfo.Slot) + "-levelsCompleted", levels_completed); Task> getPellysGathered = SyncAPClientServerData("REPO-" + session.Players.GetPlayerName(session.ConnectionInfo.Slot) + "-pellysGathered", pellys_gathered); Task> getValuablesGathered = SyncAPClientServerData("REPO-" + session.Players.GetPlayerName(session.ConnectionInfo.Slot) + "-valuablesGathered", valuables_gathered); Task> getMonsterSoulsGathered = SyncAPClientServerData("REPO-" + session.Players.GetPlayerName(session.ConnectionInfo.Slot) + "-monsterSoulsGathered", monster_souls_gathered); APSaveData saveData = APSave.saveData; saveData.trapsUsed = await Plugin.connection.session.DataStorage["REPO-" + Plugin.connection.session.Players.GetPlayerName(Plugin.connection.session.ConnectionInfo.Slot) + "-trapsUsed"].GetAsync>(); saveData = APSave.saveData; saveData.levelsCompleted = await getLevelsCompleted; saveData = APSave.saveData; saveData.pellysGathered = await getPellysGathered; saveData = APSave.saveData; saveData.valuablesGathered = await getValuablesGathered; saveData = APSave.saveData; saveData.monsterSoulsGathered = await getMonsterSoulsGathered; Plugin.Logger.LogInfo((object)"Data storage sync complete"); } } private async Task SyncAPClientServerData(string serverDataKey, long localData) { long num = Math.Max(localData, await session.DataStorage[serverDataKey].GetAsync()); session.DataStorage[serverDataKey] = num; return num; } private async Task> SyncAPClientServerData(string serverDataKey, List localData) { List serverData = await session.DataStorage[serverDataKey].GetAsync>(); foreach (string item in localData.Where((string element) => !serverData.Contains(element))) { serverData.Add(item); } session.DataStorage[serverDataKey] = serverData; return serverData; } public void SendDeathLink() { if (connected && SemiFunc.IsMasterClientOrSingleplayer() && DeathLinkEnabled()) { deathLinkService.SendDeathLink(new DeathLink(session.Players.ActivePlayer.Name, session.Players.ActivePlayer.Name + " got scrapped by the Taxman.")); } } public void HandleIncomingDeathlink(DeathLink deathLinkObject) { if (SemiFunc.IsMasterClientOrSingleplayer() && !SemiFunc.MenuLevel() && !DeathLinkPatch.awaitingDeathLink) { Plugin.Logger.LogInfo((object)"Received death link"); DeathLinkPatch.playerWhoDied = deathLinkObject.Source; DeathLinkPatch.awaitingDeathLink = true; } } internal bool DeathLinkEnabled() { if (!Plugin.BoundConfig.OverrideMWDeathlink.Value || !Plugin.BoundConfig.Deathlink.Value) { if (!Plugin.BoundConfig.OverrideMWDeathlink.Value) { return (bool)Plugin.connection.slotData["death_link"]; } return false; } return true; } internal void TickUpdate(RunManager __instance) { if (connected) { this.TickInLevel(__instance); } } } [HarmonyPatch(typeof(PlayerController), "Update")] internal class CheckForDisconnect { private static double timeSinceLastCheck = 5.0; [HarmonyPostfix] private static void CheckDC() { if (SemiFunc.IsMasterClientOrSingleplayer() && !((Object)RunManager.instance.levelCurrent).name.Contains("Menu") && !((Object)RunManager.instance.levelCurrent).name.Contains("Splash") && !Plugin.connection.connected) { if (Plugin.reconnectTask == null) { Plugin.Logger.LogInfo((object)"Disconnected from AP Server"); Plugin.reconnectTask = Plugin.connection.ClientDisconnected(); } else if (Plugin.reconnectTask.Status == TaskStatus.RanToCompletion && timeSinceLastCheck >= 5.0) { Plugin.reconnectTask = null; timeSinceLastCheck = 0.0; } else { timeSinceLastCheck += Time.deltaTime; } } } } public class PluginConfig { public ConfigEntry DisplayAPMessagesOnTruckScreen; public ConfigEntry EnemyWeightIncrease; public ConfigEntry ValuableSubstitutionChance; public ConfigEntry Deathlink; public ConfigEntry OverrideMWDeathlink; public ConfigEntry APServerAddress; public ConfigEntry APServerPort; public ConfigEntry APPassword; public ConfigEntry APSlotName; public PluginConfig(ConfigFile cfg) { DisplayAPMessagesOnTruckScreen = cfg.Bind("General", "Display Archipelago messages on truck screen", true, "If true, the truck screen will display messages from the multiworld chat. Not recommended for large multiworlds due to the sheer volume of items being sent/received."); EnemyWeightIncrease = cfg.Bind("Bad Luck Protection", "Spawn weight increase for unextracted enemy souls", 50u, "Once half of all souls have been extracted, the spawn weight of enemies whose souls haven't been extracted is raised by this amount. Every enemy has a default spawn weight of 100. Remember that weights are NOT percentages, so a weight of 50 doesn't mean that a monster has a 50% chance to spawn. Minimum value is 0."); ValuableSubstitutionChance = cfg.Bind("Bad Luck Protection", "Chance to replace previously extracted valuables", 40u, "The chance to replace previously extracted valuables with undiscovered ones in the same size group, if possible. 0 means no valuables will be replaced, 50 means roughly half of all valuables will be replaced, 100 means all valuables will be replaced when possible. Minimum value is 0, maximum is 100."); Deathlink = cfg.Bind("General", "Enable death link", false, "When you die, everyone who enabled death link dies. Of course, the reverse is true too."); OverrideMWDeathlink = cfg.Bind("General", "Override yaml death link option", false, "If true, the mod config will be used to turn death link on/off instead of the yaml option."); APServerAddress = cfg.Bind("Connection", "Address", "archipelago.gg", "The address of the multiworld server (usually archipelago.gg)."); APServerPort = cfg.Bind("Connection", "Port", "", "The port that the multiworld is hosted on."); APPassword = cfg.Bind("Connection", "Password", "", "The password for the multiworld, if it has one."); APSlotName = cfg.Bind("Connection", "Slot Name", "", "Your slot name in the multiworld."); ClearUnusedEntries(cfg); } private void ClearUnusedEntries(ConfigFile cfg) { ((Dictionary)((object)cfg).GetType().GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(cfg, null)).Clear(); cfg.Save(); } } public class CustomRPCs : MonoBehaviour { public void CallUpdateItemNameRPC(string name, GameObject inst) { Plugin.Logger.LogInfo((object)"Calling UpdateItemNameRPC"); PhotonView component = inst.GetComponent(); object[] array = new object[1] { name }; component.RPC("UpdateItemNameRPC", (RpcTarget)3, array); } public void CallFocusTextRPC(string message, Color mainCol, Color flashCol, float lingerTime, GameObject inst) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (GameManager.instance.gameMode == 1) { PhotonView component = inst.GetComponent(); object[] array = new object[4] { message, mainCol, flashCol, lingerTime }; component.RPC("FocusTextRPC", (RpcTarget)0, array); } else { FocusTextOffline(message, mainCol, flashCol, lingerTime); } } public void CallSyncSlotDataWithClientsRpc(GameObject inst) { if (GameManager.instance.gameMode == 1 && PhotonNetwork.IsMasterClient) { Plugin.Logger.LogInfo((object)"Syncing ap data with clients"); PhotonView component = inst.GetComponent(); object[] array = new object[8] { Enumerable.ToArray(APSave.saveData.pellysGathered), Enumerable.ToArray(APSave.saveData.valuablesGathered), Enumerable.ToArray(APSave.saveData.monsterSoulsGathered), APSave.saveData.locationsScouted.ToDictionary((KeyValuePair kvp) => kvp.Key, (KeyValuePair kvp) => kvp.Value.ToJson(full: true)), APSave.saveData.pellysRequired, APSave.saveData.valuableHunt, APSave.saveData.monsterHunt, APSave.saveData.trapsUsed }; component.RPC("SyncSlotDataWithClientsRpc", (RpcTarget)1, array); } } public void CallClientChangeMonsterOrbName(GameObject inst, string enemyName) { Plugin.Logger.LogInfo((object)"Calling ClientChangeMonsterOrbName"); PhotonView component = inst.GetComponent(); object[] array = new object[1] { enemyName }; component.RPC("ClientChangeMonsterOrbName", (RpcTarget)0, array); } public void CallSendClientDeathLink(GameObject inst, string playerWhoDied, string playerSteamID) { if (GameManager.instance.gameMode == 1 && PhotonNetwork.IsMasterClient) { Plugin.Logger.LogInfo((object)"Sending death link notification to clients"); PhotonView component = inst.GetComponent(); object[] array = new object[2] { playerWhoDied, playerSteamID }; component.RPC("SendClientDeathLink", (RpcTarget)0, array); } } public void CallClientDeathLinkFinished(GameObject inst, string playerSteamIdWhoWasPosessed) { Plugin.Logger.LogInfo((object)"Notifying clients that death link processing is finished"); PhotonView component = inst.GetComponent(); object[] array = new object[1] { playerSteamIdWhoWasPosessed }; component.RPC("ClientDeathLinkFinished", (RpcTarget)2, array); } public void CallPingClientsWithNoise(GameObject inst, string fieldName, Vector3 position) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) Plugin.Logger.LogInfo((object)"Playing lure trap sound for clients"); PhotonView component = inst.GetComponent(); object[] array = new object[2] { fieldName, position }; component.RPC("PingClientsWithNoise", (RpcTarget)0, array); } [PunRPC] public void UpdateItemNameRPC(string name, PhotonMessageInfo info) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) Plugin.Logger.LogInfo((object)"UpdateItemNameRPC Called"); ItemAttributes component = ((Component)info.photonView).gameObject.GetComponent(); AccessTools.Field(typeof(ItemAttributes), "itemName").SetValue(component, name.Replace("_", " ")); } [PunRPC] public void FocusTextRPC(string message, Color mainCol, Color flashCol, float lingerTime) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) SemiFunc.UIFocusText(message, mainCol, flashCol, lingerTime); } public void FocusTextOffline(string message, Color mainCol, Color flashCol, float lingerTime) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) SemiFunc.UIFocusText(message, mainCol, flashCol, lingerTime); } [PunRPC] public void SyncSlotDataWithClientsRpc(string[] pellys_gathered, string[] valuables_gathered, string[] monster_souls_gathered, Dictionary locations_scouted, long pellys_required, bool valuable_hunt, bool monster_hunt, Dictionary trapsUsed) { if (APSave.saveData == null) { APSave.saveData = new APSaveData(); } APSave.saveData.pellysGathered = pellys_gathered.ToList(); APSave.saveData.valuablesGathered = valuables_gathered.ToList(); APSave.saveData.monsterSoulsGathered = monster_souls_gathered.ToList(); APSave.saveData.locationsScouted = locations_scouted.ToDictionary((KeyValuePair kvp) => kvp.Key, (KeyValuePair kvp) => SerializableItemInfo.FromJson(kvp.Value)); APSave.saveData.pellysRequired = pellys_required; APSave.saveData.valuableHunt = valuable_hunt; APSave.saveData.monsterHunt = monster_hunt; APSave.saveData.trapsUsed = trapsUsed; Plugin.Logger.LogInfo((object)"Ap data synced with host"); } [PunRPC] public void ClientChangeMonsterOrbName(string enemyName) { EnemyDespawnPatch.ChangeEnemyOrbNames(enemyName); } [PunRPC] public void SendClientDeathLink(string apPlayerWhoDied, string chosenPlayerSteamID) { DeathLinkPatch.PosessDeathlink(apPlayerWhoDied, chosenPlayerSteamID); } [PunRPC] public void ClientDeathLinkFinished(string playerSteamIdWhoWasPosessed) { DeathLinkPatch.DeathLinkFinished(playerSteamIdWhoWasPosessed); } [PunRPC] public void PingClientsWithNoise(string soundFieldName, Vector3 position) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_0046: Unknown result type (might be due to invalid IL or missing references) Sound val = (Sound)(AccessTools.Field(typeof(PlayerAvatar), soundFieldName)?.GetValue(PlayerAvatar.instance)); if (val == null) { Plugin.Logger.LogError((object)("Unable to play sound. No field found with the name '" + soundFieldName + "'")); } else { val.Play(position, 1f, 2f, 1f, 1f); } } } [HarmonyPatch(typeof(TruckScreenText), "GotoNextLevel")] internal class NextLevelCheckCompletePatch { [HarmonyPostfix] private static void CheckComplete() { Plugin.Logger.LogInfo((object)"Truck Go To Next"); if (APSave.CheckCompletion(out var _)) { Plugin.connection.SendCompletion(); } } } [BepInPlugin("Automagic.ArchipelagoREPO", "Archipelago Randomizer", "0.4.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Logger = null; public static ArchipelagoConnection connection; public static Task reconnectTask = null; public static PlayerController _player; public static CustomRPCs customRPCManager; public static GameObject customRPCManagerObject; public static bool showMenu = true; public static string apAddress; public static string apPort; public static string apPassword; public static string apSlot; public static int LastShopItemChecked = 0; public static List ShopItemsBought = new List(); public static List ShopItemsAvailable = new List(); internal static PluginConfig BoundConfig { get; private set; } = null; private void Awake() { //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_0094: 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_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) Logger = ((BaseUnityPlugin)this).Logger; _player = PlayerController.instance; BoundConfig = new PluginConfig(((BaseUnityPlugin)this).Config); apAddress = BoundConfig.APServerAddress.Value; apPort = BoundConfig.APServerPort.Value; apPassword = BoundConfig.APPassword.Value; apSlot = BoundConfig.APSlotName.Value; Logger.LogInfo((object)"Plugin RepoAP is loaded!"); Harmony val = new Harmony("RepoAP"); val.PatchAll(); val.PatchAll(typeof(DeathLinkPatch)); val.PatchAll(typeof(ShopPopulateItemVolumesPatch)); val.PatchAll(typeof(UpgradeSpawningPatch)); val.PatchAll(typeof(APItemNamePatch)); val.PatchAll(typeof(EnemyDespawnPatch)); val.PatchAll(typeof(TrapHandler)); val.PatchAll(typeof(Events)); } internal static void Initialize() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) Logger.LogDebug((object)"In Initialize"); connection = new ArchipelagoConnection(); customRPCManagerObject = new GameObject("RepoAPCustomRPCManager") { hideFlags = (HideFlags)61 }; customRPCManagerObject.SetActive(false); customRPCManager = customRPCManagerObject.AddComponent(); customRPCManagerObject.AddComponent(); Object.DontDestroyOnLoad((Object)(object)customRPCManager); string text = "RepoAP/" + ((Object)customRPCManagerObject).name; PrefabRef val = NetworkPrefabs.RegisterNetworkPrefab(text, customRPCManagerObject); if (val != null) { NetworkPrefabs.SpawnNetworkPrefab(val, Vector3.zero, Quaternion.identity, (byte)0, (object[])null); Logger.LogInfo((object)"Registered customRPCManagerObject for multiplayer RPCs."); } else { Logger.LogError((object)"Failed to register customRPCManagerObject. Multiplayer may be borked."); } customRPCManagerObject.GetComponent().ViewID = text.GetHashCode(); Logger.LogDebug((object)$"customRPCManagerObject has ViewID {customRPCManagerObject.GetComponent().ViewID}"); ItemData.CreateItemDataTable(); } public static ArchipelagoConnection GetConnection() { return connection; } public void CheckLocation(long locID) { connection.ActivateCheck(locID); } public static void ProcessItems() { if (connection.connected) { connection.checkItemsReceived?.MoveNext(); connection.incomingItemHandler?.MoveNext(); connection.outgoingItemHandler?.MoveNext(); connection.messageHandler?.MoveNext(); } } public static void UpdateAPAddress(string input) { apAddress = input; } } [HarmonyPatch(typeof(GameManager))] internal static class GameManagerPatch { [HarmonyPatch("Awake")] [HarmonyPostfix] public static void AwakePatch(GameManager __instance) { if (!((Object)(object)__instance != (Object)(object)GameManager.instance)) { Plugin.Initialize(); } } } [HarmonyPatch(typeof(RunManager))] internal static class RunManagerPatch { [HarmonyPatch("Update")] [HarmonyPostfix] public static void UpdatePatch() { Plugin.ProcessItems(); } } [HarmonyPatch(typeof(MenuPageSaves), "OnLoadGame")] internal class PreventLoadingRunWhenNotConnectedPatch { [HarmonyPrefix] private static bool OnLoadGamePatch() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (Plugin.connection.session == null || !Plugin.connection.connected) { MenuManager.instance.PageCloseAllAddedOnTop(); MenuManager.instance.PagePopUp("Not Connected", Color.red, "Not connected to AP server. Please connect before loading a save.", "OK", true); return false; } return true; } } [HarmonyPatch(typeof(MenuPageSaves), "OnNewGame")] internal class PreventNewRunWhenNotConnectedPatch { [HarmonyPrefix] private static bool OnNewGamePatch() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (Plugin.connection.session == null || !Plugin.connection.connected) { MenuManager.instance.PageCloseAllAddedOnTop(); MenuManager.instance.PagePopUp("Not Connected", Color.red, "Not connected to AP server. Please connect before creating a save.", "OK", true); return false; } return true; } } internal class ItemData { private const int baseID = 75912022; public const int shopStockID = 10; public static Dictionary itemIDToName; public static Dictionary itemNameToID; public static void CreateItemDataTable() { itemIDToName = new Dictionary(); itemNameToID = new Dictionary(); List list = new List(); List list2 = new List(); int num = 10; list2.Add(0L); list.Add(LocationNames.swiftbroom_academy); list2.Add(1L); list.Add(LocationNames.headman_manor); list2.Add(2L); list.Add(LocationNames.mcjannek_station); list2.Add(3L); list.Add(LocationNames.museum_of_human_art); list2.Add(num++); list.Add(ItemNames.shop_stock); list2.Add(num++); list.Add(ItemNames.upgrade_health); list2.Add(num++); list.Add(ItemNames.upgrade_strength); list2.Add(num++); list.Add(ItemNames.upgrade_range); list2.Add(num++); list.Add(ItemNames.upgrade_sprint_speed); list2.Add(num++); list.Add(ItemNames.upgrade_stamina); list2.Add(num++); list.Add(ItemNames.upgrade_player_count); list2.Add(num++); list.Add(ItemNames.upgrade_double_jump); list2.Add(num++); list.Add(ItemNames.upgrade_tumble_launch); list2.Add(num++); list.Add(ItemNames.upgrade_crouch_rest); list2.Add(num++); list.Add(ItemNames.upgrade_tumble_wings); list2.Add(num++); list.Add(ItemNames.upgrade_tumble_climb); list2.Add(num++); list.Add(ItemNames.upgrade_death_head_battery); list2.Add(num++); list.Add(ItemNames.small_health); list2.Add(num++); list.Add(ItemNames.progressive_health); list2.Add(num++); list.Add(ItemNames.baseball_bat); list2.Add(num++); list.Add(ItemNames.frying_pan); list2.Add(num++); list.Add(ItemNames.sledge_hammer); list2.Add(num++); list.Add(ItemNames.sword); list2.Add(num++); list.Add(ItemNames.inflatable_hammer); list2.Add(num++); list.Add(ItemNames.prodzap); list2.Add(num++); list.Add(ItemNames.gun); list2.Add(num++); list.Add(ItemNames.shotgun); list2.Add(num++); list.Add(ItemNames.tranq_gun); list2.Add(num++); list.Add(ItemNames.pulse_pistol); list2.Add(num++); list.Add(ItemNames.photon_blaster); list2.Add(num++); list.Add(ItemNames.boltzap); list2.Add(num++); list.Add(ItemNames.cart_cannon); list2.Add(num++); list.Add(ItemNames.cart_laser); list2.Add(num++); list.Add(ItemNames.grenade); list2.Add(num++); list.Add(ItemNames.shock_grenade); list2.Add(num++); list.Add(ItemNames.human_grenade); list2.Add(num++); list.Add(ItemNames.stun_grenade); list2.Add(num++); list.Add(ItemNames.duct_taped_grenade); list2.Add(num++); list.Add(ItemNames.shockwave_mine); list2.Add(num++); list.Add(ItemNames.stun_mine); list2.Add(num++); list.Add(ItemNames.explosive_mine); list2.Add(num++); list.Add(ItemNames.rubber_duck); list2.Add(num++); list.Add(ItemNames.recharge_drone); list2.Add(num++); list.Add(ItemNames.indestructible_drone); list2.Add(num++); list.Add(ItemNames.roll_drone); list2.Add(num++); list.Add(ItemNames.feather_drone); list2.Add(num++); list.Add(ItemNames.zero_grav_drone); list2.Add(num++); list.Add(ItemNames.pocket_cart); list2.Add(num++); list.Add(ItemNames.cart); list2.Add(num++); list.Add(ItemNames.valuable_detector); list2.Add(num++); list.Add(ItemNames.extraction_detector); list2.Add(num++); list.Add(ItemNames.energy_crystal); list2.Add(num++); list.Add(ItemNames.zero_grav_orb); list2.Add(num++); list.Add(ItemNames.duck_bucket); list2.Add(num++); list.Add(ItemNames.phase_bridge); list2.Add(num++); list.Add(ItemNames.monster_trap); list2.Add(num++); list.Add(ItemNames.audit_trap); list2.Add(num++); list.Add(ItemNames.lure_trap); list2.Add(num++); list.Add(ItemNames.moon_phase_trap); for (int i = 0; i < list2.Count; i++) { itemIDToName.Add(list2[i], list[i]); itemNameToID.Add(list[i], list2[i]); } } public static void AddItemToInventory(long itemId, bool repeatedAdditions) { string text = IdToItemName(RemoveBaseId(itemId)); Plugin.Logger.LogDebug((object)("Attempting to add item to inventory: " + RemoveBaseId(itemId) + " : " + text)); if (LocationNames.all_levels.Contains(text)) { APSave.AddLevelReceived(text); } else if (text == ItemNames.shop_stock) { APSave.AddStockReceived(); APSave.UpdateAvailableItems(); } else if (text.Contains("Upgrade") || text.Contains("Item Health Pack")) { if (!StatsManager.instance.runStats.TryGetValue(text + " ReceivedFromAP", out var value)) { PunManager.instance.SetRunStatSet(text + " ReceivedFromAP", 0); value = 0; } if (APSave.GetItemsReceived()[itemId] > value) { StatsManager.instance.ItemPurchase(text); PunManager.instance.SetRunStatSet(text + " ReceivedFromAP", value + 1); } else { Plugin.Logger.LogDebug((object)("Item " + text + " has already been received. Skipping...")); } } } public static string IdToItemName(long itemId) { return itemIDToName[itemId]; } public static long RemoveBaseId(long id) { return id - 75912022; } public static long AddBaseId(long id) { return id + 75912022; } } public static class ItemNames { public static string shop_stock = "Progressive Shop Stock"; public static string upgrade_player_count = "Item Upgrade Map Player Count"; public static string upgrade_crouch_rest = "Item Upgrade Player Crouch Rest"; public static string upgrade_stamina = "Item Upgrade Player Energy"; public static string upgrade_double_jump = "Item Upgrade Player Extra Jump"; public static string upgrade_range = "Item Upgrade Player Grab Range"; public static string upgrade_strength = "Item Upgrade Player Grab Strength"; public static string upgrade_health = "Item Upgrade Player Health"; public static string upgrade_sprint_speed = "Item Upgrade Player Sprint Speed"; public static string upgrade_tumble_launch = "Item Upgrade Player Tumble Launch"; public static string upgrade_tumble_wings = "Item Upgrade Player Tumble Wings"; public static string upgrade_tumble_climb = "Item Upgrade Player Tumble Climb"; public static string upgrade_death_head_battery = "Item Upgrade Death Head Battery"; public static string valuable_tracker = "Item Valuable Tracker"; public static string ap_item = "Item Upgrade AP Item"; public static string progressive_health = "Progressive Health Pack Unlock"; public static string small_health = "Item Health Pack Small"; public static string baseball_bat = "Baseball Bat Unlock"; public static string frying_pan = "Frying Pan Unlock"; public static string sledge_hammer = "Sledge Hammer Unlock"; public static string sword = "Sword Unlock"; public static string inflatable_hammer = "Inflatable Hammer Unlock"; public static string prodzap = "Prodzap Unlock"; public static string gun = "Gun Unlock"; public static string shotgun = "Shotgun Unlock"; public static string tranq_gun = "Tranq Gun Unlock"; public static string pulse_pistol = "Pulse Pistol Unlock"; public static string photon_blaster = "Photon Blaster Unlock"; public static string boltzap = "Boltzap Unlock"; public static string cart_cannon = "C.A.R.T. Cannon Unlock"; public static string cart_laser = "C.A.R.T. Laser Unlock"; public static string grenade = "Grenade Unlock"; public static string shock_grenade = "Shockwave Grenade Unlock"; public static string stun_grenade = "Stun Grenade Unlock"; public static string human_grenade = "Human Grenade Unlock"; public static string duct_taped_grenade = "Duct Taped Grenades Unlock"; public static string shockwave_mine = "Shockwave Mine Unlock"; public static string stun_mine = "Trapzap Unlock"; public static string explosive_mine = "Explosive Mine Unlock"; public static string rubber_duck = "Rubber Duck Unlock"; public static string recharge_drone = "Recharge Drone Unlock"; public static string indestructible_drone = "Indestructible Drone Unlock"; public static string roll_drone = "Roll Drone Unlock"; public static string feather_drone = "Feather Drone Unlock"; public static string zero_grav_drone = "Zero Gravity Drone Unlock"; public static string pocket_cart = "POCKET C.A.R.T. Unlock"; public static string cart = "C.A.R.T. Unlock"; public static string valuable_detector = "Valuable Tracker Unlock"; public static string extraction_detector = "Extraction Tracker Unlock"; public static string energy_crystal = "Energy Crystal Unlock"; public static string zero_grav_orb = "Zero Gravity Orb Unlock"; public static string duck_bucket = "Duck Bucket Unlock"; public static string phase_bridge = "Phase Bridge Unlock"; public static string monster_trap = "Extra Monster Trap"; public static string audit_trap = "Audit Trap"; public static string lure_trap = "Monster Lure Trap"; public static string moon_phase_trap = "Progressive Moon Phase Trap"; } [HarmonyPatch(typeof(RunManager), "SetRunLevel")] internal class LevelLockPatch { internal static int levelIndex = -1; [HarmonyPostfix] private static void SetRunLevelPre(RunManager __instance) { Dictionary levelsReceived = APSave.GetLevelsReceived(); if ((levelsReceived != null && levelsReceived.Count == 0) || Plugin.connection.session == null) { Plugin.Logger.LogError((object)"No Levels found in Save!"); return; } Dictionary levelsReceived2 = APSave.GetLevelsReceived(); List list = new List(); foreach (KeyValuePair item in levelsReceived2) { Plugin.Logger.LogDebug((object)("Player has " + item.Key)); list.Add(item.Key); } int[] array = new int[list.Count]; int num = 0; Plugin.Logger.LogDebug((object)"Calculating level weights based on valuables gathered..."); foreach (string levelName in list) { Level val2 = ((IEnumerable)__instance.levels).FirstOrDefault((Func)((Level lev) => levelName.Contains(lev.NarrativeName))); if ((Object)(object)val2 == (Object)null) { Plugin.Logger.LogWarning((object)("Level " + levelName + " not found in RunManager levels when selecting a weight!")); array[num] = 0; num++; continue; } List list2 = new List(); foreach (LevelValuables valuablePreset in val2.ValuablePresets) { list2.AddRange(valuablePreset.tiny); list2.AddRange(valuablePreset.small); list2.AddRange(valuablePreset.medium); list2.AddRange(valuablePreset.big); list2.AddRange(valuablePreset.wide); list2.AddRange(valuablePreset.tall); list2.AddRange(valuablePreset.veryTall); } int count = list2.Count; int num2 = list2.Count((PrefabRef val) => !val.PrefabName.Contains("Pelly") && !APSave.WasValuableGathered(val.PrefabName)); Plugin.Logger.LogDebug((object)$"Level {val2.NarrativeName} has {num2}/{count} valuables left to collect (besides pellys)."); int num3 = (int)(100f * (float)num2 / (float)count); foreach (PrefabRef item2 in list2.Where((PrefabRef val) => val.PrefabName.Contains("Pelly"))) { num3 += ((!APSave.WasPellyGathered(item2.PrefabName, ((Object)val2).name)) ? 5 : 0); } Plugin.Logger.LogDebug((object)$"Assigned level {val2.NarrativeName} a weight of {num3}"); array[num] = num3; num++; } if (array.Sum() == 0) { Plugin.Logger.LogInfo((object)"All valuables collected! Defaulting to random level selection."); levelIndex = Random.RandomRangeInt(0, list.Count); } else { int num4 = Random.RandomRangeInt(0, array.Sum()); int num5 = 0; for (int i = 0; i < array.Length; i++) { num5 += array[i]; if (num4 < num5) { levelIndex = i; Plugin.Logger.LogInfo((object)$"Selected {list[levelIndex]} ({(float)array[levelIndex] * 100f / (float)array.Sum()}% chance)"); break; } } } string text = list[levelIndex]; Level levelCurrent = null; Plugin.Logger.LogInfo((object)("Setting level to " + text)); foreach (Level level in __instance.levels) { if (text.Contains(level.NarrativeName)) { levelCurrent = level; } else { Plugin.Logger.LogDebug((object)(level.NarrativeName + " != " + text)); } } __instance.levelCurrent = levelCurrent; Plugin.Logger.LogDebug((object)("Returning " + ((Object)__instance.levelCurrent).name)); Plugin.customRPCManager.CallSyncSlotDataWithClientsRpc(Plugin.customRPCManagerObject); } } [HarmonyPatch(typeof(PunManager), "Start")] internal class DisplayCompletionStatusPatch { private static void Postfix() { if (!((Object)RunManager.instance.levelCurrent).name.Contains("Menu")) { APSave.CheckCompletion(out var status); HandleAPTruckScreenMessages.TruckScreenChatPatch.AddMessage("AP", status); } } } internal class EnemyDespawnPatch { [HarmonyPatch(typeof(EnemyParent), "Despawn")] [HarmonyPostfix] private static void OrbNaming(ref string ___enemyName, ref Enemy ___Enemy) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown bool num = (bool)AccessTools.Field(typeof(Enemy), "HasHealth").GetValue(___Enemy); EnemyHealth val = (EnemyHealth)AccessTools.Field(typeof(Enemy), "Health").GetValue(___Enemy); int num2 = (int)AccessTools.Field(typeof(EnemyHealth), "healthCurrent").GetValue(val); if (num && val.spawnValuable && num2 <= 0) { if (!SemiFunc.IsMultiplayer()) { ChangeEnemyOrbNames(___enemyName); } else { Plugin.customRPCManager.CallClientChangeMonsterOrbName(Plugin.customRPCManagerObject, ___enemyName); } } } internal static void ChangeEnemyOrbNames(string enemyName) { EnemyValuable[] array = (EnemyValuable[])(object)Object.FindObjectsByType(typeof(EnemyValuable), (FindObjectsSortMode)0); foreach (EnemyValuable val in array) { if (((Object)val).name.Contains("Enemy Valuable")) { ((Object)val).name = enemyName + " Soul"; } } } } internal class ExtractSendCheck { private static void CheckValuable(GameObject valuableObject) { Plugin.Logger.LogInfo((object)("Extracting " + ((Object)valuableObject).name)); if (!Object.op_Implicit((Object)(object)valuableObject) || !Object.op_Implicit((Object)(object)valuableObject.GetComponent())) { return; } if (((Object)valuableObject).name.Contains("Pelly")) { Plugin.connection.ActivateCheck(LocationData.PellyNameToID(((Object)valuableObject).name + ((Object)RunManager.instance.levelCurrent).name)); APSave.AddPellyGathered(((Object)RunManager.instance.levelCurrent).name + ((Object)valuableObject).name); } else if (((Object)valuableObject).name.Contains("Soul")) { long num = LocationData.MonsterSoulNameToID(((Object)valuableObject).name); if (LocationData.RemoveBaseId(num) != 0L) { Plugin.connection.ActivateCheck(num); APSave.AddMonsterSoulGathered(((Object)valuableObject).name); } } else if (((Object)valuableObject).name.Contains("Valuable")) { long num2 = LocationData.ValuableNameToID(((Object)valuableObject).name); if (LocationData.RemoveBaseId(num2) != 0L) { Plugin.connection.ActivateCheck(num2); APSave.AddValuableGathered(((Object)valuableObject).name); } } } public static void Send(FieldInfo totalHaulField) { if (!SemiFunc.IsMasterClientOrSingleplayer() || RoundDirector.instance.dollarHaulList.Count <= 0) { return; } foreach (GameObject dollarHaul in RoundDirector.instance.dollarHaulList) { CheckValuable(dollarHaul); } } public static void SendFirst(FieldInfo totalHaulField) { if (SemiFunc.IsMasterClientOrSingleplayer() && RoundDirector.instance.dollarHaulList.Count > 0) { CheckValuable(RoundDirector.instance.dollarHaulList[0]); } } } [HarmonyPatch(typeof(ExtractionPoint))] internal class ExtractionSendCheckPatch { private static FieldInfo field = AccessTools.Field(typeof(RoundDirector), "totalHaul"); [HarmonyPrefix] [HarmonyPatch("DestroyAllPhysObjectsInHaulList")] private static void ExtractAllPatch() { if (Plugin.connection != null) { ExtractSendCheck.Send(field); } } [HarmonyPrefix] [HarmonyPatch("DestroyTheFirstPhysObjectsInHaulList")] private static void ExtractFirstPatch() { if (Plugin.connection != null) { ExtractSendCheck.SendFirst(field); } } [HarmonyPostfix] [HarmonyPatch("DestroyAllPhysObjectsInHaulList")] private static void ExtractAllSyncWithClientsPatch() { if (Plugin.connection != null) { Plugin.customRPCManager.CallSyncSlotDataWithClientsRpc(Plugin.customRPCManagerObject); } } } internal static class LocationData { private const int baseID = 75912022; public const int pellyOffset = 100; public const int valuableOffset = 200; public const int monsterOffset = 500; public static void CreateLocationTables() { for (int i = 0; i < 100; i++) { } } public static string GetBaseName(string name) { name = name.Replace("Valuable ", "").Replace("(Clone)", ""); foreach (string item in LocationNames.all_levels_short) { name = name.Replace(item + " ", ""); } return name; } public static long MonsterSoulNameToID(string name) { long num = 0L; name = GetBaseName(name); num = LocationNames.all_monster_souls.IndexOf(name); if (num == -1) { Plugin.Logger.LogWarning((object)(name + "'s id not found")); num = 0L; } else { num += 500; } return 75912022 + num; } public static string MonsterSoulIDToName(long id) { id -= 75912022; id -= 500; return LocationNames.all_monster_souls[(int)id]; } public static long ValuableNameToID(string name) { long num = 0L; name = GetBaseName(name); num = LocationNames.all_valuables.IndexOf(name); if (num == -1) { Plugin.Logger.LogWarning((object)(name + "'s id not found")); num = 0L; } else { num += 200; } return 75912022 + num; } public static string ValuableIDToName(long id) { id -= 75912022; id -= 200; return LocationNames.all_valuables[(int)id]; } public static long PellyNameToID(string name) { int num = 100; int num2 = 1; foreach (string item in LocationNames.all_levels_short) { if (name.Contains(item)) { num += num2; break; } num2 += 3; } num2 = 0; foreach (string all_pelly in LocationNames.all_pellys) { if (name.Contains(all_pelly)) { num += num2; break; } num2++; } return 75912022 + num; } public static long ShopItemToID(string name) { if (name.Any(char.IsDigit)) { name = new string(name.Where((char x) => char.IsDigit(x)).ToArray()); int num = int.Parse(name); return 75912022 + num; } return 0L; } public static long RemoveBaseId(long id) { return id - 75912022; } public static long AddBaseId(long id) { return id + 75912022; } } internal class LocationNames { public static string standard_pelly = "Standard Pelly"; public static string glass_pelly = "Glass Pelly"; public static string gold_pelly = "Gold Pelly"; public static string[] pellys = new string[3] { standard_pelly, glass_pelly, gold_pelly }; public static string upgrade_pur = "Upgrade Purchase"; public static string swiftbroom_academy = "Swiftbroom Academy "; public static string headman_manor = "Headman Manor "; public static string mcjannek_station = "McJannek Station "; public static string museum_of_human_art = "Museum of Human Art "; public const string goblet = "Goblet"; public const string uranium_mug = "Uranium Mug"; public const string emerald_bracelet = "Emerald Bracelet"; public const string ocarina = "Ocarina"; public const string pocket_watch = "Pocket Watch"; public const string music_box = "Music Box"; public const string instrument = "Instrument"; public const string doll = "Doll"; public const string uranium_plate = "Uranium Plate"; public const string globe = "Globe"; public const string frog = "Frog"; public const string toy_monkey = "Toy Monkey"; public const string money = "Money"; public const string small_vase = "Vase Small"; public const string radio = "Radio"; public const string gramophone = "Gramophone"; public const string ship_in_a_bottle = "Ship in a bottle"; public const string kettle = "Kettle"; public const string old_camera = "Old Camera"; public const string magnifying_glass = "Magnifying Glass"; public const string map = "Map"; public const string vase = "Vase"; public const string bottle = "Bottle"; public const string clown_doll = "Clown"; public const string trophy = "Trophy"; public const string television = "Television"; public const string diamond_display = "Diamond Display"; public const string scream_doll = "Scream Doll"; public const string telescope = "Telescope"; public const string chunky_vase = "Vase Chunky"; public const string big_vase = "Vase Big"; public const string piano = "Piano"; public const string animal_crate = "Animal Crate"; public const string harp = "Harp"; public const string painting = "Painting"; public const string grandfather_clock = "Grandfather Clock"; public const string dinosaur = "Dinosaur"; public const string golden_statue = "Golden Statue"; public const string coffin = "Coffin"; public const string keycard = "Keycard"; public const string uranium_petri_dish = "Uranium Petri Dish"; public const string eraser = "Eraser"; public const string pills = "Pills"; public const string stapler = "Stapler"; public const string usb_stick = "Usb stick"; public const string coffee_cup = "Coffee Cup"; public const string smartwatch = "Smartwatch"; public const string phone = "Phone"; public const string bonsai = "Bonsai"; public const string hdd = "HDD"; public const string calculator = "Calculator"; public const string vhs = "VHS"; public const string camera = "Camera"; public const string laptop = "Laptop"; public const string sample = "Sample"; public const string sample_pack = "Sample Six Pack"; public const string fan = "Fan"; public const string computer = "Computer"; public const string printer_3d = "3D Printer"; public const string propane_tank = "Propane Tank"; public const string scale = "Scale"; public const string flashlight = "Flashlight"; public const string fire_extinguisher = "Fire Extinguisher"; public const string icepick = "Icepick"; public const string barrel = "Barrel"; public const string sample_cooler = "Sample Cooler"; public const string big_sample = "Big Sample"; public const string creature_leg = "Creature Leg"; public const string guitar = "Guitar"; public const string ice_saw = "Ice Saw"; public const string flamethrower = "Flamethrower"; public const string ice_block = "Ice Block"; public const string snow_bike = "Snow Bike"; public const string centrifuge = "Centrifuge"; public const string sample_cooler_wide = "Sample Cooler Wide"; public const string science_station = "Science Station"; public const string heavy_water = "Heavy Water"; public const string jackhammer = "Jackhammer"; public const string server_rack = "Server Rack"; public const string cryo_pod = "Cryo Pod"; public const string eye_ball = "Eye Ball"; public const string diamond = "Diamond"; public const string bird_skull = "Bird Skull"; public const string bug = "Bug"; public const string glowing_jar = "Glowing Jar"; public const string small_gem = "Small Gem"; public const string small_potion = "Small Potion"; public const string chomp_book = "Chomp Book"; public const string love_potion = "Love Potion"; public const string red_mushroom = "Red Mushroom"; public const string pendant = "Pendant"; public const string fortune_card = "Fortune Card"; public const string levitation_potion = "Levitation Potion"; public const string gem_box = "Gem Box"; public const string crown = "Crown"; public const string power_crystal = "Power Crystal"; public const string time_glass = "Time Glass"; public const string goblin_head = "Goblin Head"; public const string tentacle = "Tentacle"; public const string star_wand = "Star Wand"; public const string poison_chalice = "Poison Chalice"; public const string crystal = "Crystal"; public const string crystal_ball = "Crystal Ball"; public const string eye_of_orpigox = "Eye of Orpigox"; public const string cube_of_knowledge = "Cube of Knowledge"; public const string master_potion = "Master Potion"; public const string unicorn_horn = "Unicorn Horn"; public const string forever_candle = "Forever Candle"; public const string cauldron_box = "Cauldron Box"; public const string spider_potion = "Spider Potion"; public const string griffin_statue = "Griffin Statue"; public const string dragon_skull = "Dragon Skull"; public const string alchemy_staion = "Alchemy Station"; public const string goblin_arm = "Goblin Arm"; public const string dumgolfs_staff = "Dumgolfs Staff"; public const string sword = "Sword"; public const string broom = "Broom"; public const string troll_finger = "Troll Finger"; public const string tooth = "Tooth"; public const string silverfish = "SilverFish"; public const string rubendoll = "RubenDoll"; public const string goldfish = "GoldFish"; public const string fish = "Fish"; public const string cool_brain = "Cool brain"; public const string toast = "Toast"; public const string goldtooth = "GoldTooth"; public const string wire_figure = "Wire Figure"; public const string flesh_blob = "Flesh Blob"; public const string banana_bow = "Banana Bow"; public const string cubic_tower = "Cubic Tower"; public const string car = "Car"; public const string ladybug = "ladybug"; public const string duck_man = "duck man"; public const string cube_ball = "Cube ball"; public const string cocktail = "Cocktail"; public const string pimpleguy = "PimpleGuy"; public const string plane = "Plane"; public const string pacifier = "Pacifier"; public const string monkeybox = "MonkeyBox"; public const string gumball = "Gumball"; public const string handface = "Handface"; public const string cubic_sculpture = "Cubic Sculpture"; public const string teeth_bot = "Teeth Bot"; public const string uranium_mug_deluxe = "Uranium Mug Deluxe"; public const string golden_swirl = "Golden Swirl"; public const string boombox = "Boombox"; public const string gem_burger = "Gem Burger"; public const string baby_head = "Baby Head"; public const string egg = "Egg"; public const string horse = "Horse"; public const string worm = "Worm"; public const string vinyl = "Vinyl"; public const string tray = "Tray"; public const string milk = "Milk"; public const string tall_guy = "Tall Guy"; public const string blender = "Blender"; public const string traffic_light = "Traffic Light"; public static readonly ReadOnlyCollection all_pellys = new ReadOnlyCollection(new List { standard_pelly, glass_pelly, gold_pelly }); public static readonly ReadOnlyCollection all_levels_short = new ReadOnlyCollection(new List { "Wizard", "Manor", "Arctic", "Museum" }); public static readonly ReadOnlyCollection all_levels = new ReadOnlyCollection(new List { swiftbroom_academy, headman_manor, mcjannek_station, museum_of_human_art }); public static readonly ReadOnlyCollection all_valuables = new ReadOnlyCollection(new List { "Goblet", "Uranium Mug", "Emerald Bracelet", "Ocarina", "Pocket Watch", "Music Box", "Instrument", "Doll", "Uranium Plate", "Globe", "Frog", "Toy Monkey", "Money", "Vase Small", "Radio", "Gramophone", "Ship in a bottle", "Kettle", "Old Camera", "Magnifying Glass", "Map", "Vase", "Bottle", "Clown", "Trophy", "Television", "Diamond Display", "Scream Doll", "Telescope", "Vase Chunky", "Vase Big", "Piano", "Animal Crate", "Harp", "Painting", "Grandfather Clock", "Dinosaur", "Golden Statue", "Coffin", "Keycard", "Uranium Petri Dish", "Eraser", "Pills", "Stapler", "Usb stick", "Coffee Cup", "Smartwatch", "Phone", "Bonsai", "HDD", "Calculator", "VHS", "Camera", "Laptop", "Sample", "Sample Six Pack", "Fan", "Computer", "3D Printer", "Propane Tank", "Scale", "Flashlight", "Fire Extinguisher", "Icepick", "Barrel", "Sample Cooler", "Big Sample", "Creature Leg", "Guitar", "Ice Saw", "Flamethrower", "Ice Block", "Snow Bike", "Centrifuge", "Sample Cooler Wide", "Science Station", "Heavy Water", "Jackhammer", "Server Rack", "Cryo Pod", "Eye Ball", "Diamond", "Bird Skull", "Bug", "Glowing Jar", "Small Gem", "Small Potion", "Chomp Book", "Love Potion", "Red Mushroom", "Pendant", "Fortune Card", "Levitation Potion", "Gem Box", "Crown", "Power Crystal", "Time Glass", "Goblin Head", "Tentacle", "Star Wand", "Poison Chalice", "Crystal", "Crystal Ball", "Eye of Orpigox", "Cube of Knowledge", "Master Potion", "Unicorn Horn", "Forever Candle", "Cauldron Box", "Spider Potion", "Griffin Statue", "Dragon Skull", "Alchemy Station", "Goblin Arm", "Dumgolfs Staff", "Sword", "Broom", "Troll Finger", "Tooth", "SilverFish", "RubenDoll", "GoldFish", "Fish", "Cool brain", "Toast", "GoldTooth", "Wire Figure", "Flesh Blob", "Banana Bow", "Cubic Tower", "Car", "ladybug", "duck man", "Cube ball", "Cocktail", "PimpleGuy", "Plane", "Pacifier", "MonkeyBox", "Gumball", "Handface", "Cubic Sculpture", "Teeth Bot", "Uranium Mug Deluxe", "Golden Swirl", "Boombox", "Gem Burger", "Baby Head", "Egg", "Horse", "Worm", "Vinyl", "Tray", "Milk", "Tall Guy", "Blender", "Traffic Light" }); public const string animal_soul = "Animal Soul"; public const string apex_predator_soul = "Apex Predator Soul"; public const string bella_soul = "Bella Soul"; public const string birthday_boy_soul = "Birthday Boy Soul"; public const string bowtie_soul = "Bowtie Soul"; public const string chef_soul = "Chef Soul"; public const string cleanup_crew_soul = "Cleanup Crew Soul"; public const string clown_soul = "Clown Soul"; public const string elsa_soul = "Elsa Soul"; public const string gambit_soul = "Gambit Soul"; public const string headgrab_soul = "Headgrab Soul"; public const string headman_soul = "Headman Soul"; public const string heart_hugger_soul = "Heart Hugger Soul"; public const string hidden_soul = "Hidden Soul"; public const string huntsman_soul = "Huntsman Soul"; public const string loom_soul = "Loom Soul"; public const string mentalist_soul = "Mentalist Soul"; public const string oogly_soul = "Oogly Soul"; public const string peeper_soul = "Peeper Soul"; public const string reaper_soul = "Reaper Soul"; public const string robe_soul = "Robe Soul"; public const string rugrat_soul = "Rugrat Soul"; public const string shadow_child_soul = "Shadow Child Soul"; public const string spewer_soul = "Spewer Soul"; public const string tick_soul = "Tick Soul"; public const string trudge_soul = "Trudge Soul"; public const string upscream_soul = "Upscream Soul"; public static readonly ReadOnlyCollection all_monster_souls = new ReadOnlyCollection(new List { "Animal Soul", "Apex Predator Soul", "Bella Soul", "Birthday Boy Soul", "Bowtie Soul", "Chef Soul", "Cleanup Crew Soul", "Clown Soul", "Elsa Soul", "Gambit Soul", "Headgrab Soul", "Headman Soul", "Heart Hugger Soul", "Hidden Soul", "Huntsman Soul", "Loom Soul", "Mentalist Soul", "Oogly Soul", "Peeper Soul", "Reaper Soul", "Robe Soul", "Rugrat Soul", "Shadow Child Soul", "Spewer Soul", "Tick Soul", "Trudge Soul", "Upscream Soul" }); } [HarmonyPatch(typeof(ValuableDirector), "Start")] internal class PellySpawingPatch { [HarmonyPrefix] private static void RemovePellysFromList(ValuableDirector __instance) { if (Plugin.connection?.session == null || APSave.saveData.pellySpawning) { return; } Plugin.Logger.LogDebug((object)"In ValuableDirector"); _ = (List)AccessTools.Field(typeof(ValuableDirector), "mediumValuables").GetValue(__instance); foreach (LevelValuables valuablePreset in LevelGenerator.Instance.Level.ValuablePresets) { foreach (PrefabRef item in valuablePreset.medium.Where((PrefabRef x) => x.PrefabName.Contains("Pelly")).ToList()) { Plugin.Logger.LogInfo((object)("Removing: " + item.PrefabName)); valuablePreset.medium.Remove(item); } } } } [HarmonyPatch(typeof(PhysGrabObject))] internal class PhysGrabObjectPatch { [HarmonyPatch("GrabStarted")] [HarmonyPostfix] private static void OrbInfoTextEnabler(PhysGrabObject __instance) { string name = ((Object)((Component)__instance).gameObject).name; bool flag = name.Contains("Soul"); bool flag2 = name.Contains("Valuable"); bool flag3 = name.Contains("Pelly"); if (name.Contains("Surplus") || !(flag || flag2 || flag3)) { return; } string text = ""; if (Plugin.connection.session != null || !SemiFunc.IsMasterClientOrSingleplayer()) { long id = -1L; bool flag4 = false; bool flag5 = false; if (flag) { id = LocationData.MonsterSoulNameToID(name); flag4 = APSave.WasMonsterSoulGathered(name); flag5 = APSave.saveData.monsterHunt; } else if (flag3) { id = LocationData.PellyNameToID(name + ((Object)RunManager.instance.levelCurrent).name); flag4 = APSave.WasPellyGathered(name, ((Object)RunManager.instance.levelCurrent).name); flag5 = APSave.saveData.pellySpawning; } else if (flag2) { id = LocationData.ValuableNameToID(name); flag4 = APSave.WasValuableGathered(name); flag5 = APSave.saveData.valuableHunt; } SerializableItemInfo scoutedLocation = APSave.GetScoutedLocation(id); if (flag5 && flag4) { text = "
extracted"; } else if (flag5 && scoutedLocation == null) { text = "
not extracted"; } else if (flag4 && scoutedLocation != null) { text = "
found"; } else if (!flag4 && scoutedLocation != null) { try { text = string.Format("
{1}'s {2}", "#ef8a62", scoutedLocation.Player, scoutedLocation.ItemName); } catch (Exception ex) { Plugin.Logger.LogWarning((object)("OrbInfoTextEnabler: " + ex.Message)); text = "
unknown"; } } } name = LocationData.GetBaseName(name); SemiFunc.UIItemInfoText((ItemAttributes)null, name + text); } } [HarmonyPatch(typeof(ExtractionPoint), "DestroyAllPhysObjectsInShoppingList")] internal class ShopSendCheckPatch { private static FieldInfo field = AccessTools.Field(typeof(ShopManager), "shoppingList"); private static List shoppingList; private static FieldInfo field2 = AccessTools.Field(typeof(ItemAttributes), "value"); private static int value; [HarmonyPrefix] private static bool ShopCheckPatch(ExtractionPoint __instance) { //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Invalid comparison between Unknown and I4 //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Invalid comparison between Unknown and I4 shoppingList = (List)field.GetValue(ShopManager.instance); Plugin.Logger.LogInfo((object)"Connected in shop check"); if (SemiFunc.IsMasterClientOrSingleplayer()) { if (Plugin.connection == null) { Plugin.Logger.LogInfo((object)"Connection Null"); return true; } foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { player.playerDeathHead.Revive(); } List list = new List(); foreach (ItemAttributes shopping in shoppingList) { value = (int)field2.GetValue(shopping); if (!Object.op_Implicit((Object)(object)shopping) || !Object.op_Implicit((Object)(object)((Component)shopping).GetComponent()) || SemiFunc.StatGetRunCurrency() - value < 0) { continue; } SemiFunc.StatSetRunCurrency(SemiFunc.StatGetRunCurrency() - value); if (((Object)shopping.item).name == ItemNames.ap_item) { Plugin.Logger.LogInfo((object)("AP ITEM PURCHASED " + ((Object)shopping).name)); long num = LocationData.ShopItemToID(((Object)shopping).name); if (num != 0L) { Plugin.connection.ActivateCheck(num); } } else { Plugin.Logger.LogInfo((object)("Not AP Item\n" + ((Object)shopping.item).name + " != " + ItemNames.ap_item)); StatsManager.instance.ItemPurchase(((Object)shopping.item).name); } if ((int)shopping.item.itemType == 3 && ((Object)shopping.item).name != ItemNames.ap_item) { StatsManager.instance.AddItemsUpgradesPurchased(((Object)shopping.item).name); } if ((int)shopping.item.itemType == 5) { StatsManager.instance.runStats["chargingStationChargeTotal"] += 17; if (StatsManager.instance.runStats["chargingStationChargeTotal"] > 100) { StatsManager.instance.runStats["chargingStationChargeTotal"] = 100; } } ((Component)shopping).GetComponent().DestroyPhysGrabObject(); list.Add(shopping); } foreach (ItemAttributes item in list) { List list2 = (List)field.GetValue(ShopManager.instance); list2.Remove(item); field.SetValue(ShopManager.instance, list2); } SemiFunc.ShopUpdateCost(); } return false; } } [HarmonyPatch(typeof(PunManager), "Start")] internal class ItemNumberTracker { [HarmonyPostfix] private static void SetNumber() { Plugin.Logger.LogInfo((object)("Current Level: " + ((Object)RunManager.instance.levelCurrent).name)); if (!((Object)RunManager.instance.levelCurrent).name.Contains("Menu") && !((Object)RunManager.instance.levelCurrent).name.Contains("Splash")) { Plugin.LastShopItemChecked = 0; APSave.UpdateAvailableItems(); } } } [HarmonyPatch(typeof(ShopManager), "GetAllItemsFromStatsManager")] internal class CreateShopItemListPatch { private static bool IsItemUnlockedInMultiworld(Item itemToCheck) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)itemToCheck.itemType == 3) { Plugin.Logger.LogDebug((object)("Item '" + itemToCheck.itemName + "' is an upgrade and always able to spawn")); return true; } if (itemToCheck.itemName.Contains("Health Pack")) { if (itemToCheck.itemName.Contains("Large") && !APSave.IsItemReceived(ItemData.AddBaseId(ItemData.itemNameToID[ItemNames.progressive_health]), 3)) { return false; } if (itemToCheck.itemName.Contains("Medium") && !APSave.IsItemReceived(ItemData.AddBaseId(ItemData.itemNameToID[ItemNames.progressive_health]), 2)) { return false; } if (itemToCheck.itemName.Contains("Small") && !APSave.IsItemReceived(ItemData.AddBaseId(ItemData.itemNameToID[ItemNames.progressive_health]))) { return false; } Plugin.Logger.LogDebug((object)("Item '" + itemToCheck.itemName + "' is unlocked and able to spawn")); return true; } if (!ItemData.itemNameToID.TryGetValue(itemToCheck.itemName + " Unlock", out var value)) { if (itemToCheck.itemName.Equals("Duct Taped Grenades")) { value = ItemData.itemNameToID[ItemNames.duct_taped_grenade]; } else if (itemToCheck.itemName.Equals("POCKET C.A.R.T.")) { value = ItemData.itemNameToID[ItemNames.pocket_cart]; } else if (itemToCheck.itemName.Equals("Extraction Tracker")) { value = ItemData.itemNameToID[ItemNames.extraction_detector]; } else if (itemToCheck.itemName.Equals("Shockwave Grenade")) { value = ItemData.itemNameToID[ItemNames.shock_grenade]; } else { if (!itemToCheck.itemName.Equals("Valuable Tracker")) { Plugin.Logger.LogDebug((object)("Item '" + itemToCheck.itemName + "' not found in AP data table")); return true; } value = ItemData.itemNameToID[ItemNames.valuable_detector]; } } if (APSave.IsItemReceived(ItemData.AddBaseId(value))) { Plugin.Logger.LogDebug((object)("Item '" + itemToCheck.itemName + "' is unlocked and able to spawn")); return true; } Plugin.Logger.LogDebug((object)("Item '" + itemToCheck.itemName + "' is not unlocked and will not spawn")); return false; } [HarmonyPostfix] private static void RemoveLockedItemsFromShopPool(ShopManager __instance) { //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Invalid comparison between Unknown and I4 //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Invalid comparison between Unknown and I4 //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Invalid comparison between Unknown and I4 //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Invalid comparison between Unknown and I4 //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Invalid comparison between Unknown and I4 //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Invalid comparison between Unknown and I4 if (SemiFunc.IsNotMasterClient()) { return; } List list = ShopManager.instance.potentialItems.Where((Item item) => IsItemUnlockedInMultiworld(item)).ToList(); List potentialItemConsumables = ShopManager.instance.potentialItemConsumables.Where((Item item) => IsItemUnlockedInMultiworld(item)).ToList(); List list2 = ShopManager.instance.potentialItemUpgrades.Where((Item item) => (Object)(object)item != (Object)(object)StatsManager.instance.itemDictionary[ItemNames.ap_item]).ToList(); List potentialItemHealthPacks = ShopManager.instance.potentialItemHealthPacks.Where((Item item) => IsItemUnlockedInMultiworld(item)).ToList(); Dictionary> dictionary = new Dictionary>(); foreach (List value in ShopManager.instance.potentialSecretItems.Values) { list.AddRange(value.Where((Item secretItem) => IsItemUnlockedInMultiworld(secretItem))); } bool flag = list2.Count() == 0; foreach (Item value2 in StatsManager.instance.itemDictionary.Values) { if ((int)value2.itemType != 2 && (int)value2.itemType != 12 && (int)value2.itemType != 3 && (int)value2.itemType != 5 && (int)value2.itemType != 8 && (IsItemUnlockedInMultiworld(value2) || Random.Range(0, 100) < 10)) { if (!dictionary.ContainsKey((itemSecretShopType)1)) { dictionary.Add((itemSecretShopType)1, new List()); } dictionary[(itemSecretShopType)1].Add(value2); } else if (flag && (int)value2.itemType == 3 && (Object)(object)value2 != (Object)(object)StatsManager.instance.itemDictionary[ItemNames.ap_item]) { list2.Add(value2); } } if (flag) { ListExtension.Shuffle((IList)list2); } foreach (List value3 in dictionary.Values) { ListExtension.Shuffle((IList)value3); } ShopManager.instance.potentialItems = list; ShopManager.instance.potentialItemConsumables = potentialItemConsumables; ShopManager.instance.potentialItemUpgrades = list2; ShopManager.instance.potentialItemHealthPacks = potentialItemHealthPacks; ShopManager.instance.potentialSecretItems = dictionary; } } internal class ShopPopulateItemVolumesPatch { private static readonly FieldInfo potentialItemsInfo = AccessTools.Field(typeof(ShopManager), "potentialItems"); private static readonly FieldInfo potentialItemConsumablesInfo = AccessTools.Field(typeof(ShopManager), "potentialItemConsumables"); private static readonly FieldInfo itemSpawnTargetAmountInfo = AccessTools.Field(typeof(ShopManager), "itemSpawnTargetAmount"); [HarmonyPatch(typeof(PunManager), "ShopPopulateItemVolumes")] [HarmonyTranspiler] private static IEnumerable PreventStupidZeroUpgradesPatch(IEnumerable instructions) { Plugin.Logger.LogInfo((object)"Patching ShopPopulateItemVolumes with a transpiler to let upgrades spawn..."); bool flag = false; bool flag2 = false; int num = -1; List list = new List(instructions); for (int i = 0; i < list.Count; i++) { if (CodeInstructionExtensions.LoadsField(list[i], potentialItemsInfo, false)) { flag = true; continue; } if (flag && CodeInstructionExtensions.LoadsField(list[i], potentialItemConsumablesInfo, false)) { flag2 = true; continue; } if (flag && flag2 && CodeInstructionExtensions.LoadsField(list[i], itemSpawnTargetAmountInfo, false)) { break; } if (flag && flag2 && list[i].opcode == OpCodes.Leave) { num = i; break; } } if (num != -1) { list[num].opcode = OpCodes.Nop; Plugin.Logger.LogInfo((object)"Successfully patched ShopPopulateItemVolumes."); return list; } Plugin.Logger.LogInfo((object)"Failed to patch ShopPopulateItemVolumes. Target instruction not found!"); return instructions; } } [HarmonyPatch(typeof(PunManager), "SpawnShopItem")] internal class SpawnShopItemPatch { [HarmonyPrefix] private static bool ReplaceItemPatch(ref bool __result, ref ItemVolume itemVolume, ref List itemList, ref int spawnCount, bool isSecret = false) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Invalid comparison between Unknown and I4 //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: 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_013b: Invalid comparison between Unknown and I4 if (itemList.Count <= 0 || (int)itemVolume.itemVolume != 6) { return true; } for (int num = itemList.Count - 1; num >= 0; num--) { Item val = ((!itemList[num].itemName.Contains("Upgrade") || ((Object)itemList[num]).name.Contains("Counted") || Plugin.ShopItemsAvailable.Count <= 0) ? itemList[num] : StatsManager.instance.itemDictionary[ItemNames.ap_item]); if (val.itemVolume == itemVolume.itemVolume) { ((Component)ShopManager.instance.itemRotateHelper).transform.parent = ((Component)itemVolume).transform; ((Component)ShopManager.instance.itemRotateHelper).transform.localRotation = val.spawnRotationOffset; Quaternion rotation = ((Component)ShopManager.instance.itemRotateHelper).transform.rotation; ((Component)ShopManager.instance.itemRotateHelper).transform.parent = ((Component)ShopManager.instance).transform; if (SemiFunc.IsMultiplayer()) { GameObject val2 = PhotonNetwork.InstantiateRoomObject(val.prefab.ResourcePath, ((Component)itemVolume).transform.position, rotation, (byte)0, (object[])null); Plugin.LastShopItemChecked++; if ((int)val.itemType == 3 && ((Object)val).name == ItemNames.ap_item) { Plugin.Logger.LogDebug((object)("Replacing " + itemList[num].itemName + " with a random AP item")); int index = new Random().Next(Plugin.ShopItemsAvailable.Count); int num2 = Plugin.ShopItemsAvailable[index]; ((Object)val2).name = ((Object)val2).name + "_Counted_" + num2; Plugin.ShopItemsAvailable.RemoveAt(index); } } else { GameObject val3 = Object.Instantiate(val.prefab.Prefab, ((Component)itemVolume).transform.position, rotation); Plugin.LastShopItemChecked++; if ((int)val.itemType == 3 && ((Object)val).name == ItemNames.ap_item) { Plugin.Logger.LogDebug((object)("Replacing " + itemList[num].itemName + " with a random AP item")); int index2 = new Random().Next(Plugin.ShopItemsAvailable.Count); int num3 = Plugin.ShopItemsAvailable[index2]; ((Object)val3).name = ((Object)val3).name + "_Counted_" + num3; Plugin.ShopItemsAvailable.RemoveAt(index2); Plugin.Logger.LogDebug((object)$"Spawned AP Item with ID: {num3}"); } } itemList.RemoveAt(num); if (!isSecret) { spawnCount++; } __result = true; return false; } } __result = false; return false; } } [HarmonyPatch(typeof(PunManager), "ShopPopulateItemVolumes")] internal class ApStoreItemsPatch { [HarmonyPrefix] private static void RefreshAvailableAPShopItems() { Plugin.Logger.LogInfo((object)"Refreshing Available AP Shop Items"); APSave.UpdateAvailableItems(); } } internal class APItemNamePatch { private static List apItems = new List(); [HarmonyPatch(typeof(ItemAttributes), "Start")] [HarmonyPostfix] private static void NamePatch(ref string ___itemName, ItemAttributes __instance) { if (___itemName.Contains("Archipelago")) { ((Component)__instance).gameObject.AddComponent(); if (SemiFunc.IsMasterClientOrSingleplayer() && SemiFunc.RunIsShop()) { apItems.Add(__instance); } } } [HarmonyPatch(typeof(LevelGenerator), "GenerateDone")] [HarmonyPostfix] private static void SyncAPItemNamesWithClients() { if (!SemiFunc.IsMasterClientOrSingleplayer() || !SemiFunc.RunIsShop()) { return; } Plugin.Logger.LogDebug((object)$"In SyncAPItemNamesWithClients. apItems has {apItems.Count} entries"); foreach (ItemAttributes apItem in apItems) { string text = ((Object)apItem).name; if (text.Any(char.IsDigit)) { text = new string(text.Where((char x) => char.IsDigit(x)).ToArray()); } SerializableItemInfo scoutedShopItem = APSave.GetScoutedShopItem(LocationData.AddBaseId(long.Parse(text))); string text2 = $"{scoutedShopItem.Player}'s {scoutedShopItem.ItemName}"; if (GameManager.instance.gameMode == 1) { Plugin.customRPCManager.CallUpdateItemNameRPC(text2, ((Component)apItem).gameObject); } else { AccessTools.Field(typeof(ItemAttributes), "itemName").SetValue(apItem, text2); } } apItems.Clear(); } } [HarmonyPatch(typeof(PlayerController), "Update")] internal class DebugKeys { [HarmonyPrefix] private static void Prefix() { } } public static class MyPluginInfo { public const string PLUGIN_GUID = "RepoAP"; public const string PLUGIN_NAME = "Archipelago Randomizer"; public const string PLUGIN_VERSION = "0.4.0"; } } namespace RepoAP.Locations { [HarmonyPatch(typeof(EnemyDirector), "PickEnemies")] internal class AdjustMonsterOddsPatch { private static readonly RarityPreset apBoosted = ScriptableObject.CreateInstance(); private static bool initialized = false; private static Dictionary originalPresets = new Dictionary(); private static void InitApBoostedPreset() { ((Object)apBoosted).name = "AP Boosted"; apBoosted.chance = 100f + (float)Math.Max(0u, Plugin.BoundConfig.EnemyWeightIncrease.Value); } [HarmonyPrefix] private static void RaiseSpawnChanceOfUndefeatedEnemies(ref List _enemiesList) { if (!initialized) { InitApBoostedPreset(); initialized = true; } int num = 0; foreach (string all_monster_soul in LocationNames.all_monster_souls) { if (APSave.saveData.monsterSoulsGathered.Contains(all_monster_soul)) { num++; } } if (Plugin.connection.session == null || !Plugin.connection.connected || !((float)num / (float)LocationNames.all_monster_souls.Count > 0.5f)) { return; } foreach (EnemySetup item in _enemiesList.Where((EnemySetup enemy) => enemy.spawnObjects.Count == 1 && (Object)(object)enemy.spawnObjects[0].Prefab.GetComponent() != (Object)null)) { bool flag = APSave.WasMonsterSoulGathered(item.spawnObjects[0].Prefab.GetComponent()?.enemyName + " Soul"); if (!flag && (Object)(object)item.rarityPreset != (Object)(object)apBoosted) { originalPresets[((Object)item).name] = item.rarityPreset; item.rarityPreset = apBoosted; Plugin.Logger.LogInfo((object)$"Raised spawn weight of enemy '{((Object)item).name}' to {item.rarityPreset.chance}"); } else if (flag && (Object)(object)item.rarityPreset == (Object)(object)apBoosted) { item.rarityPreset = originalPresets[((Object)item).name]; Plugin.Logger.LogInfo((object)("Reduced spawn weight of enemy '" + ((Object)item).name + "' back to the default value")); } } } } [HarmonyPatch(typeof(ValuableDirector), "Spawn")] internal class AdjustValuableOddsPatch { [HarmonyPrefix] private static void ReplaceFoundValuableWithChance(ref ValuableDirector __instance, ref PrefabRef _valuable, ref ValuableVolume _volume, ref string _path) { //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_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected I4, but got Unknown bool flag = (float)Random.Range(0, 100) < Mathf.Clamp((float)Plugin.BoundConfig.ValuableSubstitutionChance.Value, 0f, 100f); Type volumeType = _volume.VolumeType; PrefabRef realValuable = _valuable; string text = (int)volumeType switch { 0 => "tinyValuables", 1 => "smallValuables", 2 => "mediumValuables", 3 => "bigValuables", 4 => "wideValuables", 5 => "tallValuables", 6 => "veryTallValuables", _ => "", }; if ((flag && text != "" && _valuable.PrefabName.Contains("Pelly")) ? APSave.WasPellyGathered(_valuable.PrefabName, ((Object)RunManager.instance.levelCurrent).name) : APSave.WasValuableGathered(_valuable.PrefabName)) { string prefabName = realValuable.PrefabName; List list = ((List)AccessTools.Field(typeof(ValuableDirector), text).GetValue(__instance)).Where((PrefabRef prefab) => prefab != realValuable && !(prefab.PrefabName.Contains("Pelly") ? APSave.WasPellyGathered(prefab.PrefabName, ((Object)RunManager.instance.levelCurrent).name) : APSave.WasValuableGathered(prefab.PrefabName))).ToList(); if (list.Count == 0) { Plugin.Logger.LogInfo((object)("No undiscovered valuables available to replace " + prefabName + " in group " + text)); return; } _valuable = list[Random.Range(0, list.Count)]; Plugin.Logger.LogInfo((object)("Replaced valuable " + prefabName + " with " + _valuable.PrefabName)); } } } } namespace RepoAP.Items { internal class StartRunWithAPItems { internal static void GrantAPItems() { if (Plugin.connection.session == null) { return; } Plugin.Logger.LogInfo((object)"Start Run With AP Items"); foreach (KeyValuePair item in APSave.GetItemsReceived()) { for (int i = 0; i < item.Value; i++) { ItemData.AddItemToInventory(item.Key, repeatedAdditions: true); } } } } [HarmonyPatch(typeof(StatsManager), "SaveFileCreate")] internal class CreateRunWithAPItemsPatch { [HarmonyPostfix] private static void RunStartStatsPatch() { Plugin.Logger.LogDebug((object)"Granting ap items from StatsManager.SaveFileCreate"); StartRunWithAPItems.GrantAPItems(); } } [HarmonyPatch(typeof(StatsManager), "LoadGame")] internal class LoadRunWithAPItemsPatch { [HarmonyPostfix] private static void RunStartStatsPatch() { Plugin.Logger.LogDebug((object)"Granting ap items from StatsManager.LoadGame"); StartRunWithAPItems.GrantAPItems(); } } [HarmonyPatch(typeof(RunManager), "ResetProgress")] internal class RestartRunWithAPItemsPatch { [HarmonyPostfix] private static void RunStartStatsPatch(RunManager __instance, bool ___gameOver) { if (!___gameOver && (Object)(object)__instance.levelCurrent == (Object)(object)__instance.levelArena) { Plugin.Logger.LogDebug((object)"Granting ap items from RunManager.ResetProgress"); StartRunWithAPItems.GrantAPItems(); } } } } namespace RepoAP.Core { internal class DeathLinkPatch { public static string playerWhoDied; public static bool awaitingDeathLink = false; public static bool isDeadFromDeathLink = false; public static List playersWithActiveDeathCountdown = new List(); [HarmonyPatch(typeof(RunManager), "ChangeLevel")] [HarmonyPrefix] private static void SendDeadPatch(RunManager __instance, bool _levelFailed, bool ___gameOver, Level ___levelPrevious) { if (!(!SemiFunc.IsMasterClientOrSingleplayer() || !_levelFailed || ___gameOver) && !((Object)(object)__instance.levelCurrent == (Object)(object)__instance.levelArena) && !((Object)(object)___levelPrevious == (Object)(object)__instance.levelArena) && !((Object)(object)__instance.levelCurrent == (Object)(object)__instance.levelShop) && !SemiFunc.IsMainMenu()) { if (!isDeadFromDeathLink) { Plugin.Logger.LogInfo((object)"All players dead. Sending death link"); Plugin.connection.SendDeathLink(); } isDeadFromDeathLink = false; } } [HarmonyPatch(typeof(RunManager), "ChangeLevel")] [HarmonyPostfix] private static void ResetActiveDeathlinkPlayerListInCaseOfIssues() { playersWithActiveDeathCountdown.Clear(); } [HarmonyPatch(typeof(PlayerAvatar), "PlayerDeath")] [HarmonyPostfix] private static void CallDeathLinkFinishedWhenLocalPlayerDead(PlayerAvatar __instance) { if (SemiFunc.IsMultiplayer()) { Plugin.customRPCManager.CallClientDeathLinkFinished(Plugin.customRPCManagerObject, (string)AccessTools.Field(typeof(PlayerAvatar), "steamID").GetValue(__instance)); } } internal static void ReceiveDeathLinkPatch(RunManager __instance) { if (!SemiFunc.IsMasterClientOrSingleplayer() || !awaitingDeathLink) { return; } if ((bool)AccessTools.Field(typeof(RunManager), "allPlayersDead").GetValue(__instance) || SemiFunc.MenuLevel() || (Object)(object)RunManager.instance.levelCurrent == (Object)(object)RunManager.instance.levelLobby) { awaitingDeathLink = false; } else if (SemiFunc.IsMultiplayer() && GameDirector.instance.PlayerList.Count > 1) { Plugin.Logger.LogDebug((object)"Running multiplayer deathlink"); List list = GameDirector.instance.PlayerList.Where((PlayerAvatar player) => !(bool)AccessTools.Field(typeof(PlayerAvatar), "isDisabled").GetValue(player) && !playersWithActiveDeathCountdown.Contains((string)AccessTools.Field(typeof(PlayerAvatar), "steamID").GetValue(player))).ToList(); if (list.Count <= 0) { Plugin.Logger.LogDebug((object)"No deathlink candidates"); awaitingDeathLink = false; return; } Plugin.Logger.LogDebug((object)$"Found {list.Count} deathlink candidate(s)"); int num = Random.Range(0, list.Count); string text = (string)AccessTools.Field(typeof(PlayerAvatar), "steamID").GetValue(list[num]); if (text == (string)AccessTools.Field(typeof(PlayerAvatar), "steamID").GetValue(PlayerAvatar.instance)) { Plugin.Logger.LogDebug((object)"Candidate was me. Starting deathlink countdown"); PosessDeathlink(playerWhoDied, text); } else { Plugin.Logger.LogDebug((object)$"Candidate was not me. Starting countdown for player with steam id {num}"); Plugin.customRPCManager.CallSendClientDeathLink(Plugin.customRPCManagerObject, playerWhoDied, text); } playersWithActiveDeathCountdown.Add(text); awaitingDeathLink = false; if (list.Count <= 1) { isDeadFromDeathLink = true; } } else { Plugin.Logger.LogDebug((object)"Running singleplayer deathlink"); if ((Object)(object)__instance.levelCurrent == (Object)(object)__instance.levelShop) { RunManager.instance.ChangeLevel(false, false, (ChangeLevelType)5); } else { RunManager.instance.ChangeLevel(false, false, (ChangeLevelType)1); } awaitingDeathLink = false; DeathLinkFinished("it doesn't matter what goes here because playersWithActiveDeathCountdown isn't checked in singleplayer"); } } public static void PosessDeathlink(string playerWhoDied, string playerSteamIDToPosess) { //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Expected O, but got Unknown //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Expected O, but got Unknown //IL_02e6: Unknown result type (might be due to invalid IL or missing references) //IL_0308: Unknown result type (might be due to invalid IL or missing references) if (playerSteamIDToPosess != (string)AccessTools.Field(typeof(PlayerAvatar), "steamID").GetValue(PlayerAvatar.instance)) { Plugin.Logger.LogInfo((object)"Deathlink came through but it wasn't for me"); return; } Plugin.Logger.LogInfo((object)("Starting deathlink sequence for player with steam id " + playerSteamIDToPosess)); if ((bool)AccessTools.Field(typeof(PlayerAvatar), "isDisabled").GetValue(PlayerAvatar.instance)) { return; } Plugin.Logger.LogDebug((object)"This player is marked for deathlink"); try { ChatManager.instance.PossessChatScheduleStart(2); List list = new List { "How could " + playerWhoDied + " throw away their life like that?", "I can't believe " + playerWhoDied + " is dead.", "This is your fault, " + playerWhoDied + ".", "Why did " + playerWhoDied + " have to die?", "No, " + playerWhoDied + ", you were supposed to live!", "ALERT: " + playerWhoDied + " SUCKS AT STAYING ALIVE" }; ChatManager.instance.PossessChat((PossessChatID)4, list[Random.Range(0, list.Count)], 2f, Color.red, 0f, true, 2, (UnityEvent)null); ChatManager.instance.PossessChat((PossessChatID)4, "Now I am going to die in...", 1.5f, Color.red, 0.5f, true, 2, (UnityEvent)null); ChatManager.instance.PossessChat((PossessChatID)4, "5...", 0.25f, Color.red, 0.3f, true, 2, (UnityEvent)null); ChatManager.instance.PossessChat((PossessChatID)4, "4...", 0.25f, Color.red, 0.3f, true, 2, (UnityEvent)null); ChatManager.instance.PossessChat((PossessChatID)4, "3...", 0.25f, Color.red, 0.3f, true, 2, (UnityEvent)null); ChatManager.instance.PossessChat((PossessChatID)4, "2...", 0.25f, Color.red, 0.3f, true, 2, (UnityEvent)null); ChatManager.instance.PossessChat((PossessChatID)4, "1...", 0.5f, Color.red, 0.3f, true, 2, (UnityEvent)null); UnityEvent val = new UnityEvent(); val.AddListener((UnityAction)delegate { DeathlinkSelfDestruct(playerSteamIDToPosess); }); List list2 = new List { "Goodbye, cruel world", "Curse you " + playerWhoDied, "Remember me", "aaaaaaaaaaaaaa", "Kaboo-", "Tell the Taxman I-", "Wait, it's okay, I'm not going to-", "[CENSORED]", "I hate this job", "I can see the light", "They are coming for you next" }; ChatManager.instance.PossessChat((PossessChatID)3, list2[Random.Range(0, list2.Count)], 2f, Color.red, 0f, true, 2, (UnityEvent)null); ChatManager.instance.PossessChat((PossessChatID)3, "", 2f, Color.red, 0f, false, 0, val); ChatManager.instance.PossessChatScheduleEnd(); } catch (NullReferenceException arg) { Plugin.Logger.LogError((object)$"Deathlink scheduling failed: {arg}\n"); Plugin.Logger.LogWarning((object)("Some additional information related to the problem:\n" + $"\tCurrent level: {RunManager.instance.levelCurrent}")); Plugin.customRPCManager.CallClientDeathLinkFinished(Plugin.customRPCManagerObject, playerSteamIDToPosess); ChatManager.instance.ClearAllChatBatches(); } Plugin.Logger.LogDebug((object)"Finished deathlink scheduling. Let's see what happens"); } public static void DeathlinkSelfDestruct(string playerIdWhoWasPosessed) { AccessTools.Field(typeof(PlayerHealth), "health").SetValue(PlayerAvatar.instance.playerHealth, 0); PlayerAvatar.instance.playerHealth.Hurt(1, false, -1); Plugin.Logger.LogDebug((object)"I should be dead from deathlink now"); Plugin.customRPCManager.CallClientDeathLinkFinished(Plugin.customRPCManagerObject, playerIdWhoWasPosessed); } public static void DeathLinkFinished(string playerIdWhoWasPosessed) { playersWithActiveDeathCountdown.Remove(playerIdWhoWasPosessed); if ((bool)AccessTools.Field(typeof(RunManager), "allPlayersDead").GetValue(RunManager.instance)) { isDeadFromDeathLink = true; } } } public delegate void TickEventHandler(RunManager __instance); internal class Events { private static float lastProcessTime; private const float TICK_FREQUENCY = 5f; [HarmonyPatch(typeof(RunManager), "Update")] [HarmonyPostfix] private static void TickUpdate(RunManager __instance) { float unscaledTime = Time.unscaledTime; if (SemiFunc.IsMasterClientOrSingleplayer() && !SemiFunc.MenuLevel() && !((Object)(object)RunManager.instance.levelCurrent == (Object)(object)RunManager.instance.levelLobby) && !(unscaledTime - lastProcessTime < 5f)) { lastProcessTime = unscaledTime; Plugin.connection.TickUpdate(__instance); } } } public class HandleAPTruckScreenMessages { [HarmonyPatch(typeof(TruckScreenText), "MessageSendCustomRPC")] public class OverridePlayerNameCheckPatch { private static string nicknameCacheman = ""; private static string customFormattedNickname = ""; private static void Prefix(TruckScreenText __instance) { if (customFormattedNickname != string.Empty) { FieldInfo fieldInfo = AccessTools.Field(typeof(TruckScreenText), "nicknameTaxman"); nicknameCacheman = (string)fieldInfo.GetValue(__instance); fieldInfo.SetValue(__instance, customFormattedNickname); } } private static void Postfix(TruckScreenText __instance) { if (nicknameCacheman != string.Empty) { AccessTools.Field(typeof(TruckScreenText), "nicknameTaxman").SetValue(__instance, nicknameCacheman); } } public static void SetNickname(string nickname, string color) { customFormattedNickname = "\n\n" + nickname + ":\n"; } public static void SetFormattedNickname(string nicknameFormatted) { customFormattedNickname = "\n\n" + nicknameFormatted + "\n"; } public static void ResetNickname() { customFormattedNickname = string.Empty; } } [HarmonyPatch(typeof(TruckScreenText), "Update")] public class TruckScreenChatPatch { private struct messageData { public string message { get; set; } public string nickname { get; set; } public messageData(string nick, string msg) { message = msg; nickname = nick; } } private const float CHAT_MESSAGE_FREQUENCY = 0.25f; private const float MAX_MESSAGE_QUEUE_SIZE = 200f; private const float MESSAGE_BATCH_SIZE = 5f; private const string nickAP = "ARCHIPELAGO:"; private static float lastProcessTime = 0f; private static ConcurrentQueue messageQueue = new ConcurrentQueue(); private static void Prefix(TruckScreenText __instance) { float unscaledTime = Time.unscaledTime; if (Plugin.connection == null) { return; } int num = (int)AccessTools.Field(typeof(TruckScreenText), "currentLineIndex").GetValue(__instance); int index = (int)AccessTools.Field(typeof(TruckScreenText), "currentPageIndex").GetValue(__instance); if (num < __instance.pages[index].textLines.Count || !(unscaledTime - lastProcessTime >= 0.25f)) { return; } for (int i = 0; (float)i < 5f; i++) { if (!messageQueue.TryDequeue(out var result)) { break; } OverridePlayerNameCheckPatch.SetFormattedNickname(result.nickname); __instance.MessageSendCustom(string.Empty, result.message, 0); OverridePlayerNameCheckPatch.ResetNickname(); } lastProcessTime = unscaledTime; } public static void AddMessage(string preformattedNickname, string message) { string nick = ((!(preformattedNickname == "AP")) ? ("\n\n" + preformattedNickname + "\n") : "ARCHIPELAGO:"); messageData item = new messageData(nick, message); if ((!string.IsNullOrEmpty(Plugin.apSlot) && message.Contains(Plugin.apSlot)) || (float)messageQueue.Count < 200f) { messageQueue.Enqueue(item); } } public static void AddMessage(string nickname, string hexColor, string message) { string nick = ((!(nickname == "AP")) ? ("\n\n" + nickname + ":\n") : "ARCHIPELAGO:"); messageData item = new messageData(nick, message); messageQueue.Enqueue(item); } } } internal class TrapHandler { [CompilerGenerated] private sealed class d__3 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; private int 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__3(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: 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_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__2 = 0; break; case 1: <>1__state = -1; goto IL_016f; case 2: { <>1__state = -1; goto IL_016f; } IL_016f: 5__2++; break; } if (5__2 < 6) { List list = SemiFunc.LevelPointsGetInPlayerRooms(); Vector3 val = ((list.Count <= 0) ? ((Component)GameDirector.instance.PlayerList[Random.Range(0, GameDirector.instance.PlayerList.Count)]).transform.position : ((Component)list[Random.Range(0, list.Count)]).transform.position); SemiFunc.EnemyInvestigate(val, 100f, true); float num = (float)AccessTools.Field(typeof(EnemyDirector), "investigatePointTime").GetValue(EnemyDirector.instance); AccessTools.Field(typeof(EnemyDirector), "investigatePointTimer").SetValue(EnemyDirector.instance, num); AccessTools.Field(typeof(EnemyDirector), "investigatePointTime").SetValue(EnemyDirector.instance, Mathf.Min(num + 2f, 15f)); if (!SemiFunc.IsMultiplayer()) { Plugin.customRPCManager.PingClientsWithNoise("truckReturn", val); } else { Plugin.customRPCManager.CallPingClientsWithNoise(Plugin.customRPCManagerObject, "truckReturn", val); } <>2__current = (object)new WaitForSeconds(20f); <>1__state = 2; return true; } lureTrapActive = false; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static bool lureTrapActive; internal static void UseTrapPatch(RunManager __instance) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_01c2: Unknown result type (might be due to invalid IL or missing references) _ = Time.unscaledTime; if (!SemiFunc.IsMasterClientOrSingleplayer() || SemiFunc.MenuLevel() || (Object)(object)RunManager.instance.levelCurrent == (Object)(object)RunManager.instance.levelLobby) { return; } Level val = (Level)AccessTools.Field(typeof(RunManager), "levelPrevious").GetValue(__instance); foreach (string item in APSave.saveData.trapsUsed.Keys.ToList()) { long key = ItemData.AddBaseId(ItemData.itemNameToID[item]); if (!APSave.saveData.itemsReceived.ContainsKey(key) || APSave.saveData.itemsReceived[key] <= APSave.saveData.trapsUsed[item]) { continue; } Plugin.Logger.LogInfo((object)("Trying to use trap '" + item + "'")); bool flag = false; switch (item) { case "Extra Monster Trap": if (!((Object)(object)__instance.levelCurrent == (Object)(object)__instance.levelArena) && !((Object)(object)val == (Object)(object)__instance.levelArena) && !((Object)(object)__instance.levelCurrent == (Object)(object)__instance.levelShop) && !SemiFunc.IsMainMenu()) { EnemySetup val2 = EnemyDirector.instance.enemiesDifficulty3[Random.Range(0, EnemyDirector.instance.enemiesDifficulty3.Count)]; LevelPoint val3 = LevelGenerator.Instance.LevelPathPoints[Random.Range(0, LevelGenerator.Instance.LevelPathPoints.Count)]; LevelGenerator.Instance.EnemySpawn(val2, ((Component)val3).transform.position); RunManager.instance.enemiesSpawned.Add(val2); flag = true; } break; case "Audit Trap": if (!((Object)(object)__instance.levelCurrent != (Object)(object)__instance.levelShop)) { SemiFunc.StatSetRunCurrency(StatsManager.instance.GetRunStatCurrency() / 2); flag = true; TutorialDirector.instance.ActivateTip("AuditTrap", 0f, false, -1f, true); } break; case "Monster Lure Trap": if (!((Object)(object)__instance.levelCurrent == (Object)(object)__instance.levelArena) && !((Object)(object)val == (Object)(object)__instance.levelArena) && !((Object)(object)__instance.levelCurrent == (Object)(object)__instance.levelShop) && !SemiFunc.IsMainMenu() && !lureTrapActive) { lureTrapActive = true; ((MonoBehaviour)RunManager.instance).StartCoroutine(LureTrapCycle()); flag = true; TutorialDirector.instance.ActivateTip("LureTrap", 0f, false, -1f, true); } break; case "Progressive Moon Phase Trap": flag = true; break; default: Plugin.Logger.LogWarning((object)("Attempted to use trap '" + item + "' which is not known")); break; } if (flag) { APSave.saveData.trapsUsed[item]++; Plugin.Logger.LogInfo((object)("Successfully used trap '" + item + "'")); } } Plugin.connection.session.DataStorage["REPO-" + Plugin.connection.session.Players.GetPlayerName(Plugin.connection.session.ConnectionInfo.Slot) + "-trapsUsed"] = (JToken)(object)JObject.FromObject((object)APSave.saveData.trapsUsed); } [HarmonyPatch(typeof(RunManager), "CalculateMoonLevel")] [HarmonyPrefix] private static bool CalculateMoonLevelPatch(int _levelsCompleted, ref int __result) { if (APSave.saveData == null || !APSave.saveData.trapsUsed.TryGetValue(ItemNames.moon_phase_trap, out var value)) { return false; } __result = Math.Max((_levelsCompleted + 1) / 5, value); return false; } [IteratorStateMachine(typeof(d__3))] private static IEnumerator LureTrapCycle() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__3(0); } } [HarmonyPatch(typeof(TutorialDirector), "Awake")] internal class TutorialPatches { private static readonly TutorialPage lureTrapTutorial = new TutorialPage { pageName = "LureTrap", video = null, text = "A Monster Lure trap is active! Hide or Fight!", focusText = "Lure Trap Active!", dummyText = "Good luck!" }; private static readonly TutorialPage auditTrapTutorial = new TutorialPage { pageName = "AuditTrap", video = null, text = "You've been audited! Goodbye, money!", focusText = "You've been audited!", dummyText = "Goodbye, money!" }; [HarmonyPostfix] public static void CreateAPTutorialPages(TutorialDirector __instance) { if (!__instance.tutorialPages.Contains(lureTrapTutorial)) { __instance.tutorialPages.Add(lureTrapTutorial); } if (!__instance.tutorialPages.Contains(auditTrapTutorial)) { __instance.tutorialPages.Add(auditTrapTutorial); } } } internal class UpgradeSpawningPatch { private const int MAX_UPGRADE_ROWS = 3; private const int MAX_UPGRADES_PER_ROW = 32; [HarmonyPatch(typeof(PunManager), "TruckPopulateItemVolumes")] [HarmonyPrefix] internal static void PrepareNewTruckItemVolumes(PunManager __instance, ItemManager ___itemManager) { //IL_0056: 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) //IL_006c: 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) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: 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_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_011e: 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_012e: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0134: 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_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) List list = ___itemManager.itemVolumes.Where((ItemVolume volume) => (int)volume.itemVolume == 6).ToList(); if (list.Count < 16) { return; } Plugin.Logger.LogDebug((object)"Creating new upgrades spawn positions in the truck"); Vector3 val = Vector3.Cross(((Component)list[7]).transform.position, ((Component)list[8]).transform.position); Vector3 normalized = ((Vector3)(ref val)).normalized; normalized.y = 0f; if (normalized.z > 0f) { normalized.z *= -1f; } if (normalized.x > 0f) { normalized.x *= -1f; } Vector3 val2 = ((Component)list[7]).transform.position - ((Component)list[8]).transform.position; Vector3 val3 = ((Component)list[7]).transform.position + normalized * 26f - val2; List list2 = new List(); for (int i = 0; i < 3; i++) { Vector3 val4 = val3; for (int j = 0; j < 32; j++) { ItemVolume item = Object.Instantiate(list[7], val4 += normalized, ((Component)list[8]).transform.rotation); list2.Add(item); } val3 += val2; } ___itemManager.itemVolumes.AddRange(list2); Plugin.Logger.LogDebug((object)$"Created {list2.Count} new spots for upgrades in truck"); } [HarmonyPatch(typeof(StatsManager), "LoadItemsFromFolder")] [HarmonyPostfix] internal static void AllowMoreOfUpgradeTypeToSpawnPatch(StatsManager __instance) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 foreach (Item value in __instance.itemDictionary.Values) { if ((int)value.itemType == 3 && value.maxAmount < 20) { value.maxAmount = 20; } } } } } internal interface IConcurrentHashSet { bool TryAdd(T item); bool Contains(T item); void UnionWith(T[] otherSet); T[] ToArray(); ReadOnlyCollection AsToReadOnlyCollection(); ReadOnlyCollection AsToReadOnlyCollectionExcept(IConcurrentHashSet otherSet); } namespace Archipelago.MultiClient.Net { [Serializable] public abstract class ArchipelagoPacketBase { [JsonIgnore] internal JObject jobject; [JsonProperty("cmd")] [JsonConverter(typeof(StringEnumConverter))] public abstract ArchipelagoPacketType PacketType { get; } public JObject ToJObject() { return jobject; } } public interface IArchipelagoSession : IArchipelagoSessionActions { IArchipelagoSocketHelper Socket { get; } IReceivedItemsHelper Items { get; } ILocationCheckHelper Locations { get; } IPlayerHelper Players { get; } IDataStorageHelper DataStorage { get; } IConnectionInfoProvider ConnectionInfo { get; } IRoomStateHelper RoomState { get; } IMessageLogHelper MessageLog { get; } Task ConnectAsync(); Task LoginAsync(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true); LoginResult TryConnectAndLogin(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true); } public class ArchipelagoSession : IArchipelagoSession, IArchipelagoSessionActions { private const int ArchipelagoConnectionTimeoutInSeconds = 4; private ConnectionInfoHelper connectionInfo; private TaskCompletionSource loginResultTask = new TaskCompletionSource(); private TaskCompletionSource roomInfoPacketTask = new TaskCompletionSource(); public IArchipelagoSocketHelper Socket { get; } public IReceivedItemsHelper Items { get; } public ILocationCheckHelper Locations { get; } public IPlayerHelper Players { get; } public IDataStorageHelper DataStorage { get; } public IConnectionInfoProvider ConnectionInfo => connectionInfo; public IRoomStateHelper RoomState { get; } public IMessageLogHelper MessageLog { get; } internal ArchipelagoSession(IArchipelagoSocketHelper socket, IReceivedItemsHelper items, ILocationCheckHelper locations, IPlayerHelper players, IRoomStateHelper roomState, ConnectionInfoHelper connectionInfoHelper, IDataStorageHelper dataStorage, IMessageLogHelper messageLog) { Socket = socket; Items = items; Locations = locations; Players = players; RoomState = roomState; connectionInfo = connectionInfoHelper; DataStorage = dataStorage; MessageLog = messageLog; socket.PacketReceived += Socket_PacketReceived; } private void Socket_PacketReceived(ArchipelagoPacketBase packet) { if (!(packet is ConnectedPacket) && !(packet is ConnectionRefusedPacket)) { if (packet is RoomInfoPacket result) { roomInfoPacketTask.TrySetResult(result); } return; } if (packet is ConnectedPacket && RoomState.Version != null && RoomState.Version >= new Version(0, 3, 8)) { LogUsedVersion(); } loginResultTask.TrySetResult(LoginResult.FromPacket(packet)); } private void LogUsedVersion() { try { string fileVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion; Socket.SendPacketAsync(new SetPacket { Key = ".NetUsedVersions", DefaultValue = (JToken)(object)JObject.FromObject((object)new Dictionary()), Operations = new OperationSpecification[1] { Operation.Update(new Dictionary { { ConnectionInfo.Game + ":" + fileVersion + ":NET45", true } }) } }); } catch { } } public Task ConnectAsync() { roomInfoPacketTask = new TaskCompletionSource(); Task.Factory.StartNew(delegate { try { Task task = Socket.ConnectAsync(); task.Wait(TimeSpan.FromSeconds(4.0)); if (!task.IsCompleted) { roomInfoPacketTask.TrySetCanceled(); } } catch (AggregateException) { roomInfoPacketTask.TrySetCanceled(); } }); return roomInfoPacketTask.Task; } public Task LoginAsync(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true) { loginResultTask = new TaskCompletionSource(); if (!roomInfoPacketTask.Task.IsCompleted) { loginResultTask.TrySetResult(new LoginFailure("You are not connected, run ConnectAsync() first")); return loginResultTask.Task; } connectionInfo.SetConnectionParameters(game, tags, itemsHandlingFlags, uuid); try { Socket.SendPacket(BuildConnectPacket(name, password, version, requestSlotData)); } catch (ArchipelagoSocketClosedException) { loginResultTask.TrySetResult(new LoginFailure("You are not connected, run ConnectAsync() first")); return loginResultTask.Task; } SetResultAfterTimeout(loginResultTask, 4, new LoginFailure("Connection timed out.")); return loginResultTask.Task; } private static void SetResultAfterTimeout(TaskCompletionSource task, int timeoutInSeconds, T result) { new CancellationTokenSource(TimeSpan.FromSeconds(timeoutInSeconds)).Token.Register(delegate { task.TrySetResult(result); }); } public LoginResult TryConnectAndLogin(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true) { Task task = ConnectAsync(); try { task.Wait(TimeSpan.FromSeconds(4.0)); } catch (AggregateException ex) { if (ex.GetBaseException() is OperationCanceledException) { return new LoginFailure("Connection timed out."); } return new LoginFailure(ex.GetBaseException().Message); } if (!task.IsCompleted) { return new LoginFailure("Connection timed out."); } return LoginAsync(game, name, itemsHandlingFlags, version, tags, uuid, password, requestSlotData).Result; } private ConnectPacket BuildConnectPacket(string name, string password, Version version, bool requestSlotData) { return new ConnectPacket { Game = ConnectionInfo.Game, Name = name, Password = password, Tags = ConnectionInfo.Tags, Uuid = ConnectionInfo.Uuid, Version = ((version != null) ? new NetworkVersion(version) : new NetworkVersion(0, 6, 0)), ItemsHandling = ConnectionInfo.ItemsHandlingFlags, RequestSlotData = requestSlotData }; } public void Say(string message) { Socket.SendPacket(new SayPacket { Text = message }); } public void SetClientState(ArchipelagoClientState state) { Socket.SendPacket(new StatusUpdatePacket { Status = state }); } public void SetGoalAchieved() { SetClientState(ArchipelagoClientState.ClientGoal); } } public interface IArchipelagoSessionActions { void Say(string message); void SetClientState(ArchipelagoClientState state); void SetGoalAchieved(); } public static class ArchipelagoSessionFactory { public static ArchipelagoSession CreateSession(Uri uri) { ArchipelagoSocketHelper socket = new ArchipelagoSocketHelper(uri); DataPackageCache cache = new DataPackageCache(socket); ConnectionInfoHelper connectionInfoHelper = new ConnectionInfoHelper(socket); PlayerHelper playerHelper = new PlayerHelper(socket, connectionInfoHelper); ItemInfoResolver itemInfoResolver = new ItemInfoResolver(cache, connectionInfoHelper); LocationCheckHelper locationCheckHelper = new LocationCheckHelper(socket, itemInfoResolver, connectionInfoHelper, playerHelper); ReceivedItemsHelper items = new ReceivedItemsHelper(socket, locationCheckHelper, itemInfoResolver, connectionInfoHelper, playerHelper); RoomStateHelper roomState = new RoomStateHelper(socket, locationCheckHelper); DataStorageHelper dataStorage = new DataStorageHelper(socket, connectionInfoHelper); MessageLogHelper messageLog = new MessageLogHelper(socket, itemInfoResolver, playerHelper, connectionInfoHelper); return new ArchipelagoSession(socket, items, locationCheckHelper, playerHelper, roomState, connectionInfoHelper, dataStorage, messageLog); } public static ArchipelagoSession CreateSession(string hostname, int port = 38281) { return CreateSession(ParseUri(hostname, port)); } internal static Uri ParseUri(string hostname, int port) { string text = hostname; if (!text.StartsWith("ws://") && !text.StartsWith("wss://")) { text = "unspecified://" + text; } if (!text.Substring(text.IndexOf("://", StringComparison.Ordinal) + 3).Contains(":")) { text += $":{port}"; } if (text.EndsWith(":")) { text += port; } return new Uri(text); } } public abstract class LoginResult { public abstract bool Successful { get; } public static LoginResult FromPacket(ArchipelagoPacketBase packet) { if (!(packet is ConnectedPacket connectedPacket)) { if (packet is ConnectionRefusedPacket connectionRefusedPacket) { return new LoginFailure(connectionRefusedPacket); } throw new ArgumentOutOfRangeException("packet", "packet is not a connection result packet"); } return new LoginSuccessful(connectedPacket); } } public class LoginSuccessful : LoginResult { public override bool Successful => true; public int Team { get; } public int Slot { get; } public Dictionary SlotData { get; } public LoginSuccessful(ConnectedPacket connectedPacket) { Team = connectedPacket.Team; Slot = connectedPacket.Slot; SlotData = connectedPacket.SlotData; } } public class LoginFailure : LoginResult { public override bool Successful => false; public ConnectionRefusedError[] ErrorCodes { get; } public string[] Errors { get; } public LoginFailure(ConnectionRefusedPacket connectionRefusedPacket) { if (connectionRefusedPacket.Errors != null) { ErrorCodes = connectionRefusedPacket.Errors.ToArray(); Errors = ErrorCodes.Select(GetErrorMessage).ToArray(); } else { ErrorCodes = new ConnectionRefusedError[0]; Errors = new string[0]; } } public LoginFailure(string message) { ErrorCodes = new ConnectionRefusedError[0]; Errors = new string[1] { message }; } private static string GetErrorMessage(ConnectionRefusedError errorCode) { return errorCode switch { ConnectionRefusedError.InvalidSlot => "The slot name did not match any slot on the server.", ConnectionRefusedError.InvalidGame => "The slot is set to a different game on the server.", ConnectionRefusedError.SlotAlreadyTaken => "The slot already has a connection with a different uuid established.", ConnectionRefusedError.IncompatibleVersion => "The client and server version mismatch.", ConnectionRefusedError.InvalidPassword => "The password is invalid.", ConnectionRefusedError.InvalidItemsHandling => "The item handling flags provided are invalid.", _ => $"Unknown error: {errorCode}.", }; } } internal class TwoWayLookup : IEnumerable>, IEnumerable { private readonly Dictionary aToB = new Dictionary(); private readonly Dictionary bToA = new Dictionary(); public TA this[TB b] => bToA[b]; public TB this[TA a] => aToB[a]; public void Add(TA a, TB b) { aToB[a] = b; bToA[b] = a; } public void Add(TB b, TA a) { Add(a, b); } public bool TryGetValue(TA a, out TB b) { return aToB.TryGetValue(a, out b); } public bool TryGetValue(TB b, out TA a) { return bToA.TryGetValue(b, out a); } public IEnumerator> GetEnumerator() { return bToA.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } } namespace Archipelago.MultiClient.Net.Packets { public class BouncedPacket : BouncePacket { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Bounced; } public class BouncePacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Bounce; [JsonProperty("games")] public List Games { get; set; } = new List(); [JsonProperty("slots")] public List Slots { get; set; } = new List(); [JsonProperty("tags")] public List Tags { get; set; } = new List(); [JsonProperty("data")] public Dictionary Data { get; set; } } public class ConnectedPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Connected; [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } [JsonProperty("players")] public NetworkPlayer[] Players { get; set; } [JsonProperty("missing_locations")] public long[] MissingChecks { get; set; } [JsonProperty("checked_locations")] public long[] LocationsChecked { get; set; } [JsonProperty("slot_data")] public Dictionary SlotData { get; set; } [JsonProperty("slot_info")] public Dictionary SlotInfo { get; set; } [JsonProperty("hint_points")] public int? HintPoints { get; set; } } public class ConnectionRefusedPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.ConnectionRefused; [JsonProperty("errors", ItemConverterType = typeof(StringEnumConverter))] public ConnectionRefusedError[] Errors { get; set; } } public class ConnectPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Connect; [JsonProperty("password")] public string Password { get; set; } [JsonProperty("game")] public string Game { get; set; } [JsonProperty("name")] public string Name { get; set; } [JsonProperty("uuid")] public string Uuid { get; set; } [JsonProperty("version")] public NetworkVersion Version { get; set; } [JsonProperty("tags")] public string[] Tags { get; set; } [JsonProperty("items_handling")] public ItemsHandlingFlags ItemsHandling { get; set; } [JsonProperty("slot_data")] public bool RequestSlotData { get; set; } } public class ConnectUpdatePacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.ConnectUpdate; [JsonProperty("tags")] public string[] Tags { get; set; } [JsonProperty("items_handling")] public ItemsHandlingFlags? ItemsHandling { get; set; } } public class DataPackagePacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.DataPackage; [JsonProperty("data")] public Archipelago.MultiClient.Net.Models.DataPackage DataPackage { get; set; } } public class GetDataPackagePacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.GetDataPackage; [JsonProperty("games")] public string[] Games { get; set; } } public class GetPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Get; [JsonProperty("keys")] public string[] Keys { get; set; } } public class InvalidPacketPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.InvalidPacket; [JsonProperty("type")] public InvalidPacketErrorType ErrorType { get; set; } [JsonProperty("text")] public string ErrorText { get; set; } [JsonProperty("original_cmd")] public ArchipelagoPacketType OriginalCmd { get; set; } } public class LocationChecksPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.LocationChecks; [JsonProperty("locations")] public long[] Locations { get; set; } } public class LocationInfoPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.LocationInfo; [JsonProperty("locations")] public NetworkItem[] Locations { get; set; } } public class LocationScoutsPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.LocationScouts; [JsonProperty("locations")] public long[] Locations { get; set; } [JsonProperty("create_as_hint")] public int CreateAsHint { get; set; } } public class PrintJsonPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.PrintJSON; [JsonProperty("data")] public JsonMessagePart[] Data { get; set; } [JsonProperty("type")] [JsonConverter(typeof(StringEnumConverter))] public JsonMessageType? MessageType { get; set; } } public class ItemPrintJsonPacket : PrintJsonPacket { [JsonProperty("receiving")] public int ReceivingPlayer { get; set; } [JsonProperty("item")] public NetworkItem Item { get; set; } } public class ItemCheatPrintJsonPacket : PrintJsonPacket { [JsonProperty("receiving")] public int ReceivingPlayer { get; set; } [JsonProperty("item")] public NetworkItem Item { get; set; } [JsonProperty("team")] public int Team { get; set; } } public class HintPrintJsonPacket : PrintJsonPacket { [JsonProperty("receiving")] public int ReceivingPlayer { get; set; } [JsonProperty("item")] public NetworkItem Item { get; set; } [JsonProperty("found")] public bool? Found { get; set; } } public class JoinPrintJsonPacket : PrintJsonPacket { [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } [JsonProperty("tags")] public string[] Tags { get; set; } } public class LeavePrintJsonPacket : PrintJsonPacket { [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } } public class ChatPrintJsonPacket : PrintJsonPacket { [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } [JsonProperty("message")] public string Message { get; set; } } public class ServerChatPrintJsonPacket : PrintJsonPacket { [JsonProperty("message")] public string Message { get; set; } } public class TutorialPrintJsonPacket : PrintJsonPacket { } public class TagsChangedPrintJsonPacket : PrintJsonPacket { [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } [JsonProperty("tags")] public string[] Tags { get; set; } } public class CommandResultPrintJsonPacket : PrintJsonPacket { } public class AdminCommandResultPrintJsonPacket : PrintJsonPacket { } public class GoalPrintJsonPacket : PrintJsonPacket { [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } } public class ReleasePrintJsonPacket : PrintJsonPacket { [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } } public class CollectPrintJsonPacket : PrintJsonPacket { [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } } public class CountdownPrintJsonPacket : PrintJsonPacket { [JsonProperty("countdown")] public int RemainingSeconds { get; set; } } public class ReceivedItemsPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.ReceivedItems; [JsonProperty("index")] public int Index { get; set; } [JsonProperty("items")] public NetworkItem[] Items { get; set; } } public class RetrievedPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Retrieved; [JsonProperty("keys")] public Dictionary Data { get; set; } } public class RoomInfoPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.RoomInfo; [JsonProperty("version")] public NetworkVersion Version { get; set; } [JsonProperty("generator_version")] public NetworkVersion GeneratorVersion { get; set; } [JsonProperty("tags")] public string[] Tags { get; set; } [JsonProperty("password")] public bool Password { get; set; } [JsonProperty("permissions")] public Dictionary Permissions { get; set; } [JsonProperty("hint_cost")] public int HintCostPercentage { get; set; } [JsonProperty("location_check_points")] public int LocationCheckPoints { get; set; } [JsonProperty("players")] public NetworkPlayer[] Players { get; set; } [JsonProperty("games")] public string[] Games { get; set; } [JsonProperty("datapackage_checksums")] public Dictionary DataPackageChecksums { get; set; } [JsonProperty("seed_name")] public string SeedName { get; set; } [JsonProperty("time")] public double Timestamp { get; set; } } public class RoomUpdatePacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.RoomUpdate; [JsonProperty("tags")] public string[] Tags { get; set; } [JsonProperty("password")] public bool? Password { get; set; } [JsonProperty("permissions")] public Dictionary Permissions { get; set; } = new Dictionary(); [JsonProperty("hint_cost")] public int? HintCostPercentage { get; set; } [JsonProperty("location_check_points")] public int? LocationCheckPoints { get; set; } [JsonProperty("players")] public NetworkPlayer[] Players { get; set; } [JsonProperty("hint_points")] public int? HintPoints { get; set; } [JsonProperty("checked_locations")] public long[] CheckedLocations { get; set; } } public class SayPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Say; [JsonProperty("text")] public string Text { get; set; } } public class SetNotifyPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.SetNotify; [JsonProperty("keys")] public string[] Keys { get; set; } } public class SetPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Set; [JsonProperty("key")] public string Key { get; set; } [JsonProperty("default")] public JToken DefaultValue { get; set; } [JsonProperty("operations")] public OperationSpecification[] Operations { get; set; } [JsonProperty("want_reply")] public bool WantReply { get; set; } [JsonExtensionData] public Dictionary AdditionalArguments { get; set; } [OnDeserialized] internal void OnDeserializedMethod(StreamingContext context) { AdditionalArguments?.Remove("cmd"); } } public class SetReplyPacket : SetPacket { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.SetReply; [JsonProperty("value")] public JToken Value { get; set; } [JsonProperty("original_value")] public JToken OriginalValue { get; set; } } public class StatusUpdatePacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.StatusUpdate; [JsonProperty("status")] public ArchipelagoClientState Status { get; set; } } public class SyncPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Sync; } internal class UnknownPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Unknown; } public class UpdateHintPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.UpdateHint; [JsonProperty("player")] public int Player { get; set; } [JsonProperty("location")] public long Location { get; set; } [JsonProperty("status")] public HintStatus Status { get; set; } } } namespace Archipelago.MultiClient.Net.Models { public struct Color : IEquatable { public static Color Red = new Color(byte.MaxValue, 0, 0); public static Color Green = new Color(0, 128, 0); public static Color Yellow = new Color(byte.MaxValue, byte.MaxValue, 0); public static Color Blue = new Color(0, 0, byte.MaxValue); public static Color Magenta = new Color(byte.MaxValue, 0, byte.MaxValue); public static Color Cyan = new Color(0, byte.MaxValue, byte.MaxValue); public static Color Black = new Color(0, 0, 0); public static Color White = new Color(byte.MaxValue, byte.MaxValue, byte.MaxValue); public static Color SlateBlue = new Color(106, 90, 205); public static Color Salmon = new Color(250, 128, 114); public static Color Plum = new Color(221, 160, 221); public byte R { get; set; } public byte G { get; set; } public byte B { get; set; } public Color(byte r, byte g, byte b) { R = r; G = g; B = b; } public override bool Equals(object obj) { if (obj is Color color && R == color.R && G == color.G) { return B == color.B; } return false; } public bool Equals(Color other) { if (R == other.R && G == other.G) { return B == other.B; } return false; } public override int GetHashCode() { return ((-1520100960 * -1521134295 + R.GetHashCode()) * -1521134295 + G.GetHashCode()) * -1521134295 + B.GetHashCode(); } public static bool operator ==(Color left, Color right) { return left.Equals(right); } public static bool operator !=(Color left, Color right) { return !(left == right); } } public class DataPackage { [JsonProperty("games")] public Dictionary Games { get; set; } = new Dictionary(); } public class DataStorageElement { internal DataStorageElementContext Context; internal List Operations = new List(0); internal DataStorageHelper.DataStorageUpdatedHandler Callbacks; internal Dictionary AdditionalArguments = new Dictionary(0); private JToken cachedValue; public event DataStorageHelper.DataStorageUpdatedHandler OnValueChanged { add { Context.AddHandler(Context.Key, value); } remove { Context.RemoveHandler(Context.Key, value); } } internal DataStorageElement(DataStorageElementContext context) { Context = context; } internal DataStorageElement(OperationType operationType, JToken value) { Operations = new List(1) { new OperationSpecification { OperationType = operationType, Value = value } }; } internal DataStorageElement(DataStorageElement source, OperationType operationType, JToken value) : this(source.Context) { Operations = source.Operations.ToList(); Callbacks = source.Callbacks; AdditionalArguments = source.AdditionalArguments; Operations.Add(new OperationSpecification { OperationType = operationType, Value = value }); } internal DataStorageElement(DataStorageElement source, Callback callback) : this(source.Context) { Operations = source.Operations.ToList(); Callbacks = source.Callbacks; AdditionalArguments = source.AdditionalArguments; Callbacks = (DataStorageHelper.DataStorageUpdatedHandler)Delegate.Combine(Callbacks, callback.Method); } internal DataStorageElement(DataStorageElement source, AdditionalArgument additionalArgument) : this(source.Context) { Operations = source.Operations.ToList(); Callbacks = source.Callbacks; AdditionalArguments = source.AdditionalArguments; AdditionalArguments[additionalArgument.Key] = additionalArgument.Value; } public static DataStorageElement operator ++(DataStorageElement a) { return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(1)); } public static DataStorageElement operator --(DataStorageElement a) { return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(-1)); } public static DataStorageElement operator +(DataStorageElement a, int b) { return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b)); } public static DataStorageElement operator +(DataStorageElement a, long b) { return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b)); } public static DataStorageElement operator +(DataStorageElement a, float b) { return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b)); } public static DataStorageElement operator +(DataStorageElement a, double b) { return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b)); } public static DataStorageElement operator +(DataStorageElement a, decimal b) { return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b)); } public static DataStorageElement operator +(DataStorageElement a, string b) { return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b)); } public static DataStorageElement operator +(DataStorageElement a, JToken b) { return new DataStorageElement(a, OperationType.Add, b); } public static DataStorageElement operator +(DataStorageElement a, IEnumerable b) { return new DataStorageElement(a, OperationType.Add, (JToken)(object)JArray.FromObject((object)b)); } public static DataStorageElement operator +(DataStorageElement a, OperationSpecification s) { return new DataStorageElement(a, s.OperationType, s.Value); } public static DataStorageElement operator +(DataStorageElement a, Callback c) { return new DataStorageElement(a, c); } public static DataStorageElement operator +(DataStorageElement a, AdditionalArgument arg) { return new DataStorageElement(a, arg); } public static DataStorageElement operator *(DataStorageElement a, int b) { return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b)); } public static DataStorageElement operator *(DataStorageElement a, long b) { return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b)); } public static DataStorageElement operator *(DataStorageElement a, float b) { return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b)); } public static DataStorageElement operator *(DataStorageElement a, double b) { return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b)); } public static DataStorageElement operator *(DataStorageElement a, decimal b) { return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b)); } public static DataStorageElement operator %(DataStorageElement a, int b) { return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b)); } public static DataStorageElement operator %(DataStorageElement a, long b) { return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b)); } public static DataStorageElement operator %(DataStorageElement a, float b) { return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b)); } public static DataStorageElement operator %(DataStorageElement a, double b) { return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b)); } public static DataStorageElement operator %(DataStorageElement a, decimal b) { return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b)); } public static DataStorageElement operator ^(DataStorageElement a, int b) { return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b)); } public static DataStorageElement operator ^(DataStorageElement a, long b) { return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b)); } public static DataStorageElement operator ^(DataStorageElement a, float b) { return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b)); } public static DataStorageElement operator ^(DataStorageElement a, double b) { return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b)); } public static DataStorageElement operator ^(DataStorageElement a, decimal b) { return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b)); } public static DataStorageElement operator -(DataStorageElement a, int b) { return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(-b))); } public static DataStorageElement operator -(DataStorageElement a, long b) { return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(-b))); } public static DataStorageElement operator -(DataStorageElement a, float b) { return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(0f - b))); } public static DataStorageElement operator -(DataStorageElement a, double b) { return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(0.0 - b))); } public static DataStorageElement operator -(DataStorageElement a, decimal b) { return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(-b))); } public static DataStorageElement operator /(DataStorageElement a, int b) { return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1m / (decimal)b))); } public static DataStorageElement operator /(DataStorageElement a, long b) { return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1m / (decimal)b))); } public static DataStorageElement operator /(DataStorageElement a, float b) { return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1.0 / (double)b))); } public static DataStorageElement operator /(DataStorageElement a, double b) { return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1.0 / b))); } public static DataStorageElement operator /(DataStorageElement a, decimal b) { return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1m / b))); } public static implicit operator DataStorageElement(bool b) { return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(b)); } public static implicit operator DataStorageElement(int i) { return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(i)); } public static implicit operator DataStorageElement(long l) { return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(l)); } public static implicit operator DataStorageElement(decimal m) { return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(m)); } public static implicit operator DataStorageElement(double d) { return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(d)); } public static implicit operator DataStorageElement(float f) { return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(f)); } public static implicit operator DataStorageElement(string s) { if (s != null) { return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(s)); } return new DataStorageElement(OperationType.Replace, (JToken)(object)JValue.CreateNull()); } public static implicit operator DataStorageElement(JToken o) { return new DataStorageElement(OperationType.Replace, o); } public static implicit operator DataStorageElement(Array a) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)a)); } public static implicit operator DataStorageElement(List l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator bool(DataStorageElement e) { return RetrieveAndReturnBoolValue(e); } public static implicit operator bool?(DataStorageElement e) { return RetrieveAndReturnBoolValue(e); } public static implicit operator int(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator int?(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator long(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator long?(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator float(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator float?(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator double(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator double?(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator decimal(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator decimal?(DataStorageElement e) { return RetrieveAndReturnDecimalValue(e); } public static implicit operator string(DataStorageElement e) { return RetrieveAndReturnStringValue(e); } public static implicit operator bool[](DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator int[](DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator long[](DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator decimal[](DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator double[](DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator float[](DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator string[](DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator object[](DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator List(DataStorageElement e) { return RetrieveAndReturnArrayValue>(e); } public static implicit operator List(DataStorageElement e) { return RetrieveAndReturnArrayValue>(e); } public static implicit operator List(DataStorageElement e) { return RetrieveAndReturnArrayValue>(e); } public static implicit operator List(DataStorageElement e) { return RetrieveAndReturnArrayValue>(e); } public static implicit operator List(DataStorageElement e) { return RetrieveAndReturnArrayValue>(e); } public static implicit operator List(DataStorageElement e) { return RetrieveAndReturnArrayValue>(e); } public static implicit operator List(DataStorageElement e) { return RetrieveAndReturnArrayValue>(e); } public static implicit operator List(DataStorageElement e) { return RetrieveAndReturnArrayValue>(e); } public static implicit operator Array(DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator JArray(DataStorageElement e) { return RetrieveAndReturnArrayValue(e); } public static implicit operator JToken(DataStorageElement e) { return e.Context.GetData(e.Context.Key); } public static DataStorageElement operator +(DataStorageElement a, BigInteger b) { return new DataStorageElement(a, OperationType.Add, JToken.Parse(b.ToString())); } public static DataStorageElement operator *(DataStorageElement a, BigInteger b) { return new DataStorageElement(a, OperationType.Mul, JToken.Parse(b.ToString())); } public static DataStorageElement operator %(DataStorageElement a, BigInteger b) { return new DataStorageElement(a, OperationType.Mod, JToken.Parse(b.ToString())); } public static DataStorageElement operator ^(DataStorageElement a, BigInteger b) { return new DataStorageElement(a, OperationType.Pow, JToken.Parse(b.ToString())); } public static DataStorageElement operator -(DataStorageElement a, BigInteger b) { return new DataStorageElement(a, OperationType.Add, JToken.Parse((-b).ToString())); } public static DataStorageElement operator /(DataStorageElement a, BigInteger b) { throw new InvalidOperationException("DataStorage[Key] / BigInterger is not supported, due to loss of precision when using integer division"); } public static implicit operator DataStorageElement(BigInteger bi) { return new DataStorageElement(OperationType.Replace, JToken.Parse(bi.ToString())); } public static implicit operator BigInteger(DataStorageElement e) { return RetrieveAndReturnBigIntegerValue(e); } public static implicit operator BigInteger?(DataStorageElement e) { return RetrieveAndReturnBigIntegerValue(e); } private static T RetrieveAndReturnBigIntegerValue(DataStorageElement e) { if (e.cachedValue != null) { if (!BigInteger.TryParse(((object)e.cachedValue).ToString(), out var result)) { return default(T); } return (T)Convert.ChangeType(result, IsNullable() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T)); } BigInteger result2; BigInteger? bigInteger = (BigInteger.TryParse(((object)e.Context.GetData(e.Context.Key)).ToString(), out result2) ? new BigInteger?(result2) : null); if (!bigInteger.HasValue && !IsNullable()) { bigInteger = Activator.CreateInstance(); } foreach (OperationSpecification operation in e.Operations) { if (operation.OperationType == OperationType.Floor || operation.OperationType == OperationType.Ceil) { continue; } if (!BigInteger.TryParse(((object)operation.Value).ToString(), NumberStyles.AllowLeadingSign, null, out var result3)) { throw new InvalidOperationException($"DataStorage[Key] cannot be converted to BigInterger as its value its not an integer number, value: {operation.Value}"); } switch (operation.OperationType) { case OperationType.Replace: bigInteger = result3; break; case OperationType.Add: bigInteger += result3; break; case OperationType.Mul: bigInteger *= result3; break; case OperationType.Mod: bigInteger %= result3; break; case OperationType.Pow: bigInteger = BigInteger.Pow(bigInteger.Value, (int)operation.Value); break; case OperationType.Max: { BigInteger value = result3; BigInteger? bigInteger2 = bigInteger; if (value > bigInteger2) { bigInteger = result3; } break; } case OperationType.Min: { BigInteger value = result3; BigInteger? bigInteger2 = bigInteger; if (value < bigInteger2) { bigInteger = result3; } break; } case OperationType.Xor: bigInteger ^= result3; break; case OperationType.Or: bigInteger |= result3; break; case OperationType.And: bigInteger &= result3; break; case OperationType.LeftShift: bigInteger <<= (int)operation.Value; break; case OperationType.RightShift: bigInteger >>= (int)operation.Value; break; } } e.cachedValue = JToken.Parse(bigInteger.ToString()); if (!bigInteger.HasValue) { return default(T); } return (T)Convert.ChangeType(bigInteger.Value, IsNullable() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T)); } public void Initialize(JToken value) { Context.Initialize(Context.Key, value); } public void Initialize(IEnumerable value) { Context.Initialize(Context.Key, (JToken)(object)JArray.FromObject((object)value)); } public Task GetAsync() { return GetAsync().ContinueWith((Task r) => r.Result.ToObject()); } public Task GetAsync() { return Context.GetAsync(Context.Key); } private static T RetrieveAndReturnArrayValue(DataStorageElement e) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Invalid comparison between Unknown and I4 //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Invalid comparison between Unknown and I4 //IL_00d7: Unknown result type (might be due to invalid IL or missing references) if (e.cachedValue != null) { return ((JToken)(JArray)e.cachedValue).ToObject(); } JArray val = (JArray)(((object)e.Context.GetData(e.Context.Key).ToObject()) ?? ((object)new JArray())); foreach (OperationSpecification operation in e.Operations) { switch (operation.OperationType) { case OperationType.Add: if ((int)operation.Value.Type != 2) { throw new InvalidOperationException($"Cannot perform operation {OperationType.Add} on Array value, with a non Array value: {operation.Value}"); } ((JContainer)val).Merge((object)operation.Value); break; case OperationType.Replace: if ((int)operation.Value.Type != 2) { throw new InvalidOperationException($"Cannot replace Array value, with a non Array value: {operation.Value}"); } val = (JArray)(((object)operation.Value.ToObject()) ?? ((object)new JArray())); break; default: throw new InvalidOperationException($"Cannot perform operation {operation.OperationType} on Array value"); } } e.cachedValue = (JToken)(object)val; return ((JToken)val).ToObject(); } private static string RetrieveAndReturnStringValue(DataStorageElement e) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Invalid comparison between Unknown and I4 //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Invalid comparison between Unknown and I4 if (e.cachedValue != null) { return (string)e.cachedValue; } JToken val = e.Context.GetData(e.Context.Key); string text = (((int)val.Type == 10) ? null : ((object)val).ToString()); foreach (OperationSpecification operation in e.Operations) { switch (operation.OperationType) { case OperationType.Add: text += (string)operation.Value; break; case OperationType.Mul: if ((int)operation.Value.Type != 6) { throw new InvalidOperationException($"Cannot perform operation {OperationType.Mul} on string value, with a non interger value: {operation.Value}"); } text = string.Concat(Enumerable.Repeat(text, (int)operation.Value)); break; case OperationType.Replace: text = (string)operation.Value; break; default: throw new InvalidOperationException($"Cannot perform operation {operation.OperationType} on string value"); } } if (text == null) { e.cachedValue = (JToken)(object)JValue.CreateNull(); } else { e.cachedValue = JToken.op_Implicit(text); } return (string)e.cachedValue; } private static T RetrieveAndReturnBoolValue(DataStorageElement e) { if (e.cachedValue != null) { return e.cachedValue.ToObject(); } bool? flag = e.Context.GetData(e.Context.Key).ToObject() ?? ((bool?)Activator.CreateInstance(typeof(T))); foreach (OperationSpecification operation in e.Operations) { if (operation.OperationType == OperationType.Replace) { flag = (bool?)operation.Value; continue; } throw new InvalidOperationException($"Cannot perform operation {operation.OperationType} on boolean value"); } e.cachedValue = JToken.op_Implicit(flag); if (!flag.HasValue) { return default(T); } return (T)Convert.ChangeType(flag.Value, IsNullable() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T)); } private static T RetrieveAndReturnDecimalValue(DataStorageElement e) { if (e.cachedValue != null) { return e.cachedValue.ToObject(); } decimal? num = e.Context.GetData(e.Context.Key).ToObject(); if (!num.HasValue && !IsNullable()) { num = Activator.CreateInstance(); } foreach (OperationSpecification operation in e.Operations) { switch (operation.OperationType) { case OperationType.Replace: num = (decimal)operation.Value; break; case OperationType.Add: num += (decimal?)(decimal)operation.Value; break; case OperationType.Mul: num *= (decimal?)(decimal)operation.Value; break; case OperationType.Mod: num %= (decimal?)(decimal)operation.Value; break; case OperationType.Pow: num = (decimal)Math.Pow((double)num.Value, (double)operation.Value); break; case OperationType.Max: num = Math.Max(num.Value, (decimal)operation.Value); break; case OperationType.Min: num = Math.Min(num.Value, (decimal)operation.Value); break; case OperationType.Xor: num = (long)num.Value ^ (long)operation.Value; break; case OperationType.Or: num = (long)num.Value | (long)operation.Value; break; case OperationType.And: num = (long)num.Value & (long)operation.Value; break; case OperationType.LeftShift: num = (long)num.Value << (int)operation.Value; break; case OperationType.RightShift: num = (long)num.Value >> (int)operation.Value; break; case OperationType.Floor: num = Math.Floor(num.Value); break; case OperationType.Ceil: num = Math.Ceiling(num.Value); break; } } e.cachedValue = JToken.op_Implicit(num); if (!num.HasValue) { return default(T); } return (T)Convert.ChangeType(num.Value, IsNullable() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T)); } private static bool IsNullable() { if (typeof(T).IsGenericType) { return typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition(); } return false; } public T To() { if (Operations.Count != 0) { throw new InvalidOperationException("DataStorageElement.To() cannot be used together with other operations on the DataStorageElement"); } return Context.GetData(Context.Key).ToObject(); } public override string ToString() { return (Context?.ToString() ?? "(null)") + ", (" + ListOperations() + ")"; } private string ListOperations() { if (Operations != null) { return string.Join(", ", Operations.Select((OperationSpecification o) => o.ToString()).ToArray()); } return "none"; } } internal class DataStorageElementContext { internal string Key { get; set; } internal Action AddHandler { get; set; } internal Action RemoveHandler { get; set; } internal Func GetData { get; set; } internal Action Initialize { get; set; } internal Func> GetAsync { get; set; } public override string ToString() { return "Key: " + Key; } } public class GameData { [JsonProperty("location_name_to_id")] public Dictionary LocationLookup { get; set; } = new Dictionary(); [JsonProperty("item_name_to_id")] public Dictionary ItemLookup { get; set; } = new Dictionary(); [Obsolete("use Checksum instead")] [JsonProperty("version")] public int Version { get; set; } [JsonProperty("checksum")] public string Checksum { get; set; } } public class Hint { [JsonProperty("receiving_player")] public int ReceivingPlayer { get; set; } [JsonProperty("finding_player")] public int FindingPlayer { get; set; } [JsonProperty("item")] public long ItemId { get; set; } [JsonProperty("location")] public long LocationId { get; set; } [JsonProperty("item_flags")] public ItemFlags ItemFlags { get; set; } [JsonProperty("found")] public bool Found { get; set; } [JsonProperty("entrance")] public string Entrance { get; set; } [JsonProperty("status")] public HintStatus Status { get; set; } } public class ItemInfo { private readonly IItemInfoResolver itemInfoResolver; public long ItemId { get; } public long LocationId { get; } public PlayerInfo Player { get; } public ItemFlags Flags { get; } public string ItemName => itemInfoResolver.GetItemName(ItemId, ItemGame); public string ItemDisplayName => ItemName ?? $"Item: {ItemId}"; public string LocationName => itemInfoResolver.GetLocationName(LocationId, LocationGame); public string LocationDisplayName => LocationName ?? $"Location: {LocationId}"; public string ItemGame { get; } public string LocationGame { get; } public ItemInfo(NetworkItem item, string receiverGame, string senderGame, IItemInfoResolver itemInfoResolver, PlayerInfo player) { this.itemInfoResolver = itemInfoResolver; ItemGame = receiverGame; LocationGame = senderGame; ItemId = item.Item; LocationId = item.Location; Flags = item.Flags; Player = player; } public SerializableItemInfo ToSerializable() { return new SerializableItemInfo { IsScout = (GetType() == typeof(ScoutedItemInfo)), ItemId = ItemId, LocationId = LocationId, PlayerSlot = Player, Player = Player, Flags = Flags, ItemGame = ItemGame, ItemName = ItemName, LocationGame = LocationGame, LocationName = LocationName }; } } public class ScoutedItemInfo : ItemInfo { public new PlayerInfo Player => base.Player; public bool IsReceiverRelatedToActivePlayer { get; } public ScoutedItemInfo(NetworkItem item, string receiverGame, string senderGame, IItemInfoResolver itemInfoResolver, IPlayerHelper players, PlayerInfo player) : base(item, receiverGame, senderGame, itemInfoResolver, player) { IsReceiverRelatedToActivePlayer = (players.ActivePlayer ?? new PlayerInfo()).IsRelatedTo(player); } } public class JsonMessagePart { [JsonProperty("type")] [JsonConverter(typeof(StringEnumConverter), new object[] { typeof(SnakeCaseNamingStrategy) })] public JsonMessagePartType? Type { get; set; } [JsonProperty("color")] [JsonConverter(typeof(StringEnumConverter), new object[] { typeof(SnakeCaseNamingStrategy) })] public JsonMessagePartColor? Color { get; set; } [JsonProperty("text")] public string Text { get; set; } [JsonProperty("player")] public int? Player { get; set; } [JsonProperty("flags")] public ItemFlags? Flags { get; set; } [JsonProperty("hint_status")] public HintStatus? HintStatus { get; set; } } public struct NetworkItem { [JsonProperty("item")] public long Item { get; set; } [JsonProperty("location")] public long Location { get; set; } [JsonProperty("player")] public int Player { get; set; } [JsonProperty("flags")] public ItemFlags Flags { get; set; } } public struct NetworkPlayer { [JsonProperty("team")] public int Team { get; set; } [JsonProperty("slot")] public int Slot { get; set; } [JsonProperty("alias")] public string Alias { get; set; } [JsonProperty("name")] public string Name { get; set; } } public struct NetworkSlot { [JsonProperty("name")] public string Name { get; set; } [JsonProperty("game")] public string Game { get; set; } [JsonProperty("type")] public SlotType Type { get; set; } [JsonProperty("group_members")] public int[] GroupMembers { get; set; } } public class NetworkVersion { [JsonProperty("major")] public int Major { get; set; } [JsonProperty("minor")] public int Minor { get; set; } [JsonProperty("build")] public int Build { get; set; } [JsonProperty("class")] public string Class => "Version"; public NetworkVersion() { } public NetworkVersion(int major, int minor, int build) { Major = major; Minor = minor; Build = build; } public NetworkVersion(Version version) { Major = version.Major; Minor = version.Minor; Build = version.Build; } public Version ToVersion() { return new Version(Major, Minor, Build); } } public class OperationSpecification { [JsonProperty("operation")] [JsonConverter(typeof(StringEnumConverter), new object[] { typeof(SnakeCaseNamingStrategy) })] public OperationType OperationType; [JsonProperty("value")] public JToken Value { get; set; } public override string ToString() { return $"{OperationType}: {Value}"; } } public static class Operation { public static OperationSpecification Min(int i) { return new OperationSpecification { OperationType = OperationType.Min, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Min(long i) { return new OperationSpecification { OperationType = OperationType.Min, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Min(float i) { return new OperationSpecification { OperationType = OperationType.Min, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Min(double i) { return new OperationSpecification { OperationType = OperationType.Min, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Min(decimal i) { return new OperationSpecification { OperationType = OperationType.Min, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Min(JToken i) { return new OperationSpecification { OperationType = OperationType.Min, Value = i }; } public static OperationSpecification Min(BigInteger i) { return new OperationSpecification { OperationType = OperationType.Min, Value = JToken.Parse(i.ToString()) }; } public static OperationSpecification Max(int i) { return new OperationSpecification { OperationType = OperationType.Max, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Max(long i) { return new OperationSpecification { OperationType = OperationType.Max, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Max(float i) { return new OperationSpecification { OperationType = OperationType.Max, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Max(double i) { return new OperationSpecification { OperationType = OperationType.Max, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Max(decimal i) { return new OperationSpecification { OperationType = OperationType.Max, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Max(JToken i) { return new OperationSpecification { OperationType = OperationType.Max, Value = i }; } public static OperationSpecification Max(BigInteger i) { return new OperationSpecification { OperationType = OperationType.Max, Value = JToken.Parse(i.ToString()) }; } public static OperationSpecification Remove(JToken value) { return new OperationSpecification { OperationType = OperationType.Remove, Value = value }; } public static OperationSpecification Pop(int value) { return new OperationSpecification { OperationType = OperationType.Pop, Value = JToken.op_Implicit(value) }; } public static OperationSpecification Pop(JToken value) { return new OperationSpecification { OperationType = OperationType.Pop, Value = value }; } public static OperationSpecification Update(IDictionary dictionary) { return new OperationSpecification { OperationType = OperationType.Update, Value = (JToken)(object)JObject.FromObject((object)dictionary) }; } public static OperationSpecification Floor() { return new OperationSpecification { OperationType = OperationType.Floor, Value = null }; } public static OperationSpecification Ceiling() { return new OperationSpecification { OperationType = OperationType.Ceil, Value = null }; } } public static class Bitwise { public static OperationSpecification Xor(long i) { return new OperationSpecification { OperationType = OperationType.Xor, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Xor(BigInteger i) { return new OperationSpecification { OperationType = OperationType.Xor, Value = JToken.Parse(i.ToString()) }; } public static OperationSpecification Or(long i) { return new OperationSpecification { OperationType = OperationType.Or, Value = JToken.op_Implicit(i) }; } public static OperationSpecification Or(BigInteger i) { return new OperationSpecification { OperationType = OperationType.Or, Value = JToken.Parse(i.ToString()) }; } public static OperationSpecification And(long i) { return new OperationSpecification { OperationType = OperationType.And, Value = JToken.op_Implicit(i) }; } public static OperationSpecification And(BigInteger i) { return new OperationSpecification { OperationType = OperationType.And, Value = JToken.Parse(i.ToString()) }; } public static OperationSpecification LeftShift(long i) { return new OperationSpecification { OperationType = OperationType.LeftShift, Value = JToken.op_Implicit(i) }; } public static OperationSpecification RightShift(long i) { return new OperationSpecification { OperationType = OperationType.RightShift, Value = JToken.op_Implicit(i) }; } } public class Callback { internal DataStorageHelper.DataStorageUpdatedHandler Method { get; set; } private Callback() { } public static Callback Add(DataStorageHelper.DataStorageUpdatedHandler callback) { return new Callback { Method = callback }; } } public class AdditionalArgument { internal string Key { get; set; } internal JToken Value { get; set; } private AdditionalArgument() { } public static AdditionalArgument Add(string name, JToken value) { return new AdditionalArgument { Key = name, Value = value }; } } public class MinimalSerializableItemInfo { public long ItemId { get; set; } public long LocationId { get; set; } public int PlayerSlot { get; set; } public ItemFlags Flags { get; set; } public string ItemGame { get; set; } public string LocationGame { get; set; } } public class SerializableItemInfo : MinimalSerializableItemInfo { public bool IsScout { get; set; } public PlayerInfo Player { get; set; } public string ItemName { get; set; } public string LocationName { get; set; } [JsonIgnore] public string ItemDisplayName => ItemName ?? $"Item: {base.ItemId}"; [JsonIgnore] public string LocationDisplayName => LocationName ?? $"Location: {base.LocationId}"; public string ToJson(bool full = false) { //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_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown MinimalSerializableItemInfo minimalSerializableItemInfo = this; if (!full) { minimalSerializableItemInfo = new MinimalSerializableItemInfo { ItemId = base.ItemId, LocationId = base.LocationId, PlayerSlot = base.PlayerSlot, Flags = base.Flags }; if (IsScout) { minimalSerializableItemInfo.ItemGame = base.ItemGame; } else { minimalSerializableItemInfo.LocationGame = base.LocationGame; } } JsonSerializerSettings val = new JsonSerializerSettings { NullValueHandling = (NullValueHandling)1, Formatting = (Formatting)0 }; return JsonConvert.SerializeObject((object)minimalSerializableItemInfo, val); } public static SerializableItemInfo FromJson(string json, IArchipelagoSession session = null) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown ItemInfoStreamingContext additional = ((session != null) ? new ItemInfoStreamingContext { Items = session.Items, Locations = session.Locations, PlayerHelper = session.Players, ConnectionInfo = session.ConnectionInfo } : null); JsonSerializerSettings val = new JsonSerializerSettings { Context = new StreamingContext(StreamingContextStates.Other, additional) }; return JsonConvert.DeserializeObject(json, val); } [OnDeserialized] internal void OnDeserializedMethod(StreamingContext streamingContext) { if (base.ItemGame == null && base.LocationGame != null) { IsScout = false; } else if (base.ItemGame != null && base.LocationGame == null) { IsScout = true; } if (streamingContext.Context is ItemInfoStreamingContext itemInfoStreamingContext) { if (IsScout && base.LocationGame == null) { base.LocationGame = itemInfoStreamingContext.ConnectionInfo.Game; } else if (!IsScout && base.ItemGame == null) { base.ItemGame = itemInfoStreamingContext.ConnectionInfo.Game; } if (ItemName == null) { ItemName = itemInfoStreamingContext.Items.GetItemName(base.ItemId, base.ItemGame); } if (LocationName == null) { LocationName = itemInfoStreamingContext.Locations.GetLocationNameFromId(base.LocationId, base.LocationGame); } if (Player == null) { Player = itemInfoStreamingContext.PlayerHelper.GetPlayerInfo(base.PlayerSlot); } } } } internal class ItemInfoStreamingContext { public IReceivedItemsHelper Items { get; set; } public ILocationCheckHelper Locations { get; set; } public IPlayerHelper PlayerHelper { get; set; } public IConnectionInfoProvider ConnectionInfo { get; set; } } } namespace Archipelago.MultiClient.Net.MessageLog.Parts { public class EntranceMessagePart : MessagePart { internal EntranceMessagePart(JsonMessagePart messagePart) : base(MessagePartType.Entrance, messagePart, Archipelago.MultiClient.Net.Colors.PaletteColor.Blue) { base.Text = messagePart.Text; } } public class HintStatusMessagePart : MessagePart { internal HintStatusMessagePart(JsonMessagePart messagePart) : base(MessagePartType.HintStatus, messagePart) { base.Text = messagePart.Text; if (messagePart.HintStatus.HasValue) { base.PaletteColor = ColorUtils.GetColor(messagePart.HintStatus.Value); } } } public class ItemMessagePart : MessagePart { public ItemFlags Flags { get; } public long ItemId { get; } public int Player { get; } internal ItemMessagePart(IPlayerHelper players, IItemInfoResolver items, JsonMessagePart part) : base(MessagePartType.Item, part) { Flags = part.Flags.GetValueOrDefault(); base.PaletteColor = ColorUtils.GetColor(Flags); Player = part.Player.GetValueOrDefault(); string game = (players.GetPlayerInfo(Player) ?? new PlayerInfo()).Game; JsonMessagePartType? type = part.Type; if (type.HasValue) { switch (type.GetValueOrDefault()) { case JsonMessagePartType.ItemId: ItemId = long.Parse(part.Text); base.Text = items.GetItemName(ItemId, game) ?? $"Item: {ItemId}"; break; case JsonMessagePartType.ItemName: ItemId = 0L; base.Text = part.Text; break; } } } } public class LocationMessagePart : MessagePart { public long LocationId { get; } public int Player { get; } internal LocationMessagePart(IPlayerHelper players, IItemInfoResolver itemInfoResolver, JsonMessagePart part) : base(MessagePartType.Location, part, Archipelago.MultiClient.Net.Colors.PaletteColor.Green) { Player = part.Player.GetValueOrDefault(); string game = (players.GetPlayerInfo(Player) ?? new PlayerInfo()).Game; JsonMessagePartType? type = part.Type; if (type.HasValue) { switch (type.GetValueOrDefault()) { case JsonMessagePartType.LocationId: LocationId = long.Parse(part.Text); base.Text = itemInfoResolver.GetLocationName(LocationId, game) ?? $"Location: {LocationId}"; break; case JsonMessagePartType.LocationName: LocationId = itemInfoResolver.GetLocationId(part.Text, game); base.Text = part.Text; break; } } } } public class MessagePart { public string Text { get; internal set; } public MessagePartType Type { get; internal set; } public Color Color => GetColor(BuiltInPalettes.Dark); public PaletteColor? PaletteColor { get; protected set; } public bool IsBackgroundColor { get; internal set; } internal MessagePart(MessagePartType type, JsonMessagePart messagePart, PaletteColor? color = null) { Type = type; Text = messagePart.Text; if (color.HasValue) { PaletteColor = color.Value; } else if (messagePart.Color.HasValue) { PaletteColor = ColorUtils.GetColor(messagePart.Color.Value); IsBackgroundColor = messagePart.Color.Value >= JsonMessagePartColor.BlackBg; } else { PaletteColor = null; } } public T GetColor(Palette palette) { return palette[PaletteColor]; } public override string ToString() { return Text; } } public enum MessagePartType { Text, Player, Item, Location, Entrance, HintStatus } public class PlayerMessagePart : MessagePart { public bool IsActivePlayer { get; } public int SlotId { get; } internal PlayerMessagePart(IPlayerHelper players, IConnectionInfoProvider connectionInfo, JsonMessagePart part) : base(MessagePartType.Player, part) { switch (part.Type) { case JsonMessagePartType.PlayerId: SlotId = int.Parse(part.Text); IsActivePlayer = SlotId == connectionInfo.Slot; base.Text = players.GetPlayerAlias(SlotId) ?? $"Player {SlotId}"; break; case JsonMessagePartType.PlayerName: SlotId = 0; IsActivePlayer = false; base.Text = part.Text; break; } base.PaletteColor = (IsActivePlayer ? Archipelago.MultiClient.Net.Colors.PaletteColor.Magenta : Archipelago.MultiClient.Net.Colors.PaletteColor.Yellow); } } } namespace Archipelago.MultiClient.Net.MessageLog.Messages { public class AdminCommandResultLogMessage : LogMessage { internal AdminCommandResultLogMessage(MessagePart[] parts) : base(parts) { } } public class ChatLogMessage : PlayerSpecificLogMessage { public string Message { get; } internal ChatLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot, string message) : base(parts, players, team, slot) { Message = message; } } public class CollectLogMessage : PlayerSpecificLogMessage { internal CollectLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot) : base(parts, players, team, slot) { } } public class CommandResultLogMessage : LogMessage { internal CommandResultLogMessage(MessagePart[] parts) : base(parts) { } } public class CountdownLogMessage : LogMessage { public int RemainingSeconds { get; } internal CountdownLogMessage(MessagePart[] parts, int remainingSeconds) : base(parts) { RemainingSeconds = remainingSeconds; } } public class GoalLogMessage : PlayerSpecificLogMessage { internal GoalLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot) : base(parts, players, team, slot) { } } public class HintItemSendLogMessage : ItemSendLogMessage { public bool IsFound { get; } internal HintItemSendLogMessage(MessagePart[] parts, IPlayerHelper players, int receiver, int sender, NetworkItem item, bool found, IItemInfoResolver itemInfoResolver) : base(parts, players, receiver, sender, item, itemInfoResolver) { IsFound = found; } } public class ItemCheatLogMessage : ItemSendLogMessage { internal ItemCheatLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot, NetworkItem item, IItemInfoResolver itemInfoResolver) : base(parts, players, slot, 0, item, team, itemInfoResolver) { } } public class ItemSendLogMessage : LogMessage { private PlayerInfo ActivePlayer { get; } public PlayerInfo Receiver { get; } public PlayerInfo Sender { get; } public bool IsReceiverTheActivePlayer => Receiver == ActivePlayer; public bool IsSenderTheActivePlayer => Sender == ActivePlayer; public bool IsRelatedToActivePlayer { get { if (!ActivePlayer.IsRelatedTo(Receiver)) { return ActivePlayer.IsRelatedTo(Sender); } return true; } } public ItemInfo Item { get; } internal ItemSendLogMessage(MessagePart[] parts, IPlayerHelper players, int receiver, int sender, NetworkItem item, IItemInfoResolver itemInfoResolver) : this(parts, players, receiver, sender, item, players.ActivePlayer.Team, itemInfoResolver) { } internal ItemSendLogMessage(MessagePart[] parts, IPlayerHelper players, int receiver, int sender, NetworkItem item, int team, IItemInfoResolver itemInfoResolver) : base(parts) { ActivePlayer = players.ActivePlayer ?? new PlayerInfo(); Receiver = players.GetPlayerInfo(team, receiver) ?? new PlayerInfo(); Sender = players.GetPlayerInfo(team, sender) ?? new PlayerInfo(); PlayerInfo player = players.GetPlayerInfo(team, item.Player) ?? new PlayerInfo(); Item = new ItemInfo(item, Receiver.Game, Sender.Game, itemInfoResolver, player); } } public class JoinLogMessage : PlayerSpecificLogMessage { public string[] Tags { get; } internal JoinLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot, string[] tags) : base(parts, players, team, slot) { Tags = tags; } } public class LeaveLogMessage : PlayerSpecificLogMessage { internal LeaveLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot) : base(parts, players, team, slot) { } } public class LogMessage { public MessagePart[] Parts { get; } internal LogMessage(MessagePart[] parts) { Parts = parts; } public override string ToString() { if (Parts.Length == 1) { return Parts[0].Text; } StringBuilder stringBuilder = new StringBuilder(); MessagePart[] parts = Parts; foreach (MessagePart messagePart in parts) { stringBuilder.Append(messagePart.Text); } return stringBuilder.ToString(); } } public abstract class PlayerSpecificLogMessage : LogMessage { private PlayerInfo ActivePlayer { get; } public PlayerInfo Player { get; } public bool IsActivePlayer => Player == ActivePlayer; public bool IsRelatedToActivePlayer => ActivePlayer.IsRelatedTo(Player); internal PlayerSpecificLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot) : base(parts) { ActivePlayer = players.ActivePlayer ?? new PlayerInfo(); Player = players.GetPlayerInfo(team, slot) ?? new PlayerInfo(); } } public class ReleaseLogMessage : PlayerSpecificLogMessage { internal ReleaseLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot) : base(parts, players, team, slot) { } } public class ServerChatLogMessage : LogMessage { public string Message { get; } internal ServerChatLogMessage(MessagePart[] parts, string message) : base(parts) { Message = message; } } public class TagsChangedLogMessage : PlayerSpecificLogMessage { public string[] Tags { get; } internal TagsChangedLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot, string[] tags) : base(parts, players, team, slot) { Tags = tags; } } public class TutorialLogMessage : LogMessage { internal TutorialLogMessage(MessagePart[] parts) : base(parts) { } } } namespace Archipelago.MultiClient.Net.Helpers { public class ArchipelagoSocketHelper : BaseArchipelagoSocketHelper, IArchipelagoSocketHelper { public Uri Uri { get; } internal ArchipelagoSocketHelper(Uri hostUri) : base(CreateWebSocket(), 1024) { Uri = hostUri; SecurityProtocolType securityProtocolType = SecurityProtocolType.Tls13; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | securityProtocolType; } private static ClientWebSocket CreateWebSocket() { return new ClientWebSocket(); } public async Task ConnectAsync() { await ConnectToProvidedUri(Uri); StartPolling(); } private async Task ConnectToProvidedUri(Uri uri) { if (uri.Scheme != "unspecified") { try { await Socket.ConnectAsync(uri, CancellationToken.None); return; } catch (Exception e) { OnError(e); throw; } } List errors = new List(0); try { await Socket.ConnectAsync(uri.AsWss(), CancellationToken.None); if (Socket.State == WebSocketState.Open) { return; } } catch (Exception item) { errors.Add(item); Socket = CreateWebSocket(); } try { await Socket.ConnectAsync(uri.AsWs(), CancellationToken.None); } catch (Exception item2) { errors.Add(item2); OnError(new AggregateException(errors)); throw; } } } public class BaseArchipelagoSocketHelper where T : WebSocket { private static readonly ArchipelagoPacketConverter Converter = new ArchipelagoPacketConverter(); private readonly BlockingCollection>> sendQueue = new BlockingCollection>>(); internal T Socket; private readonly int bufferSize; public bool Connected { get { if (Socket.State != WebSocketState.Open) { return Socket.State == WebSocketState.CloseReceived; } return true; } } public event ArchipelagoSocketHelperDelagates.PacketReceivedHandler PacketReceived; public event ArchipelagoSocketHelperDelagates.PacketsSentHandler PacketsSent; public event ArchipelagoSocketHelperDelagates.ErrorReceivedHandler ErrorReceived; public event ArchipelagoSocketHelperDelagates.SocketClosedHandler SocketClosed; public event ArchipelagoSocketHelperDelagates.SocketOpenedHandler SocketOpened; internal BaseArchipelagoSocketHelper(T socket, int bufferSize = 1024) { Socket = socket; this.bufferSize = bufferSize; } internal void StartPolling() { if (this.SocketOpened != null) { this.SocketOpened(); } Task.Run((Func)PollingLoop); Task.Run((Func)SendLoop); } private async Task PollingLoop() { byte[] buffer = new byte[bufferSize]; while (Socket.State == WebSocketState.Open) { string message = null; try { message = await ReadMessageAsync(buffer); } catch (Exception e) { OnError(e); } OnMessageReceived(message); } } private async Task SendLoop() { while (Socket.State == WebSocketState.Open) { try { await HandleSendBuffer(); } catch (Exception e) { OnError(e); } await Task.Delay(20); } } private async Task ReadMessageAsync(byte[] buffer) { using MemoryStream readStream = new MemoryStream(buffer.Length); WebSocketReceiveResult result; do { result = await Socket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); if (result.MessageType == WebSocketMessageType.Close) { try { await Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); } catch { } OnSocketClosed(); } else { readStream.Write(buffer, 0, result.Count); } } while (!result.EndOfMessage); return Encoding.UTF8.GetString(readStream.ToArray()); } public async Task DisconnectAsync() { await Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closure requested by client", CancellationToken.None); OnSocketClosed(); } public void SendPacket(ArchipelagoPacketBase packet) { SendMultiplePackets(new List { packet }); } public void SendMultiplePackets(List packets) { SendMultiplePackets(packets.ToArray()); } public void SendMultiplePackets(params ArchipelagoPacketBase[] packets) { SendMultiplePacketsAsync(packets).Wait(); } public Task SendPacketAsync(ArchipelagoPacketBase packet) { return SendMultiplePacketsAsync(new List { packet }); } public Task SendMultiplePacketsAsync(List packets) { return SendMultiplePacketsAsync(packets.ToArray()); } public Task SendMultiplePacketsAsync(params ArchipelagoPacketBase[] packets) { TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); foreach (ArchipelagoPacketBase item in packets) { sendQueue.Add(new Tuple>(item, taskCompletionSource)); } return taskCompletionSource.Task; } private async Task HandleSendBuffer() { List list = new List(); List> tasks = new List>(); Tuple> tuple = sendQueue.Take(); list.Add(tuple.Item1); tasks.Add(tuple.Item2); Tuple> item; while (sendQueue.TryTake(out item)) { list.Add(item.Item1); tasks.Add(item.Item2); } if (!list.Any()) { return; } if (Socket.State != WebSocketState.Open) { throw new ArchipelagoSocketClosedException(); } ArchipelagoPacketBase[] packets = list.ToArray(); string s = JsonConvert.SerializeObject((object)packets); byte[] messageBuffer = Encoding.UTF8.GetBytes(s); int messagesCount = (int)Math.Ceiling((double)messageBuffer.Length / (double)bufferSize); for (int i = 0; i < messagesCount; i++) { int num = bufferSize * i; int num2 = bufferSize; bool endOfMessage = i + 1 == messagesCount; if (num2 * (i + 1) > messageBuffer.Length) { num2 = messageBuffer.Length - num; } await Socket.SendAsync(new ArraySegment(messageBuffer, num, num2), WebSocketMessageType.Text, endOfMessage, CancellationToken.None); } foreach (TaskCompletionSource item2 in tasks) { item2.TrySetResult(result: true); } OnPacketSend(packets); } private void OnPacketSend(ArchipelagoPacketBase[] packets) { try { if (this.PacketsSent != null) { this.PacketsSent(packets); } } catch (Exception e) { OnError(e); } } private void OnSocketClosed() { try { if (this.SocketClosed != null) { this.SocketClosed(""); } } catch (Exception e) { OnError(e); } } private void OnMessageReceived(string message) { try { if (string.IsNullOrEmpty(message) || this.PacketReceived == null) { return; } List list = null; try { list = JsonConvert.DeserializeObject>(message, (JsonConverter[])(object)new JsonConverter[1] { Converter }); } catch (Exception e) { OnError(e); } if (list == null) { return; } foreach (ArchipelagoPacketBase item in list) { this.PacketReceived(item); } } catch (Exception e2) { OnError(e2); } } protected void OnError(Exception e) { try { if (this.ErrorReceived != null) { this.ErrorReceived(e, e.Message); } } catch (Exception ex) { Console.Out.WriteLine("Error occured during reporting of errorOuter Errror: " + e.Message + " " + e.StackTrace + "Inner Errror: " + ex.Message + " " + ex.StackTrace); } } } public interface IConnectionInfoProvider { string Game { get; } int Team { get; } int Slot { get; } string[] Tags { get; } ItemsHandlingFlags ItemsHandlingFlags { get; } string Uuid { get; } void UpdateConnectionOptions(string[] tags); void UpdateConnectionOptions(ItemsHandlingFlags itemsHandlingFlags); void UpdateConnectionOptions(string[] tags, ItemsHandlingFlags itemsHandlingFlags); } public class ConnectionInfoHelper : IConnectionInfoProvider { private readonly IArchipelagoSocketHelper socket; public string Game { get; private set; } public int Team { get; private set; } public int Slot { get; private set; } public string[] Tags { get; internal set; } public ItemsHandlingFlags ItemsHandlingFlags { get; internal set; } public string Uuid { get; private set; } internal ConnectionInfoHelper(IArchipelagoSocketHelper socket) { this.socket = socket; Reset(); socket.PacketReceived += PacketReceived; } private void PacketReceived(ArchipelagoPacketBase packet) { if (!(packet is ConnectedPacket connectedPacket)) { if (packet is ConnectionRefusedPacket) { Reset(); } return; } Team = connectedPacket.Team; Slot = connectedPacket.Slot; if (connectedPacket.SlotInfo != null && connectedPacket.SlotInfo.ContainsKey(Slot)) { Game = connectedPacket.SlotInfo[Slot].Game; } } internal void SetConnectionParameters(string game, string[] tags, ItemsHandlingFlags itemsHandlingFlags, string uuid) { Game = game; Tags = tags ?? new string[0]; ItemsHandlingFlags = itemsHandlingFlags; Uuid = uuid ?? Guid.NewGuid().ToString(); } private void Reset() { Game = null; Team = -1; Slot = -1; Tags = new string[0]; ItemsHandlingFlags = ItemsHandlingFlags.NoItems; Uuid = null; } public void UpdateConnectionOptions(string[] tags) { UpdateConnectionOptions(tags, ItemsHandlingFlags); } public void UpdateConnectionOptions(ItemsHandlingFlags itemsHandlingFlags) { UpdateConnectionOptions(Tags, ItemsHandlingFlags); } public void UpdateConnectionOptions(string[] tags, ItemsHandlingFlags itemsHandlingFlags) { SetConnectionParameters(Game, tags, itemsHandlingFlags, Uuid); socket.SendPacket(new ConnectUpdatePacket { Tags = Tags, ItemsHandling = ItemsHandlingFlags }); } } public interface IDataStorageHelper : IDataStorageWrapper { DataStorageElement this[Scope scope, string key] { get; set; } DataStorageElement this[string key] { get; set; } } public class DataStorageHelper : IDataStorageHelper, IDataStorageWrapper { public delegate void DataStorageUpdatedHandler(JToken originalValue, JToken newValue, Dictionary additionalArguments); private readonly Dictionary onValueChangedEventHandlers = new Dictionary(); private readonly Dictionary operationSpecificCallbacks = new Dictionary(); private readonly Dictionary> asyncRetrievalTasks = new Dictionary>(); private readonly IArchipelagoSocketHelper socket; private readonly IConnectionInfoProvider connectionInfoProvider; public DataStorageElement this[Scope scope, string key] { get { return this[AddScope(scope, key)]; } set { this[AddScope(scope, key)] = value; } } public DataStorageElement this[string key] { get { return new DataStorageElement(GetContextForKey(key)); } set { SetValue(key, value); } } internal DataStorageHelper(IArchipelagoSocketHelper socket, IConnectionInfoProvider connectionInfoProvider) { this.socket = socket; this.connectionInfoProvider = connectionInfoProvider; socket.PacketReceived += OnPacketReceived; } private void OnPacketReceived(ArchipelagoPacketBase packet) { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Invalid comparison between Unknown and I4 if (!(packet is RetrievedPacket retrievedPacket)) { if (packet is SetReplyPacket setReplyPacket) { if (setReplyPacket.AdditionalArguments != null && setReplyPacket.AdditionalArguments.ContainsKey("Reference") && (int)setReplyPacket.AdditionalArguments["Reference"].Type == 15 && operationSpecificCallbacks.TryGetValue((Guid)setReplyPacket.AdditionalArguments["Reference"], out var value)) { value(setReplyPacket.OriginalValue, setReplyPacket.Value, setReplyPacket.AdditionalArguments); operationSpecificCallbacks.Remove((Guid)setReplyPacket.AdditionalArguments["Reference"]); } if (onValueChangedEventHandlers.TryGetValue(setReplyPacket.Key, out var value2)) { value2(setReplyPacket.OriginalValue, setReplyPacket.Value, setReplyPacket.AdditionalArguments); } } return; } foreach (KeyValuePair datum in retrievedPacket.Data) { if (asyncRetrievalTasks.TryGetValue(datum.Key, out var value3)) { value3.TrySetResult(datum.Value); asyncRetrievalTasks.Remove(datum.Key); } } } private Task GetAsync(string key) { if (asyncRetrievalTasks.TryGetValue(key, out var value)) { return value.Task; } TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); asyncRetrievalTasks[key] = taskCompletionSource; socket.SendPacketAsync(new GetPacket { Keys = new string[1] { key } }); return taskCompletionSource.Task; } private void Initialize(string key, JToken value) { socket.SendPacketAsync(new SetPacket { Key = key, DefaultValue = value, Operations = new OperationSpecification[1] { new OperationSpecification { OperationType = OperationType.Default } } }); } private JToken GetValue(string key) { Task async = GetAsync(key); if (!async.Wait(TimeSpan.FromSeconds(2.0))) { throw new TimeoutException("Timed out retrieving data for key `" + key + "`. This may be due to an attempt to retrieve a value from the DataStorageHelper in a synchronous fashion from within a PacketReceived handler. When using the DataStorageHelper from within code which runs on the websocket thread then use the asynchronous getters. Ex: `DataStorageHelper[\"" + key + "\"].GetAsync().ContinueWith(x => {});`Be aware that DataStorageHelper calls tend to cause packet responses, so making a call from within a PacketReceived handler may cause an infinite loop."); } return async.Result; } private void SetValue(string key, DataStorageElement e) { if (key.StartsWith("_read_")) { throw new InvalidOperationException("DataStorage write operation on readonly key '" + key + "' is not allowed"); } if (e == null) { e = new DataStorageElement(OperationType.Replace, (JToken)(object)JValue.CreateNull()); } if (e.Context == null) { e.Context = GetContextForKey(key); } else if (e.Context.Key != key) { e.Operations.Insert(0, new OperationSpecification { OperationType = OperationType.Replace, Value = GetValue(e.Context.Key) }); } Dictionary dictionary = e.AdditionalArguments ?? new Dictionary(0); if (e.Callbacks != null) { Guid guid = Guid.NewGuid(); operationSpecificCallbacks[guid] = e.Callbacks; dictionary["Reference"] = JToken.FromObject((object)guid); socket.SendPacketAsync(new SetPacket { Key = key, Operations = e.Operations.ToArray(), WantReply = true, AdditionalArguments = dictionary }); } else { socket.SendPacketAsync(new SetPacket { Key = key, Operations = e.Operations.ToArray(), AdditionalArguments = dictionary }); } } private DataStorageElementContext GetContextForKey(string key) { return new DataStorageElementContext { Key = key, GetData = GetValue, GetAsync = GetAsync, Initialize = Initialize, AddHandler = AddHandler, RemoveHandler = RemoveHandler }; } private void AddHandler(string key, DataStorageUpdatedHandler handler) { if (onValueChangedEventHandlers.ContainsKey(key)) { Dictionary dictionary = onValueChangedEventHandlers; dictionary[key] = (DataStorageUpdatedHandler)Delegate.Combine(dictionary[key], handler); } else { onValueChangedEventHandlers[key] = handler; } socket.SendPacketAsync(new SetNotifyPacket { Keys = new string[1] { key } }); } private void RemoveHandler(string key, DataStorageUpdatedHandler handler) { if (onValueChangedEventHandlers.ContainsKey(key)) { Dictionary dictionary = onValueChangedEventHandlers; dictionary[key] = (DataStorageUpdatedHandler)Delegate.Remove(dictionary[key], handler); if (onValueChangedEventHandlers[key] == null) { onValueChangedEventHandlers.Remove(key); } } } private string AddScope(Scope scope, string key) { return scope switch { Scope.Global => key, Scope.Game => $"{scope}:{connectionInfoProvider.Game}:{key}", Scope.Team => $"{scope}:{connectionInfoProvider.Team}:{key}", Scope.Slot => $"{scope}:{connectionInfoProvider.Slot}:{key}", Scope.ReadOnly => "_read_" + key, _ => throw new ArgumentOutOfRangeException("scope", scope, "Invalid scope for key " + key), }; } private DataStorageElement GetHintsElement(int? slot = null, int? team = null) { return this[Scope.ReadOnly, $"hints_{team ?? connectionInfoProvider.Team}_{slot ?? connectionInfoProvider.Slot}"]; } private DataStorageElement GetSlotDataElement(int? slot = null) { return this[Scope.ReadOnly, $"slot_data_{slot ?? connectionInfoProvider.Slot}"]; } private DataStorageElement GetItemNameGroupsElement(string game = null) { return this[Scope.ReadOnly, "item_name_groups_" + (game ?? connectionInfoProvider.Game)]; } private DataStorageElement GetLocationNameGroupsElement(string game = null) { return this[Scope.ReadOnly, "location_name_groups_" + (game ?? connectionInfoProvider.Game)]; } private DataStorageElement GetClientStatusElement(int? slot = null, int? team = null) { return this[Scope.ReadOnly, $"client_status_{team ?? connectionInfoProvider.Team}_{slot ?? connectionInfoProvider.Slot}"]; } private DataStorageElement GetRaceModeElement() { return this[Scope.ReadOnly, "race_mode"]; } public Hint[] GetHints(int? slot = null, int? team = null) { return GetHintsElement(slot, team).To(); } public Task GetHintsAsync(int? slot = null, int? team = null) { return GetHintsElement(slot, team).GetAsync(); } public void TrackHints(Action onHintsUpdated, bool retrieveCurrentlyUnlockedHints = true, int? slot = null, int? team = null) { GetHintsElement(slot, team).OnValueChanged += delegate(JToken _, JToken newValue, Dictionary x) { onHintsUpdated(newValue.ToObject()); }; if (retrieveCurrentlyUnlockedHints) { GetHintsAsync(slot, team).ContinueWith(delegate(Task t) { onHintsUpdated(t.Result); }); } } public Dictionary GetSlotData(int? slot = null) { return GetSlotData>(slot); } public T GetSlotData(int? slot = null) where T : class { return GetSlotDataElement(slot).To(); } public Task> GetSlotDataAsync(int? slot = null) { return GetSlotDataAsync>(slot); } public Task GetSlotDataAsync(int? slot = null) where T : class { return GetSlotDataElement(slot).GetAsync(); } public Dictionary GetItemNameGroups(string game = null) { return GetItemNameGroupsElement(game).To>(); } public Task> GetItemNameGroupsAsync(string game = null) { return GetItemNameGroupsElement(game).GetAsync>(); } public Dictionary GetLocationNameGroups(string game = null) { return GetLocationNameGroupsElement(game).To>(); } public Task> GetLocationNameGroupsAsync(string game = null) { return GetLocationNameGroupsElement(game).GetAsync>(); } public ArchipelagoClientState GetClientStatus(int? slot = null, int? team = null) { return GetClientStatusElement(slot, team).To().GetValueOrDefault(); } public Task GetClientStatusAsync(int? slot = null, int? team = null) { return GetClientStatusElement(slot, team).GetAsync().ContinueWith((Task r) => r.Result.GetValueOrDefault()); } public void TrackClientStatus(Action onStatusUpdated, bool retrieveCurrentClientStatus = true, int? slot = null, int? team = null) { GetClientStatusElement(slot, team).OnValueChanged += delegate(JToken _, JToken newValue, Dictionary x) { onStatusUpdated(newValue.ToObject()); }; if (retrieveCurrentClientStatus) { GetClientStatusAsync(slot, team).ContinueWith(delegate(Task t) { onStatusUpdated(t.Result); }); } } public bool GetRaceMode() { return GetRaceModeElement().To().GetValueOrDefault() > 0; } public Task GetRaceModeAsync() { return GetRaceModeElement().GetAsync().ContinueWith((Task t) => t.Result.GetValueOrDefault() > 0); } } public interface IDataStorageWrapper { Hint[] GetHints(int? slot = null, int? team = null); Task GetHintsAsync(int? slot = null, int? team = null); void TrackHints(Action onHintsUpdated, bool retrieveCurrentlyUnlockedHints = true, int? slot = null, int? team = null); Dictionary GetSlotData(int? slot = null); T GetSlotData(int? slot = null) where T : class; Task> GetSlotDataAsync(int? slot = null); Task GetSlotDataAsync(int? slot = null) where T : class; Dictionary GetItemNameGroups(string game = null); Task> GetItemNameGroupsAsync(string game = null); Dictionary GetLocationNameGroups(string game = null); Task> GetLocationNameGroupsAsync(string game = null); ArchipelagoClientState GetClientStatus(int? slot = null, int? team = null); Task GetClientStatusAsync(int? slot = null, int? team = null); void TrackClientStatus(Action onStatusUpdated, bool retrieveCurrentClientStatus = true, int? slot = null, int? team = null); bool GetRaceMode(); Task GetRaceModeAsync(); } public class ArchipelagoSocketHelperDelagates { public delegate void PacketReceivedHandler(ArchipelagoPacketBase packet); public delegate void PacketsSentHandler(ArchipelagoPacketBase[] packets); public delegate void ErrorReceivedHandler(Exception e, string message); public delegate void SocketClosedHandler(string reason); public delegate void SocketOpenedHandler(); } public interface IArchipelagoSocketHelper { Uri Uri { get; } bool Connected { get; } event ArchipelagoSocketHelperDelagates.PacketReceivedHandler PacketReceived; event ArchipelagoSocketHelperDelagates.PacketsSentHandler PacketsSent; event ArchipelagoSocketHelperDelagates.ErrorReceivedHandler ErrorReceived; event ArchipelagoSocketHelperDelagates.SocketClosedHandler SocketClosed; event ArchipelagoSocketHelperDelagates.SocketOpenedHandler SocketOpened; void SendPacket(ArchipelagoPacketBase packet); void SendMultiplePackets(List packets); void SendMultiplePackets(params ArchipelagoPacketBase[] packets); Task ConnectAsync(); Task DisconnectAsync(); Task SendPacketAsync(ArchipelagoPacketBase packet); Task SendMultiplePacketsAsync(List packets); Task SendMultiplePacketsAsync(params ArchipelagoPacketBase[] packets); } public interface ILocationCheckHelper { ReadOnlyCollection AllLocations { get; } ReadOnlyCollection AllLocationsChecked { get; } ReadOnlyCollection AllMissingLocations { get; } event LocationCheckHelper.CheckedLocationsUpdatedHandler CheckedLocationsUpdated; void CompleteLocationChecks(params long[] ids); Task CompleteLocationChecksAsync(params long[] ids); Task> ScoutLocationsAsync(HintCreationPolicy hintCreationPolicy, params long[] ids); Task> ScoutLocationsAsync(bool createAsHint, params long[] ids); Task> ScoutLocationsAsync(params long[] ids); long GetLocationIdFromName(string game, string locationName); string GetLocationNameFromId(long locationId, string game = null); } public class LocationCheckHelper : ILocationCheckHelper { public delegate void CheckedLocationsUpdatedHandler(ReadOnlyCollection newCheckedLocations); private readonly IConcurrentHashSet allLocations = new ConcurrentHashSet(); private readonly IConcurrentHashSet locationsChecked = new ConcurrentHashSet(); private readonly IConcurrentHashSet serverConfirmedChecks = new ConcurrentHashSet(); private ReadOnlyCollection missingLocations = new ReadOnlyCollection(new long[0]); private readonly IArchipelagoSocketHelper socket; private readonly IItemInfoResolver itemInfoResolver; private readonly IConnectionInfoProvider connectionInfoProvider; private readonly IPlayerHelper players; private bool awaitingLocationInfoPacket; private TaskCompletionSource> locationInfoPacketCallbackTask; public ReadOnlyCollection AllLocations => allLocations.AsToReadOnlyCollection(); public ReadOnlyCollection AllLocationsChecked => locationsChecked.AsToReadOnlyCollection(); public ReadOnlyCollection AllMissingLocations => missingLocations; public event CheckedLocationsUpdatedHandler CheckedLocationsUpdated; internal LocationCheckHelper(IArchipelagoSocketHelper socket, IItemInfoResolver itemInfoResolver, IConnectionInfoProvider connectionInfoProvider, IPlayerHelper players) { this.socket = socket; this.itemInfoResolver = itemInfoResolver; this.connectionInfoProvider = connectionInfoProvider; this.players = players; socket.PacketReceived += Socket_PacketReceived; } private void Socket_PacketReceived(ArchipelagoPacketBase packet) { if (!(packet is ConnectedPacket connectedPacket)) { if (!(packet is RoomUpdatePacket roomUpdatePacket)) { if (!(packet is LocationInfoPacket locationInfoPacket)) { if (packet is InvalidPacketPacket invalidPacketPacket && awaitingLocationInfoPacket && invalidPacketPacket.OriginalCmd == ArchipelagoPacketType.LocationScouts) { locationInfoPacketCallbackTask.TrySetException(new ArchipelagoServerRejectedPacketException(invalidPacketPacket.OriginalCmd, invalidPacketPacket.ErrorType, "location scout rejected by the server: " + invalidPacketPacket.ErrorText)); awaitingLocationInfoPacket = false; locationInfoPacketCallbackTask = null; } } else { if (!awaitingLocationInfoPacket) { return; } if (locationInfoPacketCallbackTask != null) { Dictionary result = locationInfoPacket.Locations.ToDictionary((NetworkItem item) => item.Location, delegate(NetworkItem item) { PlayerInfo playerInfo = players.GetPlayerInfo(item.Player) ?? new PlayerInfo(); return new ScoutedItemInfo(item, playerInfo.Game, connectionInfoProvider.Game, itemInfoResolver, players, playerInfo); }); locationInfoPacketCallbackTask.TrySetResult(result); } awaitingLocationInfoPacket = false; locationInfoPacketCallbackTask = null; } } else { CheckLocations(roomUpdatePacket.CheckedLocations); if (roomUpdatePacket.CheckedLocations != null) { serverConfirmedChecks.UnionWith(roomUpdatePacket.CheckedLocations); } } } else { allLocations.UnionWith(connectedPacket.LocationsChecked); allLocations.UnionWith(connectedPacket.MissingChecks); serverConfirmedChecks.UnionWith(connectedPacket.LocationsChecked); missingLocations = new ReadOnlyCollection(connectedPacket.MissingChecks); CheckLocations(connectedPacket.LocationsChecked); } } public void CompleteLocationChecks(params long[] ids) { CheckLocations(ids); LocationChecksPacket locationChecksPacket = GetLocationChecksPacket(); if (locationChecksPacket.Locations.Any()) { socket.SendPacket(locationChecksPacket); } } public Task CompleteLocationChecksAsync(params long[] ids) { return Task.Factory.StartNew((Func)async delegate { CheckLocations(ids); LocationChecksPacket locationChecksPacket = GetLocationChecksPacket(); if (locationChecksPacket.Locations.Any()) { await socket.SendPacketAsync(locationChecksPacket); } }); } private LocationChecksPacket GetLocationChecksPacket() { return new LocationChecksPacket { Locations = locationsChecked.AsToReadOnlyCollectionExcept(serverConfirmedChecks).ToArray() }; } public Task> ScoutLocationsAsync(HintCreationPolicy hintCreationPolicy, params long[] ids) { long[] array = ids.Where((long i) => allLocations.Contains(i)).ToArray(); if (!array.Any()) { return Task.FromResult(new Dictionary()); } locationInfoPacketCallbackTask = new TaskCompletionSource>(); awaitingLocationInfoPacket = true; socket.SendPacketAsync(new LocationScoutsPacket { Locations = array, CreateAsHint = (int)hintCreationPolicy }); return locationInfoPacketCallbackTask.Task; } public Task> ScoutLocationsAsync(bool createAsHint, params long[] ids) { return ScoutLocationsAsync(createAsHint ? HintCreationPolicy.CreateAndAnnounce : HintCreationPolicy.None, ids); } public Task> ScoutLocationsAsync(params long[] ids) { return ScoutLocationsAsync(createAsHint: false, ids); } public long GetLocationIdFromName(string game, string locationName) { return itemInfoResolver.GetLocationId(locationName, game); } public string GetLocationNameFromId(long locationId, string game = null) { return itemInfoResolver.GetLocationName(locationId, game); } private void CheckLocations(ICollection locationIds) { if (locationIds == null || !locationIds.Any()) { return; } List list = new List(); foreach (long locationId in locationIds) { if (allLocations.Contains(locationId) && locationsChecked.TryAdd(locationId)) { list.Add(locationId); } } missingLocations = allLocations.AsToReadOnlyCollectionExcept(locationsChecked); if (list.Any()) { this.CheckedLocationsUpdated?.Invoke(new ReadOnlyCollection(list)); } } } public interface IMessageLogHelper { event MessageLogHelper.MessageReceivedHandler OnMessageReceived; } public class MessageLogHelper : IMessageLogHelper { public delegate void MessageReceivedHandler(LogMessage message); private readonly IItemInfoResolver itemInfoResolver; private readonly IPlayerHelper players; private readonly IConnectionInfoProvider connectionInfo; public event MessageReceivedHandler OnMessageReceived; internal MessageLogHelper(IArchipelagoSocketHelper socket, IItemInfoResolver itemInfoResolver, IPlayerHelper players, IConnectionInfoProvider connectionInfo) { this.itemInfoResolver = itemInfoResolver; this.players = players; this.connectionInfo = connectionInfo; socket.PacketReceived += Socket_PacketReceived; } private void Socket_PacketReceived(ArchipelagoPacketBase packet) { if (this.OnMessageReceived != null && packet is PrintJsonPacket printJsonPacket) { TriggerOnMessageReceived(printJsonPacket); } } private void TriggerOnMessageReceived(PrintJsonPacket printJsonPacket) { foreach (PrintJsonPacket item in SplitPacketsPerLine(printJsonPacket)) { MessagePart[] parsedData = GetParsedData(item); LogMessage message = ((item is ItemPrintJsonPacket itemPrintJsonPacket) ? new ItemSendLogMessage(parsedData, players, itemPrintJsonPacket.ReceivingPlayer, itemPrintJsonPacket.Item.Player, itemPrintJsonPacket.Item, itemInfoResolver) : ((item is ItemCheatPrintJsonPacket itemCheatPrintJsonPacket) ? new ItemCheatLogMessage(parsedData, players, itemCheatPrintJsonPacket.Team, itemCheatPrintJsonPacket.ReceivingPlayer, itemCheatPrintJsonPacket.Item, itemInfoResolver) : ((item is HintPrintJsonPacket hintPrintJsonPacket) ? new HintItemSendLogMessage(parsedData, players, hintPrintJsonPacket.ReceivingPlayer, hintPrintJsonPacket.Item.Player, hintPrintJsonPacket.Item, hintPrintJsonPacket.Found.HasValue && hintPrintJsonPacket.Found.Value, itemInfoResolver) : ((item is JoinPrintJsonPacket joinPrintJsonPacket) ? new JoinLogMessage(parsedData, players, joinPrintJsonPacket.Team, joinPrintJsonPacket.Slot, joinPrintJsonPacket.Tags) : ((item is LeavePrintJsonPacket leavePrintJsonPacket) ? new LeaveLogMessage(parsedData, players, leavePrintJsonPacket.Team, leavePrintJsonPacket.Slot) : ((item is ChatPrintJsonPacket chatPrintJsonPacket) ? new ChatLogMessage(parsedData, players, chatPrintJsonPacket.Team, chatPrintJsonPacket.Slot, chatPrintJsonPacket.Message) : ((item is ServerChatPrintJsonPacket serverChatPrintJsonPacket) ? new ServerChatLogMessage(parsedData, serverChatPrintJsonPacket.Message) : ((item is TutorialPrintJsonPacket) ? new TutorialLogMessage(parsedData) : ((item is TagsChangedPrintJsonPacket tagsChangedPrintJsonPacket) ? new TagsChangedLogMessage(parsedData, players, tagsChangedPrintJsonPacket.Team, tagsChangedPrintJsonPacket.Slot, tagsChangedPrintJsonPacket.Tags) : ((item is CommandResultPrintJsonPacket) ? new CommandResultLogMessage(parsedData) : ((item is AdminCommandResultPrintJsonPacket) ? new AdminCommandResultLogMessage(parsedData) : ((item is GoalPrintJsonPacket goalPrintJsonPacket) ? new GoalLogMessage(parsedData, players, goalPrintJsonPacket.Team, goalPrintJsonPacket.Slot) : ((item is ReleasePrintJsonPacket releasePrintJsonPacket) ? new ReleaseLogMessage(parsedData, players, releasePrintJsonPacket.Team, releasePrintJsonPacket.Slot) : ((item is CollectPrintJsonPacket collectPrintJsonPacket) ? new CollectLogMessage(parsedData, players, collectPrintJsonPacket.Team, collectPrintJsonPacket.Slot) : ((!(item is CountdownPrintJsonPacket countdownPrintJsonPacket)) ? new LogMessage(parsedData) : new CountdownLogMessage(parsedData, countdownPrintJsonPacket.RemainingSeconds)))))))))))))))); this.OnMessageReceived?.Invoke(message); } } private static IEnumerable SplitPacketsPerLine(PrintJsonPacket printJsonPacket) { List list = new List(); List list2 = new List(); JsonMessagePart[] data = printJsonPacket.Data; foreach (JsonMessagePart jsonMessagePart in data) { string[] array = jsonMessagePart.Text.Split(new char[1] { '\n' }); for (int j = 0; j < array.Length; j++) { string text = array[j]; list2.Add(new JsonMessagePart { Text = text, Type = jsonMessagePart.Type, Color = jsonMessagePart.Color, Flags = jsonMessagePart.Flags, Player = jsonMessagePart.Player, HintStatus = jsonMessagePart.HintStatus }); if (j < array.Length - 1) { PrintJsonPacket printJsonPacket2 = CloneWithoutData(printJsonPacket); printJsonPacket2.Data = list2.ToArray(); list.Add(printJsonPacket2); list2 = new List(); } } } PrintJsonPacket printJsonPacket3 = CloneWithoutData(printJsonPacket); printJsonPacket3.Data = list2.ToArray(); list.Add(printJsonPacket3); return list; } private static PrintJsonPacket CloneWithoutData(PrintJsonPacket source) { if (!(source is ItemPrintJsonPacket itemPrintJsonPacket)) { if (!(source is ItemCheatPrintJsonPacket itemCheatPrintJsonPacket)) { if (!(source is HintPrintJsonPacket hintPrintJsonPacket)) { if (!(source is JoinPrintJsonPacket joinPrintJsonPacket)) { if (!(source is LeavePrintJsonPacket leavePrintJsonPacket)) { if (!(source is ChatPrintJsonPacket chatPrintJsonPacket)) { if (!(source is ServerChatPrintJsonPacket serverChatPrintJsonPacket)) { if (!(source is TutorialPrintJsonPacket tutorialPrintJsonPacket)) { if (!(source is TagsChangedPrintJsonPacket tagsChangedPrintJsonPacket)) { if (!(source is CommandResultPrintJsonPacket commandResultPrintJsonPacket)) { if (!(source is AdminCommandResultPrintJsonPacket adminCommandResultPrintJsonPacket)) { if (!(source is GoalPrintJsonPacket goalPrintJsonPacket)) { if (!(source is ReleasePrintJsonPacket releasePrintJsonPacket)) { if (!(source is CollectPrintJsonPacket collectPrintJsonPacket)) { if (source is CountdownPrintJsonPacket countdownPrintJsonPacket) { return new CountdownPrintJsonPacket { RemainingSeconds = countdownPrintJsonPacket.RemainingSeconds }; } return new PrintJsonPacket { MessageType = source.MessageType }; } return new CollectPrintJsonPacket { MessageType = collectPrintJsonPacket.MessageType, Team = collectPrintJsonPacket.Team, Slot = collectPrintJsonPacket.Slot }; } return new ReleasePrintJsonPacket { MessageType = releasePrintJsonPacket.MessageType, Team = releasePrintJsonPacket.Team, Slot = releasePrintJsonPacket.Slot }; } return new GoalPrintJsonPacket { MessageType = goalPrintJsonPacket.MessageType, Team = goalPrintJsonPacket.Team, Slot = goalPrintJsonPacket.Slot }; } return new AdminCommandResultPrintJsonPacket { MessageType = adminCommandResultPrintJsonPacket.MessageType }; } return new CommandResultPrintJsonPacket { MessageType = commandResultPrintJsonPacket.MessageType }; } return new TagsChangedPrintJsonPacket { MessageType = tagsChangedPrintJsonPacket.MessageType, Team = tagsChangedPrintJsonPacket.Team, Slot = tagsChangedPrintJsonPacket.Slot, Tags = tagsChangedPrintJsonPacket.Tags }; } return new TutorialPrintJsonPacket { MessageType = tutorialPrintJsonPacket.MessageType }; } return new ServerChatPrintJsonPacket { MessageType = serverChatPrintJsonPacket.MessageType, Message = serverChatPrintJsonPacket.Message }; } return new ChatPrintJsonPacket { MessageType = chatPrintJsonPacket.MessageType, Team = chatPrintJsonPacket.Team, Slot = chatPrintJsonPacket.Slot, Message = chatPrintJsonPacket.Message }; } return new LeavePrintJsonPacket { MessageType = leavePrintJsonPacket.MessageType, Team = leavePrintJsonPacket.Team, Slot = leavePrintJsonPacket.Slot }; } return new JoinPrintJsonPacket { MessageType = joinPrintJsonPacket.MessageType, Team = joinPrintJsonPacket.Team, Slot = joinPrintJsonPacket.Slot, Tags = joinPrintJsonPacket.Tags }; } return new HintPrintJsonPacket { MessageType = hintPrintJsonPacket.MessageType, ReceivingPlayer = hintPrintJsonPacket.ReceivingPlayer, Item = hintPrintJsonPacket.Item, Found = hintPrintJsonPacket.Found }; } return new ItemCheatPrintJsonPacket { MessageType = itemCheatPrintJsonPacket.MessageType, ReceivingPlayer = itemCheatPrintJsonPacket.ReceivingPlayer, Item = itemCheatPrintJsonPacket.Item, Team = itemCheatPrintJsonPacket.Team }; } return new ItemPrintJsonPacket { MessageType = itemPrintJsonPacket.MessageType, ReceivingPlayer = itemPrintJsonPacket.ReceivingPlayer, Item = itemPrintJsonPacket.Item }; } internal MessagePart[] GetParsedData(PrintJsonPacket packet) { return packet.Data.Select(GetMessagePart).ToArray(); } private MessagePart GetMessagePart(JsonMessagePart part) { switch (part.Type) { case JsonMessagePartType.ItemId: case JsonMessagePartType.ItemName: return new ItemMessagePart(players, itemInfoResolver, part); case JsonMessagePartType.PlayerId: case JsonMessagePartType.PlayerName: return new PlayerMessagePart(players, connectionInfo, part); case JsonMessagePartType.LocationId: case JsonMessagePartType.LocationName: return new LocationMessagePart(players, itemInfoResolver, part); case JsonMessagePartType.EntranceName: return new EntranceMessagePart(part); case JsonMessagePartType.HintStatus: return new HintStatusMessagePart(part); default: return new MessagePart(MessagePartType.Text, part); } } } public interface IPlayerHelper { ReadOnlyDictionary> Players { get; } IEnumerable AllPlayers { get; } PlayerInfo ActivePlayer { get; } string GetPlayerAlias(int slot); string GetPlayerName(int slot); string GetPlayerAliasAndName(int slot); PlayerInfo GetPlayerInfo(int team, int slot); PlayerInfo GetPlayerInfo(int slot); } public class PlayerHelper : IPlayerHelper { private readonly IConnectionInfoProvider connectionInfo; private ReadOnlyDictionary> players = new ReadOnlyDictionary>(new Dictionary>(0)); public ReadOnlyDictionary> Players => players; public IEnumerable AllPlayers => players.SelectMany((KeyValuePair> kvp) => kvp.Value); public PlayerInfo ActivePlayer => GetPlayerInfo(connectionInfo.Team, connectionInfo.Slot); public PlayerInfo GetPlayerInfo(int team, int slot) { if (team < 0 || slot < 0 || players.Count <= team || players[team].Count <= slot) { return null; } return players[team][slot]; } public PlayerInfo GetPlayerInfo(int slot) { return GetPlayerInfo(connectionInfo.Team, slot); } internal PlayerHelper(IArchipelagoSocketHelper socket, IConnectionInfoProvider connectionInfo) { this.connectionInfo = connectionInfo; socket.PacketReceived += PacketReceived; } public string GetPlayerAlias(int slot) { if (players == null) { return null; } return players[connectionInfo.Team].FirstOrDefault((PlayerInfo p) => p.Slot == slot)?.Alias; } public string GetPlayerName(int slot) { if (players == null) { return null; } return players[connectionInfo.Team].FirstOrDefault((PlayerInfo p) => p.Slot == slot)?.Name; } public string GetPlayerAliasAndName(int slot) { if (players == null) { return null; } PlayerInfo playerInfo = players[connectionInfo.Team].FirstOrDefault((PlayerInfo p) => p.Slot == slot); if (playerInfo == null) { return null; } return playerInfo.Alias + " (" + playerInfo.Name + ")"; } private void PacketReceived(ArchipelagoPacketBase packet) { if (!(packet is ConnectedPacket connectedPacket)) { if (packet is RoomUpdatePacket roomUpdatePacket) { UpdatePlayerInfo(roomUpdatePacket.Players); } } else { CreatePlayerInfo(connectedPacket.Players, connectedPacket.SlotInfo); } } private void CreatePlayerInfo(NetworkPlayer[] networkPlayers, Dictionary slotInfos) { NetworkSlot[] source = ((slotInfos == null) ? new NetworkSlot[0] : slotInfos.Values.Where((NetworkSlot s) => s.Type == SlotType.Group).ToArray()); int num = 0; int num2 = 0; NetworkPlayer[] array = networkPlayers; for (int i = 0; i < array.Length; i++) { NetworkPlayer networkPlayer = array[i]; if (networkPlayer.Team > num) { num = networkPlayer.Team; } if (networkPlayer.Slot > num2) { num2 = networkPlayer.Slot; } } Dictionary dictionary = new Dictionary(num); for (int j = 0; j <= num; j++) { dictionary[j] = new PlayerInfo[num2 + 1]; dictionary[j][0] = new PlayerInfo { Team = j, Slot = 0, Name = "Server", Alias = "Server", Game = "Archipelago", Groups = new NetworkSlot[0] }; } array = networkPlayers; for (int i = 0; i < array.Length; i++) { NetworkPlayer p = array[i]; dictionary[p.Team][p.Slot] = new PlayerInfo { Team = p.Team, Slot = p.Slot, Name = p.Name, Alias = p.Alias, Game = slotInfos?[p.Slot].Game, Groups = source.Where((NetworkSlot g) => g.GroupMembers.Contains(p.Slot)).ToArray(), GroupMembers = slotInfos?[p.Slot].GroupMembers }; } Dictionary> dictionary2 = new Dictionary>(dictionary.Count); foreach (KeyValuePair item in dictionary) { dictionary2[item.Key] = new ReadOnlyCollection(item.Value); } players = new ReadOnlyDictionary>(dictionary2); } private void UpdatePlayerInfo(NetworkPlayer[] networkPlayers) { if (networkPlayers != null && networkPlayers.Length != 0) { for (int i = 0; i < networkPlayers.Length; i++) { NetworkPlayer networkPlayer = networkPlayers[i]; players[networkPlayer.Team][networkPlayer.Slot].Name = networkPlayer.Name; players[networkPlayer.Team][networkPlayer.Slot].Alias = networkPlayer.Alias; } } } } public class PlayerInfo : IEquatable { public int Team { get; internal set; } public int Slot { get; internal set; } public string Alias { get; internal set; } public string Name { get; internal set; } public string Game { get; internal set; } public NetworkSlot[] Groups { get; internal set; } internal int[] GroupMembers { get; set; } public bool IsGroup { get { if (GroupMembers != null) { return GroupMembers.Length != 0; } return false; } } [Obsolete] public bool IsSharingGroupWith(int team, int slot) { if (Team != team || Slot != slot) { if (Groups != null) { return Groups.Any((NetworkSlot g) => g.GroupMembers.Contains(slot)); } return false; } return true; } public IEnumerable GetGroupMembers(IPlayerHelper playerHelper) { if (!IsGroup) { return null; } return GroupMembers.Select((int g) => playerHelper.GetPlayerInfo(Team, g)); } public bool IsRelatedTo(PlayerInfo other) { if (!(this == other)) { if (Team == other.Team) { if (other.GroupMembers == null || !other.GroupMembers.Contains(this)) { if (GroupMembers != null) { return GroupMembers.Contains(other); } return false; } return true; } return false; } return true; } public static implicit operator int(PlayerInfo p) { return p.Slot; } public static bool operator ==(PlayerInfo lhs, PlayerInfo rhs) { return lhs?.Equals(rhs) ?? ((object)rhs == null); } public static bool operator !=(PlayerInfo lhs, PlayerInfo rhs) { return !(lhs == rhs); } public override string ToString() { return Alias ?? Name ?? $"Player: {Slot}"; } public PlayerInfo() { } [JsonConstructor] public PlayerInfo(int team, int slot, string name, string alias, string game, NetworkSlot[] groups, int[] groupMembers) { Team = team; Slot = slot; Name = name; Alias = alias; Game = game; Groups = groups; GroupMembers = groupMembers; } public bool Equals(PlayerInfo other) { if ((object)other == null) { return false; } if ((object)this == other) { return true; } if (Team == other.Team) { return Slot == other.Slot; } return false; } public override bool Equals(object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((PlayerInfo)obj); } public override int GetHashCode() { return (Team * 397) ^ Slot; } } public interface IReceivedItemsHelper { int Index { get; } ReadOnlyCollection AllItemsReceived { get; } event ReceivedItemsHelper.ItemReceivedHandler ItemReceived; string GetItemName(long id, string game = null); bool Any(); ItemInfo PeekItem(); ItemInfo DequeueItem(); } public class ReceivedItemsHelper : IReceivedItemsHelper { public delegate void ItemReceivedHandler(ReceivedItemsHelper helper); private readonly IArchipelagoSocketHelper socket; private readonly ILocationCheckHelper locationsHelper; private readonly IItemInfoResolver itemInfoResolver; private readonly IConnectionInfoProvider connectionInfoProvider; private readonly IPlayerHelper playerHelper; private ConcurrentQueue itemQueue; private readonly IConcurrentList allItemsReceived; private ReadOnlyCollection cachedReceivedItems; public int Index => cachedReceivedItems.Count; public ReadOnlyCollection AllItemsReceived => cachedReceivedItems; public event ItemReceivedHandler ItemReceived; internal ReceivedItemsHelper(IArchipelagoSocketHelper socket, ILocationCheckHelper locationsHelper, IItemInfoResolver itemInfoResolver, IConnectionInfoProvider connectionInfoProvider, IPlayerHelper playerHelper) { this.socket = socket; this.locationsHelper = locationsHelper; this.itemInfoResolver = itemInfoResolver; this.connectionInfoProvider = connectionInfoProvider; this.playerHelper = playerHelper; itemQueue = new ConcurrentQueue(); allItemsReceived = new ConcurrentList(); cachedReceivedItems = allItemsReceived.AsReadOnlyCollection(); socket.PacketReceived += Socket_PacketReceived; } public bool Any() { return !itemQueue.IsEmpty; } public ItemInfo PeekItem() { itemQueue.TryPeek(out var result); return result; } public ItemInfo DequeueItem() { itemQueue.TryDequeue(out var result); return result; } public string GetItemName(long id, string game = null) { return itemInfoResolver.GetItemName(id, game); } private void Socket_PacketReceived(ArchipelagoPacketBase packet) { if (packet.PacketType != ArchipelagoPacketType.ReceivedItems) { return; } ReceivedItemsPacket receivedItemsPacket = (ReceivedItemsPacket)packet; if (receivedItemsPacket.Index == 0) { PerformResynchronization(receivedItemsPacket); return; } if (allItemsReceived.Count != receivedItemsPacket.Index) { socket.SendPacket(new SyncPacket()); locationsHelper.CompleteLocationChecks(); return; } NetworkItem[] items = receivedItemsPacket.Items; for (int i = 0; i < items.Length; i++) { NetworkItem item = items[i]; PlayerInfo playerInfo = playerHelper.GetPlayerInfo(item.Player) ?? new PlayerInfo(); ItemInfo item2 = new ItemInfo(item, connectionInfoProvider.Game, playerInfo.Game, itemInfoResolver, playerInfo); allItemsReceived.Add(item2); itemQueue.Enqueue(item2); cachedReceivedItems = allItemsReceived.AsReadOnlyCollection(); this.ItemReceived?.Invoke(this); } } private void PerformResynchronization(ReceivedItemsPacket receivedItemsPacket) { ReadOnlyCollection readOnlyCollection = allItemsReceived.AsReadOnlyCollection(); itemQueue = new ConcurrentQueue(); allItemsReceived.Clear(); NetworkItem[] items = receivedItemsPacket.Items; for (int i = 0; i < items.Length; i++) { NetworkItem item = items[i]; PlayerInfo playerInfo = playerHelper.GetPlayerInfo(item.Player) ?? new PlayerInfo(); ItemInfo itemInfo = new ItemInfo(item, connectionInfoProvider.Game, playerInfo.Game, itemInfoResolver, playerInfo); itemQueue.Enqueue(itemInfo); allItemsReceived.Add(itemInfo); cachedReceivedItems = allItemsReceived.AsReadOnlyCollection(); if (this.ItemReceived != null && !readOnlyCollection.Contains(itemInfo)) { this.ItemReceived(this); } } } } public interface IRoomStateHelper { int HintCost { get; } int HintCostPercentage { get; } int LocationCheckPoints { get; } int HintPoints { get; } Version Version { get; } Version GeneratorVersion { get; } bool HasPassword { get; } Permissions ReleasePermissions { get; } Permissions ForfeitPermissions { get; } Permissions CollectPermissions { get; } Permissions RemainingPermissions { get; } string Seed { get; } DateTime RoomInfoSendTime { get; } ReadOnlyCollection ServerTags { get; } } public class RoomStateHelper : IRoomStateHelper { private readonly ILocationCheckHelper locationCheckHelper; private string[] tags; public int HintCost { get; private set; } public int HintCostPercentage { get; private set; } public int LocationCheckPoints { get; private set; } public int HintPoints { get; private set; } public Version Version { get; private set; } public Version GeneratorVersion { get; private set; } public bool HasPassword { get; private set; } public Permissions ReleasePermissions { get; private set; } public Permissions ForfeitPermissions => ReleasePermissions; public Permissions CollectPermissions { get; private set; } public Permissions RemainingPermissions { get; private set; } public string Seed { get; private set; } public DateTime RoomInfoSendTime { get; private set; } public ReadOnlyCollection ServerTags { get { if (tags != null) { return new ReadOnlyCollection(tags); } return null; } } internal RoomStateHelper(IArchipelagoSocketHelper socket, ILocationCheckHelper locationCheckHelper) { this.locationCheckHelper = locationCheckHelper; socket.PacketReceived += PacketReceived; } private void PacketReceived(ArchipelagoPacketBase packet) { if (!(packet is ConnectedPacket packet2)) { if (!(packet is RoomUpdatePacket packet3)) { if (packet is RoomInfoPacket packet4) { OnRoomInfoPacketReceived(packet4); } } else { OnRoomUpdatedPacketReceived(packet3); } } else { OnConnectedPacketReceived(packet2); } } private void OnConnectedPacketReceived(ConnectedPacket packet) { if (packet.HintPoints.HasValue) { HintPoints = packet.HintPoints.Value; } int num = packet.LocationsChecked.Length + packet.MissingChecks.Length; HintCost = (int)Math.Max(0m, (decimal)num * 0.01m * (decimal)HintCostPercentage); } private void OnRoomInfoPacketReceived(RoomInfoPacket packet) { HintCostPercentage = packet.HintCostPercentage; LocationCheckPoints = packet.LocationCheckPoints; Version = packet.Version?.ToVersion(); GeneratorVersion = packet.GeneratorVersion?.ToVersion(); HasPassword = packet.Password; Seed = packet.SeedName; RoomInfoSendTime = UnixTimeConverter.UnixTimeStampToDateTime(packet.Timestamp); tags = packet.Tags; if (packet.Permissions != null) { Permissions value2; if (packet.Permissions.TryGetValue("release", out var value)) { ReleasePermissions = value; } else if (packet.Permissions.TryGetValue("forfeit", out value2)) { ReleasePermissions = value2; } if (packet.Permissions.TryGetValue("collect", out var value3)) { CollectPermissions = value3; } if (packet.Permissions.TryGetValue("remaining", out var value4)) { RemainingPermissions = value4; } } } private void OnRoomUpdatedPacketReceived(RoomUpdatePacket packet) { if (packet.HintCostPercentage.HasValue) { HintCostPercentage = packet.HintCostPercentage.Value; HintCost = (int)Math.Max(0m, (decimal)locationCheckHelper.AllLocations.Count * 0.01m * (decimal)packet.HintCostPercentage.Value); } if (packet.LocationCheckPoints.HasValue) { LocationCheckPoints = packet.LocationCheckPoints.Value; } if (packet.HintPoints.HasValue) { HintPoints = packet.HintPoints.Value; } if (packet.Tags != null) { tags = packet.Tags; } if (packet.Password.HasValue) { HasPassword = packet.Password.Value; } if (packet.Permissions != null) { Permissions value2; if (packet.Permissions.TryGetValue("release", out var value)) { ReleasePermissions = value; } else if (packet.Permissions.TryGetValue("forfeit", out value2)) { ReleasePermissions = value2; } if (packet.Permissions.TryGetValue("collect", out var value3)) { CollectPermissions = value3; } if (packet.Permissions.TryGetValue("remaining", out var value4)) { RemainingPermissions = value4; } } } } } namespace Archipelago.MultiClient.Net.Extensions { internal static class UriExtensions { private static readonly string UriSchemeWss = "wss"; private static readonly string UriSchemeWs = "ws"; public static Uri WithSchema(this Uri uri, string schema) { return new UriBuilder(uri) { Scheme = schema }.Uri; } public static Uri AsWss(this Uri uri) { return uri.WithSchema(UriSchemeWss); } public static Uri AsWs(this Uri uri) { return uri.WithSchema(UriSchemeWs); } } } namespace Archipelago.MultiClient.Net.Exceptions { public class ArchipelagoServerRejectedPacketException : Exception { public ArchipelagoPacketType FaultyPacketType { get; } public InvalidPacketErrorType ErrorType { get; } public ArchipelagoServerRejectedPacketException(ArchipelagoPacketType faultyPacketType, InvalidPacketErrorType errorType, string message) : base(message) { FaultyPacketType = faultyPacketType; ErrorType = errorType; } } public class ArchipelagoSocketClosedException : Exception { public ArchipelagoSocketClosedException() { } public ArchipelagoSocketClosedException(string message) : base(message) { } public ArchipelagoSocketClosedException(string message, Exception innerException) : base(message, innerException) { } protected ArchipelagoSocketClosedException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } namespace Archipelago.MultiClient.Net.Enums { public enum ArchipelagoClientState { ClientUnknown = 0, ClientConnected = 5, ClientReady = 10, ClientPlaying = 20, ClientGoal = 30 } public enum ArchipelagoPacketType { RoomInfo, ConnectionRefused, Connected, ReceivedItems, LocationInfo, RoomUpdate, PrintJSON, Connect, ConnectUpdate, LocationChecks, LocationScouts, StatusUpdate, Say, GetDataPackage, DataPackage, Sync, Bounced, Bounce, InvalidPacket, Get, Retrieved, Set, SetReply, SetNotify, UpdateHint, Unknown } public enum ConnectionRefusedError { InvalidSlot, InvalidGame, SlotAlreadyTaken, IncompatibleVersion, InvalidPassword, InvalidItemsHandling } public enum Scope { Global, Game, Team, Slot, ReadOnly } public enum HintCreationPolicy { None, CreateAndAnnounce, CreateAndAnnounceOnce } public enum HintStatus { Found = 40, Unspecified = 0, NoPriority = 10, Avoid = 20, Priority = 30 } public enum InvalidPacketErrorType { Cmd, Arguments } [Flags] public enum ItemFlags { None = 0, Advancement = 1, NeverExclude = 2, Trap = 4 } [Flags] public enum ItemsHandlingFlags { NoItems = 0, RemoteItems = 1, IncludeOwnItems = 3, IncludeStartingInventory = 5, AllItems = 7 } public enum JsonMessagePartColor { Bold, Underline, Black, Red, Green, Yellow, Blue, Magenta, Cyan, White, BlackBg, RedBg, GreenBg, YellowBg, BlueBg, MagentaBg, CyanBg, WhiteBg } public enum JsonMessagePartType { Text, PlayerId, PlayerName, ItemId, ItemName, LocationId, LocationName, EntranceName, Color, HintStatus } public enum JsonMessageType { ItemSend, ItemCheat, Hint, Join, Part, Chat, ServerChat, Tutorial, TagsChanged, CommandResult, AdminCommandResult, Goal, Release, Collect, Countdown } public enum OperationType { Add, Mul, Max, Min, Replace, Default, Mod, Pow, Xor, Or, And, LeftShift, RightShift, Remove, Pop, Update, Floor, Ceil } [JsonConverter(typeof(PermissionsEnumConverter))] [Flags] public enum Permissions { Disabled = 0, Enabled = 1, Goal = 2, Auto = 6, AutoEnabled = 7 } [Flags] public enum SlotType { Spectator = 0, Player = 1, Group = 2 } } namespace Archipelago.MultiClient.Net.DataPackage { internal class DataPackageCache : IDataPackageCache { private readonly IArchipelagoSocketHelper socket; private readonly Dictionary inMemoryCache = new Dictionary(); internal IFileSystemDataPackageProvider FileSystemDataPackageProvider; public DataPackageCache(IArchipelagoSocketHelper socket) { this.socket = socket; socket.PacketReceived += Socket_PacketReceived; } public DataPackageCache(IArchipelagoSocketHelper socket, IFileSystemDataPackageProvider fileSystemProvider) { this.socket = socket; FileSystemDataPackageProvider = fileSystemProvider; socket.PacketReceived += Socket_PacketReceived; } private void Socket_PacketReceived(ArchipelagoPacketBase packet) { if (!(packet is RoomInfoPacket packet2)) { if (packet is DataPackagePacket dataPackagePacket) { UpdateDataPackageFromServer(dataPackagePacket.DataPackage); } return; } if (FileSystemDataPackageProvider == null) { FileSystemDataPackageProvider = new FileSystemCheckSumDataPackageProvider(); } List cacheInvalidatedGamesByChecksum = GetCacheInvalidatedGamesByChecksum(packet2); if (cacheInvalidatedGamesByChecksum.Any()) { socket.SendPacket(new GetDataPackagePacket { Games = cacheInvalidatedGamesByChecksum.ToArray() }); } } public bool TryGetDataPackageFromCache(out Dictionary gameData) { gameData = inMemoryCache; return inMemoryCache.Any(); } public bool TryGetGameDataFromCache(string game, out IGameDataLookup gameData) { if (inMemoryCache.TryGetValue(game, out var value)) { gameData = value; return true; } gameData = null; return false; } internal void UpdateDataPackageFromServer(Archipelago.MultiClient.Net.Models.DataPackage package) { foreach (KeyValuePair game in package.Games) { inMemoryCache[game.Key] = new GameDataLookup(game.Value); FileSystemDataPackageProvider.SaveDataPackageToFile(game.Key, game.Value); } } private List GetCacheInvalidatedGamesByChecksum(RoomInfoPacket packet) { List list = new List(); string[] games = packet.Games; foreach (string text in games) { if (packet.DataPackageChecksums != null && packet.DataPackageChecksums.TryGetValue(text, out var value) && FileSystemDataPackageProvider.TryGetDataPackage(text, value, out var gameData) && gameData.Checksum == value) { inMemoryCache[text] = new GameDataLookup(gameData); } else { list.Add(text); } } return list; } } internal class FileSystemCheckSumDataPackageProvider : IFileSystemDataPackageProvider { private static readonly string CacheFolder; static FileSystemCheckSumDataPackageProvider() { CacheFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), Path.Combine("Archipelago", Path.Combine("Cache", "datapackage"))); } public bool TryGetDataPackage(string game, string checksum, out GameData gameData) { string path = Path.Combine(Path.Combine(CacheFolder, GetFileSystemSafeFileName(game)), checksum + ".json"); if (!File.Exists(path)) { gameData = null; return false; } try { string text = File.ReadAllText(path); gameData = JsonConvert.DeserializeObject(text); return true; } catch { gameData = null; return false; } } public void SaveDataPackageToFile(string game, GameData gameData) { string text = Path.Combine(CacheFolder, GetFileSystemSafeFileName(game)); string path = Path.Combine(text, GetFileSystemSafeFileName(gameData.Checksum) + ".json"); try { Directory.CreateDirectory(text); string contents = JsonConvert.SerializeObject((object)gameData); File.WriteAllText(path, contents); } catch { } } private string GetFileSystemSafeFileName(string gameName) { string result = gameName; char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char c in invalidFileNameChars) { gameName = gameName.Replace(c.ToString(), string.Empty); } return result; } } internal interface IGameDataLookup { TwoWayLookup Locations { get; } TwoWayLookup Items { get; } string Checksum { get; } } internal class GameDataLookup : IGameDataLookup { public TwoWayLookup Locations { get; } public TwoWayLookup Items { get; } public string Checksum { get; } public GameDataLookup(GameData gameData) { Locations = new TwoWayLookup(); Items = new TwoWayLookup(); Checksum = gameData.Checksum; foreach (KeyValuePair item in gameData.LocationLookup) { Locations.Add(item.Value, item.Key); } foreach (KeyValuePair item2 in gameData.ItemLookup) { Items.Add(item2.Value, item2.Key); } } } internal interface IDataPackageCache { bool TryGetGameDataFromCache(string game, out IGameDataLookup gameData); bool TryGetDataPackageFromCache(out Dictionary gameData); } internal interface IFileSystemDataPackageProvider { bool TryGetDataPackage(string game, string checksum, out GameData gameData); void SaveDataPackageToFile(string game, GameData gameData); } public interface IItemInfoResolver { string GetItemName(long itemId, string game = null); string GetLocationName(long locationId, string game = null); long GetLocationId(string locationName, string game = null); } internal class ItemInfoResolver : IItemInfoResolver { private readonly IDataPackageCache cache; private readonly IConnectionInfoProvider connectionInfoProvider; public ItemInfoResolver(IDataPackageCache cache, IConnectionInfoProvider connectionInfoProvider) { this.cache = cache; this.connectionInfoProvider = connectionInfoProvider; } public string GetItemName(long itemId, string game = null) { if (game == null) { game = connectionInfoProvider.Game ?? "Archipelago"; } if (itemId < 0) { game = "Archipelago"; } if (!cache.TryGetGameDataFromCache(game, out var gameData)) { return null; } if (!gameData.Items.TryGetValue(itemId, out var b)) { return null; } return b; } public string GetLocationName(long locationId, string game = null) { if (game == null) { game = connectionInfoProvider.Game ?? "Archipelago"; } if (locationId < 0) { game = "Archipelago"; } if (!cache.TryGetGameDataFromCache(game, out var gameData)) { return null; } if (!gameData.Locations.TryGetValue(locationId, out var b)) { return null; } return b; } public long GetLocationId(string locationName, string game = null) { if (game == null) { game = connectionInfoProvider.Game ?? "Archipelago"; } if (cache.TryGetGameDataFromCache(game, out var gameData) && gameData.Locations.TryGetValue(locationName, out var a)) { return a; } if (cache.TryGetGameDataFromCache("Archipelago", out var gameData2) && gameData2.Locations.TryGetValue(locationName, out var a2)) { return a2; } return -1L; } } } namespace Archipelago.MultiClient.Net.Converters { public class ArchipelagoPacketConverter : JsonConverter { private static readonly Dictionary> PacketDeserializationMap = new Dictionary>(23) { [ArchipelagoPacketType.RoomInfo] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.ConnectionRefused] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.Connected] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.ReceivedItems] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.LocationInfo] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.RoomUpdate] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.PrintJSON] = DeserializePrintJsonPacket, [ArchipelagoPacketType.Connect] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.ConnectUpdate] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.LocationChecks] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.LocationScouts] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.StatusUpdate] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.Say] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.GetDataPackage] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.DataPackage] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.Bounce] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.Bounced] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.InvalidPacket] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.Get] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.Retrieved] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.Set] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.SetNotify] = (JObject obj) => ((JToken)obj).ToObject(), [ArchipelagoPacketType.SetReply] = (JObject obj) => ((JToken)obj).ToObject() }; public override bool CanWrite => false; public override bool CanConvert(Type objectType) { return objectType.IsAssignableFrom(typeof(ArchipelagoPacketBase)); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject val = JObject.Load(reader); ArchipelagoPacketType result; ArchipelagoPacketBase archipelagoPacketBase = ((!EnumTryParse(((object)val["cmd"])?.ToString(), out result) || !PacketDeserializationMap.ContainsKey(result)) ? new UnknownPacket() : PacketDeserializationMap[result](val)); archipelagoPacketBase.jobject = val; return archipelagoPacketBase; } private static ArchipelagoPacketBase DeserializePrintJsonPacket(JObject obj) { JToken val = default(JToken); if (obj.TryGetValue("type", ref val)) { if (EnumTryParse(((object)val).ToString(), out var result)) { switch (result) { case JsonMessageType.ItemSend: return ((JToken)obj).ToObject(); case JsonMessageType.ItemCheat: return ((JToken)obj).ToObject(); case JsonMessageType.Hint: return ((JToken)obj).ToObject(); case JsonMessageType.Join: return ((JToken)obj).ToObject(); case JsonMessageType.Part: return ((JToken)obj).ToObject(); case JsonMessageType.Chat: return ((JToken)obj).ToObject(); case JsonMessageType.ServerChat: return ((JToken)obj).ToObject(); case JsonMessageType.Tutorial: return ((JToken)obj).ToObject(); case JsonMessageType.TagsChanged: return ((JToken)obj).ToObject(); case JsonMessageType.CommandResult: return ((JToken)obj).ToObject(); case JsonMessageType.AdminCommandResult: return ((JToken)obj).ToObject(); case JsonMessageType.Goal: return ((JToken)obj).ToObject(); case JsonMessageType.Release: return ((JToken)obj).ToObject(); case JsonMessageType.Collect: return ((JToken)obj).ToObject(); case JsonMessageType.Countdown: return ((JToken)obj).ToObject(); } } obj["type"] = null; } return ((JToken)obj).ToObject(); } private static bool EnumTryParse(string value, out TEnum result) where TEnum : struct, IConvertible { return Enum.TryParse(value, out result); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } } public class PermissionsEnumConverter : JsonConverter { public override bool CanConvert(Type objectType) { if (!(objectType == typeof(string)) && !(objectType == typeof(Permissions))) { return objectType == typeof(int); } return true; } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { string text = reader.Value.ToString(); if (int.TryParse(text, out var result)) { return (Permissions)result; } Permissions permissions = Permissions.Disabled; if (text.Contains("enabled")) { permissions |= Permissions.Enabled; } if (text.Contains("auto")) { permissions |= Permissions.Auto; } if (text.Contains("goal")) { permissions |= Permissions.Goal; } return permissions; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { Permissions permissions = (Permissions)value; writer.WriteValue((int)permissions); } } public static class UnixTimeConverter { private static DateTime UtcEpoch => new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); public static DateTime UnixTimeStampToDateTime(double unixTimeStamp) { if (unixTimeStamp > 1000000000000.0) { unixTimeStamp /= 1000.0; } return UtcEpoch.AddSeconds(unixTimeStamp); } public static double ToUnixTimeStamp(this DateTime dateTime) { return (dateTime - UtcEpoch).TotalMilliseconds / 1000.0; } } } namespace Archipelago.MultiClient.Net.ConcurrentCollection { internal class ConcurrentHashSet : IConcurrentHashSet { private readonly ConcurrentDictionary set = new ConcurrentDictionary(); public bool TryAdd(T item) { return set.TryAdd(item, 0); } public bool Contains(T item) { return set.ContainsKey(item); } public void UnionWith(T[] otherSet) { foreach (T key in otherSet) { set.TryAdd(key, 0); } } public T[] ToArray() { return set.Keys.ToArray(); } public ReadOnlyCollection AsToReadOnlyCollection() { return (ReadOnlyCollection)set.Keys; } public ReadOnlyCollection AsToReadOnlyCollectionExcept(IConcurrentHashSet otherSet) { ICollection keys = set.Keys; List list = new List(keys.Count); foreach (T item in keys) { if (!otherSet.Contains(item)) { list.Add(item); } } return new ReadOnlyCollection(list); } } internal class ConcurrentList : IConcurrentList { private readonly ConcurrentDictionary list = new ConcurrentDictionary(); public int Count => list.Count; public void Add(T item) { list.TryAdd(list.Count, item); } public void Clear() { list.Clear(); } public ReadOnlyCollection AsReadOnlyCollection() { return (ReadOnlyCollection)list.Values; } } internal interface IConcurrentList { int Count { get; } void Add(T item); void Clear(); ReadOnlyCollection AsReadOnlyCollection(); } } namespace Archipelago.MultiClient.Net.Colors { public static class BuiltInPalettes { public static readonly Palette Dark = new Palette(Color.White, new Dictionary { [PaletteColor.White] = Color.White, [PaletteColor.Black] = Color.Black, [PaletteColor.Red] = Color.Red, [PaletteColor.Green] = Color.Green, [PaletteColor.Blue] = Color.Blue, [PaletteColor.Cyan] = Color.Cyan, [PaletteColor.Magenta] = Color.Magenta, [PaletteColor.Yellow] = Color.Yellow, [PaletteColor.SlateBlue] = Color.SlateBlue, [PaletteColor.Salmon] = Color.Salmon, [PaletteColor.Plum] = Color.Plum }); } public static class ColorUtils { public const PaletteColor ActivePlayerColor = PaletteColor.Magenta; public const PaletteColor NonActivePlayerColor = PaletteColor.Yellow; public static PaletteColor? GetColor(JsonMessagePartColor color) { switch (color) { case JsonMessagePartColor.Red: case JsonMessagePartColor.RedBg: return PaletteColor.Red; case JsonMessagePartColor.Green: case JsonMessagePartColor.GreenBg: return PaletteColor.Green; case JsonMessagePartColor.Yellow: case JsonMessagePartColor.YellowBg: return PaletteColor.Yellow; case JsonMessagePartColor.Blue: case JsonMessagePartColor.BlueBg: return PaletteColor.Blue; case JsonMessagePartColor.Magenta: case JsonMessagePartColor.MagentaBg: return PaletteColor.Magenta; case JsonMessagePartColor.Cyan: case JsonMessagePartColor.CyanBg: return PaletteColor.Cyan; case JsonMessagePartColor.Black: case JsonMessagePartColor.BlackBg: return PaletteColor.Black; case JsonMessagePartColor.White: case JsonMessagePartColor.WhiteBg: return PaletteColor.White; default: return null; } } public static PaletteColor? GetColor(HintStatus status) { return status switch { HintStatus.Found => PaletteColor.Green, HintStatus.NoPriority => PaletteColor.SlateBlue, HintStatus.Avoid => PaletteColor.Salmon, HintStatus.Priority => PaletteColor.Plum, _ => null, }; } public static PaletteColor? GetColor(Hint hint) { return GetColor(hint.Status); } public static PaletteColor? GetColor(ItemFlags flags) { if (HasFlag(flags, ItemFlags.Advancement)) { return PaletteColor.Plum; } if (HasFlag(flags, ItemFlags.NeverExclude)) { return PaletteColor.SlateBlue; } if (HasFlag(flags, ItemFlags.Trap)) { return PaletteColor.Salmon; } return PaletteColor.Cyan; } public static PaletteColor? GetColor(ItemInfo item) { return GetColor(item.Flags); } public static PaletteColor? GetColor(PlayerInfo player, IConnectionInfoProvider connectionInfo) { return (player.Slot == connectionInfo.Slot) ? PaletteColor.Magenta : PaletteColor.Yellow; } private static bool HasFlag(ItemFlags flags, ItemFlags flag) { return flags.HasFlag(flag); } } public class Palette { private readonly Dictionary palette; public T DefaultColor { get; private set; } public T this[PaletteColor? color] { get { if (color.HasValue && palette.TryGetValue(color.Value, out var value)) { return value; } return DefaultColor; } } public Palette(T defaultColor, Dictionary palette) { DefaultColor = defaultColor; this.palette = new Dictionary(palette); } public Palette Transform(Func transform) { U defaultColor = transform(DefaultColor); Dictionary dictionary = new Dictionary(palette.Count); foreach (KeyValuePair item in palette) { dictionary[item.Key] = transform(item.Value); } return new Palette(defaultColor, dictionary); } public Palette Edit(T newDefault, Dictionary paletteEdits) { Dictionary dictionary = new Dictionary(palette); foreach (KeyValuePair paletteEdit in paletteEdits) { dictionary[paletteEdit.Key] = paletteEdit.Value; } return new Palette(newDefault, dictionary); } public Palette Edit(Dictionary paletteEdits) { return Edit(DefaultColor, paletteEdits); } } public enum PaletteColor { White, Black, Red, Green, Blue, Cyan, Magenta, Yellow, SlateBlue, Salmon, Plum } } namespace Archipelago.MultiClient.Net.BounceFeatures.DeathLink { public class DeathLink : IEquatable { public DateTime Timestamp { get; internal set; } public string Source { get; } public string Cause { get; } public DeathLink(string sourcePlayer, string cause = null) { Timestamp = DateTime.UtcNow; Source = sourcePlayer; Cause = cause; } internal static bool TryParse(Dictionary data, out DeathLink deathLink) { try { if (!data.TryGetValue("time", out var value) || !data.TryGetValue("source", out var value2)) { deathLink = null; return false; } string cause = null; if (data.TryGetValue("cause", out var value3)) { cause = ((object)value3).ToString(); } deathLink = new DeathLink(((object)value2).ToString(), cause) { Timestamp = UnixTimeConverter.UnixTimeStampToDateTime(value.ToObject()) }; return true; } catch { deathLink = null; return false; } } public bool Equals(DeathLink other) { if ((object)other == null) { return false; } if ((object)this == other) { return true; } if (Source == other.Source && Timestamp.Date.Equals(other.Timestamp.Date) && Timestamp.Hour == other.Timestamp.Hour && Timestamp.Minute == other.Timestamp.Minute) { return Timestamp.Second == other.Timestamp.Second; } return false; } public override bool Equals(object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((DeathLink)obj); } public override int GetHashCode() { return (Timestamp.GetHashCode() * 397) ^ ((Source != null) ? Source.GetHashCode() : 0); } public static bool operator ==(DeathLink lhs, DeathLink rhs) { return lhs?.Equals(rhs) ?? ((object)rhs == null); } public static bool operator !=(DeathLink lhs, DeathLink rhs) { return !(lhs == rhs); } } public static class DeathLinkProvider { public static DeathLinkService CreateDeathLinkService(this ArchipelagoSession session) { return new DeathLinkService(session.Socket, session.ConnectionInfo); } } public class DeathLinkService { public delegate void DeathLinkReceivedHandler(DeathLink deathLink); private readonly IArchipelagoSocketHelper socket; private readonly IConnectionInfoProvider connectionInfoProvider; private DeathLink lastSendDeathLink; public event DeathLinkReceivedHandler OnDeathLinkReceived; internal DeathLinkService(IArchipelagoSocketHelper socket, IConnectionInfoProvider connectionInfoProvider) { this.socket = socket; this.connectionInfoProvider = connectionInfoProvider; socket.PacketReceived += OnPacketReceived; } private void OnPacketReceived(ArchipelagoPacketBase packet) { if (packet is BouncedPacket bouncedPacket && bouncedPacket.Tags.Contains("DeathLink") && DeathLink.TryParse(bouncedPacket.Data, out var deathLink) && (!(lastSendDeathLink != null) || !(lastSendDeathLink == deathLink)) && this.OnDeathLinkReceived != null) { this.OnDeathLinkReceived(deathLink); } } public void SendDeathLink(DeathLink deathLink) { BouncePacket bouncePacket = new BouncePacket { Tags = new List { "DeathLink" }, Data = new Dictionary { { "time", JToken.op_Implicit(deathLink.Timestamp.ToUnixTimeStamp()) }, { "source", JToken.op_Implicit(deathLink.Source) } } }; if (deathLink.Cause != null) { bouncePacket.Data.Add("cause", JToken.op_Implicit(deathLink.Cause)); } lastSendDeathLink = deathLink; socket.SendPacketAsync(bouncePacket); } public void EnableDeathLink() { if (Array.IndexOf(connectionInfoProvider.Tags, "DeathLink") == -1) { connectionInfoProvider.UpdateConnectionOptions(connectionInfoProvider.Tags.Concat(new string[1] { "DeathLink" }).ToArray()); } } public void DisableDeathLink() { if (Array.IndexOf(connectionInfoProvider.Tags, "DeathLink") != -1) { connectionInfoProvider.UpdateConnectionOptions(connectionInfoProvider.Tags.Where((string t) => t != "DeathLink").ToArray()); } } } }