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.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Archipelago.MultiClient.Net; using Archipelago.MultiClient.Net.BounceFeatures.DeathLink; using Archipelago.MultiClient.Net.Colors; using Archipelago.MultiClient.Net.ConcurrentCollection; using Archipelago.MultiClient.Net.Converters; using Archipelago.MultiClient.Net.DataPackage; using Archipelago.MultiClient.Net.Enums; using Archipelago.MultiClient.Net.Exceptions; using Archipelago.MultiClient.Net.Extensions; using Archipelago.MultiClient.Net.Helpers; using Archipelago.MultiClient.Net.MessageLog.Messages; using Archipelago.MultiClient.Net.MessageLog.Parts; using Archipelago.MultiClient.Net.Models; using Archipelago.MultiClient.Net.Packets; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Mirror; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; 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: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.3.7.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } 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; } } } internal interface IConcurrentHashSet { bool TryAdd(T item); bool Contains(T item); void UnionWith(T[] otherSet); T[] ToArray(); ReadOnlyCollection AsToReadOnlyCollection(); ReadOnlyCollection AsToReadOnlyCollectionExcept(IConcurrentHashSet otherSet); } namespace System.Diagnostics.CodeAnalysis { [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, Inherited = false)] internal sealed class AllowNullAttribute : Attribute { } [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] internal class DoesNotReturnIfAttribute : Attribute { public bool ParameterValue { get; } public DoesNotReturnIfAttribute(bool parameterValue) { ParameterValue = parameterValue; } } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, Inherited = false)] internal sealed class MaybeNullAttribute : Attribute { } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true)] internal sealed class NotNullAttribute : Attribute { } [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] internal sealed class NotNullWhenAttribute : Attribute { public bool ReturnValue { get; } public NotNullWhenAttribute(bool returnValue) { ReturnValue = returnValue; } } } namespace AtlyssArchipelagoWIP { public static class ArchipelagoAchievementDetection { public static int CountIllusionStonesInInventory() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown Player mainPlayer = Player._mainPlayer; if ((Object)mainPlayer == (Object)null) { return 0; } PlayerInventory component = ((Component)mainPlayer).GetComponent(); if ((Object)component == (Object)null) { return 0; } if (!(AccessTools.Field(typeof(PlayerInventory), "_heldItems")?.GetValue(component) is List list)) { return 0; } int num = 0; foreach (ItemData item in list) { if (item != null && !string.IsNullOrEmpty(item._itemName) && item._itemName.IndexOf("Illusion Stone", StringComparison.OrdinalIgnoreCase) >= 0) { num += Mathf.Max(1, item._quantity); } } return num; } } public static class ArchipelagoDungeonClearDetection { private static readonly Dictionary TriggerTagToClearingCheck = new Dictionary(StringComparer.Ordinal) { { "ATLYSS_ACHIEVEMENT_01", "Clearing Catacombs (1-6)" }, { "ATLYSS_ACHIEVEMENT_02", "Clearing Catacombs (6-12)" }, { "ATLYSS_ACHIEVEMENT_03", "Clearing Catacombs (12-18)" }, { "ATLYSS_ACHIEVEMENT_07", "Clearing Grove (15-20)" }, { "ATLYSS_ACHIEVEMENT_13", "Clearing Grove (20-25)" } }; public static string TryGetClearingAchievementCheckFromTriggerTag(string triggerTag) { if (string.IsNullOrEmpty(triggerTag)) { return null; } if (!TriggerTagToClearingCheck.TryGetValue(triggerTag, out string value)) { return null; } return value; } public static string TryGetClearingAchievementCheck(string sceneOrMapName, int difficultyIndex) { if (string.IsNullOrEmpty(sceneOrMapName) || difficultyIndex < 0 || difficultyIndex > 2) { return null; } bool flag = IsSanctumCatacombsScene(sceneOrMapName); bool flag2 = IsCrescentGroveScene(sceneOrMapName); if (!flag && !flag2) { return null; } if (flag) { return difficultyIndex switch { 0 => "Clearing Catacombs (1-6)", 1 => "Clearing Catacombs (6-12)", 2 => "Clearing Catacombs (12-18)", _ => null, }; } return difficultyIndex switch { 0 => "Clearing Grove (15-20)", 1 => "Clearing Grove (15-20)", 2 => "Clearing Grove (20-25)", _ => null, }; } public static string ResolveSceneToken(MapInstance mapInstance, PatternInstanceManager patternInstance) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if ((Object)mapInstance != (Object)null) { if (!string.IsNullOrEmpty(mapInstance._mapName)) { return mapInstance._mapName; } Scene loadedScene = mapInstance._loadedScene; if (((Scene)(ref loadedScene)).IsValid() && !string.IsNullOrEmpty(((Scene)(ref loadedScene)).path)) { return ((Scene)(ref loadedScene)).path; } if (((Scene)(ref loadedScene)).IsValid() && !string.IsNullOrEmpty(((Scene)(ref loadedScene)).name)) { return ((Scene)(ref loadedScene)).name; } } if ((Object)patternInstance != (Object)null) { Scene scene = ((Component)patternInstance).gameObject.scene; return ((Scene)(ref scene)).name; } return null; } internal static bool IsSanctumCatacombsScene(string sceneOrMapName) { if (sceneOrMapName.IndexOf("sanctumCatacombs", StringComparison.OrdinalIgnoreCase) < 0 && sceneOrMapName.IndexOf("Sanctum Catacombs", StringComparison.OrdinalIgnoreCase) < 0) { return sceneOrMapName.IndexOf("map_dungeon00", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } internal static bool IsCrescentGroveScene(string sceneOrMapName) { if (sceneOrMapName.IndexOf("crescentGrove", StringComparison.OrdinalIgnoreCase) < 0 && sceneOrMapName.IndexOf("Crescent Grove", StringComparison.OrdinalIgnoreCase) < 0) { return sceneOrMapName.IndexOf("map_dungeon01", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } } public static class ArchipelagoDungeonFloorAccess { public static bool IsSanctumCatacombsFloorUnlocked(AtlyssArchipelagoPlugin plugin, int floorIndex) { if ((Object)(object)plugin == (Object)null || !plugin.connected) { return true; } return floorIndex switch { 0 => HasPortalKey(plugin, "Sanctum Catacombs lvl 1 Portal"), 1 => HasPortalKey(plugin, "Sanctum Catacombs lvl 1 Portal") && HasPortalKey(plugin, "Sanctum Catacombs lvl 2 Portal"), 2 => HasPortalKey(plugin, "Sanctum Catacombs lvl 1 Portal") && HasPortalKey(plugin, "Sanctum Catacombs lvl 2 Portal") && HasPortalKey(plugin, "Sanctum Catacombs lvl 3 Portal"), _ => false, }; } public static bool IsCrescentGroveFloorUnlocked(AtlyssArchipelagoPlugin plugin, int floorIndex) { if ((Object)(object)plugin == (Object)null || !plugin.connected) { return true; } return floorIndex switch { 0 => HasPortalKey(plugin, "Crescent Grove lvl 1 Portal"), 1 => HasPortalKey(plugin, "Crescent Grove lvl 2 Portal"), _ => false, }; } private static bool HasPortalKey(AtlyssArchipelagoPlugin plugin, string portalKey) { bool received; if (plugin.randomPortalsEnabled) { return plugin.TryGetPortalReceived(portalKey, out received) && received; } return ArchipelagoProgressivePortalAccess.HasProgressivePortalUnlockedViaOrder(plugin, portalKey); } } public static class ArchipelagoGameDataTables { public readonly struct ProgressivePortalRequirement { public readonly int SanctumCount; public readonly int TuulCount; public ProgressivePortalRequirement(int sanctumCount, int tuulCount) { SanctumCount = sanctumCount; TuulCount = tuulCount; } } public const string CrescentRoadPortalKey = "Crescent Road Portal"; public const string CrescentKeepPortalKey = "Crescent Keep Portal"; public const string CrescentGroveLevel1PortalKey = "Crescent Grove lvl 1 Portal"; public const string CrescentGroveLevel2PortalKey = "Crescent Grove lvl 2 Portal"; public const string SanctumCatacombsLevel1PortalKey = "Sanctum Catacombs lvl 1 Portal"; public const string SanctumCatacombsLevel2PortalKey = "Sanctum Catacombs lvl 2 Portal"; public const string SanctumCatacombsLevel3PortalKey = "Sanctum Catacombs lvl 3 Portal"; public const int ProgressiveSanctumPortalItemCount = 11; public const int ProgressiveTuulPortalItemCount = 3; public const string ProgressiveSanctumPortalItemName = "Progressive Sanctum Portal"; public const string ProgressiveTuulPortalItemName = "Progressive Tuul Portal"; private const string SanctumCatacombsScene = "Assets/Scenes/map_dungeon00_sanctumCatacombs.unity"; public static readonly IReadOnlyDictionary PortalScenes = new Dictionary { { "Sanctum Portal", "Assets/Scenes/00_zone_forest/_zone00_sanctum.unity" }, { "Outer Sanctum Portal", "Assets/Scenes/00_zone_forest/_zone00_outerSanctum.unity" }, { "Arcwood Pass Portal", "Assets/Scenes/00_zone_forest/_zone00_arcwoodPass.unity" }, { "Effold Terrace Portal", "Assets/Scenes/00_zone_forest/_zone00_effoldTerrace.unity" }, { "Tuul Valley Portal", "Assets/Scenes/00_zone_forest/_zone00_tuulValley.unity" }, { "Sanctum Catacombs lvl 1 Portal", "Assets/Scenes/map_dungeon00_sanctumCatacombs.unity" }, { "Sanctum Catacombs lvl 2 Portal", "Assets/Scenes/map_dungeon00_sanctumCatacombs.unity" }, { "Sanctum Catacombs lvl 3 Portal", "Assets/Scenes/map_dungeon00_sanctumCatacombs.unity" }, { "Crescent Road Portal", "Assets/Scenes/00_zone_forest/_zone00_crescentRoad.unity" }, { "Tuul Enclave Portal", "Assets/Scenes/00_zone_forest/_zone00_tuulEnclave.unity" }, { "Luvora Garden Portal", "Assets/Scenes/00_zone_forest/_zone00_luvoraGarden.unity" }, { "Crescent Keep Portal", "Assets/Scenes/00_zone_forest/_zone00_crescentKeep.unity" }, { "Bularr Fortress Portal", "Assets/Scenes/00_zone_forest/_zone00_bularFortress.unity" }, { "Crescent Grove lvl 1 Portal", "Assets/Scenes/map_dungeon01_crescentGrove.unity" }, { "Crescent Grove lvl 2 Portal", "Assets/Scenes/map_dungeon01_crescentGrove.unity" }, { "Gate of the Moon Portal", "Assets/Scenes/00_zone_forest/_zone00_gateOfTheMoon.unity" }, { "Wall of the Stars Portal", "Assets/Scenes/00_zone_forest/_zone00_wallOfTheStars.unity" }, { "Redwoud Portal", "Assets/Scenes/map_zone00_redwoud.unity" }, { "Trial of the Stars Portal", "Assets/Scenes/00_zone_forest/_zone00_trialOfTheStars.unity" } }; public static readonly IReadOnlyList ProgressiveSanctumPortalOrder = new List { "Outer Sanctum Portal", "Arcwood Pass Portal", "Sanctum Catacombs lvl 1 Portal", "Sanctum Catacombs lvl 2 Portal", "Sanctum Catacombs lvl 3 Portal", "Effold Terrace Portal", "Crescent Road Portal", "Luvora Garden Portal", "Crescent Keep Portal", "Crescent Grove lvl 1 Portal", "Crescent Grove lvl 2 Portal" }; public static readonly IReadOnlyList ProgressiveTuulPortalOrder = new List { "Tuul Valley Portal", "Tuul Enclave Portal", "Bularr Fortress Portal" }; public static readonly IReadOnlyDictionary PortalProgressiveRequirements = new Dictionary { { "Outer Sanctum Portal", new ProgressivePortalRequirement(1, 0) }, { "Arcwood Pass Portal", new ProgressivePortalRequirement(2, 0) }, { "Sanctum Catacombs lvl 1 Portal", new ProgressivePortalRequirement(3, 0) }, { "Sanctum Catacombs lvl 2 Portal", new ProgressivePortalRequirement(4, 0) }, { "Sanctum Catacombs lvl 3 Portal", new ProgressivePortalRequirement(5, 0) }, { "Effold Terrace Portal", new ProgressivePortalRequirement(6, 0) }, { "Tuul Valley Portal", new ProgressivePortalRequirement(6, 1) }, { "Crescent Road Portal", new ProgressivePortalRequirement(7, 1) }, { "Luvora Garden Portal", new ProgressivePortalRequirement(8, 1) }, { "Crescent Keep Portal", new ProgressivePortalRequirement(9, 1) }, { "Tuul Enclave Portal", new ProgressivePortalRequirement(9, 2) }, { "Crescent Grove lvl 1 Portal", new ProgressivePortalRequirement(10, 2) }, { "Crescent Grove lvl 2 Portal", new ProgressivePortalRequirement(11, 2) }, { "Bularr Fortress Portal", new ProgressivePortalRequirement(11, 3) } }; public static readonly Dictionary AllLocationNameToId = new Dictionary { { "A Warm Welcome", 1L }, { "Communing Catacombs", 2L }, { "Diva Must Die", 3L }, { "The Keep Within", 4L }, { "Tethering Grove", 5L }, { "The Glyphik Booklet", 6L }, { "Cleaning Terrace", 7L }, { "Ancient Beings", 8L }, { "Wicked Wizboars", 9L }, { "Spiraling In The Grove", 10L }, { "Hell In The Grove", 11L }, { "Nulversa", 12L }, { "Finding Ammagon", 13L }, { "The Colossus", 14L }, { "Night Spirits", 15L }, { "Ridding Slimes", 16L }, { "Huntin' Hogs", 17L }, { "Purging the Grove", 18L }, { "Cleansing the Grove", 19L }, { "Call of Fury", 20L }, { "Mastery of Strength", 21L }, { "Mastery of Dexterity", 22L }, { "Mastery of Mind", 23L }, { "Beckoning Foes", 24L }, { "Ghostly Goods", 25L }, { "Makin' a Mekspear", 26L }, { "Makin' a Wizwand", 27L }, { "Makin' a Vile Blade", 28L }, { "Makin' a Golem Chestpiece", 29L }, { "Makin' a Ragespear", 30L }, { "Makin' a Monolith Chestpiece", 31L }, { "Makin' a Firebreath Blade", 32L }, { "Makin' a Follycannon", 33L }, { "Summore' Spectral Powder!", 34L }, { "Makin' More Mekspears", 35L }, { "Makin' More Wizwands", 36L }, { "Makin' More Vile Blades", 37L }, { "Summore' Golem Chestpieces", 38L }, { "Makin' More Ragespears", 39L }, { "Summore' Monolith Chestpieces", 40L }, { "Summore' Firebreath Blades", 41L }, { "Makin' More Follycannons", 42L }, { "Focusin' in", 43L }, { "Whatta' Rush!", 44L }, { "The Voice of Zuulneruda", 45L }, { "Killing Tomb", 46L }, { "Purging the Undead", 47L }, { "Rattlecage Rage", 48L }, { "Consumed Madness", 49L }, { "Eradicating the Undead", 50L }, { "Reviling the Rageboars", 51L }, { "Gatling Galius", 52L }, { "Reviling more Rageboars", 53L }, { "Facing Foes", 54L }, { "The Gall of Galius", 55L }, { "Dense Ingots", 56L }, { "Amberite Ingots", 57L }, { "Sapphite Ingots", 58L }, { "Reach Level 2", 59L }, { "Reach Level 4", 60L }, { "Reach Level 6", 61L }, { "Reach Level 8", 62L }, { "Reach Level 10", 63L }, { "Reach Level 12", 64L }, { "Reach Level 14", 65L }, { "Reach Level 16", 66L }, { "Reach Level 18", 67L }, { "Reach Level 20", 68L }, { "Reach Level 22", 69L }, { "Reach Level 24", 70L }, { "Reach Level 26", 71L }, { "Reach Level 28", 72L }, { "Reach Level 30", 73L }, { "Reach Level 32", 74L }, { "Buy Item #1 from Sally's Nook", 75L }, { "Buy Item #2 from Sally's Nook", 76L }, { "Buy Item #3 from Sally's Nook", 77L }, { "Buy Item #4 from Sally's Nook", 78L }, { "Buy Item #5 from Sally's Nook", 79L }, { "Buy Item #1 from Skrit's Sikrit Market", 80L }, { "Buy Item #2 from Skrit's Sikrit Market", 81L }, { "Buy Item #3 from Skrit's Sikrit Market", 82L }, { "Buy Item #4 from Skrit's Sikrit Market", 83L }, { "Buy Item #5 from Skrit's Sikrit Market", 84L }, { "Buy Item #1 from Frankie's Goods", 85L }, { "Buy Item #2 from Frankie's Goods", 86L }, { "Buy Item #3 from Frankie's Goods", 87L }, { "Buy Item #4 from Frankie's Goods", 88L }, { "Buy Item #5 from Frankie's Goods", 89L }, { "Buy Item #1 from Dye Merchant", 90L }, { "Buy Item #2 from Dye Merchant", 91L }, { "Buy Item #3 from Dye Merchant", 92L }, { "Buy Item #4 from Dye Merchant", 93L }, { "Buy Item #5 from Dye Merchant", 94L }, { "Buy Item #1 from Tesh's Wares", 95L }, { "Buy Item #2 from Tesh's Wares", 96L }, { "Buy Item #3 from Tesh's Wares", 97L }, { "Buy Item #4 from Tesh's Wares", 98L }, { "Buy Item #5 from Tesh's Wares", 99L }, { "Buy Item #1 from Nesh's Wares", 100L }, { "Buy Item #2 from Nesh's Wares", 101L }, { "Buy Item #3 from Nesh's Wares", 102L }, { "Buy Item #4 from Nesh's Wares", 103L }, { "Buy Item #5 from Nesh's Wares", 104L }, { "Buy Item #1 from Rikko's Treasures", 105L }, { "Buy Item #2 from Rikko's Treasures", 106L }, { "Buy Item #3 from Rikko's Treasures", 107L }, { "Buy Item #4 from Rikko's Treasures", 108L }, { "Buy Item #5 from Rikko's Treasures", 109L }, { "Buy Item #1 from Cotoo's Treasures", 110L }, { "Buy Item #2 from Cotoo's Treasures", 111L }, { "Buy Item #3 from Cotoo's Treasures", 112L }, { "Buy Item #4 from Cotoo's Treasures", 113L }, { "Buy Item #5 from Cotoo's Treasures", 114L }, { "Buy Item #1 from Ruka's Furnace", 115L }, { "Buy Item #2 from Ruka's Furnace", 116L }, { "Buy Item #3 from Ruka's Furnace", 117L }, { "Buy Item #4 from Ruka's Furnace", 118L }, { "Buy Item #5 from Ruka's Furnace", 119L }, { "Buy Item #1 from Torta's Fishing Shack", 120L }, { "Buy Item #2 from Torta's Fishing Shack", 121L }, { "Buy Item #3 from Torta's Fishing Shack", 122L }, { "Buy Item #4 from Torta's Fishing Shack", 123L }, { "Buy Item #5 from Torta's Fishing Shack", 124L }, { "Buy Item #1 from Mad Statue's Gift", 125L }, { "Buy Item #2 from Mad Statue's Gift", 126L }, { "Buy Item #3 from Mad Statue's Gift", 127L }, { "Buy Item #4 from Mad Statue's Gift", 128L }, { "Buy Item #5 from Mad Statue's Gift", 129L }, { "Fishing Lv. 1", 130L }, { "Fishing Lv. 2", 131L }, { "Fishing Lv. 3", 132L }, { "Fishing Lv. 4", 133L }, { "Fishing Lv. 5", 134L }, { "Fishing Lv. 6", 135L }, { "Fishing Lv. 7", 136L }, { "Fishing Lv. 8", 137L }, { "Fishing Lv. 9", 138L }, { "Fishing Lv. 10", 139L }, { "Mining Lv. 1", 140L }, { "Mining Lv. 2", 141L }, { "Mining Lv. 3", 142L }, { "Mining Lv. 4", 143L }, { "Mining Lv. 5", 144L }, { "Mining Lv. 6", 145L }, { "Mining Lv. 7", 146L }, { "Mining Lv. 8", 147L }, { "Mining Lv. 9", 148L }, { "Mining Lv. 10", 149L }, { "A New Journey", 150L }, { "Clearing Catacombs (1-6)", 151L }, { "Clearing Catacombs (6-12)", 152L }, { "Clearing Catacombs (12-18)", 153L }, { "Clearing Grove (15-20)", 154L }, { "Clearing Grove (20-25)", 155L }, { "Altered Vision", 156L }, { "Scaling the Tower", 157L }, { "Rude!", 158L }, { "Slime Diva", 159L }, { "Lord Zuulneruda", 160L }, { "Lord Kaluuz", 161L }, { "Colossus", 162L }, { "Valdur", 163L }, { "Galius", 164L }, { "Buy Fishing Rod", 165L }, { "Buy Pickaxe", 166L }, { "Defeat Creep: Lesser Wisp", 170L }, { "Defeat Creep: Slime", 171L }, { "Defeat Creep: Tar Spawn", 172L }, { "Defeat Creep: Greater Wisp", 173L }, { "Defeat Creep: Mini Geist", 174L }, { "Defeat Creep: Slimek", 175L }, { "Defeat Creep: Deathgel", 176L }, { "Defeat Creep: Geist", 177L }, { "Defeat Creep: Mekboar", 178L }, { "Defeat Creep: Mini Golem", 179L }, { "Defeat Creep: Toxin", 180L }, { "Defeat Creep: Blightwood", 181L }, { "Defeat Creep: Mouth", 182L }, { "Defeat Creep: Golem", 183L }, { "Defeat Creep: Hellsludge", 184L }, { "Defeat Creep: Klamkull", 185L }, { "Defeat Creep: Wizboar", 186L }, { "Defeat Creep: Miasma", 187L }, { "Defeat Creep: Vout", 188L }, { "Defeat Creep: Deadwood", 189L }, { "Defeat Creep: Stezza", 190L }, { "Defeat Creep: Alshar", 191L }, { "Defeat Creep: Blue Minichroom", 192L }, { "Defeat Creep: Red Minichroom", 193L }, { "Defeat Creep: Rageboar", 194L }, { "Defeat Creep: Maw", 195L }, { "Defeat Creep: Mandragora", 196L }, { "Defeat Creep: Gale Muchroom", 197L }, { "Defeat Creep: Aqua Muchroom", 198L }, { "Defeat Creep: Firebreath", 199L }, { "Defeat Creep: Poltergeist", 200L }, { "Defeat Creep: Vultia", 201L }, { "Defeat Creep: Boomboar", 202L }, { "Defeat Creep: Carbuncle", 203L }, { "Defeat Creep: Monolith", 204L }, { "Defeat Creep: Nagrath", 205L }, { "Defeat Creep: Barknaught", 206L }, { "Defeat Creep: Warboar", 207L }, { "Defeat Creep: Sirius", 208L }, { "Defeat Creep: Death Knight", 209L }, { "Defeat Creep: Demigolem", 210L }, { "Defeat Creep: Biggun", 211L }, { "Defeat Creep: Nograd", 212L }, { "Defeat Creep: Rokkamek", 213L }, { "Defeat Creep: Dolkin", 214L }, { "Defeat Creep: Gahool", 215L }, { "Defeat Creep: Kruole", 216L } }; internal static readonly Dictionary AllQuestToLocation = new Dictionary { { "A Warm Welcome", 1L }, { "Communing Catacombs", 2L }, { "Diva Must Die", 3L }, { "The Keep Within", 4L }, { "Tethering Grove", 5L }, { "The Glyphik Booklet", 6L }, { "Cleaning Terrace", 7L }, { "Ancient Beings", 8L }, { "Wicked Wizboars", 9L }, { "Spiraling In The Grove", 10L }, { "Hell In The Grove", 11L }, { "Nulversa", 12L }, { "Finding Ammagon", 13L }, { "The Colossus", 14L }, { "Night Spirits", 15L }, { "Ridding Slimes", 16L }, { "Huntin' Hogs", 17L }, { "Purging the Grove", 18L }, { "Cleansing the Grove", 19L }, { "Call of Fury", 20L }, { "Mastery of Strength", 21L }, { "Mastery of Dexterity", 22L }, { "Mastery of Mind", 23L }, { "Beckoning Foes", 24L }, { "Ghostly Goods", 25L }, { "Makin' a Mekspear", 26L }, { "Makin' a Wizwand", 27L }, { "Makin' a Vile Blade", 28L }, { "Makin' a Golem Chestpiece", 29L }, { "Makin' a Ragespear", 30L }, { "Makin' a Monolith Chestpiece", 31L }, { "Makin' a Firebreath Blade", 32L }, { "Makin' a Follycannon", 33L }, { "Summore' Spectral Powder!", 34L }, { "Makin' More Mekspears", 35L }, { "Makin' More Wizwands", 36L }, { "Makin' More Vile Blades", 37L }, { "Summore' Golem Chestpieces", 38L }, { "Makin' More Ragespears", 39L }, { "Summore' Monolith Chestpieces", 40L }, { "Summore' Firebreath Blades", 41L }, { "Makin' More Follycannons", 42L }, { "Focusin' in", 43L }, { "Whatta' Rush!", 44L }, { "The Voice of Zuulneruda", 45L }, { "Killing Tomb", 46L }, { "Purging the Undead", 47L }, { "Rattlecage Rage", 48L }, { "Consumed Madness", 49L }, { "Eradicating the Undead", 50L }, { "Reviling the Rageboars", 51L }, { "Gatling Galius", 52L }, { "Reviling more Rageboars", 53L }, { "Facing Foes", 54L }, { "The Gall of Galius", 55L }, { "Dense Ingots", 56L }, { "Amberite Ingots", 57L }, { "Sapphite Ingots", 58L } }; internal static readonly Dictionary ApQuestVanillaCompletionNames = new Dictionary { { "Nulversa", new string[3] { "Nulversa Magica", "Nulversa Viscera", "Nulversa, Greenversa!" } } }; internal static readonly KeyValuePair[] AllQuestToLocationPairs = BuildAllQuestToLocationPairs(); public static readonly Dictionary ItemNameMapping = new Dictionary { { "Bunbag Pack", "(lv-0) STATUSCONSUMABLE_Bunbag" }, { "Bunjar Pack", "(lv-0) STATUSCONSUMABLE_Bunjar" }, { "Bunpot Pack", "(lv-0) STATUSCONSUMABLE_Bunpot" }, { "Regen Potion Pack", "(lv-10) STATUSCONSUMABLE_Regen Potion" }, { "Regen Vial Pack", "(lv-0) STATUSCONSUMABLE_Regen Vial" }, { "Magiclove Pack", "(lv-0) STATUSCONSUMABLE_Magiclove" }, { "Magiflower Pack", "(lv-0) STATUSCONSUMABLE_Magiflower" }, { "Magileaf Pack", "(lv-0) STATUSCONSUMABLE_Magileaf" }, { "Stamstar", "(lv-0) STATUSCONSUMABLE_Stamstar" }, { "Agility Potion Pack", "(lv-10) STATUSCONSUMABLE_Agility Potion" }, { "Agility Vial Pack", "(lv-0) STATUSCONSUMABLE_Agility Vial" }, { "Bolster Potion Pack", "(lv-10) STATUSCONSUMABLE_Bolster Potion" }, { "Bolster Vial Pack", "(lv-0) STATUSCONSUMABLE_Bolster Vial" }, { "Wisdom Potion Pack", "(lv-10) STATUSCONSUMABLE_Wisdom Potion" }, { "Wisdom Vial Pack", "(lv-0) STATUSCONSUMABLE_Wisdom Vial" }, { "Tome of Greater Experience", "(lv-0) STATUSCONSUMABLE_Tome of Greater Experience" }, { "Tome of Experience", "(lv-0) STATUSCONSUMABLE_Tome of Experience" }, { "Tome of Lesser Experience", "(lv-0) STATUSCONSUMABLE_Tome of Lesser Experience" }, { "Carrot Cake Pack", "(lv-0) STATUSCONSUMABLE_Carrot Cake" }, { "Minchroom Juice Pack", "(lv-0) STATUSCONSUMABLE_Minchroom Juice" }, { "Spectral Powder Pack", "(lv-0) STATUSCONSUMABLE_Spectral Powder" }, { "Geistlord Badge", "TRADEITEM_Geistlord Badge" }, { "Coldgeist Badge", "TRADEITEM_Coldgeist Badge" }, { "Earthcore Badge", "TRADEITEM_Earthcore Badge" }, { "Windcore Badge", "TRADEITEM_Windcore Badge" }, { "Iron Cluster", "TRADEITEM_Iron Cluster" }, { "Copper Cluster", "TRADEITEM_Copper Cluster" }, { "Mithril Cluster", "TRADEITEM_Mithril Cluster" }, { "Dense Ingot", "TRADEITEM_Dense Ingot" }, { "Sapphite Ingot", "TRADEITEM_Sapphite Ingot" }, { "Amberite Ingot", "TRADEITEM_Amberite Ingot" }, { "Soul Pearl", "TRADEITEM_Soul Pearl" }, { "Experience Bond", "TRADEITEM_Experience Bond" }, { "Fishing Rod", "Fishing Rod" }, { "Pickaxe", "Pickaxe" }, { "Crypt Blade", "(lv-2) WEAPON_Crypt Blade (Sword, Strength)" }, { "Femur Club", "(lv-2) WEAPON_Femur Club (Sword, Strength)" }, { "Ironbark Sword", "(lv-2) WEAPON_Ironbark Sword (Sword, Strength)" }, { "Slimecrust Blade", "(lv-2) WEAPON_Slimecrust Blade (Sword, Strength)" }, { "Gilded Sword", "(lv-4) WEAPON_Gilded Sword (Sword, Strength)" }, { "Splitbark Club", "(lv-4) WEAPON_Splitbark Club (Sword, Strength)" }, { "Demicrypt Blade", "(lv-6) WEAPON_Demicrypt Blade (Sword, Strength)" }, { "Dense Mace", "(lv-6) WEAPON_Dense Mace (Sword, Strength)" }, { "Iron Sword", "(lv-6) WEAPON_Iron Sword (Sword, Strength)" }, { "Dawn Mace", "(lv-8) WEAPON_Dawn Mace (Sword, Strength)" }, { "Rude Blade", "(lv-8) WEAPON_Rude Blade (Sword, Strength)" }, { "Vile Blade", "(lv-8) WEAPON_Vile Blade (Sword, Strength)" }, { "Amberite Sword", "(lv-12) WEAPON_Amberite Sword (Sword, Strength)" }, { "Nethercrypt Blade", "(lv-12) WEAPON_Nethercrypt Blade (Sword, Strength)" }, { "Coldgeist Blade", "(lv-16) WEAPON_Coldgeist Blade (Sword, Strength)" }, { "Mithril Sword", "(lv-16) WEAPON_Mithril Sword (Sword, Strength)" }, { "Serrated Blade", "(lv-16) WEAPON_Serrated Blade (Sword, Strength)" }, { "Nulrok Mace", "(lv-20) WEAPON_Nulrok Mace (Sword, Strength)" }, { "Firebreath Blade", "(lv-22) WEAPON_Firebreath Blade (Sword, Strength)" }, { "Valdur Blade", "(lv-24) WEAPON_Valdur Blade (Sword, Strength)" }, { "Fier Blade", "(lv-26) WEAPON_Fier Blade (Sword, Strength)" }, { "Slimek Axehammer", "(lv-4) WEAPON_Slimek Axehammer (Hammer, Strength)" }, { "Dense Hammer", "(lv-6) WEAPON_Dense Hammer (Hammer, Strength)" }, { "Iron Axehammer", "(lv-6) WEAPON_Iron Axehammer (Hammer, Strength)" }, { "Crypt Pounder", "(lv-8) WEAPON_Crypt Pounder (Hammer, Strength)" }, { "Quake Pummeler", "(lv-18) WEAPON_Quake Pummeler (Hammer, Strength)" }, { "Mini Geist Scythe", "(lv-4) WEAPON_Mini Geist Scythe (Greatblade, Strength)" }, { "Geist Scythe", "(lv-6) WEAPON_Geist Scythe (Greatblade, Strength)" }, { "Stone Greatblade", "(lv-8) WEAPON_Stone Greatblade (Greatblade, Strength)" }, { "Amberite Warstar", "(lv-12) WEAPON_Amberite Warstar (Greatblade, Strength)" }, { "Dolkin's Axe", "(lv-12) WEAPON_Dolkin's Axe (Greatblade, Strength)" }, { "Poltergeist Scythe", "(lv-14) WEAPON_Poltergeist Scythe (Greatblade, Strength)" }, { "Coldgeist Punisher", "(lv-16) WEAPON_Coldgeist Punisher (Greatblade, Strength)" }, { "Deadwood Axe", "(lv-16) WEAPON_Deadwood Axe (Greatblade, Strength)" }, { "Mithril Greatsword", "(lv-16) WEAPON_Mithril Greatsword (Greatblade, Strength)" }, { "Deathknight Runeblade", "(lv-22) WEAPON_Deathknight Runeblade (Greatblade, Strength)" }, { "Ryzer Greataxe", "(lv-26) WEAPON_Ryzer Greataxe (Greatblade, Strength)" }, { "Dense Spear", "(lv-6) WEAPON_Dense Spear (Polearm, Strength)" }, { "Iron Spear", "(lv-6) WEAPON_Iron Spear (Polearm, Strength)" }, { "Cryptsinge Halberd", "(lv-8) WEAPON_Cryptsinge Halberd (Polearm, Strength)" }, { "Mekspear", "(lv-8) WEAPON_Mekspear (Polearm, Strength)" }, { "Amberite Halberd", "(lv-12) WEAPON_Amberite Halberd (Polearm, Strength)" }, { "Necroroyal Halberd", "(lv-12) WEAPON_Necroroyal Halberd (Polearm, Strength)" }, { "Sinner Bardiche", "(lv-12) WEAPON_Sinner Bardiche (Polearm, Strength)" }, { "Mithril Halberd", "(lv-16) WEAPON_Mithril Halberd (Polearm, Strength)" }, { "Ragespear", "(lv-16) WEAPON_Ragespear (Polearm, Strength)" }, { "Serrated Spear", "(lv-16) WEAPON_Serrated Spear (Polearm, Strength)" }, { "Sapphite Spear", "(lv-18) WEAPON_Sapphite Spear (Polearm, Strength)" }, { "Nulrok Spear", "(lv-20) WEAPON_Nulrok Spear (Polearm, Strength)" }, { "Cryotribe Spear", "(lv-22) WEAPON_Cryotribe Spear (Polearm, Strength)" }, { "Flametribe Spear", "(lv-22) WEAPON_Flametribe Spear (Polearm, Strength)" }, { "Marrow Bauble", "(lv-2) WEAPON_Marrow Bauble (Scepter, Mind)" }, { "Splitbark Scepter", "(lv-2) WEAPON_Splitbark Scepter (Scepter, Mind)" }, { "Demicrypt Bauble", "(lv-6) WEAPON_Demicrypt Bauble (Scepter, Mind)" }, { "Iron Scepter", "(lv-6) WEAPON_Iron Scepter (Scepter, Mind)" }, { "Cryo Cane", "(lv-8) WEAPON_Cryo Cane (Scepter, Mind)" }, { "Slime Diva Baton", "(lv-8) WEAPON_Slime Diva Baton (Scepter, Mind)" }, { "Pyre Cane", "(lv-12) WEAPON_Pyre Cane (Scepter, Mind)" }, { "Wizwand", "(lv-12) WEAPON_Wizwand (Scepter, Mind)" }, { "Nethercrypt Bauble", "(lv-14) WEAPON_Nethercrypt Bauble (Scepter, Mind)" }, { "Aquapetal Staff", "(lv-16) WEAPON_Aquapetal Staff (Scepter, Mind)" }, { "Flamepetal Staff", "(lv-16) WEAPON_Flamepetal Staff (Scepter, Mind)" }, { "Mithril Scepter", "(lv-16) WEAPON_Mithril Scepter (Scepter, Mind)" }, { "Sapphite Scepter", "(lv-18) WEAPON_Sapphite Scepter (Scepter, Mind)" }, { "Voalstark Wand", "(lv-24) WEAPON_Voalstark Wand (Scepter, Mind)" }, { "Cryptcall Bell", "(lv-8) WEAPON_Cryptcall Bell (Magic Bell, Mind)" }, { "Iron Bell", "(lv-8) WEAPON_Iron Bell (Magic Bell, Mind)" }, { "Coldgeist Frostcaller", "(lv-16) WEAPON_Coldgeist Frostcaller (Magic Bell, Mind)" }, { "Mithril Bell", "(lv-16) WEAPON_Mithril Bell (Magic Bell, Mind)" }, { "Colossus Tone", "(lv-18) WEAPON_Colossus Tone (Magic Bell, Mind)" }, { "Sapphite Bell", "(lv-18) WEAPON_Sapphite Bell (Magic Bell, Mind)" }, { "Slimecrust Katars", "(lv-2) WEAPON_Slimecrust Katars (Katars, Dexterity)" }, { "Cryptsinge Katars", "(lv-4) WEAPON_Cryptsinge Katars (Katars, Dexterity)" }, { "Slimek Shivs", "(lv-4) WEAPON_Slimek Shivs (Katars, Dexterity)" }, { "Deathgel Shivs", "(lv-6) WEAPON_Deathgel Shivs (Katars, Dexterity)" }, { "Dense Katars", "(lv-6) WEAPON_Dense Katars (Katars, Dexterity)" }, { "Iron Katars", "(lv-8) WEAPON_Iron Katars (Katars, Dexterity)" }, { "Runic Katars", "(lv-10) WEAPON_Runic Katars (Katars, Dexterity)" }, { "Geistlord Claws", "(lv-12) WEAPON_Geistlord Claws (Katars, Dexterity)" }, { "Hellsludge Shivs", "(lv-14) WEAPON_Hellsludge Shivs (Katars, Dexterity)" }, { "Mithril Katars", "(lv-14) WEAPON_Mithril Katars (Katars, Dexterity)" }, { "Frostbite Claws", "(lv-16) WEAPON_Frostbite Claws (Katars, Dexterity)" }, { "Serrated Knuckles", "(lv-16) WEAPON_Serrated Knuckles (Katars, Dexterity)" }, { "Rummok Bladerings", "(lv-18) WEAPON_Rummok Bladerings (Katars, Dexterity)" }, { "Sapphite Katars", "(lv-18) WEAPON_Sapphite Katars (Katars, Dexterity)" }, { "Golemfist Katars", "(lv-20) WEAPON_Golemfist Katars (Katars, Dexterity)" }, { "Crypt Bow", "(lv-2) WEAPON_Crypt Bow (Bow, Dexterity)" }, { "Demicrypt Bow", "(lv-6) WEAPON_Demicrypt Bow (Bow, Dexterity)" }, { "Iron Bow", "(lv-6) WEAPON_Iron Bow (Bow, Dexterity)" }, { "Mekspike Bow", "(lv-8) WEAPON_Mekspike Bow (Bow, Dexterity)" }, { "Menace Bow", "(lv-8) WEAPON_Menace Bow (Bow, Dexterity)" }, { "Petrified Bow", "(lv-12) WEAPON_Petrified Bow (Bow, Dexterity)" }, { "Mithril Bow", "(lv-14) WEAPON_Mithril Bow (Bow, Dexterity)" }, { "Necroroyal Bow", "(lv-14) WEAPON_Necroroyal Bow (Bow, Dexterity)" }, { "Coldgeist Bow", "(lv-16) WEAPON_Coldgeist Bow (Bow, Dexterity)" }, { "Serrated Longbow", "(lv-16) WEAPON_Serrated Longbow (Bow, Dexterity)" }, { "Torrentius Longbow", "(lv-24) WEAPON_Torrentius Longbow (Bow, Dexterity)" }, { "Amberite Boomstick", "(lv-12) WEAPON_Amberite Boomstick (Shotgun, Dexterity)" }, { "Magitek Burstgun", "(lv-20) WEAPON_Magitek Burstgun (Shotgun, Dexterity)" }, { "Follycannon", "(lv-26) WEAPON_Follycannon (Shotgun, Dexterity)" }, { "Agility Ears", "(lv-1) HELM_Agility Ears" }, { "Festive Hat", "(lv-1) HELM_Festive Hat" }, { "Fishin Hat", "(lv-1) HELM_Fishin Hat" }, { "Leather Cap", "(lv-1) HELM_Leather Cap" }, { "Newfold Halo", "(lv-1) HELM_Newfold Halo" }, { "Orefinder Hat", "(lv-1) HELM_Orefinder Hat" }, { "Spooky Hat", "(lv-1) HELM_Spooky Hat" }, { "Top Hat", "(lv-1) HELM_Top Hat" }, { "Wizard Hat", "(lv-1) HELM_Wizard Hat" }, { "Acolyte Hood", "(lv-4) HELM_Acolyte Hood" }, { "Cryptsinge Halo", "(lv-4) HELM_Cryptsinge Halo" }, { "Initiate Spectacles", "(lv-4) HELM_Initiate Spectacles" }, { "Demicrypt Halo", "(lv-6) HELM_Demicrypt Halo" }, { "Dense Helm", "(lv-6) HELM_Dense Helm" }, { "Diva Crown", "(lv-6) HELM_Diva Crown" }, { "Iron Halo", "(lv-6) HELM_Iron Halo" }, { "Necromancer Hood", "(lv-8) HELM_Necromancer Hood" }, { "Geistlord Crown", "(lv-10) HELM_Geistlord Crown" }, { "Journeyman Spectacles", "(lv-10) HELM_Journeyman Spectacles" }, { "Amberite Helm", "(lv-12) HELM_Amberite Helm" }, { "Focus Circlet", "(lv-12) HELM_Focus Circlet" }, { "Magistrate Circlet", "(lv-12) HELM_Magistrate Circlet" }, { "Rage Circlet", "(lv-12) HELM_Rage Circlet" }, { "Focusi Glasses", "(lv-14) HELM_Focusi Glasses" }, { "Nethercrypt Halo", "(lv-14) HELM_Nethercrypt Halo" }, { "Carbuncle Hat", "(lv-16) HELM_Carbuncle Hat" }, { "Geistlord Eye", "(lv-16) HELM_Geistlord Eye" }, { "Glyphgrift Halo", "(lv-16) HELM_Glyphgrift Halo" }, { "Jestercast Memory", "(lv-16) HELM_Jestercast Memory" }, { "Knightguard Halo", "(lv-16) HELM_Knightguard Halo" }, { "Mithril Halo", "(lv-16) HELM_Mithril Halo" }, { "Sapphite Mindhat", "(lv-18) HELM_Sapphite Mindhat" }, { "Dire Helm", "(lv-22) HELM_Dire Helm" }, { "Druidic Halo", "(lv-22) HELM_Druidic Halo" }, { "Guardel Helm", "(lv-22) HELM_Guardel Helm" }, { "Leathen Cap", "(lv-22) HELM_Leathen Cap" }, { "Boarus Helm", "(lv-24) HELM_Boarus Helm" }, { "Deathknight Helm", "(lv-24) HELM_Deathknight Helm" }, { "Emerock Halo", "(lv-24) HELM_Emerock Halo" }, { "Wizlad Hood", "(lv-24) HELM_Wizlad Hood" }, { "Boarus Torment", "(lv-26) HELM_Boarus Torment" }, { "Initiate Cloak", "(lv-2) CAPE_Initiate Cloak" }, { "Slimewoven Cloak", "(lv-4) CAPE_Slimewoven Cloak" }, { "Nokket Cloak", "(lv-6) CAPE_Nokket Cloak" }, { "Rugged Cloak", "(lv-6) CAPE_Rugged Cloak" }, { "Regazuul Cape", "(lv-10) CAPE_Regazuul Cape" }, { "Flux Cloak", "(lv-12) CAPE_Flux Cloak" }, { "Cozy Cloak", "(lv-14) CAPE_Cozy Cloak" }, { "Nethercrypt Cloak", "(lv-14) CAPE_Nethercrypt Cloak" }, { "Cobblerage Cloak", "(lv-16) CAPE_Cobblerage Cloak" }, { "Deathward Cape", "(lv-16) CAPE_Deathward Cape" }, { "Forlorn Cloak", "(lv-16) CAPE_Forlorn Cloak" }, { "Meshlink Cape", "(lv-16) CAPE_Meshlink Cape" }, { "Sagecaller Cape", "(lv-16) CAPE_Sagecaller Cape" }, { "Roudon Cape", "(lv-18) CAPE_Roudon Cape" }, { "Blueversa Cape", "(lv-20) CAPE_Blueversa Cape" }, { "Greenversa Cape", "(lv-20) CAPE_Greenversa Cape" }, { "Nulversa Cape", "(lv-20) CAPE_Nulversa Cape" }, { "Redversa Cape", "(lv-20) CAPE_Redversa Cape" }, { "Windgolem Cloak", "(lv-22) CAPE_Windgolem Cloak" }, { "Mekwar Drape", "(lv-24) CAPE_Mekwar Drape" }, { "Aero Top", "(lv-1) CHESTPIECE_Aero Top" }, { "Bunhost Garb", "(lv-1) CHESTPIECE_Bunhost Garb" }, { "Festive Coat", "(lv-1) CHESTPIECE_Festive Coat" }, { "Fisher Overalls", "(lv-1) CHESTPIECE_Fisher Overalls" }, { "Leather Top", "(lv-1) CHESTPIECE_Leather Top" }, { "Necro Marrow", "(lv-1) CHESTPIECE_Necro Marrow" }, { "Noble Shirt", "(lv-1) CHESTPIECE_Noble Shirt" }, { "Nutso Top", "(lv-1) CHESTPIECE_Nutso Top" }, { "Orefinder Vest", "(lv-1) CHESTPIECE_Orefinder Vest" }, { "Ritualist Garb", "(lv-1) CHESTPIECE_Ritualist Garb" }, { "Sagecloth Top", "(lv-1) CHESTPIECE_Sagecloth Top" }, { "Silken Top", "(lv-1) CHESTPIECE_Silken Top" }, { "Spooky Garment", "(lv-1) CHESTPIECE_Spooky Garment" }, { "Vampiric Coat", "(lv-1) CHESTPIECE_Vampiric Coat" }, { "Ghostly Tabard", "(lv-2) CHESTPIECE_Ghostly Tabard" }, { "Poacher Cloth", "(lv-2) CHESTPIECE_Poacher Cloth" }, { "Ragged Shirt", "(lv-2) CHESTPIECE_Ragged Shirt" }, { "Slimecrust Chest", "(lv-2) CHESTPIECE_Slimecrust Chest" }, { "Worn Robe", "(lv-2) CHESTPIECE_Worn Robe" }, { "Cryptsinge Chest", "(lv-4) CHESTPIECE_Cryptsinge Chest" }, { "Journeyman Vest", "(lv-4) CHESTPIECE_Journeyman Vest" }, { "Slimek Chest", "(lv-4) CHESTPIECE_Slimek Chest" }, { "Dense Chestpiece", "(lv-6) CHESTPIECE_Dense Chestpiece" }, { "Trodd Tunic", "(lv-6) CHESTPIECE_Trodd Tunic" }, { "Iron Chestpiece", "(lv-7) CHESTPIECE_Iron Chestpiece" }, { "Tattered Battlerobe", "(lv-8) CHESTPIECE_Tattered Battlerobe" }, { "Apprentice Robe", "(lv-10) CHESTPIECE_Apprentice Robe" }, { "Duelist Garb", "(lv-10) CHESTPIECE_Duelist Garb" }, { "Skywrill Tabard", "(lv-10) CHESTPIECE_Skywrill Tabard" }, { "Sleeper's Robe", "(lv-10) CHESTPIECE_Sleeper's Robe" }, { "Warrior Chest", "(lv-10) CHESTPIECE_Warrior Chest" }, { "Amberite Breastplate", "(lv-12) CHESTPIECE_Amberite Breastplate" }, { "Golem Chestpiece", "(lv-12) CHESTPIECE_Golem Chestpiece" }, { "Lord Breastplate", "(lv-12) CHESTPIECE_Lord Breastplate" }, { "Nethercrypt Tabard", "(lv-12) CHESTPIECE_Nethercrypt Tabard" }, { "Reapsow Garb", "(lv-12) CHESTPIECE_Reapsow Garb" }, { "Witchlock Robe", "(lv-12) CHESTPIECE_Witchlock Robe" }, { "Chainmail Guard", "(lv-14) CHESTPIECE_Chainmail Guard" }, { "Ornamented Battlerobe", "(lv-14) CHESTPIECE_Ornamented Battlerobe" }, { "Carbuncle Robe", "(lv-16) CHESTPIECE_Carbuncle Robe" }, { "Chainscale Chest", "(lv-16) CHESTPIECE_Chainscale Chest" }, { "Gemveil Raiment", "(lv-16) CHESTPIECE_Gemveil Raiment" }, { "King Breastplate", "(lv-16) CHESTPIECE_King Breastplate" }, { "Mercenary Vestment", "(lv-16) CHESTPIECE_Mercenary Vestment" }, { "Mithril Chestpiece", "(lv-16) CHESTPIECE_Mithril Chestpiece" }, { "Reaper Gi", "(lv-16) CHESTPIECE_Reaper Gi" }, { "Witchwizard Robe", "(lv-16) CHESTPIECE_Witchwizard Robe" }, { "Berserker Chestpiece", "(lv-18) CHESTPIECE_Berserker Chestpiece" }, { "Fuguefall Duster", "(lv-18) CHESTPIECE_Fuguefall Duster" }, { "Magilord Overalls", "(lv-18) CHESTPIECE_Magilord Overalls" }, { "Monolith Chestpiece", "(lv-18) CHESTPIECE_Monolith Chestpiece" }, { "Sapphite Guard", "(lv-18) CHESTPIECE_Sapphite Guard" }, { "Druidic Robe", "(lv-20) CHESTPIECE_Druidic Robe" }, { "Emerock Chestpiece", "(lv-20) CHESTPIECE_Emerock Chestpiece" }, { "Fortified Vestment", "(lv-20) CHESTPIECE_Fortified Vestment" }, { "Roudon Chestpiece", "(lv-20) CHESTPIECE_Roudon Chestpiece" }, { "Earthbind Tabard", "(lv-22) CHESTPIECE_Earthbind Tabard" }, { "Gemveil Breastplate", "(lv-22) CHESTPIECE_Gemveil Breastplate" }, { "Roudon Robe", "(lv-22) CHESTPIECE_Roudon Robe" }, { "Ruggrok Vest", "(lv-22) CHESTPIECE_Ruggrok Vest" }, { "Executioner Vestment", "(lv-24) CHESTPIECE_Executioner Vestment" }, { "Fender Garb", "(lv-24) CHESTPIECE_Fender Garb" }, { "Wizlad Robe", "(lv-24) CHESTPIECE_Wizlad Robe" }, { "Aero Pants", "(lv-1) LEGGINGS_Aero Pants" }, { "Bunhost Leggings", "(lv-1) LEGGINGS_Bunhost Leggings" }, { "Festive Trousers", "(lv-1) LEGGINGS_Festive Trousers" }, { "Leather Britches", "(lv-1) LEGGINGS_Leather Britches" }, { "Necro Caustics", "(lv-1) LEGGINGS_Necro Caustics" }, { "Noble Pants", "(lv-1) LEGGINGS_Noble Pants" }, { "Nutso Pants", "(lv-1) LEGGINGS_Nutso Pants" }, { "Orefinder", "(lv-1) LEGGINGS_Orefinder Trousers" }, { "Ritualist Straps", "(lv-1) LEGGINGS_Ritualist Straps" }, { "Sagecloth Shorts", "(lv-1) LEGGINGS_Sagecloth Shorts" }, { "Silken Loincloth", "(lv-1) LEGGINGS_Silken Loincloth" }, { "Vampiric Leggings", "(lv-1) LEGGINGS_Vampiric Leggings" }, { "Ghostly Legwraps", "(lv-2) LEGGINGS_Ghostly Legwraps" }, { "Journeyman Shorts", "(lv-2) LEGGINGS_Journeyman Shorts" }, { "Slimecrust Leggings", "(lv-2) LEGGINGS_Slimecrust Leggings" }, { "Journeyman Leggings", "(lv-4) LEGGINGS_Journeyman Leggings" }, { "Slimek Leggings", "(lv-4) LEGGINGS_Slimek Leggings" }, { "Dense Leggings", "(lv-6) LEGGINGS_Dense Leggings" }, { "Sash Leggings", "(lv-8) LEGGINGS_Sash Leggings" }, { "Warrior Leggings", "(lv-10) LEGGINGS_Warrior Leggings" }, { "Amberite Leggings", "(lv-12) LEGGINGS_Amberite Leggings" }, { "Chainmail Leggings", "(lv-12) LEGGINGS_Chainmail Leggings" }, { "Darkcloth Pants", "(lv-12) LEGGINGS_Darkcloth Pants" }, { "Lord Greaves", "(lv-12) LEGGINGS_Lord Greaves" }, { "Reapsow Pants", "(lv-12) LEGGINGS_Reapsow Pants" }, { "Witchlock Loincloth", "(lv-12) LEGGINGS_Witchlock Loincloth" }, { "King Greaves", "(lv-16) LEGGINGS_King Greaves" }, { "Mercenary Leggings", "(lv-16) LEGGINGS_Mercenary Leggings" }, { "Reaper Leggings", "(lv-16) LEGGINGS_Reaper Leggings" }, { "Stridebond Pants", "(lv-16) LEGGINGS_Stridebond Pants" }, { "Witchwizard Garterbelt", "(lv-16) LEGGINGS_Witchwizard Garterbelt" }, { "Berserker Leggings", "(lv-18) LEGGINGS_Berserker Leggings" }, { "Fuguefall Pants", "(lv-18) LEGGINGS_Fuguefall Pants" }, { "Magilord Boots", "(lv-18) LEGGINGS_Magilord Boots" }, { "Sapphite Leggings", "(lv-18) LEGGINGS_Sapphite Leggings" }, { "Jadewail Trousers", "(lv-20) LEGGINGS_Jadewail Trousers" }, { "Temrak Britches", "(lv-20) LEGGINGS_Temrak Britches" }, { "Eschek Greaves", "(lv-22) LEGGINGS_Eschek Greaves" }, { "Gemveil Leggings", "(lv-22) LEGGINGS_Gemveil Leggings" }, { "Executioner Leggings", "(lv-24) LEGGINGS_Executioner Leggings" }, { "Fender Leggings", "(lv-24) LEGGINGS_Fender Leggings" }, { "Wooden Shield", "(lv-1) SHIELD_Wooden Shield" }, { "Crypt Buckler", "(lv-4) SHIELD_Crypt Buckler" }, { "Slimek Shield", "(lv-4) SHIELD_Slimek Shield" }, { "Demicrypt Buckler", "(lv-6) SHIELD_Demicrypt Buckler" }, { "Dense Shield", "(lv-6) SHIELD_Dense Shield" }, { "Iron Shield", "(lv-6) SHIELD_Iron Shield" }, { "Iris Shield", "(lv-8) SHIELD_Iris Shield" }, { "Omen Shield", "(lv-8) SHIELD_Omen Shield" }, { "Amberite Shield", "(lv-12) SHIELD_Amberite Shield" }, { "Slabton Shield", "(lv-12) SHIELD_Slabton Shield" }, { "Mithril Shield", "(lv-14) SHIELD_Mithril Shield" }, { "Nethercrypt Shield", "(lv-14) SHIELD_Nethercrypt Shield" }, { "Rustweary Shield", "(lv-16) SHIELD_Rustweary Shield" }, { "Rustwise Shield", "(lv-16) SHIELD_Rustwise Shield" }, { "Sapphite Shield", "(lv-18) SHIELD_Sapphite Shield" }, { "Rigor Buckler", "(lv-20) SHIELD_Rigor Buckler" }, { "Daemon Shield", "(lv-22) SHIELD_Daemon Shield" }, { "Irisun Shield", "(lv-22) SHIELD_Irisun Shield" }, { "Old Ring", "(lv-1) RING_Old Ring" }, { "Ring Of Ambition", "(lv-1) RING_Ring Of Ambition" }, { "Nograd's Amulet", "(lv-2) RING_Nograd's Amulet" }, { "The One Ring", "(lv-2) RING_The One Ring" }, { "Ambersquire Ring", "(lv-6) RING_Ambersquire Ring" }, { "Emeraldfocus Ring", "(lv-6) RING_Emeraldfocus Ring" }, { "Sapphireweave Ring", "(lv-6) RING_Sapphireweave Ring" }, { "Edon's Pendant", "(lv-8) RING_Edon's Pendant" }, { "Geistlord Ring", "(lv-12) RING_Geistlord Ring" }, { "Students Ring", "(lv-12) RING_Students Ring" }, { "Pearlpond Ring", "(lv-14) RING_Pearlpond Ring" }, { "Slitherwraith Ring", "(lv-14) RING_Slitherwraith Ring" }, { "Geistlord Band", "(lv-16) RING_Geistlord Band" }, { "Jadetrout Ring", "(lv-16) RING_Jadetrout Ring" }, { "Orbos Ring", "(lv-16) RING_Orbos Ring" }, { "Valor Ring", "(lv-16) RING_Valor Ring" }, { "Earthwoken Ring", "(lv-18) RING_Earthwoken Ring" }, { "Noji Talisman", "(lv-20) RING_Noji Talisman" }, { "Valdur Effigy", "(lv-24) RING_Valdur Effigy" }, { "Glyphik Booklet", "(lv-26) RING_Glyphik Booklet" }, { "Tessellated Drive", "(lv-26) RING_Tessellated Drive" }, { "Aqua Muchroom Cap", "TRADEITEM_Aqua Muchroom Cap" }, { "Barknaught Face", "TRADEITEM_Barknaught Face" }, { "Blightwood Log", "TRADEITEM_Blightwood Log" }, { "Blightwood Stick", "TRADEITEM_Blightwood Stick" }, { "Blue Minchroom Cap", "TRADEITEM_Blue Minchroom Cap" }, { "Boomboar Gear", "TRADEITEM_Boomboar Gear" }, { "Boomboar Head", "TRADEITEM_Boomboar Head" }, { "Boomboar Pouch", "TRADEITEM_Boomboar Pouch" }, { "Burnrose", "TRADEITEM_Burnrose" }, { "Carbuncle Foot", "TRADEITEM_Carbuncle Foot" }, { "Cursed Note", "TRADEITEM_Cursed Note" }, { "Deadwood Log", "TRADEITEM_Deadwood Log" }, { "Deathgel Core", "TRADEITEM_Deathgel Core" }, { "Deathknight Gauntlet", "TRADEITEM_Deathknight Gauntlet" }, { "Demigolem Core", "TRADEITEM_Demigolem Core" }, { "Demigolem Gem", "TRADEITEM_Demigolem Gem" }, { "Diva Necklace", "TRADEITEM_Diva Necklace" }, { "Firebreath Gland", "TRADEITEM_Firebreath Gland" }, { "Fluxfern", "TRADEITEM_Fluxfern" }, { "Gale Muchroom Cap", "TRADEITEM_Gale Muchroom Cap" }, { "Geist Collar", "TRADEITEM_Geist Collar" }, { "Ghostdust", "TRADEITEM_Ghostdust" }, { "Golem Core", "TRADEITEM_Golem Core" }, { "Golem Gem", "TRADEITEM_Golem Gem" }, { "Green Lipstick", "TRADEITEM_Green Lipstick" }, { "Hellsludge Core", "TRADEITEM_Hellsludge Core" }, { "Maw Eye", "TRADEITEM_Maw Eye" }, { "Mekboar Head", "TRADEITEM_Mekboar Head" }, { "Mekboar Spear", "TRADEITEM_Mekboar Spear" }, { "Monolith Core", "TRADEITEM_Monolith Core" }, { "Monolith Gem", "TRADEITEM_Monolith Gem" }, { "Mouth Bittertooth", "TRADEITEM_Mouth Bittertooth" }, { "Mouth Eye", "TRADEITEM_Mouth Eye" }, { "Rageboar Head", "TRADEITEM_Rageboar Head" }, { "Rageboar Spear", "TRADEITEM_Rageboar Spear" }, { "Red Minchroom Cap", "TRADEITEM_Red Minchroom Cap" }, { "Rock", "TRADEITEM_Rock" }, { "Slime Core", "TRADEITEM_Slime Core" }, { "Slime Diva Ears", "TRADEITEM_Slime Diva Ears" }, { "Slime Ears", "TRADEITEM_Slime Ears" }, { "Slimek Core", "TRADEITEM_Slimek Core" }, { "Slimek Ears", "TRADEITEM_Slimek Ears" }, { "Slimek Eye", "TRADEITEM_Slimek Eye" }, { "Vinethorn", "TRADEITEM_Vinethorn" }, { "Vout Antennae", "TRADEITEM_Vout Antennae" }, { "Vout Wing", "TRADEITEM_Vout Wing" }, { "Warboar Axe", "TRADEITEM_Warboar Axe" }, { "Warboar Head", "TRADEITEM_Warboar Head" }, { "Wizboar Head", "TRADEITEM_Wizboar Head" }, { "Wizboar Scepter", "TRADEITEM_Wizboar Scepter" }, { "Amberite Ore", "TRADEITEM_Amberite Ore" }, { "Dense Ore", "TRADEITEM_Dense Ore" }, { "Sapphite Ore", "TRADEITEM_Sapphite Ore" }, { "Coal", "TRADEITEM_Coal" }, { "Big Wan", "TRADEITEM_Big Wan" }, { "Bittering Katfish", "TRADEITEM_Bittering Katfish" }, { "Bonefish", "TRADEITEM_Bonefish" }, { "Smiling Wrellfish", "TRADEITEM_Smiling Wrellfish" }, { "Squangfish", "TRADEITEM_Squangfish" }, { "Sugeel", "TRADEITEM_Sugeel" }, { "Sugshrimp", "TRADEITEM_Sugshrimp" }, { "Windtail Fish", "TRADEITEM_Windtail Fish" }, { "Old Boot", "TRADEITEM_Old Boot" }, { "Agility Stone", "TRADEITEM_Agility Stone" }, { "Angela's Tear", "TRADEITEM_Angela's Tear" }, { "Epic Carrot", "TRADEITEM_Epic Carrot" }, { "Flux Stone", "TRADEITEM_Flux Stone" }, { "Illusion Stone", "TRADEITEM_Illusion Stone" }, { "Might Stone", "TRADEITEM_Might Stone" }, { "Starlight Gem", "TRADEITEM_Starlight Gem" }, { "Black Dye", "TRADEITEM_Black Dye" }, { "Blue Dye", "TRADEITEM_Blue Dye" }, { "Brown Dye", "TRADEITEM_Brown Dye" }, { "Cyan Dye", "TRADEITEM_Cyan Dye" }, { "Green Dye", "TRADEITEM_Green Dye" }, { "Grey Dye", "TRADEITEM_Grey Dye" }, { "Lime Dye", "TRADEITEM_Lime Dye" }, { "Orange Dye", "TRADEITEM_Orange Dye" }, { "Pink Dye", "TRADEITEM_Pink Dye" }, { "Purple Dye", "TRADEITEM_Purple Dye" }, { "Red Dye", "TRADEITEM_Red Dye" }, { "White Dye", "TRADEITEM_White Dye" }, { "Yellow Dye", "TRADEITEM_Yellow Dye" }, { "Crowns (Small)", "CURRENCY_250" }, { "Crowns (Medium)", "CURRENCY_500" }, { "Crowns (Large)", "CURRENCY_1000" }, { "Crowns (Huge)", "CURRENCY_2500" }, { "Tome of Naivety", "(lv-0) STATUSCONSUMABLE_Tome of Naivety" }, { "Tome of Unlearning", "(lv-0) STATUSCONSUMABLE_Tome of Unlearning" } }; public static readonly Dictionary>> ProgressiveItemTiers = new Dictionary>> { { "Progressive Any Weapon", new List> { new List { "Crypt Buckler", "Slimek Shield" }, new List { "Demicrypt Buckler", "Dense Shield", "Iron Shield" }, new List { "Iris Shield", "Omen Shield", "Rustweary Shield", "Rustwise Shield" }, new List { "Amberite Shield", "Slabton Shield", "Mithril Shield", "Nethercrypt Shield" }, new List { "Rigor Buckler", "Sapphite Shield", "Daemon Shield", "Irisun Shield" } } }, { "Progressive Any Helmet", new List> { new List { "Agility Ears", "Leather Cap", "Newfold Halo" }, new List { "Acolyte Hood", "Cryptsinge Halo", "Initiate Spectacles", "Demicrypt Halo", "Dense Helm", "Diva Crown" }, new List { "Iron Halo", "Necromancer Hood", "Journeyman Spectacles" }, new List { "Geistlord Crown", "Geistlord Eye", "Amberite Helm", "Focus Circlet", "Magistrate Circlet", "Rage Circlet", "Focusi Glasses", "Nethercrypt Halo" }, new List { "Carbuncle Hat", "Glyphgrift Halo", "Jestercast Memory", "Knightguard Halo", "Mithril Halo", "Wizlad Hood", "Dire Helm", "Druidic Halo", "Guardel Helm", "Leathen Cap" }, new List { "Sapphite Mindhat", "Boarus Helm", "Deathknight Helm", "Emerock Halo", "Boarus Torment" } } }, { "Progressive Any Cape", new List> { new List { "Initiate Cloak", "Slimewoven Cloak", "Nokket Cloak", "Rugged Cloak" }, new List { "Cobblerage Cloak", "Forlorn Cloak", "Flux Cloak", "Meshlink Cape", "Cozy Cloak", "Nethercrypt Cloak" }, new List { "Regazuul Cape", "Deathward Cape", "Sagecaller Cape" }, new List { "Windgolem Cloak", "Roudon Cape", "Blueversa Cape", "Greenversa Cape", "Nulversa Cape", "Redversa Cape" }, new List { "Mekwar Drape" } } }, { "Progressive Any Chest Piece", new List> { new List { "Aero Top", "Leather Top", "Necro Marrow", "Nutso Top", "Sagecloth Top", "Ghostly Tabard", "Poacher Cloth", "Ragged Shirt", "Slimecrust Chest", "Worn Robe" }, new List { "Cryptsinge Chest", "Journeyman Vest", "Slimek Chest", "Apprentice Robe", "Dense Chestpiece", "Trodd Tunic", "Tattered Battlerobe" }, new List { "Iron Chestpiece", "Duelist Garb", "Amberite Breastplate", "Nethercrypt Tabard", "Chainmail Guard", "Skywrill Tabard", "Sleeper's Robe", "Warrior Chest", "Golem Chestpiece", "Carbuncle Robe" }, new List { "Ornamented Battlerobe", "Chainscale Chest", "Mercenary Vestment", "Mithril Chestpiece", "Druidic Robe" }, new List { "Gemveil Raiment", "Monolith Chestpiece", "Sapphite Guard", "Fortified Vestment", "Roudon Chestpiece", "Roudon Robe", "Earthbind Tabard", "Gemveil Breastplate", "Ruggrok Vest", "Emerock Chestpiece" } } }, { "Progressive Any Leggings", new List> { new List { "Aero Pants", "Leather Britches", "Necro Caustics", "Nutso Pants", "Sagecloth Shorts", "Ghostly Legwraps", "Slimecrust Leggings" }, new List { "Journeyman Shorts", "Journeyman Leggings", "Slimek Leggings", "Dense Leggings", "Sash Leggings" }, new List { "Amberite Leggings", "Chainmail Leggings", "Darkcloth Pants", "Warrior Leggings" }, new List { "Mercenary Leggings", "Stridebond Pants" }, new List { "Sapphite Leggings", "Jadewail Trousers", "Temrak Britches", "Eschek Greaves", "Gemveil Leggings" } } }, { "Progressive Any Trinket", new List> { new List { "Nograd's Amulet", "Old Ring", "Ring Of Ambition", "The One Ring" }, new List { "Nograd's Amulet", "Students Ring" }, new List { "The One Ring", "Geistlord Ring", "Geistlord Band" }, new List { "Ambersquire Ring", "Emeraldfocus Ring", "Pearlpond Ring", "Slitherwraith Ring", "Jadetrout Ring" }, new List { "Sapphireweave Ring", "Edon's Pendant", "Orbos Ring", "Valor Ring" }, new List { "Earthwoken Ring", "Noji Talisman", "Valdur Effigy", "Glyphik Booklet", "Tessellated Drive" } } }, { "Progressive Fighter Weapon", new List> { new List { "Crypt Blade", "Femur Club", "Ironbark Sword", "Slimecrust Blade", "Gilded Sword", "Splitbark Club", "Slimek Axehammer", "Mini Geist Scythe", "Geist Scythe" }, new List { "Demicrypt Blade", "Dense Mace", "Iron Sword", "Dense Hammer", "Iron Axehammer", "Crypt Pounder", "Stone Greatblade", "Dense Spear", "Iron Spear", "Cryptsinge Halberd", "Dawn Mace", "Rude Blade" }, new List { "Vile Blade", "Poltergeist Scythe", "Mekspear", "Amberite Sword", "Nethercrypt Blade", "Amberite Warstar", "Dolkin's Axe", "Amberite Halberd", "Necroroyal Halberd", "Sinner Bardiche" }, new List { "Coldgeist Blade", "Mithril Sword", "Serrated Blade", "Mithril Halberd", "Ragespear", "Serrated Spear" }, new List { "Nulrok Mace", "Coldgeist Punisher", "Nulrok Spear", "Firebreath Blade", "Quake Pummeler", "Deadwood Axe", "Mithril Greatsword", "Sapphite Spear", "Valdur Blade", "Deathknight Runeblade", "Cryotribe Spear", "Flametribe Spear" }, new List { "Fier Blade", "Ryzer Greataxe" } } }, { "Progressive Fighter Chest Piece", new List> { new List { "Lord Breastplate" }, new List { "King Breastplate", "Berserker Chestpiece" }, new List { "Executioner Vestment" } } }, { "Progressive Fighter Leggings", new List> { new List { "Lord Greaves" }, new List { "King Greaves", "Berserker Leggings" }, new List { "Executioner Leggings" } } }, { "Progressive Mystic Weapon", new List> { new List { "Marrow Bauble", "Splitbark Scepter", "Demicrypt Bauble", "Cryptcall Bell" }, new List { "Iron Scepter", "Cryo Cane", "Slime Diva Baton", "Iron Bell" }, new List { "Pyre Cane", "Wizwand", "Nethercrypt Bauble", "Aquapetal Staff", "Flamepetal Staff", "Coldgeist Frostcaller" }, new List { "Mithril Scepter", "Mithril Bell" }, new List { "Sapphite Scepter", "Colossus Tone", "Voalstark Wand", "Sapphite Bell" } } }, { "Progressive Mystic Helmet", new List> { new List { "Wizlad Hood" } } }, { "Progressive Mystic Chest Piece", new List> { new List { "Witchlock Robe" }, new List { "Witchwizard Robe", "Magilord Overalls" }, new List { "Wizlad Robe" } } }, { "Progressive Mystic Leggings", new List> { new List { "Witchlock Loincloth" }, new List { "Witchwizard Garterbelt", "Magilord Boots" } } }, { "Progressive Bandit Weapon", new List> { new List { "Slimecrust Katars", "Cryptsinge Katars", "Slimek Shivs", "Crypt Bow" }, new List { "Deathgel Shivs", "Dense Katars", "Iron Katars", "Runic Katars", "Demicrypt Bow", "Iron Bow", "Mekspike Bow", "Menace Bow" }, new List { "Geistlord Claws", "Hellsludge Shivs", "Petrified Bow", "Necroroyal Bow" }, new List { "Mithril Katars", "Frostbite Claws", "Serrated Knuckles", "Mithril Bow", "Coldgeist Bow", "Serrated Longbow", "Amberite Boomstick" }, new List { "Rummok Bladerings", "Sapphite Katars", "Golemfist Katars", "Torrentius Longbow", "Magitek Burstgun" }, new List { "Follycannon" } } }, { "Progressive Bandit Chest Piece", new List> { new List { "Reapsow Garb" }, new List { "Reaper Gi", "Fuguefall Duster" }, new List { "Fender Garb" } } }, { "Progressive Bandit Leggings", new List> { new List { "Reapsow Pants" }, new List { "Reaper Leggings", "Fuguefall Pants" }, new List { "Fender Leggings" } } } }; public static ProgressivePortalRequirement GetProgressivePortalRequirement(string portalKey) { if (PortalProgressiveRequirements.TryGetValue(portalKey, out var value)) { return value; } return new ProgressivePortalRequirement(0, 0); } public static bool IsProgressivePortalItem(string itemName) { if (!(itemName == "Progressive Sanctum Portal")) { return itemName == "Progressive Tuul Portal"; } return true; } internal static bool IsApQuestFinished(Func hasFinishedQuest, string apQuestName) { if (hasFinishedQuest(apQuestName)) { return true; } if (!ApQuestVanillaCompletionNames.TryGetValue(apQuestName, out string[] value)) { return false; } string[] array = value; foreach (string arg in array) { if (hasFinishedQuest(arg)) { return true; } } return false; } internal static bool IsApQuestFinishedInProfileJson(string finishedQuestsJson, string apQuestName) { if (finishedQuestsJson.Contains("\"" + apQuestName + "\"")) { return true; } if (!ApQuestVanillaCompletionNames.TryGetValue(apQuestName, out string[] value)) { return false; } string[] array = value; foreach (string text in array) { if (finishedQuestsJson.Contains("\"" + text + "\"")) { return true; } } return false; } private static KeyValuePair[] BuildAllQuestToLocationPairs() { KeyValuePair[] array = new KeyValuePair[AllQuestToLocation.Count]; int num = 0; foreach (KeyValuePair item in AllQuestToLocation) { array[num++] = item; } return array; } } internal static class ArchipelagoItemProgression { private static readonly HashSet[] ClassFilterSets = new HashSet[7] { null, new HashSet { 'F' }, new HashSet { 'M' }, new HashSet { 'B' }, new HashSet { 'F', 'M' }, new HashSet { 'F', 'B' }, new HashSet { 'M', 'B' } }; internal static readonly Dictionary EquipmentItemTiers = new Dictionary { { "Acolyte Hood", 1 }, { "Aero Pants", 1 }, { "Aero Top", 1 }, { "Agility Ears", 1 }, { "Amberite Boomstick", 3 }, { "Amberite Breastplate", 3 }, { "Amberite Halberd", 3 }, { "Amberite Helm", 3 }, { "Amberite Leggings", 3 }, { "Amberite Shield", 3 }, { "Amberite Sword", 3 }, { "Amberite Warstar", 3 }, { "Ambersquire Ring", 2 }, { "Apprentice Robe", 2 }, { "Aquapetal Staff", 4 }, { "Berserker Chestpiece", 4 }, { "Berserker Leggings", 4 }, { "Blueversa Cape", 4 }, { "Boarus Helm", 5 }, { "Boarus Torment", 5 }, { "Carbuncle Hat", 4 }, { "Carbuncle Robe", 4 }, { "Chainmail Guard", 3 }, { "Chainmail Leggings", 3 }, { "Chainscale Chest", 4 }, { "Cobblerage Cloak", 4 }, { "Coldgeist Blade", 4 }, { "Coldgeist Bow", 4 }, { "Coldgeist Frostcaller", 4 }, { "Coldgeist Punisher", 4 }, { "Colossus Tone", 4 }, { "Cozy Cloak", 3 }, { "Cryo Cane", 2 }, { "Cryotribe Spear", 5 }, { "Crypt Blade", 1 }, { "Crypt Bow", 1 }, { "Crypt Buckler", 1 }, { "Crypt Pounder", 2 }, { "Cryptcall Bell", 2 }, { "Cryptsinge Chest", 1 }, { "Cryptsinge Halberd", 2 }, { "Cryptsinge Halo", 1 }, { "Cryptsinge Katars", 1 }, { "Daemon Shield", 5 }, { "Darkcloth Pants", 3 }, { "Dawn Mace", 2 }, { "Deadwood Axe", 4 }, { "Deathgel Shivs", 2 }, { "Deathknight Helm", 5 }, { "Deathknight Runeblade", 5 }, { "Deathward Cape", 4 }, { "Demicrypt Bauble", 2 }, { "Demicrypt Blade", 2 }, { "Demicrypt Bow", 2 }, { "Demicrypt Buckler", 2 }, { "Demicrypt Halo", 2 }, { "Dense Chestpiece", 2 }, { "Dense Hammer", 2 }, { "Dense Helm", 2 }, { "Dense Katars", 2 }, { "Dense Leggings", 2 }, { "Dense Mace", 2 }, { "Dense Shield", 2 }, { "Dense Spear", 2 }, { "Dire Helm", 5 }, { "Diva Crown", 2 }, { "Dolkin's Axe", 3 }, { "Druidic Halo", 5 }, { "Druidic Robe", 4 }, { "Duelist Garb", 2 }, { "Earthbind Tabard", 5 }, { "Earthwoken Ring", 4 }, { "Edon's Pendant", 2 }, { "Emeraldfocus Ring", 2 }, { "Emerock Chestpiece", 4 }, { "Emerock Halo", 5 }, { "Eschek Greaves", 5 }, { "Executioner Leggings", 5 }, { "Executioner Vestment", 5 }, { "Femur Club", 1 }, { "Fender Garb", 5 }, { "Fender Leggings", 5 }, { "Fier Blade", 5 }, { "Firebreath Blade", 5 }, { "Flamepetal Staff", 4 }, { "Flametribe Spear", 5 }, { "Flux Cloak", 3 }, { "Focus Circlet", 3 }, { "Focusi Glasses", 3 }, { "Follycannon", 5 }, { "Forlorn Cloak", 4 }, { "Fortified Vestment", 4 }, { "Frostbite Claws", 4 }, { "Fuguefall Duster", 4 }, { "Fuguefall Pants", 4 }, { "Geist Scythe", 2 }, { "Geistlord Band", 4 }, { "Geistlord Claws", 3 }, { "Geistlord Crown", 2 }, { "Geistlord Eye", 4 }, { "Geistlord Ring", 3 }, { "Gemveil Breastplate", 5 }, { "Gemveil Leggings", 5 }, { "Gemveil Raiment", 4 }, { "Ghostly Legwraps", 1 }, { "Ghostly Tabard", 1 }, { "Gilded Sword", 1 }, { "Glyphgrift Halo", 4 }, { "Glyphik Booklet", 5 }, { "Golem Chestpiece", 3 }, { "Golemfist Katars", 4 }, { "Greenversa Cape", 4 }, { "Guardel Helm", 5 }, { "Hellsludge Shivs", 3 }, { "Initiate Cloak", 1 }, { "Initiate Spectacles", 1 }, { "Iris Shield", 2 }, { "Irisun Shield", 5 }, { "Iron Axehammer", 2 }, { "Iron Bell", 2 }, { "Iron Bow", 2 }, { "Iron Chestpiece", 2 }, { "Iron Halo", 2 }, { "Iron Katars", 2 }, { "Iron Scepter", 2 }, { "Iron Shield", 2 }, { "Iron Spear", 2 }, { "Iron Sword", 2 }, { "Ironbark Sword", 1 }, { "Jadetrout Ring", 4 }, { "Jadewail Trousers", 4 }, { "Jestercast Memory", 4 }, { "Journeyman Leggings", 1 }, { "Journeyman Shorts", 1 }, { "Journeyman Spectacles", 2 }, { "Journeyman Vest", 1 }, { "King Breastplate", 4 }, { "King Greaves", 4 }, { "Knightguard Halo", 4 }, { "Leathen Cap", 5 }, { "Leather Britches", 1 }, { "Leather Cap", 1 }, { "Leather Top", 1 }, { "Lord Breastplate", 3 }, { "Lord Greaves", 3 }, { "Magilord Boots", 4 }, { "Magilord Overalls", 4 }, { "Magistrate Circlet", 3 }, { "Magitek Burstgun", 4 }, { "Marrow Bauble", 1 }, { "Mekspear", 2 }, { "Mekspike Bow", 2 }, { "Mekwar Drape", 5 }, { "Menace Bow", 2 }, { "Mercenary Leggings", 4 }, { "Mercenary Vestment", 4 }, { "Meshlink Cape", 4 }, { "Mini Geist Scythe", 1 }, { "Mithril Bell", 4 }, { "Mithril Bow", 3 }, { "Mithril Chestpiece", 4 }, { "Mithril Greatsword", 4 }, { "Mithril Halberd", 4 }, { "Mithril Halo", 4 }, { "Mithril Katars", 3 }, { "Mithril Scepter", 4 }, { "Mithril Shield", 3 }, { "Mithril Sword", 4 }, { "Monolith Chestpiece", 4 }, { "Necro Caustics", 1 }, { "Necro Marrow", 1 }, { "Necromancer Hood", 2 }, { "Necroroyal Bow", 3 }, { "Necroroyal Halberd", 3 }, { "Nethercrypt Bauble", 3 }, { "Nethercrypt Blade", 3 }, { "Nethercrypt Cloak", 3 }, { "Nethercrypt Halo", 3 }, { "Nethercrypt Shield", 3 }, { "Nethercrypt Tabard", 3 }, { "Newfold Halo", 1 }, { "Nograd's Amulet", 1 }, { "Noji Talisman", 4 }, { "Nokket Cloak", 2 }, { "Nulrok Mace", 4 }, { "Nulrok Spear", 4 }, { "Nulversa Cape", 4 }, { "Nutso Pants", 1 }, { "Nutso Top", 1 }, { "Old Ring", 1 }, { "Omen Shield", 2 }, { "Orbos Ring", 4 }, { "Ornamented Battlerobe", 3 }, { "Pearlpond Ring", 3 }, { "Petrified Bow", 3 }, { "Poacher Cloth", 1 }, { "Poltergeist Scythe", 3 }, { "Pyre Cane", 3 }, { "Quake Pummeler", 4 }, { "Rage Circlet", 3 }, { "Ragespear", 4 }, { "Ragged Shirt", 1 }, { "Reaper Gi", 4 }, { "Reaper Leggings", 4 }, { "Reapsow Garb", 3 }, { "Reapsow Pants", 3 }, { "Redversa Cape", 4 }, { "Regazuul Cape", 2 }, { "Rigor Buckler", 4 }, { "Ring Of Ambition", 1 }, { "Roudon Cape", 4 }, { "Roudon Chestpiece", 4 }, { "Roudon Robe", 5 }, { "Rude Blade", 2 }, { "Rugged Cloak", 2 }, { "Ruggrok Vest", 5 }, { "Rummok Bladerings", 4 }, { "Runic Katars", 2 }, { "Rustweary Shield", 4 }, { "Rustwise Shield", 4 }, { "Ryzer Greataxe", 5 }, { "Sagecaller Cape", 4 }, { "Sagecloth Shorts", 1 }, { "Sagecloth Top", 1 }, { "Sapphireweave Ring", 2 }, { "Sapphite Bell", 4 }, { "Sapphite Guard", 4 }, { "Sapphite Katars", 4 }, { "Sapphite Leggings", 4 }, { "Sapphite Mindhat", 4 }, { "Sapphite Scepter", 4 }, { "Sapphite Shield", 4 }, { "Sapphite Spear", 4 }, { "Sash Leggings", 2 }, { "Serrated Blade", 4 }, { "Serrated Knuckles", 4 }, { "Serrated Longbow", 4 }, { "Serrated Spear", 4 }, { "Sinner Bardiche", 3 }, { "Skywrill Tabard", 2 }, { "Slabton Shield", 3 }, { "Sleeper's Robe", 2 }, { "Slime Diva Baton", 2 }, { "Slimecrust Blade", 1 }, { "Slimecrust Chest", 1 }, { "Slimecrust Katars", 1 }, { "Slimecrust Leggings", 1 }, { "Slimek Axehammer", 1 }, { "Slimek Chest", 1 }, { "Slimek Leggings", 1 }, { "Slimek Shield", 1 }, { "Slimek Shivs", 1 }, { "Slimewoven Cloak", 1 }, { "Slitherwraith Ring", 3 }, { "Splitbark Club", 1 }, { "Splitbark Scepter", 1 }, { "Stone Greatblade", 2 }, { "Stridebond Pants", 4 }, { "Students Ring", 3 }, { "Tattered Battlerobe", 2 }, { "Temrak Britches", 4 }, { "Tessellated Drive", 5 }, { "The One Ring", 1 }, { "Torrentius Longbow", 5 }, { "Trodd Tunic", 2 }, { "Valdur Blade", 5 }, { "Valdur Effigy", 5 }, { "Valor Ring", 4 }, { "Vile Blade", 2 }, { "Voalstark Wand", 5 }, { "Warrior Chest", 2 }, { "Warrior Leggings", 2 }, { "Windgolem Cloak", 5 }, { "Witchlock Loincloth", 3 }, { "Witchlock Robe", 3 }, { "Witchwizard Garterbelt", 4 }, { "Witchwizard Robe", 4 }, { "Wizlad Hood", 5 }, { "Wizlad Robe", 5 }, { "Wizwand", 3 }, { "Wooden Shield", 1 }, { "Worn Robe", 1 } }; internal static readonly Dictionary EquipmentClassAffinity = new Dictionary { { "Amberite Boomstick", "B" }, { "Amberite Halberd", "F" }, { "Amberite Shield", "FM" }, { "Amberite Sword", "F" }, { "Amberite Warstar", "F" }, { "Aquapetal Staff", "M" }, { "Berserker Chestpiece", "F" }, { "Berserker Leggings", "F" }, { "Coldgeist Blade", "F" }, { "Coldgeist Bow", "B" }, { "Coldgeist Frostcaller", "M" }, { "Coldgeist Punisher", "F" }, { "Colossus Tone", "M" }, { "Cryo Cane", "M" }, { "Cryotribe Spear", "F" }, { "Crypt Blade", "F" }, { "Crypt Bow", "B" }, { "Crypt Buckler", "FM" }, { "Crypt Pounder", "F" }, { "Cryptcall Bell", "M" }, { "Cryptsinge Halberd", "F" }, { "Cryptsinge Katars", "B" }, { "Daemon Shield", "FM" }, { "Dawn Mace", "F" }, { "Deadwood Axe", "F" }, { "Deathgel Shivs", "B" }, { "Deathknight Runeblade", "F" }, { "Demicrypt Bauble", "M" }, { "Demicrypt Blade", "F" }, { "Demicrypt Bow", "B" }, { "Demicrypt Buckler", "FM" }, { "Dense Hammer", "F" }, { "Dense Katars", "B" }, { "Dense Mace", "F" }, { "Dense Shield", "FM" }, { "Dense Spear", "F" }, { "Dolkin's Axe", "F" }, { "Executioner Leggings", "F" }, { "Executioner Vestment", "F" }, { "Femur Club", "F" }, { "Fender Garb", "B" }, { "Fender Leggings", "B" }, { "Fier Blade", "F" }, { "Firebreath Blade", "F" }, { "Flamepetal Staff", "M" }, { "Flametribe Spear", "F" }, { "Focus Circlet", "M" }, { "Focusi Glasses", "M" }, { "Follycannon", "B" }, { "Frostbite Claws", "B" }, { "Fuguefall Duster", "B" }, { "Fuguefall Pants", "B" }, { "Geist Scythe", "F" }, { "Geistlord Claws", "B" }, { "Gilded Sword", "F" }, { "Golemfist Katars", "B" }, { "Hellsludge Shivs", "B" }, { "Iris Shield", "FM" }, { "Irisun Shield", "FM" }, { "Iron Axehammer", "F" }, { "Iron Bell", "M" }, { "Iron Bow", "B" }, { "Iron Katars", "B" }, { "Iron Scepter", "M" }, { "Iron Shield", "FM" }, { "Iron Spear", "F" }, { "Iron Sword", "F" }, { "Ironbark Sword", "F" }, { "King Breastplate", "F" }, { "King Greaves", "F" }, { "Lord Breastplate", "F" }, { "Lord Greaves", "F" }, { "Magilord Boots", "M" }, { "Magilord Overalls", "M" }, { "Magitek Burstgun", "B" }, { "Marrow Bauble", "M" }, { "Mekspear", "F" }, { "Mekspike Bow", "B" }, { "Menace Bow", "B" }, { "Mini Geist Scythe", "F" }, { "Mithril Bell", "M" }, { "Mithril Bow", "B" }, { "Mithril Greatsword", "F" }, { "Mithril Halberd", "F" }, { "Mithril Katars", "B" }, { "Mithril Scepter", "M" }, { "Mithril Shield", "FM" }, { "Mithril Sword", "F" }, { "Necroroyal Bow", "B" }, { "Necroroyal Halberd", "F" }, { "Nethercrypt Bauble", "M" }, { "Nethercrypt Blade", "F" }, { "Nethercrypt Shield", "FM" }, { "Nulrok Mace", "F" }, { "Nulrok Spear", "F" }, { "Omen Shield", "FM" }, { "Petrified Bow", "B" }, { "Poltergeist Scythe", "F" }, { "Pyre Cane", "M" }, { "Quake Pummeler", "F" }, { "Ragespear", "F" }, { "Reaper Gi", "B" }, { "Reaper Leggings", "B" }, { "Reapsow Garb", "B" }, { "Reapsow Pants", "B" }, { "Rigor Buckler", "FM" }, { "Rude Blade", "F" }, { "Rummok Bladerings", "B" }, { "Runic Katars", "B" }, { "Rustweary Shield", "FM" }, { "Rustwise Shield", "FM" }, { "Ryzer Greataxe", "F" }, { "Sapphite Bell", "M" }, { "Sapphite Katars", "B" }, { "Sapphite Mindhat", "M" }, { "Sapphite Scepter", "M" }, { "Sapphite Shield", "FM" }, { "Sapphite Spear", "F" }, { "Serrated Blade", "F" }, { "Serrated Knuckles", "B" }, { "Serrated Longbow", "B" }, { "Serrated Spear", "F" }, { "Sinner Bardiche", "F" }, { "Slabton Shield", "FM" }, { "Slime Diva Baton", "M" }, { "Slimecrust Blade", "F" }, { "Slimecrust Katars", "B" }, { "Slimek Axehammer", "F" }, { "Slimek Shield", "FM" }, { "Slimek Shivs", "B" }, { "Splitbark Club", "F" }, { "Splitbark Scepter", "M" }, { "Stone Greatblade", "F" }, { "Torrentius Longbow", "B" }, { "Valdur Blade", "F" }, { "Vile Blade", "F" }, { "Voalstark Wand", "M" }, { "Witchlock Loincloth", "M" }, { "Witchlock Robe", "M" }, { "Witchwizard Garterbelt", "M" }, { "Witchwizard Robe", "M" }, { "Wizlad Hood", "M" }, { "Wizlad Robe", "M" }, { "Wizwand", "M" }, { "Wooden Shield", "FM" } }; internal static int LevelToMaxTier(int level) { if (level <= 5) { return 1; } if (level <= 10) { return 2; } if (level <= 15) { return 3; } if (level <= 20) { return 4; } return 5; } internal static bool TryGetEquipmentTier(string itemName, out int tier) { return EquipmentItemTiers.TryGetValue(itemName, out tier); } internal static bool TryGetClassAffinity(string itemName, out string affinity) { return EquipmentClassAffinity.TryGetValue(itemName, out affinity); } internal static bool ItemPassesClassFilter(int classFilterOption, string itemName) { if (classFilterOption <= 0 || classFilterOption >= ClassFilterSets.Length) { return true; } if (!TryGetClassAffinity(itemName, out string affinity)) { return true; } HashSet hashSet = ClassFilterSets[classFilterOption]; string text = affinity; foreach (char item in text) { if (hashSet.Contains(item)) { return true; } } return false; } internal static bool ItemPassesGatedLevel(int playerLevel, string itemName) { if (!TryGetEquipmentTier(itemName, out var tier)) { return true; } return tier <= LevelToMaxTier(playerLevel); } } internal sealed class DeferredEquipmentGrant { public string ItemName; public string TraceId; public bool IsProgressiveEquipment; } public class ArchipelagoPortalUnlocks : MonoBehaviour { private struct PortalData { public string portalCaption; public string spawnID; public string sceneName; } private const string PortalContainerName = "_PORTALS"; private const string PortalContainerAltName = "_PORTAL"; private const string SanctumCatacombsMapName = "map_dungeon00_sanctumCatacombs"; private const string CrescentGroveMapName = "map_dungeon01_crescentGrove"; private const string MainMenuRootSceneName = "01_rootScene"; private static readonly string[] ProgressivePortalScenes = new string[11] { "Assets/Scenes/00_zone_forest/_zone00_outerSanctum.unity", "Assets/Scenes/00_zone_forest/_zone00_effoldTerrace.unity", "Assets/Scenes/00_zone_forest/_zone00_arcwoodPass.unity", "Assets/Scenes/00_zone_forest/_zone00_tuulValley.unity", "Assets/Scenes/00_zone_forest/_zone00_crescentRoad.unity", "Assets/Scenes/map_dungeon00_sanctumCatacombs.unity", "Assets/Scenes/00_zone_forest/_zone00_luvoraGarden.unity", "Assets/Scenes/00_zone_forest/_zone00_crescentKeep.unity", "Assets/Scenes/00_zone_forest/_zone00_tuulEnclave.unity", "Assets/Scenes/00_zone_forest/_zone00_bularFortress.unity", "Assets/Scenes/map_dungeon01_crescentGrove.unity" }; private static readonly Dictionary CanonicalUnlockedPortalDestinations = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Outer Sanctum", new PortalData { portalCaption = "Outer Sanctum", spawnID = "spawnPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_outerSanctum.unity" } }, { "Arcwood Pass", new PortalData { portalCaption = "Arcwood Pass", spawnID = "spawnPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_arcwoodPass.unity" } }, { "Effold Terrace", new PortalData { portalCaption = "Effold Terrace", spawnID = "startPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_effoldTerrace.unity" } }, { "Tuul Valley", new PortalData { portalCaption = "Tuul Valley", spawnID = "spawnPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_tuulValley.unity" } }, { "Crescent Road", new PortalData { portalCaption = "Crescent Road", spawnID = "spawnPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_crescentRoad.unity" } }, { "Sanctum Catacombs", new PortalData { portalCaption = "Sanctum Catacombs", spawnID = "spawnPoint", sceneName = "Assets/Scenes/map_dungeon00_sanctumCatacombs.unity" } }, { "Luvora Garden", new PortalData { portalCaption = "Luvora Garden", spawnID = "startPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_luvoraGarden.unity" } }, { "Crescent Keep", new PortalData { portalCaption = "Crescent Keep", spawnID = "startPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_crescentKeep.unity" } }, { "Tuul Enclave", new PortalData { portalCaption = "Tuul Enclave", spawnID = "spawnPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_tuulEnclave.unity" } }, { "Bularr Fortress", new PortalData { portalCaption = "Bularr Fortress", spawnID = "startPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_bularFortress.unity" } }, { "Crescent Grove", new PortalData { portalCaption = "Crescent Grove", spawnID = "startPoint", sceneName = "Assets/Scenes/map_dungeon01_crescentGrove.unity" } } }; private AtlyssArchipelagoPlugin basePlugin; private List lockedScenes = new List(); private Dictionary LockedDataToPortalData = new Dictionary(); private Dictionary PortalDataToLockedData = new Dictionary { { new PortalData { portalCaption = "Outer Sanctum", spawnID = "spawnPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_outerSanctum.unity" }, new PortalData { portalCaption = "Outer Sanctum (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_sanctum.unity" } }, { new PortalData { portalCaption = "Sanctum Arena", spawnID = "spawnPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_sanctumArena.unity" }, new PortalData { portalCaption = "Sanctum Arena (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_sanctum.unity" } }, { new PortalData { portalCaption = "Sanctum", spawnID = "gatePoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_sanctum.unity" }, new PortalData { portalCaption = "Sanctum (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_outerSanctum.unity" } }, { new PortalData { portalCaption = "Effold Terrace", spawnID = "startPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_effoldTerrace.unity" }, new PortalData { portalCaption = "Effold Terrace (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_outerSanctum.unity" } }, { new PortalData { portalCaption = "Arcwood Pass", spawnID = "spawnPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_arcwoodPass.unity" }, new PortalData { portalCaption = "Arcwood Pass (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_outerSanctum.unity" } }, { new PortalData { portalCaption = "Tuul Valley", spawnID = "spawnPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_tuulValley.unity" }, new PortalData { portalCaption = "Tuul Valley (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_outerSanctum.unity" } }, { new PortalData { portalCaption = "Outer Sanctum", spawnID = "arcwoodSpawn", sceneName = "Assets/Scenes/00_zone_forest/_zone00_outerSanctum.unity" }, new PortalData { portalCaption = "Outer Sanctum (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_arcwoodPass.unity" } }, { new PortalData { portalCaption = "Crescent Road", spawnID = "spawnPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_crescentRoad.unity" }, new PortalData { portalCaption = "Crescent Road (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_arcwoodPass.unity" } }, { new PortalData { portalCaption = "Executioner's Tomb", spawnID = "spawnPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_catacombsArena.unity" }, new PortalData { portalCaption = "Executioner's Tomb (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_arcwoodPass.unity" } }, { new PortalData { portalCaption = "Sanctum Catacombs", spawnID = "", sceneName = "Assets/Scenes/map_dungeon00_sanctumCatacombs.unity" }, new PortalData { portalCaption = "Sanctum Catacombs (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_arcwoodPass.unity" } }, { new PortalData { portalCaption = "Arcwood Pass", spawnID = "keepSpawn", sceneName = "Assets/Scenes/00_zone_forest/_zone00_arcwoodPass.unity" }, new PortalData { portalCaption = "Arcwood Pass (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_crescentRoad.unity" } }, { new PortalData { portalCaption = "Luvora Garden", spawnID = "startPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_luvoraGarden.unity" }, new PortalData { portalCaption = "Luvora Garden (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_crescentRoad.unity" } }, { new PortalData { portalCaption = "Crescent Keep", spawnID = "startPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_crescentKeep.unity" }, new PortalData { portalCaption = "Crescent Keep (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_crescentRoad.unity" } }, { new PortalData { portalCaption = "Outer Sanctum", spawnID = "terraceSpawn", sceneName = "Assets/Scenes/00_zone_forest/_zone00_outerSanctum.unity" }, new PortalData { portalCaption = "Outer Sanctum (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_effoldTerrace.unity" } }, { new PortalData { portalCaption = "Outer Sanctum", spawnID = "tullValleyPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_outerSanctum.unity" }, new PortalData { portalCaption = "Outer Sanctum (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_tuulValley.unity" } }, { new PortalData { portalCaption = "Tuul Enclave", spawnID = "spawnPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_tuulEnclave.unity" }, new PortalData { portalCaption = "Tuul Enclave (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_tuulValley.unity" } }, { new PortalData { portalCaption = "Tuul Valley", spawnID = "enclavePoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_tuulValley.unity" }, new PortalData { portalCaption = "Tuul Valley (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_tuulEnclave.unity" } }, { new PortalData { portalCaption = "Bularr Fortress", spawnID = "startPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_bularFortress.unity" }, new PortalData { portalCaption = "Bularr Fortress (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_tuulEnclave.unity" } }, { new PortalData { portalCaption = "Tuul Enclave", spawnID = "fortSpawn", sceneName = "Assets/Scenes/00_zone_forest/_zone00_tuulEnclave.unity" }, new PortalData { portalCaption = "Tuul Enclave (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_bularFortress.unity" } }, { new PortalData { portalCaption = "Crescent Road", spawnID = "gardenPoint", sceneName = "Assets/Scenes/00_zone_forest/_zone00_crescentRoad.unity" }, new PortalData { portalCaption = "Crescent Road (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_luvoraGarden.unity" } }, { new PortalData { portalCaption = "Crescent Road", spawnID = "keepSpawn", sceneName = "Assets/Scenes/00_zone_forest/_zone00_crescentRoad.unity" }, new PortalData { portalCaption = "Crescent Road (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_crescentKeep.unity" } }, { new PortalData { portalCaption = "Crescent Grove", spawnID = "startPoint", sceneName = "Assets/Scenes/map_dungeon01_crescentGrove.unity" }, new PortalData { portalCaption = "Crescent Grove (Locked!)", spawnID = "", sceneName = "Assets/Scenes/00_zone_forest/_zone00_crescentKeep.unity" } } }; private bool _deferPortalRefreshWhileShopOpen; private void Awake() { basePlugin = AtlyssArchipelagoPlugin.Instance; SceneManager.sceneLoaded += OnSceneLoaded; foreach (KeyValuePair portalDataToLockedDatum in PortalDataToLockedData) { LockedDataToPortalData[portalDataToLockedDatum.Value] = portalDataToLockedDatum.Key; } } private void OnSceneLoaded(Scene s, LoadSceneMode m) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) ((MonoBehaviour)this).StartCoroutine(EnforcePortalLocksWhenReady(s)); } private IEnumerator EnforcePortalLocksWhenReady(Scene newScene) { //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) if (!ShouldManagePortalsInScene(newScene)) { yield break; } yield return (object)new WaitUntil((Func)(() => ((Scene)(ref newScene)).isLoaded)); float connectDeadline = Time.unscaledTime + 30f; while (!ShouldEnforcePortalLocks() && Time.unscaledTime < connectDeadline) { yield return null; } if (!ShouldEnforcePortalLocks()) { yield break; } float deadline = Time.unscaledTime + 5f; while (Time.unscaledTime < deadline) { if (!ShouldEnforcePortalLocks() || TryApplyPortalLocksToScene(newScene)) { yield break; } yield return null; } if (ShouldEnforcePortalLocks()) { AtlyssArchipelagoPlugin.SLogWarn("Portal lock pass timed out for scene " + ((Scene)(ref newScene)).name + " (_PORTALS not found)."); } } private bool ShouldEnforcePortalLocks() { return basePlugin.PortalLockEnforcementActive; } private static bool ShouldManagePortalsInScene(Scene scene) { if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded) { return false; } if (((Scene)(ref scene)).name != "map_dungeon00_sanctumCatacombs" && ((Scene)(ref scene)).name != "map_dungeon01_crescentGrove") { return ((Scene)(ref scene)).name != "01_rootScene"; } return false; } private GameObject FindPortalContainerInScene(Scene scene) { GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); foreach (GameObject val in rootGameObjects) { if (((Object)val).name == "_PORTALS" || ((Object)val).name == "_PORTAL") { return val; } } return null; } private bool TryApplyPortalLocksToScene(Scene scene) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown if (!ShouldManagePortalsInScene(scene) || !ShouldEnforcePortalLocks()) { return true; } GameObject val = FindPortalContainerInScene(scene); if ((Object)val == (Object)null) { return false; } int num = 0; for (int i = 0; i < val.transform.childCount; i++) { Portal component = ((Component)val.transform.GetChild(i)).GetComponent(); if ((Object)component == (Object)null) { continue; } ScenePortalData scenePortal = component._scenePortal; if (scenePortal != null && lockedScenes.Contains(scenePortal._subScene)) { PortalData key = new PortalData { portalCaption = scenePortal._portalCaptionTitle, spawnID = scenePortal._spawnPointTag, sceneName = scenePortal._subScene }; if (PortalDataToLockedData.TryGetValue(key, out var value)) { scenePortal._portalCaptionTitle = value.portalCaption; scenePortal._spawnPointTag = value.spawnID; scenePortal._subScene = value.sceneName; num++; } else { AtlyssArchipelagoPlugin.SLogError("The portal to " + scenePortal._portalCaptionTitle + " wasn't found in the dictionary!"); } } } if (num > 0) { AtlyssArchipelagoPlugin.SLogInfo($"Applied {num} portal lock(s) in {((Scene)(ref scene)).name}"); } return true; } public void ApplyPortalLocksToAllLoadedScenes() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (!ShouldEnforcePortalLocks()) { return; } bool flag = false; for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); if (ShouldManagePortalsInScene(sceneAt) && !TryApplyPortalLocksToScene(sceneAt)) { flag = true; } } RefreshPortalsInAllLoadedScenes(); if (flag) { ((MonoBehaviour)this).StartCoroutine(RetryApplyPortalLocksToAllLoadedScenes()); } } private IEnumerator RetryApplyPortalLocksToAllLoadedScenes() { float deadline = Time.unscaledTime + 5f; while (Time.unscaledTime < deadline && ShouldEnforcePortalLocks()) { bool flag = false; for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); if (ShouldManagePortalsInScene(sceneAt) && !TryApplyPortalLocksToScene(sceneAt)) { flag = true; } } RefreshPortalsInAllLoadedScenes(); if (!flag) { break; } yield return null; } } private IEnumerator EnforcePortalLocks(Scene newScene) { //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) yield return EnforcePortalLocksWhenReady(newScene); } private void RefreshPortalsInScene(Scene scene) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Expected O, but got Unknown if (!((Scene)(ref scene)).isLoaded) { return; } GameObject val = null; GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); foreach (GameObject val2 in rootGameObjects) { if (((Object)val2).name == "_PORTALS" || ((Object)val2).name == "_PORTAL") { val = val2; break; } } if ((Object)val == (Object)null) { return; } int num = 0; for (int j = 0; j < val.transform.childCount; j++) { Portal component = ((Component)val.transform.GetChild(j)).GetComponent(); if ((Object)component == (Object)null) { continue; } ScenePortalData scenePortal = component._scenePortal; NormalizeUnlockedPortalRoute(scenePortal); if (scenePortal._portalCaptionTitle.EndsWith("(Locked!)")) { PortalData key = new PortalData { portalCaption = scenePortal._portalCaptionTitle, spawnID = scenePortal._spawnPointTag, sceneName = scenePortal._subScene }; if (LockedDataToPortalData.TryGetValue(key, out var value) && !lockedScenes.Contains(value.sceneName)) { scenePortal._portalCaptionTitle = value.portalCaption; scenePortal._spawnPointTag = value.spawnID; scenePortal._subScene = value.sceneName; num++; AtlyssArchipelagoPlugin.SLogInfo("Portal restored in real-time: " + value.portalCaption); } } } if (num > 0) { AtlyssArchipelagoPlugin.SLogInfo($"Refreshed {num} portal(s) in {((Scene)(ref scene)).name} without reload"); } } public void RefreshPortalsInAllLoadedScenes() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); if (((Scene)(ref sceneAt)).isLoaded) { RefreshPortalsInScene(sceneAt); } } } private void NormalizeUnlockedPortalRoute(ScenePortalData scenePortal) { if (scenePortal == null) { return; } string text = scenePortal._portalCaptionTitle ?? ""; bool flag = text.EndsWith("(Locked!)", StringComparison.OrdinalIgnoreCase); string key = (flag ? text.Substring(0, text.Length - " (Locked!)".Length) : text); if (CanonicalUnlockedPortalDestinations.TryGetValue(key, out var value) && !lockedScenes.Contains(value.sceneName)) { bool flag2 = false; if (scenePortal._subScene != value.sceneName) { scenePortal._subScene = value.sceneName; flag2 = true; } if (string.IsNullOrWhiteSpace(scenePortal._spawnPointTag)) { scenePortal._spawnPointTag = value.spawnID; flag2 = true; } if (flag) { scenePortal._portalCaptionTitle = value.portalCaption; flag2 = true; } if (flag2) { AtlyssArchipelagoPlugin.SLogWarn("Corrected portal routing after unlock: " + value.portalCaption); } } } public void ApplyAreaAccessMode() { string text = (basePlugin.randomPortalsEnabled ? "Random Portals" : "Progressive Portals"); AtlyssArchipelagoPlugin.SLogInfo("Portal Mode: " + text + " - Locking all portals"); lockedScenes.Clear(); string[] progressivePortalScenes = ProgressivePortalScenes; foreach (string sceneName in progressivePortalScenes) { BlockAccessToScene(sceneName); } } public void BlockAccessToScene(string sceneName) { if (!lockedScenes.Contains(sceneName)) { lockedScenes.Add(sceneName); AtlyssArchipelagoPlugin.SLogInfo(sceneName + " has been locked by Archipelago"); } } public void UnblockAccessToScene(string sceneName) { if (lockedScenes.Contains(sceneName)) { lockedScenes.Remove(sceneName); AtlyssArchipelagoPlugin.SLogInfo(sceneName + " is no longer being locked by Archipelago"); } RequestPortalSceneRefresh(); } private void RequestPortalSceneRefresh() { if (AtlyssArchipelagoPlugin.IsShopInterfaceOpen()) { _deferPortalRefreshWhileShopOpen = true; return; } _deferPortalRefreshWhileShopOpen = false; RefreshPortalsInAllLoadedScenes(); } public void FlushDeferredPortalRefresh() { if (_deferPortalRefreshWhileShopOpen) { _deferPortalRefreshWhileShopOpen = false; RefreshPortalsInAllLoadedScenes(); } } public bool IsSceneLocked(string sceneName) { return lockedScenes.Contains(sceneName); } } public sealed class ArchipelagoProfessionTools { private enum ToolKind { FishingRod, Pickaxe } public const string BuyFishingRodLocation = "Buy Fishing Rod"; public const string BuyPickaxeLocation = "Buy Pickaxe"; public const string FishingRodItem = "Fishing Rod"; public const string PickaxeItem = "Pickaxe"; private static readonly string[] FishingRodShopNames = new string[2] { "Fishing Rod", "Wooden Fishing Rod" }; private static readonly string[] PickaxeShopNames = new string[3] { "Pickaxe", "Mining Pickaxe", "Wooden Pickaxe" }; private const string PoolToolRepeatPurchaseMessage = "Profession Tools: Pool. Your tool comes from the multiworld — the shop check is already sent; you cannot buy another here."; private const string StaticToolRepeatPurchaseMessage = "You already have this profession tool — you cannot buy another here."; public const string CannotSellProfessionToolMessage = "Profession Tools: You cannot sell the Fishing Rod or Pickaxe. Check your inventory or Spike storage."; private readonly AtlyssArchipelagoPlugin _plugin; public bool IsPoolMode => _plugin.professionToolsOption == 1; public static bool IsProfessionToolItemName(string itemName) { if (string.IsNullOrWhiteSpace(itemName)) { return false; } if (!string.Equals(itemName, "Fishing Rod", StringComparison.OrdinalIgnoreCase) && !string.Equals(itemName, "Pickaxe", StringComparison.OrdinalIgnoreCase) && !MatchesToolName(itemName, FishingRodShopNames)) { return MatchesToolName(itemName, PickaxeShopNames); } return true; } public ArchipelagoProfessionTools(AtlyssArchipelagoPlugin plugin) { _plugin = plugin; } public bool TryBlockProfessionToolDiscard(ItemData itemData) { if ((Object)(object)_plugin == (Object)null || !_plugin.connected) { return false; } if (!TryResolveToolKind(itemData?._itemName, null, out var _)) { return false; } ShowShopError("Profession Tools: You cannot sell the Fishing Rod or Pickaxe. Check your inventory or Spike storage."); return true; } public bool TryHandleVanillaToolPurchase(ShopkeepManager shop, ScriptableItem scriptableItem, ShopListDataEntry setEntry) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Expected O, but got Unknown //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Expected O, but got Unknown if ((Object)(object)_plugin == (Object)null || !_plugin.connected || _plugin._session == null) { return false; } string text = setEntry?._shopStruct._itemName; if (!string.IsNullOrEmpty(text) && text.StartsWith("[AP]", StringComparison.Ordinal)) { return false; } string text2 = (((Object)scriptableItem != (Object)null) ? scriptableItem._itemName : null); if ((!((Object)scriptableItem != (Object)null) || !TryResolveToolKind(null, text2, out var kind)) && !TryResolveToolKind(text, text2, out kind)) { return false; } string text3 = ((kind == ToolKind.FishingRod) ? "Buy Fishing Rod" : "Buy Pickaxe"); if (!ArchipelagoGameDataTables.AllLocationNameToId.TryGetValue(text3, out var value)) { AtlyssArchipelagoPlugin.SLogError("Profession tool purchase: missing location id for " + text3); return false; } bool flag = _plugin._session.Locations.AllLocationsChecked != null && _plugin._session.Locations.AllLocationsChecked.Contains(value); bool flag2 = ((kind == ToolKind.FishingRod) ? _plugin.fishingRodToolAcquired : _plugin.pickaxeToolAcquired); if (flag || flag2) { if (flag && !IsPoolMode) { MarkToolAcquired(kind); } string message = (IsPoolMode ? "Profession Tools: Pool. Your tool comes from the multiworld — the shop check is already sent; you cannot buy another here." : "You already have this profession tool — you cannot buy another here."); AtlyssArchipelagoPlugin.SLogInfo("Profession tool purchase blocked: kind=" + kind.ToString() + " display='" + text + "' script='" + text2 + "' locationChecked=" + flag + " toolFlag=" + flag2); ShowShopError(message); return true; } Player mainPlayer = Player._mainPlayer; if ((Object)mainPlayer == (Object)null) { AtlyssArchipelagoPlugin.SLogError("Profession tool purchase: player was null."); return false; } PlayerInventory component = ((Component)mainPlayer).GetComponent(); if ((Object)component == (Object)null) { AtlyssArchipelagoPlugin.SLogError("Profession tool purchase: PlayerInventory was null."); return false; } int num = ResolvePurchaseCost(scriptableItem, setEntry); string text4 = ((kind == ToolKind.FishingRod) ? "Fishing Rod" : "Pickaxe"); if (component._heldCurrency < num) { ShowShopError("Not enough Crowns"); return true; } _plugin.SendCheckByName(text3); _plugin.SendAPChatMessage("Purchased " + AtlyssArchipelagoPlugin.EscapeServerChatToken(text4) + "! Check sent to the multiworld."); if (!IsPoolMode) { MarkToolAcquired(kind); } AtlyssArchipelagoPlugin.SLogInfo("Profession tool purchase: " + text4 + " (mode=" + ((_plugin.professionToolsOption == 0) ? "static" : "pool") + ", cost=" + num + ")"); if (IsPoolMode) { component.Network_heldCurrency -= num; component.Play_PurchaseSound(); return true; } return false; } public void SyncAcquiredFlagsFromSession() { if (IsPoolMode) { SyncPoolToolAcquiredFromReceivedItems(); } else if (_plugin._session?.Locations.AllLocationsChecked != null) { if (ArchipelagoGameDataTables.AllLocationNameToId.TryGetValue("Buy Fishing Rod", out var value) && _plugin._session.Locations.AllLocationsChecked.Contains(value)) { MarkToolAcquired(ToolKind.FishingRod); } if (ArchipelagoGameDataTables.AllLocationNameToId.TryGetValue("Buy Pickaxe", out var value2) && _plugin._session.Locations.AllLocationsChecked.Contains(value2)) { MarkToolAcquired(ToolKind.Pickaxe); } } } public bool PlayerPhysicallyHasProfessionTool(string apItemName) { if (!TryResolveToolKind(apItemName, null, out var kind)) { return false; } return PlayerPhysicallyHasProfessionTool(kind); } private bool PlayerPhysicallyHasProfessionTool(ToolKind kind) { string[] shopNamePatterns = GetShopNamePatterns(kind); if (PlayerInventoryContainsTool(shopNamePatterns)) { return true; } return ArchipelagoSpikeStorage.ContainsItemMatchingNames(shopNamePatterns); } public ScriptableItem TryLocateProfessionToolScriptable(string apItemName) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown if ((Object)GameManager._current == (Object)null) { return null; } if (!TryResolveToolKind(apItemName, null, out var kind)) { return null; } string[] shopNamePatterns = GetShopNamePatterns(kind); foreach (string text in shopNamePatterns) { ScriptableItem val = GameManager._current.Locate_Item(text); if ((Object)val != (Object)null) { return val; } } return null; } public void NotifyToolItemReceived(string itemName) { if (!string.IsNullOrEmpty(itemName)) { if (string.Equals(itemName, "Fishing Rod", StringComparison.Ordinal)) { MarkToolAcquired(ToolKind.FishingRod); } else if (string.Equals(itemName, "Pickaxe", StringComparison.Ordinal)) { MarkToolAcquired(ToolKind.Pickaxe); } } } public void SyncProfessionLevelBaselines() { if (!TryReadProfessionLevel("Fishing", out var level)) { level = 0; } if (!TryReadProfessionLevel("Mining", out var level2)) { level2 = 0; } if (!_plugin.fishingRodToolAcquired && level >= 1) { level = 0; } if (!_plugin.pickaxeToolAcquired && level2 >= 1) { level2 = 0; } _plugin.SetProfessionLevelBaselines(level, level2); AtlyssArchipelagoPlugin.SLogInfo("Profession baselines: fishing=" + level + ", mining=" + level2 + " (rod=" + _plugin.fishingRodToolAcquired + ", pick=" + _plugin.pickaxeToolAcquired + ")"); } public bool ShouldDeferProfessionCheck(string locationName) { if (!IsProfessionLevelCheck(locationName, out var isFishing)) { return false; } if (IsPoolMode) { string itemName = (isFishing ? "Fishing Rod" : "Pickaxe"); return !_plugin.HasReceivedApItemNamed(itemName); } if (!isFishing) { return !_plugin.pickaxeToolAcquired; } return !_plugin.fishingRodToolAcquired; } private static bool IsProfessionLevelCheck(string locationName, out bool isFishing) { if (locationName.StartsWith("Fishing Lv.", StringComparison.Ordinal)) { isFishing = true; return true; } if (locationName.StartsWith("Mining Lv.", StringComparison.Ordinal)) { isFishing = false; return true; } isFishing = false; return false; } private void SyncPoolToolAcquiredFromReceivedItems() { if (_plugin.HasReceivedApItemNamed("Fishing Rod")) { MarkToolAcquired(ToolKind.FishingRod); } if (_plugin.HasReceivedApItemNamed("Pickaxe")) { MarkToolAcquired(ToolKind.Pickaxe); } } private void MarkToolAcquired(ToolKind kind) { if (kind == ToolKind.FishingRod) { _plugin.fishingRodToolAcquired = true; } else { _plugin.pickaxeToolAcquired = true; } FlushEarnedProfessionChecksAfterToolAcquire(kind); } private void FlushEarnedProfessionChecksAfterToolAcquire(ToolKind kind) { if (!_plugin.connected || _plugin._session == null) { return; } string professionName = ((kind == ToolKind.FishingRod) ? "Fishing" : "Mining"); string text = ((kind == ToolKind.FishingRod) ? "Fishing Lv." : "Mining Lv."); int num = ((kind == ToolKind.FishingRod) ? _plugin._previousFishingLevel : _plugin._previousMiningLevel); if (!TryReadProfessionLevel(professionName, out var level)) { return; } for (int i = num + 1; i <= level; i++) { string text2 = text + " " + i; if (!ArchipelagoGameDataTables.AllLocationNameToId.TryGetValue(text2, out var value) || _plugin._session.Locations.AllLocationsChecked == null || !_plugin._session.Locations.AllLocationsChecked.Contains(value)) { _plugin.SendCheckByName(text2); AtlyssArchipelagoPlugin.SLogInfo(text2 + " reached (unlocked with profession tool)!"); } } if (kind == ToolKind.FishingRod) { _plugin.SetProfessionLevelBaselines(level, _plugin._previousMiningLevel); } else { _plugin.SetProfessionLevelBaselines(_plugin._previousFishingLevel, level); } } private static bool TryResolveToolKind(string displayName, string scriptName, out ToolKind kind) { if (MatchesToolName(scriptName, PickaxeShopNames)) { kind = ToolKind.Pickaxe; return true; } if (MatchesToolName(scriptName, FishingRodShopNames)) { kind = ToolKind.FishingRod; return true; } if (MatchesToolName(displayName, PickaxeShopNames)) { kind = ToolKind.Pickaxe; return true; } if (MatchesToolName(displayName, FishingRodShopNames)) { kind = ToolKind.FishingRod; return true; } kind = ToolKind.FishingRod; return false; } private static string[] GetShopNamePatterns(ToolKind kind) { if (kind != ToolKind.FishingRod) { return PickaxeShopNames; } return FishingRodShopNames; } private static bool PlayerInventoryContainsTool(string[] patterns) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown try { Player mainPlayer = Player._mainPlayer; if ((Object)mainPlayer == (Object)null) { return false; } PlayerInventory component = ((Component)mainPlayer).GetComponent(); if ((Object)component == (Object)null) { return false; } if (!(AccessTools.Field(typeof(PlayerInventory), "_heldItems")?.GetValue(component) is List list)) { return false; } foreach (ItemData item in list) { if (item != null && MatchesToolName(item._itemName, patterns)) { return true; } } } catch { return false; } return false; } private static bool MatchesToolName(string name, string[] patterns) { if (string.IsNullOrWhiteSpace(name)) { return false; } string text = name.Trim(); foreach (string value in patterns) { if (text.Equals(value, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static int ResolvePurchaseCost(ScriptableItem scriptableItem, ShopListDataEntry setEntry) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown if ((Object)(object)setEntry != (Object)null && setEntry._shopStruct._useDedicatedValue) { return setEntry._shopStruct._dedicatedValue; } if ((Object)scriptableItem != (Object)null) { return scriptableItem._vendorCost; } return 0; } private static bool TryReadProfessionLevel(string professionName, out int level) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_00a3: Unknown result type (might be due to invalid IL or missing references) level = 0; try { Player mainPlayer = Player._mainPlayer; if ((Object)mainPlayer == (Object)null || (Object)mainPlayer._pStats == (Object)null) { return false; } if ((Object)GameManager._current == (Object)null || (Object)GameManager._current._statLogics == (Object)null) { return false; } ScriptableProfession[] scriptableProfessions = GameManager._current._statLogics._scriptableProfessions; for (int i = 0; i < mainPlayer._pStats._syncProfessions.Count && i < scriptableProfessions.Length; i++) { ScriptableProfession val = scriptableProfessions[i]; if (!((Object)val == (Object)null) && string.Equals(val._professionName, professionName, StringComparison.Ordinal)) { level = mainPlayer._pStats._syncProfessions[i]._professionLvl; return true; } } } catch { return false; } return false; } private static void ShowShopError(string message) { ErrorPromptTextManager current = ErrorPromptTextManager.current; if ((Object)(object)current != (Object)null) { current.Init_ErrorPrompt(message); } } } public static class ArchipelagoProgressivePortalAccess { public static bool HasProgressivePortalUnlocked(AtlyssArchipelagoPlugin plugin, string portalKey) { if ((Object)(object)plugin == (Object)null) { return false; } ArchipelagoGameDataTables.ProgressivePortalRequirement progressivePortalRequirement = ArchipelagoGameDataTables.GetProgressivePortalRequirement(portalKey); return MeetsProgressiveRequirement(plugin, progressivePortalRequirement.SanctumCount, progressivePortalRequirement.TuulCount); } public static bool HasProgressivePortalUnlockedViaOrder(AtlyssArchipelagoPlugin plugin, string portalKey) { if ((Object)(object)plugin == (Object)null) { return false; } int num = IndexOfPortalKey(ArchipelagoGameDataTables.ProgressiveSanctumPortalOrder, portalKey); if (num >= 0) { return plugin.ProgressiveSanctumPortalCount > num; } int num2 = IndexOfPortalKey(ArchipelagoGameDataTables.ProgressiveTuulPortalOrder, portalKey); if (num2 >= 0) { return plugin.ProgressiveTuulPortalCount > num2; } return HasProgressivePortalUnlocked(plugin, portalKey); } private static int IndexOfPortalKey(IReadOnlyList order, string portalKey) { for (int i = 0; i < order.Count; i++) { if (order[i] == portalKey) { return i; } } return -1; } public static bool MeetsProgressiveRequirement(AtlyssArchipelagoPlugin plugin, int sanctumCount, int tuulCount) { if ((Object)(object)plugin == (Object)null) { return false; } if (plugin.ProgressiveSanctumPortalCount >= sanctumCount) { return plugin.ProgressiveTuulPortalCount >= tuulCount; } return false; } } internal static class ArchipelagoQuestListUi { private const string CompletedCheckSuffix = " ✓"; internal static bool ShouldEnhanceQuestList() { AtlyssArchipelagoPlugin instance = AtlyssArchipelagoPlugin.Instance; if ((Object)(object)instance == (Object)null || !instance.connected) { return false; } instance.EnsureQuestCompletionStateSeeded(); return true; } internal static string ResolveApQuestKey(ScriptableQuest quest) { if ((Object)(object)quest == (Object)null || string.IsNullOrEmpty(quest._questName)) { return null; } string questName = quest._questName; if (ArchipelagoGameDataTables.AllQuestToLocation.ContainsKey(questName)) { return questName; } foreach (KeyValuePair apQuestVanillaCompletionName in ArchipelagoGameDataTables.ApQuestVanillaCompletionNames) { string[] value = apQuestVanillaCompletionName.Value; if (value == null) { continue; } for (int i = 0; i < value.Length; i++) { if (value[i] == questName) { return apQuestVanillaCompletionName.Key; } } } return null; } internal static bool IsQuestCompleted(ScriptableQuest quest) { if (!ShouldEnhanceQuestList() || (Object)(object)quest == (Object)null) { return false; } AtlyssArchipelagoPlugin instance = AtlyssArchipelagoPlugin.Instance; PlayerQuesting playerQuesting = Player._mainPlayer?._pQuest; if (playerQuesting?._finishedQuests != null && playerQuesting._finishedQuests.ContainsKey(quest._questName)) { return true; } string text = ResolveApQuestKey(quest); if (text != null) { if (playerQuesting?._finishedQuests != null && ArchipelagoGameDataTables.IsApQuestFinished((string questKey) => playerQuesting._finishedQuests.ContainsKey(questKey), text)) { return true; } if (instance.IsApQuestMarkedComplete(text)) { return true; } } return false; } internal static void ApplyCompletedRowStyle(QuestListDataEntry entry) { if (!((Object)(object)entry == (Object)null) && !((Object)(object)entry._scriptableQuest == (Object)null) && !((Object)(object)((ListDataEntry)entry)._dataNameText == (Object)null)) { string text = ((ListDataEntry)entry)._dataNameText.text ?? string.Empty; if (!text.Contains("✓")) { ((ListDataEntry)entry)._dataNameText.text = text + " ✓"; } } } internal static void RefreshQuestListIfOpen() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 if (!ShouldEnhanceQuestList()) { return; } DialogManager current = DialogManager._current; if (!((Object)(object)current == (Object)null) && (int)current._currentDialogUIPrompt == 1) { ScriptableDialogData scriptableDialog = current._scriptableDialog; QuestSelectionManager current2 = QuestSelectionManager._current; if (!((Object)(object)scriptableDialog == (Object)null) && scriptableDialog._scriptableQuests != null && !((Object)(object)current2 == (Object)null)) { current2.Create_QuestListing(scriptableDialog._scriptableQuests); } } } } public class ArchipelagoShopSanity { private class ShopAPItemInfo { public string ItemName; public string FromPlayer; public long LocationId; public int Price; } private readonly AtlyssArchipelagoPlugin _plugin; private readonly ManualLogSource _logger; private long[] _shopLocationIds; private static readonly Dictionary MERCHANT_NPC_TO_SHOP = new Dictionary { { "Sally", "Sally's Nook" }, { "Skrit", "Skrit's Sikrit Market" }, { "sallyWorker_frankie_01", "Frankie's Goods" }, { "dyeMerchant", "Dye Merchant" }, { "Tesh", "Tesh's Wares" }, { "Nesh", "Nesh's Wares" }, { "Rikko", "Rikko's Treasures" }, { "Cotoo", "Cotoo's Treasures" }, { "Ruka", "Ruka's Furnace" }, { "fisher", "Torta's Fishing Shack" }, { "madStatue", "Mad Statue's Gift" } }; private Dictionary> _merchantLocationIds = new Dictionary>(); private readonly Dictionary _shopLocationSlots = new Dictionary(); private static readonly int[] SHOP_PRICE_TIERS = new int[5] { 200, 400, 600, 800, 1000 }; private Dictionary _scoutedShopItems = new Dictionary(); private HashSet _purchasedShopItems = new HashSet(); private readonly Dictionary> _vendorKeyToLocationIds = new Dictionary>(StringComparer.OrdinalIgnoreCase); private int _committedRelistNpcInstanceId = -1; private bool _forceFullRelist; private bool _shopItemsInitialized; public bool IsInitialized => _shopItemsInitialized; public void ApplyPersistedPurchasedShopLocations(IEnumerable locationIds) { _purchasedShopItems.Clear(); if (locationIds == null) { return; } foreach (long locationId in locationIds) { if (locationId > 0) { _purchasedShopItems.Add(locationId); } } } public List SnapshotPurchasedShopLocationIds() { return _purchasedShopItems.ToList(); } private void EnsureShopLocationIdsInitialized() { if (_shopLocationIds == null || _shopLocationIds.Length == 0) { InitializeShopLocations(); } } private int MergePurchasedShopLocationsFromCheckedEnumerable(IEnumerable checkedLocationIds) { if (checkedLocationIds == null) { return 0; } EnsureShopLocationIdsInitialized(); if (_shopLocationIds == null || _shopLocationIds.Length == 0) { return 0; } HashSet hashSet = new HashSet(_shopLocationIds); int num = 0; foreach (long checkedLocationId in checkedLocationIds) { if (hashSet.Contains(checkedLocationId) && _purchasedShopItems.Add(checkedLocationId)) { num++; } } if (num > 0) { _logger.LogInfo((object)$"[AtlyssAP] Marked {num} AP shop location(s) purchased (from server checked list)."); } return num; } public int MergePurchasedShopLocationsFromServer(ArchipelagoSession session) { try { if (session?.Locations == null) { return 0; } return MergePurchasedShopLocationsFromCheckedEnumerable(session.Locations.AllLocationsChecked); } catch (Exception ex) { _logger.LogError((object)("[AtlyssAP] Merge shop purchases from server failed: " + ex.Message)); return 0; } } public int MergePurchasedShopLocationsFromIncrementalChecks(IEnumerable newlyCheckedLocationIds) { try { return MergePurchasedShopLocationsFromCheckedEnumerable(newlyCheckedLocationIds); } catch (Exception ex) { _logger.LogError((object)("[AtlyssAP] Merge shop purchases from incremental checks failed: " + ex.Message)); return 0; } } public ArchipelagoShopSanity(AtlyssArchipelagoPlugin plugin, ManualLogSource logger) { _plugin = plugin; _logger = logger; } public void InitializeShopLocations() { _merchantLocationIds.Clear(); _shopLocationSlots.Clear(); List list = new List(); foreach (KeyValuePair item in MERCHANT_NPC_TO_SHOP) { string value = item.Value; List list2 = new List(); for (int i = 1; i <= 5; i++) { string text = $"Buy Item #{i} from {value}"; if (ArchipelagoGameDataTables.AllLocationNameToId.TryGetValue(text, out var value2)) { list2.Add(value2); _shopLocationSlots[value2] = (value, i); list.Add(value2); } else { _logger.LogWarning((object)("[AtlyssAP] Shop location not found in DataTables: " + text)); } } _merchantLocationIds[value] = list2; _logger.LogDebug((object)$"[AtlyssAP] Mapped {value}: {list2.Count} locations"); } _shopLocationIds = list.ToArray(); _logger.LogInfo((object)$"[AtlyssAP] Initialized {_shopLocationIds.Length} shop location IDs across {_merchantLocationIds.Count} merchants"); } public void SendLocationScouts(ArchipelagoSession session) { try { if (session == null) { _logger.LogError((object)"[AtlyssAP] Cannot scout - no session"); return; } if (_shopLocationIds == null || _shopLocationIds.Length == 0) { InitializeShopLocations(); } if (_shopLocationIds.Length == 0) { _logger.LogError((object)"[AtlyssAP] No shop location IDs found - cannot scout"); return; } _logger.LogInfo((object)$"[AtlyssAP] Scouting {_shopLocationIds.Length} shop locations..."); session.Locations.ScoutLocationsAsync(_shopLocationIds).ContinueWith(delegate(Task> task) { if (task.IsFaulted) { _logger.LogError((object)("[AtlyssAP] Scout failed: " + task.Exception?.GetBaseException().Message)); } else if (task.Status == TaskStatus.RanToCompletion) { ProcessScoutedLocations(session, task.Result); } }); } catch (Exception ex) { _logger.LogError((object)("[AtlyssAP] Error sending shop scouts: " + ex.Message)); } } public void RequestFullRelist() { _forceFullRelist = true; _committedRelistNpcInstanceId = -1; } public bool ShouldPerformFullRelist(NetNPC shopNpc) { if ((Object)(object)shopNpc == (Object)null) { return false; } if (_forceFullRelist) { _forceFullRelist = false; _committedRelistNpcInstanceId = -1; return true; } return ((Object)shopNpc).GetInstanceID() != _committedRelistNpcInstanceId; } public void MarkRelistCommitted(NetNPC shopNpc) { _committedRelistNpcInstanceId = (((Object)(object)shopNpc != (Object)null) ? ((Object)shopNpc).GetInstanceID() : (-1)); } private void ClearRelistSession() { _committedRelistNpcInstanceId = -1; _forceFullRelist = false; } public static string NormalizeApVendorKey(string vendorKey) { if (string.IsNullOrWhiteSpace(vendorKey)) { return vendorKey ?? string.Empty; } string text = vendorKey.Trim(); if (!text.StartsWith("[AP] (x", StringComparison.Ordinal)) { return text; } int num = text.IndexOf(") ", StringComparison.Ordinal); if (num < 0) { return text; } return "[AP] " + text.Substring(num + 2); } private static string BuildBaseVendorKey(ShopAPItemInfo scoutedItem) { return "[AP] " + AtlyssArchipelagoPlugin.EscapeServerChatToken(scoutedItem.ItemName) + " (" + AtlyssArchipelagoPlugin.EscapeServerChatToken(scoutedItem.FromPlayer) + ")"; } private static string VendorMapKey(NetNPC shopNpc, string vendorDictionaryKey) { string text = NormalizeApVendorKey(vendorDictionaryKey); if ((Object)(object)shopNpc == (Object)null || string.IsNullOrEmpty(text)) { return text ?? string.Empty; } return ((Object)shopNpc).GetInstanceID() + "\u001e" + text; } private bool IsShopLocationUnlocked(long locationId) { if (!_shopLocationSlots.TryGetValue(locationId, out (string, int) value)) { return true; } return ArchipelagoShopSlotAccess.IsSlotUnlocked(_plugin, value.Item1, value.Item2); } private static bool TryResolveMerchantShopDisplayName(string npcGameObjectName, out string shopDisplayName) { foreach (KeyValuePair item in MERCHANT_NPC_TO_SHOP) { if (npcGameObjectName.Contains(item.Key)) { shopDisplayName = item.Value; return true; } } shopDisplayName = null; return false; } private static bool ShouldDeferPurchaseTimeShopSync(NetNPC shopNpc) { if ((Object)(object)shopNpc == (Object)null) { return false; } string name = ((Object)((Component)shopNpc).gameObject).name; if (name != null) { return name.IndexOf("fisher", StringComparison.OrdinalIgnoreCase) >= 0; } return false; } private void ShuffleArray(int[] values) { for (int num = values.Length - 1; num > 0; num--) { int num2 = Random.Range(0, num + 1); int num3 = values[num]; values[num] = values[num2]; values[num2] = num3; } } private void ProcessScoutedLocations(ArchipelagoSession session, Dictionary scoutedLocations) { try { _scoutedShopItems.Clear(); _vendorKeyToLocationIds.Clear(); _logger.LogInfo((object)$"[AtlyssAP] Processing {scoutedLocations.Count} scouted shop items"); foreach (KeyValuePair> merchantLocationId in _merchantLocationIds) { string key = merchantLocationId.Key; List value = merchantLocationId.Value; int[] array = (int[])SHOP_PRICE_TIERS.Clone(); ShuffleArray(array); for (int i = 0; i < value.Count; i++) { long num = value[i]; if (!scoutedLocations.TryGetValue(num, out ScoutedItemInfo value2)) { _logger.LogWarning((object)$"[AtlyssAP] Missing scouted data for location {num}"); continue; } string text = session.Items.GetItemName(value2.ItemId, value2.ItemGame) ?? $"Item {value2.ItemId}"; string text2 = session.Players.GetPlayerName(value2.Player) ?? $"Player {value2.Player}"; int num2 = ((i < array.Length) ? array[i] : SHOP_PRICE_TIERS[0]); _scoutedShopItems[num] = new ShopAPItemInfo { ItemName = text, FromPlayer = text2, LocationId = num, Price = num2 }; _logger.LogDebug((object)$"[AtlyssAP] Scouted {key} #{i + 1}: {text} from {text2} ({num2} crowns)"); } } _shopItemsInitialized = true; _logger.LogInfo((object)$"[AtlyssAP] Shop items initialized! {_scoutedShopItems.Count} items ready."); } catch (Exception ex) { _logger.LogError((object)("[AtlyssAP] Error processing scouted locations: " + ex.Message)); } } private List AllocateApVendorSlots(Dictionary vendorItems, int want) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) int num = -1; foreach (ShopkeepItemStruct value in vendorItems.Values) { int slotNumber = value._itemData._slotNumber; if (slotNumber > num) { num = slotNumber; } } List list = new List(want); long num2 = (long)num + 1L; if (num2 + want - 1 > int.MaxValue) { num2 = 2147483647L - (long)want + 1; if (num2 < 0) { num2 = 0L; } } for (int i = 0; i < want; i++) { list.Add((int)(num2 + i)); } return list; } public void InjectAPShopItems(NetNPC npc) { //IL_035f: Unknown result type (might be due to invalid IL or missing references) //IL_0364: Unknown result type (might be due to invalid IL or missing references) //IL_036c: Unknown result type (might be due to invalid IL or missing references) //IL_0373: Unknown result type (might be due to invalid IL or missing references) //IL_037a: Unknown result type (might be due to invalid IL or missing references) //IL_0382: Unknown result type (might be due to invalid IL or missing references) //IL_0389: Unknown result type (might be due to invalid IL or missing references) //IL_0390: Unknown result type (might be due to invalid IL or missing references) //IL_0399: Expected O, but got Unknown //IL_039b: Unknown result type (might be due to invalid IL or missing references) //IL_040e: Unknown result type (might be due to invalid IL or missing references) //IL_0410: Unknown result type (might be due to invalid IL or missing references) //IL_041a: Unknown result type (might be due to invalid IL or missing references) //IL_0335: Unknown result type (might be due to invalid IL or missing references) //IL_0341: Unknown result type (might be due to invalid IL or missing references) try { string name = ((Object)((Component)npc).gameObject).name; string text = null; foreach (KeyValuePair item in MERCHANT_NPC_TO_SHOP) { if (name.Contains(item.Key)) { text = item.Value; break; } } if (text == null) { _logger.LogWarning((object)("[AtlyssAP] Unknown merchant NPC: '" + name + "' - skipping AP injection")); return; } if (!_merchantLocationIds.TryGetValue(text, out List value)) { _logger.LogWarning((object)("[AtlyssAP] No location IDs found for shop: " + text)); return; } _logger.LogInfo((object)$"[AtlyssAP] Matched to merchant: {text} ({value.Count} locations)"); Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); Dictionary dictionary2 = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (long item2 in value) { if (IsShopLocationUnlocked(item2) && !_purchasedShopItems.Contains(item2) && _scoutedShopItems.TryGetValue(item2, out ShopAPItemInfo value2)) { string key = BuildBaseVendorKey(value2); if (!dictionary.TryGetValue(key, out var value3)) { value3 = (dictionary[key] = new List()); dictionary2[key] = value2; } value3.Add(item2); } } List list2 = new List(); foreach (string key4 in npc._vendorItems.Keys) { if (key4.StartsWith("[AP] ", StringComparison.Ordinal)) { string key2 = NormalizeApVendorKey(key4); if (!dictionary.ContainsKey(key2)) { list2.Add(key4); } } } foreach (string item3 in list2) { npc._vendorItems.Remove(item3); _vendorKeyToLocationIds.Remove(VendorMapKey(npc, item3)); _vendorKeyToLocationIds.Remove(item3); _logger.LogDebug((object)("[AtlyssAP] Pruned stale AP shop row: " + item3)); } List list3 = AllocateApVendorSlots(npc._vendorItems, dictionary.Count); if (list3.Count < dictionary.Count) { _logger.LogWarning((object)$"[AtlyssAP] Only allocated {list3.Count} vendor slot(s) for AP rows at {text} (wanted {dictionary.Count})."); } int num = 0; foreach (KeyValuePair> item4 in dictionary) { if (num >= list3.Count || !dictionary2.TryGetValue(item4.Key, out var value4)) { break; } int count = item4.Value.Count; if (count <= 0) { continue; } string key3 = item4.Key; if (npc._vendorItems.TryGetValue(key3, out var value5)) { value5._stockQuantity = count; value5._dedicatedValue = value4.Price; value5._useDedicatedValue = true; value5._removeAtEmptyStock = false; value5._itemName = key3; if (value5._itemData != null) { value5._itemData._itemName = key3; } } else { int num2 = list3[num]; ItemData itemData = new ItemData { _itemName = key3, _quantity = 0, _maxQuantity = 1, _slotNumber = num2, _modifierID = 0, _isEquipped = false, _isAltWeapon = false }; ShopkeepItemStruct value6 = new ShopkeepItemStruct { _itemName = key3, _dedicatedValue = value4.Price, _useDedicatedValue = true, _itemData = itemData, _stockQuantity = count, _removeAtEmptyStock = false, _isbuybackItem = false, _isGambleItem = false, _equipModifierID = 0, _specialItemCostName = string.Empty, _specialItemCostQuantity = 0, _gambleValue = 0 }; npc._vendorItems.Add(key3, value6); _logger.LogDebug((object)$"[AtlyssAP] Injected: {key3} in slot {num2} for {value4.Price} crowns (stock={count})"); } item4.Value.Sort(); _vendorKeyToLocationIds[VendorMapKey(npc, key3)] = item4.Value; _vendorKeyToLocationIds[key3] = item4.Value; num++; } _logger.LogInfo((object)$"[AtlyssAP] Successfully injected {num} AP item row(s) into {text}!"); } catch (Exception ex) { _logger.LogError((object)("[AtlyssAP] Error injecting shop items: " + ex.Message)); } } public bool TryHandleAPItemPurchase(NetNPC shopNpc, string shopItemDisplayName, string vendorDictionaryKey) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown try { if ((Object)shopNpc == (Object)null) { _logger.LogError((object)"[AtlyssAP] AP shop purchase: shop NPC was null."); return false; } string text = NormalizeApVendorKey(((!string.IsNullOrWhiteSpace(vendorDictionaryKey)) ? vendorDictionaryKey : shopItemDisplayName)?.Trim() ?? ""); ShopAPItemInfo shopAPItemInfo = ResolvePurchasableItem(shopNpc, text); if (shopAPItemInfo == null) { _logger.LogWarning((object)("[AtlyssAP] No unpurchased AP shop row for vendor key: " + text)); return false; } if (!IsLocationSoldByMerchant(shopNpc, shopAPItemInfo.LocationId)) { return false; } if (!IsShopLocationUnlocked(shopAPItemInfo.LocationId)) { _logger.LogWarning((object)"[AtlyssAP] AP shop purchase blocked: portal progression has not unlocked this slot."); return false; } if (_plugin._session == null || !_plugin._session.Socket.Connected) { _logger.LogError((object)"[AtlyssAP] Not connected to Archipelago server!"); return false; } if (_plugin._session.Locations.AllLocationsChecked != null && _plugin._session.Locations.AllLocationsChecked.Contains(shopAPItemInfo.LocationId)) { if (_purchasedShopItems.Add(shopAPItemInfo.LocationId)) { _plugin.PersistShopPurchasedState(); } _logger.LogWarning((object)$"[AtlyssAP] Refusing AP shop purchase: location {shopAPItemInfo.LocationId} already checked on server (syncing local shop state)."); return false; } CompletePurchase(shopNpc, shopAPItemInfo, text); return true; } catch (Exception ex) { _logger.LogError((object)("[AtlyssAP] Error handling AP purchase: " + ex.Message)); return false; } } private ShopAPItemInfo ResolvePurchasableItem(NetNPC shopNpc, string vendorKey) { vendorKey = NormalizeApVendorKey(vendorKey); string key = VendorMapKey(shopNpc, vendorKey); if (!string.IsNullOrEmpty(vendorKey) && _vendorKeyToLocationIds.TryGetValue(key, out List value)) { foreach (long item in value) { if (IsShopLocationUnlocked(item) && !_purchasedShopItems.Contains(item) && _scoutedShopItems.TryGetValue(item, out ShopAPItemInfo value2)) { return value2; } } } _logger.LogWarning((object)("[AtlyssAP] AP shop strict key miss; refusing fallback for key: " + vendorKey)); return null; } private bool IsLocationSoldByMerchant(NetNPC shopNpc, long locationId) { if (TryResolveMerchantShopDisplayName(((Object)((Component)shopNpc).gameObject).name, out string shopDisplayName) && _merchantLocationIds.TryGetValue(shopDisplayName, out List value) && !value.Contains(locationId)) { _logger.LogError((object)$"[AtlyssAP] Refusing AP shop purchase: location {locationId} is not sold by this merchant ('{shopDisplayName}')."); return false; } return true; } private void CompletePurchase(NetNPC shopNpc, ShopAPItemInfo info, string vendorKey) { string itemName = info.ItemName; _plugin._session.Locations.CompleteLocationChecks(info.LocationId); _purchasedShopItems.Add(info.LocationId); _plugin.PersistShopPurchasedState(); _plugin.SendAPChatMessage("Purchased " + AtlyssArchipelagoPlugin.EscapeServerChatToken(itemName) + "! Item sent to Spike storage!"); _logger.LogInfo((object)$"[AtlyssAP] Shop purchase completed: {vendorKey} (Location {info.LocationId}, item={itemName})"); if (!ShouldDeferPurchaseTimeShopSync(shopNpc)) { RefreshOpenShopAfterPurchase(shopNpc); } int remainingCountForVendorKey = GetRemainingCountForVendorKey(shopNpc, vendorKey); if (remainingCountForVendorKey > 0) { _plugin.SendAPChatMessage($"AP stack remaining: {remainingCountForVendorKey}"); } } public void SyncApRowStockQuantities(NetNPC shopNpc) { if ((Object)(object)shopNpc == (Object)null || shopNpc._vendorItems == null) { return; } List list = new List(); foreach (string key in shopNpc._vendorItems.Keys) { if (key.StartsWith("[AP] ", StringComparison.Ordinal)) { list.Add(key); } } foreach (string item in list) { string vendorKey = NormalizeApVendorKey(item); if (shopNpc._vendorItems.TryGetValue(item, out var value)) { int remainingCountForVendorKey = GetRemainingCountForVendorKey(shopNpc, vendorKey); value._stockQuantity = remainingCountForVendorKey; value._removeAtEmptyStock = false; } } } public void RefreshOpenShopAfterPurchase(NetNPC shopNpc) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown SyncApRowStockQuantities(shopNpc); ShopkeepManager current = ShopkeepManager._current; if ((Object)current != (Object)null && (Object)shopNpc != (Object)null) { ShopUiIconStackBadgePatch.ApplyBadges(current, shopNpc); } } private int GetRemainingCountForVendorKey(NetNPC shopNpc, string vendorKey) { vendorKey = NormalizeApVendorKey(vendorKey); string key = VendorMapKey(shopNpc, vendorKey); if (!_vendorKeyToLocationIds.TryGetValue(key, out List value)) { return 0; } int num = 0; foreach (long item in value) { if (!_purchasedShopItems.Contains(item)) { num++; } } return num; } public bool TryGetRemainingCountForVendorKey(NetNPC shopNpc, string vendorKey, out int remaining) { remaining = 0; if ((Object)(object)shopNpc == (Object)null || string.IsNullOrWhiteSpace(vendorKey)) { return false; } string key = VendorMapKey(shopNpc, NormalizeApVendorKey(vendorKey.Trim())); if (!_vendorKeyToLocationIds.TryGetValue(key, out List value) || value == null) { return false; } remaining = GetRemainingCountForVendorKey(shopNpc, vendorKey.Trim()); return true; } public void Reset() { _scoutedShopItems.Clear(); _purchasedShopItems.Clear(); _vendorKeyToLocationIds.Clear(); ClearRelistSession(); _shopItemsInitialized = false; _logger.LogInfo((object)"[AtlyssAP] Shop sanity state reset"); } } public static class ArchipelagoShopSlotAccess { private readonly struct PortalGateDefinition { public readonly string[] RandomPortalItems; public readonly int ProgressiveSanctumCount; public readonly int ProgressiveTuulCount; public PortalGateDefinition(string[] randomPortalItems, int sanctumCount, int tuulCount) { RandomPortalItems = randomPortalItems; ProgressiveSanctumCount = sanctumCount; ProgressiveTuulCount = tuulCount; } } private static readonly PortalGateDefinition CatacombsLevel2Gate = new PortalGateDefinition(new string[4] { "Outer Sanctum Portal", "Arcwood Pass Portal", "Sanctum Catacombs lvl 1 Portal", "Sanctum Catacombs lvl 2 Portal" }, 4, 0); public static bool IsSlotUnlocked(AtlyssArchipelagoPlugin plugin, string merchantDisplayName, int slotNumber) { if ((Object)(object)plugin == (Object)null) { return false; } return HasCatacombsLevel2PortalGate(plugin); } private static bool HasCatacombsLevel2PortalGate(AtlyssArchipelagoPlugin plugin) { if (plugin.randomPortalsEnabled) { for (int i = 0; i < CatacombsLevel2Gate.RandomPortalItems.Length; i++) { string portalKey = CatacombsLevel2Gate.RandomPortalItems[i]; if (!plugin.TryGetPortalReceived(portalKey, out var received) || !received) { return false; } } return true; } return ArchipelagoProgressivePortalAccess.MeetsProgressiveRequirement(plugin, CatacombsLevel2Gate.ProgressiveSanctumCount, CatacombsLevel2Gate.ProgressiveTuulCount); } } public class ArchipelagoSpikeStorage { [Serializable] public class ItemBankData { public List _heldItemStorage = new List(); } private const int NUM_BANKS = 7; public const int SpikeSlotsPerCategory = 48; public const int SpikeStorageCategoryCount = 3; public const int SpikeSlotsPerBankTab = 144; private static HashSet _droppedItemsLogged = new HashSet(); private static HashSet _validatedBanks = new HashSet(); private static int _cachedSpikeTotalSlots = -1; private static int[][] _cachedSlotIndicesPerItemType; private static HashSet[] _cachedSlotSetPerItemType; private static HashSet _cachedAllPlausibleStorageSlotIndices; private static int[] _cachedPerCategoryDisplay1BasedByStorageIndex; private static bool _cachedLayoutIsStaticFallback; private static bool _cachedCoincidentPerCategoryGrid; private static ItemBankData _cachedMasterBank; private static bool _masterBankCacheLoaded; private static bool _masterBankDirty; private static readonly ItemBankData[] _cachedNumberedBanks = new ItemBankData[8]; private static readonly bool[] _numberedBankCacheLoaded = new bool[8]; private static readonly bool[] _numberedBankDirty = new bool[8]; private static bool _spikeUiRefreshPending; private static float _spikeDiskFlushAfter = -1f; private static float _nextSpikeUiRefreshAt = -1f; private const float SpikeDiskFlushDebounceSeconds = 0.35f; private const float SpikeUiRefreshMinIntervalSeconds = 0.2f; private static string _lastMasterBankDiskJson; private static readonly string[] _lastNumberedBankDiskJson = new string[8]; private static readonly Dictionary _spikeCategoryByItemNameCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private const BindingFlags StorageManagerFieldFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static List GetDistinctStorageManagersSortedBySlotCountDescending() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown List list = new List(); ItemStorageManager current = ItemStorageManager._current; if ((Object)current != (Object)null) { list.Add(current); } ItemStorageManager[] array = Object.FindObjectsOfType(); if (array != null) { foreach (ItemStorageManager val in array) { if ((Object)val != (Object)null && !list.Contains(val)) { list.Add(val); } } } list.Sort(delegate(ItemStorageManager a, ItemStorageManager b) { int value = (((Object)(object)a != (Object)null && a._storageItemSlots != null) ? a._storageItemSlots.Length : 0); return (((Object)(object)b != (Object)null && b._storageItemSlots != null) ? b._storageItemSlots.Length : 0).CompareTo(value); }); return list; } private static List GetStorageManagersWithMaximumSlotCount() { List distinctStorageManagersSortedBySlotCountDescending = GetDistinctStorageManagersSortedBySlotCountDescending(); if (distinctStorageManagersSortedBySlotCountDescending.Count == 0) { return distinctStorageManagersSortedBySlotCountDescending; } int storageItemSlotCount = GetStorageItemSlotCount(distinctStorageManagersSortedBySlotCountDescending[0]); if (storageItemSlotCount <= 0) { return new List(); } List list = new List(); for (int i = 0; i < distinctStorageManagersSortedBySlotCountDescending.Count; i++) { ItemStorageManager val = distinctStorageManagersSortedBySlotCountDescending[i]; if (GetStorageItemSlotCount(val) == storageItemSlotCount) { list.Add(val); } } return list; } private static int GetStorageItemSlotCount(ItemStorageManager ism) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown if ((Object)ism == (Object)null) { return 0; } ItemSlotUI[] storageItemSlots = ism._storageItemSlots; if (storageItemSlots == null) { return 0; } return storageItemSlots.Length; } private static float GetHorizontalLayoutSortKey(Transform t) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) if ((Object)t == (Object)null) { return 0f; } RectTransform val = (RectTransform)(object)((t is RectTransform) ? t : null); if ((Object)val != (Object)null) { float x = ((Transform)val).localPosition.x; if (Mathf.Abs(x) > 1E-05f) { return x; } x = val.anchoredPosition.x; if (Mathf.Abs(x) > 1E-05f) { return x; } return ((Transform)val).position.x; } return t.position.x; } private static Transform GetStorageColumnRootChildOfSlotContainer(Transform slotTransform, Transform slotContainer) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown if ((Object)slotTransform == (Object)null || (Object)slotContainer == (Object)null) { return null; } Transform val = slotTransform; while ((Object)(object)val != (Object)null) { if ((Object)val == (Object)(object)slotContainer) { return null; } if ((Object)val.parent == (Object)(object)slotContainer) { return val; } val = val.parent; } return null; } private static bool TryBuildThreeColumnRootsUnderSlotRegion(ItemSlotUI[] storageItemSlots, Transform slotContainer, out Transform[] columnsSortedLtr, out Transform slotClassificationParent) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown columnsSortedLtr = null; slotClassificationParent = null; if ((Object)slotContainer == (Object)null) { return false; } Transform val = slotContainer; for (int i = 0; i < 5; i++) { Dictionary dictionary = new Dictionary(); for (int j = 0; j < storageItemSlots.Length; j++) { if (!((Object)storageItemSlots[j] == (Object)null)) { Transform storageColumnRootChildOfSlotContainer = GetStorageColumnRootChildOfSlotContainer(((Component)storageItemSlots[j]).transform, val); if ((Object)storageColumnRootChildOfSlotContainer == (Object)null) { return false; } int instanceID = ((Object)storageColumnRootChildOfSlotContainer).GetInstanceID(); if (!dictionary.ContainsKey(instanceID)) { dictionary[instanceID] = storageColumnRootChildOfSlotContainer; } } } if (dictionary.Count == 3) { List list = new List(dictionary.Values); list.Sort((Transform a, Transform b) => GetHorizontalLayoutSortKey(a).CompareTo(GetHorizontalLayoutSortKey(b))); columnsSortedLtr = list.ToArray(); slotClassificationParent = val; return true; } if (dictionary.Count != 1) { return false; } using Dictionary.Enumerator enumerator = dictionary.GetEnumerator(); if (!enumerator.MoveNext()) { return false; } Transform value = enumerator.Current.Value; if (value.childCount < 3) { return false; } val = value; } return false; } private static bool TryProbeSlotContainerColumnBandsForManager(ItemStorageManager ism, out int totalSlots, out int[][] bands, out HashSet[] sets, out HashSet allValid) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Expected O, but got Unknown //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Expected O, but got Unknown totalSlots = 0; bands = null; sets = null; allValid = null; if ((Object)ism == (Object)null) { return false; } ItemSlotUI[] storageItemSlots = ism._storageItemSlots; if (storageItemSlots == null || storageItemSlots.Length < 3) { return false; } object? obj = typeof(ItemStorageManager).GetField("_slotContainer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(ism); Transform val = (Transform)((obj is Transform) ? obj : null); if ((Object)val == (Object)null) { return false; } if (!TryBuildThreeColumnRootsUnderSlotRegion(storageItemSlots, val, out Transform[] columnsSortedLtr, out Transform slotClassificationParent)) { return false; } int num = 0; for (int i = 0; i < storageItemSlots.Length; i++) { if (!((Object)storageItemSlots[i] == (Object)null)) { num++; } } if (num == 0) { return false; } List[] array = new List[3]; for (int j = 0; j < 3; j++) { array[j] = new List(); } for (int k = 0; k < storageItemSlots.Length; k++) { ItemSlotUI val2 = storageItemSlots[k]; if ((Object)val2 == (Object)null) { continue; } Transform storageColumnRootChildOfSlotContainer = GetStorageColumnRootChildOfSlotContainer(((Component)val2).transform, slotClassificationParent); int num2 = -1; for (int l = 0; l < 3; l++) { if ((Object)storageColumnRootChildOfSlotContainer == (Object)(object)columnsSortedLtr[l]) { num2 = l; break; } } if (num2 < 0) { return false; } array[num2].Add(k); } if (array[0].Count + array[1].Count + array[2].Count != num) { return false; } for (int m = 0; m < 3; m++) { if (array[m].Count == 0) { return false; } array[m].Sort(); } totalSlots = storageItemSlots.Length; bands = new int[3][]; sets = new HashSet[3]; for (int n = 0; n < 3; n++) { bands[n] = array[n].ToArray(); sets[n] = new HashSet(array[n]); } allValid = BuildUnionOfAllBandSlots(bands); for (int num3 = 0; num3 < storageItemSlots.Length; num3++) { if ((Object)storageItemSlots[num3] == (Object)null) { allValid.Add(num3); } } return true; } private static bool TryProbeSlotContainerColumnBands(out int totalSlots, out int[][] bands, out HashSet[] sets, out HashSet allValid) { totalSlots = 0; bands = null; sets = null; allValid = null; try { foreach (ItemStorageManager item in GetStorageManagersWithMaximumSlotCount()) { if (TryProbeSlotContainerColumnBandsForManager(item, out totalSlots, out bands, out sets, out allValid)) { return true; } } return false; } catch { return false; } } private static bool TryResolveStorageSlotToClosestItemTypeTab(Transform slotTransform, Transform gearTab, Transform consumableTab, Transform tradeTab, out ItemType itemType) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown itemType = (ItemType)0; if ((Object)slotTransform == (Object)null) { return false; } int num = int.MaxValue; bool result = false; int num2 = 0; Transform val = slotTransform; while ((Object)(object)val != (Object)null) { if ((Object)val == (Object)(object)gearTab && num2 < num) { num = num2; itemType = (ItemType)0; result = true; } if ((Object)val == (Object)(object)consumableTab && num2 < num) { num = num2; itemType = (ItemType)1; result = true; } if ((Object)val == (Object)(object)tradeTab && num2 < num) { num = num2; itemType = (ItemType)2; result = true; } val = val.parent; num2++; } return result; } private static int[][] BuildUniformStrideBands(int totalSlots, int stride) { int[][] array = new int[3][]; for (int i = 0; i < 3; i++) { int num = i * stride; int num2 = Mathf.Min(stride, Mathf.Max(0, totalSlots - num)); int[] array2 = new int[num2]; for (int j = 0; j < num2; j++) { array2[j] = num + j; } array[i] = array2; } return array; } private static HashSet[] BuildSlotSetsFromBandArrays(int[][] bands) { HashSet[] array = new HashSet[3]; for (int i = 0; i < 3; i++) { array[i] = new HashSet(bands[i]); } return array; } private static HashSet BuildUnionOfAllBandSlots(int[][] bands) { HashSet hashSet = new HashSet(); for (int i = 0; i < 3; i++) { int[] array = bands[i]; for (int j = 0; j < array.Length; j++) { hashSet.Add(array[j]); } } return hashSet; } private static bool TryProbeHierarchySlotBandsForManager(ItemStorageManager ism, out int totalSlots, out int[][] bands, out HashSet[] sets, out HashSet allValid) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected O, but got Unknown //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Expected O, but got Unknown //IL_0115: Unknown result type (might be due to invalid IL or missing references) totalSlots = 0; bands = null; sets = null; allValid = null; if ((Object)ism == (Object)null) { return false; } ItemSlotUI[] storageItemSlots = ism._storageItemSlots; if (storageItemSlots == null || storageItemSlots.Length < 3) { return false; } Type? typeFromHandle = typeof(ItemStorageManager); object? obj = typeFromHandle.GetField("_gearTabContainer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(ism); Transform val = (Transform)((obj is Transform) ? obj : null); object? obj2 = typeFromHandle.GetField("_consumableTabContainer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(ism); Transform val2 = (Transform)((obj2 is Transform) ? obj2 : null); object? obj3 = typeFromHandle.GetField("_tradeItemTabContainer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(ism); Transform val3 = (Transform)((obj3 is Transform) ? obj3 : null); if ((Object)val == (Object)null || (Object)val2 == (Object)null || (Object)val3 == (Object)null) { return false; } List[] array = new List[3]; for (int i = 0; i < 3; i++) { array[i] = new List(); } for (int j = 0; j < storageItemSlots.Length; j++) { ItemSlotUI val4 = storageItemSlots[j]; if (!((Object)val4 == (Object)null) && TryResolveStorageSlotToClosestItemTypeTab(((Component)val4).transform, val, val2, val3, out var itemType)) { array[itemType].Add(j); } } if (array[0].Count + array[1].Count + array[2].Count != storageItemSlots.Length) { return false; } for (int k = 0; k < 3; k++) { if (array[k].Count == 0) { return false; } array[k].Sort(); } totalSlots = storageItemSlots.Length; bands = new int[3][]; sets = new HashSet[3]; for (int l = 0; l < 3; l++) { bands[l] = array[l].ToArray(); sets[l] = new HashSet(array[l]); } allValid = BuildUnionOfAllBandSlots(bands); return true; } private static bool TryProbeHierarchySlotBands(out int totalSlots, out int[][] bands, out HashSet[] sets, out HashSet allValid) { totalSlots = 0; bands = null; sets = null; allValid = null; try { foreach (ItemStorageManager item in GetStorageManagersWithMaximumSlotCount()) { if (TryProbeHierarchySlotBandsForManager(item, out totalSlots, out bands, out sets, out allValid)) { return true; } } return false; } catch { return false; } } private static int[][] BuildInterleavedModThreeBands(int totalSlots) { List[] array = new List[3]; for (int i = 0; i < 3; i++) { array[i] = new List(); } for (int j = 0; j < totalSlots; j++) { array[j % 3].Add(j); } int[][] array2 = new int[3][]; for (int k = 0; k < 3; k++) { array2[k] = array[k].ToArray(); } return array2; } private static bool TryBuildInterleavedModThreeBandsFromLiveSlotsForManager(ItemStorageManager ism, out int totalSlots, out int[][] bands, out HashSet[] sets, out HashSet allValid) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown totalSlots = 0; bands = null; sets = null; allValid = null; if ((Object)ism == (Object)null) { return false; } ItemSlotUI[] storageItemSlots = ism._storageItemSlots; if (storageItemSlots == null || storageItemSlots.Length < 3) { return false; } int num = storageItemSlots.Length; if (num % 3 != 0) { return false; } totalSlots = num; bands = BuildInterleavedModThreeBands(num); sets = BuildSlotSetsFromBandArrays(bands); allValid = new HashSet(); for (int i = 0; i < num; i++) { allValid.Add(i); } return true; } private static bool TryBuildInterleavedModThreeBandsFromLiveSlots(out int totalSlots, out int[][] bands, out HashSet[] sets, out HashSet allValid) { totalSlots = 0; bands = null; sets = null; allValid = null; try { foreach (ItemStorageManager item in GetStorageManagersWithMaximumSlotCount()) { if (TryBuildInterleavedModThreeBandsFromLiveSlotsForManager(item, out totalSlots, out bands, out sets, out allValid)) { return true; } } return false; } catch { return false; } } private static bool TryBuildUniformBandsFromLiveSlotsForManager(ItemStorageManager ism, out int totalSlots, out int[][] bands, out HashSet[] sets, out HashSet allValid) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown totalSlots = 0; bands = null; sets = null; allValid = null; if ((Object)ism == (Object)null) { return false; } ItemSlotUI[] storageItemSlots = ism._storageItemSlots; if (storageItemSlots == null || storageItemSlots.Length < 3) { return false; } int num = storageItemSlots.Length; if (num % 3 != 0) { return false; } int stride = num / 3; totalSlots = num; bands = BuildUniformStrideBands(num, stride); sets = BuildSlotSetsFromBandArrays(bands); allValid = new HashSet(); for (int i = 0; i < num; i++) { allValid.Add(i); } return true; } private static bool TryBuildUniformBandsFromLiveSlots(out int totalSlots, out int[][] bands, out HashSet[] sets, out HashSet allValid) { totalSlots = 0; bands = null; sets = null; allValid = null; try { foreach (ItemStorageManager item in GetStorageManagersWithMaximumSlotCount()) { if (TryBuildUniformBandsFromLiveSlotsForManager(item, out totalSlots, out bands, out sets, out allValid)) { return true; } } return false; } catch { return false; } } private static bool LiveSlotCountImpliesCoincidentPerCategoryGrid(int slotCount) { if (slotCount <= 0) { return false; } if (slotCount == 48) { return true; } return slotCount % 3 != 0; } private static bool TryBuildCoincidentPerCategoryBandsFromLiveSlotsForManager(ItemStorageManager ism, out int totalSlots, out int[][] bands, out HashSet[] sets, out HashSet allValid) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown totalSlots = 0; bands = null; sets = null; allValid = null; if ((Object)ism == (Object)null) { return false; } ItemSlotUI[] storageItemSlots = ism._storageItemSlots; if (storageItemSlots == null || storageItemSlots.Length < 3) { return false; } int num = storageItemSlots.Length; if (!LiveSlotCountImpliesCoincidentPerCategoryGrid(num)) { return false; } bands = new int[3][]; for (int i = 0; i < 3; i++) { int[] array = new int[num]; for (int j = 0; j < num; j++) { array[j] = j; } bands[i] = array; } sets = BuildSlotSetsFromBandArrays(bands); allValid = new HashSet(); for (int k = 0; k < num; k++) { allValid.Add(k); } totalSlots = num; return true; } private static bool TryBuildCoincidentPerCategoryBandsFromLiveSlots(out int totalSlots, out int[][] bands, out HashSet[] sets, out HashSet allValid) { totalSlots = 0; bands = null; sets = null; allValid = null; try { foreach (ItemStorageManager item in GetStorageManagersWithMaximumSlotCount()) { if (TryBuildCoincidentPerCategoryBandsFromLiveSlotsForManager(item, out totalSlots, out bands, out sets, out allValid)) { return true; } } return false; } catch { return false; } } private static void ApplyFallbackConstantBands() { int[][] bands = BuildUniformStrideBands(144, 48); HashSet[] sets = BuildSlotSetsFromBandArrays(bands); HashSet hashSet = new HashSet(); for (int i = 0; i < 144; i++) { hashSet.Add(i); } CommitSpikeSlotLayoutCacheState(144, bands, sets, hashSet, staticFallback: true); } public static void InvalidateSpikeSlotLayoutCache() { _cachedSpikeTotalSlots = -1; _cachedSlotIndicesPerItemType = null; _cachedSlotSetPerItemType = null; _cachedAllPlausibleStorageSlotIndices = null; _cachedPerCategoryDisplay1BasedByStorageIndex = null; _cachedLayoutIsStaticFallback = false; _cachedCoincidentPerCategoryGrid = false; } private static void RebuildPerCategoryDisplayOrdinalCache() { _cachedPerCategoryDisplay1BasedByStorageIndex = null; if (_cachedSpikeTotalSlots <= 0 || _cachedSlotIndicesPerItemType == null) { return; } int[] array = new int[_cachedSpikeTotalSlots]; for (int i = 0; i < _cachedSpikeTotalSlots; i++) { array[i] = 0; } for (int j = 0; j < 3; j++) { int[] array2 = _cachedSlotIndicesPerItemType[j]; if (array2 == null) { continue; } for (int k = 0; k < array2.Length; k++) { int num = array2[k]; if (num >= 0 && num < array.Length) { array[num] = k + 1; } } } _cachedPerCategoryDisplay1BasedByStorageIndex = array; } private static void CommitSpikeSlotLayoutCacheState(int totalSlots, int[][] bands, HashSet[] sets, HashSet allValid, bool staticFallback, bool coincidentPerCategoryGrid = false) { _cachedSpikeTotalSlots = totalSlots; _cachedSlotIndicesPerItemType = bands; _cachedSlotSetPerItemType = sets; _cachedAllPlausibleStorageSlotIndices = allValid; _cachedLayoutIsStaticFallback = staticFallback; _cachedCoincidentPerCategoryGrid = coincidentPerCategoryGrid; RebuildPerCategoryDisplayOrdinalCache(); } private static void EnsureSpikeLayoutCache() { List distinctStorageManagersSortedBySlotCountDescending = GetDistinctStorageManagersSortedBySlotCountDescending(); int num = 0; if (distinctStorageManagersSortedBySlotCountDescending.Count > 0) { num = GetStorageItemSlotCount(distinctStorageManagersSortedBySlotCountDescending[0]); } bool flag = _cachedSpikeTotalSlots > 0 && _cachedSlotIndicesPerItemType != null && _cachedSlotSetPerItemType != null && _cachedAllPlausibleStorageSlotIndices != null; if (flag && num > 0 && num == _cachedSpikeTotalSlots && LiveSlotCountImpliesCoincidentPerCategoryGrid(num) == _cachedCoincidentPerCategoryGrid) { if (_cachedPerCategoryDisplay1BasedByStorageIndex == null && _cachedSlotIndicesPerItemType != null) { RebuildPerCategoryDisplayOrdinalCache(); } return; } if (flag && !_cachedLayoutIsStaticFallback && num > 0 && num < _cachedSpikeTotalSlots) { if (_cachedPerCategoryDisplay1BasedByStorageIndex == null && _cachedSlotIndicesPerItemType != null) { RebuildPerCategoryDisplayOrdinalCache(); } return; } if (flag && num == 0) { if (_cachedPerCategoryDisplay1BasedByStorageIndex == null && _cachedSlotIndicesPerItemType != null) { RebuildPerCategoryDisplayOrdinalCache(); } return; } InvalidateSpikeSlotLayoutCache(); ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (TryBuildCoincidentPerCategoryBandsFromLiveSlots(out int totalSlots, out int[][] bands, out HashSet[] sets, out HashSet allValid)) { CommitSpikeSlotLayoutCacheState(totalSlots, bands, sets, allValid, staticFallback: false, coincidentPerCategoryGrid: true); if (staticLogger != null) { staticLogger.LogInfo((object)$"[AtlyssAP] Spike slot map (per-category local grid 0..{totalSlots - 1} per tab; not stride-banded): length={totalSlots}"); } } else if (TryProbeSlotContainerColumnBands(out totalSlots, out bands, out sets, out allValid)) { CommitSpikeSlotLayoutCacheState(totalSlots, bands, sets, allValid, staticFallback: false); if (staticLogger != null) { int[] array = bands[0]; int[] array2 = bands[1]; int[] array3 = bands[2]; staticLogger.LogInfo((object)$"[AtlyssAP] Spike slot map (_slotContainer columns): length={totalSlots}, GEAR={array.Length} ({array[0]}–{array[^1]}), CONS={array2.Length} ({array2[0]}–{array2[^1]}), TRADE={array3.Length} ({array3[0]}–{array3[^1]})"); } } else if (TryProbeHierarchySlotBands(out totalSlots, out bands, out sets, out allValid)) { CommitSpikeSlotLayoutCacheState(totalSlots, bands, sets, allValid, staticFallback: false); if (staticLogger != null) { int[] array4 = bands[0]; int[] array5 = bands[1]; int[] array6 = bands[2]; staticLogger.LogInfo((object)$"[AtlyssAP] Spike slot map (hierarchy): length={totalSlots}, GEAR={array4.Length} (idx {array4[0]}–{array4[^1]}), CONS={array5.Length} ({array5[0]}–{array5[^1]}), TRADE={array6.Length} ({array6[0]}–{array6[^1]})"); } } else if (TryBuildUniformBandsFromLiveSlots(out totalSlots, out bands, out sets, out allValid)) { CommitSpikeSlotLayoutCacheState(totalSlots, bands, sets, allValid, staticFallback: false); if (staticLogger != null) { staticLogger.LogInfo((object)$"[AtlyssAP] Spike slot map (uniform stride {totalSlots / 3}, linear bands / typical 8-wide rows): length={totalSlots}"); } } else if (TryBuildInterleavedModThreeBandsFromLiveSlots(out totalSlots, out bands, out sets, out allValid)) { CommitSpikeSlotLayoutCacheState(totalSlots, bands, sets, allValid, staticFallback: false); if (staticLogger != null) { int[] array7 = bands[0]; int[] array8 = bands[1]; int[] array9 = bands[2]; staticLogger.LogInfo((object)$"[AtlyssAP] Spike slot map (interleaved i%3, 3-column stripe layout): length={totalSlots}, GEAR first={array7[0]} last={array7[^1]}, CONS first={array8[0]}, TRADE first={array9[0]}"); } } else { ApplyFallbackConstantBands(); if (staticLogger != null) { staticLogger.LogInfo((object)"[AtlyssAP] Spike slot map: static fallback (no live ItemStorageManager / hierarchy probe)."); } } } public static bool TryGetSpikeSlotPerCategoryDisplayNumber(int storageSlotIndex, out int display1Based) { EnsureSpikeLayoutCache(); if (_cachedPerCategoryDisplay1BasedByStorageIndex == null || storageSlotIndex < 0 || storageSlotIndex >= _cachedPerCategoryDisplay1BasedByStorageIndex.Length) { display1Based = 0; return false; } display1Based = _cachedPerCategoryDisplay1BasedByStorageIndex[storageSlotIndex]; return display1Based > 0; } public static void EnsureAllStorageSlotGameObjectsActive(ItemStorageManager ism) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown try { if ((Object)ism == (Object)null) { return; } ItemSlotUI[] storageItemSlots = ism._storageItemSlots; if (storageItemSlots == null) { return; } foreach (ItemSlotUI val in storageItemSlots) { if (!((Object)val == (Object)null)) { ((Component)val).gameObject.SetActive(true); } } } catch { } } private static int GetSpikeBankMaxSlots() { EnsureSpikeLayoutCache(); return _cachedSpikeTotalSlots; } private static bool StorageSlotMatchesItemType(int slotNumber, ItemType category) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected I4, but got Unknown EnsureSpikeLayoutCache(); int num = (int)category; if (num < 0 || num >= 3) { num = 0; } return _cachedSlotSetPerItemType[num].Contains(slotNumber); } private static int FindFirstFreeSlotInItemTypeBand(HashSet occupied, ItemType category) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected I4, but got Unknown EnsureSpikeLayoutCache(); int num = (int)category; if (num < 0 || num >= 3) { num = 0; } int[] array = _cachedSlotIndicesPerItemType[num]; foreach (int num2 in array) { if (!occupied.Contains(num2)) { return num2; } } return -1; } private static int FindFirstFreeSlotInAnyValidStorageSlot(HashSet occupied) { EnsureSpikeLayoutCache(); for (int i = 0; i < _cachedSpikeTotalSlots; i++) { if (_cachedAllPlausibleStorageSlotIndices.Contains(i) && !occupied.Contains(i)) { return i; } } return -1; } private static string GetSessionMarkerPath() { return Path.Combine(Path.Combine(Application.dataPath, "profileCollections"), "ap_session_active"); } public static bool IsAPSessionActive() { return File.Exists(GetSessionMarkerPath()); } public static void SetAPSessionActive() { try { string sessionMarkerPath = GetSessionMarkerPath(); string directoryName = Path.GetDirectoryName(sessionMarkerPath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } File.WriteAllText(sessionMarkerPath, DateTime.UtcNow.ToString("o")); ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogInfo((object)"[AtlyssAP] AP session marker set (storage will persist across restarts)"); } } catch (Exception ex) { ManualLogSource staticLogger2 = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger2 != null) { staticLogger2.LogError((object)("[AtlyssAP] Failed to set session marker: " + ex.Message)); } } } public static void ClearAPSession() { try { string sessionMarkerPath = GetSessionMarkerPath(); if (File.Exists(sessionMarkerPath)) { File.Delete(sessionMarkerPath); } ClearAllAPBanks(); _droppedItemsLogged.Clear(); _validatedBanks.Clear(); InvalidateSpikeSlotLayoutCache(); ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogInfo((object)"[AtlyssAP] AP session cleared (marker removed, banks wiped)"); } } catch (Exception ex) { ManualLogSource staticLogger2 = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger2 != null) { staticLogger2.LogError((object)("[AtlyssAP] Failed to clear session: " + ex.Message)); } } } public static string GetAPMasterBankPath() { return Path.Combine(Path.Combine(Application.dataPath, "profileCollections"), "atl_itemBank_ap"); } public static string GetAPBankPath(int bankNumber) { if (bankNumber < 1 || bankNumber > 7) { throw new ArgumentException($"Bank number must be between 1 and {7}"); } return Path.Combine(Path.Combine(Application.dataPath, "profileCollections"), $"atl_itemBank_ap_{bankNumber:D2}"); } private static string GetVanillaMasterBankPath() { return Path.Combine(Path.Combine(Application.dataPath, "profileCollections"), "atl_itemBank"); } private static string GetVanillaBankPath(int bankNumber) { if (bankNumber < 1 || bankNumber > 7) { throw new ArgumentException($"Bank number must be between 1 and {7}"); } return Path.Combine(Path.Combine(Application.dataPath, "profileCollections"), $"atl_itemBank_{bankNumber:D2}"); } private static void WriteJsonDirect(string path, string json) { string directoryName = Path.GetDirectoryName(path); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } using FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read); using StreamWriter streamWriter = new StreamWriter(stream); streamWriter.Write(json); } private static void MirrorAPMasterToVanilla(string json) { try { WriteJsonDirect(GetVanillaMasterBankPath(), json); WriteJsonDirect(GetVanillaMasterBankPath() + ".json", json); } catch (Exception ex) { ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogWarning((object)("[AtlyssAP] Failed to mirror AP MASTER bank to vanilla files: " + ex.Message)); } } } private static void MirrorAPBankToVanilla(int bankNumber, string json) { try { string vanillaBankPath = GetVanillaBankPath(bankNumber); WriteJsonDirect(vanillaBankPath, json); WriteJsonDirect(vanillaBankPath + ".json", json); } catch (Exception ex) { ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogWarning((object)$"[AtlyssAP] Failed to mirror AP bank {bankNumber} to vanilla files: {ex.Message}"); } } } public static void InitializeAPBanks() { try { string aPMasterBankPath = GetAPMasterBankPath(); string directoryName = Path.GetDirectoryName(aPMasterBankPath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } if (!File.Exists(aPMasterBankPath)) { string contents = JsonConvert.SerializeObject((object)new ItemBankData(), (Formatting)1); File.WriteAllText(aPMasterBankPath, contents); ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogInfo((object)"[AtlyssAP] Created AP master item bank"); } } for (int i = 1; i <= 7; i++) { string aPBankPath = GetAPBankPath(i); if (!File.Exists(aPBankPath)) { string contents2 = JsonConvert.SerializeObject((object)new ItemBankData(), (Formatting)1); File.WriteAllText(aPBankPath, contents2); ManualLogSource staticLogger2 = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger2 != null) { staticLogger2.LogInfo((object)$"[AtlyssAP] Created AP item bank {i}"); } } } ManualLogSource staticLogger3 = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger3 != null) { staticLogger3.LogInfo((object)"[AtlyssAP] Archipelago item banks initialized"); } } catch (Exception ex) { ManualLogSource staticLogger4 = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger4 != null) { staticLogger4.LogError((object)("[AtlyssAP] Failed to initialize AP banks: " + ex.Message)); } } } public static ItemType ResolveSpikeCategoryForPlacement(ScriptableItem def, string unityOrArchipelagoMappingKey) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrEmpty(unityOrArchipelagoMappingKey) && TryInferItemTypeFromUnityAssetString(unityOrArchipelagoMappingKey, out var itemType)) { return itemType; } if ((Object)def != (Object)null) { return def._itemType; } return (ItemType)0; } private static bool TryInferItemTypeFromUnityAssetString(string assetKey, out ItemType itemType) { itemType = (ItemType)0; if (string.IsNullOrEmpty(assetKey)) { return false; } if (assetKey.IndexOf("TRADEITEM_", StringComparison.OrdinalIgnoreCase) >= 0) { itemType = (ItemType)2; return true; } if (assetKey.IndexOf("STATUSCONSUMABLE_", StringComparison.OrdinalIgnoreCase) >= 0) { itemType = (ItemType)1; return true; } string[] array = new string[8] { "WEAPON_", "HELM_", "CHESTPIECE_", "LEGGINGS_", "CAPE_", "SHIELD_", "RING_", "TOOL_" }; for (int i = 0; i < array.Length; i++) { if (assetKey.IndexOf(array[i], StringComparison.OrdinalIgnoreCase) >= 0) { itemType = (ItemType)0; return true; } } return false; } private static ItemType ResolveSpikeCategoryForItem(string itemName) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: 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_006d: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(itemName)) { return (ItemType)0; } if (_spikeCategoryByItemNameCache.TryGetValue(itemName, out var value)) { return value; } if (ArchipelagoGameDataTables.ItemNameMapping.TryGetValue(itemName, out string value2) && TryInferItemTypeFromUnityAssetString(value2, out var itemType)) { _spikeCategoryByItemNameCache[itemName] = itemType; return itemType; } try { if ((Object)GameManager._current != (Object)null) { ScriptableItem val = GameManager._current.Locate_Item(itemName); if ((Object)val != (Object)null) { _spikeCategoryByItemNameCache[itemName] = val._itemType; return val._itemType; } } } catch { } _spikeCategoryByItemNameCache[itemName] = (ItemType)0; return (ItemType)0; } private static void RepairAndPersistBankIfPossible(ItemBankData bank, Action persist) { if (bank != null) { EnsureSpikeLayoutCache(); if (RepairBankSlotLayout(bank)) { persist(bank); } } } public static void RefreshSpikeListingIfUiActive() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown try { ItemStorageManager current = ItemStorageManager._current; if (!((Object)current == (Object)null) && current._isOpen) { ((MonoBehaviour)current).CancelInvoke("Begin_StorageListing"); current.Init_StorageListing(); } } catch (Exception ex) { ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogWarning((object)("[AtlyssAP] Spike UI refresh skipped: " + ex.Message)); } } } public static void RequestSpikeUiRefresh() { _spikeUiRefreshPending = true; } public static void ProcessPendingSpikeUiRefresh() { if (_spikeUiRefreshPending && !(Time.unscaledTime < _nextSpikeUiRefreshAt)) { _spikeUiRefreshPending = false; _nextSpikeUiRefreshAt = Time.unscaledTime + 0.2f; RefreshSpikeListingIfUiActive(); } } public static void InvalidateSpikeBankCache() { _masterBankCacheLoaded = false; _masterBankDirty = false; _cachedMasterBank = null; for (int i = 1; i <= 7; i++) { _numberedBankCacheLoaded[i] = false; _numberedBankDirty[i] = false; _cachedNumberedBanks[i] = null; } } public static void NotifyExternalBankPersistedByGame(bool isMasterBank, int bankNumber) { if (isMasterBank) { _masterBankCacheLoaded = false; _masterBankDirty = false; _cachedMasterBank = null; } else if (bankNumber >= 1 && bankNumber <= 7) { _numberedBankCacheLoaded[bankNumber] = false; _numberedBankDirty[bankNumber] = false; _cachedNumberedBanks[bankNumber] = null; } } private static void ScheduleSpikeDiskFlush() { if (_spikeDiskFlushAfter < 0f) { _spikeDiskFlushAfter = Time.unscaledTime + 0.35f; } } public static void FlushPendingSpikeWrites(bool force = false) { if (((!_masterBankCacheLoaded || !_masterBankDirty) && !Enumerable.Range(1, 7).Any((int i) => _numberedBankCacheLoaded[i] && _numberedBankDirty[i])) || (!force && Time.unscaledTime < _spikeDiskFlushAfter)) { return; } _spikeDiskFlushAfter = -1f; if (_masterBankCacheLoaded && _masterBankDirty) { WriteAPMasterBankToDisk(_cachedMasterBank); _masterBankDirty = false; } for (int num = 1; num <= 7; num++) { if (_numberedBankCacheLoaded[num] && _numberedBankDirty[num]) { WriteAPBankToDisk(num, _cachedNumberedBanks[num]); _numberedBankDirty[num] = false; } } } private static bool CompactCoincidentBankSlots(ItemBankData bank) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected I4, but got Unknown if (bank == null || bank._heldItemStorage == null || bank._heldItemStorage.Count == 0) { return false; } if (!_cachedCoincidentPerCategoryGrid) { return false; } List[] array = new List[3]; for (int i = 0; i < 3; i++) { array[i] = new List(); } foreach (ItemData item in bank._heldItemStorage) { int num = (int)ResolveSpikeCategoryForItem(item._itemName); if (num < 0 || num >= 3) { num = 0; } array[num].Add(item); } bool result = false; for (int j = 0; j < 3; j++) { List list = array[j]; list.Sort((ItemData a, ItemData b) => a._slotNumber.CompareTo(b._slotNumber)); for (int num2 = 0; num2 < list.Count; num2++) { if (list[num2]._slotNumber != num2) { list[num2]._slotNumber = num2; result = true; } } } return result; } private static bool RepairBankSlotLayout(ItemBankData bank) { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected I4, but got Unknown //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) if (bank == null || bank._heldItemStorage == null || bank._heldItemStorage.Count == 0) { return false; } int spikeBankMaxSlots = GetSpikeBankMaxSlots(); EnsureSpikeLayoutCache(); bool result = false; if (_cachedCoincidentPerCategoryGrid && CompactCoincidentBankSlots(bank)) { result = true; } HashSet[] array = new HashSet[3]; for (int i = 0; i < 3; i++) { array[i] = new HashSet(); } foreach (ItemData item in bank._heldItemStorage) { ItemType val = ResolveSpikeCategoryForItem(item._itemName); int num = (int)val; if (num < 0 || num >= 3) { num = 0; } bool flag = false; if (item._slotNumber < 0 || item._slotNumber >= spikeBankMaxSlots || !_cachedAllPlausibleStorageSlotIndices.Contains(item._slotNumber)) { flag = true; } else if (!StorageSlotMatchesItemType(item._slotNumber, val)) { flag = true; } else if (array[num].Contains(item._slotNumber)) { flag = true; } if (flag) { int num2 = FindFirstFreeSlotInItemTypeBand(array[num], val); if (num2 < 0) { num2 = FindFirstFreeSlotInAnyValidStorageSlot(array[num]); } if (num2 >= 0) { ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogWarning((object)$"[AtlyssAP] Fixing item '{item._itemName}' slot {item._slotNumber} -> {num2} (was out-of-bounds, wrong category band, or overlapping)"); } item._slotNumber = num2; array[num].Add(num2); result = true; continue; } item._slotNumber = -1; result = true; if (!_droppedItemsLogged.Contains(item._itemName)) { _droppedItemsLogged.Add(item._itemName); ManualLogSource staticLogger2 = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger2 != null) { staticLogger2.LogWarning((object)("[AtlyssAP] Bank full, dropping item '" + item._itemName + "' (further drops of this item will be silent)")); } } } else { array[num].Add(item._slotNumber); } } int num3 = bank._heldItemStorage.RemoveAll((ItemData item) => item._slotNumber < 0 || item._slotNumber >= spikeBankMaxSlots || !_cachedAllPlausibleStorageSlotIndices.Contains(item._slotNumber)); if (num3 > 0) { result = true; ManualLogSource staticLogger3 = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger3 != null) { staticLogger3.LogInfo((object)$"[AtlyssAP] Removed {num3} overflow item(s) from bank"); } } return result; } private static bool ValidateBank(ItemBankData bank) { if (bank == null || bank._heldItemStorage == null || bank._heldItemStorage.Count == 0) { return false; } return RepairBankSlotLayout(bank); } public static ItemBankData LoadAPBank(int bankNumber) { if (bankNumber < 1 || bankNumber > 7) { return new ItemBankData(); } if (!_numberedBankCacheLoaded[bankNumber]) { _cachedNumberedBanks[bankNumber] = ReadAPBankFromDisk(bankNumber); _numberedBankCacheLoaded[bankNumber] = true; } return _cachedNumberedBanks[bankNumber]; } public static void SaveAPBank(int bankNumber, ItemBankData data) { if (bankNumber >= 1 && bankNumber <= 7) { _cachedNumberedBanks[bankNumber] = data ?? new ItemBankData(); _numberedBankCacheLoaded[bankNumber] = true; _numberedBankDirty[bankNumber] = true; ScheduleSpikeDiskFlush(); } } private static ItemBankData ReadAPBankFromDisk(int bankNumber) { try { string aPBankPath = GetAPBankPath(bankNumber); if (!File.Exists(aPBankPath)) { return new ItemBankData(); } ItemBankData itemBankData = JsonConvert.DeserializeObject(File.ReadAllText(aPBankPath)); itemBankData = itemBankData ?? new ItemBankData(); if (!_validatedBanks.Contains(aPBankPath)) { if (ValidateBank(itemBankData)) { SaveAPBank(bankNumber, itemBankData); ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogInfo((object)$"[AtlyssAP] Fixed slot numbers in AP bank {bankNumber}"); } } _validatedBanks.Add(aPBankPath); } return itemBankData; } catch (Exception ex) { ManualLogSource staticLogger2 = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger2 != null) { staticLogger2.LogError((object)$"[AtlyssAP] Failed to load AP bank {bankNumber}: {ex.Message}"); } return new ItemBankData(); } } private static void WriteAPBankToDisk(int bankNumber, ItemBankData data) { try { string aPBankPath = GetAPBankPath(bankNumber); string text = JsonConvert.SerializeObject((object)data, (Formatting)1); if (bankNumber < 1 || bankNumber > 7 || !(text == _lastNumberedBankDiskJson[bankNumber]) || !File.Exists(aPBankPath)) { File.WriteAllText(aPBankPath, text); if (bankNumber >= 1 && bankNumber <= 7) { _lastNumberedBankDiskJson[bankNumber] = text; } MirrorAPBankToVanilla(bankNumber, text); } } catch (Exception ex) { ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogError((object)$"[AtlyssAP] Failed to save AP bank {bankNumber}: {ex.Message}"); } } } public static ItemBankData LoadAPMasterBank() { if (!_masterBankCacheLoaded) { _cachedMasterBank = ReadAPMasterBankFromDisk(); _masterBankCacheLoaded = true; } return _cachedMasterBank; } public static void SaveAPMasterBank(ItemBankData data) { _cachedMasterBank = data ?? new ItemBankData(); _masterBankCacheLoaded = true; _masterBankDirty = true; ScheduleSpikeDiskFlush(); } private static ItemBankData ReadAPMasterBankFromDisk() { try { string aPMasterBankPath = GetAPMasterBankPath(); if (!File.Exists(aPMasterBankPath)) { return new ItemBankData(); } ItemBankData itemBankData = JsonConvert.DeserializeObject(File.ReadAllText(aPMasterBankPath)); itemBankData = itemBankData ?? new ItemBankData(); if (!_validatedBanks.Contains(aPMasterBankPath)) { if (ValidateBank(itemBankData)) { SaveAPMasterBank(itemBankData); ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogInfo((object)"[AtlyssAP] Fixed slot numbers in AP master bank"); } } _validatedBanks.Add(aPMasterBankPath); } return itemBankData; } catch (Exception ex) { ManualLogSource staticLogger2 = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger2 != null) { staticLogger2.LogError((object)("[AtlyssAP] Failed to load AP master bank: " + ex.Message)); } return new ItemBankData(); } } private static void WriteAPMasterBankToDisk(ItemBankData data) { try { string aPMasterBankPath = GetAPMasterBankPath(); string text = JsonConvert.SerializeObject((object)data, (Formatting)1); if (!(text == _lastMasterBankDiskJson) || !File.Exists(aPMasterBankPath)) { File.WriteAllText(aPMasterBankPath, text); _lastMasterBankDiskJson = text; MirrorAPMasterToVanilla(text); } } catch (Exception ex) { ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogError((object)("[AtlyssAP] Failed to save AP master bank: " + ex.Message)); } } } public static bool ContainsItemMatchingNames(IReadOnlyList namePatterns) { if (namePatterns == null || namePatterns.Count == 0) { return false; } if (!AreAPBanksInitialized()) { return false; } if (BankContainsMatchingName(LoadAPMasterBank(), namePatterns)) { return true; } for (int i = 1; i <= 7; i++) { if (BankContainsMatchingName(LoadAPBank(i), namePatterns)) { return true; } } return false; } private static bool BankContainsMatchingName(ItemBankData bank, IReadOnlyList namePatterns) { if (bank?._heldItemStorage == null) { return false; } foreach (ItemData item in bank._heldItemStorage) { if (item == null || string.IsNullOrWhiteSpace(item._itemName)) { continue; } string text = item._itemName.Trim(); for (int i = 0; i < namePatterns.Count; i++) { string text2 = namePatterns[i]; if (!string.IsNullOrWhiteSpace(text2) && text.Equals(text2.Trim(), StringComparison.OrdinalIgnoreCase)) { return true; } } } return false; } public static bool AddItemToAPSpike(ItemData itemToAdd, ItemType spikeCategory) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) try { ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (itemToAdd._maxQuantity > 1 && TryMergeIntoExistingStacks(itemToAdd)) { RequestSpikeUiRefresh(); return true; } ItemBankData itemBankData = LoadAPMasterBank(); RepairAndPersistBankIfPossible(itemBankData, SaveAPMasterBank); int num = FindNextOpenSlotInCategory(itemBankData, spikeCategory); if (num >= 0) { itemToAdd._slotNumber = num; itemToAdd._isEquipped = false; itemBankData._heldItemStorage.Add(itemToAdd); SaveAPMasterBank(itemBankData); if (staticLogger != null) { int display1Based; string arg = (TryGetSpikeSlotPerCategoryDisplayNumber(num, out display1Based) ? display1Based.ToString() : num.ToString()); staticLogger.LogInfo((object)$"[AtlyssAP] Added {itemToAdd._itemName} to AP Spike MASTER bank slot {arg} ({spikeCategory})"); } RequestSpikeUiRefresh(); return true; } for (int i = 1; i <= 7; i++) { int bankIndex = i; ItemBankData itemBankData2 = LoadAPBank(i); RepairAndPersistBankIfPossible(itemBankData2, delegate(ItemBankData b) { SaveAPBank(bankIndex, b); }); int num2 = FindNextOpenSlotInCategory(itemBankData2, spikeCategory); if (num2 >= 0) { itemToAdd._slotNumber = num2; itemToAdd._isEquipped = false; itemBankData2._heldItemStorage.Add(itemToAdd); SaveAPBank(i, itemBankData2); if (staticLogger != null) { int display1Based2; string text = (TryGetSpikeSlotPerCategoryDisplayNumber(num2, out display1Based2) ? display1Based2.ToString() : num2.ToString()); staticLogger.LogInfo((object)$"[AtlyssAP] Added {itemToAdd._itemName} to AP Spike bank {i} slot {text} ({spikeCategory})"); } RequestSpikeUiRefresh(); return true; } } if (staticLogger != null) { staticLogger.LogWarning((object)("[AtlyssAP] All AP Spike banks are full! Cannot add " + itemToAdd._itemName)); } return false; } catch (Exception ex) { ManualLogSource staticLogger2 = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger2 != null) { staticLogger2.LogError((object)("[AtlyssAP] Failed to add item to AP Spike: " + ex.Message)); } return false; } } private static bool TryMergeIntoExistingStacks(ItemData itemToAdd) { ItemBankData bank = LoadAPMasterBank(); if (TryMergeItemIntoBank(itemToAdd, bank, delegate(ItemBankData data) { SaveAPMasterBank(data); }, "MASTER")) { return true; } for (int num = 1; num <= 7; num++) { ItemBankData bank2 = LoadAPBank(num); int bankIndex = num; if (TryMergeItemIntoBank(itemToAdd, bank2, delegate(ItemBankData data) { SaveAPBank(bankIndex, data); }, $"bank {num}")) { return true; } } return false; } private static bool TryMergeItemIntoBank(ItemData itemToAdd, ItemBankData bank, Action saveBank, string bankLabel) { foreach (ItemData item in bank._heldItemStorage) { if (!(item._itemName == itemToAdd._itemName) || item._quantity >= item._maxQuantity) { continue; } int num = Math.Min(item._maxQuantity - item._quantity, itemToAdd._quantity); item._quantity += num; itemToAdd._quantity -= num; saveBank(bank); if (itemToAdd._quantity == 0) { ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogInfo((object)("[AtlyssAP] Added " + itemToAdd._itemName + " to AP Spike " + bankLabel + " (stacked)")); } return true; } } return false; } private static int FindNextOpenSlotInCategory(ItemBankData bank, ItemType spikeCategory) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected I4, but got Unknown //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected I4, but got Unknown //IL_006e: Unknown result type (might be due to invalid IL or missing references) HashSet hashSet = new HashSet(); int num = (int)spikeCategory; if (num < 0 || num >= 3) { num = 0; } foreach (ItemData item in bank._heldItemStorage) { int num2 = (int)ResolveSpikeCategoryForItem(item._itemName); if (num2 < 0 || num2 >= 3) { num2 = 0; } if (num2 == num) { hashSet.Add(item._slotNumber); } } return FindFirstFreeSlotInItemTypeBand(hashSet, spikeCategory); } public static int GetTotalItemCount() { int num = 0; ItemBankData itemBankData = LoadAPMasterBank(); num += itemBankData._heldItemStorage.Count; for (int i = 1; i <= 7; i++) { ItemBankData itemBankData2 = LoadAPBank(i); num += itemBankData2._heldItemStorage.Count; } return num; } public static bool AreAPBanksInitialized() { if (!File.Exists(GetAPMasterBankPath())) { return false; } for (int i = 1; i <= 7; i++) { if (!File.Exists(GetAPBankPath(i))) { return false; } } return true; } public static void ClearAllAPBanks() { try { ItemBankData data = new ItemBankData(); SaveAPMasterBank(data); for (int i = 1; i <= 7; i++) { SaveAPBank(i, data); } FlushPendingSpikeWrites(force: true); _droppedItemsLogged.Clear(); _validatedBanks.Clear(); InvalidateSpikeSlotLayoutCache(); InvalidateSpikeBankCache(); ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogInfo((object)"[AtlyssAP] Cleared all AP item banks (master + numbered)"); } } catch (Exception ex) { ManualLogSource staticLogger2 = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger2 != null) { staticLogger2.LogError((object)("[AtlyssAP] Failed to clear AP banks: " + ex.Message)); } } } } [BepInPlugin("com.blitz.atlyss.ap", "Atlyss Archipelago", "0.2.3")] public class AtlyssArchipelagoPlugin : BaseUnityPlugin { private sealed class ProgressivePickOption { public string ConcreteItemName; public string ClassTrackKey; public int UsedTierIndex; } private long _connectAttemptId; private static readonly string[] ClassFilterDisplayNames = new string[7] { "All Classes", "Fighter", "Mystic", "Bandit", "Fighter + Mystic", "Fighter + Bandit", "Mystic + Bandit" }; private bool _goalSent; private float _lastAllQuestsCheck; private bool _allQuestsDiagLogged; private readonly HashSet _completedAchievements = new HashSet(); private bool _newJourneySent; private bool _achievementScanDone; private static readonly Dictionary BOSS_TO_GOALS = new Dictionary { { "Slime Diva", new int[2] { 0, 6 } }, { "Lord Zuulneruda", new int[2] { 1, 6 } }, { "Colossus", new int[2] { 2, 6 } }, { "Galius", new int[2] { 3, 6 } }, { "Lord Kaluuz", new int[2] { 4, 6 } }, { "Valdur", new int[2] { 5, 6 } } }; private static readonly string[] AllBossGoalNames = new string[6] { "Slime Diva", "Lord Zuulneruda", "Colossus", "Galius", "Lord Kaluuz", "Valdur" }; internal const int GoalOptionCount = 9; internal static readonly string[] GoalDisplayNames = new string[9] { "Slime Diva", "Lord Zuulneruda", "Colossus", "Galius", "Lord Kaluuz", "Valdur", "All Bosses", "All Quests", "Level 32" }; internal static readonly string[] GoalChatMessages = new string[9] { "Your goal: Defeat Slime Diva", "Your goal: Defeat Lord Zuulneruda", "Your goal: Defeat Colossus", "Your goal: Defeat Galius", "Your goal: Defeat Lord Kaluuz", "Your goal: Defeat Valdur", "Your goal: Defeat All Bosses", "Your goal: Complete All Quests", "Your goal: Reach Level 32" }; private static readonly FieldInfo CreepNameField = typeof(Creep).GetField("_creepName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo CreepScriptableField = typeof(Creep).GetField("_scriptableCreep", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private readonly HashSet _killedBossNames = new HashSet(); private MapInstance _lastTrackedMap; private CreepSpawner _trackedBossSpawner; private Creep _trackedBoss; private string _trackedBossName; private bool _bossDeathFired; private string _activeProfilePath; private static Harmony _harmony; private static GameObject scriptHolder; private static ArchipelagoPortalUnlocks portalLocker; private static readonly FieldInfo maxOnscreenMessages = AccessTools.Field(typeof(ChatBehaviour), "_maxGameLogicLines"); public ConfigEntry cfgServer; public ConfigEntry cfgSlot; public ConfigEntry cfgPassword; public ConfigEntry cfgDeathlink; public ConfigEntry cfgAutoReconnect; public InputField apServer; public InputField apSlot; public InputField apPassword; public Toggle apDeathlink; public ArchipelagoSession _session; public bool connected; private bool connecting; private bool _suppressAutoReconnect; private bool _autoReconnectWhenReady; private float _autoReconnectAt = -1f; private int _autoReconnectAttempt; private LocationCheckHelper.CheckedLocationsUpdatedHandler _checkedLocationsUpdatedHandler; private MessageLogHelper.MessageReceivedHandler _messageLogReceivedHandler; private const int MaxAutoReconnectAttempts = 8; private const float AutoReconnectBaseDelaySeconds = 3f; private const float AutoReconnectMaxDelaySeconds = 60f; private Dictionary slotData = new Dictionary(); public DeathLinkService _dlService; public static int reactingToDeathLink; private int goalOption; public bool randomPortalsEnabled; private bool shopSanityEnabled; private bool achievementsEnabled = true; private int equipmentProgressionOption = 1; private bool creepSanityEnabled; private int classFilterOption; private readonly Queue _deferredGatedEquipment = new Queue(); public float experienceMultiplier = 1f; public float crownMultiplier = 1f; private int progressiveSanctumPortalCount; private int progressiveTuulPortalCount; private Dictionary _progressiveCounts = new Dictionary(); private readonly HashSet _grantedProgressiveConcreteItems = new HashSet(); public readonly ConcurrentQueue _mainThreadQueue = new ConcurrentQueue(); private readonly Dictionary _scriptableItemLookupCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Queue _pendingCheckIds = new Queue(); private readonly HashSet _queuedCheckIds = new HashSet(); private float _nextCheckSendAt; private const int MaxChecksPerUpdate = 1; private const float CheckSendIntervalSeconds = 0.06f; private const int MaxMainThreadActionsPerUpdate = 8; private const float MainThreadActionBudgetSeconds = 0.004f; private const float GameplayPollSliceIntervalSeconds = 0.03f; private float _nextGameplayPollAt; private int _gameplayPollIndex; private float _nextBossScanAt; private const float BossScanIntervalSeconds = 0.2f; private int _questPollCursor; private const int QuestPollChunkSize = 24; private readonly Queue _deferredProfessionChecks = new Queue(); private const int MaxProfessionChecksDrainPerUpdate = 2; private int _goal7QuestCursor; private int _goal7StepsInPass; private bool _goal7PassClean; private const int Goal7QuestKeysPerPoll = 20; private Dictionary _portalItemsReceived = new Dictionary { { "Sanctum Portal", false }, { "Outer Sanctum Portal", false }, { "Arcwood Pass Portal", false }, { "Effold Terrace Portal", false }, { "Tuul Valley Portal", false }, { "Sanctum Catacombs lvl 1 Portal", false }, { "Sanctum Catacombs lvl 2 Portal", false }, { "Sanctum Catacombs lvl 3 Portal", false }, { "Crescent Road Portal", false }, { "Tuul Enclave Portal", false }, { "Luvora Garden Portal", false }, { "Crescent Keep Portal", false }, { "Bularr Fortress Portal", false }, { "Crescent Grove lvl 1 Portal", false }, { "Crescent Grove lvl 2 Portal", false }, { "Gate of the Moon Portal", false }, { "Wall of the Stars Portal", false }, { "Redwoud Portal", false }, { "Trial of the Stars Portal", false } }; public ArchipelagoShopSanity _shopSanity; public ArchipelagoProfessionTools _professionTools; public int professionToolsOption; public bool fishingRodToolAcquired; public bool pickaxeToolAcquired; private readonly HashSet _reportedChecks = new HashSet(); private int _lastLevel; internal int _previousFishingLevel; internal int _previousMiningLevel; private HashSet _completedQuests = new HashSet(); private bool _questDebugLogged; private bool _questCompletionStateSeeded; private string currentSessionId = ""; private const string SESSION_STATE_FILE = "ap_session_state.dat"; private int _lastProcessedItemIndex; private int _itemTraceCounter; private bool _suppressPortalUnlockAnnouncements; private readonly HashSet _grantedItemKeys = new HashSet(); private static readonly string[] ChatMethodCandidates = new string[6] { "Init_ChatMessage", "Init_PlayerMessage", "Init_LocalMessage", "Init_SystemMessage", "Add_ChatMessage", "Push_ChatMessage" }; private MethodInfo _cachedChatRouteMethod; private bool _chatRouteDiscoveryLogged; private bool _chatRouteSelectedLogged; private bool _pendingConnect; private bool _pendingConnectNotified; private bool _pressF5ConnectHintShownThisSession; private float _lastSaveQuitDisconnectAt = -999f; private const float SaveQuitDisconnectCooldownSeconds = 1f; private bool _pendingDisconnectAfterSaveQuit; private float _nextShopBadgeRefreshAt; private const float ShopBadgeRefreshIntervalSeconds = 0.25f; private bool _shopInterfaceWasOpen; private readonly Queue _pendingChatMessages = new Queue(); private const int PendingChatMessagesCap = 64; private bool _chatQueueDropLogged; private int _chatQueueDroppedCount; private ChatBehaviour _maxGameLogicLinesAppliedTo; private Canvas _apStatusCanvas; private GameObject _apStatusPanel; private Text _apStatusText; private float _apStatusUntil; private bool _chatBecameReadyLogged; private bool _chatNotReadyLogged; private string _stickyPromptMessage; private float _stickyPromptUntil; private float _nextStickyPromptAt; private Color _stickyPromptColor = new Color(1f, 0.35f, 0.35f, 1f); private static readonly int[] LevelMilestoneLevels = new int[16] { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32 }; private static readonly string[] AchievementCheckNames = new string[9] { "A New Journey", "Clearing Catacombs (1-6)", "Clearing Catacombs (6-12)", "Clearing Catacombs (12-18)", "Clearing Grove (15-20)", "Clearing Grove (20-25)", "Altered Vision", "Scaling the Tower", "Rude!" }; private int _grantedItemProgressDirty; private int _progressiveEquipmentDirty; private int _portalProgressDirty; private int _shopPurchasedDirty; private float _persistenceFlushAfter = -1f; private const float PersistenceFlushDebounceSeconds = 0.4f; internal bool PortalLockEnforcementActive { get { if (!connected) { return connecting; } return true; } } public int ProgressiveSanctumPortalCount => progressiveSanctumPortalCount; public int ProgressiveTuulPortalCount => progressiveTuulPortalCount; public static AtlyssArchipelagoPlugin Instance { get; private set; } public static ManualLogSource StaticLogger { get; private set; } private void PollForAchievements() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown try { Player mainPlayer = Player._mainPlayer; if ((Object)mainPlayer == (Object)null) { return; } PlayerStats pStats = mainPlayer._pStats; if (!((Object)pStats == (Object)null)) { if (!_achievementScanDone) { RunAchievementDiagnostics(mainPlayer); EnsureActiveProfilePath(); _achievementScanDone = true; } CheckNewJourney(pStats); CheckScalingTheTower(); if (Time.unscaledTime >= _nextBossScanAt) { _nextBossScanAt = Time.unscaledTime + 0.2f; CheckBossDeaths(); } } } catch (Exception ex) { LogError("Error polling achievements: " + ex.Message); } } private void CheckNewJourney(PlayerStats stats) { if (!_newJourneySent && stats.Network_currentLevel >= 1) { SendAchievementCheck("A New Journey"); _newJourneySent = true; } } private void CheckBossDeaths() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_0049: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Expected O, but got Unknown //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Expected O, but got Unknown //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Expected O, but got Unknown //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Expected O, but got Unknown //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Expected O, but got Unknown //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Expected O, but got Unknown try { Player mainPlayer = Player._mainPlayer; if ((Object)mainPlayer == (Object)null) { return; } MapInstance network_playerMapInstance = mainPlayer.Network_playerMapInstance; if ((Object)network_playerMapInstance == (Object)null) { return; } if ((Object)network_playerMapInstance != (Object)_lastTrackedMap) { _lastTrackedMap = network_playerMapInstance; _trackedBossSpawner = null; _trackedBoss = null; _trackedBossName = null; _bossDeathFired = false; } if (_bossDeathFired) { return; } if ((Object)_trackedBoss != (Object)null) { StatusEntity statusEntity = _trackedBoss._statusEntity; if (!((Object)(object)statusEntity == (Object)null) && statusEntity._currentHealth <= 0) { _bossDeathFired = true; string text = NormalizeBossName(_trackedBossName); if (string.IsNullOrEmpty(text)) { text = _trackedBossName; } HandleBossDeath(text, _trackedBossName); } return; } if ((Object)_trackedBossSpawner == (Object)null) { PatternInstanceManager patternInstance = network_playerMapInstance._patternInstance; if ((Object)patternInstance != (Object)null && (Object)patternInstance._bossSpawner != (Object)null) { string text2 = patternInstance._bossSpawner._creepToSpawn?._creepName; string text3 = NormalizeBossName(text2); if (text3 != null) { _trackedBossSpawner = patternInstance._bossSpawner; _trackedBossName = text3; LogDebug("Tracking boss spawner: normalized='" + text3 + "', raw='" + text2 + "'"); } } else { try { GameObject val = ((IEnumerable)((Scene)(ref network_playerMapInstance._loadedScene)).GetRootGameObjects()).FirstOrDefault((Func)((GameObject o) => ((Object)o).name == "_CREEPSPAWNER" || ((Object)o).name == "_CREEPSPAWNERS")); if ((Object)val != (Object)null) { CreepSpawner[] componentsInChildren = val.GetComponentsInChildren(); foreach (CreepSpawner val2 in componentsInChildren) { if (!((Object)val2._creepToSpawn == (Object)null)) { string creepName = val2._creepToSpawn._creepName; string text4 = NormalizeBossName(creepName); if (val2._isSpecialSpawn && text4 != null) { _trackedBossSpawner = val2; _trackedBossName = text4; LogDebug("Tracking boss fallback spawner: normalized='" + text4 + "', raw='" + creepName + "'"); break; } } } } } catch { } } } CreepSpawner trackedBossSpawner = _trackedBossSpawner; if ((Object)(object)trackedBossSpawner != (Object)null) { List spawnedCreeps = trackedBossSpawner._spawnedCreeps; if (spawnedCreeps != null && spawnedCreeps.Count > 0) { _trackedBoss = _trackedBossSpawner._spawnedCreeps[0]; } } } catch (Exception ex) { LogError("Error in CheckBossDeaths: " + ex.Message); } } private void TryFireGoalOnBossDeath(string bossName) { if (_goalSent || _session == null || string.IsNullOrEmpty(bossName) || !BOSS_TO_GOALS.TryGetValue(bossName, out int[] value)) { return; } int[] array = value; foreach (int num in array) { if (num == goalOption) { if (num != 6 || AllBossGoalNames.All((string requiredBoss) => _killedBossNames.Contains(requiredBoss))) { SendGoalComplete(); } break; } } } private void EnsureActiveProfilePath() { if (!string.IsNullOrEmpty(_activeProfilePath) && File.Exists(_activeProfilePath)) { return; } try { string path = Path.Combine(Application.dataPath, "profileCollections"); if (!Directory.Exists(path)) { return; } string activeProfilePath = null; DateTime dateTime = DateTime.MinValue; string[] files = Directory.GetFiles(path, "atl_characterProfile_*"); foreach (string text in files) { if (!text.EndsWith("_bak")) { DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(text); if (lastWriteTimeUtc > dateTime) { dateTime = lastWriteTimeUtc; activeProfilePath = text; } } } _activeProfilePath = activeProfilePath; } catch (Exception ex) { LogWarn("Could not resolve active profile path: " + ex.Message); } } public void SendAchievementCheck(string achievementName) { if (!achievementsEnabled) { _completedAchievements.Add(achievementName); } else { if (!ArchipelagoGameDataTables.AllLocationNameToId.TryGetValue(achievementName, out var value)) { return; } if (IsLocationCheckSatisfied(value)) { _completedAchievements.Add(achievementName); if (achievementName == "A New Journey") { _newJourneySent = true; } } else if (!_completedAchievements.Contains(achievementName)) { _completedAchievements.Add(achievementName); if (achievementName == "A New Journey") { _newJourneySent = true; } SendCheckById(value); LogInfo("Achievement check sent: " + achievementName); } } } public void CleanupAchievementWatchers() { } private void RunAchievementDiagnostics(Player localPlayer) { //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Expected O, but got Unknown //IL_031a: Unknown result type (might be due to invalid IL or missing references) //IL_0325: Expected O, but got Unknown LogInfo("--- Achievement Diagnostic Scan ---"); FriendlyNPC_Hitbox[] array = Object.FindObjectsOfType(); FieldInfo fieldInfo = AccessTools.Field(typeof(FriendlyNPC_Hitbox), "_achievementTag"); LogInfo($"FriendlyNPC_Hitbox count: {array.Length}"); FriendlyNPC_Hitbox[] array2 = array; foreach (FriendlyNPC_Hitbox val in array2) { string text = ((fieldInfo != null) ? ((string)fieldInfo.GetValue(val)) : "(field not found)"); LogInfo(" '" + ((Object)((Component)val).gameObject).name + "' | tag: '" + text + "'"); } LogInfo("PlayerStats fields of interest:"); FieldInfo[] fields = typeof(PlayerStats).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo2 in fields) { string text2 = fieldInfo2.Name.ToLower(); if (text2.Contains("class") || text2.Contains("subclass") || text2.Contains("tier") || text2.Contains("appear") || text2.Contains("skin") || text2.Contains("cosmetic") || text2.Contains("illusion") || text2.Contains("achieve") || text2.Contains("advanced") || text2.Contains("vanity") || text2.Contains("skill") || text2.Contains("prof")) { try { object value = fieldInfo2.GetValue(localPlayer._pStats); LogInfo($" PlayerStats.{fieldInfo2.Name} ({fieldInfo2.FieldType.Name}) = {value}"); } catch { } } } LogInfo("All player components (vanity/skill/appear fields):"); MonoBehaviour[] components = ((Component)localPlayer).GetComponents(); foreach (MonoBehaviour val2 in components) { if ((Object)val2 == (Object)null) { continue; } bool flag = false; fields = ((object)val2).GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo3 in fields) { string text3 = fieldInfo3.Name.ToLower(); if (!text3.Contains("vanity") && !text3.Contains("skill") && !text3.Contains("appear") && !text3.Contains("equip")) { continue; } try { object value2 = fieldInfo3.GetValue(val2); if (!flag) { LogInfo(" [" + ((object)val2).GetType().Name + "]"); flag = true; } LogInfo($" .{fieldInfo3.Name} ({fieldInfo3.FieldType.Name}) = {value2}"); } catch { } } } LogInfo("PlayerInventory fields of interest:"); PlayerInventory component = ((Component)localPlayer).GetComponent(); if ((Object)component != (Object)null) { fields = typeof(PlayerInventory).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo4 in fields) { string text4 = fieldInfo4.Name.ToLower(); if (text4.Contains("vanity") || text4.Contains("item") || text4.Contains("inven")) { try { object value3 = fieldInfo4.GetValue(component); LogInfo($" PlayerInventory.{fieldInfo4.Name} ({fieldInfo4.FieldType.Name}) = {value3}"); } catch { } } } } LogInfo("Achievement and chest components in scene:"); components = Object.FindObjectsOfType(); foreach (MonoBehaviour val3 in components) { string text5 = ((object)val3).GetType().Name.ToLower(); bool num = text5.Contains("achieve"); bool flag2 = text5.Contains("chest") || text5.Contains("loot") || text5.Contains("treasure"); if (!num && !flag2) { continue; } LogInfo(" [" + ((object)val3).GetType().Name + "] on '" + ((Object)((Component)val3).gameObject).name + "'"); if (!(((object)val3).GetType().Name == "SteamAchievementTrigger")) { continue; } MethodInfo[] methods = ((object)val3).GetType().GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { LogInfo(" method: " + methodInfo.Name + "(" + string.Join(", ", Array.ConvertAll(methodInfo.GetParameters(), (ParameterInfo p) => p.ParameterType.Name)) + ")"); } } string text6 = Path.Combine(Application.dataPath, "profileCollections"); LogInfo($"profileCollections: {text6} | exists: {Directory.Exists(text6)}"); if (Directory.Exists(text6)) { string[] files = Directory.GetFiles(text6); foreach (string path in files) { LogInfo(" profile file: " + Path.GetFileName(path)); } } LogInfo("--- End of Achievement Diagnostic Scan ---"); } private static string NormalizeBossName(string rawBossName) { if (string.IsNullOrWhiteSpace(rawBossName)) { return null; } if (BOSS_TO_GOALS.ContainsKey(rawBossName)) { return rawBossName; } string text = rawBossName.Trim().ToLowerInvariant(); if (text.Contains("slime") && text.Contains("diva")) { return "Slime Diva"; } if (text.Contains("zuulneruda")) { return "Lord Zuulneruda"; } if (text.Contains("colossus")) { return "Colossus"; } if (text.Contains("galius")) { return "Galius"; } if (text.Contains("kaluuz")) { return "Lord Kaluuz"; } if (text.Contains("valdur")) { return "Valdur"; } return null; } private static bool LooksLikeKnownBossName(string rawBossName) { if (string.IsNullOrWhiteSpace(rawBossName)) { return false; } string text = rawBossName.Trim().ToLowerInvariant(); if ((!text.Contains("slime") || !text.Contains("diva")) && !text.Contains("zuulneruda") && !text.Contains("colossus") && !text.Contains("galius") && !text.Contains("kaluuz")) { return text.Contains("valdur"); } return true; } private void HandleBossDeath(string normalizedBossName, string rawBossName) { if (!string.IsNullOrEmpty(normalizedBossName) && !_killedBossNames.Contains(normalizedBossName)) { _killedBossNames.Add(normalizedBossName); LogInfo("Boss death detected: " + normalizedBossName + " (raw='" + rawBossName + "')"); TrySendBossKillLocationCheck(normalizedBossName); TryFireGoalOnBossDeath(normalizedBossName); } } private void TrySendBossKillLocationCheck(string normalizedBossName) { if (!string.IsNullOrEmpty(normalizedBossName) && ArchipelagoGameDataTables.AllLocationNameToId.ContainsKey(normalizedBossName)) { SendCheckByName(normalizedBossName); LogInfo("Boss kill location check queued: " + normalizedBossName); } } private string ResolveCreepSpeciesName(Creep creep) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown if ((Object)creep == (Object)null) { return null; } if (CreepScriptableField != null) { object? value = CreepScriptableField.GetValue(creep); ScriptableCreep val = (ScriptableCreep)((value is ScriptableCreep) ? value : null); if ((Object)val != (Object)null && !string.IsNullOrWhiteSpace(val._creepName)) { return val._creepName.Trim(); } } if (CreepNameField != null) { string text = CreepNameField.GetValue(creep) as string; if (!string.IsNullOrWhiteSpace(text)) { return text.Trim(); } } if (!string.IsNullOrWhiteSpace(creep.Network_creepDisplayName)) { return creep.Network_creepDisplayName.Trim(); } return null; } private void TrySendCreepSanityLocationCheck(string creepSpeciesName) { if (creepSanityEnabled && !string.IsNullOrWhiteSpace(creepSpeciesName)) { string text = NormalizeBossName(creepSpeciesName) ?? creepSpeciesName.Trim(); string text2 = "Defeat Creep: " + text; if (ArchipelagoGameDataTables.AllLocationNameToId.TryGetValue(text2, out var value) && !_reportedChecks.Contains(value) && !_queuedCheckIds.Contains(value)) { SendCheckById(value); LogInfo("Creep sanity location check queued: " + text2); } } } internal void NotifyBossDeathFromCreep(Creep creep, string source) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Expected O, but got Unknown try { if ((Object)creep == (Object)null) { return; } string creepSpeciesName = ResolveCreepSpeciesName(creep); TrySendCreepSanityLocationCheck(creepSpeciesName); string rawBossName = creep.Network_creepDisplayName; string text = NormalizeBossName(rawBossName); if (text == null && !LooksLikeKnownBossName(rawBossName) && CreepNameField != null) { rawBossName = CreepNameField.GetValue(creep) as string; text = NormalizeBossName(rawBossName); } if (text == null && CreepScriptableField != null) { object? value = CreepScriptableField.GetValue(creep); ScriptableCreep val = (ScriptableCreep)((value is ScriptableCreep) ? value : null); if ((Object)val != (Object)null) { rawBossName = val._creepName; text = NormalizeBossName(rawBossName); } } if (text != null) { HandleBossDeath(text, rawBossName); } } catch (Exception ex) { LogError("Boss death hook error: " + ex.Message); } } private bool CanConnectNow(out string reason) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown reason = null; if ((Object)Player._mainPlayer == (Object)null) { reason = "Player not loaded yet."; return false; } if (!IsChatReady()) { reason = "World / chat UI not ready yet."; return false; } return true; } private bool IsWelcomeLineVisibleInMainChat() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown try { Player mainPlayer = Player._mainPlayer; if ((Object)mainPlayer == (Object)null) { return false; } ChatBehaviour chatBehaviour = mainPlayer._chatBehaviour; if ((Object)chatBehaviour == (Object)null) { return false; } ChatBehaviourAssets chatAssets = chatBehaviour._chatAssets; if ((Object)chatAssets == (Object)null) { return false; } TextMeshProUGUI chatText = chatAssets._chatText; if ((Object)chatText == (Object)null) { return false; } string text = ((TMP_Text)chatText).text; return !string.IsNullOrEmpty(text) && text.IndexOf("Welcome to ATLYSS", StringComparison.OrdinalIgnoreCase) >= 0; } catch { return false; } } internal void TryOfferPressF5ConnectHintAfterWelcomePainted() { OfferPressF5ConnectHintOnce(); } private void OfferPressF5ConnectHintOnce() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) if (!_pressF5ConnectHintShownThisSession && !connected && !connecting && CanConnectNow(out string _) && IsWelcomeLineVisibleInMainChat()) { _pressF5ConnectHintShownThisSession = true; ShowStickyImmediateStatusPrompt("Press F5 to connect to Archipelago.", 10f, (Color?)new Color(1f, 0.95f, 0.3f, 1f)); SendAPChatMessage("Press F5 to connect to Archipelago."); LogInfo("Character and world ready — press F5 to connect to Archipelago."); } } private object[] BuildInvocationArgs(ParameterInfo[] parameters, string formattedMessage) { object[] array = new object[parameters.Length]; for (int i = 0; i < parameters.Length; i++) { Type parameterType = parameters[i].ParameterType; if (i == 0 && parameterType == typeof(string)) { array[i] = formattedMessage; continue; } if (parameterType == typeof(bool)) { array[i] = false; continue; } if (parameterType == typeof(int)) { array[i] = 0; continue; } if (parameterType == typeof(float)) { array[i] = 0f; continue; } if (parameterType.IsEnum) { Array values = Enum.GetValues(parameterType); array[i] = ((values.Length > 0) ? values.GetValue(0) : Activator.CreateInstance(parameterType)); continue; } if (!parameterType.IsValueType) { array[i] = null; continue; } return null; } return array; } private bool TryInvokeChatMethod(ChatBehaviour chatBehaviour, MethodInfo method, string formattedMessage) { try { ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length == 0 || parameters.Length > 3 || parameters[0].ParameterType != typeof(string)) { return false; } object[] array = BuildInvocationArgs(parameters, formattedMessage); if (array == null) { return false; } method.Invoke(chatBehaviour, array); _cachedChatRouteMethod = method; if (!_chatRouteSelectedLogged) { _chatRouteSelectedLogged = true; LogInfo("Chat routed via ChatBehaviour." + method.Name + "(" + string.Join(", ", parameters.Select((ParameterInfo p) => p.ParameterType.Name).ToArray()) + ")"); } return true; } catch { return false; } } private bool TrySendToChatBox(ChatBehaviour chatBehaviour, string formattedMessage) { try { Type type = ((object)chatBehaviour).GetType(); if (_cachedChatRouteMethod != null && TryInvokeChatMethod(chatBehaviour, _cachedChatRouteMethod, formattedMessage)) { return true; } string[] chatMethodCandidates = ChatMethodCandidates; MethodInfo[] array; foreach (string name in chatMethodCandidates) { array = (from m in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == name select m).ToArray(); foreach (MethodInfo method in array) { if (TryInvokeChatMethod(chatBehaviour, method, formattedMessage)) { return true; } } } array = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where(delegate(MethodInfo m) { string name2 = m.Name; if (name2.StartsWith("Send_", StringComparison.OrdinalIgnoreCase)) { return false; } if (name2.IndexOf("GameLogic", StringComparison.OrdinalIgnoreCase) >= 0) { return false; } if (name2.IndexOf("Chat", StringComparison.OrdinalIgnoreCase) < 0 && name2.IndexOf("Message", StringComparison.OrdinalIgnoreCase) < 0) { return false; } ParameterInfo[] parameters = m.GetParameters(); return parameters.Length != 0 && parameters.Length <= 3 && parameters[0].ParameterType == typeof(string); }).ToArray(); foreach (MethodInfo method2 in array) { if (TryInvokeChatMethod(chatBehaviour, method2, formattedMessage)) { return true; } } if (!_chatRouteDiscoveryLogged) { _chatRouteDiscoveryLogged = true; string text = string.Join(", ", (from m in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) where m.Name.IndexOf("Chat", StringComparison.OrdinalIgnoreCase) >= 0 || m.Name.IndexOf("Message", StringComparison.OrdinalIgnoreCase) >= 0 select m.Name).Distinct().ToArray()); LogWarn("No safe chat route found; available Chat/Message methods: " + text); } } catch (Exception ex) { LogWarn("Chat-box routing failed, using fallback overlay: " + ex.Message); } return false; } private bool TryDeliverAPChatMessage(string message) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown Player mainPlayer = Player._mainPlayer; if ((Object)mainPlayer == (Object)null) { return false; } ChatBehaviour chatBehaviour = mainPlayer._chatBehaviour; if ((Object)chatBehaviour == (Object)null) { return false; } string text = "[Archipelago] " + message; if (!TrySendToChatBox(chatBehaviour, text)) { EnsureGameLogicLinesPinned(chatBehaviour); chatBehaviour.Init_GameLogicMessage(text); } return true; } private void EnsureGameLogicLinesPinned(ChatBehaviour chatBehaviour) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0025: Expected O, but got Unknown if ((Object)chatBehaviour == (Object)null || (Object)_maxGameLogicLinesAppliedTo == (Object)chatBehaviour) { return; } try { maxOnscreenMessages.SetValue(chatBehaviour, 50); _maxGameLogicLinesAppliedTo = chatBehaviour; } catch (Exception ex) { LogWarn("Failed to pin ChatBehaviour._maxGameLogicLines: " + ex.Message); } } private bool IsChatReady() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown Player mainPlayer = Player._mainPlayer; if ((Object)mainPlayer == (Object)null) { return false; } return (Object)mainPlayer._chatBehaviour != (Object)null; } private Font ResolveStatusFont() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown try { Text val = Object.FindObjectOfType(); if ((Object)val != (Object)null && (Object)val.font != (Object)null) { return val.font; } } catch { } return Resources.GetBuiltinResource("Arial.ttf"); } private static Sprite CreateSolidUISprite() { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false); val.SetPixel(0, 0, Color.white); val.Apply(); return Sprite.Create(val, new Rect(0f, 0f, 1f, 1f), new Vector2(0.5f, 0.5f), 100f); } private void EnsureStatusBanner() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Expected O, but got Unknown //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown if (!((Object)_apStatusCanvas != (Object)null) || !((Object)_apStatusPanel != (Object)null) || !((Object)_apStatusText != (Object)null)) { GameObject val = new GameObject("AP_StatusCanvas"); Object.DontDestroyOnLoad((Object)val); _apStatusCanvas = val.AddComponent(); _apStatusCanvas.renderMode = (RenderMode)0; _apStatusCanvas.sortingOrder = 32000; val.AddComponent(); val.AddComponent(); _apStatusPanel = new GameObject("AP_StatusPanel"); _apStatusPanel.transform.SetParent(val.transform, false); Image obj = _apStatusPanel.AddComponent(); obj.sprite = CreateSolidUISprite(); obj.type = (Type)0; ((Graphic)obj).color = new Color(0f, 0f, 0f, 0.72f); ((Graphic)obj).raycastTarget = false; RectTransform component = _apStatusPanel.GetComponent(); component.anchorMin = new Vector2(0.5f, 1f); component.anchorMax = new Vector2(0.5f, 1f); component.pivot = new Vector2(0.5f, 1f); component.anchoredPosition = new Vector2(0f, -165f); component.sizeDelta = new Vector2(1400f, 120f); GameObject val2 = new GameObject("AP_StatusText"); val2.transform.SetParent(_apStatusPanel.transform, false); _apStatusText = val2.AddComponent(); _apStatusText.font = ResolveStatusFont(); _apStatusText.fontSize = 38; _apStatusText.alignment = (TextAnchor)4; ((Graphic)_apStatusText).raycastTarget = false; _apStatusText.horizontalOverflow = (HorizontalWrapMode)1; _apStatusText.verticalOverflow = (VerticalWrapMode)0; RectTransform component2 = val2.GetComponent(); component2.anchorMin = new Vector2(0f, 0f); component2.anchorMax = new Vector2(1f, 1f); component2.offsetMin = new Vector2(14f, 10f); component2.offsetMax = new Vector2(-14f, -10f); _apStatusPanel.SetActive(false); } } private void ShowStatusBanner(string message, Color color, float seconds = 4.5f) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown //IL_0032: Unknown result type (might be due to invalid IL or missing references) try { EnsureStatusBanner(); if ((Object)_apStatusText != (Object)null && (Object)_apStatusPanel != (Object)null) { ((Graphic)_apStatusText).color = color; _apStatusText.text = message; _apStatusPanel.SetActive(true); _apStatusUntil = Time.unscaledTime + Math.Max(1f, seconds); } } catch { } } private bool TryGetErrorPromptManager(out ErrorPromptTextManager manager) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown manager = null; try { ErrorPromptTextManager current = ErrorPromptTextManager.current; if (IsUsable(current)) { manager = current; return true; } GameObject val = GameObject.Find("_GameUI_MainMenu"); if ((Object)val != (Object)null) { ErrorPromptTextManager component = val.GetComponent(); if (IsUsable(component)) { manager = component; return true; } component = val.GetComponentInChildren(true); if (IsUsable(component)) { manager = component; return true; } } GameObject val2 = GameObject.Find("_GameUI_InGame"); if ((Object)val2 != (Object)null) { ErrorPromptTextManager component2 = val2.GetComponent(); if (IsUsable(component2)) { manager = component2; return true; } component2 = val2.GetComponentInChildren(true); if (IsUsable(component2)) { manager = component2; return true; } } ErrorPromptTextManager val3 = Object.FindObjectOfType(); if (IsUsable(val3)) { manager = val3; return true; } ErrorPromptTextManager[] array = Resources.FindObjectsOfTypeAll(); if (array != null) { foreach (ErrorPromptTextManager val4 in array) { if (IsUsable(val4)) { manager = val4; return true; } } } } catch { } return false; static bool IsUsable(ErrorPromptTextManager candidate) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown if ((Object)candidate == (Object)null) { return false; } GameObject gameObject = ((Component)candidate).gameObject; if ((Object)gameObject != (Object)null && gameObject.activeInHierarchy) { return ((Behaviour)candidate).isActiveAndEnabled; } return false; } } private bool ShowImmediateStatusPrompt(string plainMessage) { try { if (TryGetErrorPromptManager(out ErrorPromptTextManager manager)) { manager.Init_ErrorPrompt(plainMessage); return true; } } catch { } return false; } private void ShowStickyImmediateStatusPrompt(string plainMessage, float seconds = 10f, Color? fallbackColor = null) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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) Color valueOrDefault = fallbackColor.GetValueOrDefault(new Color(1f, 0.35f, 0.35f, 1f)); _stickyPromptMessage = plainMessage; _stickyPromptUntil = Time.unscaledTime + Math.Max(1f, seconds); _nextStickyPromptAt = Time.unscaledTime + 1.1f; _stickyPromptColor = valueOrDefault; if (!ShowImmediateStatusPrompt(plainMessage)) { ShowStatusBanner(plainMessage, valueOrDefault, seconds); } } public void SendAPChatMessageDeferred(string message) { try { if (_pendingChatMessages.Count < 64) { _pendingChatMessages.Enqueue(message); return; } _chatQueueDroppedCount++; if (!_chatQueueDropLogged) { _chatQueueDropLogged = true; LogWarn($"AP chat queue full ({64} cap) — dropping further messages until existing ones drain. Subsequent drops will count silently."); } } catch (Exception ex) { LogError("Failed to queue chat message: " + ex.Message); } } public void SendAPChatMessage(string message) { try { if (TryDeliverAPChatMessage(message)) { return; } if (_pendingChatMessages.Count < 64) { _pendingChatMessages.Enqueue(message); return; } _chatQueueDroppedCount++; if (!_chatQueueDropLogged) { _chatQueueDropLogged = true; LogWarn($"AP chat queue full ({64} cap) — dropping further messages until existing ones drain. Subsequent drops will count silently."); } } catch (Exception ex) { LogError("Failed to send chat message: " + ex.Message); } } private void FlushPendingChatMessages(int maxMessages) { if (_pendingChatMessages.Count == 0) { NoteChatQueueDrainedIfPreviouslyOverflowed(); return; } if (!IsChatReady()) { if (!_chatNotReadyLogged) { _chatNotReadyLogged = true; LogInfo($"Chat not ready yet; holding {_pendingChatMessages.Count} queued AP message(s)."); } return; } _chatNotReadyLogged = false; if (!_chatBecameReadyLogged) { _chatBecameReadyLogged = true; LogInfo($"Chat became ready; flushing {_pendingChatMessages.Count} queued AP message(s)."); } int num = 0; while (_pendingChatMessages.Count > 0 && num < maxMessages && TryDeliverAPChatMessage(_pendingChatMessages.Peek())) { _pendingChatMessages.Dequeue(); num++; } if (_pendingChatMessages.Count == 0) { NoteChatQueueDrainedIfPreviouslyOverflowed(); } } private void NoteChatQueueDrainedIfPreviouslyOverflowed() { if (_chatQueueDropLogged) { LogInfo($"AP chat queue drained. {_chatQueueDroppedCount} message(s) were dropped during the previous overflow episode."); _chatQueueDropLogged = false; _chatQueueDroppedCount = 0; } } public void HandleArchipelagoCommand(string message) { if (!connected || _session == null) { SendAPChatMessage("Not connected to Archipelago!"); return; } string[] array = message.TrimStart(new char[1] { '/' }).Trim().Split(new char[1] { ' ' }, 2); string text = array[0].ToLower(); string text2 = ((array.Length > 1) ? array[1] : ""); LogInfo("Command received: " + text + " " + text2); switch (text) { case "release": HandleReleaseCommand(); return; case "collect": HandleCollectCommand(); return; case "hint": HandleHintCommand(text2); return; case "help": HandleHelpCommand(); return; case "players": HandlePlayersCommand(); return; case "status": HandleStatusCommand(); return; } try { _session.Socket.SendPacket(new SayPacket { Text = message }); LogInfo("Sent command to server: " + message); } catch (Exception ex) { LogError("Failed to send command: " + ex.Message); SendAPChatMessage("Unknown command: /" + EscapeServerChatToken(text) + ""); } } private void HandleReleaseCommand() { try { _session.Say("!release"); SendAPChatMessage("Release requested!"); LogInfo("Release command executed"); } catch (Exception ex) { LogError("Failed to execute release: " + ex.Message); SendAPChatMessage("Failed to execute release"); } } private void HandleCollectCommand() { try { _session.Say("!collect"); SendAPChatMessage("Collect requested!"); LogInfo("Collect command executed"); } catch (Exception ex) { LogError("Failed to execute collect: " + ex.Message); SendAPChatMessage("Failed to execute collect"); } } private void HandleHintCommand(string args) { try { if (string.IsNullOrWhiteSpace(args)) { SendAPChatMessage("Usage: /hint [item name]"); return; } SendAPChatMessage("Requesting hint for: " + EscapeServerChatToken(args) + ""); _session.Say("!hint " + args); LogInfo("Hint requested for: " + args); } catch (Exception ex) { LogError("Failed to request hint: " + ex.Message); } } private void HandleHelpCommand() { SendAPChatMessage("Archipelago Commands:"); SendAPChatMessage("/release - Release remaining items"); SendAPChatMessage("/collect - Collect items from others"); SendAPChatMessage("/hint [item] - Request hint"); SendAPChatMessage("/players - List connected players"); SendAPChatMessage("/status - Show completion status"); SendAPChatMessage("/help - Show this message"); } private void HandlePlayersCommand() { try { IEnumerable allPlayers = _session.Players.AllPlayers; int num = 0; foreach (PlayerInfo item in allPlayers) { _ = item; num++; } SendAPChatMessage($"Connected Players ({num}):"); foreach (PlayerInfo item2 in allPlayers) { string token = item2.Name ?? $"Player {item2.Slot}"; string token2 = item2.Game ?? "Unknown"; SendAPChatMessage("- " + EscapeServerChatToken(token) + " (" + EscapeServerChatToken(token2) + ")"); } LogInfo($"Listed {num} players"); } catch (Exception ex) { LogError("Failed to list players: " + ex.Message); SendAPChatMessage("Failed to get player list"); } } private void HandleStatusCommand() { try { int count = _reportedChecks.Count; int count2 = ArchipelagoGameDataTables.AllLocationNameToId.Count; int count3 = ArchipelagoGameDataTables.AllQuestToLocation.Count; float num = ((count2 > 0) ? ((float)count / (float)count2 * 100f) : 0f); SendAPChatMessage($"Progress: {count}/{count2} ({num:F1}%)"); SendAPChatMessage($"Level milestones: {_lastLevel}/32"); SendAPChatMessage($"Quest completions: {_completedQuests.Count}/{count3}"); LogInfo($"Status: {count}/{count2} locations checked"); } catch (Exception ex) { LogError("Failed to show status: " + ex.Message); } } private void TryConnect() { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Expected O, but got Unknown //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Expected O, but got Unknown //IL_01ce: Unknown result type (might be due to invalid IL or missing references) if (connected) { LogWarn("Already connected."); ShowImmediateStatusPrompt("Already connected to Archipelago."); SendAPChatMessage("Already connected to Archipelago."); return; } if (connecting) { LogWarn("Connection in progress..."); ShowImmediateStatusPrompt("Archipelago connection in progress..."); SendAPChatMessage("Connection already in progress..."); return; } if (!CanConnectNow(out string reason)) { _pendingConnect = true; ShowStickyImmediateStatusPrompt("Load a character first, then connect to Archipelago.", 8f); SendAPChatMessage("Load a character first, then connect to Archipelago."); if (!_pendingConnectNotified) { _pendingConnectNotified = true; LogInfo("Connection deferred until character is loaded. Reason: " + reason); } return; } _pendingConnect = false; _pendingConnectNotified = false; bool flag = (Object)apServer != (Object)null && (Object)apSlot != (Object)null; string server = (flag ? apServer.text : cfgServer.Value); string slot = (flag ? apSlot.text : cfgSlot.Value); string typedPassword = (((Object)apPassword != (Object)null) ? apPassword.text : cfgPassword.Value); bool flag2 = (((Object)apDeathlink != (Object)null) ? apDeathlink.isOn : cfgDeathlink.Value); if (string.IsNullOrWhiteSpace(server) || string.IsNullOrWhiteSpace(slot)) { LogWarn("Missing server or slot name. Open AP settings tab once or set Connection.Server/Connection.Slot in config."); ShowStatusBanner("Missing AP Server/Slot config.", new Color(1f, 0.35f, 0.35f, 1f), 6f); SendAPChatMessage("Missing AP Server/Slot config."); return; } if (!flag) { LogInfo("AP settings UI not initialized yet; connecting using saved config values."); } _suppressAutoReconnect = false; _autoReconnectWhenReady = false; _autoReconnectAt = -1f; if (_session != null) { TeardownLiveSession(); } LogInfo("=== CONNECTING TO ARCHIPELAGO ==="); LogInfo("Server: " + server); LogInfo("Slot: " + slot); connecting = true; long attemptId = ++_connectAttemptId; string passwordArg = (string.IsNullOrWhiteSpace(typedPassword) ? null : typedPassword); string[] tagsArg = ((!flag2) ? Array.Empty() : new string[1] { "DeathLink" }); Task.Run(delegate { ArchipelagoSession workerSession = null; DeathLinkService workerDlService = null; LoginResult workerResult = null; Exception workerError = null; try { workerSession = ArchipelagoSessionFactory.CreateSession(server); workerDlService = workerSession.CreateDeathLinkService(); workerResult = workerSession.TryConnectAndLogin("Atlyss", slot, ItemsHandlingFlags.AllItems, null, tagsArg, null, passwordArg); } catch (Exception ex) { workerError = ex; } _mainThreadQueue.Enqueue(delegate { OnConnectAttemptCompleted(attemptId, workerSession, workerDlService, workerResult, workerError, server, slot, typedPassword); }); }); } private void OnConnectAttemptCompleted(long attemptId, ArchipelagoSession workerSession, DeathLinkService workerDlService, LoginResult workerResult, Exception workerError, string server, string slot, string typedPassword) { //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Expected O, but got Unknown //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Expected O, but got Unknown //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Expected O, but got Unknown //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Expected O, but got Unknown if (attemptId != _connectAttemptId || !connecting) { LogInfo("Connect attempt result ignored (disconnected or superseded before it completed)."); TearDownStaleSession(workerSession); return; } if (workerError != null) { LogError("Connection failed: " + workerError.Message); LogError("Stack: " + workerError.StackTrace); TearDownStaleSession(workerSession); connecting = false; connected = false; MaybeScheduleReconnectAfterFailure(); return; } if (workerResult == null || !workerResult.Successful) { if (workerResult is LoginFailure { Errors: not null } loginFailure && loginFailure.Errors.Length != 0) { LogError("Login failed: " + string.Join(", ", loginFailure.Errors)); } else { LogError("Login failed."); } TearDownStaleSession(workerSession); connecting = false; connected = false; MaybeScheduleReconnectAfterFailure(); return; } try { _session = workerSession; _dlService = workerDlService; LogInfo("Session and DeathLink service created"); LogInfo("Login successful!"); cfgServer.Value = server; cfgSlot.Value = slot; cfgPassword.Value = string.Empty; ApplySlotData(workerResult); InitializeSessionPersistence(server, slot); SpikePatch.InitializeAPStorage(); int lastProcessedItemIndex = _lastProcessedItemIndex; _suppressPortalUnlockAnnouncements = true; try { ReconcileItemReplayOnConnect(); ReconcileGrantLedgerFromExistingApProgress(lastProcessedItemIndex); DrainQueuedReceivedItems(); GrantMissingReceivedItemsAfterSavedIndex(lastProcessedItemIndex); } finally { _suppressPortalUnlockAnnouncements = false; } WireSessionEvents(); RequestInitialPackets(); SendAPChatMessage("Connected to multiworld!"); SendGoalChatMessage(); portalLocker.ApplyAreaAccessMode(); ReapplyUnlockedPortals(); if (shopSanityEnabled) { _shopSanity.SendLocationScouts(_session); } Player mainPlayer = Player._mainPlayer; if ((Object)mainPlayer != (Object)null) { PlayerStats component = ((Component)mainPlayer).GetComponent(); if ((Object)component != (Object)null) { _lastLevel = component.Network_currentLevel; LogInfo($"Starting at level {_lastLevel}"); } } _professionTools?.SyncAcquiredFlagsFromSession(); _professionTools?.SyncProfessionLevelBaselines(); connected = true; connecting = false; _pendingConnect = false; _pendingConnectNotified = false; _autoReconnectAttempt = 0; _autoReconnectAt = -1f; _autoReconnectWhenReady = false; SeedCompletionStateFromServer(); if ((Object)mainPlayer != (Object)null) { PlayerStats component2 = ((Component)mainPlayer).GetComponent(); if ((Object)component2 != (Object)null) { EnsureLevelMilestoneChecks(component2.Network_currentLevel); } } portalLocker.ApplyPortalLocksToAllLoadedScenes(); FinalizeConnectSuccess(); } catch (Exception ex) { LogError("Post-login wiring failed: " + ex.Message); LogError("Stack: " + ex.StackTrace); Disconnect(); connecting = false; connected = false; } } private void TearDownStaleSession(ArchipelagoSession staleSession) { if (staleSession == null) { return; } try { if (staleSession.Socket != null) { staleSession.Socket.DisconnectAsync(); } } catch (Exception ex) { LogWarn("Failed to tear down stale Archipelago session: " + ex.Message); } } private void ApplySlotData(LoginResult loginResult) { try { if (loginResult is LoginSuccessful { SlotData: not null } loginSuccessful) { slotData = loginSuccessful.SlotData; if (slotData.TryGetValue("goal", out object value)) { goalOption = NormalizeGoalOption(Convert.ToInt32(value)); LogInfo("Goal: " + GoalDisplayNames[goalOption]); } if (slotData.TryGetValue("random_portals", out object value2)) { randomPortalsEnabled = Convert.ToInt32(value2) == 1; LogInfo("Portal Mode: " + (randomPortalsEnabled ? "Random Portals" : "Progressive Portals")); } if (slotData.TryGetValue("shop_sanity", out object value3)) { shopSanityEnabled = Convert.ToInt32(value3) == 1; LogInfo("Shop Sanity: " + (shopSanityEnabled ? "Enabled" : "Disabled")); } if (slotData.TryGetValue("achievements", out object value4)) { achievementsEnabled = Convert.ToInt32(value4) == 1; LogInfo("Achievements: " + (achievementsEnabled ? "Enabled" : "Disabled")); } else { achievementsEnabled = true; } creepSanityEnabled = slotData.TryGetValue("creep_sanity", out object value5) && Convert.ToInt32(value5) == 1; if (creepSanityEnabled) { LogWarn("Creep Sanity is on in slot data but the apworld does not place Defeat Creep locations yet; checks will not land on the server."); } if (slotData.TryGetValue("experience_multiplier", out object value6)) { experienceMultiplier = AtlyssEconomyMultipliers.FromOptionValue(Convert.ToInt32(value6)); } else { experienceMultiplier = 1f; } LogInfo($"Experience Multiplier: x{experienceMultiplier:0.##}"); if (slotData.TryGetValue("crown_multiplier", out object value7)) { crownMultiplier = AtlyssEconomyMultipliers.FromOptionValue(Convert.ToInt32(value7)); } else { crownMultiplier = 1f; } LogInfo($"Crown Multiplier: x{crownMultiplier:0.##}"); if (slotData.TryGetValue("equipment_progression", out object value8)) { equipmentProgressionOption = NormalizeEquipmentProgressionOption(Convert.ToInt32(value8)); } else { equipmentProgressionOption = 1; } LogInfo("Equipment Progression: " + ((equipmentProgressionOption == 0) ? "Gated" : "Unrestricted")); if (slotData.TryGetValue("class_filter", out object value9)) { classFilterOption = NormalizeClassFilterOption(Convert.ToInt32(value9)); } else { classFilterOption = 0; } LogInfo("Class Filter: " + ClassFilterDisplayNames[classFilterOption]); if (slotData.TryGetValue("profession_tools", out object value10)) { professionToolsOption = NormalizeProfessionToolsOption(Convert.ToInt32(value10)); } else { professionToolsOption = 0; } LogInfo("Profession Tools: " + ((professionToolsOption == 0) ? "Static" : "Pool")); } else { LogWarn("Login successful but slot data is null - using default options"); } } catch (Exception ex) { LogWarn("Could not read slot data: " + ex.Message); } } private static int NormalizeGoalOption(int raw) { if (raw >= 0 && raw < 9) { return raw; } SLogWarn("Invalid goal slot value " + raw + "; using Slime Diva."); return 0; } private static int NormalizeEquipmentProgressionOption(int raw) { if (raw == 0 || raw == 1) { return raw; } SLogWarn("Invalid equipment_progression slot value " + raw + "; using Unrestricted."); return 1; } private static int NormalizeClassFilterOption(int raw) { if (raw >= 0 && raw < ClassFilterDisplayNames.Length) { return raw; } SLogWarn("Invalid class_filter slot value " + raw + "; using All Classes."); return 0; } private static int NormalizeProfessionToolsOption(int raw) { if (raw < 0) { return 0; } if (raw >= 2) { return 1; } return raw; } private void SendGoalChatMessage() { if (goalOption >= 0 && goalOption < GoalChatMessages.Length) { SendAPChatMessage(GoalChatMessages[goalOption]); } } private string BuildSessionKey(string slot) { return slot.Trim() + "_" + _session.RoomState.Seed; } private static string BuildLegacySessionKey(string server, string slot, string seed) { return server + "_" + slot.Trim() + "_" + seed; } private void InitializeSessionPersistence(string server, string slot) { EnsurePersistenceMigratedFromLegacyAppData(); LogInfo("AP persistence directory: " + GetApPersistenceDirectory()); string seed = _session.RoomState.Seed; string text = BuildSessionKey(slot); string legacySessionId = BuildLegacySessionKey(server, slot, seed); if (LoadSessionState(text, legacySessionId)) { LogInfo("New AP session detected - clearing storage"); ArchipelagoSpikeStorage.ClearAPSession(); _lastProcessedItemIndex = 0; SaveSessionState(text, 0); lock (_grantedItemKeys) { _grantedItemKeys.Clear(); } SaveGrantedItemKeys(text); ClearShopPurchasedStateFile(text); _progressiveCounts.Clear(); _grantedProgressiveConcreteItems.Clear(); SavePortalProgressState(text); SaveProgressiveEquipmentState(text); LogInfo("Saved session ID: " + text); } currentSessionId = text; LoadGrantedItemKeys(currentSessionId); LoadShopPurchasedLocations(currentSessionId); LoadPortalProgressState(currentSessionId, legacySessionId); LoadProgressiveEquipmentState(currentSessionId, legacySessionId); if (_shopSanity != null && _shopSanity.MergePurchasedShopLocationsFromServer(_session) > 0) { SaveShopPurchasedLocations(currentSessionId); } } private void RequestInitialPackets() { try { _session.Socket.SendPacket(new GetDataPackagePacket { Games = new string[1] { "Atlyss" } }); LogInfo("Data package requested."); } catch (Exception ex) { LogWarn("Data package warning: " + ex.Message); } try { _session.Socket.SendPacket(new SyncPacket()); LogInfo("Sync packet sent."); } catch (Exception ex2) { LogWarn("Sync packet warning: " + ex2.Message); } } private void WireSessionEvents() { _session.Items.ItemReceived += OnItemReceived; _session.Socket.PacketReceived += OnPacketReceived; _dlService.OnDeathLinkReceived += OnDeathLinkReceived; _checkedLocationsUpdatedHandler = OnCheckedLocationsUpdated; _session.Locations.CheckedLocationsUpdated += _checkedLocationsUpdatedHandler; _session.Socket.SocketClosed += OnArchipelagoSocketClosed; _session.Socket.ErrorReceived += OnArchipelagoSocketError; _messageLogReceivedHandler = OnArchipelagoMessageLogReceived; _session.MessageLog.OnMessageReceived += _messageLogReceivedHandler; } private void OnCheckedLocationsUpdated(ReadOnlyCollection locations) { LogInfo($"Server confirmed {locations.Count} checked location(s)"); if (locations != null) { foreach (long location in locations) { _reportedChecks.Add(location); } } if (_shopSanity != null && locations != null && locations.Count > 0 && !string.IsNullOrEmpty(currentSessionId) && _shopSanity.MergePurchasedShopLocationsFromIncrementalChecks(locations) > 0) { SaveShopPurchasedLocations(currentSessionId); } } private void OnArchipelagoSocketClosed(string reason) { LogWarn("Socket closed: " + reason); _mainThreadQueue.Enqueue(delegate { HandleUnexpectedDisconnect(reason); }); } private void OnArchipelagoSocketError(Exception exception, string message) { if ((message != null && message.IndexOf("being aborted", StringComparison.OrdinalIgnoreCase) >= 0) || (exception != null && (exception is ThreadAbortException || exception.GetType().Name == "ThreadAbortException" || (exception.Message != null && exception.Message.IndexOf("being aborted", StringComparison.OrdinalIgnoreCase) >= 0)))) { LogDebug("Socket shutdown (thread abort); ignoring."); } else { LogError("Socket error: " + message + " — " + exception?.Message); } } private void TeardownLiveSession() { if (_session == null) { connected = false; connecting = false; return; } if (!string.IsNullOrEmpty(currentSessionId)) { ArchipelagoSpikeStorage.FlushPendingSpikeWrites(force: true); FlushDirtyPersistenceIfNeeded(force: true); int num = _lastProcessedItemIndex; if (_session.Items != null) { num = Math.Max(num, _session.Items.Index); } SaveSessionState(currentSessionId, num); SaveGrantedItemKeys(currentSessionId); SaveShopPurchasedLocations(currentSessionId); SavePortalProgressState(currentSessionId); SaveProgressiveEquipmentState(currentSessionId); } try { _session.Items.ItemReceived -= OnItemReceived; _session.Socket.PacketReceived -= OnPacketReceived; if (_dlService != null) { _dlService.OnDeathLinkReceived -= OnDeathLinkReceived; } if (_checkedLocationsUpdatedHandler != null) { _session.Locations.CheckedLocationsUpdated -= _checkedLocationsUpdatedHandler; _checkedLocationsUpdatedHandler = null; } _session.Socket.SocketClosed -= OnArchipelagoSocketClosed; _session.Socket.ErrorReceived -= OnArchipelagoSocketError; if (_messageLogReceivedHandler != null) { _session.MessageLog.OnMessageReceived -= _messageLogReceivedHandler; _messageLogReceivedHandler = null; } } catch (Exception ex) { LogWarn("Failed to detach session handlers: " + ex.Message); } try { if (_session.Socket != null) { _session.Socket.DisconnectAsync(); } } catch (Exception ex2) { LogWarn("Socket disconnect call failed: " + ex2.Message); } _session = null; _dlService = null; connected = false; connecting = false; } private void HandleUnexpectedDisconnect(string reason) { if (connected || _session != null) { LogWarn("Unexpectedly disconnected from Archipelago: " + reason); TeardownLiveSession(); _connectAttemptId++; string reason2; if (_suppressAutoReconnect || !cfgAutoReconnect.Value) { SendAPChatMessage("Disconnected from Archipelago! Press F5 to reconnect."); } else if (!CanConnectNow(out reason2)) { _autoReconnectWhenReady = true; SendAPChatMessage("Disconnected from Archipelago. Will reconnect when your character is loaded."); } else { ScheduleAutoReconnect(); } } } private void ScheduleAutoReconnect() { _autoReconnectAttempt++; if (_autoReconnectAttempt > 8) { _autoReconnectAt = -1f; SendAPChatMessage("Auto-reconnect stopped. Press F5 to try again."); return; } float num = Mathf.Min(3f * Mathf.Pow(2f, (float)(_autoReconnectAttempt - 1)), 60f); _autoReconnectAt = Time.unscaledTime + num; SendAPChatMessage("Disconnected — reconnecting in " + num.ToString("0") + "s..."); LogInfo("Auto-reconnect attempt " + _autoReconnectAttempt + " scheduled in " + num.ToString("0") + "s"); } private void MaybeScheduleReconnectAfterFailure() { if (!_suppressAutoReconnect && cfgAutoReconnect.Value && _autoReconnectAttempt > 0) { if (!CanConnectNow(out string _)) { _autoReconnectWhenReady = true; } else { ScheduleAutoReconnect(); } } } private void FinalizeConnectSuccess() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) LogInfo("=== Connected and ready! ==="); LogInfo("Automatic detection active - level-ups and quests will be tracked!"); LogInfo("Items will be sent to Spike storage!"); ShowStickyImmediateStatusPrompt("Connected to Archipelago.", 12f, (Color?)new Color(0.35f, 1f, 0.35f, 1f)); FlushPendingChatMessages(128); } private void Disconnect() { //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) if (!connected && _session == null && !connecting) { return; } _suppressAutoReconnect = true; _autoReconnectAt = -1f; _autoReconnectAttempt = 0; _autoReconnectWhenReady = false; _connectAttemptId++; try { TeardownLiveSession(); } finally { _reportedChecks.Clear(); _pendingCheckIds.Clear(); _queuedCheckIds.Clear(); _nextCheckSendAt = 0f; _nextGameplayPollAt = 0f; _gameplayPollIndex = 0; _nextBossScanAt = 0f; _deferredProfessionChecks.Clear(); _questPollCursor = 0; _goal7QuestCursor = 0; _goal7StepsInPass = 0; _goal7PassClean = false; _completedQuests.Clear(); _lastProcessedItemIndex = 0; _lastLevel = 0; _previousFishingLevel = 0; _previousMiningLevel = 0; fishingRodToolAcquired = false; pickaxeToolAcquired = false; _questDebugLogged = false; _questCompletionStateSeeded = false; creepSanityEnabled = false; experienceMultiplier = 1f; crownMultiplier = 1f; foreach (string item in _portalItemsReceived.Keys.ToList()) { _portalItemsReceived[item] = false; } SetProgressivePortalLineCounts(0, 0); _progressiveCounts.Clear(); _grantedProgressiveConcreteItems.Clear(); _goalSent = false; _killedBossNames.Clear(); _completedAchievements.Clear(); _achievementScanDone = false; _newJourneySent = false; lock (_grantedItemKeys) { _grantedItemKeys.Clear(); } _chatBecameReadyLogged = false; _chatNotReadyLogged = false; _chatQueueDropLogged = false; _chatQueueDroppedCount = 0; _maxGameLogicLinesAppliedTo = null; _stickyPromptMessage = null; _stickyPromptUntil = 0f; _nextStickyPromptAt = 0f; _stickyPromptColor = new Color(1f, 0.35f, 0.35f, 1f); _pressF5ConnectHintShownThisSession = false; _pendingConnect = false; _pendingConnectNotified = false; _activeProfilePath = null; CleanupAchievementWatchers(); if (_shopSanity != null) { _shopSanity.Reset(); } LogInfo("Disconnected."); } } internal void SetProgressivePortalLineCounts(int sanctumCount, int tuulCount) { progressiveSanctumPortalCount = Math.Max(0, Math.Min(sanctumCount, 11)); progressiveTuulPortalCount = Math.Max(0, Math.Min(tuulCount, 3)); } public bool TryGetPortalReceived(string portalKey, out bool received) { return _portalItemsReceived.TryGetValue(portalKey, out received); } public void SetPortalReceived(string portalKey, bool value) { if (_portalItemsReceived.ContainsKey(portalKey)) { _portalItemsReceived[portalKey] = value; } } internal static bool IsShopInterfaceOpen() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown ShopkeepManager current = ShopkeepManager._current; if ((Object)current != (Object)null && current._isOpen) { return (Object)current._shopKeepNPC != (Object)null; } return false; } internal void RequestOpenShopSanityRefreshIfNeeded() { if (!connected || !shopSanityEnabled || _shopSanity == null || !_shopSanity.IsInitialized) { return; } _mainThreadQueue.Enqueue(delegate { _shopSanity.RequestFullRelist(); if (IsShopInterfaceOpen()) { ShopkeepManager current = ShopkeepManager._current; _shopSanity.SyncApRowStockQuantities(current._shopKeepNPC); LogDebug("Deferred AP shop relist/UI rebuild while shop interface is open."); } }); } internal static string EscapeServerChatToken(string token) { if (string.IsNullOrEmpty(token)) { return token ?? string.Empty; } return token.Replace("<", "<\u200b").Replace(">", "\u200b>"); } internal static string ClampServerLogToken(string token, int maxLength = 200) { if (string.IsNullOrEmpty(token)) { return token ?? string.Empty; } int length = token.Length; int num = Math.Min(length, maxLength); StringBuilder stringBuilder = new StringBuilder(num + 16); for (int i = 0; i < num; i++) { char c = token[i]; if (c < ' ' || c == '\u007f') { stringBuilder.Append(' '); } else { stringBuilder.Append(c); } } if (length > maxLength) { stringBuilder.Append("…(+").Append(length - maxLength).Append(')'); } return stringBuilder.ToString(); } internal void LogInfo(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("[AtlyssAP] " + message)); } internal void LogWarn(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[AtlyssAP] " + message)); } internal void LogError(string message) { ((BaseUnityPlugin)this).Logger.LogError((object)("[AtlyssAP] " + message)); } internal void LogDebug(string message) { ((BaseUnityPlugin)this).Logger.LogDebug((object)("[AtlyssAP] " + message)); } internal static void SLogInfo(string message) { ManualLogSource staticLogger = StaticLogger; if (staticLogger != null) { staticLogger.LogInfo((object)("[AtlyssAP] " + message)); } } internal static void SLogWarn(string message) { ManualLogSource staticLogger = StaticLogger; if (staticLogger != null) { staticLogger.LogWarning((object)("[AtlyssAP] " + message)); } } internal static void SLogError(string message) { ManualLogSource staticLogger = StaticLogger; if (staticLogger != null) { staticLogger.LogError((object)("[AtlyssAP] " + message)); } } internal static void SLogDebug(string message) { ManualLogSource staticLogger = StaticLogger; if (staticLogger != null) { staticLogger.LogDebug((object)("[AtlyssAP] " + message)); } } private void Awake() { //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Expected O, but got Unknown //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Expected O, but got Unknown //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Expected O, but got Unknown Instance = this; _professionTools = new ArchipelagoProfessionTools(this); StaticLogger = ((BaseUnityPlugin)this).Logger; cfgServer = ((BaseUnityPlugin)this).Config.Bind("Connection", "Server", "localhost", "Archipelago host. You can also put host:port here."); cfgSlot = ((BaseUnityPlugin)this).Config.Bind("Connection", "Slot", "Player", "Your Archipelago slot name."); cfgPassword = ((BaseUnityPlugin)this).Config.Bind("Connection", "Password", "", "Room password (optional). NOT persisted: the value is wiped on every plugin startup and after each successful connect, matching the convention used by other Archipelago clients to keep plaintext passwords off disk. Re-type the password in the AP settings tab each session if your room requires one."); cfgDeathlink = ((BaseUnityPlugin)this).Config.Bind("Connection", "DeathLink", false, "If DeathLink should be enabled (can be changed ingame)."); cfgAutoReconnect = ((BaseUnityPlugin)this).Config.Bind("Connection", "AutoReconnect", true, "After an unexpected socket drop, retry login with backoff (save/quit and main menu still fully disconnect)."); ScrubPersistedPassword(); LogInfo("Plugin loaded. Version " + ((BaseUnityPlugin)this).Info.Metadata.Version); ArchipelagoSpikeStorage.InvalidateSpikeSlotLayoutCache(); LogInfo("ALL QUESTS + Commands + Item In Spike Storage + Progressive Equipment + 60 Shop Locations + 16 Achievements!"); LogInfo("Press F5 to connect to Archipelago"); _harmony = new Harmony("com.blitz.atlyss.ap.harmony"); scriptHolder = new GameObject("Archipelago Script Holder"); Object.DontDestroyOnLoad((Object)scriptHolder); portalLocker = scriptHolder.AddComponent(); _shopSanity = new ArchipelagoShopSanity(this, ((BaseUnityPlugin)this).Logger); try { _harmony.PatchAll(); ExperienceMultiplierPatch.ApplyExplicit(_harmony, ((BaseUnityPlugin)this).Logger); CrownMultiplierPatch.ApplyExplicit(_harmony, ((BaseUnityPlugin)this).Logger); SaveQuitDisconnectPatch.Apply(_harmony, delegate(string message) { LogInfo(message); }, delegate(string message) { LogWarn(message); }); LogInfo("Harmony patches applied successfully"); } catch (Exception ex) { LogError("Failed to apply Harmony patches: " + ex.Message); } } private void OnDestroy() { Disconnect(); } private void ScrubPersistedPassword() { if (cfgPassword != null && !string.IsNullOrEmpty(cfgPassword.Value)) { LogInfo("Clearing previously persisted Archipelago password from BepInEx config (passwords are session-only)."); cfgPassword.Value = string.Empty; } } internal void NotifySaveQuitTriggered(string source) { if (connected && _session != null && !(Time.unscaledTime - _lastSaveQuitDisconnectAt < 1f)) { _lastSaveQuitDisconnectAt = Time.unscaledTime; _pendingDisconnectAfterSaveQuit = true; LogInfo("Save/Quit detected (" + source + ") - waiting for confirm/menu transition before disconnect."); } } private void Update() { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected O, but got Unknown //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Expected O, but got Unknown if (_pendingDisconnectAfterSaveQuit && connected && _session != null && (Object)GameObject.Find("_GameUI_MainMenu") != (Object)null) { _pendingDisconnectAfterSaveQuit = false; LogInfo("Save+Quit confirm completed - disconnecting from Archipelago."); Disconnect(); } else if (connected && _session != null && (Object)GameObject.Find("_GameUI_MainMenu") != (Object)null) { LogInfo("Main menu detected while connected - disconnecting from Archipelago."); Disconnect(); } if ((Object)_apStatusPanel != (Object)null && _apStatusPanel.activeSelf && Time.unscaledTime >= _apStatusUntil) { _apStatusPanel.SetActive(false); } if (!string.IsNullOrEmpty(_stickyPromptMessage)) { if (Time.unscaledTime >= _stickyPromptUntil) { _stickyPromptMessage = null; } else if (Time.unscaledTime >= _nextStickyPromptAt) { if (!ShowImmediateStatusPrompt(_stickyPromptMessage)) { ShowStatusBanner(_stickyPromptMessage, _stickyPromptColor, 1.25f); } _nextStickyPromptAt = Time.unscaledTime + 1.1f; } } bool flag = IsShopInterfaceOpen(); if (_shopInterfaceWasOpen && !flag) { portalLocker?.FlushDeferredPortalRefresh(); } _shopInterfaceWasOpen = flag; if (connected && _shopSanity != null && _shopSanity.IsInitialized && flag && Time.unscaledTime >= _nextShopBadgeRefreshAt) { _nextShopBadgeRefreshAt = Time.unscaledTime + 0.25f; ShopkeepManager current = ShopkeepManager._current; if ((Object)current != (Object)null) { ShopUiIconStackBadgePatch.ApplyBadges(current, current._shopKeepNPC); } } if (!connected && !connecting && CanConnectNow(out string reason)) { if (_autoReconnectWhenReady && cfgAutoReconnect.Value && !_suppressAutoReconnect) { _autoReconnectWhenReady = false; _pendingConnect = false; _pendingConnectNotified = false; LogInfo("Character/world ready — auto-reconnecting to Archipelago."); TryConnect(); } else if (_pendingConnect) { _pendingConnect = false; _pendingConnectNotified = false; FlushPendingChatMessages(128); LogInfo("Character/world ready after an early connect attempt — use F5 to connect (no auto-connect)."); if (!_pressF5ConnectHintShownThisSession) { OfferPressF5ConnectHintOnce(); } } } if (_autoReconnectAt > 0f && Time.unscaledTime >= _autoReconnectAt && !connected && !connecting && !_suppressAutoReconnect && cfgAutoReconnect.Value && CanConnectNow(out reason)) { _autoReconnectAt = -1f; LogInfo("Auto-reconnect timer elapsed — connecting to Archipelago."); TryConnect(); } FlushPendingChatMessages(64); FlushPendingLocationChecks(); DrainDeferredProfessionChecks(); int num = 0; float num2 = Time.realtimeSinceStartup + 0.004f; Action result; while (num < 8 && Time.realtimeSinceStartup < num2 && _mainThreadQueue.TryDequeue(out result)) { try { result?.Invoke(); num++; } catch (Exception ex) { LogError("Main-thread action error: " + ex.Message); } } ArchipelagoSpikeStorage.FlushPendingSpikeWrites(); ArchipelagoSpikeStorage.ProcessPendingSpikeUiRefresh(); FlushDirtyPersistenceIfNeeded(); if (Input.GetKeyDown((KeyCode)286)) { _autoReconnectAttempt = 0; _autoReconnectAt = -1f; TryConnect(); } if (connected) { PollGameplaySliced(); } } internal void SetProfessionLevelBaselines(int fishingLevel, int miningLevel) { _previousFishingLevel = fishingLevel; _previousMiningLevel = miningLevel; } public void ReenableSettingsTabs() { ((MonoBehaviour)this).StartCoroutine(EnableSettingsTabs()); } private IEnumerator EnableSettingsTabs() { yield return (object)new WaitForEndOfFrame(); GameObject.Find("_SettingsManager/Canvas_SettingsMenu/_dolly_settingsMenu/_dolly_tabButtons").SetActive(true); } internal void SeedCompletionStateFromServer() { if (_session?.Locations == null) { return; } ReadOnlyCollection allLocationsChecked = _session.Locations.AllLocationsChecked; foreach (long item in allLocationsChecked) { _reportedChecks.Add(item); } string[] achievementCheckNames = AchievementCheckNames; foreach (string text in achievementCheckNames) { if (ArchipelagoGameDataTables.AllLocationNameToId.TryGetValue(text, out var value) && allLocationsChecked.Contains(value)) { _completedAchievements.Add(text); } } if (_completedAchievements.Contains("A New Journey")) { _newJourneySent = true; } SeedQuestCompletionStateFromSaveAndServer(); } internal bool IsApQuestMarkedComplete(string apQuestName) { if (string.IsNullOrEmpty(apQuestName)) { return false; } if (_completedQuests.Contains(apQuestName)) { return true; } if (ArchipelagoGameDataTables.AllQuestToLocation.TryGetValue(apQuestName, out var value)) { return IsLocationCheckSatisfied(value); } return false; } internal void EnsureQuestCompletionStateSeeded() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown if (!_questCompletionStateSeeded && connected && !((Object)Player._mainPlayer == (Object)null)) { _questCompletionStateSeeded = true; SeedQuestCompletionStateFromSaveAndServer(); } } internal void SeedQuestCompletionStateFromSaveAndServer() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown Player mainPlayer = Player._mainPlayer; PlayerQuesting playerQuesting = (((Object)mainPlayer != (Object)null) ? ((Component)mainPlayer).GetComponent() : null); foreach (KeyValuePair item in ArchipelagoGameDataTables.AllQuestToLocation) { string key = item.Key; if ((playerQuesting?._finishedQuests != null && ArchipelagoGameDataTables.IsApQuestFinished((string questKey) => playerQuesting._finishedQuests.ContainsKey(questKey), key)) || IsLocationCheckSatisfied(item.Value)) { _completedQuests.Add(key); } } } private bool IsLocationCheckSatisfied(long locationId) { if (_reportedChecks.Contains(locationId) || _queuedCheckIds.Contains(locationId)) { return true; } return (_session?.Locations?.AllLocationsChecked)?.Contains(locationId) ?? false; } private bool IsLevelMilestoneCheckPending(long locationId) { return !IsLocationCheckSatisfied(locationId); } private void EnsureLevelMilestoneChecks(int currentLevel) { if (!connected || _session == null || currentLevel < 2) { return; } int num = Math.Min(currentLevel, 32); int[] levelMilestoneLevels = LevelMilestoneLevels; foreach (int num2 in levelMilestoneLevels) { if (num2 <= num) { string text = $"Reach Level {num2}"; if (ArchipelagoGameDataTables.AllLocationNameToId.TryGetValue(text, out var value) && IsLevelMilestoneCheckPending(value)) { SendCheckById(value); LogInfo(text + " milestone!"); } continue; } break; } } private void PollForLevelChanges() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown try { Player mainPlayer = Player._mainPlayer; if ((Object)mainPlayer == (Object)null) { return; } PlayerStats component = ((Component)mainPlayer).GetComponent(); if ((Object)component == (Object)null) { return; } int network_currentLevel = component.Network_currentLevel; if (connected && _session != null) { if (network_currentLevel > _lastLevel) { LogInfo($"Level changed: {_lastLevel} -> {network_currentLevel}"); TryFlushDeferredGatedEquipment(); } else if (network_currentLevel < _lastLevel) { LogWarn($"Level decreased: {_lastLevel} -> {network_currentLevel}; tracking new level."); } EnsureLevelMilestoneChecks(network_currentLevel); } else if (network_currentLevel < _lastLevel) { LogWarn($"Level decreased: {_lastLevel} -> {network_currentLevel}; tracking new level."); } _lastLevel = network_currentLevel; } catch (Exception ex) { LogError("Error polling level: " + ex.Message); } } private void PollForSkillLevelChanges() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) try { Player mainPlayer = Player._mainPlayer; if ((Object)mainPlayer == (Object)null) { return; } PlayerStats pStats = mainPlayer._pStats; if ((Object)pStats == (Object)null) { return; } for (int i = 0; i < pStats._syncProfessions.Count; i++) { ProfessionStruct val = pStats._syncProfessions[i]; ScriptableProfession val2 = null; if ((Object)GameManager._current != (Object)null && (Object)GameManager._current._statLogics != (Object)null && i < GameManager._current._statLogics._scriptableProfessions.Length) { val2 = GameManager._current._statLogics._scriptableProfessions[i]; } if ((Object)val2 == (Object)null) { continue; } if (val2._professionName == "Fishing") { int professionLvl = val._professionLvl; if (professionLvl > _previousFishingLevel) { int previousFishingLevel = _previousFishingLevel; if (connected && _session != null) { for (int j = _previousFishingLevel + 1; j <= professionLvl; j++) { string text = $"Fishing Lv. {j}"; if (_professionTools == null || !_professionTools.ShouldDeferProfessionCheck(text)) { _deferredProfessionChecks.Enqueue(text); previousFishingLevel = j; } } } else { for (int k = _previousFishingLevel + 1; k <= professionLvl; k++) { string text2 = $"Fishing Lv. {k}"; SendCheckByName(text2); LogInfo(text2 + " reached!"); previousFishingLevel = k; } } _previousFishingLevel = previousFishingLevel; } } if (!(val2._professionName == "Mining")) { continue; } int professionLvl2 = val._professionLvl; if (professionLvl2 <= _previousMiningLevel) { continue; } int previousMiningLevel = _previousMiningLevel; if (connected && _session != null) { for (int l = _previousMiningLevel + 1; l <= professionLvl2; l++) { string text3 = $"Mining Lv. {l}"; if (_professionTools == null || !_professionTools.ShouldDeferProfessionCheck(text3)) { _deferredProfessionChecks.Enqueue(text3); previousMiningLevel = l; } } } else { for (int m = _previousMiningLevel + 1; m <= professionLvl2; m++) { string text4 = $"Mining Lv. {m}"; SendCheckByName(text4); LogInfo(text4 + " reached!"); previousMiningLevel = m; } } _previousMiningLevel = previousMiningLevel; } } catch (Exception ex) { LogError("Error checking skill levels: " + ex.Message); } } private void PollForQuestCompletions() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown try { Player mainPlayer = Player._mainPlayer; if ((Object)mainPlayer == (Object)null) { return; } PlayerQuesting playerQuesting = ((Component)mainPlayer).GetComponent(); if ((Object)playerQuesting == (Object)null || playerQuesting._finishedQuests == null) { return; } EnsureQuestCompletionStateSeeded(); if (!_questDebugLogged && playerQuesting._finishedQuests.Count > 0) { LogDebug($"Quest snapshot: {playerQuesting._finishedQuests.Count} completed quest(s) in save."); _questDebugLogged = true; } KeyValuePair[] allQuestToLocationPairs = ArchipelagoGameDataTables.AllQuestToLocationPairs; int num = allQuestToLocationPairs.Length; if (num > 0) { int num2 = Math.Min(24, num); for (int i = 0; i < num2; i++) { KeyValuePair keyValuePair = allQuestToLocationPairs[_questPollCursor]; _questPollCursor++; if (_questPollCursor >= num) { _questPollCursor = 0; } string key = keyValuePair.Key; long value = keyValuePair.Value; if (ArchipelagoGameDataTables.IsApQuestFinished((string questKey) => playerQuesting._finishedQuests.ContainsKey(questKey), key) && !_completedQuests.Contains(key)) { SendCheckById(value); _completedQuests.Add(key); LogInfo("Quest completed: " + key); ArchipelagoQuestListUi.RefreshQuestListIfOpen(); } } } CheckGoalCompletion(mainPlayer, playerQuesting); } catch (Exception ex) { LogError("Error polling quests: " + ex.Message); } } public void SendCheckById(long locationId) { if (!connected || _session == null) { LogError("Not connected; cannot send check."); } else if (!_reportedChecks.Contains(locationId) && !_queuedCheckIds.Contains(locationId)) { _pendingCheckIds.Enqueue(locationId); _queuedCheckIds.Add(locationId); } } private void DrainDeferredProfessionChecks() { if (_deferredProfessionChecks.Count == 0) { return; } if (!connected || _session == null) { _deferredProfessionChecks.Clear(); return; } int num = 0; while (num < 2 && _deferredProfessionChecks.Count > 0) { string text = _deferredProfessionChecks.Dequeue(); if (_professionTools == null || !_professionTools.ShouldDeferProfessionCheck(text)) { SendCheckByName(text); LogInfo(text + " reached!"); num++; } } } private void FlushPendingLocationChecks() { if (!connected || _session == null || _session.Socket == null || !_session.Socket.Connected) { return; } int num = 0; while (_pendingCheckIds.Count > 0 && num < 1 && Time.unscaledTime >= _nextCheckSendAt) { long num2 = _pendingCheckIds.Dequeue(); _queuedCheckIds.Remove(num2); if (!_reportedChecks.Contains(num2)) { try { _session.Locations.CompleteLocationChecks(num2); _reportedChecks.Add(num2); num++; _nextCheckSendAt = Time.unscaledTime + 0.06f; LogInfo($"Sent check ID: {num2}"); } catch (Exception ex) { LogError("Failed to send check: " + ex.Message); break; } } } } private void PollGameplaySliced() { if (!(Time.unscaledTime < _nextGameplayPollAt)) { _nextGameplayPollAt = Time.unscaledTime + 0.03f; switch (_gameplayPollIndex) { case 0: PollForLevelChanges(); break; case 1: PollForQuestCompletions(); break; case 2: PollForSkillLevelChanges(); break; default: PollForAchievements(); break; } _gameplayPollIndex = (_gameplayPollIndex + 1) % 4; } } public void SendCheckByName(string locationName) { if (ArchipelagoGameDataTables.AllLocationNameToId.TryGetValue(locationName, out var value)) { SendCheckById(value); } else { LogWarn("Location not found in DataTables: '" + locationName + "'"); } } public void SendGoalComplete() { if (_goalSent || _session == null) { return; } try { _session.Socket.SendPacket(new StatusUpdatePacket { Status = ArchipelagoClientState.ClientGoal }); _goalSent = true; SendAPChatMessage("GOAL COMPLETE! You have achieved your objective!"); LogInfo($"Goal met (option {goalOption}) — sent ClientGoal to server."); } catch (Exception ex) { LogError("Failed to send goal completion: " + ex.Message); } } private bool CheckAllQuestsFromProfile() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown try { float unscaledTime = Time.unscaledTime; if (unscaledTime - _lastAllQuestsCheck < 5f) { return false; } _lastAllQuestsCheck = unscaledTime; Player mainPlayer = Player._mainPlayer; if (!((Object)mainPlayer == (Object)null)) { PlayerQuesting liveQuesting = ((Component)mainPlayer).GetComponent(); if ((Object)(object)liveQuesting != (Object)null && liveQuesting._finishedQuests != null && liveQuesting._finishedQuests.Count > 0) { bool flag = true; foreach (string key in ArchipelagoGameDataTables.AllQuestToLocation.Keys) { if (!ArchipelagoGameDataTables.IsApQuestFinished((string questKey) => liveQuesting._finishedQuests.ContainsKey(questKey), key)) { flag = false; break; } } if (flag) { LogInfo("All Quests goal: all keys present in live _finishedQuests (skip profile JSON scan)."); return true; } } } string activeProfilePath = _activeProfilePath; if (string.IsNullOrEmpty(activeProfilePath) || !File.Exists(activeProfilePath)) { return false; } string text = File.ReadAllText(activeProfilePath); int num = text.IndexOf("\"_finishedQuests\""); if (num < 0) { if (!_allQuestsDiagLogged) { LogWarn("All Quests goal: '_finishedQuests' key not found in profile JSON."); LogWarn("Profile may use a different key. Check profile file manually."); _allQuestsDiagLogged = true; } return false; } int num2 = text.IndexOf('{', num); if (num2 < 0) { return false; } int num3 = 0; int num4 = num2; for (int num5 = num2; num5 < text.Length; num5++) { if (text[num5] == '{') { num3++; } else if (text[num5] == '}') { num3--; if (num3 == 0) { num4 = num5; break; } } } string finishedQuestsJson = text.Substring(num2, num4 - num2 + 1); if (!_allQuestsDiagLogged) { LogInfo("All Quests goal: found _finishedQuests block. Checking quest names..."); _allQuestsDiagLogged = true; } foreach (string key2 in ArchipelagoGameDataTables.AllQuestToLocation.Keys) { if (!ArchipelagoGameDataTables.IsApQuestFinishedInProfileJson(finishedQuestsJson, key2)) { LogDebug("All Quests: '" + key2 + "' not yet in profile JSON (live save may be ahead)."); return false; } } LogInfo("All Quests: all quests confirmed in profile — sending goal."); return true; } catch (Exception ex) { LogError("Error reading profile for All Quests check: " + ex.Message); return false; } } private void CheckGoalCompletion(Player localPlayer, PlayerQuesting questing) { //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Expected O, but got Unknown if (_goalSent || _session == null) { return; } bool flag = false; switch (goalOption) { case 0: flag = _killedBossNames.Contains("Slime Diva"); break; case 1: flag = _killedBossNames.Contains("Lord Zuulneruda"); break; case 2: flag = _killedBossNames.Contains("Colossus"); break; case 3: flag = _killedBossNames.Contains("Galius"); break; case 4: flag = _killedBossNames.Contains("Lord Kaluuz"); break; case 5: flag = _killedBossNames.Contains("Valdur"); break; case 6: flag = AllBossGoalNames.All((string bossName) => _killedBossNames.Contains(bossName)); break; case 7: { flag = CheckAllQuestsFromProfile(); if (flag) { break; } if (questing._finishedQuests == null) { flag = false; break; } KeyValuePair[] allQuestToLocationPairs = ArchipelagoGameDataTables.AllQuestToLocationPairs; int num = allQuestToLocationPairs.Length; if (num == 0) { flag = false; break; } int num2 = Math.Min(20, num); for (int num3 = 0; num3 < num2; num3++) { if (_goal7StepsInPass == 0) { _goal7PassClean = true; } KeyValuePair keyValuePair = allQuestToLocationPairs[_goal7QuestCursor]; _goal7QuestCursor++; if (_goal7QuestCursor >= num) { _goal7QuestCursor = 0; } if (!ArchipelagoGameDataTables.IsApQuestFinished((string questKey) => questing._finishedQuests.ContainsKey(questKey), keyValuePair.Key)) { _goal7PassClean = false; } _goal7StepsInPass++; if (_goal7StepsInPass >= num) { if (_goal7PassClean) { flag = true; } _goal7StepsInPass = 0; } } break; } case 8: { PlayerStats component = ((Component)localPlayer).GetComponent(); flag = (Object)component != (Object)null && component.Network_currentLevel >= 32; break; } } if (flag) { SendGoalComplete(); } } public void CheckScalingTheTower() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) if (_completedAchievements.Contains("Scaling the Tower")) { return; } Player mainPlayer = Player._mainPlayer; if (!((Object)mainPlayer == (Object)null)) { Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(-7.3102f, 250.92f, -321.9825f); if (Vector3.Distance(((Component)mainPlayer).transform.position, val) < 5f) { SendAchievementCheck("Scaling the Tower"); } } } private void OnItemReceived(IReceivedItemsHelper helper) { try { ItemInfo itemInfo = helper.DequeueItem(); if (itemInfo != null) { int num = 0; ReadOnlyCollection readOnlyCollection = _session?.Items?.AllItemsReceived; num = ((readOnlyCollection == null || readOnlyCollection.Count <= 0 || readOnlyCollection[readOnlyCollection.Count - 1] != itemInfo) ? GetReceivedItemOrdinal(itemInfo) : readOnlyCollection.Count); TryAcceptReceivedItemForGrant(itemInfo, num); } } catch (Exception ex) { LogError("Error receiving item: " + ex.Message); } } private int GetReceivedItemOrdinal(ItemInfo itemInfo) { if (itemInfo == null || _session?.Items?.AllItemsReceived == null) { return 0; } ReadOnlyCollection allItemsReceived = _session.Items.AllItemsReceived; int count = allItemsReceived.Count; if (count > 0 && allItemsReceived[count - 1] == itemInfo) { return count; } for (int i = 0; i < count; i++) { if (allItemsReceived[i] == itemInfo) { return i + 1; } } return count; } private bool TryAcceptReceivedItemForGrant(ItemInfo itemInfo, int itemOrdinal) { if (itemInfo == null || _session?.Items == null || itemOrdinal <= 0) { return false; } string itemName = _session.Items.GetItemName(itemInfo.ItemId, itemInfo.ItemGame) ?? $"Item {itemInfo.ItemId}"; string token = _session.Players.GetPlayerName(itemInfo.Player) ?? $"Player {itemInfo.Player}"; string text = BuildGrantedItemKey(itemInfo); lock (_grantedItemKeys) { if (_grantedItemKeys.Contains(text)) { _lastProcessedItemIndex = Math.Max(_lastProcessedItemIndex, itemOrdinal); LogDebug($"Skipping item #{itemOrdinal} {ClampServerLogToken(itemName)} (already in grant ledger)."); return false; } } if (itemOrdinal <= _lastProcessedItemIndex) { RecordGrantLedgerKey(text); _lastProcessedItemIndex = Math.Max(_lastProcessedItemIndex, itemOrdinal); PersistGrantedItemProgress(currentSessionId); LogDebug($"Skipping reconnect replay item #{itemOrdinal} {ClampServerLogToken(itemName)} (ordinal <= saved progress)."); return false; } string traceId = $"itm-{++_itemTraceCounter:D6}"; LogDebug($"[AtlyssAP][{traceId}] Received item #{itemOrdinal}: {ClampServerLogToken(itemName)} from {ClampServerLogToken(token)} (itemId={itemInfo.ItemId}, locationId={itemInfo.LocationId})"); lock (_grantedItemKeys) { _grantedItemKeys.Add(text); } _lastProcessedItemIndex = Math.Max(_lastProcessedItemIndex, itemOrdinal); PersistGrantedItemProgress(currentSessionId); _mainThreadQueue.Enqueue(delegate { HandleReceivedItem(itemName, traceId); }); return true; } private void DrainQueuedReceivedItems() { if (_session?.Items != null) { while (_session.Items.Any()) { OnItemReceived(_session.Items); } } } private void ReconcileItemReplayOnConnect() { if (_session?.Items != null) { ReadOnlyCollection allItemsReceived = _session.Items.AllItemsReceived; if (allItemsReceived != null && allItemsReceived.Count != 0) { SyncPortalUnlocksFromReceivedItems(); SyncProgressiveEquipmentFromReceivedItems(); } } } private void ReconcileGrantLedgerFromExistingApProgress(int savedReceivedItemOrdinal) { if (_session?.Items?.AllItemsReceived == null) { return; } ReadOnlyCollection allItemsReceived = _session.Items.AllItemsReceived; if (allItemsReceived.Count == 0) { return; } lock (_grantedItemKeys) { if (_grantedItemKeys.Count > 0) { return; } } if (savedReceivedItemOrdinal > 0) { int num = Math.Min(savedReceivedItemOrdinal, allItemsReceived.Count); for (int i = 0; i < num; i++) { RecordGrantLedgerKey(BuildGrantedItemKey(allItemsReceived[i])); } _lastProcessedItemIndex = Math.Max(_lastProcessedItemIndex, savedReceivedItemOrdinal); PersistGrantedItemProgress(currentSessionId); LogInfo($"Healed {num} grant ledger entries from saved ordinal {savedReceivedItemOrdinal}."); } else if (ArchipelagoSpikeStorage.IsAPSessionActive() && ArchipelagoSpikeStorage.GetTotalItemCount() > 0) { for (int j = 0; j < allItemsReceived.Count; j++) { RecordGrantLedgerKey(BuildGrantedItemKey(allItemsReceived[j])); } _lastProcessedItemIndex = allItemsReceived.Count; PersistGrantedItemProgress(currentSessionId); LogInfo($"Rebuilt grant ledger from server queue ({allItemsReceived.Count} entries) — spike already has AP progress; skipped duplicate grants."); } } private void GrantMissingReceivedItemsAfterSavedIndex(int savedReceivedItemOrdinal) { if (_session?.Items == null) { return; } ReadOnlyCollection allItemsReceived = _session.Items.AllItemsReceived; if (allItemsReceived == null || allItemsReceived.Count == 0) { return; } int num = 0; for (int i = savedReceivedItemOrdinal; i < allItemsReceived.Count; i++) { int itemOrdinal = i + 1; if (TryAcceptReceivedItemForGrant(allItemsReceived[i], itemOrdinal)) { num++; } } if (num > 0) { LogInfo($"Connect: granted {num} new queued item(s) beyond saved ordinal {savedReceivedItemOrdinal}."); } } private void HandleReceivedItem(string itemName, string traceId) { try { LogDebug("[AtlyssAP][" + traceId + "] Handling item route for '" + ClampServerLogToken(itemName) + "'"); if (itemName.StartsWith("Character Level ", StringComparison.Ordinal) || itemName.StartsWith("Complete: ", StringComparison.Ordinal)) { LogDebug("[AtlyssAP][" + traceId + "] Route=logic_event (no in-game grant)"); } else if (!HandleProgressivePortalItem(itemName, traceId) && !HandleProgressiveEquipmentItem(itemName, traceId) && !HandlePortalUnlockItem(itemName, traceId) && !HandleCurrencyItem(itemName, traceId) && !HandleMappedStorageItem(itemName, traceId)) { LogWarn("[AtlyssAP][" + traceId + "] Unknown item: " + ClampServerLogToken(itemName)); } } catch (Exception ex) { LogError("[AtlyssAP][" + traceId + "] Error handling item '" + ClampServerLogToken(itemName) + "': " + ex.Message); } } private bool HandleProgressivePortalItem(string itemName, string traceId) { if (itemName == "Progressive Sanctum Portal") { if (progressiveSanctumPortalCount >= 11) { return true; } progressiveSanctumPortalCount++; LogInfo($"[AtlyssAP][{traceId}] {itemName} #{progressiveSanctumPortalCount}"); NotifyProgressivePortalUnlocked(ArchipelagoGameDataTables.ProgressiveSanctumPortalOrder[progressiveSanctumPortalCount - 1]); PersistPortalProgressState(); RequestOpenShopSanityRefreshIfNeeded(); return true; } if (itemName == "Progressive Tuul Portal") { if (progressiveTuulPortalCount >= 3) { return true; } progressiveTuulPortalCount++; LogInfo($"[AtlyssAP][{traceId}] {itemName} #{progressiveTuulPortalCount}"); NotifyProgressivePortalUnlocked(ArchipelagoGameDataTables.ProgressiveTuulPortalOrder[progressiveTuulPortalCount - 1]); PersistPortalProgressState(); RequestOpenShopSanityRefreshIfNeeded(); return true; } return false; } private void NotifyProgressivePortalUnlocked(string portalName) { if (ArchipelagoGameDataTables.PortalScenes.TryGetValue(portalName, out string value)) { portalLocker.UnblockAccessToScene(value); if (!_suppressPortalUnlockAnnouncements) { SendAPChatMessageDeferred("" + EscapeServerChatToken(portalName.Replace(" Portal", "")) + " unlocked!"); } } } private bool HandleProgressiveEquipmentItem(string itemName, string traceId) { if (!itemName.StartsWith("Progressive ", StringComparison.Ordinal) || ArchipelagoGameDataTables.IsProgressivePortalItem(itemName)) { return false; } LogInfo("[AtlyssAP][" + traceId + "] Route=progressive_equipment"); HandleProgressiveEquipment(itemName, traceId); return true; } private bool HandlePortalUnlockItem(string itemName, string traceId) { if (!_portalItemsReceived.ContainsKey(itemName)) { return false; } LogInfo("[AtlyssAP][" + traceId + "] Route=portal_unlock"); _portalItemsReceived[itemName] = true; LogInfo("Received " + ClampServerLogToken(itemName) + "!"); if (ArchipelagoGameDataTables.PortalScenes.TryGetValue(itemName, out string value)) { portalLocker.UnblockAccessToScene(value); if (!_suppressPortalUnlockAnnouncements) { SendAPChatMessageDeferred("" + EscapeServerChatToken(itemName.Replace(" Portal", "")) + " unlocked!"); } } PersistPortalProgressState(); RequestOpenShopSanityRefreshIfNeeded(); return true; } private bool HandleCurrencyItem(string itemName, string traceId) { if (!itemName.StartsWith("Crowns (")) { return false; } LogInfo("[AtlyssAP][" + traceId + "] Route=currency"); int currencyAmount = GetCurrencyAmount(itemName); if (currencyAmount > 0) { GiveCurrency(currencyAmount); } return true; } private int GetMainPlayerLevel() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown Player mainPlayer = Player._mainPlayer; if ((Object)mainPlayer == (Object)null) { return 1; } PlayerStats component = ((Component)mainPlayer).GetComponent(); if ((Object)component == (Object)null) { return 1; } return Math.Max(1, component.Network_currentLevel); } private bool TryDeferEquipmentReceive(string itemName, string traceId, out string deferReason) { deferReason = null; if (!ArchipelagoItemProgression.ItemPassesClassFilter(classFilterOption, itemName)) { deferReason = "class filter"; return false; } if (equipmentProgressionOption == 0 && !ArchipelagoItemProgression.ItemPassesGatedLevel(GetMainPlayerLevel(), itemName)) { _deferredGatedEquipment.Enqueue(new DeferredEquipmentGrant { ItemName = itemName, TraceId = traceId }); deferReason = "gated level"; return true; } return false; } internal void TryFlushDeferredGatedEquipment() { if (equipmentProgressionOption != 0 || _deferredGatedEquipment.Count == 0) { return; } int mainPlayerLevel = GetMainPlayerLevel(); int count = _deferredGatedEquipment.Count; for (int i = 0; i < count; i++) { DeferredEquipmentGrant deferredEquipmentGrant = _deferredGatedEquipment.Dequeue(); string value; if (deferredEquipmentGrant.IsProgressiveEquipment) { if (!HandleProgressiveEquipment(deferredEquipmentGrant.ItemName, deferredEquipmentGrant.TraceId, fromDeferred: true)) { _deferredGatedEquipment.Enqueue(deferredEquipmentGrant); } } else if (!ArchipelagoItemProgression.ItemPassesGatedLevel(mainPlayerLevel, deferredEquipmentGrant.ItemName)) { _deferredGatedEquipment.Enqueue(deferredEquipmentGrant); } else if (!ArchipelagoItemProgression.ItemPassesClassFilter(classFilterOption, deferredEquipmentGrant.ItemName)) { LogWarn("[AtlyssAP][" + deferredEquipmentGrant.TraceId + "] Deferred item failed class filter at grant time: " + ClampServerLogToken(deferredEquipmentGrant.ItemName)); } else if (ArchipelagoGameDataTables.ItemNameMapping.TryGetValue(deferredEquipmentGrant.ItemName, out value)) { TryGrantMappedStorageItem(deferredEquipmentGrant.ItemName, value, deferredEquipmentGrant.TraceId, announce: true); } } } private bool HandleMappedStorageItem(string itemName, string traceId) { if (!ArchipelagoGameDataTables.ItemNameMapping.TryGetValue(itemName, out string value)) { return false; } if (ShouldSkipProfessionToolStorageGrant(itemName)) { LogInfo("[AtlyssAP][" + traceId + "] Skipped storage grant for " + ClampServerLogToken(itemName) + " (already in inventory or Spike)"); SendAPChatMessageDeferred("Already have " + EscapeServerChatToken(itemName) + " — duplicate not stored."); return true; } if (TryDeferEquipmentReceive(itemName, traceId, out string deferReason)) { SendAPChatMessageDeferred("Held " + EscapeServerChatToken(itemName) + " until level allows (gated progression)."); LogInfo($"[AtlyssAP][{traceId}] Deferred ({deferReason}): {ClampServerLogToken(itemName)} queue={_deferredGatedEquipment.Count}"); return true; } if (deferReason == "class filter") { SendAPChatMessageDeferred("Skipped " + EscapeServerChatToken(itemName) + " — wrong class for your class_filter setting."); LogWarn("[AtlyssAP][" + traceId + "] Class filter blocked: " + ClampServerLogToken(itemName)); return true; } return TryGrantMappedStorageItem(itemName, value, traceId, announce: false); } private bool TryGrantMappedStorageItem(string itemName, string mappedGameItemName, string traceId, bool announce) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) LogDebug("[AtlyssAP][" + traceId + "] Route=mapped_item mappedName='" + mappedGameItemName + "'"); int quantity = DetermineItemQuantity(itemName); try { ScriptableItem resolvedScript; ItemData val = CreateItemData(itemName, mappedGameItemName, quantity, out resolvedScript); if (val != null) { LogDebug($"[AtlyssAP][{traceId}] CreateItemData success item='{val._itemName}' qty={val._quantity}"); if (ArchipelagoSpikeStorage.AddItemToAPSpike(val, ArchipelagoSpikeStorage.ResolveSpikeCategoryForPlacement(resolvedScript, mappedGameItemName))) { _professionTools?.NotifyToolItemReceived(itemName); if (announce) { SendAPChatMessageDeferred("Received " + EscapeServerChatToken(itemName) + "! (was held for gated progression)"); } else { SendAPChatMessageDeferred("Received " + EscapeServerChatToken(itemName) + "! Check Spike's storage!"); } LogDebug("[AtlyssAP][" + traceId + "] Added " + ClampServerLogToken(itemName) + " to Spike storage"); } else { LogWarn("[AtlyssAP][" + traceId + "] Failed to add " + ClampServerLogToken(itemName) + " to storage - banks full!"); SendAPChatMessage("Storage full! Could not store " + EscapeServerChatToken(itemName) + ""); } } else { LogWarn("[AtlyssAP][" + traceId + "] CreateItemData returned null for mappedName='" + mappedGameItemName + "'"); } } catch (Exception ex) { LogError("[AtlyssAP][" + traceId + "] Failed to process item " + ClampServerLogToken(itemName) + ": " + ex.Message); } return true; } private List GetActiveClassNames() { return classFilterOption switch { 1 => new List { "Fighter" }, 2 => new List { "Mystic" }, 3 => new List { "Bandit" }, 4 => new List { "Fighter", "Mystic" }, 5 => new List { "Fighter", "Bandit" }, 6 => new List { "Mystic", "Bandit" }, _ => new List { "Fighter", "Bandit", "Mystic" }, }; } private int GetProgressiveTrackCount(string trackKey) { if (!_progressiveCounts.TryGetValue(trackKey, out var value)) { return 0; } return value; } private static string BuildClassProgressiveKey(string className, string equipmentSuffix) { return "Progressive " + className + " " + equipmentSuffix; } private static bool TryParseProgressiveEquipmentKey(string progressiveItemName, out string equipmentSuffix, out bool isAnyProgressive) { if (!progressiveItemName.StartsWith("Progressive ", StringComparison.Ordinal)) { equipmentSuffix = null; isAnyProgressive = false; return false; } string text = progressiveItemName.Substring("Progressive ".Length); if (text.StartsWith("Any ", StringComparison.Ordinal)) { equipmentSuffix = text.Substring("Any ".Length); isAnyProgressive = true; return true; } string[] array = new string[3] { "Fighter", "Mystic", "Bandit" }; for (int i = 0; i < array.Length; i++) { string text2 = array[i] + " "; if (text.StartsWith(text2, StringComparison.Ordinal)) { equipmentSuffix = text.Substring(text2.Length); isAnyProgressive = false; return true; } } equipmentSuffix = text; isAnyProgressive = false; return true; } private List FilterProgressiveTierCandidates(List tierItemNames, int mainPlayerLevel) { if (equipmentProgressionOption != 0) { return tierItemNames; } return tierItemNames.Where((string candidate) => ArchipelagoItemProgression.ItemPassesClassFilter(classFilterOption, candidate) && ArchipelagoItemProgression.ItemPassesGatedLevel(mainPlayerLevel, candidate)).ToList(); } private bool TryDeferProgressiveEquipment(string apItemName, string traceId, int tierIndex, int mainPlayerLevel, bool fromDeferred) { if (equipmentProgressionOption != 0) { return false; } int num = Math.Max(0, ArchipelagoItemProgression.LevelToMaxTier(mainPlayerLevel) - 1); if (tierIndex <= num) { return false; } if (!fromDeferred) { _deferredGatedEquipment.Enqueue(new DeferredEquipmentGrant { ItemName = apItemName, TraceId = traceId, IsProgressiveEquipment = true }); } LogInfo($"[AtlyssAP][{traceId}] Deferred {ClampServerLogToken(apItemName)}: needs tier band {tierIndex + 1}, player level {mainPlayerLevel} allows band {num + 1}."); return true; } private bool TrySelectFromClassTrack(string classTrackKey, List> tierTable, int startTierIndex, int mainPlayerLevel, string apItemName, string traceId, bool fromDeferred, out ProgressivePickOption pick) { pick = null; if (tierTable == null || tierTable.Count == 0) { return false; } if (TryDeferProgressiveEquipment(apItemName, traceId, startTierIndex, mainPlayerLevel, fromDeferred)) { return false; } int num = Math.Max(0, Math.Min(startTierIndex, tierTable.Count - 1)); int num2 = ((equipmentProgressionOption == 0) ? Math.Max(0, ArchipelagoItemProgression.LevelToMaxTier(mainPlayerLevel) - 1) : (tierTable.Count - 1)); for (int i = num; i < tierTable.Count && (equipmentProgressionOption != 0 || i <= num2); i++) { List list = tierTable[i]; if (list == null || list.Count == 0) { continue; } List list2 = FilterProgressiveTierCandidates(list, mainPlayerLevel); if (list2.Count != 0) { List list3 = list2.Where((string candidate) => !_grantedProgressiveConcreteItems.Contains(candidate)).ToList(); if (list3.Count > 0) { pick = new ProgressivePickOption { ConcreteItemName = list3[Random.Range(0, list3.Count)], ClassTrackKey = classTrackKey, UsedTierIndex = i }; return true; } } } int num3 = Math.Min(num2, tierTable.Count - 1); List list4 = FilterProgressiveTierCandidates(tierTable[num3], mainPlayerLevel); if (list4.Count == 0) { return false; } LogInfo($"All ungranted rewards for {ClampServerLogToken(classTrackKey)} through band {num3 + 1} were already granted; using duplicate fallback."); pick = new ProgressivePickOption { ConcreteItemName = list4[Random.Range(0, list4.Count)], ClassTrackKey = classTrackKey, UsedTierIndex = num3 }; return true; } private static HashSet CollectClassProgressiveItemNames(string equipmentSuffix, List activeClassNames) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (string activeClassName in activeClassNames) { string key = BuildClassProgressiveKey(activeClassName, equipmentSuffix); if (!ArchipelagoGameDataTables.ProgressiveItemTiers.TryGetValue(key, out List> value)) { continue; } foreach (List item in value) { if (item == null) { continue; } foreach (string item2 in item) { hashSet.Add(item2); } } } return hashSet; } private bool TryBuildAnyExclusivePick(string anyProgressiveKey, List> anyTierTable, int anyTrackIndex, string equipmentSuffix, List activeClassNames, int mainPlayerLevel, string traceId, bool fromDeferred, out ProgressivePickOption pick) { pick = null; if (anyTierTable == null || anyTierTable.Count == 0) { return false; } if (TryDeferProgressiveEquipment(anyProgressiveKey, traceId, anyTrackIndex, mainPlayerLevel, fromDeferred)) { return false; } int num = Math.Max(0, Math.Min(anyTrackIndex, anyTierTable.Count - 1)); List list = anyTierTable[num]; if (list == null || list.Count == 0) { return false; } HashSet classItems = CollectClassProgressiveItemNames(equipmentSuffix, activeClassNames); List tierItemNames = list.Where((string item) => !classItems.Contains(item)).ToList(); List list2 = FilterProgressiveTierCandidates(tierItemNames, mainPlayerLevel); if (list2.Count == 0) { return false; } List list3 = list2.Where((string candidate) => !_grantedProgressiveConcreteItems.Contains(candidate)).ToList(); List list4 = ((list3.Count > 0) ? list3 : list2); if (list3.Count == 0) { LogInfo($"All Any-exclusive rewards for {ClampServerLogToken(anyProgressiveKey)} band {num + 1} were already granted; using duplicate fallback."); } pick = new ProgressivePickOption { ConcreteItemName = list4[Random.Range(0, list4.Count)], ClassTrackKey = null, UsedTierIndex = num }; return true; } private void AdvanceProgressiveReceiptCount(string trackKey) { _progressiveCounts[trackKey] = GetProgressiveTrackCount(trackKey) + 1; PersistProgressiveEquipmentState(); } private void SyncClassTrackBand(string classTrackKey, int usedTierIndex) { int num = usedTierIndex + 1; if (GetProgressiveTrackCount(classTrackKey) < num) { _progressiveCounts[classTrackKey] = num; PersistProgressiveEquipmentState(); } } private static string ResolveProgressiveConcreteDeliveryName(string concreteDisplayName) { if (ArchipelagoGameDataTables.ItemNameMapping.TryGetValue(concreteDisplayName, out string value)) { return value; } return concreteDisplayName; } private bool DeliverProgressiveConcrete(string apItemName, string traceId, ProgressivePickOption pick, int apReceiptCount) { //IL_00c7: Unknown result type (might be due to invalid IL or missing references) string concreteItemName = pick.ConcreteItemName; string text = ResolveProgressiveConcreteDeliveryName(concreteItemName); string text2 = (string.IsNullOrEmpty(pick.ClassTrackKey) ? "Any-exclusive" : pick.ClassTrackKey); LogInfo($"Progressive equipment: {ClampServerLogToken(apItemName)} receipt #{apReceiptCount} -> {concreteItemName} (track {text2}, band {pick.UsedTierIndex + 1})"); try { ScriptableItem resolvedScript; ItemData val = CreateItemData(apItemName, text, 1, out resolvedScript); if (val == null) { LogWarn("[AtlyssAP][" + traceId + "] CreateItemData returned null for progressive concrete '" + concreteItemName + "' (deliveryName='" + text + "')"); return true; } if (!ArchipelagoSpikeStorage.AddItemToAPSpike(val, ArchipelagoSpikeStorage.ResolveSpikeCategoryForPlacement(resolvedScript, text))) { LogWarn("Failed to add " + concreteItemName + " to storage - banks full!"); return true; } _grantedProgressiveConcreteItems.Add(concreteItemName); PersistProgressiveEquipmentState(); SendAPChatMessageDeferred("Received " + EscapeServerChatToken(apItemName) + "! Got " + EscapeServerChatToken(concreteItemName) + " — check Spike's storage!"); LogInfo($"Added {concreteItemName} to Spike storage ({ClampServerLogToken(apItemName)} receipt #{apReceiptCount})"); } catch (Exception ex) { LogError("Failed to deliver progressive item " + concreteItemName + ": " + ex.Message); } return true; } private bool HandleProgressiveEquipment(string itemName, string traceId, bool fromDeferred = false) { if (!ArchipelagoGameDataTables.ProgressiveItemTiers.TryGetValue(itemName, out List> value)) { LogWarn("No tier map for progressive item: " + ClampServerLogToken(itemName)); return true; } if (!TryParseProgressiveEquipmentKey(itemName, out string equipmentSuffix, out bool isAnyProgressive)) { LogWarn("Unrecognized progressive equipment key: " + ClampServerLogToken(itemName)); return true; } int mainPlayerLevel = GetMainPlayerLevel(); int num = GetProgressiveTrackCount(itemName) + 1; List activeClassNames = GetActiveClassNames(); if (isAnyProgressive) { List list = new List(); foreach (string item in activeClassNames) { string text = BuildClassProgressiveKey(item, equipmentSuffix); if (ArchipelagoGameDataTables.ProgressiveItemTiers.TryGetValue(text, out List> value2)) { int progressiveTrackCount = GetProgressiveTrackCount(text); if (TrySelectFromClassTrack(text, value2, progressiveTrackCount, mainPlayerLevel, itemName, traceId, fromDeferred, out ProgressivePickOption pick)) { list.Add(pick); } } } int progressiveTrackCount2 = GetProgressiveTrackCount(itemName); if (TryBuildAnyExclusivePick(itemName, value, progressiveTrackCount2, equipmentSuffix, activeClassNames, mainPlayerLevel, traceId, fromDeferred, out ProgressivePickOption pick2)) { list.Add(pick2); } if (list.Count == 0) { if (!fromDeferred) { _deferredGatedEquipment.Enqueue(new DeferredEquipmentGrant { ItemName = itemName, TraceId = traceId, IsProgressiveEquipment = true }); } LogInfo($"[AtlyssAP][{traceId}] Deferred {ClampServerLogToken(itemName)} receipt #{num}: no valid pick at current class tracks."); return false; } ProgressivePickOption progressivePickOption = list[Random.Range(0, list.Count)]; AdvanceProgressiveReceiptCount(itemName); if (!string.IsNullOrEmpty(progressivePickOption.ClassTrackKey)) { SyncClassTrackBand(progressivePickOption.ClassTrackKey, progressivePickOption.UsedTierIndex); } return DeliverProgressiveConcrete(itemName, traceId, progressivePickOption, num); } if (!TrySelectFromClassTrack(itemName, value, GetProgressiveTrackCount(itemName), mainPlayerLevel, itemName, traceId, fromDeferred, out ProgressivePickOption pick3)) { if (!fromDeferred) { _deferredGatedEquipment.Enqueue(new DeferredEquipmentGrant { ItemName = itemName, TraceId = traceId, IsProgressiveEquipment = true }); } LogInfo($"[AtlyssAP][{traceId}] Deferred {ClampServerLogToken(itemName)} receipt #{num}: no valid class-track pick."); return false; } AdvanceProgressiveReceiptCount(itemName); SyncClassTrackBand(itemName, pick3.UsedTierIndex); return DeliverProgressiveConcrete(itemName, traceId, pick3, num); } private ScriptableItem LocateItemCached(string lookupTag) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown if (string.IsNullOrEmpty(lookupTag) || (Object)GameManager._current == (Object)null) { return null; } if (_scriptableItemLookupCache.TryGetValue(lookupTag, out ScriptableItem value) && (Object)value != (Object)null) { return value; } ScriptableItem val = GameManager._current.Locate_Item(lookupTag); if ((Object)val != (Object)null) { _scriptableItemLookupCache[lookupTag] = val; } return val; } private ItemData CreateItemData(string apItemName, string gameItemName, int quantity, out ScriptableItem resolvedScript) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected O, but got Unknown //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //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 //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Expected O, but got Unknown //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Expected O, but got Unknown //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Expected O, but got Unknown //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Expected O, but got Unknown //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02d8: Unknown result type (might be due to invalid IL or missing references) //IL_02e0: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_02ee: Unknown result type (might be due to invalid IL or missing references) //IL_02f6: Expected O, but got Unknown resolvedScript = null; ScriptableItem val = _professionTools?.TryLocateProfessionToolScriptable(apItemName); if ((Object)val != (Object)null) { resolvedScript = val; int num = val._maxStackAmount; if (num < 1) { num = 99; } return new ItemData { _itemName = val._itemName, _quantity = quantity, _maxQuantity = num, _modifierID = 0, _isEquipped = false, _slotNumber = 0 }; } string text = gameItemName; int num2 = gameItemName.LastIndexOf('_'); if (num2 >= 0 && num2 < gameItemName.Length - 1) { text = gameItemName.Substring(num2 + 1); } ScriptableItem val2 = null; List list = new List(); val2 = LocateItemCached(text); list.Add("1. '" + text + "'"); if ((Object)val2 == (Object)null && text.Contains(" (") && text.EndsWith(")")) { string text2 = text.Substring(0, text.LastIndexOf(" (")); val2 = LocateItemCached(text2); list.Add("2. '" + text2 + "'"); } if ((Object)val2 == (Object)null && text != gameItemName) { val2 = LocateItemCached(gameItemName); list.Add("3. '" + gameItemName + "'"); } if ((Object)val2 == (Object)null) { string text3 = text.Replace(" ", ""); val2 = LocateItemCached(text3); list.Add("4. '" + text3 + "'"); } if ((Object)val2 == (Object)null && gameItemName.Contains("WEAPON_")) { string text4 = gameItemName.Substring(gameItemName.IndexOf("WEAPON_") + 7); if (text4.Contains(" (")) { string text5 = text4.Substring(0, text4.IndexOf(" (")); val2 = LocateItemCached(text5); list.Add("5. '" + text5 + "'"); } } if ((Object)val2 == (Object)null) { LogError("Could not find ScriptableItem for: '" + text + "'"); LogError("Original Unity name: '" + gameItemName + "'"); LogError($"Tried {list.Count} variations:"); foreach (string item in list) { LogError(" - " + item); } LogWarn("This item may not be available in the current game version or uses a different name format"); return null; } resolvedScript = val2; int num3 = val2._maxStackAmount; if (num3 < 1) { num3 = 99; } return new ItemData { _itemName = val2._itemName, _quantity = quantity, _maxQuantity = num3, _modifierID = 0, _isEquipped = false, _slotNumber = 0 }; } private int GetCurrencyAmount(string itemName) { return itemName switch { "Crowns (Small)" => 250, "Crowns (Medium)" => 500, "Crowns (Large)" => 1000, "Crowns (Huge)" => 2500, _ => 0, }; } private int DetermineItemQuantity(string itemName) { if (string.Equals(itemName, "Fishing Rod", StringComparison.Ordinal) || string.Equals(itemName, "Pickaxe", StringComparison.Ordinal)) { return 1; } if (ArchipelagoGameDataTables.ItemNameMapping.TryGetValue(itemName, out string value)) { string[] array = new string[8] { "WEAPON_", "HELM_", "CHESTPIECE_", "LEGGINGS_", "CAPE_", "SHIELD_", "RING_", "TOOL_" }; foreach (string value2 in array) { if (value.Contains(value2)) { return 1; } } } return 10; } private bool ShouldSkipProfessionToolStorageGrant(string itemName) { if (_professionTools != null) { return _professionTools.PlayerPhysicallyHasProfessionTool(itemName); } return false; } private void GiveCurrency(int amount) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown try { Player mainPlayer = Player._mainPlayer; if ((Object)mainPlayer == (Object)null) { LogError("Player not found!"); return; } PlayerInventory component = ((Component)mainPlayer).GetComponent(); if ((Object)component == (Object)null) { LogError("PlayerInventory not found!"); return; } component.Network_heldCurrency += amount; LogInfo($"Gave {amount} Crowns! New total: {component.Network_heldCurrency}"); } catch (Exception ex) { LogError("Failed to give currency: " + ex.Message); } } private void OnPacketReceived(ArchipelagoPacketBase packet) { _mainThreadQueue.Enqueue(delegate { HandlePacketReceivedOnMainThread(packet); }); } private void OnArchipelagoMessageLogReceived(LogMessage message) { _mainThreadQueue.Enqueue(delegate { DeliverArchipelagoLogMessage(message); }); } private void DeliverArchipelagoLogMessage(LogMessage message) { if (_session != null && _session.Socket.Connected && message != null && !(message is HintItemSendLogMessage)) { string text = FormatArchipelagoLogMessage(message); if (!string.IsNullOrWhiteSpace(text)) { SendAPChatMessage(text); } } } private static string FormatArchipelagoLogMessage(LogMessage message) { if (message?.Parts == null || message.Parts.Length == 0) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(); MessagePart[] parts = message.Parts; foreach (MessagePart messagePart in parts) { string text = messagePart?.Text; if (!string.IsNullOrEmpty(text)) { string value = EscapeServerChatToken(text); stringBuilder.Append("") .Append(value) .Append(""); } } return stringBuilder.ToString(); } private static string TmpHexFromApColor(Color color) { return color.R.ToString("X2") + color.G.ToString("X2") + color.B.ToString("X2"); } private void HandlePacketReceivedOnMainThread(ArchipelagoPacketBase packet) { if (_session != null && _session.Socket.Connected && packet is HintPrintJsonPacket) { HandleHintPrintJsonOnMainThread(packet); } } private void HandleHintPrintJsonOnMainThread(ArchipelagoPacketBase packet) { if (packet is HintPrintJsonPacket hintPrintJsonPacket) { int slot = _session.Players.ActivePlayer.Slot; int receivingPlayer = hintPrintJsonPacket.ReceivingPlayer; NetworkItem item = hintPrintJsonPacket.Item; PlayerInfo playerInfo = _session.Players.GetPlayerInfo(receivingPlayer); PlayerInfo playerInfo2 = _session.Players.GetPlayerInfo(item.Player); string text = playerInfo?.Game ?? _session.Players.ActivePlayer?.Game ?? "Atlyss"; string game = playerInfo2?.Game ?? text; string token = _session.Items.GetItemName(item.Item, text) ?? $"Item {item.Item}"; string token2 = _session.Locations.GetLocationNameFromId(item.Location, game) ?? $"Location {item.Location}"; if (receivingPlayer == slot) { string token3 = playerInfo2?.Alias ?? playerInfo2?.Name ?? $"Player {item.Player}"; string message = "Your " + EscapeServerChatToken(token) + " is at " + EscapeServerChatToken(token2) + " in " + EscapeServerChatToken(token3) + "'s world."; SendAPChatMessage(message); } else if (item.Player == slot) { PlayerInfo playerInfo3 = playerInfo2; string token4 = playerInfo3?.Alias ?? playerInfo3?.Name ?? $"Player {item.Player}"; string message2 = "" + EscapeServerChatToken(token4) + "'s " + EscapeServerChatToken(token) + " is at your " + EscapeServerChatToken(token2) + ""; SendAPChatMessage(message2); } else { LogWarn("Received a hint for a different player. Ignoring."); } } } private void OnDeathLinkReceived(DeathLink dl) { string cause = dl.Cause; string source = dl.Source; _mainThreadQueue.Enqueue(delegate { ApplyDeathLinkOnMainThread(cause, source); }); } private void ApplyDeathLinkOnMainThread(string cause, string source) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_0032: Unknown result type (might be due to invalid IL or missing references) reactingToDeathLink = 2; try { Player mainPlayer = Player._mainPlayer; if ((Object)mainPlayer == (Object)null) { LogWarn("Player not found for DeathLink! Possibly on Main Menu?"); reactingToDeathLink = 0; return; } mainPlayer._playerZoneType = (ZoneType)1; mainPlayer._statusEntity.Subtract_Health(10000); ((BaseUnityPlugin)this).Logger.LogMessage((object)"[AtlyssAP] DeathLink Received!"); string text = ((!string.IsNullOrWhiteSpace(cause)) ? (EscapeServerChatToken(cause) + ".") : ("Killed by " + EscapeServerChatToken(source) + " on Archipelago.")); try { GameObject.Find("_GameUI_InGame").GetComponent().Init_ErrorPrompt(text); } catch (Exception ex) { LogError("Failed to display DeathLink message: " + ex.Message); } } catch (Exception ex2) { LogError("Failed to process DeathLink: " + ex2.Message); reactingToDeathLink = 0; } } public void ToggleDeathLink(bool enabled) { if (_session != null && _dlService != null && _session.Socket.Connected) { if (enabled) { _dlService.EnableDeathLink(); } else { _dlService.DisableDeathLink(); } } } internal void MarkGrantedItemProgressDirty() { Interlocked.Exchange(ref _grantedItemProgressDirty, 1); SchedulePersistenceFlush(); } internal void MarkProgressiveEquipmentDirty() { Interlocked.Exchange(ref _progressiveEquipmentDirty, 1); SchedulePersistenceFlush(); } internal void MarkPortalProgressDirty() { Interlocked.Exchange(ref _portalProgressDirty, 1); SchedulePersistenceFlush(); } internal void MarkShopPurchasedDirty() { Interlocked.Exchange(ref _shopPurchasedDirty, 1); SchedulePersistenceFlush(); } private void SchedulePersistenceFlush() { if (_persistenceFlushAfter < 0f) { _persistenceFlushAfter = Time.unscaledTime + 0.4f; } } internal void FlushDirtyPersistenceIfNeeded(bool force = false) { if (!string.IsNullOrEmpty(currentSessionId) && (Volatile.Read(in _grantedItemProgressDirty) != 0 || Volatile.Read(in _progressiveEquipmentDirty) != 0 || Volatile.Read(in _portalProgressDirty) != 0 || Volatile.Read(in _shopPurchasedDirty) != 0) && (force || !(Time.unscaledTime < _persistenceFlushAfter))) { _persistenceFlushAfter = -1f; if (Interlocked.CompareExchange(ref _grantedItemProgressDirty, 0, 1) == 1) { SaveGrantedItemKeys(currentSessionId); SaveSessionState(currentSessionId, _lastProcessedItemIndex); } if (Interlocked.CompareExchange(ref _progressiveEquipmentDirty, 0, 1) == 1) { SaveProgressiveEquipmentState(currentSessionId); } if (Interlocked.CompareExchange(ref _portalProgressDirty, 0, 1) == 1) { SavePortalProgressState(currentSessionId); } if (Interlocked.CompareExchange(ref _shopPurchasedDirty, 0, 1) == 1) { SaveShopPurchasedLocations(currentSessionId); } } } private static string GetApPersistenceDirectory() { string text = Path.Combine(Application.dataPath, "profileCollections"); if (!Directory.Exists(text)) { Directory.CreateDirectory(text); } return text; } private void EnsurePersistenceMigratedFromLegacyAppData() { try { string persistentDataPath = Application.persistentDataPath; string apPersistenceDirectory = GetApPersistenceDirectory(); if (string.Equals(persistentDataPath, apPersistenceDirectory, StringComparison.OrdinalIgnoreCase)) { return; } CopyPersistenceFileIfPresent(Path.Combine(persistentDataPath, "ap_session_state.dat"), Path.Combine(apPersistenceDirectory, "ap_session_state.dat")); if (Directory.Exists(persistentDataPath)) { string[] files = Directory.GetFiles(persistentDataPath, "ap_*.dat"); foreach (string text in files) { CopyPersistenceFileIfPresent(text, Path.Combine(apPersistenceDirectory, Path.GetFileName(text))); } } } catch (Exception ex) { LogWarn("Legacy AP persistence migration failed: " + ex.Message); } } private string GetSessionStatePath() { return Path.Combine(GetApPersistenceDirectory(), "ap_session_state.dat"); } private bool LoadSessionState(string sessionId, string legacySessionId = null) { try { int num = CountPersistedGrantedItemKeys(sessionId); if (!string.IsNullOrEmpty(legacySessionId) && !string.Equals(legacySessionId, sessionId, StringComparison.Ordinal)) { int num2 = CountPersistedGrantedItemKeys(legacySessionId); if (num2 > num) { MigrateSessionPersistenceFiles(legacySessionId, sessionId); num = num2; } } string sessionStatePath = GetSessionStatePath(); if (File.Exists(sessionStatePath)) { string[] array = File.ReadAllLines(sessionStatePath); if (array.Length >= 1 && (array[0] == sessionId || (!string.IsNullOrEmpty(legacySessionId) && array[0] == legacySessionId))) { if (array[0] != sessionId) { MigrateSessionPersistenceFiles(legacySessionId, sessionId); } _lastProcessedItemIndex = ((array.Length >= 2 && int.TryParse(array[1], out var result)) ? result : num); if (_lastProcessedItemIndex < num) { _lastProcessedItemIndex = num; SaveSessionState(sessionId, _lastProcessedItemIndex); } LogInfo($"Same session resumed at item index {_lastProcessedItemIndex} (ledger={num})."); return false; } } if (num > 0) { _lastProcessedItemIndex = num; SaveSessionState(sessionId, _lastProcessedItemIndex); LogInfo($"Resuming from grant ledger at item index {_lastProcessedItemIndex}; session marker missing or mismatched."); return false; } return true; } catch { return true; } } private void MigrateSessionPersistenceFiles(string legacySessionId, string sessionId) { try { if (!string.IsNullOrEmpty(legacySessionId) && !string.IsNullOrEmpty(sessionId) && !string.Equals(legacySessionId, sessionId, StringComparison.Ordinal)) { CopyPersistenceFileIfPresent(GetGrantedItemsStatePath(legacySessionId), GetGrantedItemsStatePath(sessionId)); CopyPersistenceFileIfPresent(GetPortalProgressStatePath(legacySessionId), GetPortalProgressStatePath(sessionId)); CopyPersistenceFileIfPresent(GetProgressiveEquipmentStatePath(legacySessionId), GetProgressiveEquipmentStatePath(sessionId)); CopyPersistenceFileIfPresent(GetShopPurchasedStatePath(legacySessionId), GetShopPurchasedStatePath(sessionId)); LogInfo("Migrated AP persistence files from legacy session key to slot/seed key."); } } catch (Exception ex) { LogWarn("Failed to migrate legacy AP persistence files: " + ex.Message); } } private static void CopyPersistenceFileIfPresent(string sourcePath, string destinationPath) { if (File.Exists(sourcePath) && !File.Exists(destinationPath)) { File.Copy(sourcePath, destinationPath); } } private int CountPersistedGrantedItemKeys(string sessionId) { try { if (string.IsNullOrEmpty(sessionId)) { return 0; } string grantedItemsStatePath = GetGrantedItemsStatePath(sessionId); if (!File.Exists(grantedItemsStatePath)) { return 0; } return File.ReadAllLines(grantedItemsStatePath).Count((string line) => !string.IsNullOrWhiteSpace(line)); } catch { return 0; } } private void SaveSessionState(string sessionId, int itemCount) { try { File.WriteAllLines(GetSessionStatePath(), new string[2] { sessionId, itemCount.ToString() }); LogDebug($"Session state saved: {sessionId} | last item index {itemCount}."); } catch (Exception ex) { LogError("Failed to save session state: " + ex.Message); } } private static string HashSessionIdForFilename(string sessionId) { return Convert.ToBase64String(Encoding.UTF8.GetBytes(sessionId)).Replace('/', '_').Replace('+', '-') .TrimEnd(new char[1] { '=' }); } private string GetGrantedItemsStatePath(string sessionId) { return Path.Combine(GetApPersistenceDirectory(), "ap_granted_items_" + HashSessionIdForFilename(sessionId) + ".dat"); } private string GetShopPurchasedStatePath(string sessionId) { return Path.Combine(GetApPersistenceDirectory(), "ap_shop_purchased_" + HashSessionIdForFilename(sessionId) + ".dat"); } private string GetPortalProgressStatePath(string sessionId) { return Path.Combine(GetApPersistenceDirectory(), "ap_portal_progress_" + HashSessionIdForFilename(sessionId) + ".dat"); } private string GetProgressiveEquipmentStatePath(string sessionId) { return Path.Combine(GetApPersistenceDirectory(), "ap_progressive_equipment_" + HashSessionIdForFilename(sessionId) + ".dat"); } private void PersistProgressiveEquipmentState() { MarkProgressiveEquipmentDirty(); } private void SaveProgressiveEquipmentState(string sessionId) { try { if (string.IsNullOrEmpty(sessionId)) { return; } List list = new List(); foreach (KeyValuePair item in _progressiveCounts.OrderBy, string>((KeyValuePair x) => x.Key, StringComparer.Ordinal)) { if (item.Value > 0) { list.Add("COUNT\t" + item.Key + "\t" + item.Value); } } foreach (string item2 in _grantedProgressiveConcreteItems.OrderBy((string x) => x, StringComparer.Ordinal)) { if (!string.IsNullOrWhiteSpace(item2)) { list.Add("GRANTED\t" + item2); } } File.WriteAllLines(GetProgressiveEquipmentStatePath(sessionId), list); } catch (Exception ex) { LogError("Failed to save progressive equipment state: " + ex.Message); } } private void LoadProgressiveEquipmentState(string sessionId, string legacySessionId = null) { try { _progressiveCounts.Clear(); _grantedProgressiveConcreteItems.Clear(); string path = ResolveProgressiveEquipmentStatePath(sessionId, legacySessionId); if (!File.Exists(path)) { RebuildProgressiveEquipmentFromReceivedItems(); LogInfo($"No progressive equipment file found; rebuilt receipt counts for {_progressiveCounts.Count} track(s), granted={_grantedProgressiveConcreteItems.Count}"); SaveProgressiveEquipmentState(sessionId); return; } string[] array = File.ReadAllLines(path); for (int i = 0; i < array.Length; i++) { string text = array[i]?.Trim(); if (!string.IsNullOrEmpty(text)) { string[] array2 = text.Split(new char[1] { '\t' }); if (array2.Length >= 3 && array2[0] == "COUNT" && int.TryParse(array2[2], out var result) && result > 0) { _progressiveCounts[array2[1]] = result; } else if (array2.Length >= 2 && array2[0] == "GRANTED" && !string.IsNullOrWhiteSpace(array2[1])) { _grantedProgressiveConcreteItems.Add(array2[1]); } } } LogInfo($"Loaded progressive equipment: {_progressiveCounts.Count} track count(s), {_grantedProgressiveConcreteItems.Count} granted concrete item(s)."); } catch (Exception ex) { LogError("Failed to load progressive equipment state: " + ex.Message); } } private string ResolveProgressiveEquipmentStatePath(string sessionId, string legacySessionId) { string progressiveEquipmentStatePath = GetProgressiveEquipmentStatePath(sessionId); if (File.Exists(progressiveEquipmentStatePath)) { return progressiveEquipmentStatePath; } if (!string.IsNullOrEmpty(legacySessionId) && !string.Equals(legacySessionId, sessionId, StringComparison.Ordinal)) { string progressiveEquipmentStatePath2 = GetProgressiveEquipmentStatePath(legacySessionId); if (File.Exists(progressiveEquipmentStatePath2)) { CopyPersistenceFileIfPresent(progressiveEquipmentStatePath2, progressiveEquipmentStatePath); if (!File.Exists(progressiveEquipmentStatePath)) { return progressiveEquipmentStatePath2; } return progressiveEquipmentStatePath; } } return progressiveEquipmentStatePath; } private void RebuildProgressiveEquipmentFromReceivedItems() { if (_session?.Items == null) { return; } ReadOnlyCollection allItemsReceived = _session.Items.AllItemsReceived; if (allItemsReceived == null || allItemsReceived.Count == 0) { return; } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (ItemInfo item in allItemsReceived) { string itemName = _session.Items.GetItemName(item.ItemId, item.ItemGame); if (!string.IsNullOrEmpty(itemName) && itemName.StartsWith("Progressive ", StringComparison.Ordinal) && !ArchipelagoGameDataTables.IsProgressivePortalItem(itemName) && ArchipelagoGameDataTables.ProgressiveItemTiers.ContainsKey(itemName)) { dictionary.TryGetValue(itemName, out var value); dictionary[itemName] = value + 1; } } foreach (KeyValuePair item2 in dictionary) { _progressiveCounts[item2.Key] = item2.Value; } } internal void SyncProgressiveEquipmentFromReceivedItems() { if (_session?.Items == null) { return; } ReadOnlyCollection allItemsReceived = _session.Items.AllItemsReceived; if (allItemsReceived == null || allItemsReceived.Count == 0) { return; } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (ItemInfo item in allItemsReceived) { string itemName = _session.Items.GetItemName(item.ItemId, item.ItemGame); if (!string.IsNullOrEmpty(itemName) && itemName.StartsWith("Progressive ", StringComparison.Ordinal) && !ArchipelagoGameDataTables.IsProgressivePortalItem(itemName) && ArchipelagoGameDataTables.ProgressiveItemTiers.ContainsKey(itemName)) { dictionary.TryGetValue(itemName, out var value); dictionary[itemName] = value + 1; } } bool flag = false; foreach (KeyValuePair item2 in dictionary) { int value2; int num = (_progressiveCounts.TryGetValue(item2.Key, out value2) ? value2 : 0); if (item2.Value > num) { _progressiveCounts[item2.Key] = item2.Value; flag = true; } } if (flag) { LogInfo($"Synced progressive equipment receipt counts from received items ({_progressiveCounts.Count} track(s))."); PersistProgressiveEquipmentState(); } } private void PersistPortalProgressState() { MarkPortalProgressDirty(); } private void SavePortalProgressState(string sessionId) { try { if (string.IsNullOrEmpty(sessionId)) { return; } string portalProgressStatePath = GetPortalProgressStatePath(sessionId); List list = new List { progressiveSanctumPortalCount.ToString(), progressiveTuulPortalCount.ToString() }; foreach (KeyValuePair item in _portalItemsReceived.Where((KeyValuePair x) => x.Value)) { list.Add(item.Key); } File.WriteAllLines(portalProgressStatePath, list); } catch (Exception ex) { LogError("Failed to save portal progress state: " + ex.Message); } } private void LoadPortalProgressState(string sessionId, string legacySessionId = null) { try { SetProgressivePortalLineCounts(0, 0); foreach (string item in _portalItemsReceived.Keys.ToList()) { _portalItemsReceived[item] = false; } string path = ResolvePortalProgressStatePath(sessionId, legacySessionId); if (!File.Exists(path)) { RebuildPortalProgressFromGrantedLedger(); LogInfo($"No portal progress file found; rebuilt state from ledger: sanctum={progressiveSanctumPortalCount}, tuul={progressiveTuulPortalCount}, random={_portalItemsReceived.Count((KeyValuePair x) => x.Value)}"); SavePortalProgressState(sessionId); return; } string[] array = File.ReadAllLines(path); if (array.Length >= 2 && int.TryParse(array[0], out var result) && int.TryParse(array[1], out var result2)) { SetProgressivePortalLineCounts(result, result2); } for (int num = 1; num < array.Length; num++) { string text = array[num]?.Trim(); if (!string.IsNullOrEmpty(text) && _portalItemsReceived.ContainsKey(text)) { _portalItemsReceived[text] = true; } } LogInfo($"Loaded portal progress: sanctum={progressiveSanctumPortalCount}, tuul={progressiveTuulPortalCount}, random={_portalItemsReceived.Count((KeyValuePair x) => x.Value)}"); } catch (Exception ex) { LogError("Failed to load portal progress state: " + ex.Message); } } private string ResolvePortalProgressStatePath(string sessionId, string legacySessionId) { string portalProgressStatePath = GetPortalProgressStatePath(sessionId); if (File.Exists(portalProgressStatePath)) { return portalProgressStatePath; } if (!string.IsNullOrEmpty(legacySessionId) && !string.Equals(legacySessionId, sessionId, StringComparison.Ordinal)) { string portalProgressStatePath2 = GetPortalProgressStatePath(legacySessionId); if (File.Exists(portalProgressStatePath2)) { CopyPersistenceFileIfPresent(portalProgressStatePath2, portalProgressStatePath); if (!File.Exists(portalProgressStatePath)) { return portalProgressStatePath2; } return portalProgressStatePath; } } return portalProgressStatePath; } private void RebuildPortalProgressFromGrantedLedger() { if (_session?.Items == null || _grantedItemKeys == null) { return; } string[] array; lock (_grantedItemKeys) { if (_grantedItemKeys.Count == 0) { return; } array = new string[_grantedItemKeys.Count]; _grantedItemKeys.CopyTo(array); } int num = 0; int num2 = 0; string[] array2 = array; foreach (string text in array2) { if (string.IsNullOrWhiteSpace(text) || !TryParseGrantedItemKey(text, out long itemId, out string itemGame)) { continue; } string itemName = _session.Items.GetItemName(itemId, itemGame); if (!string.IsNullOrEmpty(itemName)) { if (itemName == "Progressive Sanctum Portal") { num++; } else if (itemName == "Progressive Tuul Portal") { num2++; } else if (_portalItemsReceived.ContainsKey(itemName)) { _portalItemsReceived[itemName] = true; } } } SetProgressivePortalLineCounts(num, num2); } internal void SyncPortalUnlocksFromReceivedItems() { if (_session?.Items == null) { return; } ReadOnlyCollection allItemsReceived = _session.Items.AllItemsReceived; if (allItemsReceived == null || allItemsReceived.Count == 0) { return; } int num = 0; int num2 = 0; foreach (ItemInfo item in allItemsReceived) { string itemName = _session.Items.GetItemName(item.ItemId, item.ItemGame); if (!string.IsNullOrEmpty(itemName)) { if (itemName == "Progressive Sanctum Portal") { num++; } else if (itemName == "Progressive Tuul Portal") { num2++; } else if (_portalItemsReceived.ContainsKey(itemName)) { _portalItemsReceived[itemName] = true; } } } if (num > progressiveSanctumPortalCount || num2 > progressiveTuulPortalCount) { SetProgressivePortalLineCounts(Math.Max(progressiveSanctumPortalCount, num), Math.Max(progressiveTuulPortalCount, num2)); } int num3 = _portalItemsReceived.Count((KeyValuePair x) => x.Value); LogInfo($"Synced portal unlocks from received items: sanctum={progressiveSanctumPortalCount}, tuul={progressiveTuulPortalCount}, random={num3}"); if (!string.IsNullOrEmpty(currentSessionId)) { SavePortalProgressState(currentSessionId); } } private void ReapplyUnlockedPortals() { if ((Object)(object)portalLocker == (Object)null) { return; } for (int i = 0; i < progressiveSanctumPortalCount && i < ArchipelagoGameDataTables.ProgressiveSanctumPortalOrder.Count; i++) { string key = ArchipelagoGameDataTables.ProgressiveSanctumPortalOrder[i]; if (ArchipelagoGameDataTables.PortalScenes.TryGetValue(key, out string value)) { portalLocker.UnblockAccessToScene(value); } } for (int j = 0; j < progressiveTuulPortalCount && j < ArchipelagoGameDataTables.ProgressiveTuulPortalOrder.Count; j++) { string key2 = ArchipelagoGameDataTables.ProgressiveTuulPortalOrder[j]; if (ArchipelagoGameDataTables.PortalScenes.TryGetValue(key2, out string value2)) { portalLocker.UnblockAccessToScene(value2); } } foreach (KeyValuePair item in _portalItemsReceived) { if (item.Value && ArchipelagoGameDataTables.PortalScenes.TryGetValue(item.Key, out string value3)) { portalLocker.UnblockAccessToScene(value3); } } portalLocker.ApplyPortalLocksToAllLoadedScenes(); } private void ClearShopPurchasedStateFile(string sessionId) { try { if (!string.IsNullOrEmpty(sessionId)) { File.WriteAllLines(GetShopPurchasedStatePath(sessionId), Array.Empty()); } } catch (Exception ex) { LogError("Failed to clear shop-purchased state: " + ex.Message); } } private void LoadShopPurchasedLocations(string sessionId) { try { if (_shopSanity == null || string.IsNullOrEmpty(sessionId)) { return; } string shopPurchasedStatePath = GetShopPurchasedStatePath(sessionId); if (!File.Exists(shopPurchasedStatePath)) { _shopSanity.ApplyPersistedPurchasedShopLocations(Array.Empty()); return; } List list = new List(); string[] array = File.ReadAllLines(shopPurchasedStatePath); for (int i = 0; i < array.Length; i++) { if (long.TryParse(array[i].Trim(), out var result) && result > 0) { list.Add(result); } } _shopSanity.ApplyPersistedPurchasedShopLocations(list); LogInfo($"Loaded {list.Count} persisted AP shop purchase(s) for this session."); } catch (Exception ex) { LogError("Failed to load shop-purchased state: " + ex.Message); } } private void SaveShopPurchasedLocations(string sessionId) { try { if (!string.IsNullOrEmpty(sessionId) && _shopSanity != null) { string shopPurchasedStatePath = GetShopPurchasedStatePath(sessionId); long[] source = (from x in _shopSanity.SnapshotPurchasedShopLocationIds() orderby x select x).ToArray(); File.WriteAllLines(shopPurchasedStatePath, source.Select((long x) => x.ToString()).ToArray()); } } catch (Exception ex) { LogError("Failed to save shop-purchased state: " + ex.Message); } } internal void PersistShopPurchasedState() { if (!string.IsNullOrEmpty(currentSessionId)) { MarkShopPurchasedDirty(); } } private string BuildGrantedItemKey(ItemInfo itemInfo) { return $"{itemInfo.LocationId}:{itemInfo.ItemId}"; } internal bool HasReceivedApItemNamed(string itemName) { if (string.IsNullOrWhiteSpace(itemName) || _session?.Items == null) { return false; } ReadOnlyCollection allItemsReceived = _session.Items.AllItemsReceived; if (allItemsReceived != null) { for (int i = 0; i < allItemsReceived.Count; i++) { ItemInfo itemInfo = allItemsReceived[i]; if (string.Equals(_session.Items.GetItemName(itemInfo.ItemId, itemInfo.ItemGame), itemName, StringComparison.Ordinal)) { return true; } } } lock (_grantedItemKeys) { foreach (string grantedItemKey in _grantedItemKeys) { if (TryParseGrantedItemKey(grantedItemKey, out long itemId, out string itemGame) && string.Equals(_session.Items.GetItemName(itemId, itemGame), itemName, StringComparison.Ordinal)) { return true; } } } return false; } private bool TryParseGrantedItemKey(string grantedItemKey, out long itemId, out string itemGame) { itemId = 0L; itemGame = _session?.ConnectionInfo?.Game ?? "Atlyss"; if (string.IsNullOrWhiteSpace(grantedItemKey)) { return false; } string[] array = grantedItemKey.Split(new char[1] { ':' }); if (array.Length >= 4 && long.TryParse(array[1], out itemId)) { if (!string.IsNullOrWhiteSpace(array[3])) { itemGame = array[3]; } return true; } if (array.Length >= 2 && long.TryParse(array[1], out itemId)) { return true; } return false; } internal void PersistGrantedItemProgress(string sessionId) { MarkGrantedItemProgressDirty(); } internal void RecordGrantLedgerKey(string grantLedgerKey) { if (string.IsNullOrWhiteSpace(grantLedgerKey)) { return; } lock (_grantedItemKeys) { _grantedItemKeys.Add(grantLedgerKey); } } private string NormalizeGrantedItemKey(string rawKey) { if (string.IsNullOrWhiteSpace(rawKey)) { return null; } string[] array = rawKey.Split(new char[1] { ':' }); if (array.Length >= 4 && long.TryParse(array[1], out var result) && long.TryParse(array[2], out var result2)) { return $"{result}:{result2}"; } if (array.Length >= 2 && long.TryParse(array[0], out var result3) && long.TryParse(array[1], out var result4)) { return $"{result3}:{result4}"; } return null; } private void LoadGrantedItemKeys(string sessionId) { try { lock (_grantedItemKeys) { _grantedItemKeys.Clear(); } string grantedItemsStatePath = GetGrantedItemsStatePath(sessionId); if (!File.Exists(grantedItemsStatePath)) { return; } string[] array = File.ReadAllLines(grantedItemsStatePath); lock (_grantedItemKeys) { string[] array2 = array; foreach (string rawKey in array2) { string text = NormalizeGrantedItemKey(rawKey); if (!string.IsNullOrEmpty(text)) { _grantedItemKeys.Add(text); } } } LogInfo($"Loaded {_grantedItemKeys.Count} grant ledger key(s) for this session."); } catch (Exception ex) { LogError("Failed to load granted-item state: " + ex.Message); } } private void SaveGrantedItemKeys(string sessionId) { try { if (string.IsNullOrEmpty(sessionId)) { return; } string grantedItemsStatePath = GetGrantedItemsStatePath(sessionId); string[] contents; lock (_grantedItemKeys) { contents = _grantedItemKeys.OrderBy((string x) => x, StringComparer.Ordinal).ToArray(); } File.WriteAllLines(grantedItemsStatePath, contents); } catch (Exception ex) { LogError("Failed to save granted-item state: " + ex.Message); } } } internal static class AtlyssEconomyMultipliers { public static float FromOptionValue(int optionValue) { return optionValue switch { 0 => 8f, 1 => 4f, 2 => 2f, 3 => 1f, 4 => 0.75f, 5 => 0.5f, _ => 1f, }; } public static int ScaleCrownAmount(int amount) { if (amount <= 0) { return amount; } AtlyssArchipelagoPlugin instance = AtlyssArchipelagoPlugin.Instance; if ((Object)(object)instance == (Object)null || !instance.connected) { return amount; } float crownMultiplier = instance.crownMultiplier; if (Math.Abs(crownMultiplier - 1f) < 0.001f) { return amount; } return Math.Max(1, (int)Math.Round((float)amount * crownMultiplier)); } } [HarmonyPatch(typeof(FriendlyNPC_Hitbox), "OnTriggerEnter")] public static class AngelaRudePatch { private static void Postfix(FriendlyNPC_Hitbox __instance) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown try { if ((Object)AtlyssArchipelagoPlugin.Instance == (Object)null || !AtlyssArchipelagoPlugin.Instance.connected) { return; } FieldInfo fieldInfo = AccessTools.Field(typeof(FriendlyNPC_Hitbox), "_achievementTag"); if (!(fieldInfo == null) && !((string)fieldInfo.GetValue(__instance) != "ATLYSS_ACHIEVEMENT_11")) { ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogInfo((object)"[AtlyssAP] Angela 'Rude!' hitbox triggered!"); } AtlyssArchipelagoPlugin.Instance.SendAchievementCheck("Rude!"); } } catch (Exception ex) { ManualLogSource staticLogger2 = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger2 != null) { staticLogger2.LogError((object)("[AtlyssAP] Angela patch error: " + ex.Message)); } } } } [HarmonyPatch(typeof(ChatBehaviourAssets), "Start")] public static class ChatBehaviourAssetsCanvasOrderPatch { private const int ChatCanvasSortingOrder = -100; private static readonly HashSet _retargetedCanvasIds = new HashSet(); private static void Postfix(ChatBehaviourAssets __instance) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown try { if ((Object)__instance == (Object)null) { return; } HashSet hashSet = new HashSet(); CollectEffectiveCanvas((Component)(object)__instance._chatText, hashSet); CollectEffectiveCanvas((Component)(object)__instance._gameLogicText, hashSet); CollectEffectiveCanvas((Component)(object)__instance._chatInput, hashSet); foreach (Canvas item in hashSet) { if (!((Object)item == (Object)null)) { int instanceID = ((Object)item).GetInstanceID(); if (!_retargetedCanvasIds.Contains(instanceID)) { item.sortingOrder = -100; _retargetedCanvasIds.Add(instanceID); } } } } catch (Exception ex) { AtlyssArchipelagoPlugin.SLogWarn("Failed to demote chat canvas sortingOrder: " + ex.Message); } } private static void CollectEffectiveCanvas(Component anchor, HashSet sink) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown if (!((Object)anchor == (Object)null)) { Canvas val = FindEffectiveSortingCanvas(anchor.transform); if ((Object)val != (Object)null) { sink.Add(val); } } } private static Canvas FindEffectiveSortingCanvas(Transform start) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown Canvas result = null; Transform val = start; while ((Object)val != (Object)null) { Canvas component = ((Component)val).GetComponent(); if (!((Object)component == (Object)null)) { if (component.overrideSorting) { return component; } result = component; } val = val.parent; } return result; } } [HarmonyPatch(typeof(ChatBehaviour), "Send_ChatMessage")] public static class ChatBehaviourPatch { private static readonly string[] SupportedCommands = new string[6] { "/release", "/collect", "/hint", "/help", "/players", "/status" }; private static bool Prefix(ChatBehaviour __instance, string _message) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown try { if (!string.IsNullOrEmpty(_message) && _message.StartsWith("/")) { bool flag = false; string[] supportedCommands = SupportedCommands; foreach (string value in supportedCommands) { if (_message.StartsWith(value, StringComparison.OrdinalIgnoreCase)) { flag = true; break; } } if (flag) { if ((Object)AtlyssArchipelagoPlugin.Instance != (Object)null) { AtlyssArchipelagoPlugin.Instance.HandleArchipelagoCommand(_message); } __instance._focusedInChat = false; return false; } } return true; } catch (Exception ex) { ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogError((object)("[AtlyssAP] Chat patch error: " + ex.Message)); } return true; } } } [HarmonyPatch(typeof(ChatBehaviourAssets), "Update")] public static class ChatBehaviourAssetsWelcomeConnectHintPatch { private static void Postfix() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown try { if (!((Object)AtlyssArchipelagoPlugin.Instance == (Object)null)) { AtlyssArchipelagoPlugin.Instance.TryOfferPressF5ConnectHintAfterWelcomePainted(); } } catch (Exception ex) { ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogError((object)("[AtlyssAP] Welcome-hint gate (ChatBehaviourAssets.Update): " + ex.Message)); } } } } [HarmonyPatch(typeof(ChatBehaviour), "New_ChatMessage")] public static class ChatBehaviourNewChatMessageWelcomeConnectHintPatch { private static void Prefix(ChatBehaviour __instance, out bool __state) { __state = false; try { if (__instance._chatMessages != null && __instance._chatMessages.Count == 0) { __state = true; } } catch { __state = false; } } private static void Postfix(bool __state) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown if (!__state) { return; } try { if (!((Object)AtlyssArchipelagoPlugin.Instance == (Object)null)) { AtlyssArchipelagoPlugin.Instance.TryOfferPressF5ConnectHintAfterWelcomePainted(); } } catch (Exception ex) { ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogError((object)("[AtlyssAP] Welcome-hint gate (New_ChatMessage): " + ex.Message)); } } } } [HarmonyPatch(typeof(Creep), "Rpc_InitCreepDeathParams")] internal static class CreepDeathPatch { private static void Postfix(Creep __instance) { AtlyssArchipelagoPlugin instance = AtlyssArchipelagoPlugin.Instance; if ((Object)(object)instance != (Object)null && instance.connected) { instance.NotifyBossDeathFromCreep(__instance, "Creep.Rpc_InitCreepDeathParams"); } } } internal static class CrownMultiplierPatch { private static bool _activationLogged; public static void ApplyExplicit(Harmony harmony, ManualLogSource logger) { if (harmony != null) { PatchMethod(harmony, logger, typeof(ItemDropEntity), "Client_DropCurrency", "ClientDropCurrencyPrefix"); PatchMethod(harmony, logger, typeof(PlayerInventory), "Add_Currency", "AddCurrencyPrefix"); } } private static void PatchMethod(Harmony harmony, ManualLogSource logger, Type type, string methodName, string prefixName) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); if (methodInfo == null) { if (logger != null) { logger.LogWarning((object)("[AtlyssAP] Crown multiplier: missing " + type.Name + "." + methodName)); } return; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(CrownMultiplierPatch), prefixName, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); if (logger != null) { logger.LogInfo((object)("[AtlyssAP] Crown multiplier patch: " + type.Name + "." + methodName)); } } private static void ClientDropCurrencyPrefix(ItemDropEntity __instance, ref int _setRate) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown if (_setRate > 0 && !((Object)__instance == (Object)null) && !((Object)((Component)__instance).GetComponentInParent() == (Object)null)) { int num = AtlyssEconomyMultipliers.ScaleCrownAmount(_setRate); if (num != _setRate) { LogActivationOnce("creep Client_DropCurrency", _setRate, num); _setRate = num; } } } private static void AddCurrencyPrefix(ref int _value) { if (_value > 0 && !IsCreepCoinPickupGrant()) { int num = AtlyssEconomyMultipliers.ScaleCrownAmount(_value); if (num != _value) { LogActivationOnce("quest Add_Currency", _value, num); _value = num; } } } private static bool IsCreepCoinPickupGrant() { StackTrace stackTrace = new StackTrace(fNeedFileInfo: false); for (int i = 0; i < stackTrace.FrameCount && i < 16; i++) { MethodBase methodBase = stackTrace.GetFrame(i)?.GetMethod(); if (!(methodBase == null) && methodBase.Name == "UserCode_Cmd_AddCurrency__Int32") { return true; } } return false; } private static void LogActivationOnce(string source, int fromAmount, int toAmount) { if (!_activationLogged) { _activationLogged = true; float num = AtlyssArchipelagoPlugin.Instance?.crownMultiplier ?? 1f; AtlyssArchipelagoPlugin.SLogInfo($"[AtlyssAP] Crown multiplier active ({source}): {fromAmount} -> {toAmount} (x{num:0.##})"); } } } [HarmonyPatch(typeof(DungeonPortalManager), "Update")] public static class DungeonPortalManagerFloorPatch { private static readonly FieldInfo EasyButtonField = AccessTools.Field(typeof(DungeonPortalManager), "_enterDungeonButton_easy"); private static readonly FieldInfo NormalButtonField = AccessTools.Field(typeof(DungeonPortalManager), "_enterDungeonButton_normal"); private static readonly FieldInfo HardButtonField = AccessTools.Field(typeof(DungeonPortalManager), "_enterDungeonButton_hard"); private static void Postfix(DungeonPortalManager __instance) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown AtlyssArchipelagoPlugin instance = AtlyssArchipelagoPlugin.Instance; if ((Object)instance == (Object)null || !instance.connected || !__instance._isOpen) { return; } ScenePortalData scenePortal = __instance._scenePortal; if (scenePortal == null) { return; } string obj = scenePortal._subScene ?? ""; string text = scenePortal._portalCaptionTitle ?? ""; bool flag = obj.Contains("sanctumCatacombs") || text.IndexOf("Catacombs", StringComparison.OrdinalIgnoreCase) >= 0; bool flag2 = obj.Contains("crescentGrove") || text.IndexOf("Grove", StringComparison.OrdinalIgnoreCase) >= 0; if (flag || flag2) { object? obj2 = EasyButtonField?.GetValue(__instance); Button val = (Button)((obj2 is Button) ? obj2 : null); object? obj3 = NormalButtonField?.GetValue(__instance); Button val2 = (Button)((obj3 is Button) ? obj3 : null); object? obj4 = HardButtonField?.GetValue(__instance); Button val3 = (Button)((obj4 is Button) ? obj4 : null); if (flag) { GateFloorButton(val, scenePortal._easyLevelRequirement, ArchipelagoDungeonFloorAccess.IsSanctumCatacombsFloorUnlocked(instance, 0)); GateFloorButton(val2, scenePortal._normalLevelRequirement, ArchipelagoDungeonFloorAccess.IsSanctumCatacombsFloorUnlocked(instance, 1)); GateFloorButton(val3, scenePortal._hardLevelRequirement, ArchipelagoDungeonFloorAccess.IsSanctumCatacombsFloorUnlocked(instance, 2)); } else { GateGroveFloorButtons(scenePortal, val, val2, val3, instance); } } } private static void GateGroveFloorButtons(ScenePortalData scenePortal, Button easyButton, Button normalButton, Button hardButton, AtlyssArchipelagoPlugin plugin) { (Button, int)[] array = new(Button, int)[3] { (easyButton, scenePortal._easyLevelRequirement), (normalButton, scenePortal._normalLevelRequirement), (hardButton, scenePortal._hardLevelRequirement) }; int num = 0; for (int i = 0; i < array.Length; i++) { Button item = array[i].Item1; int item2 = array[i].Item2; if (item2 <= 0) { HideFloorButton(item); continue; } if (num > 1) { HideFloorButton(item); continue; } GateFloorButton(item, item2, ArchipelagoDungeonFloorAccess.IsCrescentGroveFloorUnlocked(plugin, num)); num++; } } private static void GateFloorButton(Button button, int levelRequirement, bool apAllowsFloor) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown if (!((Object)button == (Object)null) && levelRequirement > 0 && !apAllowsFloor && ((Component)button).gameObject.activeInHierarchy) { ((Component)button).gameObject.SetActive(false); } } private static void HideFloorButton(Button button) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown if (!((Object)button == (Object)null)) { GameObject gameObject = ((Component)button).gameObject; if (gameObject.activeInHierarchy) { gameObject.SetActive(false); } } } } internal static class ExperienceMultiplierPatch { private struct GainExpPatchState { public int BeforeExp; public bool SkipMultiplier; } private static bool _activationLogged; private static bool _isApplyingCorrection; [ThreadStatic] private static int _experienceTomeSuppressDepth; private static readonly FieldInfo ScriptableItemNameField = AccessTools.Field(typeof(ScriptableItem), "_itemName"); public static void ApplyExplicit(Harmony harmony, ManualLogSource logger) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown if (harmony == null) { return; } HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(ExperienceMultiplierPatch), "GainExpPrefix", (Type[])null, (Type[])null)); HarmonyMethod val2 = new HarmonyMethod(AccessTools.Method(typeof(ExperienceMultiplierPatch), "GainExpPostfix", (Type[])null, (Type[])null)); List list = (from method in AccessTools.GetDeclaredMethods(typeof(PlayerStats)) where method.Name == "GainExp" select method).ToList(); if (logger != null) { logger.LogInfo((object)$"[AtlyssAP] Experience multiplier targets: GainExp={list.Count}"); } foreach (MethodInfo item in list) { harmony.Patch((MethodBase)item, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); if (logger != null) { logger.LogInfo((object)("[AtlyssAP] XP patch attached: " + item.Name + "(" + string.Join(", ", from p in item.GetParameters() select p.ParameterType.Name) + ")")); } } if (list.Count == 0 && logger != null) { logger.LogWarning((object)"[AtlyssAP] No GainExp methods found to patch."); } PatchExperienceTomeSuppressor(harmony, logger, typeof(ScriptableStatusConsumable), "Apply_ConsumableCalculation"); PatchExperienceTomeSuppressor(harmony, logger, typeof(ScriptableClassTome), "Apply_ClassTomeConsumable"); } private static void PatchExperienceTomeSuppressor(Harmony harmony, ManualLogSource logger, Type type, string methodName) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown //IL_0067: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); if (methodInfo == null) { if (logger != null) { logger.LogWarning((object)("[AtlyssAP] XP tome suppressor: missing " + type.Name + "." + methodName)); } return; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ExperienceMultiplierPatch), "ExperienceTomeApplyPrefix", (Type[])null), new HarmonyMethod(typeof(ExperienceMultiplierPatch), "ExperienceTomeApplyPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); if (logger != null) { logger.LogInfo((object)("[AtlyssAP] XP tome suppressor patch: " + type.Name + "." + methodName)); } } internal static bool IsExperienceTomeItemName(string itemName) { if (string.IsNullOrEmpty(itemName)) { return false; } if (itemName.IndexOf("Tome of", StringComparison.OrdinalIgnoreCase) >= 0) { return itemName.IndexOf("Experience", StringComparison.OrdinalIgnoreCase) >= 0; } return false; } private static string ReadScriptableItemName(object scriptableItem) { if (scriptableItem == null || ScriptableItemNameField == null) { return null; } return ScriptableItemNameField.GetValue(scriptableItem) as string; } private static void ExperienceTomeApplyPrefix(object __instance) { if (IsExperienceTomeItemName(ReadScriptableItemName(__instance))) { _experienceTomeSuppressDepth++; } } private static void ExperienceTomeApplyPostfix(object __instance) { if (IsExperienceTomeItemName(ReadScriptableItemName(__instance))) { _experienceTomeSuppressDepth = Math.Max(0, _experienceTomeSuppressDepth - 1); } } private static void GainExpPrefix(object __instance, object[] __args, out GainExpPatchState __state) { __state = default(GainExpPatchState); __state.SkipMultiplier = _experienceTomeSuppressDepth > 0; AtlyssArchipelagoPlugin instance = AtlyssArchipelagoPlugin.Instance; if ((Object)(object)instance == (Object)null) { return; } float experienceMultiplier = instance.experienceMultiplier; if (!__state.SkipMultiplier && !(Math.Abs(experienceMultiplier - 1f) < 0.001f) && __args != null && __args.Length != 0 && !_isApplyingCorrection) { PlayerStats val = (PlayerStats)((__instance is PlayerStats) ? __instance : null); if (val != null) { __state.BeforeExp = val.Network_currentExp; } } } private static void GainExpPostfix(object __instance, GainExpPatchState __state) { if (__state.SkipMultiplier || _isApplyingCorrection) { return; } PlayerStats val = (PlayerStats)((__instance is PlayerStats) ? __instance : null); if (val == null) { return; } AtlyssArchipelagoPlugin instance = AtlyssArchipelagoPlugin.Instance; if ((Object)(object)instance == (Object)null) { return; } float experienceMultiplier = instance.experienceMultiplier; if (Math.Abs(experienceMultiplier - 1f) < 0.001f) { return; } int num = val.Network_currentExp - __state.BeforeExp; if (num <= 0) { return; } int num2 = Math.Max(1, (int)Math.Round((float)num * experienceMultiplier)); if (num2 <= num) { return; } int network_currentExp = __state.BeforeExp + num2; try { _isApplyingCorrection = true; val.Network_currentExp = network_currentExp; LogPatchActivation(AccessTools.Method(typeof(PlayerStats), "GainExp", (Type[])null, (Type[])null), num, num2, experienceMultiplier); ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { staticLogger.LogDebug((object)$"[AtlyssAP] XP correction applied after GainExp: delta {num} -> {num2}"); } } finally { _isApplyingCorrection = false; } } private static void LogPatchActivation(MethodBase originalMethod, object fromValue, object toValue, float multiplier) { if (!_activationLogged) { _activationLogged = true; ManualLogSource staticLogger = AtlyssArchipelagoPlugin.StaticLogger; if (staticLogger != null) { string text = originalMethod.DeclaringType?.Name + "." + originalMethod.Name; staticLogger.LogInfo((object)$"[AtlyssAP] Experience multiplier active on {text}: {fromValue} -> {toValue} (x{multiplier:0.##}); XP tomes excluded"); } } } } [HarmonyPatch(typeof(ItemStorageManager), "Init_StorageListing")] public static class ItemStorageManagerInitStorageListingSpikeStorageUiPatch { [HarmonyPrefix] private static void Prefix(ItemStorageManager __instance) { ArchipelagoSpikeStorage.EnsureAllStorageSlotGameObjectsActive(__instance); } } [HarmonyPatch(typeof(GameManager), "Locate_Item")] public static class LocateItemPatch { private const string ExternalApIconFileName = "Atlyss_AP.Archipelago.png-256x256_q95.png"; private static ScriptableItem _fallbackDummyItem = null; private static Sprite _customAPSprite = null; private static Dictionary _apItemCache = new Dictionary(); private static void Postfix(string _tag, ref ScriptableItem __result) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown try { if ((Object)__result != (Object)null || string.IsNullOrEmpty(_tag) || !_tag.StartsWith("[AP]")) { return; } if (_apItemCache.TryGetValue(_tag, out ScriptableItem value)) { __result = value; return; } string text = ExtractItemName(_tag); if (string.IsNullOrEmpty(text)) { AtlyssArchipelagoPlugin.SLogWarn("Could not extract item name from: " + _tag); __result = GetCustomAPItem("Unknown Item"); _apItemCache[_tag] = __result; return; } ScriptableItem val = FindRealItem(text); ScriptableItem val2 = MakeApShopScriptableItem(text, val); _apItemCache[_tag] = val2; __result = val2; if ((Object)val != (Object)null) { AtlyssArchipelagoPlugin.SLogDebug("AP shop row shell + icon from '" + val._itemName + "' for: " + _tag); } else { AtlyssArchipelagoPlugin.SLogDebug("AP shop row custom icon for: " + _tag); } } catch (Exception ex) { AtlyssArchipelagoPlugin.SLogError("Locate_Item patch error: " + ex.Message); __result = GetCustomAPItem("Unknown Item"); } } public static string ExtractItemName(string apItemName) { string text = apItemName.Replace("[AP] ", ""); if (text.StartsWith("(x", StringComparison.OrdinalIgnoreCase)) { int num = text.IndexOf(") ", StringComparison.Ordinal); if (num > 0 && num + 2 < text.Length) { text = text.Substring(num + 2); } } int num2 = text.LastIndexOf(" ("); if (num2 > 0) { return text.Substring(0, num2); } return text; } private static ScriptableItem FindRealItem(string itemName) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown if (!ArchipelagoGameDataTables.ItemNameMapping.TryGetValue(itemName, out string value)) { return null; } string text = value; int num = value.LastIndexOf('_'); if (num >= 0 && num < value.Length - 1) { text = value.Substring(num + 1); } ScriptableItem val = GameManager._current.Locate_Item(text); if ((Object)val != (Object)null) { return val; } if (text.Contains(" (") && text.EndsWith(")")) { string text2 = text.Substring(0, text.LastIndexOf(" (")); val = GameManager._current.Locate_Item(text2); if ((Object)val != (Object)null) { return val; } } string text3 = text.Replace(" ", ""); val = GameManager._current.Locate_Item(text3); if ((Object)val != (Object)null) { return val; } return null; } private static ScriptableItem MakeApShopScriptableItem(string displayName, ScriptableItem iconSource) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown ScriptableItem fallbackDummy = GetFallbackDummy(); if ((Object)fallbackDummy == (Object)null) { AtlyssArchipelagoPlugin.SLogError("Could not create custom AP item - no base item available"); return null; } ScriptableItem val = Object.Instantiate(fallbackDummy); val._itemName = displayName; val._itemType = (ItemType)1; Sprite val2 = LoadCustomAPSprite(); if ((Object)val2 != (Object)null) { TrySetItemIcon(val, val2); } else if ((Object)iconSource != (Object)null) { CopyIconFrom(iconSource, val); } else { AtlyssArchipelagoPlugin.SLogWarn("Failed to load custom icon, using base item icon"); } return val; } private static ScriptableItem GetCustomAPItem(string itemName = "Archipelago Item") { return MakeApShopScriptableItem(itemName, null); } private static void CopyIconFrom(ScriptableItem src, ScriptableItem dst) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown if ((Object)src == (Object)null || (Object)dst == (Object)null) { return; } try { string[] obj = new string[6] { "_icon", "_itemIcon", "_sprite", "_itemSprite", "icon", "sprite" }; Type type = ((object)src).GetType(); string[] array = obj; foreach (string name in array) { FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(Sprite)) { Sprite val = (Sprite)field.GetValue(src); if ((Object)val != (Object)null) { TrySetItemIcon(dst, val); break; } } } } catch (Exception ex) { AtlyssArchipelagoPlugin.SLogWarn("CopyIconFrom failed: " + ex.Message); } } private static Sprite LoadCustomAPSprite() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Expected O, but got Unknown //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) if ((Object)_customAPSprite != (Object)null) { return _customAPSprite; } try { string text = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty, "Atlyss_AP.Archipelago.png-256x256_q95.png"); if (!File.Exists(text)) { text = Path.Combine(Directory.GetCurrentDirectory(), "Atlyss_AP.Archipelago.png-256x256_q95.png"); } if (File.Exists(text)) { byte[] array = File.ReadAllBytes(text); Texture2D val = new Texture2D(2, 2); if (ImageConversion.LoadImage(val, array)) { _customAPSprite = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); AtlyssArchipelagoPlugin.SLogDebug("Loaded AP icon from file: " + text); return _customAPSprite; } AtlyssArchipelagoPlugin.SLogWarn("Failed to decode AP icon file: " + text); } Assembly executingAssembly = Assembly.GetExecutingAssembly(); string[] manifestResourceNames = executingAssembly.GetManifestResourceNames(); string text2 = null; string[] array2 = manifestResourceNames; foreach (string text3 in array2) { if (text3.EndsWith("Archipelago.png-256x256_q95.png")) { text2 = text3; break; } } if (string.IsNullOrEmpty(text2)) { AtlyssArchipelagoPlugin.SLogError("Could not find Archipelago.png-256x256_q95.png in embedded resources"); AtlyssArchipelagoPlugin.SLogError("Available resources: " + string.Join(", ", manifestResourceNames)); return null; } using Stream stream = executingAssembly.GetManifestResourceStream(text2); if (stream == null) { AtlyssArchipelagoPlugin.SLogError("Failed to open embedded resource stream"); return null; } byte[] array3 = new byte[stream.Length]; stream.Read(array3, 0, array3.Length); Texture2D val2 = new Texture2D(2, 2); if (!ImageConversion.LoadImage(val2, array3)) { AtlyssArchipelagoPlugin.SLogError("Failed to load image data into texture"); return null; } _customAPSprite = Sprite.Create(val2, new Rect(0f, 0f, (float)((Texture)val2).width, (float)((Texture)val2).height), new Vector2(0.5f, 0.5f), 100f); AtlyssArchipelagoPlugin.SLogDebug($"Successfully loaded custom AP sprite: {((Texture)val2).width}x{((Texture)val2).height}"); return _customAPSprite; } catch (Exception ex) { AtlyssArchipelagoPlugin.SLogError("Error loading custom sprite: " + ex.Message); return null; } } private static void TrySetItemIcon(ScriptableItem item, Sprite sprite) { try { string[] obj = new string[6] { "_icon", "_itemIcon", "_sprite", "_itemSprite", "icon", "sprite" }; Type type = ((object)item).GetType(); string[] array = obj; foreach (string text in array) { FieldInfo field = type.GetField(text, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(Sprite)) { field.SetValue(item, sprite); AtlyssArchipelagoPlugin.SLogDebug("Set item icon using field: " + text); return; } } AtlyssArchipelagoPlugin.SLogWarn("Could not find icon field on ScriptableItem"); } catch (Exception ex) { AtlyssArchipelagoPlugin.SLogError("Error setting item icon: " + ex.Message); } } private static ScriptableItem GetFallbackDummy() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown if ((Object)_fallbackDummyItem == (Object)null) { _fallbackDummyItem = GameManager._current.Locate_Item("Bunbag"); if ((Object)_fallbackDummyItem == (Object)null) { _fallbackDummyItem = GameManager._current.Locate_Item("Wood Sword"); } if ((Object)_fallbackDummyItem == (Object)null) { _fallbackDummyItem = GameManager._current.Locate_Item("Leather Top"); } } return _fallbackDummyItem; } } [HarmonyPatch(typeof(Player), "Player_OnDeath")] internal class PlayerDeathPatch { private static void Postfix(Player __instance) { AtlyssArchipelagoPlugin instance = AtlyssArchipelagoPlugin.Instance; if (instance._dlService != null && instance.cfgDeathlink.Value) { if (AtlyssArchipelagoPlugin.reactingToDeathLink > 0) { AtlyssArchipelagoPlugin.reactingToDeathLink--; return; } DeathLink deathLink = new DeathLink(__instance._nickname, __instance._nickname + " was defeated."); instance._dlService.SendDeathLink(deathLink); } } } [HarmonyPatch(typeof(Portal), "Handle_QuestOverrideConditions")] public static class PortalQuestOverrideBypassPatch { private const string LockedCaptionSuffix = " (Locked!)"; private static void Postfix(Portal __instance) { AtlyssArchipelagoPlugin instance = AtlyssArchipelagoPlugin.Instance; if (!((Object)(object)instance == (Object)null) && instance.PortalLockEnforcementActive && !__instance._netDisablePortal && __instance._scenePortal != null && IsApManagedPortalDestination(__instance._scenePortal)) { __instance._isPortalOpen = true; } } private static bool IsApManagedPortalDestination(ScenePortalData scenePortal) { string text = scenePortal._subScene ?? string.Empty; foreach (string value2 in ArchipelagoGameDataTables.PortalScenes.Values) { if (text == value2) { return true; } } string text2 = scenePortal._portalCaptionTitle ?? string.Empty; if (text2.EndsWith(" (Locked!)", StringComparison.OrdinalIgnoreCase)) { text2 = text2.Substring(0, text2.Length - " (Locked!)".Length); } foreach (string key in ArchipelagoGameDataTables.PortalScenes.Keys) { string value = key.Replace(" Portal", string.Empty); if (text2.Equals(value, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } } [HarmonyPatch(typeof(PlayerInventory), "Cmd_SellItem")] public static class ProfessionToolSellPatch { private static bool Prefix(ItemData _itemData) { try { AtlyssArchipelagoPlugin instance = AtlyssArchipelagoPlugin.Instance; if (instance?._professionTools == null) { return true; } return !instance._professionTools.TryBlockProfessionToolDiscard(_itemData); } catch (Exception ex) { AtlyssArchipelagoPlugin.SLogError("Profession tool sell patch error: " + ex.Message); return true; } } } [HarmonyPatch(typeof(QuestSelectionManager), "Create_QuestListing")] internal static class QuestSelectionListingPatch { private static readonly FieldInfo QuestEntriesField = AccessTools.Field(typeof(QuestSelectionManager), "_questEntries"); private static readonly FieldInfo QuestSelectionContainerField = AccessTools.Field(typeof(QuestSelectionManager), "_questSelectionContainer"); private static readonly FieldInfo QuestSelectionPrefabField = AccessTools.Field(typeof(QuestSelectionManager), "_questSelectionPrefab"); private static readonly FieldInfo AvailableQuestsField = AccessTools.Field(typeof(QuestSelectionManager), "_availableQuests"); [HarmonyPostfix] private static void Postfix(QuestSelectionManager __instance, ScriptableQuest[] _scriptableQuests) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Expected O, but got Unknown //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Expected O, but got Unknown //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) try { if (!ArchipelagoQuestListUi.ShouldEnhanceQuestList() || (Object)(object)__instance == (Object)null || _scriptableQuests == null || _scriptableQuests.Length == 0 || (Object)Player._mainPlayer == (Object)null || !(QuestEntriesField?.GetValue(__instance) is List list)) { return; } object? obj = QuestSelectionContainerField?.GetValue(__instance); Transform val = (Transform)((obj is Transform) ? obj : null); if (val == null) { return; } object? obj2 = QuestSelectionPrefabField?.GetValue(__instance); GameObject val2 = (GameObject)((obj2 is GameObject) ? obj2 : null); if (val2 == null) { return; } HashSet hashSet = new HashSet(); for (int i = 0; i < list.Count; i++) { QuestListDataEntry val3 = list[i]; if ((Object)(object)val3 != (Object)null && (Object)val3._scriptableQuest != (Object)null) { hashSet.Add(val3._scriptableQuest); } } int num = 0; foreach (ScriptableQuest val4 in _scriptableQuests) { if (!((Object)val4 == (Object)null) && !hashSet.Contains(val4) && (int)val4._questType == 0 && ArchipelagoQuestListUi.IsQuestCompleted(val4)) { GameObject val5 = Object.Instantiate(val2, Vector3.zero, Quaternion.identity); QuestListDataEntry component = val5.GetComponent(); if ((Object)(object)component == (Object)null) { Object.Destroy((Object)(object)val5); continue; } val5.transform.localScale = Vector3.zero; ((Component)component).transform.SetParent(val); component._scriptableQuest = val4; list.Add(component); hashSet.Add(val4); num++; } } if (num > 0 && AvailableQuestsField != null) { int num2 = (int)AvailableQuestsField.GetValue(__instance); AvailableQuestsField.SetValue(__instance, num2 + num); } } catch (Exception ex) { AtlyssArchipelagoPlugin.SLogWarn("Quest list AP postfix failed: " + ex.Message); } } } [HarmonyPatch(typeof(QuestListDataEntry), "Update")] internal static class QuestListDataEntryCompletedPatch { [HarmonyPostfix] private static void Postfix(QuestListDataEntry __instance) { try { if (ArchipelagoQuestListUi.ShouldEnhanceQuestList() && !((Object)(object)__instance == (Object)null) && ArchipelagoQuestListUi.IsQuestCompleted(__instance._scriptableQuest)) { ArchipelagoQuestListUi.ApplyCompletedRowStyle(__instance); } } catch (Exception ex) { AtlyssArchipelagoPlugin.SLogWarn("Quest row checkmark patch failed: " + ex.Message); } } } internal static class SaveQuitDisconnectPatch { private static readonly string[] ExplicitTypeNames = new string[4] { "PauseMenuManager", "SettingsManager", "GameManager", "MainMenuManager" }; private static readonly string[] ExplicitMethodNames = new string[10] { "SaveAndQuit", "Save_AndQuit", "SaveAndExit", "Save_AndExit", "Init_SaveAndQuit", "Init_Save_AndQuit", "QuitToMainMenu", "Quit_ToMainMenu", "SaveAndReturnToMenu", "Save_AndReturnToMenu" }; public static void Apply(Harmony harmony, Action logInfo, Action logWarning) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown HashSet hashSet = new HashSet(); MethodInfo methodInfo = AccessTools.Method(typeof(SaveQuitDisconnectPatch), "Postfix", (Type[])null, (Type[])null); if (methodInfo == null) { logWarning?.Invoke("SaveQuit patch setup failed: Postfix method missing."); return; } HarmonyMethod val = new HarmonyMethod(methodInfo); Assembly assembly = typeof(Player).Assembly; string[] explicitTypeNames = ExplicitTypeNames; foreach (string name in explicitTypeNames) { Type type = assembly.GetType(name, throwOnError: false); if (type == null) { continue; } string[] explicitMethodNames = ExplicitMethodNames; foreach (string name2 in explicitMethodNames) { MethodInfo method = type.GetMethod(name2, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) { hashSet.Add(method); } } } try { Type[] types = typeof(Player).Assembly.GetTypes(); for (int i = 0; i < types.Length; i++) { MethodInfo[] methods = types[i].GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { if (LooksLikeSaveQuitMethod(methodInfo2)) { hashSet.Add(methodInfo2); } } } } catch (Exception ex) { logWarning?.Invoke("SaveQuit heuristic scan failed: " + ex.Message); } int num = 0; foreach (MethodBase item in hashSet) { try { harmony.Patch(item, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } catch (Exception ex2) { logWarning?.Invoke("SaveQuit patch failed for " + item.DeclaringType?.Name + "." + item.Name + ": " + ex2.Message); } } logInfo?.Invoke($"SaveQuit auto-disconnect hooks active: {num}"); } private static bool LooksLikeSaveQuitMethod(MethodInfo method) { if (method == null || method.IsAbstract || method.IsGenericMethod || method.ContainsGenericParameters || method.ReturnType != typeof(void)) { return false; } string text = method.Name.ToLowerInvariant(); if (!text.Contains("save")) { return false; } if (!text.Contains("quit") && !text.Contains("exit") && !text.Contains("menu")) { return false; } return true; } private static void Postfix(MethodBase __originalMethod) { AtlyssArchipelagoPlugin instance = AtlyssArchipelagoPlugin.Instance; if (!((Object)(object)instance == (Object)null)) { string source = __originalMethod?.DeclaringType?.Name + "." + __originalMethod?.Name; instance.NotifySaveQuitTriggered(source); } } } [HarmonyPatch(typeof(SettingsManager), "Close_SettingsMenu")] internal class SettingsMenuClosePatch { private static void Prefix(SettingsManager __instance) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown if ((Object)GameObject.Find("_SettingsManager/Canvas_SettingsMenu/_dolly_settingsMenu/_dolly_apSettingsTab") != (Object)null) { AtlyssArchipelagoPlugin instance = AtlyssArchipelagoPlugin.Instance; Toggle component = GameObject.Find("_SettingsManager/Canvas_SettingsMenu/_dolly_settingsMenu/_dolly_apSettingsTab/_backdrop_gameTab/Scroll View_gameTab/Viewport_gameTab/Content_gameTab/_cell_fadeGameFeed/Toggle_fadeGameFeed").GetComponent(); if (component.isOn != instance.cfgDeathlink.Value) { instance.ToggleDeathLink(component.isOn); } instance.cfgDeathlink.Value = component.isOn; } } } [HarmonyPatch(typeof(SettingsManager), "Open_SettingsMenu")] internal class SettingsMenuOpenPatch { private const string SettingsRootPath = "_SettingsManager/Canvas_SettingsMenu/_dolly_settingsMenu"; private const string MainMenuNicknameInputPath = "_characterSelectMenu/Canvas_characterSelect/_dolly_characterManagement/_input_@nickname"; private static void Prefix(SettingsManager __instance) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Expected O, but got Unknown //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Expected O, but got Unknown //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Expected O, but got Unknown //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Expected O, but got Unknown //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_034b: Unknown result type (might be due to invalid IL or missing references) //IL_0365: Unknown result type (might be due to invalid IL or missing references) //IL_037f: Unknown result type (might be due to invalid IL or missing references) //IL_0428: Unknown result type (might be due to invalid IL or missing references) //IL_0432: Expected O, but got Unknown if ((Object)GameObject.Find("_SettingsManager/Canvas_SettingsMenu/_dolly_settingsMenu/_dolly_apSettingsTab") != (Object)null) { return; } Scene sceneByName = SceneManager.GetSceneByName("01_rootScene"); if (((Scene)(ref sceneByName)).isLoaded) { bool flag = false; if ((Object)GameObject.Find("_GameUI_MainMenu") == (Object)null) { flag = true; ((Component)((Component)MainMenuManager._current).gameObject.transform.parent).gameObject.SetActive(true); } AtlyssArchipelagoPlugin instance = AtlyssArchipelagoPlugin.Instance; GameObject obj = Object.Instantiate(GameObject.Find("_SettingsManager/Canvas_SettingsMenu/_dolly_settingsMenu/_dolly_gameSettings")); ((Object)obj).name = "_dolly_apSettingsTab"; obj.transform.SetParent(GameObject.Find("_SettingsManager/Canvas_SettingsMenu/_dolly_settingsMenu").transform, false); Transform val = obj.transform.Find("_backdrop_gameTab/Scroll View_gameTab/Viewport_gameTab/Content_gameTab"); ((Component)((Component)val).transform.Find("_header_gameSettings/Text")).GetComponent().text = "Archipelago Settings"; Transform child = ((Component)val).transform.GetChild(1); Transform child2 = ((Component)val).transform.GetChild(2); Transform child3 = ((Component)val).transform.GetChild(3); Transform child4 = ((Component)val).transform.GetChild(21); ((Component)child4).GetComponentInChildren().text = "DeathLink"; ((Component)child4).GetComponent()._setToggle = null; Object.Destroy((Object)((Component)((Component)child).transform.GetChild(1)).gameObject); Object.Destroy((Object)((Component)((Component)child2).transform.GetChild(1)).gameObject); Object.Destroy((Object)((Component)((Component)child3).transform.GetChild(1)).gameObject); ((Component)child).GetComponentInChildren().text = "Archipelago Server"; ((Component)child2).GetComponentInChildren().text = "Slot Name"; ((Component)child3).GetComponentInChildren().text = "Password"; ((Component)((Component)val).transform.Find("_header_nametagSettings/Text")).GetComponent().text = "Press F5 at any time to connect!"; for (int num = val.childCount - 3; num >= 5; num--) { Object.Destroy((Object)((Component)val.GetChild(num)).gameObject); } Transform val2 = CreateSettingsInput("Input_apServer"); Transform val3 = CreateSettingsInput("Input_apSlot"); Transform val4 = CreateSettingsInput("Input_apPassword"); ((Component)val2).transform.SetParent(((Component)child).transform, false); ((Component)val3).transform.SetParent(((Component)child2).transform, false); ((Component)val4).transform.SetParent(((Component)child3).transform, false); ((Component)val2).transform.localPosition = new Vector3(180f, 0f, 0f); ((Component)val3).transform.localPosition = new Vector3(180f, 0f, 0f); ((Component)val4).transform.localPosition = new Vector3(180f, 0f, 0f); instance.apServer = ((Component)val2).GetComponent(); instance.apSlot = ((Component)val3).GetComponent(); instance.apPassword = ((Component)val4).GetComponent(); instance.apPassword.contentType = (ContentType)7; instance.apPassword.asteriskChar = '*'; instance.apPassword.ForceLabelUpdate(); instance.apDeathlink = ((Component)((Component)child4).transform.Find("Toggle_fadeGameFeed")).gameObject.GetComponent(); ((Text)instance.apServer.placeholder).text = "archipelago.gg:38281"; ((Text)instance.apSlot.placeholder).text = "ATLYSSPlayer"; ((Text)instance.apPassword.placeholder).text = "supersecret"; ApplyConfigDefaults(instance); GameObject obj2 = Object.Instantiate(GameObject.Find("_SettingsManager/Canvas_SettingsMenu/_dolly_settingsMenu/_dolly_tabButtons/Button_gameTab")); ((Object)obj2).name = "Button_apTab"; obj2.GetComponentInChildren().text = "Archipelago"; GameObject val5 = GameObject.Find("_SettingsManager/Canvas_SettingsMenu/_dolly_settingsMenu/_dolly_tabButtons"); obj2.transform.SetParent(val5.transform, false); ((HorizontalOrVerticalLayoutGroup)val5.GetComponent()).childScaleWidth = true; ((HorizontalOrVerticalLayoutGroup)val5.GetComponent()).childControlWidth = true; ((LayoutGroup)val5.GetComponent()).padding.right = 8; val5.SetActive(false); instance.ReenableSettingsTabs(); ((UnityEvent)obj2.GetComponent