Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ValheimArchipelagoRandomizer v0.2.35
ValheimRandomizer.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net.WebSockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using Archipelago.MultiClient.Net; using Archipelago.MultiClient.Net.Enums; using Archipelago.MultiClient.Net.Exceptions; using Archipelago.MultiClient.Net.Helpers; using Archipelago.MultiClient.Net.Models; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("ValheimRandomizer")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+0660048b62c7e8f9cf251705139a0190370e03a5")] [assembly: AssemblyProduct("ValheimRandomizer")] [assembly: AssemblyTitle("ValheimRandomizer")] [assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially created with the assistance of generative AI in Cursor.")] [assembly: AssemblyMetadata("AI_Model_Vendor", "xAI")] [assembly: AssemblyVersion("1.0.0.0")] internal static class ArchipelagoConnection { public const string DefaultGoalDefeatKey = "defeated_frozenking_p3"; public const string DefaultGoalLocationName = "Defeat Kall"; public const string GoalItemName = "Victory"; private static string goalDefeatKey = "defeated_frozenking_p3"; private static string goalLocationName = "Defeat Kall"; private static ArchipelagoSession session; private static bool connected; private static bool dropChecksResolved; private static int lost; private static int sendGeneration; private static Task inflight; private static DateTime inflightStartedUtc; private static Action inflightSuccess; private static readonly object noticeLock = new object(); private static string pendingNotice; private static readonly TimeSpan SendTimeout = TimeSpan.FromSeconds(8.0); private static FieldInfo socketField; private static List<string> gottenItems = new List<string>(); private static readonly HashSet<string> warnedMissingDrops = new HashSet<string>(); public static bool IsConnected { get { if (connected) { return session != null; } return false; } } public static string DisconnectMessage { get; private set; } public static bool DropChecksEnabled { get; private set; } public static bool BossDropsEnabled { get; private set; } public static void Connect(string host, int port, string slot, string password) { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Expected O, but got Unknown //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Expected O, but got Unknown //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown Disconnect(); session = ArchipelagoSessionFactory.CreateSession(host, port); try { LoginResult val = session.TryConnectAndLogin("Valheim", slot, (ItemsHandlingFlags)7, (Version)null, (string[])null, (string)null, password, true); if (!val.Successful) { LoginFailure val2 = (LoginFailure)val; throw new Exception(string.Join("; ", val2.Errors ?? Array.Empty<string>())); } gottenItems.Clear(); warnedMissingDrops.Clear(); ResolveDropChecks(val); ResolveBossDrops(val); ApplyGoal(val); ValheimRandomizer.ApplySeedRules(ReadSlotInt(val, "research_access"), ReadSlotBool(val, "sequential_tiers")); session.Items.ItemReceived += new ItemReceivedHandler(OnApItemReceived); DisconnectMessage = null; session.Socket.SocketClosed += new SocketClosedHandler(OnSocketClosed); session.Socket.ErrorReceived += new ErrorReceivedHandler(OnSocketError); connected = true; PublishResearchCosts(); if (successSlotData(val, out var slotData)) { ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogInfo((object)("Slot data keys: " + string.Join(", ", slotData.Keys))); } } } catch { Disconnect(); throw; } } public static void Disconnect() { ReleaseSession(closeSocket: true); DisconnectMessage = null; Interlocked.Exchange(ref lost, 0); } private static void LoseConnection(string message) { if (Interlocked.Exchange(ref lost, 1) == 0) { DisconnectMessage = message; lock (noticeLock) { pendingNotice = message; } ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogError((object)message); } ReleaseSession(closeSocket: false); } } private static void ReleaseSession(bool closeSocket) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Expected O, but got Unknown //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Expected O, but got Unknown ArchipelagoSession val = session; session = null; connected = false; sendGeneration++; Task task = inflight; inflight = null; inflightSuccess = null; if (task != null && !task.IsCompleted) { task.ContinueWith(delegate(Task t) { _ = t.Exception; }, TaskContinuationOptions.OnlyOnFaulted); } DropChecksEnabled = false; BossDropsEnabled = false; dropChecksResolved = false; goalDefeatKey = "defeated_frozenking_p3"; goalLocationName = "Defeat Kall"; ValheimRandomizer.ClearSeedRules(); if (val == null) { return; } try { val.Items.ItemReceived -= new ItemReceivedHandler(OnApItemReceived); } catch (Exception ex) { ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogWarning((object)("Disconnect unhook failed: " + ex.Message)); } } try { if (val.Socket != null) { val.Socket.SocketClosed -= new SocketClosedHandler(OnSocketClosed); val.Socket.ErrorReceived -= new ErrorReceivedHandler(OnSocketError); } } catch (Exception ex2) { ManualLogSource log2 = ValheimRandomizer.Log; if (log2 != null) { log2.LogWarning((object)("Disconnect unhook failed: " + ex2.Message)); } } if (!closeSocket || val.Socket == null || !val.Socket.Connected) { return; } try { val.Socket.DisconnectAsync().ContinueWith(delegate(Task t) { ManualLogSource log4 = ValheimRandomizer.Log; if (log4 != null) { log4.LogWarning((object)("Disconnect failed: " + t.Exception.GetBaseException().Message)); } }, TaskContinuationOptions.OnlyOnFaulted); } catch (Exception ex3) { ManualLogSource log3 = ValheimRandomizer.Log; if (log3 != null) { log3.LogWarning((object)("Disconnect failed: " + ex3.Message)); } } } private static void OnSocketClosed(string reason) { LoseConnection(string.IsNullOrWhiteSpace(reason) ? "Archipelago server closed the connection. Reconnect to send checks." : ("Archipelago server closed the connection: " + reason.Trim() + ". Reconnect to send checks.")); } private static void OnSocketError(Exception exception, string message) { if (!IsDeadSocket(exception)) { ArchipelagoSession obj = session; if (SocketIsUsable((obj != null) ? obj.Socket : null)) { ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogWarning((object)("Archipelago error: " + ((!string.IsNullOrEmpty(message)) ? message : exception?.Message))); } return; } } LoseConnection("Archipelago connection lost. Reconnect to send checks."); } private static bool IsDeadSocket(Exception exception) { for (Exception ex = exception; ex != null; ex = ex.InnerException) { if (ex is ArchipelagoSocketClosedException || ex is WebSocketException) { return true; } } return false; } private static bool SocketIsUsable(IArchipelagoSocketHelper socket) { if (socket == null) { return false; } try { if (socketField == null) { socketField = FindSocketField(((object)socket).GetType()); } if (socketField?.GetValue(socket) is WebSocket webSocket) { return webSocket.State == WebSocketState.Open; } } catch (Exception ex) { ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogWarning((object)("Socket state check failed: " + ex.Message)); } } return socket.Connected; } private static FieldInfo FindSocketField(Type type) { while (type != null) { FieldInfo field = type.GetField("Socket", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field; } type = type.BaseType; } return null; } public static void PublishResearchCosts() { if (IsConnected && session != null) { IConnectionInfoProvider connectionInfo = session.ConnectionInfo; string text = "valheim_costs_" + connectionInfo.Team.ToString(CultureInfo.InvariantCulture) + "_" + connectionInfo.Slot.ToString(CultureInfo.InvariantCulture); session.DataStorage[text] = DataStorageElement.op_Implicit((ValheimRandomizer.ActiveResearchCosts == ValheimRandomizer.ResearchCostMode.Halved) ? "halved" : "default"); } } public static void PublishPity(JObject payload) { if (IsConnected && session != null && payload != null) { IConnectionInfoProvider connectionInfo = session.ConnectionInfo; string text = "valheim_pity_" + connectionInfo.Team.ToString(CultureInfo.InvariantCulture) + "_" + connectionInfo.Slot.ToString(CultureInfo.InvariantCulture); session.DataStorage[text] = DataStorageElement.op_Implicit(((JToken)payload).ToString((Formatting)0, Array.Empty<JsonConverter>())); } } public static void Pump() { string text; lock (noticeLock) { if (pendingNotice == null || (Object)(object)MessageHud.instance == (Object)null) { return; } text = pendingNotice; pendingNotice = null; } MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true); ValheimRandomizer.RevealEligibleRecipes(); } private static void ResolveDropChecks(LoginResult result) { bool flag = ReadSlotBool(result, "drop_checks"); bool flag2 = SeedHasGatherLocations(); if (flag || flag2) { DropChecksEnabled = true; dropChecksResolved = true; ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogInfo((object)(flag ? "Drop checks on from slot data." : "Drop checks on because this seed has Gather locations.")); } return; } ArchipelagoSession obj = session; object obj2; if (obj == null) { obj2 = null; } else { ILocationCheckHelper locations = obj.Locations; obj2 = ((locations != null) ? locations.AllLocations : null); } if (obj2 != null && session.Locations.AllLocations.Count > 0) { DropChecksEnabled = false; dropChecksResolved = true; ManualLogSource log2 = ValheimRandomizer.Log; if (log2 != null) { log2.LogInfo((object)"Drop checks off."); } } } private static void ResolveBossDrops(LoginResult result) { bool flag = ReadSlotBool(result, "boss_drops"); if (flag || SeedHasBossLocations()) { BossDropsEnabled = true; ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogInfo((object)(flag ? "Boss drops on from slot data." : "Boss drops on because this seed has more than one boss location.")); } } else { BossDropsEnabled = false; ManualLogSource log2 = ValheimRandomizer.Log; if (log2 != null) { log2.LogInfo((object)"Boss drops off. This seed still drops them from the boss."); } } } private static bool SeedHasBossLocations() { int num = 0; foreach (ValheimRandomizer.BossDrop bossDrop in ValheimRandomizer.BossDrops) { try { if (session.Locations.GetLocationIdFromName("Valheim", bossDrop.locationName) > 0) { num++; } } catch { return false; } if (num >= 2) { return true; } } return false; } public static void EnsureDropChecksResolved() { if (dropChecksResolved || session == null) { return; } if (SeedHasGatherLocations()) { DropChecksEnabled = true; dropChecksResolved = true; ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogInfo((object)"Drop checks on because this seed has Gather locations."); } return; } ILocationCheckHelper locations = session.Locations; if (((locations != null) ? locations.AllLocations : null) != null && session.Locations.AllLocations.Count > 0) { DropChecksEnabled = false; dropChecksResolved = true; ManualLogSource log2 = ValheimRandomizer.Log; if (log2 != null) { log2.LogInfo((object)"Drop checks off."); } } } private static bool SeedHasGatherLocations() { ArchipelagoSession obj = session; object obj2; if (obj == null) { obj2 = null; } else { ILocationCheckHelper locations = obj.Locations; obj2 = ((locations != null) ? locations.AllLocations : null); } ReadOnlyCollection<long> readOnlyCollection = (ReadOnlyCollection<long>)obj2; if (readOnlyCollection == null) { return false; } foreach (long item in readOnlyCollection) { string locationNameFromId = session.Locations.GetLocationNameFromId(item, "Valheim"); if (!string.IsNullOrEmpty(locationNameFromId) && locationNameFromId.StartsWith("Gather: ", StringComparison.Ordinal)) { return true; } } return false; } private static bool successSlotData(LoginResult result, out Dictionary<string, object> slotData) { slotData = null; LoginSuccessful val = (LoginSuccessful)(object)((result is LoginSuccessful) ? result : null); if (val == null || val.SlotData == null) { return false; } slotData = val.SlotData; return true; } private static bool ReadSlotBool(LoginResult result, string key) { if (!TrySlotValue(result, key, out var raw)) { return false; } if (TryCoerceBool(raw, out var flag)) { return flag; } ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogWarning((object)$"Slot data '{key}' was {raw.GetType().Name} '{raw}' and was not read as on."); } return false; } private static bool TryCoerceBool(object raw, out bool flag) { flag = false; if (raw == null) { return false; } if (raw is bool flag2) { flag = flag2; return true; } if (raw is string text) { if (bool.TryParse(text, out flag)) { return true; } if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { flag = result != 0; return true; } if (text.Equals("yes", StringComparison.OrdinalIgnoreCase)) { flag = true; return true; } return false; } PropertyInfo property = raw.GetType().GetProperty("Value"); if (property != null && property.PropertyType == typeof(object)) { object value = property.GetValue(raw); if (value != null && value != raw) { return TryCoerceBool(value, out flag); } } try { flag = Convert.ToInt64(raw, CultureInfo.InvariantCulture) != 0; return true; } catch { return false; } } private static void ApplyGoal(LoginResult result) { goalDefeatKey = "defeated_frozenking_p3"; goalLocationName = "Defeat Kall"; string key = ReadSlotString(result, "goal_key"); string name = ReadSlotString(result, "goal_location"); if (IsGoalKey(key) && IsGoalLocation(name)) { goalDefeatKey = key; goalLocationName = name; } ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogInfo((object)("Goal is " + goalLocationName + " (" + goalDefeatKey + ").")); } } private static string ReadSlotString(LoginResult result, string key) { if (!TrySlotValue(result, key, out var raw)) { return null; } if (raw is string text) { return text.Trim(); } try { return Convert.ToString(raw, CultureInfo.InvariantCulture)?.Trim(); } catch { return null; } } private static bool IsGoalKey(string key) { if (string.IsNullOrEmpty(key) || key.Length > 64) { return false; } foreach (char c in key) { if ((c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '_') { return false; } } return true; } private static bool IsGoalLocation(string name) { if (string.IsNullOrWhiteSpace(name) || name.Length > 80) { return false; } if (name.IndexOf('\n') < 0) { return name.IndexOf('\r') < 0; } return false; } private static int? ReadSlotInt(LoginResult result, string key) { if (!TrySlotValue(result, key, out var raw)) { return null; } if (raw is string text) { string text2 = text.Trim(); if (int.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { return result2; } if (text2.Equals("default", StringComparison.OrdinalIgnoreCase)) { return 0; } if (text2.Equals("tier", StringComparison.OrdinalIgnoreCase)) { return 1; } if (text2.Equals("materials", StringComparison.OrdinalIgnoreCase) || text2.Equals("mats", StringComparison.OrdinalIgnoreCase)) { return 2; } if (text2.Equals("natural", StringComparison.OrdinalIgnoreCase)) { return 3; } return null; } try { return Convert.ToInt32(raw, CultureInfo.InvariantCulture); } catch { return null; } } private static bool TrySlotValue(LoginResult result, string key, out object raw) { raw = null; LoginSuccessful val = (LoginSuccessful)(object)((result is LoginSuccessful) ? result : null); if (val == null || val.SlotData == null) { return false; } if (val.SlotData.TryGetValue(key, out raw)) { return raw != null; } return false; } public static void SendLocation(string researchId) { if (!ValheimRandomizer.randomized.Value || !WorldLoaded() || string.IsNullOrWhiteSpace(researchId)) { return; } if (researchId.IndexOf(' ') >= 0) { ValheimRandomizer.Log.LogWarning((object)("Refusing location key with a space: " + researchId)); return; } if (!ValheimRandomizer.researchToArchipelago.TryGetValue(researchId, out var value)) { ValheimRandomizer.Log.LogWarning((object)("No Archipelago name for research " + researchId)); return; } ValheimRandomizer.Log.LogInfo((object)("Queue location " + value)); if (!HasGlobal("ap_loc_sent_" + researchId) && !HasGlobal("ap_loc_pending_" + researchId)) { SetGlobal("ap_loc_pending_" + researchId); TryFlushPendingLocations(); } } public static void SendGoal() { if (connected && session != null && ValheimRandomizer.randomized.Value && WorldLoaded() && !HasGlobal("ap_goal_sent") && !HasGlobal("ap_goal_pending")) { ValheimRandomizer.Log.LogInfo((object)("Queue goal " + goalLocationName)); SetGlobal("ap_goal_pending"); TryFlushPendingLocations(); } } public static void SendDropLocation(string itemID, string locationName) { if (DropChecksEnabled && ValheimRandomizer.randomized.Value && WorldLoaded() && !string.IsNullOrWhiteSpace(itemID) && !HasGlobal("ap_drop_sent_" + itemID) && !HasGlobal("ap_drop_pending_" + itemID)) { ValheimRandomizer.Log.LogInfo((object)("Queue drop location " + locationName)); SetGlobal("ap_drop_pending_" + itemID); TryFlushPendingLocations(); } } private static void OnApItemReceived(ReceivedItemsHelper helper) { if (!WorldLoaded()) { return; } while (true) { ItemInfo val = helper.DequeueItem(); if (val != null) { ApplyReceivedItem(val.ItemName); continue; } break; } } private static void TryGetAllItems() { if (!WorldLoaded()) { return; } foreach (ItemInfo item in session.Items.AllItemsReceived) { ApplyReceivedItem(item.ItemName); } } private static void ApplyReceivedItem(string name) { if (string.IsNullOrEmpty(name) || gottenItems.Contains(name)) { return; } ValheimRandomizer.BossDrop value3; if (ValheimRandomizer.archipelagoToResearch.TryGetValue(name, out var value)) { gottenItems.Add(name); ValheimRandomizer.DoUnlockResearch(value); } else if (name == "Victory") { gottenItems.Add(name); } else if (name.StartsWith("Gather: ", StringComparison.Ordinal)) { if (ValheimRandomizer.dropByLocationName.TryGetValue(name, out var value2)) { string text = "ap_gather_got_" + value2.itemID; if (HasGlobal(text)) { gottenItems.Add(name); } else if (ValheimRandomizer.GrantDropBundle(value2.itemID, value2.amount)) { gottenItems.Add(name); SetGlobal(text); } } } else if (ValheimRandomizer.bossByItemName.TryGetValue(name, out value3)) { string text2 = "ap_boss_got_" + value3.itemID; if (HasGlobal(text2)) { gottenItems.Add(name); return; } int amount = ValheimRandomizer.BossGrantAmount(value3); if (ValheimRandomizer.GrantDropBundle(value3.itemID, amount)) { gottenItems.Add(name); SetGlobal(text2); } } else { gottenItems.Add(name); ValheimRandomizer.Log.LogWarning((object)("Received AP item '" + name + "' but no research mapping was found.")); } } public static void TryFlushPendingLocations() { if (!ReadyToSend()) { return; } if (!SocketIsUsable(session.Socket)) { LoseConnection("Archipelago connection lost. Reconnect to send checks."); } else { if (!WorldLoaded()) { return; } EnsureDropChecksResolved(); TryGetAllItems(); QueueBossKills(); QueueGoalIfBossDown(); List<long> list = new List<long>(); List<string> list2 = new List<string>(); List<long> list3 = new List<long>(); List<string> list4 = new List<string>(); List<long> list5 = new List<long>(); List<string> list6 = new List<string>(); string[] array; try { array = ZoneSystem.instance.GetGlobalKeys().ToArray(); } catch (Exception ex) { ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogWarning((object)("Pending location scan failed: " + ex.Message)); } return; } string[] array2 = array; foreach (string text in array2) { if (text.StartsWith("ap_pending:", StringComparison.Ordinal) || text.StartsWith("ap_sent:", StringComparison.Ordinal)) { RemoveGlobal(text); if (warnedMissingDrops.Add("legacy-spaced-keys")) { ValheimRandomizer.Log.LogWarning((object)"Cleared old spaced Archipelago keys. Those checks were not reliable and will not be resent."); } } else { if (!text.StartsWith("ap_loc_pending_", StringComparison.Ordinal)) { continue; } string text2 = text.Substring("ap_loc_pending_".Length); if (!string.IsNullOrWhiteSpace(text2) && !HasGlobal("ap_loc_sent_" + text2) && ValheimRandomizer.researchToArchipelago.TryGetValue(text2, out var value)) { long num = LookupLocationId(value); if (num > 0) { list.Add(num); list2.Add(text2); } } } } bool sendGoal = HasGlobal("ap_goal_pending") && !HasGlobal("ap_goal_sent"); long goalId = 0L; if (sendGoal) { goalId = LookupLocationId(goalLocationName, "Goal location '" + goalLocationName + "' is not in this seed. Sending goal status anyway."); } array2 = array; foreach (string text3 in array2) { if (!text3.StartsWith("ap_drop_pending_", StringComparison.Ordinal)) { continue; } string text4 = text3.Substring("ap_drop_pending_".Length); if (ValheimRandomizer.dropByItemID.TryGetValue(text4, out var value2) && !HasGlobal("ap_drop_sent_" + text4)) { long num2 = LookupLocationId(value2.locationName); if (num2 > 0) { list3.Add(num2); list4.Add(text4); } } } if (BossDropsEnabled) { array2 = array; foreach (string text5 in array2) { if (!text5.StartsWith("ap_boss_pending_", StringComparison.Ordinal)) { continue; } string text6 = text5.Substring("ap_boss_pending_".Length); if (ValheimRandomizer.bossByDefeatKey.TryGetValue(text6, out var value3) && !HasGlobal("ap_boss_sent_" + text6)) { long num3 = LookupLocationId(value3.locationName); if (num3 > 0) { list5.Add(num3); list6.Add(text6); } } } } if (list.Count == 0 && list3.Count == 0 && list5.Count == 0 && !sendGoal) { return; } ArchipelagoSession captured = session; string[] researchToMark = list2.ToArray(); string[] dropsToMark = list4.ToArray(); string[] bossesToMark = list6.ToArray(); int researchNames = list.Count; int dropNames = list3.Count; int bossNames = list5.Count; string goalName = goalLocationName; long[] locationIds = list.Concat(list3).Concat(list5).ToArray(); int generation = sendGeneration; inflightStartedUtc = DateTime.UtcNow; inflightSuccess = delegate { if (generation == sendGeneration) { for (int j = 0; j < researchToMark.Length; j++) { SetGlobal("ap_loc_sent_" + researchToMark[j]); RemoveGlobal("ap_loc_pending_" + researchToMark[j]); } if (researchNames > 0) { ManualLogSource log2 = ValheimRandomizer.Log; if (log2 != null) { log2.LogInfo((object)("Unlocked " + researchNames + " location(s).")); } } for (int k = 0; k < dropsToMark.Length; k++) { SetGlobal("ap_drop_sent_" + dropsToMark[k]); RemoveGlobal("ap_drop_pending_" + dropsToMark[k]); } if (dropNames > 0) { ManualLogSource log3 = ValheimRandomizer.Log; if (log3 != null) { log3.LogInfo((object)("Unlocked " + dropNames + " drop location(s).")); } } for (int l = 0; l < bossesToMark.Length; l++) { SetGlobal("ap_boss_sent_" + bossesToMark[l]); RemoveGlobal("ap_boss_pending_" + bossesToMark[l]); } if (bossNames > 0) { ManualLogSource log4 = ValheimRandomizer.Log; if (log4 != null) { log4.LogInfo((object)("Unlocked " + bossNames + " boss location(s).")); } } if (sendGoal) { SetGlobal("ap_goal_sent"); RemoveGlobal("ap_goal_pending"); ManualLogSource log5 = ValheimRandomizer.Log; if (log5 != null) { log5.LogInfo((object)("Goal sent: " + goalName)); } } } }; inflight = Task.Run(delegate { SendPending(captured, locationIds, sendGoal, goalId); }); } } private static long LookupLocationId(string locationName, string missingWarning = null) { try { long locationIdFromName = session.Locations.GetLocationIdFromName("Valheim", locationName); if (locationIdFromName > 0) { return locationIdFromName; } if (warnedMissingDrops.Add(locationName)) { ValheimRandomizer.Log.LogWarning((object)(missingWarning ?? ("Location '" + locationName + "' is not in this seed."))); } } catch (Exception ex) { ValheimRandomizer.Log.LogWarning((object)("AP location lookup failed for '" + locationName + "'. Error: " + ex.Message)); } return 0L; } private static void SendPending(ArchipelagoSession target, long[] locationIds, bool sendGoal, long goalId) { if (locationIds.Length != 0) { target.Locations.CompleteLocationChecks(locationIds); } if (sendGoal) { if (goalId > 0) { target.Locations.CompleteLocationChecks(new long[1] { goalId }); } target.SetGoalAchieved(); } } private static bool ReadyToSend() { Task task = inflight; if (task == null) { if (connected) { return session != null; } return false; } if (!task.IsCompleted) { if (DateTime.UtcNow - inflightStartedUtc > SendTimeout) { LoseConnection("Archipelago stopped answering. Reconnect to send checks."); } return false; } if (inflight != task) { if (connected) { return session != null; } return false; } Action action = inflightSuccess; inflight = null; inflightSuccess = null; if (task.IsFaulted || task.IsCanceled) { string text = task.Exception?.GetBaseException()?.Message; LoseConnection(string.IsNullOrEmpty(text) ? "Archipelago connection lost. Reconnect to send checks." : ("Archipelago connection lost. Reconnect to send checks. " + text)); return false; } try { action?.Invoke(); } catch (Exception ex) { ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogWarning((object)("Saving sent checks failed: " + ex.Message)); } } if (connected) { return session != null; } return false; } private static bool WorldLoaded() { if ((Object)(object)ZoneSystem.instance != (Object)null) { return (Object)(object)Player.m_localPlayer != (Object)null; } return false; } private static bool HasGlobal(string k) { if ((Object)(object)ZoneSystem.instance != (Object)null) { return ZoneSystem.instance.GetGlobalKey(k); } return false; } private static void SetGlobal(string k) { if (!((Object)(object)ZoneSystem.instance == (Object)null)) { ZoneSystem.instance.SetGlobalKey(k); } } private static void RemoveGlobal(string k) { if (!((Object)(object)ZoneSystem.instance == (Object)null)) { ZoneSystem.instance.RemoveGlobalKey(k); } } private static void QueueBossKills() { if (!BossDropsEnabled || !ValheimRandomizer.randomized.Value) { return; } foreach (ValheimRandomizer.BossDrop bossDrop in ValheimRandomizer.BossDrops) { if (HasGlobal(bossDrop.defeatKey) && !HasGlobal("ap_boss_sent_" + bossDrop.defeatKey) && !HasGlobal("ap_boss_pending_" + bossDrop.defeatKey)) { ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogInfo((object)("Queue boss location " + bossDrop.locationName)); } SetGlobal("ap_boss_pending_" + bossDrop.defeatKey); } } } private static void QueueGoalIfBossDown() { if (ValheimRandomizer.randomized.Value && !HasGlobal("ap_goal_sent") && !HasGlobal("ap_goal_pending") && HasGlobal(goalDefeatKey)) { SetGlobal("ap_goal_pending"); } } public static void CompleteGame() { if (connected && session != null) { if (!HasGlobal("ap_goal_sent") && !HasGlobal("ap_goal_pending")) { SetGlobal("ap_goal_pending"); } TryFlushPendingLocations(); } } } public class ArchipelagoUI : MonoBehaviour { private Rect _win = new Rect(30f, 80f, 460f, 620f); private int _trophyDrops; private int _researchCosts; private bool _randomized; private bool _anyMaterial; private bool _showUnlocks; private string _host; private string _slot; private string _password; private int _port; private string _status = "Not connected"; private bool _connecting; private float _fejdCheckTimer; private bool _inMainMenu; public void Init() { _randomized = ValheimRandomizer.randomized.Value; _anyMaterial = ValheimRandomizer.anyResearchMaterial.Value; _showUnlocks = ValheimRandomizer.ShowResearchUnlocks; _host = ValheimRandomizer.archipelagoHostname.Value; _port = ValheimRandomizer.archipelagoPort.Value; _slot = ValheimRandomizer.archipelagoSlot.Value; _password = ValheimRandomizer.archipelagoPassword.Value; _trophyDrops = (int)ValheimRandomizer.ActiveTrophyDrops; _researchCosts = (int)ValheimRandomizer.ActiveResearchCosts; } private void Update() { _fejdCheckTimer -= Time.unscaledDeltaTime; if (_fejdCheckTimer <= 0f) { _fejdCheckTimer = 0.5f; _inMainMenu = IsInMainMenu(); } if (!_connecting && !string.IsNullOrEmpty(ArchipelagoConnection.DisconnectMessage)) { _status = ArchipelagoConnection.DisconnectMessage; } } private static bool IsInMainMenu() { return (Object)(object)Object.FindFirstObjectByType<FejdStartup>() != (Object)null; } private void OnGUI() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) if (_inMainMenu) { Color color = GUI.color; GUI.color = Color.white; _win = GUILayout.Window(((Object)this).GetInstanceID(), _win, new WindowFunction(DrawWindow), "Archipelago – Valheim Randomizer", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true) }); GUI.color = color; } } private void DrawWindow(int id) { //IL_0451: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginVertical(Array.Empty<GUILayoutOption>()); GUILayout.Label("Connect to an Archipelago room to enable item/location randomization.", Array.Empty<GUILayoutOption>()); GUILayout.Space(6f); _randomized = GUILayout.Toggle(_randomized, "Randomized?", Array.Empty<GUILayoutOption>()); bool flag = GUILayout.Toggle(_anyMaterial, "Any one found cost item reveals a research", Array.Empty<GUILayoutOption>()); if (flag != _anyMaterial) { _anyMaterial = flag; ValheimRandomizer.SetAnyResearchMaterial(_anyMaterial); } bool flag2 = GUILayout.Toggle(_showUnlocks, "Show what each research unlocks", Array.Empty<GUILayoutOption>()); if (flag2 != _showUnlocks) { _showUnlocks = flag2; ValheimRandomizer.SetShowResearchUnlocks(_showUnlocks); } GUILayout.Space(4f); GUILayout.Label("Research access", Array.Empty<GUILayoutOption>()); bool seedControlsAccess = ValheimRandomizer.SeedControlsAccess; int activeResearchAccess = (int)ValheimRandomizer.ActiveResearchAccess; GUI.enabled = !seedControlsAccess && !_connecting; int num = GUILayout.SelectionGrid(activeResearchAccess, ValheimRandomizer.ResearchAccessLabels, 1, Array.Empty<GUILayoutOption>()); if (!seedControlsAccess && num != activeResearchAccess) { ValheimRandomizer.SetLocalResearchAccess((ValheimRandomizer.ResearchAccessMode)num); } GUI.enabled = true; GUILayout.Label(seedControlsAccess ? "Research access comes from this seed." : "Research access is local until a seed connects.", Array.Empty<GUILayoutOption>()); GUILayout.Space(4f); GUILayout.Label("Trophy drops", Array.Empty<GUILayoutOption>()); int num2 = GUILayout.SelectionGrid(_trophyDrops, ValheimRandomizer.TrophyDropLabels, 3, Array.Empty<GUILayoutOption>()); if (num2 != _trophyDrops) { _trophyDrops = num2; ValheimRandomizer.SetTrophyDrops((ValheimRandomizer.TrophyDropMode)num2); } GUILayout.Label("x2 doubles trophy chance and caps pity at 10. Easy drops the trophy on the first kill, then uses the normal rate. The host rolls these.", Array.Empty<GUILayoutOption>()); GUILayout.Space(4f); GUILayout.Label("Research costs", Array.Empty<GUILayoutOption>()); int num3 = GUILayout.SelectionGrid(_researchCosts, ValheimRandomizer.ResearchCostLabels, 2, Array.Empty<GUILayoutOption>()); if (num3 != _researchCosts) { _researchCosts = num3; ValheimRandomizer.SetResearchCosts((ValheimRandomizer.ResearchCostMode)num3); } GUILayout.Label("Halved spends half, rounded up, and never less than 1.", Array.Empty<GUILayoutOption>()); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label("Host", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }); _host = GUILayout.TextField(_host ?? "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(220f) }); GUILayout.Label("Port", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) }); if (int.TryParse(GUILayout.TextField((_port <= 0) ? "" : _port.ToString(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }), out var result)) { _port = result; } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label("Slot", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }); _slot = GUILayout.TextField(_slot ?? "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(140f) }); GUILayout.Label("Password", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }); _password = GUILayout.PasswordField(_password ?? "", '*', (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(140f) }); GUILayout.EndHorizontal(); GUILayout.Space(6f); GUILayout.Label("Status: " + _status, Array.Empty<GUILayoutOption>()); GUILayout.Label(ArchipelagoConnection.DropChecksEnabled ? "Drop checks: on for this seed" : "Drop checks: off unless the seed enables them", Array.Empty<GUILayoutOption>()); GUILayout.Label(ValheimRandomizer.SequentialTiers ? "Sequential tiers: on for this seed" : "Sequential tiers: off unless the seed enables them", Array.Empty<GUILayoutOption>()); GUILayout.Space(6f); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); bool isConnected = ArchipelagoConnection.IsConnected; GUI.enabled = !_connecting && !isConnected && ValidInputs(); if (GUILayout.Button("Connect", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) })) { SaveInputs(); StartConnect(); } GUI.enabled = isConnected && !_connecting; if (GUILayout.Button("Disconnect", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) })) { Disconnect(); } GUI.enabled = true; GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Space(6f); GUILayout.EndVertical(); GUI.DragWindow(new Rect(0f, 0f, 10000f, 20f)); } private bool ValidInputs() { if (!string.IsNullOrWhiteSpace(_host) && _port > 0 && _port < 65536) { return !string.IsNullOrWhiteSpace(_slot); } return false; } private void SaveInputs() { ValheimRandomizer.randomized.Value = _randomized; ValheimRandomizer.archipelagoHostname.Value = _host ?? ""; ValheimRandomizer.archipelagoPort.Value = _port; ValheimRandomizer.archipelagoSlot.Value = _slot ?? ""; ValheimRandomizer.archipelagoPassword.Value = _password ?? ""; } private async void StartConnect() { if (_connecting || ArchipelagoConnection.IsConnected) { return; } _connecting = true; _status = "Connecting..."; string host = _host; int port = _port; string slot = _slot; string password = _password; try { await Task.Run(delegate { ArchipelagoConnection.Connect(host, port, slot, password); }); _status = "Connected"; ValheimRandomizer.RevealEligibleRecipes(); } catch (Exception ex) { _status = "Error: " + ex.Message; ValheimRandomizer.Log.LogError((object)$"[AP] Connect error: {ex}"); } finally { _connecting = false; } } private void Disconnect() { ArchipelagoConnection.Disconnect(); _status = "Not connected"; ValheimRandomizer.RevealEligibleRecipes(); } } public static class Patches { [HarmonyPatch] private static class Player_HaveRequirements_Piece_Patch { private static IEnumerable<MethodBase> TargetMethods() { return (from m in typeof(Player).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == "HaveRequirements" select m).Where(delegate(MethodInfo m) { ParameterInfo[] parameters = m.GetParameters(); return parameters.Length != 0 && parameters[0].ParameterType == typeof(Piece); }); } private static bool Prefix(Piece piece, ref bool __result) { if ((Object)(object)piece == (Object)null) { return true; } string name = ((Object)piece).name; if (!string.IsNullOrEmpty(name) && RandomizerUtils.CanResearchBeCrafted(name)) { ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogDebug((object)("Blocked building '" + name + "' (missing research).")); } __result = false; return false; } return true; } } [HarmonyPatch] private static class Player_HaveRequirements_Recipe_Patch { private static IEnumerable<MethodBase> TargetMethods() { return (from m in typeof(Player).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == "HaveRequirements" select m).Where(delegate(MethodInfo m) { ParameterInfo[] parameters = m.GetParameters(); return parameters.Length != 0 && parameters[0].ParameterType == typeof(Recipe); }); } private static bool Prefix(Recipe recipe, ref bool __result) { if ((Object)(object)recipe == (Object)null) { return true; } string text = null; try { text = (Object.op_Implicit((Object)(object)recipe.m_item) ? ((Object)recipe.m_item).name : null); } catch { } if (!string.IsNullOrEmpty(text) && RandomizerUtils.CanResearchBeCrafted(text)) { ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogDebug((object)("Blocked crafting '" + text + "' (missing research).")); } __result = false; return false; } return true; } } [HarmonyPatch(typeof(InventoryGui), "UpdateRecipeList", new Type[] { typeof(List<Recipe>) })] public static class InventoryGui_UpdateRecipeList_Patch { private static bool Prefix(InventoryGui __instance, List<Recipe> recipes) { try { ResearchBenchList.ApplySortWeights(); bool flag = ResearchBenchList.IsOpen(); ResearchBenchList.SetUpgradeTabLabel(__instance, flag); if (!flag) { return true; } if (__instance.InCraftTab()) { recipes.RemoveAll(ResearchBenchList.IsCompleted); return true; } return !ResearchBenchList.FillDoneTab(__instance); } catch (Exception ex) { ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogWarning((object)("Research list update failed: " + ex.Message)); } return true; } } } [HarmonyPatch(typeof(InventoryGui), "UpdateRecipe")] public static class InventoryGui_UpdateRecipe_Patch { private static void Postfix(InventoryGui __instance) { try { bool flag = ResearchBenchList.IsOpen(); ResearchBenchList.SetUpgradeTabLabel(__instance, flag); if (flag && !__instance.InCraftTab()) { ResearchBenchList.LockCompletedButton(__instance); } } catch (Exception ex) { ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogWarning((object)("Research bench tab label failed: " + ex.Message)); } } } } private static class ResearchBenchList { private const string DoneLabel = "Done"; private static readonly FieldInfo AvailableRecipesField = AccessTools.Field(typeof(InventoryGui), "m_availableRecipes"); private static readonly FieldInfo SelectedRecipeField = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe"); private static readonly FieldRef<InventoryGui, float> ListBaseSize = AccessTools.FieldRefAccess<InventoryGui, float>("m_recipeListBaseSize"); private static readonly MethodInfo AddRecipeToListMethod = AccessTools.Method(typeof(InventoryGui), "AddRecipeToList", new Type[4] { typeof(Player), typeof(Recipe), typeof(ItemData), typeof(bool) }, (Type[])null); private static readonly MethodInfo UpdateCraftingPanelMethod = AccessTools.Method(typeof(InventoryGui), "UpdateCraftingPanel", new Type[1] { typeof(bool) }, (Type[])null); private static string savedUpgradeLabel; private static bool weightsApplied; private static Dictionary<string, Recipe> researchRecipes; private static TMP_Text[] upgradeTabLabels; private static TMP_Text craftButtonLabel; private static FieldInfo craftTooltipText; private static MonoBehaviour craftTooltipTarget; public static void RefreshOpenList() { if (IsOpen() && !(UpdateCraftingPanelMethod == null)) { InventoryGui instance = InventoryGui.instance; if (Object.op_Implicit((Object)(object)instance)) { UpdateCraftingPanelMethod.Invoke(instance, new object[1] { false }); } } } public static bool IsOpen() { Player localPlayer = Player.m_localPlayer; CraftingStation val = (Object.op_Implicit((Object)(object)localPlayer) ? localPlayer.GetCurrentCraftingStation() : null); if (!Object.op_Implicit((Object)(object)val)) { return false; } if (val.m_name == "Research Bench") { return true; } string text = (Object.op_Implicit((Object)(object)((Component)val).gameObject) ? ((Object)((Component)val).gameObject).name : null); if (!(text == "piece_randomizer_researchbench")) { return text?.StartsWith("piece_randomizer_researchbench", StringComparison.Ordinal) ?? false; } return true; } public static bool IsCompleted(Recipe recipe) { string text = (Object.op_Implicit((Object)(object)recipe?.m_item) ? ((Object)recipe.m_item).name : null); if (!string.IsNullOrEmpty(text) && ValheimRandomizer.researchTier.ContainsKey(text)) { return ValheimRandomizer.IsResearchCrafted(text); } return false; } public static void ApplySortWeights() { if (weightsApplied) { return; } List<Recipe> list = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.m_recipes : null); if (list == null) { return; } int num = 0; foreach (Recipe item in list) { string text = (Object.op_Implicit((Object)(object)item?.m_item) ? ((Object)item.m_item).name : null); if (!string.IsNullOrEmpty(text) && ValheimRandomizer.researchTier.TryGetValue(text, out var value)) { num++; if (item.m_listSortWeight != value) { item.m_listSortWeight = value; } } } if (num > 0 && num >= ValheimRandomizer.research.Count) { weightsApplied = true; } } public static bool FillDoneTab(InventoryGui gui) { if (AddRecipeToListMethod == null || AvailableRecipesField == null || (Object)(object)gui.m_recipeListRoot == (Object)null) { return false; } if (!(AvailableRecipesField.GetValue(gui) is IList list)) { return false; } list.Clear(); RectTransform recipeListRoot = gui.m_recipeListRoot; for (int num = ((Transform)recipeListRoot).childCount - 1; num >= 0; num--) { Object.Destroy((Object)(object)((Component)((Transform)recipeListRoot).GetChild(num)).gameObject); } EnsureRecipeIndex(); List<Recipe> list2 = new List<Recipe>(); if (researchRecipes != null) { foreach (string item in ValheimRandomizer.research) { if (ValheimRandomizer.IsResearchCrafted(item) && researchRecipes.TryGetValue(item, out var value)) { list2.Add(value); } } } list2.Sort(CompareTierThenName); Player localPlayer = Player.m_localPlayer; foreach (Recipe item2 in list2) { AddRecipeToListMethod.Invoke(gui, new object[4] { localPlayer, item2, null, true }); } float num2 = Mathf.Max(ListBaseSize.Invoke(gui), (float)list2.Count * gui.m_recipeListSpace); gui.m_recipeListRoot.SetSizeWithCurrentAnchors((Axis)1, num2); return true; } public static void LockCompletedButton(InventoryGui gui) { if ((Object)(object)gui.m_craftButton == (Object)null || SelectedRecipeField == null) { return; } object value = SelectedRecipeField.GetValue(gui); object? obj = value?.GetType().GetProperty("Recipe")?.GetValue(value); if (IsCompleted((Recipe)((obj is Recipe) ? obj : null))) { ((Selectable)gui.m_craftButton).interactable = false; if ((Object)(object)craftButtonLabel == (Object)null) { craftButtonLabel = ((Component)gui.m_craftButton).GetComponentInChildren<TMP_Text>(); } if ((Object)(object)craftButtonLabel != (Object)null && craftButtonLabel.text != "Done") { craftButtonLabel.text = "Done"; } ClearCraftTooltip((Component)(object)gui.m_craftButton); } } public static void SetUpgradeTabLabel(InventoryGui gui, bool doneTab) { if ((Object)(object)gui?.m_tabUpgrade == (Object)null) { return; } if (upgradeTabLabels == null || upgradeTabLabels.Length == 0) { TMP_Text[] componentsInChildren = ((Component)gui.m_tabUpgrade).GetComponentsInChildren<TMP_Text>(true); if (componentsInChildren != null && componentsInChildren.Length != 0) { upgradeTabLabels = componentsInChildren; } } TMP_Text[] array = upgradeTabLabels; if (array == null || array.Length == 0) { return; } TMP_Text[] array2; if (savedUpgradeLabel == null) { array2 = array; foreach (TMP_Text val in array2) { if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(val.text) && !(val.text == "Done")) { savedUpgradeLabel = val.text; break; } } } string text = (doneTab ? "Done" : (savedUpgradeLabel ?? "Upgrade")); array2 = array; foreach (TMP_Text val2 in array2) { if (!((Object)(object)val2 == (Object)null) && !(val2.text == text) && (savedUpgradeLabel == null || !(val2.text != savedUpgradeLabel) || !(val2.text != "Done"))) { val2.text = text; } } } private static void EnsureRecipeIndex() { if (researchRecipes != null && researchRecipes.Count > 0) { return; } List<Recipe> list = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.m_recipes : null); if (list == null) { return; } researchRecipes = new Dictionary<string, Recipe>(StringComparer.Ordinal); foreach (Recipe item in list) { string text = (Object.op_Implicit((Object)(object)item?.m_item) ? ((Object)item.m_item).name : null); if (!string.IsNullOrEmpty(text) && !researchRecipes.ContainsKey(text) && ValheimRandomizer.researchTier.ContainsKey(text)) { researchRecipes.Add(text, item); } } if (researchRecipes.Count == 0) { researchRecipes = null; } } private static void ClearCraftTooltip(Component button) { if ((Object)(object)button == (Object)null) { return; } if (craftTooltipText == null) { MonoBehaviour[] components = button.GetComponents<MonoBehaviour>(); foreach (MonoBehaviour val in components) { if (!((Object)(object)val == (Object)null) && !(((object)val).GetType().Name != "UITooltip")) { FieldInfo field = ((object)val).GetType().GetField("m_text"); if (field != null && field.FieldType == typeof(string)) { craftTooltipText = field; craftTooltipTarget = val; break; } } } } if (craftTooltipText != null && (Object)(object)craftTooltipTarget != (Object)null) { craftTooltipText.SetValue(craftTooltipTarget, ""); } } private static int CompareTierThenName(Recipe a, Recipe b) { int num = TierOf(a).CompareTo(TierOf(b)); if (num != 0) { return num; } return string.Compare(DisplayName(a), DisplayName(b), StringComparison.OrdinalIgnoreCase); } private static int TierOf(Recipe recipe) { string text = (Object.op_Implicit((Object)(object)recipe?.m_item) ? ((Object)recipe.m_item).name : null); if (!string.IsNullOrEmpty(text) && ValheimRandomizer.researchTier.TryGetValue(text, out var value)) { return value; } if (!((Object)(object)recipe != (Object)null)) { return 0; } return recipe.m_listSortWeight; } private static string DisplayName(Recipe recipe) { return recipe?.m_item?.m_itemData?.m_shared?.m_name ?? ""; } } [HarmonyPatch(typeof(Player), "GetMaxCarryWeight")] private static class Player_GetMaxCarryWeight_Patch { private static void Postfix(Player __instance, ref float __result) { if (!((Object)(object)__instance == (Object)null) && ((Character)__instance).IsPlayer()) { int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.Load); if (boost > 0) { __result += 10 * boost; } } } } [HarmonyPatch] internal static class Player_GetTotalFoodValue_Patch { private static readonly FieldRef<Player, List<Food>> FoodsRef = AccessTools.FieldRefAccess<Player, List<Food>>("m_foods"); private static MethodBase TargetMethod() { Type[] array = new Type[3] { typeof(float).MakeByRefType(), typeof(float).MakeByRefType(), typeof(float).MakeByRefType() }; return AccessTools.Method(typeof(Player), "GetTotalFoodValue", array, (Type[])null); } private static bool Prepare() { if (TargetMethod() != null) { return FoodsRef != null; } return false; } private static void Postfix(Player __instance, ref float hp, ref float stamina, ref float eitr) { if ((Object)(object)__instance == (Object)null) { return; } int num = FoodsRef.Invoke(__instance)?.Count ?? 0; if (num > 0) { int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.HP); if (boost > 0) { hp += 2 * num * boost; } int boost2 = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.Stamina); if (boost2 > 0) { stamina += 2 * num * boost2; } int boost3 = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.Eitr); if (boost3 > 0) { eitr += 5 * num * boost3; } } } } [HarmonyPatch] internal static class Player_AddAdrenaline_Patch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Player), "AddAdrenaline", new Type[1] { typeof(float) }, (Type[])null); } private static bool Prepare() { return TargetMethod() != null; } private static void Prefix(Player __instance, ref float v) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.Adrenaline); if (boost > 0) { v += 3 * boost; } } } } internal static class SEManPatchHelpers { public static readonly FieldRef<SEMan, Character> OwnerRef = AccessTools.FieldRefAccess<SEMan, Character>("m_character"); public static readonly FieldRef<Player, List<Food>> FoodsRef = AccessTools.FieldRefAccess<Player, List<Food>>("m_foods"); public static bool IsLocalPlayer(SEMan se) { Character obj = ((OwnerRef != null) ? OwnerRef.Invoke(se) : null); Player val = (Player)(object)((obj is Player) ? obj : null); if (val != null) { return (Object)(object)val == (Object)(object)Player.m_localPlayer; } return false; } public static int GetActiveFoodCount(Player p) { if (FoodsRef == null || (Object)(object)p == (Object)null) { return 0; } return FoodsRef.Invoke(p)?.Count ?? 0; } } [HarmonyPatch] internal static class SEMan_ModifyHealthRegen_RefOnly_Patch { private static MethodBase TargetMethod() { Type[] array = new Type[1] { typeof(float).MakeByRefType() }; return AccessTools.Method(typeof(SEMan), "ModifyHealthRegen", array, (Type[])null); } private static bool Prepare() { if (TargetMethod() != null && SEManPatchHelpers.OwnerRef != null) { return SEManPatchHelpers.FoodsRef != null; } return false; } private static void Postfix(SEMan __instance, ref float regenMultiplier) { if (!SEManPatchHelpers.IsLocalPlayer(__instance)) { return; } int activeFoodCount = SEManPatchHelpers.GetActiveFoodCount(Player.m_localPlayer); if (activeFoodCount > 0) { int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.HPRegen); if (boost > 0) { regenMultiplier += 0.5f * (float)activeFoodCount * (float)boost; } } } } [HarmonyPatch] internal static class SEMan_ModifyStaminaRegen_RefOnly_Patch { private static MethodBase TargetMethod() { Type[] array = new Type[1] { typeof(float).MakeByRefType() }; return AccessTools.Method(typeof(SEMan), "ModifyStaminaRegen", array, (Type[])null); } private static bool Prepare() { if (TargetMethod() != null && SEManPatchHelpers.OwnerRef != null) { return SEManPatchHelpers.FoodsRef != null; } return false; } private static void Postfix(SEMan __instance, ref float staminaMultiplier) { if (!SEManPatchHelpers.IsLocalPlayer(__instance)) { return; } int activeFoodCount = SEManPatchHelpers.GetActiveFoodCount(Player.m_localPlayer); if (activeFoodCount > 0) { int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.StaminaRegen); if (boost > 0) { staminaMultiplier += 0.5f * (float)activeFoodCount * (float)boost; } } } } [HarmonyPatch] internal static class SEMan_ModifyEitrRegen_RefOnly_Patch { private static MethodBase TargetMethod() { Type[] array = new Type[1] { typeof(float).MakeByRefType() }; return AccessTools.Method(typeof(SEMan), "ModifyEitrRegen", array, (Type[])null); } private static bool Prepare() { if (TargetMethod() != null && SEManPatchHelpers.OwnerRef != null) { return SEManPatchHelpers.FoodsRef != null; } return false; } private static void Postfix(SEMan __instance, ref float eitrMultiplier) { if (!SEManPatchHelpers.IsLocalPlayer(__instance)) { return; } int activeFoodCount = SEManPatchHelpers.GetActiveFoodCount(Player.m_localPlayer); if (activeFoodCount > 0) { int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.EitrRegen); if (boost > 0) { eitrMultiplier += 0.5f * (float)activeFoodCount * (float)boost; } } } } [HarmonyPatch(typeof(Player), "GetJogSpeedFactor")] internal static class Player_GetJogSpeedFactor_Patch { private static bool Prepare() { return AccessTools.Method(typeof(Player), "GetJogSpeedFactor", (Type[])null, (Type[])null) != null; } private static void Postfix(Player __instance, ref float __result) { ApplyMoveSpeed(__instance, ref __result); } } [HarmonyPatch(typeof(Player), "GetRunSpeedFactor")] internal static class Player_GetRunSpeedFactor_Patch { private static bool Prepare() { return AccessTools.Method(typeof(Player), "GetRunSpeedFactor", (Type[])null, (Type[])null) != null; } private static void Postfix(Player __instance, ref float __result) { ApplyMoveSpeed(__instance, ref __result); } } [HarmonyPatch] internal static class SEMan_ModifyRaiseSkill_Patch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(SEMan), "ModifyRaiseSkill", new Type[2] { typeof(SkillType), typeof(float).MakeByRefType() }, (Type[])null); } private static bool Prepare() { if (TargetMethod() != null) { return SEManPatchHelpers.OwnerRef != null; } return false; } private static void Postfix(SEMan __instance, SkillType skill, ref float multiplier) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if ((int)skill != 0 && SEManPatchHelpers.IsLocalPlayer(__instance)) { int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.SkillXp); if (boost > 0) { multiplier += 0.25f * (float)boost; } } } } [HarmonyPatch(typeof(Player), "GetBodyArmor")] internal static class Player_GetBodyArmor_DefenseBoost_Patch { private static void Postfix(Player __instance, ref float __result) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.Defense); if (boost > 0) { float num = 1f + 0.05f * (float)boost; __result *= num; } } } } [HarmonyPatch(typeof(Character), "Damage")] internal static class Character_Damage_AttackBoost_Patch { private static void Prefix(Character __instance, HitData hit) { if (hit == null) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer)) { int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.Attack); if (boost > 0) { float mul = 1f + 0.05f * (float)boost; ScaleDamageTypes(ref hit.m_damage, mul); } } } private static void ScaleDamageTypes(ref DamageTypes dmg, float mul) { dmg.m_damage *= mul; dmg.m_blunt *= mul; dmg.m_slash *= mul; dmg.m_pierce *= mul; dmg.m_fire *= mul; dmg.m_frost *= mul; dmg.m_lightning *= mul; dmg.m_poison *= mul; dmg.m_spirit *= mul; dmg.m_pickaxe *= mul; dmg.m_chop *= mul; } } [HarmonyPatch(typeof(TextsDialog), "AddActiveEffects")] private static class TextsDialog_AddActiveEffects_Patch { private static readonly FieldRef<TextsDialog, List<TextInfo>> TextsRef = AccessTools.FieldRefAccess<TextsDialog, List<TextInfo>>("m_texts"); private static void Prefix(TextsDialog __instance, out int __state) { __state = TextsRef.Invoke(__instance)?.Count ?? (-1); } private static void Postfix(TextsDialog __instance, int __state) { if (__state < 0) { return; } List<TextInfo> list = TextsRef.Invoke(__instance); if (list == null || list.Count != __state + 1 || list.Count == 0) { return; } string text = ValheimRandomizer.BuildTrophyStatusBlock(); if (!string.IsNullOrEmpty(text)) { TextInfo val = list[0]; if (val != null) { val.m_text = (string.IsNullOrEmpty(val.m_text) ? text : (val.m_text.TrimEnd(Array.Empty<char>()) + "\n\n" + text)); } } } } [HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")] private static class CharacterDrop_Pity_Patch { private static void Postfix() { ValheimRandomizer.MarkTrophyPityDirty(); } } [HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")] private static class CharacterDrop_GenerateDropList_Patch { private static void Postfix(List<KeyValuePair<GameObject, int>> __result) { if (ArchipelagoConnection.BossDropsEnabled && __result != null && __result.Count != 0) { __result.RemoveAll((KeyValuePair<GameObject, int> pair) => (Object)(object)pair.Key != (Object)null && ValheimRandomizer.IsBossDropPrefab(((Object)pair.Key).name)); } } } [HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")] private static class CharacterDrop_TrophyHelp_Patch { private const int PityCap = 10; private static void Prefix(CharacterDrop __instance, out float[] __state) { __state = null; if (ValheimRandomizer.ActiveTrophyDrops != ValheimRandomizer.TrophyDropMode.Double || (Object)(object)__instance == (Object)null || __instance.m_drops == null) { return; } float[] array = new float[__instance.m_drops.Count]; bool flag = false; for (int i = 0; i < __instance.m_drops.Count; i++) { Drop val = __instance.m_drops[i]; array[i] = val?.m_chance ?? 0f; if (IsTrophy(val)) { flag = true; val.m_chance *= 2f; ClampPity(val); } } if (flag) { __state = array; } } private static void Postfix(CharacterDrop __instance, List<KeyValuePair<GameObject, int>> __result, float[] __state) { if (__state != null && (Object)(object)__instance != (Object)null && __instance.m_drops != null) { int num = Math.Min(__state.Length, __instance.m_drops.Count); for (int i = 0; i < num; i++) { Drop val = __instance.m_drops[i]; if (IsTrophy(val)) { ClampPity(val); val.m_chance = __state[i]; } } } if (ValheimRandomizer.ActiveTrophyDrops == ValheimRandomizer.TrophyDropMode.Easy) { ForceFirstTrophy(__instance, __result); } } private static bool IsTrophy(Drop drop) { if (drop != null && (Object)(object)drop.m_prefab != (Object)null) { return ValheimRandomizer.trophyByItemID.ContainsKey(((Object)drop.m_prefab).name); } return false; } private static void ClampPity(Drop drop) { if (!((Object)(object)drop.m_prefab == (Object)null) && CharacterDrop.s_pseudoCounter != null) { string name = ((Object)drop.m_prefab).name; ClampOne(name, drop.m_chance); if (drop.m_levelMultiplier) { ClampOne(name, drop.m_chance * 2f); ClampOne(name, drop.m_chance * 4f); } } } private static void ClampOne(string name, float chance) { if (!(chance <= 0f) && !(chance > 0.3f)) { int key = StringExtensionMethods.GetStableHashCode(name) + (int)(chance * 1000f); if (CharacterDrop.s_pseudoCounter.TryGetValue(key, out var value) && value > 10) { CharacterDrop.s_pseudoCounter[key] = 10; } } } private static void ForceFirstTrophy(CharacterDrop dropper, List<KeyValuePair<GameObject, int>> result) { if ((Object)(object)dropper == (Object)null || dropper.m_drops == null || result == null || (Object)(object)ZoneSystem.instance == (Object)null) { return; } for (int i = 0; i < dropper.m_drops.Count; i++) { Drop val = dropper.m_drops[i]; if (!IsTrophy(val)) { continue; } string text = "ap_trophy_first_" + ((Object)val.m_prefab).name; if (ZoneSystem.instance.GetGlobalKey(text)) { continue; } ZoneSystem.instance.SetGlobalKey(text); bool flag = false; for (int j = 0; j < result.Count; j++) { if ((Object)(object)result[j].Key == (Object)(object)val.m_prefab) { flag = true; break; } } if (!flag) { result.Add(new KeyValuePair<GameObject, int>(val.m_prefab, 1)); } } } } public static void RefreshResearchBench() { try { ResearchBenchList.RefreshOpenList(); } catch (Exception ex) { ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogWarning((object)("Research bench refresh failed: " + ex.Message)); } } } private static void ApplyMoveSpeed(Player player, ref float factor) { if (!((Object)(object)player != (Object)(object)Player.m_localPlayer)) { int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.Speed); if (boost > 0) { factor *= 1f + 0.05f * (float)boost; } } } } public static class RandomizerUtils { public static void RegisterResearchBench() { //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Expected O, but got Unknown //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Expected O, but got Unknown //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Expected O, but got Unknown //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Expected O, but got Unknown ValheimRandomizer.Log.LogInfo((object)"RegisterResearchBench: Vanilla prefabs available, cloning Forge…"); GameObject val = PrefabManager.Instance.CreateClonedPrefab("piece_randomizer_researchbench", "forge"); if (!Object.op_Implicit((Object)(object)val)) { ValheimRandomizer.Log.LogError((object)"Failed to clone 'forge'"); return; } (val.GetComponent<Piece>() ?? val.AddComponent<Piece>()).m_craftingStation = null; CraftingStation val2 = val.GetComponent<CraftingStation>() ?? val.AddComponent<CraftingStation>(); GameObject prefab = PrefabManager.Instance.GetPrefab("forge"); if ((Object)(object)prefab != (Object)null) { CraftingStation component = prefab.GetComponent<CraftingStation>(); if ((Object)(object)component != (Object)null) { val2.m_name = "Research Bench"; val2.m_useDistance = component.m_useDistance; val2.m_rangeBuild = component.m_rangeBuild; val2.m_areaMarker = component.m_areaMarker; } } val2.m_craftRequireRoof = false; val2.m_craftRequireFire = false; val2.m_hasCraftTab = true; val2.m_upgrader = false; ValheimRandomizer.ResearchBenchCraftingStation = val2; PieceConfig val3 = new PieceConfig(); val3.Name = "Research Bench"; val3.Description = "Randomizer Research Bench (Forge-like, own recipes)"; val3.PieceTable = "Hammer"; val3.Category = "Randomizer"; val3.Requirements = (RequirementConfig[])(object)new RequirementConfig[2] { new RequirementConfig("Wood", 20, 0, true), new RequirementConfig("Stone", 10, 0, true) }; PieceConfig val4 = val3; PieceManager.Instance.AddPiece(new CustomPiece(val, true, val4)); ValheimRandomizer.Log.LogInfo((object)"Research Bench registered"); } public static void CreateResearch(string iconFromItemPrefab, string researchItemID, string displayName, string description, string[] requiredResearchIDs, RequirementConfig[] researchRecipeRequirements, params string[] gatedItemPrefabIDs) { //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Expected O, but got Unknown //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Expected O, but got Unknown //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Expected O, but got Unknown ValheimRandomizer.Log.LogInfo((object)("Registering research: " + displayName)); if (!Object.op_Implicit((Object)(object)PrefabManager.Instance.GetPrefab("piece_randomizer_researchbench"))) { ValheimRandomizer.Log.LogError((object)"Research bench 'piece_randomizer_researchbench' not found; aborting CreateResearchAndGateRecipes."); return; } string text = "Amber"; if (!Object.op_Implicit((Object)(object)PrefabManager.Instance.GetPrefab(text))) { ValheimRandomizer.Log.LogError((object)"Base item 'Amber' missing; cannot clone research item."); return; } GameObject val = PrefabManager.Instance.CreateClonedPrefab(researchItemID, text); if (!Object.op_Implicit((Object)(object)val)) { ValheimRandomizer.Log.LogError((object)("Failed to clone base item '" + text + "' as '" + researchItemID + "'.")); return; } ItemDrop component = val.GetComponent<ItemDrop>(); if ((Object)(object)component == (Object)null || component.m_itemData == null) { ValheimRandomizer.Log.LogError((object)"Cloned research item is missing ItemDrop/ItemData."); return; } component.m_itemData.m_shared.m_name = displayName; if (requiredResearchIDs != null && requiredResearchIDs.Length != 0) { description = description + "\r\n\r\nRequires: " + string.Join(", ", requiredResearchIDs); } ValheimRandomizer.researchBody[researchItemID] = description; ValheimRandomizer.researchUnlockPrefabs[researchItemID] = ((gatedItemPrefabIDs == null) ? Array.Empty<string>() : ((string[])gatedItemPrefabIDs.Clone())); component.m_itemData.m_shared.m_description = ValheimRandomizer.BuildResearchDescription(researchItemID); GameObject prefab = PrefabManager.Instance.GetPrefab(iconFromItemPrefab); if (Object.op_Implicit((Object)(object)prefab)) { ItemDrop component2 = prefab.GetComponent<ItemDrop>(); if (Object.op_Implicit((Object)(object)component2) && component2.m_itemData?.m_shared?.m_icons != null && component2.m_itemData.m_shared.m_icons.Length != 0) { component.m_itemData.m_shared.m_icons = (Sprite[])(object)new Sprite[1] { component2.m_itemData.m_shared.m_icons[0] }; } else { ValheimRandomizer.Log.LogWarning((object)("Icon source '" + iconFromItemPrefab + "' has no icons; keeping cloned icon.")); } } else { ValheimRandomizer.Log.LogWarning((object)("Icon source item '" + iconFromItemPrefab + "' not found; keeping cloned icon.")); } component.m_itemData.m_shared.m_weight = 0.1f; component.m_itemData.m_shared.m_teleportable = true; component.m_itemData.m_shared.m_maxStackSize = 1; component.m_itemData.m_shared.m_maxQuality = 1; ItemManager.Instance.AddItem(new CustomItem(val, true)); RecipeConfig val2 = new RecipeConfig { Name = "Recipe_" + researchItemID, Item = researchItemID, Amount = 1, CraftingStation = "piece_randomizer_researchbench", Requirements = researchRecipeRequirements }; ItemManager.Instance.AddRecipe(new CustomRecipe(val2)); ValheimRandomizer.Log.LogInfo((object)("Created research item '" + researchItemID + "' and its recipe at 'piece_randomizer_researchbench'.")); ValheimRandomizer.AddResearchRequirement(researchItemID, requiredResearchIDs); for (int i = 0; i < gatedItemPrefabIDs.Length; i++) { ValheimRandomizer.AddResearchRequirement(gatedItemPrefabIDs[i], researchItemID); } ValheimRandomizer.research.Add(researchItemID); ValheimRandomizer.researchToArchipelago.Add(researchItemID, displayName); ValheimRandomizer.archipelagoToResearch.Add(displayName, researchItemID); } public static void CreateTrophyResearch(string itemID, string researchID, ValheimRandomizer.TrophyResearch.TrophyBoost boost) { ValheimRandomizer.TrophyResearch value = new ValheimRandomizer.TrophyResearch(itemID, researchID, boost); ValheimRandomizer.trophyByItemID[itemID] = value; ValheimRandomizer.trophyByResearchID[researchID] = value; ManualLogSource log = ValheimRandomizer.Log; if (log != null) { log.LogInfo((object)$"Registered trophy research: {itemID} -> {researchID} ({boost})."); } ValheimRandomizer.researchToArchipelago.Add(researchID, "Trophy: " + itemID); ValheimRandomizer.archipelagoToResearch.Add("Trophy: " + itemID, researchID); } public static bool CanResearchBeCrafted(string itemID) { if (ValheimRandomizer.research.Contains(itemID)) { if (ValheimRandomizer.IsResearchCrafted(itemID)) { return true; } return !ValheimRandomizer.ResearchRecipeVisible(itemID); } foreach (string item in ValheimRandomizer.GetResearchRequired(itemID)) { if (!ValheimRandomizer.IsResearchUnlocked(item)) { return true; } } return false; } } [BepInPlugin("com.samupo.randomizer", "Randomizer", "0.2.35")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class ValheimRandomizer : BaseUnityPlugin { public enum ResearchAccessMode { Default, Tier, Materials, Natural } public enum TrophyDropMode { Default, Double, Easy } public enum ResearchCostMode { Default, Halved } public class TrophyResearch { public enum TrophyBoost { Load, HP, Stamina, Eitr, HPRegen, StaminaRegen, Attack, Defense, Adrenaline, EitrRegen, Speed, SkillXp } public const int LoadPerTrophy = 10; public const int HpPerFood = 2; public const int StaminaPerFood = 2; public const int EitrPerFood = 5; public const int RegenPercentPerFood = 50; public const int AdrenalinePerTrophy = 3; public const int AttackPercentPerTrophy = 5; public const int DefensePercentPerTrophy = 5; public const int SpeedPercentPerTrophy = 5; public const int SkillXpPercentPerTrophy = 25; public string itemID; public string researchID; public TrophyBoost boost; public TrophyResearch(string itemID, string researchID, TrophyBoost boost) { this.itemID = itemID; this.researchID = researchID; this.boost = boost; } } public class DropCheck { public string itemID; public string locationName; public int amount; public DropCheck(string itemID, string locationName, int amount) { this.itemID = itemID; this.locationName = locationName; this.amount = amount; } } public class BossDrop { public string itemID; public string itemName; public string locationName; public string defeatKey; public string creature; public int amount; public bool onePerPlayer; public BossDrop(string itemID, string itemName, string locationName, string defeatKey, string creature, int amount, bool onePerPlayer) { this.itemID = itemID; this.itemName = itemName; this.locationName = locationName; this.defeatKey = defeatKey; this.creature = creature; this.amount = amount; this.onePerPlayer = onePerPlayer; } } private class TrophyPityInfo { public float baseChance; public readonly List<PityChance> chances = new List<PityChance>(); } private class PityChance { public float chance; public int key; public bool starred; } public const string ModGuid = "com.samupo.randomizer"; public const string ModName = "Randomizer"; public const string ModVersion = "0.2.35"; internal static ConfigEntry<bool> randomized; internal static ConfigEntry<string> archipelagoHostname; internal static ConfigEntry<int> archipelagoPort; internal static ConfigEntry<string> archipelagoSlot; internal static ConfigEntry<string> archipelagoPassword; internal static ConfigEntry<bool> anyResearchMaterial; internal static ConfigEntry<bool> showResearchUnlocks; internal static ConfigEntry<string> researchAccess; internal static ConfigEntry<string> trophyDrops; internal static ConfigEntry<string> researchCosts; public static readonly string[] ResearchAccessLabels = new string[4] { "Default: parent chain", "Tier doors", "Materials only", "Natural: craft opens the next" }; public static readonly string[] TrophyDropLabels = new string[3] { "Default", "x2", "Easy" }; public static readonly string[] ResearchCostLabels = new string[2] { "Default", "Halved" }; public static TrophyDropMode ActiveTrophyDrops = TrophyDropMode.Default; public static ResearchCostMode ActiveResearchCosts = ResearchCostMode.Default; private static readonly Dictionary<int, string[]> TierDoorItems = new Dictionary<int, string[]> { { 1, new string[1] { "Crafting" } }, { 2, new string[5] { "Axes Tier 1", "Troll Armor", "Cooking Station", "Bed Tier 1", "Warmth Tier 1" } }, { 3, new string[4] { "Pickaxes Tier 1", "Boat Tier 1", "Smelter", "Forge" } }, { 4, new string[1] { "Bronze" } }, { 5, new string[1] { "Axes Tier 2" } }, { 6, new string[1] { "Pickaxes Tier 2" } }, { 7, new string[3] { "Pickaxes Tier 3", "Forge Upgrade 1", "Nails Tier 2" } }, { 8, new string[2] { "Artisan Table", "Blast Furnace" } }, { 9, new string[6] { "Axes Tier 4", "Wisp Fountain", "Wisplight", "Forge Upgrade 2", "Forge Upgrade 3", "Spinning Wheel" } }, { 10, new string[1] { "Pickaxes Tier 4" } }, { 11, new string[3] { "Ceramic Plates", "Boat Tier 4", "Artisan Press" } }, { 12, new string[2] { "Siege", "Snow Shovel" } } }; public const string ResearchBenchPrefabName = "piece_randomizer_researchbench"; private Player currentPlayer; public static List<string> research = new List<string>(); public static Dictionary<string, List<string>> recipeRequirements = new Dictionary<string, List<string>>(); public static Dictionary<string, TrophyResearch> trophyByItemID = new Dictionary<string, TrophyResearch>(); public static Dictionary<string, TrophyResearch> trophyByResearchID = new Dictionary<string, TrophyResearch>(StringComparer.Ordinal); public static Dictionary<string, string> researchToArchipelago = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); public static Dictionary<string, string> archipelagoToResearch = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); public static Dictionary<string, int> researchTier = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); public static Dictionary<string, string> researchBody = new Dictionary<string, string>(StringComparer.Ordinal); public static Dictionary<string, string[]> researchUnlockPrefabs = new Dictionary<string, string[]>(StringComparer.Ordinal); public static Dictionary<string, DropCheck> dropByItemID = new Dictionary<string, DropCheck>(StringComparer.OrdinalIgnoreCase); public static Dictionary<string, DropCheck> dropByLocationName = new Dictionary<string, DropCheck>(StringComparer.OrdinalIgnoreCase); public static Dictionary<string, BossDrop> bossByItemName = new Dictionary<string, BossDrop>(StringComparer.Ordinal); public static Dictionary<string, BossDrop> bossByDefeatKey = new Dictionary<string, BossDrop>(StringComparer.Ordinal); public static readonly List<BossDrop> BossDrops = new List<BossDrop>(); private static readonly HashSet<string> bossPrefabs = new HashSet<string>(StringComparer.Ordinal); public static bool SuppressDropChecks; private static Dictionary<string, int> dropSeen = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, int> dropCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); private static readonly List<string> dropMissing = new List<string>(); private static readonly HashSet<string> sentDrops = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static readonly HashSet<string> craftedResearch = new HashSet<string>(StringComparer.Ordinal); private static readonly Dictionary<string, int[]> researchBaseCosts = new Dictionary<string, int[]>(StringComparer.Ordinal); private static readonly List<ItemData> researchConsume = new List<ItemData>(); private static HashSet<string> researchIds; private static int researchIdCount = -1; private static int knownMaterialCount = -1; private static int revealSuppressed; private static bool revealQueued; private static bool inventoryDirty; private static bool scanningInventory; private static bool revealedForPlayer; private static bool dropCatchUpDone; private static bool pityDirty = true; private static bool pityWasConnected; private static bool pityChancesFrozen; private static float pityWait; private static string pitySent; private static Dictionary<string, TrophyPityInfo> pityChances; private static readonly FieldRef<Player, HashSet<string>> KnownRecipesRef = AccessTools.FieldRefAccess<Player, HashSet<string>>("m_knownRecipes"); private static readonly MethodInfo AddKnownRecipeMethod = AccessTools.Method(typeof(Player), "AddKnownRecipe", (Type[])null, (Type[])null); private static readonly MethodInfo AddKnownPieceMethod = AccessTools.Method(typeof(Player), "AddKnownPiece", (Type[])null, (Type[])null); private static readonly MethodInfo UpdatePiecesMethod = AccessTools.Method(typeof(Player), "UpdateAvailablePiecesList", (Type[])null, (Type[])null); private static readonly FieldRef<Player, HashSet<string>> KnownMaterialsRef = AccessTools.FieldRefAccess<Player, HashSet<string>>("m_knownMaterial"); private float time; public static CraftingStation ResearchBenchCraftingStation { get; set; } public static ResearchAccessMode ActiveResearchAccess { get; private set; } = ResearchAccessMode.Default; public static bool SeedControlsAccess { get; private set; } public static bool SequentialTiers { get; private set; } public static ManualLogSource Log { get; private set; } private static string DataFolder => Path.GetDirectoryName(typeof(ValheimRandomizer).Assembly.Location); public static bool ShowResearchUnlocks { get { if (showResearchUnlocks != null) { return showResearchUnlocks.Value; } return false; } } private void Awake() { //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Expected O, but got Unknown randomized = ((BaseUnityPlugin)this).Config.Bind<bool>("Archipelago", "Randomized", false, "Uses Archipelago?"); archipelagoHostname = ((BaseUnityPlugin)this).Config.Bind<string>("Archipelago", "Host", "localhost", "Archipelago server host"); archipelagoPort = ((BaseUnityPlugin)this).Config.Bind<int>("Archipelago", "Port", 38281, "Archipelago server port"); archipelagoSlot = ((BaseUnityPlugin)this).Config.Bind<string>("Archipelago", "Slot", "Player", "Your Archipelago slot name"); archipelagoPassword = ((BaseUnityPlugin)this).Config.Bind<string>("Archipelago", "Password", "", "Archipelago password (if any)"); anyResearchMaterial = ((BaseUnityPlugin)this).Config.Bind<bool>("Archipelago", "AnyResearchMaterial", false, "Off: a research recipe appears after every cost item has been found. On: finding any one cost item reveals the recipe. Crafting always spends the full cost."); showResearchUnlocks = ((BaseUnityPlugin)this).Config.Bind<bool>("Archipelago", "ShowResearchUnlocks", false, "Off: a randomized research says Unlocks: Something?. On: the description lists what that research unlocks."); researchAccess = ((BaseUnityPlugin)this).Config.Bind<string>("Archipelago", "ResearchAccess", "default", "Used until a seed connects. default, tier, materials, or natural."); trophyDrops = ((BaseUnityPlugin)this).Config.Bind<string>("Archipelago", "TrophyDrops", "default", "Trophy drop help on the host that rolls the corpse. default, x2, or easy."); researchCosts = ((BaseUnityPlugin)this).Config.Bind<string>("Archipelago", "ResearchCosts", "default", "Research bench costs. default, or halved."); ActiveResearchAccess = ParseResearchAccess(researchAccess.Value); ActiveTrophyDrops = ParseTrophyDrops(trophyDrops.Value); ActiveResearchCosts = ParseResearchCosts(researchCosts.Value); Log = ((BaseUnityPlugin)this).Logger; PrefabManager.OnVanillaPrefabsAvailable += DoOnPrefabsAvailable; new Harmony("com.samupo.randomizer").PatchAll(); GameObject val = new GameObject("ValheimRandomizer.AP_UI"); Object.DontDestroyOnLoad((Object)val); val.AddComponent<ArchipelagoUI>().Init(); } private void DoOnPrefabsAvailable() { PrefabManager.OnVanillaPrefabsAvailable -= DoOnPrefabsAvailable; RandomizerUtils.RegisterResearchBench(); AddDropChecks(); ((MonoBehaviour)this).Invoke("AddResearchRecipes", 5f); ((MonoBehaviour)this).Invoke("AddTrophyResearches", 5.5f); ((MonoBehaviour)this).Invoke("CheckRecipesAndPiecesWithoutResearch", 10f); } private void Update() { ArchipelagoConnection.Pump(); if ((Object)(object)currentPlayer != (Object)(object)Player.m_localPlayer) { if ((Object)(object)currentPlayer != (Object)null && ((Humanoid)currentPlayer).GetInventory() != null) { Inventory inventory = ((Humanoid)currentPlayer).GetInventory(); inventory.m_onChanged = (Action)Delegate.Remove(inventory.m_onChanged, new Action(OnInventoryChanged)); } currentPlayer = Player.m_localPlayer; revealedForPlayer = false; dropCatchUpDone = false; inventoryDirty = false; knownMaterialCount = MaterialCount(currentPlayer); sentDrops.Clear(); craftedResearch.Clear(); if ((Object)(object)currentPlayer != (Object)null && ((Humanoid)currentPlayer).GetInventory() != null) { Inventory inventory2 = ((Humanoid)currentPlayer).GetInventory(); inventory2.m_onChanged = (Action)Delegate.Combine(inventory2.m_onChanged, new Action(OnInventoryChanged)); SnapshotDropCounts(); } } CatchUpHeldDrops(); if (!revealedForPlayer && (Object)(object)currentPlayer != (Object)null && research.Count > 0) { RevealEligibleRecipes(); revealedForPlayer = true; } if (inventoryDirty) { FlushInventoryChecks(); } time += Time.unscaledDeltaTime; if (time > 2f) { time = 0f; ArchipelagoConnection.TryFlushPendingLocations(); } FlushTrophyPity(); } public static void MarkTrophyPityDirty() { pityDirty = true; } private void FlushTrophyPity() { if (!ArchipelagoConnection.IsConnected) { pityWasConnected = false; pityWait = 0f; return; } if (!pityWasConnected) { pityWasConnected = true; pityDirty = true; pitySent = null; } if (!pityDirty) { return; } pityWait += Time.unscaledDeltaTime; if (pityWait < 0.4f) { return; } pityWait = 0f; pityDirty = false; try { JObject val = SnapshotTrophyPity(); string text = ((JToken)val).ToString((Formatting)0, Array.Empty<JsonConverter>()); if (!(text == pitySent)) { ArchipelagoConnection.PublishPity(val); pitySent = text; } } catch (Exception ex) { pityDirty = true; ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("Trophy pity publish failed: " + ex.Message)); } } } private static JObject SnapshotTrophyPity() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Expected O, but got Unknown //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Expected O, but got Unknown //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Expected O, but got Unknown JObject val = new JObject(); EnsurePityChances(); Dictionary<int, int> s_pseudoCounter = CharacterDrop.s_pseudoCounter; if (s_pseudoCounter == null || pityChances == null) { return val; } List<string> list = new List<string>(pityChances.Keys); list.Sort(StringComparer.Ordinal); for (int i = 0; i < list.Count; i++) { TrophyPityInfo trophyPityInfo = pityChances[list[i]]; int num = 0; float num2 = trophyPityInfo.baseChance; bool flag = false; JArray val2 = new JArray(); for (int j = 0; j < trophyPityInfo.chances.Count; j++) { PityChance pityChance = trophyPityInfo.chances[j]; if (s_pseudoCounter.TryGetValue(pityChance.key, out var value)) { int num3 = Math.Max(1, value); if (ActiveTrophyDrops == TrophyDropMode.Double && num3 > 10) { num3 = 10; } if (!pityChance.starred) { flag = true; num = num3; num2 = pityChance.chance; } else { val2.Add((JToken)new JObject { ["left"] = JToken.op_Implicit(num3), ["chance"] = JToken.op_Implicit(Math.Round(pityChance.chance, 4)) }); } } } if (!flag && ((JContainer)val2).Count == 0) { if (!(trophyPityInfo.baseChance <= 0.3f)) { val[list[i]] = (JToken)new JObject { ["pity"] = JToken.op_Implicit(false), ["chance"] = JToken.op_Implicit(Math.Round(trophyPityInfo.baseChance, 4)) }; } continue; } JObject val3 = new JObject(); if (flag) { val3["left"] = JToken.op_Implicit(num); val3["chance"] = JToken.op_Implicit(Math.Round(num2, 4)); } if (((JContainer)val2).Count > 0) { val3["stars"] = (JToken)(object)val2; } val[list[i]] = (JToken)(object)val3; } return val; } private static void EnsurePityChances() { if (pityChancesFrozen || trophyByItemID.Count == 0) { return; } CharacterDrop[] array = Resources.FindObjectsOfTypeAll<CharacterDrop>(); if (array == null || array.Length == 0) { return; } Dictionary<string, TrophyPityInfo> dictionary = new Dictionary<string, TrophyPityInfo>(StringComparer.Ordinal); foreach (CharacterDrop val in array) { if ((Object)(object)val == (Object)null || val.m_drops == null) { continue; } for (int j = 0; j < val.m_drops.Count; j++) { Drop val2 = val.m_drops[j]; if (val2 == null || (Object)(object)val2.m_prefab == (Object)null) { continue; } string name = ((Object)val2.m_prefab).name; if (!string.IsNullOrEmpty(name) && trophyByItemID.ContainsKey(name)) { if (!dictionary.TryGetValue(name, out var value)) { value = (dictionary[name] = new TrophyPityInfo()); } float num = val2.m_chance; if (ActiveTrophyDrops == TrophyDropMode.Double) { num *= 2f; } value.baseChance = num; AddPityChance(value, name, num, starred: false); if (val2.m_levelMultiplier) { AddPityChance(value, name, num * 2f, starred: true); AddPityChance(value, name, num * 4f, starred: true); } } } } if (dictionary.Count != 0) { pityChances = dictionary; if ((Object)(object)ZNetScene.instance != (Object)null) { pityChancesFrozen = true; } } } private static void AddPityChance(TrophyPityInfo info, string name, float chance, bool starred) { if (chance <= 0f || chance > 0.3f) { return; } int num = StringExtensionMethods.GetStableHashCode(name) + (int)(chance * 1000f); for (int i = 0; i < info.chances.Count; i++) { if (info.chances[i].key == num) { return; } } info.chances.Add(new PityChance { chance = chance, key = num, starred = starred }); } private void OnInventoryChanged() { if (!scanningInventory) { inventoryDirty = true; } } private void FlushInventoryChecks() { if (scanningInventory) { return; } Player val = currentPlayer; Inventory val2 = (((Object)(object)val != (Object)null) ? ((Humanoid)val).GetInventory() : null); if (val2 == null) { inventoryDirty = false; return; } scanningInventory = true; inventoryDirty = false; revealSuppressed++; bool flag = false; bool flag2 = false; try { List<ItemData> allItems = val2.GetAllItems(); HashSet<string> hashSet = ResearchIdSet(); researchConsume.Clear(); bool flag3 = ArchipelagoConnection.DropChecksEnabled && !SuppressDropChecks && dropByItemID.Count > 0; if (flag3) { dropCounts.Clear(); } if (allItems != null) { for (int i = 0; i < allItems.Count; i++) { ItemData val3 = allItems[i]; GameObject val4 = val3?.m_dropPrefab; if ((Object)(object)val4 == (Object)null) { continue; } string name = ((Object)val4).name; if (string.IsNullOrEmpty(name)) { continue; } if (hashSet.Contains(name)) { researchConsume.Add(val3); } if (trophyByItemID.TryGetValue(name, out var value) && !AlreadyCrafted(value.researchID)) { craftedResearch.Add(value.researchID); UnlockResearch(value.researchID); flag = true; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)("Unlocked trophy research '" + value.researchID + "' from '" + name + "'.")); } } if (flag3 && dropByItemID.ContainsKey(name) && !DropAlreadySent(name)) { dropCounts.TryGetValue(name, out var value2); dropCounts[name] = value2 + val3.m_stack; } } } for (int j = 0; j < researchConsume.Count; j++) { ItemData val5 = researchConsume[j]; if (!((Object)(object)val5?.m_dropPrefab == (Object)null)) { string name2 = ((Object)val5.m_dropPrefab).name; bool num = !AlreadyCrafted(name2); craftedResearch.Add(name2); if (num) { UnlockResearch(name2); flag = true; } val2.RemoveItem(val5, 1); flag2 = true; } } researchConsume.Clear(); if (flag3) { ApplyDropCounts(); } if (MaterialsGrew(val)) { flag = true; } } finally { revealSuppressed--; scanningInventory = false; } if (flag || revealQueued) { revealQueued = false; RevealEligibleRecipes(); } if (flag2) { Patches.RefreshResearchBench(); } } private static HashSet<string> ResearchIdSet() { if (researchIds == null || researchIdCount != research.Count) { researchIds = new HashSet<string>(research, StringComparer.Ordinal); researchIdCount = research.Count; } return researchIds; } private static bool AlreadyCrafted(string researchId) { if (string.IsNullOrEmpty(researchId)) { return true; } if (craftedResearch.Contains(researchId)) { return true; } if (!IsResearchCrafted(researchId)) { return false; } craftedResearch.Add(researchId); return true; } private static bool DropAlreadySent(string itemID) { if (sentDrops.Contains(itemID)) { return true; } if ((Object)(object)ZoneSystem.instance == (Object)null) { return false; } if (!ZoneSystem.instance.GetGlobalKey("ap_drop_sent_" + itemID) && !ZoneSystem.instance.GetGlobalKey("ap_drop_pending_" + itemID)) { return false; } sentDrops.Add(itemID); return true; } private static int MaterialCount(Player player) { return (((Object)(object)player != (Object)null) ? KnownMaterialsRef.Invoke(player) : null)?.Count ?? 0; } private static bool MaterialsGrew(Player player) { int num = MaterialCount(player); if (num <= knownMaterialCount) { knownMaterialCount = num; return false; } knownMaterialCount = num; return true; } private static void TallyUnsentDrops(List<ItemData> items) { dropCounts.Clear(); if (items == null) { return; } for (int i = 0; i < items.Count; i++) { ItemData val = items[i]; GameObject val2 = val?.m_dropPrefab; if (!((Object)(object)val2 == (Object)null)) { string name = ((Object)val2).name; if (!string.IsNullOrEmpty(name) && dropByItemID.ContainsKey(name) && !DropAlreadySent(name)) { dropCounts.TryGetValue(name, out var value); dropCounts[name] = value + val.m_stack; } } } } private static void ApplyDropCounts() { foreach (KeyValuePair<string, int> dropCount in dropCounts) { dropSeen.TryGetValue(dropCount.Key, out var value); if (dropCount.Value > value && dropByItemID.TryGetValue(dropCount.Key, out var value2)) { ArchipelagoConnection.SendDropLocation(value2.itemID, value2.locationName); } dropSeen[dropCount.Key] = dropCount.Value; } dropMissing.Clear(); foreach (KeyValuePair<string, int> item in dropSeen) { if (!dropCounts.ContainsKey(item.Key) && !DropAlreadySent(item.Key)) { dropMissing.Add(item.Key); } } for (int i = 0; i < dropMissing.Count; i++) { dropSeen[dropMissing[i]] = 0; } dropMissing.Clear(); } public static void SnapshotDropCounts() { dropSeen.Clear(); Player localPlayer = Player.m_localPlayer; if (((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null) == null || dropByItemID.Count == 0) { return; } TallyUnsentDrops(((Humanoid)localPlayer).GetInventory().GetAllItems()); foreach (KeyValuePair<string, int> dropCount in dropCounts) { dropSeen[dropCount.Key] = dropCount.Value; } } private void CatchUpHeldDrops() { if (dropCatchUpDone || (Object)(object)currentPlayer == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null || dropByItemID.Count == 0 || !ArchipelagoConnection.DropChecksEnabled || randomized == null || !randomized.Value) { return; } dropCatchUpDone = true; TallyUnsentDrops(((Humanoid)currentPlayer).GetInventory().GetAllItems()); int num = 0; foreach (KeyValuePair<string, int> dropCount in dropCounts) { dropSeen[dropCount.Key] = dropCount.Value; if (dropCount.Value > 0) { num++; if (dropByItemID.TryGetValue(dropCount.Key, out var value)) { ArchipelagoConnection.SendDropLocation(value.itemID, value.locationName); } } } ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"Drop catch-up: {num} listed stacks already held."); } } public static bool IsBossDropPrefab(string prefabName) { if (!string.IsNullOrEmpty(prefabName)) { return bossPrefabs.Contains(prefabName); } return false; } public static int BossGrantAmount(BossDrop boss) { int num = boss.amount; int num2 = boss.amount; bool onePerPlayer = boss.onePerPlayer; GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(boss.creature) : null); CharacterDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<CharacterDrop>() : null); if ((Object)(object)val2 != (Object)null && val2.m_drops != null) { for (int i = 0; i < val2.m_drops.Count; i++) { Drop val3 = val2.m_drops[i]; if (val3 != null && !((Object)(object)val3.m_prefab == (Object)null) && string.Equals(((Object)val3.m_prefab).name, boss.itemID, StringComparison.Ordinal)) { num = val3.m_amountMin; num2 = val3.m_amountMax; onePerPlayer = val3.m_onePerPlayer; break; } } } if (num2 < num) { num2 = num; } int num3 = ((num == num2) ? num : Random.Range(num, num2 + 1)); float num4 = ((Game.m_resourceRate > 0f) ? Game.m_resourceRate : 1f); int num5 = Mathf.Max(1, Mathf.RoundToInt((float)num3 * num4)); if (onePerPlayer) { int num6 = 1; if ((Object)(object)ZNet.instance != (Object)null) { num6 = Mathf.Max(1, ZNet.instance.GetNrOfPlayers()); } num5 *= num6; } if (num5 > 100) { num5 = 100; } return num5; } public static bool GrantDropBundle(string itemID, int amount) { Player localPlayer = Player.m_localPlayer; GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectD
Archipelago.MultiClient.Net.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.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.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; 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 Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: ComVisible(false)] [assembly: Guid("35a803ad-85ed-42e9-b1e3-c6b72096f0c1")] [assembly: InternalsVisibleTo("Archipelago.MultiClient.Net.Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] [assembly: TargetFramework(".NETFramework,Version=v4.5", FrameworkDisplayName = ".NET Framework 4.5")] [assembly: AssemblyCompany("Jarno Westhof, Hussein Farran, Zach Parks")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyDescription("A client library for use with .NET based prog-langs for interfacing with Archipelago hosts.")] [assembly: AssemblyFileVersion("6.7.1.0")] [assembly: AssemblyInformationalVersion("6.7.1+0c57591db30f2497b0b4fef87164aa2bbe2e51b2")] [assembly: AssemblyProduct("Archipelago.MultiClient.Net")] [assembly: AssemblyTitle("Archipelago.MultiClient.Net")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/ArchipelagoMW/Archipelago.MultiClient.Net")] [assembly: AssemblyVersion("6.7.1.0")] internal interface IConcurrentHashSet<T> { bool TryAdd(T item); bool Contains(T item); void UnionWith(T[] otherSet); T[] ToArray(); ReadOnlyCollection<T> AsToReadOnlyCollection(); ReadOnlyCollection<T> AsToReadOnlyCollectionExcept(IConcurrentHashSet<T> otherSet); } public class AttemptingStringEnumConverter : StringEnumConverter { public AttemptingStringEnumConverter() { } public AttemptingStringEnumConverter(Type namingStrategyType) : base(namingStrategyType) { } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { try { return ((StringEnumConverter)this).ReadJson(reader, objectType, existingValue, serializer); } catch (JsonSerializationException) { return objectType.IsValueType ? Activator.CreateInstance(objectType) : null; } } } 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; } IHintsHelper Hints { get; } Task<RoomInfoPacket> ConnectAsync(); Task<LoginResult> 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<LoginResult> loginResultTask = new TaskCompletionSource<LoginResult>(); private TaskCompletionSource<RoomInfoPacket> roomInfoPacketTask = new TaskCompletionSource<RoomInfoPacket>(); 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; } public IHintsHelper Hints { get; } internal ArchipelagoSession(IArchipelagoSocketHelper socket, IReceivedItemsHelper items, ILocationCheckHelper locations, IPlayerHelper players, IRoomStateHelper roomState, ConnectionInfoHelper connectionInfoHelper, IDataStorageHelper dataStorage, IMessageLogHelper messageLog, IHintsHelper createHints) { Socket = socket; Items = items; Locations = locations; Players = players; RoomState = roomState; connectionInfo = connectionInfoHelper; DataStorage = dataStorage; MessageLog = messageLog; Hints = createHints; 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); } } else { loginResultTask.TrySetResult(LoginResult.FromPacket(packet)); } } public Task<RoomInfoPacket> ConnectAsync() { roomInfoPacketTask = new TaskCompletionSource<RoomInfoPacket>(); 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<LoginResult> 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<LoginResult>(); 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<T>(TaskCompletionSource<T> 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<RoomInfoPacket> 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 roomStateHelper = new RoomStateHelper(socket, locationCheckHelper); DataStorageHelper dataStorageHelper = new DataStorageHelper(socket, connectionInfoHelper); MessageLogHelper messageLog = new MessageLogHelper(socket, itemInfoResolver, playerHelper, connectionInfoHelper); HintsHelper createHints = new HintsHelper(socket, playerHelper, locationCheckHelper, roomStateHelper, dataStorageHelper); return new ArchipelagoSession(socket, items, locationCheckHelper, playerHelper, roomStateHelper, connectionInfoHelper, dataStorageHelper, messageLog, createHints); } 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<string, object> 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<TA, TB> : IEnumerable<KeyValuePair<TB, TA>>, IEnumerable { private readonly Dictionary<TA, TB> aToB = new Dictionary<TA, TB>(); private readonly Dictionary<TB, TA> bToA = new Dictionary<TB, TA>(); 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<KeyValuePair<TB, TA>> 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<string> Games { get; set; } = new List<string>(); [JsonProperty("slots")] public List<int> Slots { get; set; } = new List<int>(); [JsonProperty("tags")] public List<string> Tags { get; set; } = new List<string>(); [JsonProperty("data")] public Dictionary<string, JToken> 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<string, object> SlotData { get; set; } [JsonProperty("slot_info")] public Dictionary<int, NetworkSlot> 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(AttemptingStringEnumConverter))] 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 CreateHintsPacket : ArchipelagoPacketBase { public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.CreateHints; [JsonProperty("locations")] public long[] Locations { get; set; } [JsonProperty("player")] public int Player { get; set; } [JsonProperty("status")] public HintStatus Status { 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(AttemptingStringEnumConverter))] 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<string, JToken> 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<string, Permissions> 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<string, string> 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<string, Permissions> Permissions { get; set; } = new Dictionary<string, Permissions>(); [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<string, JToken> 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<Color> { 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<string, GameData> Games { get; set; } = new Dictionary<string, GameData>(); } public class DataStorageElement { internal DataStorageElementContext Context; internal List<OperationSpecification> Operations = new List<OperationSpecification>(0); internal DataStorageHelper.DataStorageUpdatedHandler Callbacks; internal Dictionary<string, JToken> AdditionalArguments = new Dictionary<string, JToken>(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<OperationSpecification>(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<bool> l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List<int> l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List<long> l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List<decimal> l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List<double> l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List<float> l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List<string> l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator DataStorageElement(List<object> l) { return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l)); } public static implicit operator bool(DataStorageElement e) { return RetrieveAndReturnBoolValue<bool>(e); } public static implicit operator bool?(DataStorageElement e) { return RetrieveAndReturnBoolValue<bool?>(e); } public static implicit operator int(DataStorageElement e) { return RetrieveAndReturnDecimalValue<int>(e); } public static implicit operator int?(DataStorageElement e) { return RetrieveAndReturnDecimalValue<int?>(e); } public static implicit operator long(DataStorageElement e) { return RetrieveAndReturnDecimalValue<long>(e); } public static implicit operator long?(DataStorageElement e) { return RetrieveAndReturnDecimalValue<long?>(e); } public static implicit operator float(DataStorageElement e) { return RetrieveAndReturnDecimalValue<float>(e); } public static implicit operator float?(DataStorageElement e) { return RetrieveAndReturnDecimalValue<float?>(e); } public static implicit operator double(DataStorageElement e) { return RetrieveAndReturnDecimalValue<double>(e); } public static implicit operator double?(DataStorageElement e) { return RetrieveAndReturnDecimalValue<double?>(e); } public static implicit operator decimal(DataStorageElement e) { return RetrieveAndReturnDecimalValue<decimal>(e); } public static implicit operator decimal?(DataStorageElement e) { return RetrieveAndReturnDecimalValue<decimal?>(e); } public static implicit operator string(DataStorageElement e) { return RetrieveAndReturnStringValue(e); } public static implicit operator bool[](DataStorageElement e) { return RetrieveAndReturnArrayValue<bool[]>(e); } public static implicit operator int[](DataStorageElement e) { return RetrieveAndReturnArrayValue<int[]>(e); } public static implicit operator long[](DataStorageElement e) { return RetrieveAndReturnArrayValue<long[]>(e); } public static implicit operator decimal[](DataStorageElement e) { return RetrieveAndReturnArrayValue<decimal[]>(e); } public static implicit operator double[](DataStorageElement e) { return RetrieveAndReturnArrayValue<double[]>(e); } public static implicit operator float[](DataStorageElement e) { return RetrieveAndReturnArrayValue<float[]>(e); } public static implicit operator string[](DataStorageElement e) { return RetrieveAndReturnArrayValue<string[]>(e); } public static implicit operator object[](DataStorageElement e) { return RetrieveAndReturnArrayValue<object[]>(e); } public static implicit operator List<bool>(DataStorageElement e) { return RetrieveAndReturnArrayValue<List<bool>>(e); } public static implicit operator List<int>(DataStorageElement e) { return RetrieveAndReturnArrayValue<List<int>>(e); } public static implicit operator List<long>(DataStorageElement e) { return RetrieveAndReturnArrayValue<List<long>>(e); } public static implicit operator List<decimal>(DataStorageElement e) { return RetrieveAndReturnArrayValue<List<decimal>>(e); } public static implicit operator List<double>(DataStorageElement e) { return RetrieveAndReturnArrayValue<List<double>>(e); } public static implicit operator List<float>(DataStorageElement e) { return RetrieveAndReturnArrayValue<List<float>>(e); } public static implicit operator List<string>(DataStorageElement e) { return RetrieveAndReturnArrayValue<List<string>>(e); } public static implicit operator List<object>(DataStorageElement e) { return RetrieveAndReturnArrayValue<List<object>>(e); } public static implicit operator Array(DataStorageElement e) { return RetrieveAndReturnArrayValue<Array>(e); } public static implicit operator JArray(DataStorageElement e) { return RetrieveAndReturnArrayValue<JArray>(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<BigInteger>(e); } public static implicit operator BigInteger?(DataStorageElement e) { return RetrieveAndReturnBigIntegerValue<BigInteger?>(e); } private static T RetrieveAndReturnBigIntegerValue<T>(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<T>() ? 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<T>()) { bigInteger = Activator.CreateInstance<BigInteger>(); } 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<T>() ? 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<T> GetAsync<T>() { return GetAsync().ContinueWith((Task<JToken> r) => r.Result.ToObject<T>()); } public Task<JToken> GetAsync() { return Context.GetAsync(Context.Key); } private static T RetrieveAndReturnArrayValue<T>(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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Invalid comparison between Unknown and I4 //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Invalid comparison between Unknown and I4 //IL_00d8: Unknown result type (might be due to invalid IL or missing references) if (e.cachedValue != null) { return ((JToken)(JArray)e.cachedValue).ToObject<T>(); } JArray val = (JArray)(((object)e.Context.GetData(e.Context.Key).ToObject<JArray>()) ?? ((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<JArray>()) ?? ((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<T>(); } 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_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: 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<T>(DataStorageElement e) { if (e.cachedValue != null) { return e.cachedValue.ToObject<T>(); } bool? flag = e.Context.GetData(e.Context.Key).ToObject<bool?>() ?? ((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<T>() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T)); } private static T RetrieveAndReturnDecimalValue<T>(DataStorageElement e) { if (e.cachedValue != null) { return e.cachedValue.ToObject<T>(); } decimal? num = e.Context.GetData(e.Context.Key).ToObject<decimal?>(); if (!num.HasValue && !IsNullable<T>()) { num = Activator.CreateInstance<decimal>(); } 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<T>() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T)); } private static bool IsNullable<T>() { if (typeof(T).IsGenericType) { return typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition(); } return false; } public T To<T>() { if (Operations.Count != 0) { throw new InvalidOperationException("DataStorageElement.To<T>() cannot be used together with other operations on the DataStorageElement"); } return Context.GetData(Context.Key).ToObject<T>(); } 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<string, DataStorageHelper.DataStorageUpdatedHandler> AddHandler { get; set; } internal Action<string, DataStorageHelper.DataStorageUpdatedHandler> RemoveHandler { get; set; } internal Func<string, JToken> GetData { get; set; } internal Action<string, JToken> Initialize { get; set; } internal Func<string, Task<JToken>> GetAsync { get; set; } public override string ToString() { return "Key: " + Key; } } public class GameData { [JsonProperty("location_name_to_id")] public Dictionary<string, long> LocationLookup { get; set; } = new Dictionary<string, long>(); [JsonProperty("item_name_to_id")] public Dictionary<string, long> ItemLookup { get; set; } = new Dictionary<string, long>(); [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(AttemptingStringEnumConverter), new object[] { typeof(SnakeCaseNamingStrategy) })] public JsonMessagePartType? Type { get; set; } [JsonProperty("color")] [JsonConverter(typeof(AttemptingStringEnumConverter), 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(AttemptingStringEnumConverter), 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<SerializableItemInfo>(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<T>(Palette<T> 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<ClientWebSocket>, 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<Exception> errors = new List<Exception>(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<T> where T : WebSocket { private static readonly ArchipelagoPacketConverter Converter = new ArchipelagoPacketConverter(); private readonly BlockingCollection<Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>>> sendQueue = new BlockingCollection<Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>>>(); 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<Task?>)PollingLoop); Task.Run((Func<Task?>)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<string> ReadMessageAsync(byte[] buffer) { using MemoryStream readStream = new MemoryStream(buffer.Length); WebSocketReceiveResult result; do { result = await Socket.ReceiveAsync(new ArraySegment<byte>(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<ArchipelagoPacketBase> { packet }); } public void SendMultiplePackets(List<ArchipelagoPacketBase> packets) { SendMultiplePackets(packets.ToArray()); } public void SendMultiplePackets(params ArchipelagoPacketBase[] packets) { SendMultiplePacketsAsync(packets).Wait(); } public Task SendPacketAsync(ArchipelagoPacketBase packet) { return SendMultiplePacketsAsync(new List<ArchipelagoPacketBase> { packet }); } public Task SendMultiplePacketsAsync(List<ArchipelagoPacketBase> packets) { return SendMultiplePacketsAsync(packets.ToArray()); } public Task SendMultiplePacketsAsync(params ArchipelagoPacketBase[] packets) { TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>(); foreach (ArchipelagoPacketBase item in packets) { sendQueue.Add(new Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>>(item, taskCompletionSource)); } return taskCompletionSource.Task; } private async Task HandleSendBuffer() { List<ArchipelagoPacketBase> list = new List<ArchipelagoPacketBase>(); List<TaskCompletionSource<bool>> tasks = new List<TaskCompletionSource<bool>>(); Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>> tuple = sendQueue.Take(); list.Add(tuple.Item1); tasks.Add(tuple.Item2); Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>> 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<byte>(messageBuffer, num, num2), WebSocketMessageType.Text, endOfMessage, CancellationToken.None); } foreach (TaskCompletionSource<bool> 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<ArchipelagoPacketBase> list = null; try { list = JsonConvert.DeserializeObject<List<ArchipelagoPacketBase>>(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<string, JToken> additionalArguments); private readonly Dictionary<string, DataStorageUpdatedHandler> onValueChangedEventHandlers = new Dictionary<string, DataStorageUpdatedHandler>(); private readonly Dictionary<Guid, DataStorageUpdatedHandler> operationSpecificCallbacks = new Dictionary<Guid, DataStorageUpdatedHandler>(); private readonly Dictionary<string, TaskCompletionSource<JToken>> asyncRetrievalTasks = new Dictionary<string, TaskCompletionSource<JToken>>(); 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_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: 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 == 8 && ((string)setReplyPacket.AdditionalArguments["Reference"]).TryParseNGuid(out var g) && operationSpecificCallbacks.TryGetValue(g, out var value)) { value(setReplyPacket.OriginalValue, setReplyPacket.Value, setReplyPacket.AdditionalArguments); operationSpecificCallbacks.Remove(g); } if (onValueChangedEventHandlers.TryGetValue(setReplyPacket.Key, out var value2)) { value2(setReplyPacket.OriginalValue, setReplyPacket.Value, setReplyPacket.AdditionalArguments); } } return; } foreach (KeyValuePair<string, JToken> datum in retrievedPacket.Data) { if (asyncRetrievalTasks.TryGetValue(datum.Key, out var value3)) { value3.TrySetResult(datum.Value); asyncRetrievalTasks.Remove(datum.Key); } } } private Task<JToken> GetAsync(string key) { if (asyncRetrievalTasks.TryGetValue(key, out var value)) { return value.Task; } TaskCompletionSource<JToken> taskCompletionSource = new TaskCompletionSource<JToken>(); 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<JToken> 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<string, JToken> dictionary = e.AdditionalArguments ?? new Dictionary<string, JToken>(0); if (e.Callbacks != null) { Guid key2 = Guid.NewGuid(); operationSpecificCallbacks[key2] = e.Callbacks; dictionary["Reference"] = JToken.op_Implicit(key2.ToString("N")); 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<string, DataStorageUpdatedHandler> 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<string, DataStorageUpdatedHandler> 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<Hint[]>(); } public Task<Hint[]> GetHintsAsync(int? slot = null, int? team = null) { return GetHintsElement(slot, team).GetAsync<Hint[]>(); } public void TrackHints(Action<Hint[]> onHintsUpdated, bool retrieveCurrentlyUnlockedHints = true, int? slot = null, int? team = null) { GetHintsElement(slot, team).OnValueChanged += delegate(JToken _, JToken newValue, Dictionary<string, JToken> x) { onHintsUpdated(newValue.ToObject<Hint[]>()); }; if (retrieveCurrentlyUnlockedHints) { GetHintsAsync(slot, team).ContinueWith(delegate(Task<Hint[]> t) { onHintsUpdated(t.Result); }); } } public Dictionary<string, object> GetSlotData(int? slot = null) { return GetSlotData<Dictionary<string, object>>(slot); } public T GetSlotData<T>(int? slot = null) where T : class { return GetSlotDataElement(slot).To<T>(); } public Task<Dictionary<string, object>> GetSlotDataAsync(int? slot = null) { return GetSlotDataAsync<Dictionary<string, object>>(slot); } public Task<T> GetSlotDataAsync<T>(int? slot = null) where T : class { return GetSlotDataElement(slot).GetAsync<T>(); } public Dictionary<string, string[]> GetItemNameGroups(string game = null) { return GetItemNameGroupsElement(game).To<Dictionary<string, string[]>>(); } public Task<Dictionary<string, string[]>> GetItemNameGroupsAsync(string game = null) { return GetItemNameGroupsElement(game).GetAsync<Dictionary<string, string[]>>(); } public Dictionary<string, string[]> GetLocationNameGroups(string game = null) { return GetLocationNameGroupsElement(game).To<Dictionary<string, string[]>>(); } public Task<Dictionary<string, string[]>> GetLocationNameGroupsAsync(string game = null) { return GetLocationNameGroupsElement(game).GetAsync<Dictionary<string, string[]>>(); } public ArchipelagoClientState GetClientStatus(int? slot = null, int? team = null) { return GetClientStatusElement(slot, team).To<ArchipelagoClientState?>().GetValueOrDefault(); } public Task<ArchipelagoClientState> GetClientStatusAsync(int? slot = null, int? team = null) { return GetClientStatusElement(slot, team).GetAsync<ArchipelagoClientState?>().ContinueWith((Task<ArchipelagoClientState?> r) => r.Result.GetValueOrDefault()); } public void TrackClientStatus(Action<ArchipelagoClientState> onStatusUpdated, bool retrieveCurrentClientStatus = true, int? slot = null, int? team = null) { Get