using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml.Serialization; using Archipelago.MultiClient.Net; using Archipelago.MultiClient.Net.BounceFeatures.DeathLink; using Archipelago.MultiClient.Net.Converters; using Archipelago.MultiClient.Net.Enums; using Archipelago.MultiClient.Net.Helpers; using Archipelago.MultiClient.Net.MessageLog.Messages; using Archipelago.MultiClient.Net.Models; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GlobalEnums; using GlobalSettings; using HarmonyLib; using HutongGames.PlayMaker; using HutongGames.PlayMaker.Actions; using InControl; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; using SilksongRandomizer; using SilksongRandomizer.Patches; using TMProOld; using TeamCherry.NestedFadeGroup; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("SilksongRandomizer")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.4.2.0")] [assembly: AssemblyInformationalVersion("0.4.2")] [assembly: AssemblyProduct("SilksongRandomizer")] [assembly: AssemblyTitle("SilksongRandomizer")] [assembly: AssemblyVersion("0.4.2.0")] internal static class HeroControllerWallJumpPatch { [HarmonyPatch(typeof(HeroController), "TryQueueWallJumpInterrupt")] private static class TryQueueWallJumpInterruptPatch { private static void Prefix(PlayerData ___playerData, out bool __state) { Apply(___playerData, out __state); } private static void Postfix(PlayerData ___playerData, bool __state) { Restore(___playerData, __state); } } [HarmonyPatch(typeof(HeroController), "IsFacingNearSlideableWall")] private static class IsFacingNearSlideableWallPatch { private static void Prefix(PlayerData ___playerData, out bool __state) { Apply(___playerData, out __state); } private static void Postfix(PlayerData ___playerData, bool __state) { Restore(___playerData, __state); } } [HarmonyPatch(typeof(HeroController), "CanWallJump", new Type[] { typeof(bool) })] private static class CanWallJumpPatch { private static void Prefix(PlayerData ___playerData, out bool __state) { Apply(___playerData, out __state); } private static void Postfix(PlayerData ___playerData, bool __state) { Restore(___playerData, __state); } } [HarmonyPatch(typeof(HeroController), "CanWallScramble")] private static class CanWallScramblePatch { private static void Prefix(PlayerData ___playerData, out bool __state) { Apply(___playerData, out __state); } private static void Postfix(PlayerData ___playerData, bool __state) { Restore(___playerData, __state); } } private static void Apply(PlayerData playerData, out bool oldValue) { oldValue = playerData.hasWalljump; SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Skill)) { playerData.hasWalljump = instance.canWallJump; } } private static void Restore(PlayerData playerData, bool oldValue) { playerData.hasWalljump = oldValue; } } namespace SilksongRandomizer { public sealed class Archipelago { private sealed class HintScoutChannel { internal readonly SemaphoreSlim Gate = new SemaphoreSlim(1, 1); internal volatile bool Poisoned; } public const string GoalLocationName = "Goal"; public const string ActTwoGoal = "act_2"; public const string ActThreeGoal = "act_3"; public const string FleaHuntGoal = "flea_hunt"; public const int DefaultFleaHuntGoalCount = 20; public const int MinimumFleaHuntGoalCount = 1; public const int MaximumFleaHuntGoalCount = 30; public const string RosaryMultiplierVanilla = "x1"; public const string RosaryMultiplierOneAndHalf = "x1_5"; public const string RosaryMultiplierDouble = "x2"; public const string RosaryMultiplierTriple = "x3"; public const string BellwayAccessBellBeastRequired = "bell_beast_required"; public const string BellwayAccessRandomizedStations = "randomized_stations"; public const string StartingLocationVanilla = "vanilla"; public const string StartingLocationBoneBottom = "bone_bottom"; public const string PriceModeVanilla = "vanilla"; public const string PriceModeFree = "free"; public const string PriceModeShuffled = "shuffled"; public const string PriceModeCheap = "cheap"; public const string PriceModeExpensive = "expensive"; private readonly object stateLock = new object(); private readonly string configuredGameName; private readonly HashSet unlockedItems = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly List receivedItems = new List(); private readonly HashSet unlockedLocationNames = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly HashSet unlockedLocationIds = new HashSet(); private readonly HashSet pendingLocationNames = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly HashSet roomLocationNames = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly Dictionary> pendingHints = new Dictionary>(StringComparer.OrdinalIgnoreCase); private HintScoutChannel hintScoutChannel = new HintScoutChannel(); private const int HintScoutTimeoutMilliseconds = 10000; private ArchipelagoSession session; private int lastQueuedItemIndex; private SaveState queuedForSaveState; private bool goalStatusPending; private volatile bool sessionReady; public static Archipelago Instance { get; private set; } public bool Connected { get { if (sessionReady) { return IsConnected(); } return false; } } public string RoomSeed { get; private set; } = string.Empty; public string SlotName { get; private set; } = string.Empty; public int Team { get; private set; } = -1; public int Slot { get; private set; } = -1; public string WorldVersion { get; private set; } = string.Empty; public string Goal { get; private set; } = string.Empty; public int FleaHuntGoalCount { get; private set; } = 20; public string StartingLocation { get; private set; } = "vanilla"; public string StartingCrest { get; private set; } = string.Empty; public bool SplitDashAndSprint { get; private set; } public bool RandomizeNeedleUpgrades { get; private set; } public bool StartWithMaps { get; private set; } public bool AutomaticCompass { get; private set; } public CheckMapMarkerMode CheckMapMarkers { get; private set; } public string BellwayAccess { get; private set; } = "bell_beast_required"; public string EnemyRosaryMultiplier { get; private set; } = "x1"; public string EnemyShardMultiplier { get; private set; } = "x1"; public string NormalShopPrices { get; private set; } = "vanilla"; public string BellwayPrices { get; private set; } = "vanilla"; public string MapPrices { get; private set; } = "vanilla"; public string PinPrices { get; private set; } = "vanilla"; public string UpgradePrices { get; private set; } = "vanilla"; public string DonationPrices { get; private set; } = "vanilla"; public IReadOnlyDictionary PurchasePrices { get; private set; } = new ReadOnlyDictionary(new Dictionary(StringComparer.Ordinal)); public bool FasterDialogue { get; private set; } public bool DeathLink { get; private set; } public bool SilkLink { get; private set; } public bool RosaryLink { get; private set; } public bool ShellShardLink { get; private set; } public bool IndividualRelicTurnIns { get; private set; } public bool LogicAuditMode { get; private set; } public string MapLogicPayloadJson { get; private set; } = string.Empty; public RandomizationMode SkillRandomization { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode ToolRandomization { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode SilkSkillRandomization { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode CrestRandomization { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode FleaRandomization { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode CrestSlotRandomization { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode MaskShardRandomization { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode SpoolFragmentRandomization { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode SilkHeartRandomization { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode BellwayRandomization { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode VentricaRandomization { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode MapRandomization { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode MelodyRandomization { get; private set; } public RandomizationMode PinRandomization { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode RelicRandomization { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode CraftingKitRandomization { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode MinorPickupRandomization { get; private set; } public RandomizationMode SimpleKeyRandomization { get; private set; } public RandomizationMode MemoryLocketRandomization { get; private set; } public RandomizationMode CraftmetalRandomization { get; private set; } public RandomizationMode MossberryRandomization { get; private set; } public RandomizationMode PollipHeartRandomization { get; private set; } public RandomizationMode SilkeaterRandomization { get; private set; } public RandomizationMode MajorKeyRandomization { get; private set; } public RandomizationMode ToolPouchRandomization { get; private set; } public RandomizationMode BossSanity { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode BellShrineSanity { get; private set; } = RandomizationMode.Anywhere; public RandomizationMode QuestSanity { get; private set; } = RandomizationMode.Anywhere; public string LastError { get; private set; } = string.Empty; public event Action OnItemReceived; public event Action OnItemSent; public event Action ConnectionStatusChanged; public Archipelago(string gameName = "Hollow Knight: Silksong") { configuredGameName = (string.IsNullOrWhiteSpace(gameName) ? "Hollow Knight: Silksong" : gameName); Instance = this; } public bool Connect(string ip, int port, string slot, string pass) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown if (string.IsNullOrWhiteSpace(ip) || string.IsNullOrWhiteSpace(slot)) { LastError = "Host and slot are required."; return false; } Disconnect(); ClearState(); LastError = string.Empty; try { session = ArchipelagoSessionFactory.CreateSession(ip, port); session.Items.ItemReceived += new ItemReceivedHandler(HandleItemReceived); session.MessageLog.OnMessageReceived += new MessageReceivedHandler(HandleMessageReceived); session.Locations.CheckedLocationsUpdated += new CheckedLocationsUpdatedHandler(OnCheckedLocationsUpdated); session.Socket.SocketClosed += new SocketClosedHandler(OnSocketClosed); session.Socket.ErrorReceived += new ErrorReceivedHandler(OnSocketError); LoginResult val = session.TryConnectAndLogin(configuredGameName, slot, (ItemsHandlingFlags)7, new Version(0, 6, 0), new string[1] { "AP" }, (string)null, pass, true); if (val == null || !val.Successful) { LoginFailure val2 = (LoginFailure)(object)((val is LoginFailure) ? val : null); LastError = ((val2 != null && val2.Errors != null && val2.Errors.Length != 0) ? string.Join("; ", val2.Errors) : "The Archipelago server rejected the login."); Disconnect(); return false; } LoginSuccessful val3 = (LoginSuccessful)(object)((val is LoginSuccessful) ? val : null); RoomSeed = ((session.RoomState == null) ? string.Empty : (session.RoomState.Seed ?? string.Empty)); SlotName = ((session.Players.ActivePlayer == (PlayerInfo)null || string.IsNullOrWhiteSpace(session.Players.ActivePlayer.Name)) ? slot : session.Players.ActivePlayer.Name); Team = ((val3 == null) ? session.ConnectionInfo.Team : val3.Team); Slot = ((val3 == null) ? session.ConnectionInfo.Slot : val3.Slot); WorldVersion = GetWorldVersion(val3); if (!IsSupportedWorldVersion(WorldVersion)) { string text = "APWorld version '" + (string.IsNullOrWhiteSpace(WorldVersion) ? "missing" : WorldVersion) + "' is incompatible with plugin 0.4.2."; Disconnect(); LastError = text; ReportStatus(text); return false; } string goal = GetGoal(val3); if (!IsSupportedGoal(goal)) { string text2 = "APWorld goal '" + (string.IsNullOrWhiteSpace(goal) ? "missing" : goal) + "' is invalid; expected 'act_2', 'act_3', or 'flea_hunt'."; Disconnect(); LastError = text2; ReportStatus(text2); return false; } string startingCrest = GetStartingCrest(val3); if (!CrestNames.IsSupportedStartingCrestKey(startingCrest)) { string text3 = "APWorld starting crest '" + (string.IsNullOrWhiteSpace(startingCrest) ? "missing" : startingCrest) + "' is invalid; expected one of: " + string.Join(", ", CrestNames.SupportedStartingCrestKeys) + "."; Disconnect(); LastError = text3; ReportStatus(text3); return false; } string startingLocation = GetStartingLocation(val3); if (!IsSupportedStartingLocation(startingLocation)) { string text4 = "APWorld starting location '" + (string.IsNullOrWhiteSpace(startingLocation) ? "missing" : startingLocation) + "' is invalid; expected 'vanilla' or 'bone_bottom'."; Disconnect(); LastError = text4; ReportStatus(text4); return false; } Goal = goal; FleaHuntGoalCount = GetIntegerSlotData(val3, "flea_hunt_count", 20, 1, 30); StartingLocation = startingLocation; StartingCrest = startingCrest; SplitDashAndSprint = GetBooleanSlotData(val3, "split_dash_and_sprint", defaultValue: false); RandomizeNeedleUpgrades = GetBooleanSlotData(val3, "randomize_needle_upgrades", defaultValue: false); StartWithMaps = GetBooleanSlotData(val3, "start_with_maps", defaultValue: false); AutomaticCompass = GetBooleanSlotData(val3, "automatic_compass", defaultValue: false); CheckMapMarkers = GetCheckMapMarkerMode(val3); BellwayAccess = GetBellwayAccess(val3); EnemyRosaryMultiplier = GetEnemyRosaryMultiplier(val3); EnemyShardMultiplier = GetEnemyShardMultiplier(val3); NormalShopPrices = GetPurchasePriceMode(val3, "normal_shop_prices"); BellwayPrices = GetPurchasePriceMode(val3, "bellway_prices"); MapPrices = GetPurchasePriceMode(val3, "map_prices"); PinPrices = GetPurchasePriceMode(val3, "pin_prices"); UpgradePrices = GetPurchasePriceMode(val3, "upgrade_prices"); DonationPrices = GetPurchasePriceMode(val3, "donation_prices"); PurchasePrices = GetPurchasePrices(val3); FasterDialogue = GetBooleanSlotData(val3, "faster_dialogue", defaultValue: false); DeathLink = GetBooleanSlotData(val3, "death_link", defaultValue: false); SilkLink = GetBooleanSlotData(val3, "silk_link", defaultValue: false); RosaryLink = GetBooleanSlotData(val3, "rosary_link", defaultValue: false); ShellShardLink = GetBooleanSlotData(val3, "shell_shard_link", defaultValue: false); IndividualRelicTurnIns = GetBooleanSlotData(val3, "individual_relic_turn_ins", defaultValue: false); LogicAuditMode = GetBooleanSlotData(val3, "logic_audit_mode", defaultValue: false); SkillRandomization = GetRandomizationModeSlotData(val3, "skill_randomization", RandomizationMode.Anywhere); ToolRandomization = GetRandomizationModeSlotData(val3, "tool_randomization", RandomizationMode.Anywhere); SilkSkillRandomization = GetRandomizationModeSlotData(val3, "silk_skill_randomization", RandomizationMode.Anywhere); CrestRandomization = GetRandomizationModeSlotData(val3, "crest_randomization", RandomizationMode.Anywhere); FleaRandomization = GetRandomizationModeSlotData(val3, "flea_randomization", RandomizationMode.Anywhere); CrestSlotRandomization = GetRandomizationModeSlotData(val3, "crest_slot_randomization", RandomizationMode.Anywhere); MaskShardRandomization = GetRandomizationModeSlotData(val3, "mask_shard_randomization", RandomizationMode.Anywhere); SpoolFragmentRandomization = GetRandomizationModeSlotData(val3, "spool_fragment_randomization", RandomizationMode.Anywhere); SilkHeartRandomization = GetRandomizationModeSlotData(val3, "silk_heart_randomization", RandomizationMode.Anywhere); BellwayRandomization = GetRandomizationModeSlotData(val3, "bellway_randomization", RandomizationMode.Anywhere); VentricaRandomization = GetRandomizationModeSlotData(val3, "ventrica_randomization", RandomizationMode.Anywhere); MapRandomization = GetRandomizationModeSlotData(val3, "map_randomization", RandomizationMode.Anywhere); MelodyRandomization = GetRandomizationModeSlotData(val3, "melody_randomization", RandomizationMode.Vanilla); PinRandomization = GetRandomizationModeSlotData(val3, "pin_randomization", RandomizationMode.Anywhere); RelicRandomization = GetRandomizationModeSlotData(val3, "relic_randomization", RandomizationMode.Anywhere); CraftingKitRandomization = GetRandomizationModeSlotData(val3, "crafting_kit_randomization", RandomizationMode.Anywhere); MinorPickupRandomization = GetRandomizationModeSlotData(val3, "minor_pickup_randomization", RandomizationMode.Vanilla); SimpleKeyRandomization = GetRandomizationModeSlotData(val3, "simple_key_randomization", RandomizationMode.Vanilla); MemoryLocketRandomization = GetRandomizationModeSlotData(val3, "memory_locket_randomization", RandomizationMode.Vanilla); CraftmetalRandomization = GetRandomizationModeSlotData(val3, "craftmetal_randomization", RandomizationMode.Vanilla); MossberryRandomization = GetRandomizationModeSlotData(val3, "mossberry_randomization", RandomizationMode.Vanilla); PollipHeartRandomization = GetRandomizationModeSlotData(val3, "pollip_heart_randomization", RandomizationMode.Vanilla); SilkeaterRandomization = GetRandomizationModeSlotData(val3, "silkeater_randomization", RandomizationMode.Vanilla); MajorKeyRandomization = GetRandomizationModeSlotData(val3, "major_key_randomization", RandomizationMode.Vanilla); ToolPouchRandomization = GetRandomizationModeSlotData(val3, "tool_pouch_randomization", RandomizationMode.Vanilla); BossSanity = GetRandomizationModeSlotData(val3, "boss_sanity", RandomizationMode.Anywhere); BellShrineSanity = GetRandomizationModeSlotData(val3, "bell_shrine_sanity", RandomizationMode.Anywhere); QuestSanity = GetRandomizationModeSlotData(val3, "quest_sanity", RandomizationMode.Anywhere); MapLogicPayloadJson = GetMapLogicPayloadJson(val3); CaptureRoomLocations(); if (SaveState.Instance != null && SaveState.Instance.IsRoomBound && !SaveState.Instance.MatchesRoom(this)) { string roomMismatchMessage = SaveState.Instance.GetRoomMismatchMessage(this); Disconnect(); LastError = roomMismatchMessage; ReportStatus(roomMismatchMessage); return false; } RefreshReceivedItems(); RefreshCheckedLocations(); return true; } catch (Exception ex) { LastError = ex.Message; Disconnect(); ReportStatus("Connection failed: " + ex.Message); return false; } } public bool IsItemUnlocked(string itemName) { if (string.IsNullOrWhiteSpace(itemName)) { return false; } string canonicalItemName = ItemSet.GetCanonicalItemName(itemName); lock (stateLock) { return unlockedItems.Contains(canonicalItemName); } } public void UnlockItem(string itemName) { AddReceivedItem(itemName, raiseEvent: true); } public IReadOnlyList GetAllReceivedItems() { lock (stateLock) { return receivedItems.ToArray(); } } public IReadOnlyDictionary GetReceivedItemCounts() { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); try { if (IsConnected()) { ArchipelagoSession obj = session; object obj2; if (obj == null) { obj2 = null; } else { IReceivedItemsHelper items = obj.Items; obj2 = ((items != null) ? items.AllItemsReceived : null); } if (obj2 != null) { foreach (ItemInfo item in session.Items.AllItemsReceived) { string itemName = GetItemName(item); if (!string.IsNullOrWhiteSpace(itemName)) { dictionary.TryGetValue(itemName, out var value); dictionary[itemName] = value + 1; } } return dictionary; } } } catch (Exception ex) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Could not read repeated AP item counts for map logic; using the saved inventory: " + ex.Message)); } } lock (stateLock) { foreach (string receivedItem in receivedItems) { dictionary[receivedItem] = 1; } return dictionary; } } public bool ValidateLoadedSave(SaveState saveState, out string error) { error = string.Empty; if (saveState == null || !IsConnected() || !saveState.IsRoomBound || saveState.MatchesRoom(this)) { return true; } error = saveState.GetRoomMismatchMessage(this); return false; } public void SynchronizeSaveState() { SaveState instance = SaveState.Instance; if (instance == null || !Connected) { return; } if (instance.IsRoomBound && !instance.MatchesRoom(this)) { LastError = instance.GetRoomMismatchMessage(this); ReportStatus(LastError); return; } if (!instance.IsRoomBound) { instance.BindToRoom(this); } instance.SetRoomLocationNames(GetRoomLocationNames()); instance.mapLogicPayloadJson = MapLogicPayloadJson ?? string.Empty; instance.logicAuditMode = LogicAuditMode; if (LogicAuditMode) { instance.bellhomePhaseToggleUnlocked = true; } instance.TryCompleteFleaHuntGoal(); string[] array; lock (stateLock) { array = unlockedLocationNames.ToArray(); } string[] array2 = array; foreach (string text in array2) { instance.checkedLocations.Add(text); if (string.Equals(text, "Goal", StringComparison.OrdinalIgnoreCase)) { instance.goalCompleted = true; } } lock (stateLock) { foreach (string checkedLocation in instance.checkedLocations) { if (!unlockedLocationNames.Contains(checkedLocation)) { pendingLocationNames.Add(checkedLocation); } } } QueueUnprocessedReceivedItems(); if (instance.goalCompleted) { goalStatusPending = true; } } public void Resynchronize() { SynchronizeSaveState(); FlushPendingLocations(); } public bool CompleteConnectionSync() { if (!IsConnected()) { LastError = "The Archipelago socket closed during login."; return false; } SaveState instance = SaveState.Instance; if (instance != null && instance.IsRoomBound && !instance.MatchesRoom(this)) { string roomMismatchMessage = instance.GetRoomMismatchMessage(this); Disconnect(); LastError = roomMismatchMessage; ReportStatus(roomMismatchMessage); return false; } sessionReady = true; try { session.SetClientState((ArchipelagoClientState)20); Resynchronize(); if (!DeathLinkManager.Configure(session, SlotName, DeathLink)) { throw new InvalidOperationException("DeathLink could not be initialized."); } if (!SilkLinkManager.Configure(session, SilkLink)) { throw new InvalidOperationException("Silk Link could not be initialized."); } if (!CurrencyLinkManager.Configure(session, RosaryLink, ShellShardLink)) { throw new InvalidOperationException("Rosary/Shell Shard links could not be initialized."); } ReportStatus("Connected to " + SlotName + " on seed " + RoomSeed + "."); return true; } catch (Exception ex) { string text = "Connection synchronization failed: " + ex.Message; Disconnect(); LastError = text; ReportStatus(text); return false; } } public void ResetReceivedItemQueueCursor() { lock (stateLock) { queuedForSaveState = null; lastQueuedItemIndex = 0; } } public void UnlockLocation(string locationName) { if (string.IsNullOrWhiteSpace(locationName)) { return; } lock (stateLock) { if (unlockedLocationNames.Contains(locationName)) { if (string.Equals(locationName, "Goal", StringComparison.OrdinalIgnoreCase)) { SendGoalStatus(); } return; } } if (!Connected) { lock (stateLock) { pendingLocationNames.Add(locationName); return; } } long locationId = GetLocationId(locationName); if (locationId < 0) { return; } try { session.Locations.CompleteLocationChecks(new long[1] { locationId }); MarkLocationUnlocked(locationName, locationId); if (string.Equals(locationName, "Goal", StringComparison.OrdinalIgnoreCase)) { goalStatusPending = true; SendGoalStatus(); } } catch (Exception ex) { lock (stateLock) { pendingLocationNames.Add(locationName); } LastError = "Could not send location '" + locationName + "': " + ex.Message; } } public bool IsLocationUnlocked(string locationName) { if (string.IsNullOrWhiteSpace(locationName)) { return false; } lock (stateLock) { if (unlockedLocationNames.Contains(locationName)) { return true; } } if (!Connected) { return false; } long locationId = GetLocationId(locationName); if (locationId < 0) { return false; } bool num = session.Locations.AllLocationsChecked.Contains(locationId); if (num) { MarkLocationUnlocked(locationName, locationId); } return num; } public bool IsLocationInRoom(string locationName) { if (string.IsNullOrWhiteSpace(locationName)) { return false; } string canonicalLocationName = LocationSet.GetCanonicalLocationName(locationName); lock (stateLock) { return roomLocationNames.Contains(canonicalLocationName); } } public IReadOnlyList GetRoomLocationNames() { lock (stateLock) { return roomLocationNames.ToArray(); } } public void Disconnect() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown sessionReady = false; DeathLinkManager.Reset(); SilkLinkManager.Reset(); CurrencyLinkManager.Reset(); ArchipelagoSession val = session; session = null; if (val == null) { return; } try { val.Items.ItemReceived -= new ItemReceivedHandler(HandleItemReceived); } catch { } try { val.MessageLog.OnMessageReceived -= new MessageReceivedHandler(HandleMessageReceived); } catch { } try { val.Locations.CheckedLocationsUpdated -= new CheckedLocationsUpdatedHandler(OnCheckedLocationsUpdated); } catch { } try { val.Socket.SocketClosed -= new SocketClosedHandler(OnSocketClosed); val.Socket.ErrorReceived -= new ErrorReceivedHandler(OnSocketError); } catch { } try { if (val.Socket != null && val.Socket.Connected) { val.Socket.DisconnectAsync(); } } catch { } } private void HandleItemReceived(ReceivedItemsHelper helper) { if (helper == null) { return; } try { while (helper.Any()) { ItemInfo item = helper.DequeueItem(); MarkItemUnlocked(item, raiseEvent: true); } if (sessionReady) { QueueUnprocessedReceivedItems(); } } catch (Exception ex) { LastError = "Failed to process received items: " + ex.Message; ReportStatus(LastError); } } private void HandleMessageReceived(LogMessage message) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_0105: Unknown result type (might be due to invalid IL or missing references) if (!sessionReady || message == null || ((object)message).GetType() != typeof(ItemSendLogMessage)) { return; } ItemSendLogMessage val = (ItemSendLogMessage)message; if (!val.IsSenderTheActivePlayer || val.IsReceiverTheActivePlayer || val.Receiver == (PlayerInfo)null || val.Item == null) { return; } string text = val.Item.ItemName; if (string.IsNullOrWhiteSpace(text)) { text = val.Item.ItemDisplayName; } if (string.IsNullOrWhiteSpace(text)) { text = "Item " + val.Item.ItemId; } string text2 = val.Receiver.Alias; if (string.IsNullOrWhiteSpace(text2)) { text2 = val.Receiver.Name; } if (string.IsNullOrWhiteSpace(text2)) { text2 = "Player " + val.Receiver.Slot; } Action action = this.OnItemSent; if (action == null) { return; } try { action("Sent " + text + " to " + text2, val.Item.Flags); } catch { } } private void OnCheckedLocationsUpdated(ReadOnlyCollection newCheckedLocations) { if (newCheckedLocations == null) { return; } foreach (long newCheckedLocation in newCheckedLocations) { string locationName = GetLocationName(newCheckedLocation); MarkLocationUnlocked(locationName, newCheckedLocation); } } private void RefreshReceivedItems() { if (!IsConnected()) { return; } foreach (ItemInfo item in session.Items.AllItemsReceived) { MarkItemUnlocked(item, raiseEvent: false); } } private void CaptureRoomLocations() { if (!IsConnected()) { return; } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (long allLocation in session.Locations.AllLocations) { string locationName = GetLocationName(allLocation); if (!string.IsNullOrWhiteSpace(locationName)) { hashSet.Add(locationName); } } lock (stateLock) { roomLocationNames.Clear(); roomLocationNames.UnionWith(hashSet); } } private void RefreshCheckedLocations() { if (!IsConnected()) { return; } foreach (long item in session.Locations.AllLocationsChecked) { string locationName = GetLocationName(item); MarkLocationUnlocked(locationName, item); } } private void FlushPendingLocations() { if (!Connected) { return; } string[] array; lock (stateLock) { array = pendingLocationNames.ToArray(); } List> list = new List>(); string[] array2 = array; foreach (string text in array2) { long locationId = GetLocationId(text); if (locationId >= 0) { list.Add(new KeyValuePair(text, locationId)); } } if (list.Count > 0) { try { session.Locations.CompleteLocationChecks(list.Select((KeyValuePair entry) => entry.Value).ToArray()); foreach (KeyValuePair item in list) { MarkLocationUnlocked(item.Key, item.Value); } } catch (Exception ex) { LastError = "Offline checks remain queued: " + ex.Message; ReportStatus(LastError); } } if (goalStatusPending || (SaveState.Instance != null && SaveState.Instance.goalCompleted)) { SendGoalStatus(); } } private void QueueUnprocessedReceivedItems() { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) if (!Connected || SaveState.Instance == null || (Object)(object)RandomizerPlugin.Instance == (Object)null || (SaveState.Instance.IsRoomBound && !SaveState.Instance.MatchesRoom(this))) { return; } ReadOnlyCollection allItemsReceived = session.Items.AllItemsReceived; List> list = new List>(); lock (stateLock) { SaveState instance = SaveState.Instance; if (instance == null) { return; } if (queuedForSaveState != instance) { queuedForSaveState = instance; lastQueuedItemIndex = instance.receivedItemIndex; } for (int i = Math.Max(instance.receivedItemIndex, lastQueuedItemIndex); i < allItemsReceived.Count; i++) { list.Add(Tuple.Create(i, GetItemName(allItemsReceived[i]), allItemsReceived[i].Flags)); } lastQueuedItemIndex = Math.Max(lastQueuedItemIndex, allItemsReceived.Count); } foreach (Tuple item in list) { RandomizerPlugin.Instance.QueueReceivedItem(item.Item1, item.Item2, item.Item3); } } private void SendGoalStatus() { if (!Connected) { goalStatusPending = true; return; } try { session.SetGoalAchieved(); goalStatusPending = false; } catch (Exception ex) { goalStatusPending = true; LastError = "Goal completion is queued for reconnect: " + ex.Message; ReportStatus(LastError); } } private void MarkItemUnlocked(ItemInfo item, bool raiseEvent) { AddReceivedItem(GetItemName(item), raiseEvent); } private string GetItemName(ItemInfo item) { if (item == null) { return null; } string text = item.ItemName; if (string.IsNullOrWhiteSpace(text)) { text = item.ItemDisplayName; } if (string.IsNullOrWhiteSpace(text)) { text = "Item " + item.ItemId; } return ItemSet.GetCanonicalItemName(text); } private void AddReceivedItem(string itemName, bool raiseEvent) { if (string.IsNullOrWhiteSpace(itemName)) { return; } Action action = null; string canonicalItemName = ItemSet.GetCanonicalItemName(itemName); lock (stateLock) { if (!unlockedItems.Add(canonicalItemName)) { return; } receivedItems.Add(canonicalItemName); if (raiseEvent) { action = this.OnItemReceived; } } if (action == null) { return; } try { action(canonicalItemName); } catch { } } private void MarkLocationUnlocked(string locationName, long locationId) { lock (stateLock) { if (!string.IsNullOrWhiteSpace(locationName)) { unlockedLocationNames.Add(locationName); pendingLocationNames.Remove(locationName); } if (locationId >= 0) { unlockedLocationIds.Add(locationId); } } } private long GetLocationId(string locationName) { if (!Connected || string.IsNullOrWhiteSpace(locationName)) { return -1L; } string[] roomLocationNameCandidates = LocationSet.GetRoomLocationNameCandidates(locationName); foreach (string text in roomLocationNameCandidates) { try { long locationIdFromName = session.Locations.GetLocationIdFromName(GetGameName(), text); if (locationIdFromName >= 0) { return locationIdFromName; } } catch { } } return -1L; } private string GetLocationName(long locationId) { if (!IsConnected() || locationId < 0) { return null; } try { return LocationSet.GetCanonicalLocationName(session.Locations.GetLocationNameFromId(locationId, GetGameName())); } catch { return null; } } private string GetGameName() { if (session != null && session.ConnectionInfo != null && !string.IsNullOrWhiteSpace(session.ConnectionInfo.Game)) { return session.ConnectionInfo.Game; } return configuredGameName; } private bool IsConnected() { if (session != null && session.Socket != null) { return session.Socket.Connected; } return false; } private static string GetWorldVersion(LoginSuccessful login) { if (login == null || login.SlotData == null || !login.SlotData.TryGetValue("world_version", out var value) || value == null) { return string.Empty; } return Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty; } private static string GetGoal(LoginSuccessful login) { if (login == null || login.SlotData == null || !login.SlotData.TryGetValue("goal", out var value) || value == null) { return string.Empty; } return Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty; } private static string GetStartingCrest(LoginSuccessful login) { if (login == null || login.SlotData == null || !login.SlotData.TryGetValue("starting_crest", out var value) || value == null) { return string.Empty; } return Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty; } private static string GetStartingLocation(LoginSuccessful login) { if (login == null || login.SlotData == null || !login.SlotData.TryGetValue("starting_location", out var value) || value == null) { return string.Empty; } return (Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty).Trim().ToLowerInvariant(); } private static string GetMapLogicPayloadJson(LoginSuccessful login) { if (login == null || login.SlotData == null) { return string.Empty; } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); string[] array = new string[4] { "easy_skips", "requirements", "abstract_requirements", "logic_item_dependencies" }; foreach (string key in array) { if (login.SlotData.TryGetValue(key, out var value) && value != null) { dictionary[key] = value; } } if (!dictionary.ContainsKey("requirements") || !dictionary.ContainsKey("abstract_requirements") || !dictionary.ContainsKey("logic_item_dependencies")) { return string.Empty; } return JsonConvert.SerializeObject((object)dictionary); } private static bool GetBooleanSlotData(LoginSuccessful login, string key, bool defaultValue) { if (login == null || login.SlotData == null || !login.SlotData.TryGetValue(key, out var value) || value == null) { return defaultValue; } if (value is bool) { return (bool)value; } string text = Convert.ToString(value, CultureInfo.InvariantCulture); if (bool.TryParse(text, out var result)) { return result; } if (long.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { return result2 != 0; } return defaultValue; } private static int GetIntegerSlotData(LoginSuccessful login, string key, int defaultValue, int minimum, int maximum) { if (login == null || login.SlotData == null || !login.SlotData.TryGetValue(key, out var value) || value == null) { return defaultValue; } string text = Convert.ToString(value, CultureInfo.InvariantCulture); if (!int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result < minimum || result > maximum) { throw new FormatException("APWorld setting '" + key + "' must be between " + minimum + " and " + maximum + "; received '" + text + "'."); } return result; } private static RandomizationMode GetRandomizationModeSlotData(LoginSuccessful login, string key, RandomizationMode defaultValue) { if (login == null || login.SlotData == null || !login.SlotData.TryGetValue(key, out var value) || value == null) { return defaultValue; } if (value is bool) { if (!(bool)value) { return RandomizationMode.Vanilla; } return RandomizationMode.Anywhere; } string text = Convert.ToString(value, CultureInfo.InvariantCulture); if (string.IsNullOrWhiteSpace(text)) { throw new FormatException("APWorld setting '" + key + "' is empty."); } string text2 = text.Trim().ToLowerInvariant().Replace("-", "_") .Replace(" ", "_"); if (bool.TryParse(text2, out var result)) { if (!result) { return RandomizationMode.Vanilla; } return RandomizationMode.Anywhere; } if (long.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { if (result2 >= 0 && result2 <= 2) { return (RandomizationMode)result2; } throw new FormatException("APWorld setting '" + key + "' must be 0 (vanilla), 1 (anywhere), or 2 (shuffle)."); } switch (text2) { case "vanilla": case "off": return RandomizationMode.Vanilla; case "anywhere": return RandomizationMode.Anywhere; case "shuffle": case "shuffle_within_category": case "within_category": return RandomizationMode.Shuffle; default: throw new FormatException("APWorld setting '" + key + "' has invalid mode '" + text + "'."); } } private static CheckMapMarkerMode GetCheckMapMarkerMode(LoginSuccessful login) { if (login == null || login.SlotData == null || !login.SlotData.TryGetValue("check_map_markers", out var value) || value == null) { return CheckMapMarkerMode.Off; } string text = Convert.ToString(value, CultureInfo.InvariantCulture); string text2 = (string.IsNullOrWhiteSpace(text) ? string.Empty : text.Trim().ToLowerInvariant().Replace("-", "_") .Replace(" ", "_")); if (long.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result >= 0 && result <= 3) { return (CheckMapMarkerMode)result; } return text2 switch { "off" => CheckMapMarkerMode.Off, "mapped_rooms" => CheckMapMarkerMode.MappedRooms, "owned_maps" => CheckMapMarkerMode.OwnedMaps, "all" => CheckMapMarkerMode.All, _ => throw new FormatException("APWorld setting 'check_map_markers' has invalid mode '" + text + "'."), }; } private static string GetEnemyRosaryMultiplier(LoginSuccessful login) { return GetEnemyDropMultiplier(login, "enemy_rosary_multiplier", "Rosary"); } private static string GetPurchasePriceMode(LoginSuccessful login, string key) { if (login == null || login.SlotData == null || !login.SlotData.TryGetValue(key, out var value) || value == null) { return "vanilla"; } string text = Convert.ToString(value, CultureInfo.InvariantCulture); string text2 = (string.IsNullOrWhiteSpace(text) ? string.Empty : text.Trim().ToLowerInvariant().Replace("-", "_") .Replace(" ", "_")); if (long.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { long num = result; if ((ulong)num <= 4uL) { switch (num) { case 0L: text2 = "vanilla"; break; case 1L: text2 = "free"; break; case 2L: text2 = "shuffled"; break; case 3L: text2 = "cheap"; break; case 4L: text2 = "expensive"; break; } } } if (IsSupportedPurchasePriceMode(text2)) { return text2; } throw new FormatException("APWorld setting '" + key + "' has invalid price mode '" + text + "'."); } private static IReadOnlyDictionary GetPurchasePrices(LoginSuccessful login) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if (login == null || login.SlotData == null || !login.SlotData.TryGetValue("purchase_prices", out var value) || value == null) { return new ReadOnlyDictionary(dictionary); } JObject val; try { val = (JObject)(((value is JObject) ? value : null) ?? JObject.FromObject(value)); } catch (Exception innerException) { throw new FormatException("APWorld setting 'purchase_prices' is not an object.", innerException); } foreach (JProperty item in val.Properties()) { string text = item.Name ?? string.Empty; if (!IsSupportedPurchasePriceKey(text) || !int.TryParse(Convert.ToString(item.Value, CultureInfo.InvariantCulture), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result < 0 || result > 2000) { throw new FormatException("APWorld purchase price '" + text + "' has invalid value '" + ((object)item.Value)?.ToString() + "'."); } dictionary.Add(text, result); } return new ReadOnlyDictionary(dictionary); } private static string GetEnemyShardMultiplier(LoginSuccessful login) { return GetEnemyDropMultiplier(login, "enemy_shard_multiplier", "Shell Shard"); } private static string GetEnemyDropMultiplier(LoginSuccessful login, string key, string displayName) { if (login == null || login.SlotData == null || !login.SlotData.TryGetValue(key, out var value) || value == null) { return "x1"; } string text = Convert.ToString(value, CultureInfo.InvariantCulture); string text2 = (string.IsNullOrWhiteSpace(text) ? string.Empty : text.Trim().ToLowerInvariant()); if (IsSupportedEnemyDropMultiplier(text2)) { return text2; } throw new FormatException("APWorld " + displayName + " setting '" + key + "' has invalid value '" + text + "'."); } private static string GetBellwayAccess(LoginSuccessful login) { if (login == null || login.SlotData == null || !login.SlotData.TryGetValue("bellway_access", out var value) || value == null) { return "bell_beast_required"; } string text = Convert.ToString(value, CultureInfo.InvariantCulture); string text2 = (string.IsNullOrWhiteSpace(text) ? string.Empty : text.Trim().ToLowerInvariant()); if (IsSupportedBellwayAccess(text2)) { return text2; } throw new FormatException("APWorld setting 'bellway_access' has invalid value '" + text + "'."); } internal static bool IsSupportedBellwayAccess(string value) { if (!string.Equals(value, "bell_beast_required", StringComparison.Ordinal)) { return string.Equals(value, "randomized_stations", StringComparison.Ordinal); } return true; } internal static bool IsSupportedEnemyRosaryMultiplier(string value) { return IsSupportedEnemyDropMultiplier(value); } internal static bool IsSupportedEnemyShardMultiplier(string value) { return IsSupportedEnemyDropMultiplier(value); } internal static bool IsSupportedPurchasePriceMode(string value) { if (!string.Equals(value, "vanilla", StringComparison.Ordinal) && !string.Equals(value, "free", StringComparison.Ordinal) && !string.Equals(value, "shuffled", StringComparison.Ordinal) && !string.Equals(value, "cheap", StringComparison.Ordinal)) { return string.Equals(value, "expensive", StringComparison.Ordinal); } return true; } private static bool IsSupportedPurchasePriceKey(string value) { if (!string.IsNullOrWhiteSpace(value)) { if (!value.StartsWith("shop:", StringComparison.Ordinal) && !value.StartsWith("bellway:", StringComparison.Ordinal) && !value.StartsWith("upgrade:plinney:", StringComparison.Ordinal)) { return value.StartsWith("donation:", StringComparison.Ordinal); } return true; } return false; } private static bool IsSupportedEnemyDropMultiplier(string value) { if (!string.Equals(value, "x1", StringComparison.Ordinal) && !string.Equals(value, "x1_5", StringComparison.Ordinal) && !string.Equals(value, "x2", StringComparison.Ordinal)) { return string.Equals(value, "x3", StringComparison.Ordinal); } return true; } internal static bool IsSupportedGoal(string goal) { if (!string.Equals(goal, "act_2", StringComparison.Ordinal) && !string.Equals(goal, "act_3", StringComparison.Ordinal)) { return string.Equals(goal, "flea_hunt", StringComparison.Ordinal); } return true; } internal static bool IsSupportedFleaHuntGoalCount(int count) { if (count >= 1) { return count <= 30; } return false; } internal static bool IsSupportedStartingLocation(string value) { if (!string.Equals(value, "vanilla", StringComparison.Ordinal)) { return string.Equals(value, "bone_bottom", StringComparison.Ordinal); } return true; } internal static bool IsSupportedWorldVersion(string worldVersion) { return string.Equals(worldVersion, "0.4.2", StringComparison.Ordinal); } private void OnSocketClosed(string reason) { sessionReady = false; DeathLinkManager.Reset(); SilkLinkManager.Reset(); CurrencyLinkManager.Reset(); LastError = (string.IsNullOrWhiteSpace(reason) ? "Disconnected from Archipelago." : ("Disconnected from Archipelago: " + reason)); ReportStatus(LastError); } private void OnSocketError(Exception exception, string message) { string text = ((!string.IsNullOrWhiteSpace(message)) ? message : ((exception == null) ? "Unknown socket error." : exception.Message)); LastError = "Archipelago network error: " + text; if (exception != null) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogError((object)("[RANDOMIZER] Archipelago socket error: " + exception)); } } ReportStatus(LastError); } private void ReportStatus(string status) { Action action = this.ConnectionStatusChanged; if (action == null) { return; } try { action(status); } catch { } } private void ClearState() { lock (stateLock) { unlockedItems.Clear(); receivedItems.Clear(); unlockedLocationNames.Clear(); unlockedLocationIds.Clear(); pendingLocationNames.Clear(); roomLocationNames.Clear(); pendingHints.Clear(); hintScoutChannel = new HintScoutChannel(); lastQueuedItemIndex = 0; queuedForSaveState = null; } RoomSeed = string.Empty; SlotName = string.Empty; Team = -1; Slot = -1; WorldVersion = string.Empty; Goal = string.Empty; FleaHuntGoalCount = 20; StartingLocation = "vanilla"; StartingCrest = string.Empty; SplitDashAndSprint = false; RandomizeNeedleUpgrades = false; StartWithMaps = false; AutomaticCompass = false; CheckMapMarkers = CheckMapMarkerMode.Off; BellwayAccess = "bell_beast_required"; EnemyRosaryMultiplier = "x1"; EnemyShardMultiplier = "x1"; NormalShopPrices = "vanilla"; BellwayPrices = "vanilla"; MapPrices = "vanilla"; PinPrices = "vanilla"; UpgradePrices = "vanilla"; DonationPrices = "vanilla"; PurchasePrices = new ReadOnlyDictionary(new Dictionary(StringComparer.Ordinal)); FasterDialogue = false; DeathLink = false; SilkLink = false; RosaryLink = false; ShellShardLink = false; IndividualRelicTurnIns = false; LogicAuditMode = false; MapLogicPayloadJson = string.Empty; SkillRandomization = RandomizationMode.Anywhere; ToolRandomization = RandomizationMode.Anywhere; SilkSkillRandomization = RandomizationMode.Anywhere; CrestRandomization = RandomizationMode.Anywhere; FleaRandomization = RandomizationMode.Anywhere; CrestSlotRandomization = RandomizationMode.Anywhere; MaskShardRandomization = RandomizationMode.Anywhere; SpoolFragmentRandomization = RandomizationMode.Anywhere; SilkHeartRandomization = RandomizationMode.Anywhere; BellwayRandomization = RandomizationMode.Anywhere; VentricaRandomization = RandomizationMode.Anywhere; MapRandomization = RandomizationMode.Anywhere; MelodyRandomization = RandomizationMode.Vanilla; PinRandomization = RandomizationMode.Anywhere; RelicRandomization = RandomizationMode.Anywhere; CraftingKitRandomization = RandomizationMode.Anywhere; MinorPickupRandomization = RandomizationMode.Vanilla; SimpleKeyRandomization = RandomizationMode.Vanilla; PollipHeartRandomization = RandomizationMode.Vanilla; ToolPouchRandomization = RandomizationMode.Vanilla; BossSanity = RandomizationMode.Anywhere; BellShrineSanity = RandomizationMode.Anywhere; QuestSanity = RandomizationMode.Anywhere; goalStatusPending = false; sessionReady = false; } public SaveState.HintData GetHint(string locationName) { if (!Connected || string.IsNullOrWhiteSpace(locationName)) { return null; } locationName = LocationSet.GetCanonicalLocationName(locationName); Task task = RequestHintAsync(locationName); if (task == null || !task.IsCompleted) { return null; } ReleaseHintRequest(locationName, task); if (task.IsCanceled || task.IsFaulted) { return null; } return task.GetAwaiter().GetResult(); } internal Task RequestHintAsync(string locationName) { if (!Connected || string.IsNullOrWhiteSpace(locationName)) { return null; } locationName = LocationSet.GetCanonicalLocationName(locationName); lock (stateLock) { if (!pendingHints.TryGetValue(locationName, out var value)) { value = FetchHintAsync(locationName, (HintCreationPolicy)2); pendingHints[locationName] = value; } return value; } } internal Task> RequestHintsAsync(IEnumerable locationNames, HintCreationPolicy hintCreationPolicy, string requestPurpose) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) return FetchHintsAsync(locationNames, hintCreationPolicy, requestPurpose); } internal void ReleaseHintRequest(string locationName, Task request) { if (request == null || !request.IsCompleted || string.IsNullOrWhiteSpace(locationName)) { return; } locationName = LocationSet.GetCanonicalLocationName(locationName); lock (stateLock) { if (pendingHints.TryGetValue(locationName, out var value) && value == request) { pendingHints.Remove(locationName); } } } private async Task FetchHintAsync(string locationName, HintCreationPolicy hintCreationPolicy) { //IL_001e: 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) Dictionary obj = await FetchHintsAsync(new string[1] { locationName }, hintCreationPolicy, "hint for " + locationName).ConfigureAwait(continueOnCapturedContext: false); string canonicalLocationName = LocationSet.GetCanonicalLocationName(locationName); SaveState.HintData value; return obj.TryGetValue(canonicalLocationName, out value) ? value : null; } private async Task> FetchHintsAsync(IEnumerable locationNames, HintCreationPolicy hintCreationPolicy, string requestPurpose) { //IL_001e: 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) Dictionary hints = new Dictionary(StringComparer.OrdinalIgnoreCase); if (!Connected || locationNames == null) { return hints; } HintScoutChannel scoutChannel = hintScoutChannel; if (scoutChannel.Poisoned) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Cannot scout " + requestPurpose + " because a previous scout timed out. Reconnect to Archipelago before retrying.")); } return hints; } bool gateAcquired = false; try { gateAcquired = await scoutChannel.Gate.WaitAsync(10000).ConfigureAwait(continueOnCapturedContext: false); if (!gateAcquired) { ManualLogSource log2 = RandomizerPlugin.Log; if (log2 != null) { log2.LogWarning((object)("[RANDOMIZER] Timed out waiting to scout " + requestPurpose + ".")); } return hints; } if (scoutChannel != hintScoutChannel || scoutChannel.Poisoned || !Connected) { return hints; } Dictionary locations = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (string locationName in locationNames) { string canonicalLocationName = LocationSet.GetCanonicalLocationName(locationName); if (string.IsNullOrWhiteSpace(canonicalLocationName) || locations.ContainsKey(canonicalLocationName)) { continue; } long locationId = GetLocationId(canonicalLocationName); if (locationId >= 0) { locations.Add(canonicalLocationName, locationId); continue; } ManualLogSource log3 = RandomizerPlugin.Log; if (log3 != null) { log3.LogWarning((object)("[RANDOMIZER] Could not resolve shop/hint location '" + canonicalLocationName + "' while scouting " + requestPurpose + ".")); } } if (locations.Count == 0) { return hints; } ArchipelagoSession requestSession = session; string requestSeed = RoomSeed; int requestTeam = Team; int requestSlot = Slot; Task> scoutTask = requestSession.Locations.ScoutLocationsAsync(hintCreationPolicy, locations.Values.Distinct().ToArray()); if (await Task.WhenAny(new Task[2] { scoutTask, Task.Delay(10000) }).ConfigureAwait(continueOnCapturedContext: false) != scoutTask) { scoutChannel.Poisoned = true; gateAcquired = false; ObservePoisonedScoutAsync(scoutChannel, scoutTask); ManualLogSource log4 = RandomizerPlugin.Log; if (log4 != null) { log4.LogWarning((object)("[RANDOMIZER] Timed out scouting " + requestPurpose + ". Reconnect to Archipelago before retrying so no scout responses can cross.")); } return hints; } Dictionary dictionary = await scoutTask.ConfigureAwait(continueOnCapturedContext: false); if (!IsSameHintSession(requestSession, requestSeed, requestTeam, requestSlot)) { return hints; } if (dictionary == null) { return hints; } foreach (KeyValuePair item in locations) { if (dictionary.TryGetValue(item.Value, out var value) && value != null) { hints[item.Key] = CreateHintData(item.Key, value); } } } catch (Exception ex) { ManualLogSource log5 = RandomizerPlugin.Log; if (log5 != null) { log5.LogWarning((object)("[RANDOMIZER] Failed to scout " + requestPurpose + ": " + ex.Message)); } } finally { if (gateAcquired) { scoutChannel.Gate.Release(); } } return hints; } private static async Task ObservePoisonedScoutAsync(HintScoutChannel scoutChannel, Task> scoutTask) { try { await scoutTask.ConfigureAwait(continueOnCapturedContext: false); } catch { } finally { scoutChannel.Gate.Release(); } } private bool IsSameHintSession(ArchipelagoSession requestSession, string requestSeed, int requestTeam, int requestSlot) { if (Connected && requestSession == session && string.Equals(requestSeed, RoomSeed, StringComparison.Ordinal) && requestTeam == Team) { return requestSlot == Slot; } return false; } private SaveState.HintData CreateHintData(string locationName, ScoutedItemInfo info) { //IL_00a1: 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) string text = (string.IsNullOrWhiteSpace(((ItemInfo)info).ItemName) ? ((ItemInfo)info).ItemDisplayName : ((ItemInfo)info).ItemName); if (info.Player != (PlayerInfo)null && string.Equals(info.Player.Game, configuredGameName, StringComparison.Ordinal)) { text = ItemSet.GetCanonicalItemName(text); } string user = null; if (info.Player != (PlayerInfo)null) { user = (string.IsNullOrWhiteSpace(info.Player.Alias) ? info.Player.Name : info.Player.Alias); } return new SaveState.HintData { locationName = locationName, user = user, item = text, flags = ((ItemInfo)info).Flags }; } } internal static class BeastShardSourceManifest { internal sealed class EnemyDropSource { internal readonly string LocationName; internal readonly string SceneName; internal readonly string HierarchyPath; internal EnemyDropSource(string locationName, string sceneName, string hierarchyPath) { LocationName = locationName; SceneName = sceneName; HierarchyPath = hierarchyPath; } } internal const string NativeItemName = "Great Shard"; internal const string CragglerLocation = "Beast Shard: Craggler"; internal const string SprintmasterLocation = "Beast Shard: Sprintmaster"; internal static readonly string[] LocationNames = new string[5] { "Beast Shard: Marrowmaw", "Beast Shard: Craggler", "Beast Shard: Pilgrim's Rest", "Beast Shard: Memorium", "Beast Shard: Sprintmaster" }; internal static readonly EnemyDropSource[] EnemyDropSources = new EnemyDropSource[4] { new EnemyDropSource("Beast Shard: Marrowmaw", "Tut_01", "Black Thread States Thread Only Variant/Normal World/Bone Thumper"), new EnemyDropSource("Beast Shard: Pilgrim's Rest", "Bone_East_10_Church", "Rhino Scene/Rhino"), new EnemyDropSource("Beast Shard: Pilgrim's Rest", "Bone_East_10_Room", "Black Thread States/Normal World/Rhino"), new EnemyDropSource("Beast Shard: Memorium", "Arborium_02", "Rhino Scene/Rhino") }; internal static IEnumerable AppendTo(IEnumerable existing) { HashSet names = new HashSet(StringComparer.OrdinalIgnoreCase); if (existing != null) { foreach (Location item in existing) { if (item != null) { names.Add(item.Name); yield return item; } } } string[] locationNames = LocationNames; foreach (string text in locationNames) { if (names.Add(text)) { yield return new Location(text, ItemType.Resource, () => false); } } } internal static bool TryGetEnemyDropLocation(HealthManager healthManager, SavedItem dropItem, int count, CollectableItemPickup customPickupPrefab, int limit, out string locationName) { locationName = null; if ((Object)(object)healthManager == (Object)null || (Object)(object)dropItem == (Object)null || !string.Equals(((Object)dropItem).name, "Great Shard", StringComparison.Ordinal) || count != 1 || (Object)(object)customPickupPrefab != (Object)null || limit != 0) { return false; } return TryGetEnemyDropLocation(healthManager, out locationName); } internal static bool TryGetEnemyDropLocation(HealthManager healthManager, out string locationName) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) locationName = null; if ((Object)(object)healthManager == (Object)null) { return false; } Scene scene = ((Component)healthManager).gameObject.scene; string name = ((Scene)(ref scene)).name; string hierarchyPath = Utils.GetHierarchyPath(((Component)healthManager).transform); EnemyDropSource enemyDropSource = null; EnemyDropSource[] enemyDropSources = EnemyDropSources; foreach (EnemyDropSource enemyDropSource2 in enemyDropSources) { if (string.Equals(name, enemyDropSource2.SceneName, StringComparison.OrdinalIgnoreCase) && string.Equals(hierarchyPath, enemyDropSource2.HierarchyPath, StringComparison.Ordinal)) { if (enemyDropSource != null && !string.Equals(enemyDropSource.LocationName, enemyDropSource2.LocationName, StringComparison.Ordinal)) { return false; } enemyDropSource = enemyDropSource2; } } if (enemyDropSource == null) { return false; } locationName = enemyDropSource.LocationName; return true; } internal static bool IsExpectedCraggler(EnemyDeathEffects deathEffects) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)deathEffects == (Object)null)) { Scene scene = ((Component)deathEffects).gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, "Crawl_04", StringComparison.OrdinalIgnoreCase) && string.Equals(Utils.GetHierarchyPath(((Component)deathEffects).transform), "Roof Crab", StringComparison.Ordinal) && string.Equals(deathEffects.setPlayerDataBool, "roofCrabDefeated", StringComparison.Ordinal)) { GameObject corpsePrefab = deathEffects.CorpsePrefab; if ((Object)(object)corpsePrefab == (Object)null || !string.Equals(NormalizeCloneName(((Object)corpsePrefab).name), "Corpse Roof Crab", StringComparison.Ordinal)) { return false; } CollectableItemPickup val = FindCragglerCorpsePickup(corpsePrefab); if ((Object)(object)val != (Object)null && (Object)(object)val.Item != (Object)null) { return string.Equals(((Object)val.Item).name, "Great Shard", StringComparison.Ordinal); } return false; } } return false; } internal static CollectableItemPickup FindCragglerCorpsePickup(GameObject corpseRoot) { if ((Object)(object)corpseRoot == (Object)null || !string.Equals(NormalizeCloneName(((Object)corpseRoot).name), "Corpse Roof Crab", StringComparison.Ordinal)) { return null; } Transform val = corpseRoot.transform.Find("Chunks/Collectable Item Pickup Instant"); if ((Object)(object)val == (Object)null) { return null; } return ((Component)val).GetComponent(); } internal static bool IsExpectedCragglerCorpsePickup(CollectableItemPickup pickup, SavedItem item) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)pickup == (Object)null) && !((Object)(object)item == (Object)null)) { Scene scene = ((Component)pickup).gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, "Crawl_04", StringComparison.OrdinalIgnoreCase) && string.Equals(((Object)item).name, "Great Shard", StringComparison.Ordinal)) { Transform transform = ((Component)pickup).transform; Transform parent = transform.parent; Transform val = (((Object)(object)parent == (Object)null) ? null : parent.parent); if (string.Equals(((Object)transform).name, "Collectable Item Pickup Instant", StringComparison.Ordinal) && (Object)(object)parent != (Object)null && string.Equals(((Object)parent).name, "Chunks", StringComparison.Ordinal) && (Object)(object)val != (Object)null) { return string.Equals(NormalizeCloneName(((Object)val).name), "Corpse Roof Crab", StringComparison.Ordinal); } return false; } } return false; } internal static bool IsExpectedSprintmasterTrackTwo(SprintRaceController controller, SavedItem reward, string raceEndEvent, string raceEndCompleteEvent) { //IL_002b: 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) if ((Object)(object)controller != (Object)null && (Object)(object)reward != (Object)null && string.Equals(((Object)reward).name, "Great Shard", StringComparison.Ordinal)) { Scene scene = ((Component)controller).gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, "Sprintmaster_Cave", StringComparison.OrdinalIgnoreCase) && string.Equals(Utils.GetHierarchyPath(((Component)controller).transform), "Race Group/Tracks/Track 2/Track 2 Race Control", StringComparison.Ordinal) && controller.LapCount == 3 && string.Equals(raceEndEvent, "RACE ENDED", StringComparison.Ordinal)) { return string.Equals(raceEndCompleteEvent, "RACE END COMPLETE", StringComparison.Ordinal); } } return false; } private static string NormalizeCloneName(string value) { if (string.IsNullOrEmpty(value) || !value.EndsWith("(Clone)", StringComparison.Ordinal)) { return value ?? string.Empty; } return value.Substring(0, value.Length - "(Clone)".Length); } } internal sealed class CardiniusCylinderTurnInEntry { internal readonly string AssetName; internal readonly string LocationName; internal CardiniusCylinderTurnInEntry(string assetName, string relicDisplayName) { AssetName = assetName; LocationName = "Relic Turn-in: " + relicDisplayName; } } internal static class CardiniusCylinderTurnInManifest { internal const string SceneName = "Library_08"; internal const string OwnerObjectName = "Librarian"; internal static readonly CardiniusCylinderTurnInEntry[] Entries = new CardiniusCylinderTurnInEntry[6] { new CardiniusCylinderTurnInEntry("Psalm Cylinder Library Roof", "Psalm Cylinder (East Whispering Vaults)"), new CardiniusCylinderTurnInEntry("Librarian Melody Cylinder", "Sacred Cylinder"), new CardiniusCylinderTurnInEntry("Psalm Cylinder Ward", "Psalm Cylinder (Underworks)"), new CardiniusCylinderTurnInEntry("Psalm Cylinder Librarian", "Psalm Cylinder (Vaultkeeper Cardinius)"), new CardiniusCylinderTurnInEntry("Psalm Cylinder Hang", "Psalm Cylinder (High Halls)"), new CardiniusCylinderTurnInEntry("Psalm Cylinder Grindle", "Psalm Cylinder (Grindle)") }; private static readonly Dictionary LocationsByAsset = BuildLocationsByAsset(); private static readonly HashSet LocationNames = BuildLocationNames(); internal static IEnumerable AppendTo(IEnumerable existingLocations) { List list = new List(existingLocations); CardiniusCylinderTurnInEntry[] entries = Entries; foreach (CardiniusCylinderTurnInEntry cardiniusCylinderTurnInEntry in entries) { CardiniusCylinderTurnInEntry capturedEntry = cardiniusCylinderTurnInEntry; list.Add(new Location(capturedEntry.LocationName, ItemType.Event, () => IsDeposited(capturedEntry.AssetName))); } return list.ToArray(); } internal static bool IsTurnInLocation(string locationName) { if (!string.IsNullOrWhiteSpace(locationName)) { return LocationNames.Contains(LocationSet.GetCanonicalLocationName(locationName)); } return false; } internal static bool TryGetLocationName(string assetName, out string locationName) { if (string.IsNullOrWhiteSpace(assetName)) { locationName = null; return false; } return LocationsByAsset.TryGetValue(assetName, out locationName); } internal static bool IsDeposited(string assetName) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) SaveState instance = SaveState.Instance; if (instance == null || !instance.individualRelicTurnIns || (Object)(object)ManagerSingleton.Instance == (Object)null || !LocationsByAsset.ContainsKey(assetName)) { return false; } CollectableRelic relic = CollectableRelicManager.GetRelic(assetName); if ((Object)(object)relic != (Object)null) { return relic.SavedData.IsDeposited; } return false; } private static Dictionary BuildLocationsByAsset() { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); CardiniusCylinderTurnInEntry[] entries = Entries; foreach (CardiniusCylinderTurnInEntry cardiniusCylinderTurnInEntry in entries) { dictionary.Add(cardiniusCylinderTurnInEntry.AssetName, cardiniusCylinderTurnInEntry.LocationName); } return dictionary; } private static HashSet BuildLocationNames() { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); CardiniusCylinderTurnInEntry[] entries = Entries; foreach (CardiniusCylinderTurnInEntry cardiniusCylinderTurnInEntry in entries) { hashSet.Add(cardiniusCylinderTurnInEntry.LocationName); } return hashSet; } } public enum CheckMapMarkerMode { Off, MappedRooms, OwnedMaps, All } internal enum MapMarkerPositionConfidence { ExactUpstream, RoomCenter } internal sealed class MapCheckPosition { internal readonly string LocationName; internal readonly string SceneName; internal readonly Vector2 PositionInScene; internal readonly Vector2 SceneSize; internal readonly MapMarkerPositionConfidence Confidence; internal MapCheckPosition(string locationName, string sceneName, float positionX, float positionY, float sceneWidth, float sceneHeight, MapMarkerPositionConfidence confidence) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) LocationName = locationName; SceneName = sceneName; PositionInScene = new Vector2(positionX, positionY); SceneSize = new Vector2(sceneWidth, sceneHeight); Confidence = confidence; } } internal static class CheckMapMarkerManifest { private static readonly MapCheckPosition[] StaticPositions = new MapCheckPosition[397] { new MapCheckPosition("Skill Unlock: Double Jump", "Peak_08b", 279.5f, 105.53f, 336f, 138f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Skill Unlock: Silk Soar", "Abyss_08", 86.91002f, 9.861683f, 164f, 106f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Skill Unlock: Wall Jump", "Shellwood_10", 40.59f, 79.24f, 79f, 102f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Skill Unlock: Drifter's Cloak", "Bone_East_Umbrella", 27.63f, 8.37f, 55f, 26f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Skill Unlock: Dash", "Bone_East_05", 100.03f, 13.27f, 190f, 47f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Skill Unlock: Harpoon", "Under_18", 26.349998f, 13.23f, 160f, 50f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Skill Unlock: Needolin", "Belltown_Shrine", 54.09f, 21.1f, 95f, 38f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spell Unlock: Silk Spear", "Mosstown_02", 86.94f, 52.31f, 160f, 65f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spell Unlock: Thread Sphere", "Greymoor_22", 39.82f, 36.49f, 110f, 50f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Dead Mans Purse", "Crawl_01", 54.5f, 85.1f, 150f, 130f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Wisp Lantern", "Belltown_08", 54.55f, 11.31f, 115f, 47f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Harpoon", "Belltown_Room_shellwood", 37.023f, 7.07f, 55f, 26f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Straight Pin", "Bone_12", 41.13f, 27.81f, 58f, 43f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Pouch: Loddie", "Bone_12", 23.02f, 4.7f, 58f, 43f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Bone Necklace", "Bone_17", 12.701f, 6.477f, 35f, 25f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Flintstone", "Dock_02b", 26.57f, 103.541954f, 94f, 120f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Bell Bind", "Dock_03b", 154.09f, 107.27748f, 168f, 120f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Sprintmaster", "Bone_East_Weavehome", 124.243004f, 90.546005f, 205f, 120f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Beast Shard: Marrowmaw", "Tut_01", 101.23343f, 17.28621f, 120f, 120f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Beast Shard: Craggler", "Crawl_04", 83.52f, 14.89f, 165f, 20f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Beast Shard: Pilgrim's Rest", "Bone_East_10_Church", 139.32f, 10.97f, 200f, 30f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Beast Shard: Memorium", "Arborium_02", 111.04646f, 8.935061f, 132f, 29f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Beast Shard: Sprintmaster", "Sprintmaster_Cave", 88.93f, 14.12f, 236f, 51f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Tri Pin", "Greymoor_15b", 184.31999f, 100.362526f, 220f, 141f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Barbed Wire", "Dust_Barb", 28.95f, 4.32f, 45f, 32f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Maggot Charm", "Aqueduct_06", 126.24f, 8.177498f, 150f, 120f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Quick Sling", "Shadow_11", 7.5999994f, 84.27748f, 29f, 112f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Magnetite Dice", "Coral_33", 20.210007f, 60.29f, 58f, 74f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Conch Drill", "Coral_Tower_01", 80.87f, 7.3100038f, 138f, 22f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Screw Attack", "Under_14", 72.106285f, 6.7143073f, 115f, 25f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Quickbind", "Ward_03", 96.49f, 32.78f, 240f, 52f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Dazzle Bind", "Library_13", 73.61f, 13.23f, 124f, 56f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Dark Mirror", "Library_13", 73.76f, 13.23f, 124f, 56f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Silk Snare", "Weave_14", 66.53f, 6.716298f, 76f, 19f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Druid's Eye", "Mosstown_02", 157.5f, 34f, 160f, 65f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Druid's Eyes", "Mosstown_02", 157.5f, 34f, 160f, 65f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Curve Claws", "Ant_21", 44.78f, 76.93f, 138f, 89f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Curvesickle", "Bone_East_22", 46.03f, 4.81f, 59f, 35f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Fractured Mask", "Ant_Merchant", 20.9f, 15.19f, 152f, 30f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Lightning Rod", "Arborium_07", 154.27f, 10.54f, 180f, 22f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Revenge Crystal", "Bellway_Peak_02", 94.1f, 9.5f, 101f, 37f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Rosary Cannon", "Hang_06_bank", 10.7113f, 10.2359f, 129f, 60f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Weighted Anklet", "Bone_East_10_Room", 44.1f, 13.85f, 155f, 50f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: White Ring", "Weave_03", 14.42f, 19.34f, 284f, 35f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Unlock: Zap Imbuement", "Coral_29", 175.7869f, 23.21f, 302f, 92f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Ruined Tool", "Shadow_Weavehome", 76.642f, 54.577f, 177f, 68f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Weaver Totem Witch", "Shellwood_25", 281.47f, 26.277496f, 290f, 40f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Psalm Cylinder Library Roof", "Library_09", 25.541971f, 12.211693f, 160f, 106f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Bone Record Wisp Top", "Wisp_08", 10.18f, 114.57337f, 30f, 125f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Weaver Totem Bonetown_upper_room", "Bonetown", 132.73f, 62.52f, 315f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Librarian Melody Cylinder", "Library_10", 14.18f, 3.87f, 150f, 30f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Psalm Cylinder Ward", "Under_08", 67.42f, 47.48f, 135f, 58f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Psalm Cylinder Librarian", "Library_08", 93.69f, 30.277496f, 115f, 40f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Psalm Cylinder Hang", "Hang_10", 72.65701f, 15.277503f, 92f, 48f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Seal Chit Ward Corpse", "Ward_02b", 51.119972f, 3.9716048f, 151f, 31f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Weaver Record Sprint_Challenge", "Bone_East_Weavehome", 51.32f, 63.324635f, 205f, 120f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Weaver Record Weave_08", "Weave_08", 41.677265f, 54.125324f, 77f, 67f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Bone Record Understore_Map_Room", "Under_16", 71.36f, 15.277498f, 80f, 60f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Bone Record Bone_East_14", "Bone_East_14", 42.205887f, 40.277493f, 140f, 80f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Seal Chit Aspid_01", "Aspid_01", 63.31f, 65.27748f, 80f, 273f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Seal Chit Silk Siphon", "Ward_05", 133.04f, 4.2774987f, 140f, 23f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Bone Record Greymoor_flooded_corridor", "Greymoor_21", 72.17f, 8.28f, 80f, 25f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Weaver Totem Slab_Bottom", "Slab_12", 98.51f, 27.28058f, 110f, 40f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Ancient Egg Abyss Middle", "Abyss_04", 98.243355f, 50.34f, 115f, 81f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Pickup: Weaver Record Conductor", "Hang_12", 15.21f, 4.91f, 64f, 19f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Weaver Effigy (Keelal, Shellwood)", "Belltown", 63.138687f, 24.92f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Bone Scroll (Wisp Thicket)", "Belltown", 63.138687f, 24.92f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Weaver Effigy (Camora, Moss Grotto)", "Belltown", 63.138687f, 24.92f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Choral Commandment (Jubilana)", "Belltown", 63.138687f, 24.92f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Rune Harp (High Halls)", "Belltown", 63.138687f, 24.92f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Choral Commandment (Western Whiteward)", "Belltown", 63.138687f, 24.92f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Rune Harp (Weavenest Cindril)", "Belltown", 63.138687f, 24.92f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Rune Harp (Weavenest Atla)", "Belltown", 63.138687f, 24.92f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Bone Scroll (Underworks)", "Belltown", 63.138687f, 24.92f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Bone Scroll (Far Fields)", "Belltown", 63.138687f, 24.92f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Choral Commandment (Moss Grotto)", "Belltown", 63.138687f, 24.92f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Choral Commandment (Eastern Whiteward)", "Belltown", 63.138687f, 24.92f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Bone Scroll (Greymoor)", "Belltown", 63.138687f, 24.92f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Weaver Effigy (Atla, The Slab)", "Belltown", 63.138687f, 24.92f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Arcane Egg", "Belltown", 63.138687f, 24.92f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Psalm Cylinder (East Whispering Vaults)", "Library_08", 29.493046f, 11.731146f, 115f, 40f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Sacred Cylinder", "Library_08", 29.493046f, 11.731146f, 115f, 40f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Psalm Cylinder (Underworks)", "Library_08", 29.493046f, 11.731146f, 115f, 40f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Psalm Cylinder (Vaultkeeper Cardinius)", "Library_08", 29.493046f, 11.731146f, 115f, 40f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Psalm Cylinder (High Halls)", "Library_08", 29.493046f, 11.731146f, 115f, 40f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic Turn-in: Psalm Cylinder (Grindle)", "Library_08", 29.493046f, 11.731146f, 115f, 40f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Bell Shrine Completion: bellShrineBoneForest", "Bellshrine", 20.5f, 17.29f, 43f, 25f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Bell Shrine Completion: bellShrineWilds", "Bellshrine_05", 20.5f, 17.29f, 43f, 25f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Bell Shrine Completion: bellShrineGreymoor", "Bellshrine_02", 20.5f, 17.29f, 43f, 25f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Bell Shrine Completion: bellShrineShellwood", "Bellshrine_03", 20.5f, 17.29f, 43f, 25f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Bell Shrine Completion: bellShrineBellhart", "Belltown_Shrine", 54.09f, 21.1f, 95f, 38f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Bellshrine: Songclave", "Bellshrine_Enclave", 28.5f, 17.29f, 43f, 25f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pale Oil: Whispering Vaults", "Library_03", 8.18f, 41.53f, 117f, 64f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Rosary Necklace: Fleatopia", "Aqueduct_05", 152.783f, 16.2396f, 332f, 100f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Rosary String: Fleatopia", "Aqueduct_05", 154.043f, 15.9796f, 332f, 100f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pale Oil: Ecstasy of the End", "Aqueduct_05", 153.113f, 15.3196f, 332f, 100f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Sharpdart", "Crawl_05", 22.850002f, 16.25f, 225f, 35f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Rune Rage", "Slab_10b", 38.35f, 9.46f, 60f, 30f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pale Nails", "Cradle_03_Destroyed", 49.44f, 133.4f, 80f, 190f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Cross Stitch", "Organ_01", 77.44f, 104.24f, 160f, 229f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pimpillo", "Wisp_06", 20.02f, 4.46f, 35f, 19f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Cogfly", "Hang_09", 71.100494f, 35.713406f, 114f, 48f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Silkshot (Original)", "Peak_12", 18.079f, 7.2077f, 50f, 22f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tacks", "Dust_Shack", 22.413942f, 6.931353f, 38f, 30f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pollip Pouch", "Room_Witch", 16.78f, 8.36f, 33f, 19f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Needle Phial", "Crawl_08", 52.86f, 10.161187f, 105f, 22f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Plasmium Phial", "Crawl_08", 52.86f, 10.161187f, 105f, 22f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pin Badge", "Peak_07", 38.05f, 90.49f, 115f, 150f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Silkshot (Forge Daughter)", "Room_Forge", 114.19f, 34.540005f, 140f, 69f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Sting Shard", "Room_Forge", 114.19f, 34.540005f, 140f, 69f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Magma Bell", "Room_Forge", 114.19f, 34.540005f, 140f, 69f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Longclaw", "Room_Huntress", 24.24f, 10.43f, 35f, 25f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Cogwork Wheel", "Under_17", 76.82001f, 27.17063f, 164f, 48f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Sawtooth Circlet", "Under_17", 76.82001f, 27.17063f, 164f, 48f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Scuttlebrace", "Under_17", 76.82001f, 27.17063f, 164f, 48f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Silkshot (Twelfth Architect)", "Under_17", 76.82001f, 27.17063f, 164f, 48f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Magnetite Brooch", "Bonetown", 277.10223f, 8.04f, 315f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Multibinder", "Belltown", 55.43f, 7.88f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Thief's Mark", "Coral_42", 24.99f, 21.43f, 62f, 32f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Snitch Pick", "Coral_42", 24.99f, 21.43f, 62f, 32f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Throwing Ring", "Shadow_24", 270.89f, 9.647703f, 296f, 34f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Crest: Beast", "Ant_20", 183.736f, 32.79f, 222f, 48f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Crest: Wanderer", "Bonegrave", 197.97f, 6.73f, 315f, 82f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Crest: Reaper", "Greymoor_20b", 25f, 10f, 50f, 20f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Crest: Architect", "Under_17", 26.26f, 34.7f, 164f, 48f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Crest: Shaman", "Tut_03", 10.006f, 16.857f, 126f, 35f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Simple Key: Bone Bottom Shop", "Bonetown", 277.10223f, 8.04f, 315f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Simple Key: Songclave Shop", "Song_Enclave", 78.99f, 8.14f, 120f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Pilgrim's Rest Shop", "Bone_East_10_Room", 44.1f, 13.85f, 155f, 50f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Pouch: Pilgrim's Rest", "Bone_East_10_Room", 44.1f, 13.85f, 155f, 50f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Hunter's March", "Ant_20", 147.39f, 13.79f, 222f, 48f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Greymoor", "Greymoor_16", 130.9f, 51.48f, 171f, 60f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Halfway Home", "Halfway_01", 8.292786f, 13f, 56f, 32f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Bellhart Shop", "Belltown", 55.43f, 7.88f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Bellhart Roof", "Belltown", 59.168686f, 66.165f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Volatile Flintbeetles", "Bonetown", 285.65f, 9.38f, 315f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: The Marrow", "Bone_18", 38.15f, 20.51f, 49f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Choral Chambers", "Bellway_City", 67.57f, 24.29f, 112f, 36f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Wormways", "Crawl_09", 130.49f, 3.34f, 150f, 58f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Blasted Steps", "Coral_02", 202.32f, 43.56f, 245f, 80f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Underworks", "Under_08", 61.07f, 16.47f, 135f, 58f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Whispering Vaults", "Library_08", 105.34f, 33.48f, 115f, 40f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Bilewater West", "Shadow_20", 17.713f, 23.86f, 230f, 65f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Deep Docks", "Dock_13", 15.26f, 3.29f, 35f, 76f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Bilewater East", "Shadow_27", 195.09f, 11.76f, 205f, 25f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: The Slab", "Slab_Cell_Quiet", 42.441864f, 30.4f, 53f, 60f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Memorium", "Arborium_05", 3.95f, 7.27f, 132f, 29f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Far Fields (Act 3)", "Bone_East_25", 151.95251f, 6.346909f, 157f, 27f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Memory Locket: Sands of Karak", "Coral_23", 90.740005f, 50.31117f, 316f, 102f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Craftmetal: Bone Bottom Shop", "Bonetown", 277.10223f, 8.04f, 315f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Craftmetal: The Marrow", "Bone_07", 41.31f, 4.98f, 92f, 74f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Craftmetal: Deep Docks", "Dock_03", 7.7f, 81.89f, 168f, 120f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Craftmetal: Blasted Steps", "Coral_32", 74.51f, 9.06f, 170f, 115f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Craftmetal: Underworks", "Under_19b", 7.57f, 7.18f, 135f, 22f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Craftmetal: Putrified Ducts", "Aqueduct_05", 328.9f, 16.35f, 332f, 100f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Craftmetal: Wisp Thicket", "Wisp_05", 46.40615f, 59.93595f, 57f, 69f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Craftmetal: Songclave Shop", "Song_Enclave", 78.99f, 8.14f, 120f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mossberry: Moss Grotto #1", "Tut_01b", 87.787f, 93.797f, 160f, 99f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mossberry: Moss Grotto #2", "Tut_02", 138.147f, 53.087f, 150f, 59f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mossberry: Bone Bottom", "Bonetown", 282.34f, 49.3f, 315f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mossberry: Mosshome", "Bone_05b", 60.18f, 25.08f, 80f, 30f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mossberry: Bonegrave", "Bonegrave", 252.64f, 42.33f, 315f, 82f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mossberry: Weavenest Atla", "Weave_03", 164.057f, 30.487f, 284f, 35f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mossberry: Memorium", "Arborium_04", 69.597f, 14.627f, 132f, 34f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pollip Heart: Shellwood #1", "Shellwood_02", 75.52999f, 79.84f, 80f, 102f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pollip Heart: Shellwood #2", "Shellwood_20", 42.57f, 34.75f, 95f, 43f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pollip Heart: Shellwood #3", "Shellwood_10", 9.519998f, 27.259996f, 79f, 102f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pollip Heart: Shellwood #4", "Shellwood_26", 25.529402f, 84.39692f, 135f, 130f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pollip Heart: Shellwood #5", "Shellwood_15", 13.329997f, 6.369997f, 135f, 25f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pollip Heart: Shellwood #6", "Shellwood_01", 92.29507f, 83.4292f, 132f, 98f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Silkeater: Deep Docks", "Dock_14", 23.34f, 9.38f, 29f, 17f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Silkeater: Greymoor", "Greymoor_04", 19.42f, 145.69f, 40f, 170f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Silkeater: Blasted Steps", "Coral_37", 19.11007f, 12.7f, 80f, 21f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Silkeater: Exhaust Organ", "Organ_01", 135.53f, 40.37f, 160f, 229f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Silkeater: Choral Chambers West", "Song_24", 52.99f, 21.53f, 61f, 55f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Silkeater: Choral Chambers East", "Song_09b", 151.97f, 139.34f, 165f, 146f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Silkeater: Whispering Vaults", "Library_14", 54.51f, 17.26f, 65f, 30f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Silkeater: Whiteward", "Ward_04", 24.19633f, 22.264452f, 35f, 28f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Silkeater: The Cradle", "Tube_Hub", 20.35f, 8.78f, 135f, 145f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Key of Apostate", "Aqueduct_04", 7.570004f, 38.651806f, 185f, 60f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("White Key", "Song_Enclave", 102.076f, 6.29f, 120f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Surgeon's Key", "Ward_07", 13.134485f, 7.159246f, 130f, 20f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Architect's Key", "Under_17", 76.83148f, 27.93f, 164f, 48f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Craw Summons", "Room_CrowCourt_02", 19.41f, 44.277f, 70f, 92f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Item: Quill", "Bone_04", 56.81f, 14.64f, 233f, 31f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Compass", "Bone_04", 56.81f, 14.64f, 233f, 31f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Mosslands", "Bone_04", 56.81f, 14.64f, 233f, 31f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: The Marrow", "Bone_04", 56.81f, 14.64f, 233f, 31f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Deep Docks", "Bone_East_01", 16.99f, 8.7f, 47f, 83f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Far Fields", "Bone_East_21", 18.78f, 5.807f, 40f, 20f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Wormways", "Crawl_01", 30.91f, 58.43f, 150f, 130f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Hunter's March", "Ant_04_mid", 198.81174f, 15.244139f, 235f, 37f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Greymoor", "Greymoor_02", 67.98311f, 5.86352f, 90f, 150f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Bellhart", "Belltown", 102.26f, 22.85f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Shellwood", "Shellwood_16", 44.36f, 11.39f, 85f, 25f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Blasted Steps", "Coral_12", 67.79f, 8.82f, 94f, 78f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Sinner's Road", "Dust_10", 112.31f, 44.842865f, 160f, 80f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Mount Fay", "Peak_02", 61.16f, 9.64f, 100f, 212f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Sands of Karak", "Coral_40", 12.82f, 9.9f, 60f, 24f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Bilewater", "Shadow_23", 35.57f, 7.87f, 52f, 19f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Pickup: Weavenest Atla", "Weave_12", 14.018001f, 11.054f, 55f, 25f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Grand Gate", "Song_19_entrance", 40.1f, 7.008f, 75f, 109f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Pickup: Underworks", "Under_16", 65.09584f, 35.32624f, 80f, 60f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Choral Chambers", "Bellway_City", 102.59f, 10.04f, 112f, 36f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Choral Chambers", "Song_01b", 102.72004f, 2.859291f, 124f, 19f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Whispering Vaults", "Library_04", 10.27f, 205.83f, 67f, 220f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Whiteward", "Ward_01", 54.51f, 19.98f, 75f, 111f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Pickup: Cogwork Core", "Cog_Bench", 26.264f, 29.038498f, 45f, 40f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: Memorium", "Arborium_11", 13.35f, 11.16f, 222f, 61f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: High Halls", "Hang_06b", 51f, 3.067242f, 62f, 22f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Pickup: The Slab", "Slab_20", 86.57f, 14.07f, 94f, 24f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Pickup: Putrified Ducts", "Aqueduct_07", 28.93f, 28.98f, 41f, 40f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: The Cradle", "Cradle_02", 54.08f, 67.16782f, 81f, 110f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Purchase: The Cradle", "Tube_Hub", 27.036f, 29.396002f, 135f, 145f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Pickup: Verdania", "Clover_20", 133.55f, 16.277496f, 150f, 47f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Map Pickup: The Abyss", "Abyss_12", 13.940001f, 26.83f, 33f, 40f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pin Purchase: Bench Pins", "Bone_04", 56.81f, 14.64f, 233f, 31f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pin Purchase: Bellway Pins", "Bone_04", 56.81f, 14.64f, 233f, 31f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pin Purchase: Vendor Pins", "Bone_East_01", 16.99f, 8.7f, 47f, 83f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pin Purchase: Ventrica Pins", "Belltown", 102.26f, 22.85f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spider Strings", "Song_Enclave", 78.99f, 8.14f, 120f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Ascendant's Grip", "Song_Enclave", 78.99f, 8.14f, 120f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Extender", "Song_Enclave", 78.99f, 8.14f, 120f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Reserve Bind", "Coral_42", 24.99f, 21.43f, 62f, 32f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Flea Brew", "Greymoor_08", 37.95f, 5.030001f, 161f, 38f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Egg of Flealia", "Aqueduct_05", 123.043f, 9.847813f, 332f, 100f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Crest: Witch", "Wisp_03", 50.790268f, 5.916079f, 179f, 47f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic: Choral Commandment (Jubilana)", "Song_Enclave", 78.99f, 8.14f, 120f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Relic: Psalm Cylinder (Grindle)", "Coral_42", 24.99f, 21.43f, 62f, 32f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Crafting Kit: Forge Daughter", "Room_Forge", 114.19f, 34.540005f, 140f, 69f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Crafting Kit: Twelfth Architect", "Under_17", 76.83148f, 27.930002f, 164f, 48f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Crafting Kit: Grindle", "Coral_42", 24.99f, 21.43f, 62f, 32f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Crafting Kit: Crawbug Clearing (Creige)", "Belltown", 48.45767f, 5.752009f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Pebb (Bone Bottom) / Grindle (Act 3)", "Bonetown", 277.10223f, 8.04f, 315f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Wormways", "Crawl_02", 22.23158f, 4.839001f, 30f, 150f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Far Fields (Above the Seamstress)", "Bone_East_20", 95.13f, 12.184626f, 150f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Shellwood", "Shellwood_14", 127.89f, 12.671935f, 135f, 19f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: The Marrow - Deep Docks Passage", "Dock_08", 50.17f, 22.04f, 110f, 35f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Weavenest Atla", "Weave_05b", 159.06729f, 28.71f, 221f, 43f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Savage Beastfly", "Belltown", 48.45767f, 5.752009f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Cogwork Core", "Song_09", 41.56f, 43.8546f, 50f, 85f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Whispering Vaults", "Library_05", 16.839998f, 71.22f, 49f, 79f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Bilewater", "Shadow_13", 309.27338f, 63.556236f, 320f, 74f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Far Fields (Skull Cave)", "Bone_East_LavaChallenge", 23.47f, 277.85f, 29f, 289f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: The Slab (Key of the Apostate)", "Slab_17", 19.240002f, 69.740005f, 81f, 77f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Mount Fay", "Peak_04c", 67.63f, 27.43f, 105f, 48f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Wisp Thicket", "Wisp_07", 251.41f, 24.9f, 290f, 39f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Jubilana (Songclave)", "Song_Enclave", 78.99f, 8.14f, 120f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Blasted Steps", "Coral_19b", 74.86f, 109.02f, 118f, 117f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Fastest in Pharloom", "Sprintmaster_Cave", 88.93f, 14.12f, 236f, 51f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: The Hidden Hunter", "Belltown", 48.45767f, 5.752009f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Dark Hearts", "Belltown", 48.45767f, 5.752009f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Mask Shard: Brightvein", "Peak_06", 28.12f, 226.3f, 51f, 233f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: Bone Bottom", "Bone_11b", 31.37f, 7.241133f, 52f, 18f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: Deep Docks (Central)", "Bone_East_13", 41.74f, 26.61509f, 128f, 35f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: Greymoor", "Greymoor_02", 30.44f, 138.69f, 90f, 150f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: The Slab", "Peak_01", 84.44f, 192.71f, 100f, 300f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: Weavenest Atla", "Weave_11", 13.3f, 15.09f, 130f, 32f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: Frey (Bellhart)", "Belltown", 55.43f, 7.88f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: Flea Caravan", "Greymoor_08", 37.95f, 5.030001f, 161f, 38f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: Cogwork Core", "Cog_07", 83.57f, 16.28f, 90f, 87f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: Underworks (East)", "Library_11b", 23.08f, 7.95f, 115f, 158f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: Grand Gate", "Song_19_entrance", 21.12f, 94.9f, 75f, 109f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: Underworks (Gauntlet)", "Under_10", 22.22f, 9.43f, 80f, 20f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: Whiteward", "Ward_01", 28.696068f, 7.392422f, 75f, 111f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: Balm for the Wounded", "Ward_09", 60.68f, 5.33f, 150f, 18f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: Deep Docks (Southeast)", "Dock_03c", 131.06f, 55.95f, 163f, 80f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: High Halls", "Hang_03_top", 14.79f, 160.62f, 30f, 170f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: Memorium", "Arborium_09", 19.04f, 33.07f, 66f, 112f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: Grindle (Blasted Steps)", "Coral_42", 24.99f, 21.43f, 62f, 32f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Spool Fragment: Jubilana (Songclave)", "Song_Enclave", 78.99f, 8.14f, 120f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Moss Mother", "Tut_03", 54.773903f, 25.76f, 126f, 35f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Bell Beast", "Bone_05", 93.285164f, 7.283544f, 201f, 22f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Silk Heart: Bell Beast", "Bone_05", 85.27516f, 8.813543f, 201f, 22f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Skull Tyrant (Bone Bottom)", "Bonetown", 313.03f, 9.82f, 315f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Lace (Deep Docks)", "Bone_East_12", 97.39f, 7.55f, 180f, 32f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Fourth Chorus", "Bone_East_08", 80.85f, 7.46f, 150f, 50f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Forebrothers Signis & Gron", "Dock_09", 30.46f, 14.275f, 77f, 35f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Moorwing", "Greymoor_05", 50.65f, 41.6f, 110f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Shrine Guardian Seth", "Shellwood_22", 104.8f, 6.81f, 182f, 28f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Disgraced Chef Lugoli", "Dust_Chef", 41.77355f, 49.95f, 60f, 74f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Phantom", "Organ_01", 77.44f, 104.24f, 160f, 229f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Groal the Great", "Shadow_18", 60.77f, 16.1f, 117f, 45f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Watcher at the Edge", "Coral_39", 126.11f, 8.44f, 192f, 43f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Lace (Cradle)", "Song_Tower_01", 59.193787f, 100.518f, 130f, 140f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Silk Heart: The Unravelled", "Ward_02", 50.76f, 14f, 150f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Silk Heart: Lace (Cradle)", "Song_Tower_01", 54.61f, 106.97f, 130f, 140f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Bone Bottom Repairs", "Bonetown", 285.65f, 9.38f, 315f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: A Lifesaving Bridge", "Bonetown", 285.65f, 9.38f, 315f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: An Icon of Hope", "Bonetown", 285.65f, 9.38f, 315f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Garb of the Pilgrims", "Bonetown", 285.65f, 9.38f, 315f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Volatile Flintbeetles", "Bonetown", 285.65f, 9.38f, 315f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: The Terrible Tyrant", "Bonetown", 285.65f, 9.38f, 315f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Restoration of Bellhart", "Belltown", 48.45767f, 5.752009f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Bellhart's Glory", "Belltown", 48.45767f, 5.752009f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Hero's Call", "Belltown", 48.45767f, 5.752009f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Silver Bells", "Belltown", 48.45767f, 5.752009f, 109f, 75f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Fine Pins", "Song_Enclave", 70.555756f, 4.579541f, 120f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Cloaks of the Choir", "Song_Enclave", 70.555756f, 4.579541f, 120f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Building Up Songclave", "Song_Enclave", 70.555756f, 4.579541f, 120f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Strengthening Songclave", "Song_Enclave", 70.555756f, 4.579541f, 120f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Pinmaster's Oil", "Belltown_Room_pinsmith", 25.81f, 8.36f, 55f, 26f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Advanced Alchemy", "Crawl_08", 52.86f, 10.161187f, 105f, 22f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Bugs of Pharloom", "Halfway_01", 25.15f, 20.850695f, 56f, 32f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Pouch: Bugs of Pharloom", "Halfway_01", 25.15f, 20.850695f, 56f, 32f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Passing of the Age", "Aqueduct_05", 238.39f, 84.8f, 332f, 100f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: The Wandering Merchant", "Song_07", 43.815228f, 5.911398f, 80f, 24f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: The Lost Merchant", "Arborium_11", 129.82f, 12.19f, 222f, 61f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: My Missing Courier", "Aspid_01", 27.02f, 112.16f, 80f, 273f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: My Missing Brother", "Dust_04", 21.46f, 69.48f, 110f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Balm for the Wounded", "Ward_09", 60.68f, 5.33f, 150f, 18f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: A Vassal Lost", "Coral_37", 65.67003f, 8.64f, 80f, 21f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Bone Bottom Supplies", "Bonetown", 290.81998f, 7.37f, 315f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Queen's Egg", "Dust_11", 104.38f, 9.72f, 140f, 40f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Survivor's Camp Supplies", "Bone_10", 30.779999f, 15.329999f, 115f, 69f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Fleatopia Supplies", "Aqueduct_05", 123.043f, 9.847813f, 332f, 100f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Tool Pouch: Fleatopia", "Aqueduct_05", 123.043f, 9.847813f, 332f, 100f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Liquid Lacquer", "Peak_Mask_Maker", 13.68f, 7.78f, 33f, 30f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Pilgrim's Rest Supplies", "Bone_East_10_Room", 44.1f, 13.85f, 155f, 50f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Songclave Supplies", "Song_Enclave", 59.92f, 7.64f, 120f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Needle Strike", "Room_Pinstress", 28.42f, 8.804682f, 50f, 26f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Bellway: Deep Docks", "Bellway_02", 83.71f, 16.146082f, 120f, 50f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Bellway: Far Fields", "Bellway_03", 69.34f, 8.15f, 150f, 60f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Bellway: Greymoor", "Bellway_04", 69.46f, 8.13f, 95f, 30f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Bellway: Bellhart", "Belltown_basement", 28.28f, 95.24f, 95f, 135f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Bellway: Blasted Steps", "Bellway_08", 94.31765f, 12.225822f, 150f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Bellway: Grand Bellway", "Bellway_City", 39.873043f, 10.18f, 112f, 36f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Bellway: The Slab", "Slab_06", 44.530003f, 5.35f, 101f, 35f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Bellway: Shellwood", "Shellwood_19", 57.94f, 5.14f, 127f, 19f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Bellway: Bilewater", "Bellway_Shadow", 51.493725f, 21.130001f, 75f, 41f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Bellway: Putrified Ducts", "Bellway_Aqueduct", 51.493725f, 21.130001f, 96f, 78f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Ventrica: Choral Chambers", "Song_01b", 48.010056f, 6.577774f, 124f, 19f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Ventrica: Underworks", "Under_22", 66.209175f, 6.59f, 81f, 20f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Ventrica: Grand Bellway", "Bellway_City", 81.66f, 13.62f, 112f, 36f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Ventrica: High Halls", "Hang_06b", 31.501278f, 6.61f, 62f, 22f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Ventrica: Songclave", "Song_Enclave_Tube", 16.076864f, 8.58f, 52f, 22f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Ventrica: Memorium", "Arborium_Tube", 16.076864f, 8.58f, 32f, 22f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pinmaster Plinney: Sharpened Needle", "Belltown_Room_pinsmith", 25.81f, 8.36f, 55f, 26f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pinmaster Plinney: Shining Needle", "Belltown_Room_pinsmith", 25.81f, 8.36f, 55f, 26f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pinmaster Plinney: Hivesteel Needle", "Belltown_Room_pinsmith", 25.81f, 8.36f, 55f, 26f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pinmaster Plinney: Pale Steel Needle", "Belltown_Room_pinsmith", 25.81f, 8.36f, 55f, 26f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pale Oil: Great Taste of Pharloom", "Song_09b", 113.60339f, 133.17f, 165f, 146f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Pristine Core: Cogwork Core", "Cog_07", 28.23f, 77.69f, 90f, 87f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Crawfather", "Room_CrowCourt_02", 32.389004f, 28.914f, 70f, 92f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Sister Splinter", "Shellwood_18", 45.96f, 16.88f, 140f, 35f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Nyleth", "Shellwood_11b", 17.22f, 149.44f, 120f, 170f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Crust King Khann", "Coral_Tower_01", 80f, 10f, 138f, 22f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Great Conchflies", "Coral_11", 54.72697f, 25.63091f, 180f, 31f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Last Judge", "Coral_Judge_Arena", 31.8f, 36f, 62f, 42f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: First Sinner", "Slab_10b", 38.35f, 9.46f, 60f, 30f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Broodmother", "Slab_16b", 54f, 10.700001f, 80f, 32f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Trobbio", "Library_13", 73.76f, 16.38f, 124f, 56f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Tormented Trobbio", "Library_13", 77.06f, 16.3f, 124f, 56f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Cogwork Dancers", "Cog_Dancers", 49.789997f, 32.33f, 80f, 45f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Second Sentinel", "Hang_17b", 30.220001f, 0.09f, 55f, 21f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Palestag", "Clover_19", 31.1f, 14.06f, 100f, 30f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Clover Dancers", "Clover_01", 119.22f, 8.16f, 175f, 22f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Skarrsinger Karmelita", "Ant_Queen", 28.986334f, 22.675333f, 55f, 34f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Father of the Flame", "Belltown_08", 56.06f, 11.54f, 115f, 47f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Gurr the Outcast", "Bone_East_18b", 172.21f, 8.889999f, 327f, 108f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Raging Conchfly", "Coral_27", 15.940002f, 26.67f, 202f, 46f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: The Unravelled", "Ward_02", 50.85f, 8.28f, 150f, 90f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Voltvyrm", "Coral_29", 173.17691f, 33.15f, 302f, 92f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Widow", "Belltown_Shrine", 60.8201f, 17.691547f, 95f, 38f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Craggler", "Crawl_04", 83.52f, 14.89f, 165f, 20f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Boss: Skull Tyrant (The Marrow)", "Bone_15", 99.57f, 15.64f, 113f, 29f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Wish: Great Taste of Pharloom", "Song_09b", 109.64f, 132.79f, 165f, 146f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: The Marrow (Tangle)", "Bone_06", 101.33655f, 24.43513f, 112f, 50f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Deep Docks Bellway (Eepy)", "Dock_16", 50.924f, 20.28f, 75f, 39f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Deep Docks Weaver Burial Spire (Squeesh)", "Bone_East_05", 31.386938f, 38.563137f, 190f, 47f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Far Fields Captured (Thoughtless)", "Bone_East_17b", 86.11141f, 30.183958f, 115f, 64f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Hunter's March (Yapper)", "Ant_03", 43.3f, 70.24f, 50f, 78f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Greymoor Craw Lake (Gwah)", "Greymoor_15b", 178.99f, 53.55f, 220f, 141f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Greymoor Tower (Snoozles)", "Greymoor_06", 25.47f, 139.12f, 40f, 207f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Shellwood (Shelly)", "Shellwood_03", 24.21f, 49.01f, 40f, 135f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Pilgrim's Rest (Sleeby)", "Bone_East_10_Church", 181.93805f, 22.203875f, 200f, 30f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Blasted Steps (Nini)", "Coral_35", 6.47f, 143.09f, 29f, 153f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Sinner's Road (Stelf)", "Dust_12", 26.1f, 3.58f, 44f, 16f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Exhaust Organ (Rangle)", "Dust_09", 5.336548f, 35.02f, 180f, 52f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Bellhart (Bellphy)", "Belltown_04", 10.85f, 89.68f, 85f, 97f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Wormways (Snacc)", "Crawl_06", 76.86933f, 28.11f, 85f, 35f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: The Slab Cell (Sway)", "Slab_Cell", 53.51f, 8.31f, 100f, 28f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Bilewater Thieves (Cower)", "Shadow_28", 30.76f, 21.72f, 65f, 34f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Deep Docks Mines (Le Bomba)", "Dock_03d", 101.53194f, 68.1466f, 110f, 78f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Wisp Thicket (Fidget)", "Under_23", 18.23f, 23.401985f, 160f, 35f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Bilehaven (Spangle)", "Shadow_10", 79.64f, 42.24f, 210f, 70f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Choral Cambers Spa (Oowa)", "Song_14", 61.72249f, 8.61472f, 72f, 21f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Sands of Karak (Crustly)", "Coral_24", 36.43f, 46.07f, 183f, 55f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Mount Fay (Ice Cube)", "Peak_05c", 246.66f, 115.87f, 268f, 125f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Songclave (Groggy)", "Library_09", 126.79f, 99.82f, 160f, 106f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Choral Cambers Walled (Mimi)", "Song_11", 50.280293f, 164.14818f, 60f, 188f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Whispering Vaults (Birdy)", "Library_01", 48.52249f, 88.60472f, 60f, 112f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: Underworks (Boomy)", "Under_21", 9.34f, 15.69f, 87f, 26f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Save Flea: The Slab Bellway (Honk Shoo)", "Slab_06", 84.49f, 28.08f, 101f, 35f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Flea: Greymoor - Kratt", "Greymoor_24", 76.09f, 15.68f, 84f, 26f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Flea: Putrified Ducts - Vog", "Bellway_Aqueduct", 71.007f, 63.120625f, 96f, 78f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Flea: Memorium - Huge Flea", "Arborium_08", 33.65f, 34.55f, 132f, 60f, MapMarkerPositionConfidence.ExactUpstream) }; private static readonly MapCheckPosition[] BeastlingCallStations = new MapCheckPosition[12] { new MapCheckPosition("Beastling Call", "Bellway_01", 80.65399f, 8.015218f, 100f, 45f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Beastling Call", "Bellway_02", 73.19f, 16.16f, 120f, 50f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Beastling Call", "Bellway_03", 80.65399f, 8.015218f, 150f, 60f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Beastling Call", "Bellway_04", 80.65399f, 8.015218f, 95f, 30f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Beastling Call", "Belltown_basement", 18.19f, 94.89f, 95f, 135f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Beastling Call", "Bellway_08", 84.81f, 12.86f, 150f, 33f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Beastling Call", "Bellway_City", 49.8f, 11.07f, 112f, 36f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Beastling Call", "Slab_06", 35.19f, 5.88f, 101f, 35f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Beastling Call", "Shellwood_19", 67.73f, 6.07f, 127f, 19f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Beastling Call", "Bone_05", 97.1f, 5.69f, 201f, 22f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Beastling Call", "Bellway_Shadow", 62.8f, 21.6f, 75f, 41f, MapMarkerPositionConfidence.ExactUpstream), new MapCheckPosition("Beastling Call", "Bellway_Aqueduct", 62.88f, 21.89f, 96f, 78f, MapMarkerPositionConfidence.ExactUpstream) }; private static readonly Dictionary DirectMapPositions = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Save Flea: The Slab Cell (Sway)", new Vector2(-404.94763f, 73.475464f) } }; private static readonly Dictionary MinorSceneSizes = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "ant_04_left", new Vector2(145f, 37f) }, { "aqueduct_01", new Vector2(265f, 42f) }, { "arborium_11", new Vector2(222f, 61f) }, { "bone_10", new Vector2(115f, 69f) }, { "bone_east_01", new Vector2(47f, 83f) }, { "bone_east_04b", new Vector2(44f, 100f) }, { "bone_east_14", new Vector2(140f, 80f) }, { "bone_east_24", new Vector2(263f, 76f) }, { "cog_07", new Vector2(90f, 87f) }, { "cog_10", new Vector2(71f, 60f) }, { "coral_03", new Vector2(60f, 215f) }, { "coral_36", new Vector2(70f, 58f) }, { "crawl_02", new Vector2(30f, 150f) }, { "dock_02", new Vector2(125f, 101f) }, { "dock_11", new Vector2(148f, 77f) }, { "dust_01", new Vector2(155f, 27f) }, { "dust_06", new Vector2(30f, 190f) }, { "bellshrine_coral", new Vector2(43f, 40f) }, { "greymoor_05", new Vector2(110f, 75f) }, { "greymoor_12", new Vector2(180f, 35f) }, { "greymoor_15", new Vector2(78f, 84f) }, { "greymoor_15b", new Vector2(220f, 141f) }, { "hang_06_bank", new Vector2(129f, 60f) }, { "hang_16", new Vector2(104f, 30f) }, { "library_02", new Vector2(120f, 64f) }, { "library_09", new Vector2(160f, 106f) }, { "library_12", new Vector2(136f, 116f) }, { "mosstown_02", new Vector2(160f, 65f) }, { "room_forge", new Vector2(140f, 69f) }, { "shadow_02", new Vector2(70f, 207f) }, { "shellwood_01", new Vector2(132f, 98f) }, { "shellwood_01b", new Vector2(48f, 109f) }, { "shellwood_13", new Vector2(141f, 80f) }, { "shellwood_25", new Vector2(290f, 40f) }, { "slab_02", new Vector2(125f, 31f) }, { "slab_04", new Vector2(100f, 37f) }, { "slab_18", new Vector2(100f, 76f) }, { "slab_22", new Vector2(260f, 50f) }, { "song_04", new Vector2(143f, 68f) }, { "song_07", new Vector2(80f, 24f) }, { "song_09", new Vector2(50f, 85f) }, { "tut_01", new Vector2(120f, 120f) }, { "under_03", new Vector2(60f, 25f) }, { "under_07c", new Vector2(85f, 106f) }, { "under_12", new Vector2(40f, 20f) }, { "under_17", new Vector2(164f, 48f) }, { "under_18", new Vector2(160f, 50f) }, { "wisp_02", new Vector2(230f, 46f) }, { "wisp_03", new Vector2(179f, 47f) } }; private static readonly string[][] ExplicitMinorCacheMarkerGroups = new string[68][] { new string[2] { "Rosary Cache: Bellhart #1", "Rosary Cache: Bellhart #2" }, new string[2] { "Rosary Cache: Bone Bottom #4", "Rosary Cache: Bone Bottom #5" }, new string[2] { "Rosary Cache: Bone Bottom #6", "Rosary Cache: Bone Bottom #7" }, new string[2] { "Rosary Cache: Bone Bottom #8", "Rosary Cache: Bone Bottom #9" }, new string[2] { "Rosary Cache: Choral Chambers #1", "Rosary Cache: Choral Chambers #2" }, new string[2] { "Rosary Cache: Choral Chambers #5", "Rosary Cache: Choral Chambers #6" }, new string[2] { "Rosary Cache: Choral Chambers #8", "Rosary Cache: Choral Chambers #9" }, new string[3] { "Rosary Cache: Choral Chambers #11", "Rosary Cache: Choral Chambers #12", "Rosary Cache: Choral Chambers #13" }, new string[2] { "Rosary Cache: Choral Chambers #15", "Rosary Cache: Choral Chambers #16" }, new string[3] { "Rosary Cache: Choral Chambers #17", "Rosary Cache: Choral Chambers #18", "Rosary Cache: Choral Chambers #19" }, new string[2] { "Rosary Cache: Deep Docks #1", "Rosary Cache: Deep Docks #2" }, new string[2] { "Rosary Cache: Deep Docks #5", "Rosary Cache: Deep Docks #6" }, new string[2] { "Rosary Cache: Far Fields #3", "Rosary Cache: Far Fields #4" }, new string[2] { "Rosary Cache: Far Fields #5", "Rosary Cache: Far Fields #6" }, new string[2] { "Rosary Cache: Far Fields #7", "Rosary Cache: Far Fields #8" }, new string[2] { "Rosary Cache: Far Fields #12", "Rosary Cache: Far Fields #13" }, new string[2] { "Rosary Cache: Far Fields #14", "Rosary Cache: Far Fields #15" }, new string[2] { "Rosary Cache: Far Fields #16", "Rosary Cache: Far Fields #17" }, new string[3] { "Rosary Cache: Far Fields #20", "Rosary Cache: Far Fields #21", "Pale Rosary Necklace: Far Fields" }, new string[2] { "Rosary Cache: Greymoor #2", "Rosary Cache: Greymoor #3" }, new string[2] { "Rosary Cache: Greymoor #4", "Rosary Cache: Greymoor #5" }, new string[2] { "Rosary Cache: Greymoor #7", "Rosary Cache: Greymoor #8" }, new string[2] { "Rosary Cache: Greymoor #11", "Rosary Cache: Greymoor #12" }, new string[2] { "Rosary Cache: Greymoor #15", "Rosary Cache: Greymoor #16" }, new string[3] { "Rosary Cache: Greymoor #23", "Rosary Cache: Greymoor #24", "Rosary Cache: Greymoor #25" }, new string[2] { "Rosary Cache: Greymoor #29", "Rosary Cache: Greymoor #30" }, new string[2] { "Rosary Cache: Greymoor #32", "Rosary Cache: Greymoor #33" }, new string[2] { "Rosary Cache: High Halls #1", "Rosary Cache: High Halls #2" }, new string[2] { "Rosary Cache: High Halls #3", "Rosary Cache: High Halls #4" }, new string[2] { "Rosary Cache: Hunter's March #1", "Rosary Cache: Hunter's March #2" }, new string[2] { "Rosary Cache: Hunter's March #4", "Rosary Cache: Hunter's March #5" }, new string[2] { "Rosary Cache: Hunter's March #6", "Rosary Cache: Hunter's March #7" }, new string[2] { "Rosary Cache: Hunter's March #8", "Rosary Cache: Hunter's March #9" }, new string[2] { "Rosary Cache: Mosshome #1", "Rosary Cache: Mosshome #2" }, new string[2] { "Rosary Cache: Mosshome #3", "Rosary Cache: Mosshome #4" }, new string[3] { "Rosary Cache: Mount Fay #1", "Rosary Cache: Mount Fay #2", "Rosary Cache: Mount Fay #3" }, new string[2] { "Rosary Cache: Sinner's Road #2", "Rosary Cache: Sinner's Road #3" }, new string[3] { "Rosary Cache: Sinner's Road #5", "Rosary Cache: Sinner's Road #6", "Rosary Cache: Sinner's Road #7" }, new string[2] { "Rosary Cache: The Marrow #1", "Rosary Cache: The Marrow #2" }, new string[2] { "Rosary Cache: The Marrow #3", "Rosary Cache: The Marrow #4" }, new string[2] { "Rosary Cache: The Marrow #14", "Rosary Cache: The Marrow #15" }, new string[2] { "Rosary Cache: The Slab #2", "Rosary Cache: The Slab #3" }, new string[2] { "Rosary Cache: Underworks #2", "Rosary Cache: Underworks #3" }, new string[2] { "Rosary Cache: Whispering Vaults #2", "Rosary Cache: Whispering Vaults #3" }, new string[2] { "Rosary Cache: Whispering Vaults #6", "Rosary Cache: Whispering Vaults #7" }, new string[2] { "Rosary Cache: Whiteward #1", "Rosary Cache: Whiteward #2" }, new string[2] { "Shell Shard Cache: Blasted Steps #2", "Shell Shard Cache: Blasted Steps #3" }, new string[2] { "Shell Shard Cache: Deep Docks #1", "Shell Shard Cache: Deep Docks #2" }, new string[2] { "Shell Shard Cache: Deep Docks #6", "Shell Shard Cache: Deep Docks #7" }, new string[2] { "Shell Shard Cache: Greymoor #1", "Shell Shard Cache: Greymoor #2" }, new string[2] { "Shell Shard Cache: Greymoor #4", "Shell Shard Cache: Greymoor #5" }, new string[2] { "Shell Shard Cache: Hunter's March #3", "Shell Shard Cache: Hunter's March #4" }, new string[2] { "Shell Shard Cache: Mount Fay #1", "Shell Shard Cache: Mount Fay #2" }, new string[2] { "Shell Shard Cache: Mount Fay #3", "Shell Shard Cache: Mount Fay #4" }, new string[2] { "Shell Shard Cache: Moss Grotto #3", "Shell Shard Cache: Moss Grotto #4" }, new string[3] { "Shell Shard Cache: Moss Grotto #5", "Shell Shard Cache: Moss Grotto #6", "Shell Shard Cache: Moss Grotto #7" }, new string[2] { "Shell Shard Cache: Putrified Ducts #4", "Shell Shard Cache: Putrified Ducts #5" }, new string[2] { "Shell Shard Cache: Putrified Ducts #9", "Shell Shard Cache: Putrified Ducts #10" }, new string[2] { "Shell Shard Cache: Sands of Karak #5", "Shell Shard Cache: Sands of Karak #6" }, new string[2] { "Shell Shard Cache: Shellwood #1", "Shell Shard Cache: Shellwood #2" }, new string[2] { "Shell Shard Cache: Shellwood #4", "Shell Shard Cache: Shellwood #5" }, new string[2] { "Shell Shard Cache: Sinner's Road #4", "Shell Shard Cache: Sinner's Road #5" }, new string[2] { "Shell Shard Cache: Sinner's Road #6", "Shell Shard Cache: Sinner's Road #7" }, new string[2] { "Shell Shard Cache: The Marrow #2", "Shell Shard Cache: The Marrow #3" }, new string[2] { "Shell Shard Cache: The Marrow #5", "Shell Shard Cache: The Marrow #6" }, new string[2] { "Shell Shard Cache: The Slab #6", "Shell Shard Cache: The Slab #7" }, new string[2] { "Shell Shard Cache: Underworks #7", "Shell Shard Cache: Underworks #8" }, new string[2] { "Shell Shard Cache: Wisp Thicket #6", "Shell Shard Cache: Wisp Thicket #7" } }; private static readonly Dictionary ExplicitMinorCacheGroupAnchors = BuildExplicitMinorCacheGroupAnchors(); private static Dictionary BuildExplicitMinorCacheGroupAnchors() { //IL_010d: Unknown result type (might be due to invalid IL or missing references) Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); MinorCacheManifest.Entry[] entries = MinorCacheManifest.Entries; foreach (MinorCacheManifest.Entry entry in entries) { dictionary[entry.LocationName] = entry; } Dictionary dictionary2 = new Dictionary(StringComparer.OrdinalIgnoreCase); string[][] explicitMinorCacheMarkerGroups = ExplicitMinorCacheMarkerGroups; Vector2 value2 = default(Vector2); foreach (string[] array in explicitMinorCacheMarkerGroups) { float num = 0f; float num2 = 0f; string text = null; bool flag = array != null && array.Length >= 2; if (flag) { string[] array2 = array; foreach (string key in array2) { if (!dictionary.TryGetValue(key, out var value) || (text != null && !string.Equals(text, value.SceneName, StringComparison.OrdinalIgnoreCase))) { flag = false; break; } text = value.SceneName; num += value.X; num2 += value.Y; } } if (flag) { ((Vector2)(ref value2))..ctor(num / (float)array.Length, num2 / (float)array.Length); string[] array2 = array; foreach (string key2 in array2) { dictionary2[key2] = value2; } } } return dictionary2; } internal static IEnumerable GetPositions() { MapCheckPosition[] staticPositions = StaticPositions; for (int i = 0; i < staticPositions.Length; i++) { yield return staticPositions[i]; } MelodyLocationManifest.Entry[] entries = MelodyLocationManifest.Entries; foreach (MelodyLocationManifest.Entry entry in entries) { if (!string.Equals(entry.LocationName, "Beastling Call", StringComparison.OrdinalIgnoreCase)) { if (string.Equals(entry.LocationName, "Elegy of the Deep", StringComparison.OrdinalIgnoreCase)) { yield return new MapCheckPosition(entry.LocationName, "Tut_03", 10.006f, 16.857f, 126f, 35f, MapMarkerPositionConfidence.ExactUpstream); } else { yield return new MapCheckPosition(entry.LocationName, entry.SceneName, entry.X, entry.Y, entry.SceneWidth, entry.SceneHeight, MapMarkerPositionConfidence.ExactUpstream); } } } MapCheckPosition currentBeastlingCallPosition = GetCurrentBeastlingCallPosition(); if (currentBeastlingCallPosition != null) { yield return currentBeastlingCallPosition; } MinorPickupManifest.Entry[] entries2 = MinorPickupManifest.Entries; foreach (MinorPickupManifest.Entry entry2 in entries2) { if ((entry2.X != 0f || entry2.Y != 0f) && MinorSceneSizes.TryGetValue(entry2.SceneName, out var value)) { yield return new MapCheckPosition(entry2.LocationName, entry2.SceneName, entry2.X, entry2.Y, value.x, value.y, MapMarkerPositionConfidence.ExactUpstream); } } MinorCacheManifest.Entry[] entries3 = MinorCacheManifest.Entries; foreach (MinorCacheManifest.Entry entry3 in entries3) { string sceneName = entry3.SceneName; float positionX = entry3.X; float positionY = entry3.Y; float sceneWidth = entry3.SceneWidth; float sceneHeight = entry3.SceneHeight; if (string.Equals(entry3.SceneName, "Chapel_Wanderer", StringComparison.OrdinalIgnoreCase)) { sceneName = "Bonegrave"; positionX = 197.97f; positionY = 6.73f; sceneWidth = 315f; sceneHeight = 82f; } if (ExplicitMinorCacheGroupAnchors.TryGetValue(entry3.LocationName, out var value2)) { positionX = value2.x; positionY = value2.y; } if (string.Equals(entry3.LocationName, "Shell Shard Cache: Moss Grotto #3", StringComparison.OrdinalIgnoreCase) || string.Equals(entry3.LocationName, "Shell Shard Cache: Moss Grotto #4", StringComparison.OrdinalIgnoreCase)) { positionX = 144.875f; positionY = 53.025f; } if (string.Equals(entry3.LocationName, "Shell Shard Cache: Moss Grotto #5", StringComparison.OrdinalIgnoreCase) || string.Equals(entry3.LocationName, "Shell Shard Cache: Moss Grotto #6", StringComparison.OrdinalIgnoreCase) || string.Equals(entry3.LocationName, "Shell Shard Cache: Moss Grotto #7", StringComparison.OrdinalIgnoreCase)) { positionX = 14.05f; positionY = 35.263332f; } if (string.Equals(entry3.LocationName, "Rosary Cache: The Marrow (Mosslands Passage) #2", StringComparison.OrdinalIgnoreCase)) { positionY = 86.4946f; } if (string.Equals(entry3.LocationName, "Rosary Cache: Deep Docks #7", StringComparison.OrdinalIgnoreCase) || string.Equals(entry3.LocationName, "Rosary Cache: Deep Docks #8", StringComparison.OrdinalIgnoreCase)) { positionX = 7.9868116f; positionY = 28.304193f; } yield return new MapCheckPosition(entry3.LocationName, sceneName, positionX, positionY, sceneWidth, sceneHeight, MapMarkerPositionConfidence.ExactUpstream); } } private static MapCheckPosition GetCurrentBeastlingCallPosition() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0014: 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_001a: 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) FastTravelLocations val = (FastTravelLocations)((PlayerData.instance == null) ? 1 : ((int)PlayerData.instance.FastTravelNPCLocation)); if ((int)val == 0) { val = (FastTravelLocations)1; } string sceneName = FastTravelScenes.GetSceneName(val); if (string.IsNullOrWhiteSpace(sceneName)) { return null; } MapCheckPosition[] beastlingCallStations = BeastlingCallStations; foreach (MapCheckPosition mapCheckPosition in beastlingCallStations) { if (string.Equals(mapCheckPosition.SceneName, sceneName, StringComparison.OrdinalIgnoreCase)) { return mapCheckPosition; } } return null; } internal static bool TryGetDirectMapPosition(string locationName, out Vector2 mapPosition) { return DirectMapPositions.TryGetValue(locationName ?? string.Empty, out mapPosition); } internal static MapCheckPosition ResolveCurrentWorldVariant(MapCheckPosition position) { if (position == null) { return null; } if (PlayerData.instance != null && PlayerData.instance.mortKeptWeightedAnklet && string.Equals(position.LocationName, "Tool Unlock: Weighted Anklet", StringComparison.OrdinalIgnoreCase)) { return new MapCheckPosition(position.LocationName, "Bone_East_07", 13.5f, 111.33f, 40f, 190f, MapMarkerPositionConfidence.ExactUpstream); } if (PlayerData.instance != null && PlayerData.instance.rhinoRuckus && string.Equals(position.LocationName, "Beast Shard: Pilgrim's Rest", StringComparison.OrdinalIgnoreCase)) { return new MapCheckPosition(position.LocationName, "Bone_East_10_Room", 46.938774f, 13.87f, 155f, 50f, MapMarkerPositionConfidence.ExactUpstream); } if (PlayerData.instance != null && PlayerData.instance.blackThreadWorld) { if (string.Equals(position.LocationName, "Tacks", StringComparison.OrdinalIgnoreCase)) { return new MapCheckPosition(position.LocationName, "Dust_Shack", 15.73441f, 5.286931f, 38f, 30f, MapMarkerPositionConfidence.ExactUpstream); } if (string.Equals(position.LocationName, "Mask Shard: Pebb (Bone Bottom) / Grindle (Act 3)", StringComparison.OrdinalIgnoreCase) || string.Equals(position.LocationName, "Simple Key: Bone Bottom Shop", StringComparison.OrdinalIgnoreCase) || string.Equals(position.LocationName, "Memory Locket: Pilgrim's Rest Shop", StringComparison.OrdinalIgnoreCase) || string.Equals(position.LocationName, "Craftmetal: Bone Bottom Shop", StringComparison.OrdinalIgnoreCase) || string.Equals(position.LocationName, "Tool Pouch: Pilgrim's Rest", StringComparison.OrdinalIgnoreCase) || string.Equals(position.LocationName, "Tool Unlock: Magnetite Dice", StringComparison.OrdinalIgnoreCase) || string.Equals(position.LocationName, "Magnetite Brooch", StringComparison.OrdinalIgnoreCase)) { return new MapCheckPosition(position.LocationName, "Coral_42", 24.99f, 21.43f, 62f, 32f, MapMarkerPositionConfidence.ExactUpstream); } if (string.Equals(position.LocationName, "Tool Pouch: Loddie", StringComparison.OrdinalIgnoreCase)) { return new MapCheckPosition(position.LocationName, "Bone_12", 23.04f, 5.35f, 58f, 43f, MapMarkerPositionConfidence.ExactUpstream); } if (string.Equals(position.LocationName, "Mask Shard: Jubilana (Songclave)", StringComparison.OrdinalIgnoreCase) || string.Equals(position.LocationName, "Spool Fragment: Jubilana (Songclave)", StringComparison.OrdinalIgnoreCase) || string.Equals(position.LocationName, "Spider Strings", StringComparison.OrdinalIgnoreCase) || string.Equals(position.LocationName, "Ascendant's Grip", StringComparison.OrdinalIgnoreCase) || string.Equals(position.LocationName, "Spool Extender", StringComparison.OrdinalIgnoreCase) || string.Equals(position.LocationName, "Relic: Choral Commandment (Jubilana)", StringComparison.OrdinalIgnoreCase) || string.Equals(position.LocationName, "Simple Key: Songclave Shop", StringComparison.OrdinalIgnoreCase) || string.Equals(position.LocationName, "Craftmetal: Songclave Shop", StringComparison.OrdinalIgnoreCase) || string.Equals(position.LocationName, "White Key", StringComparison.OrdinalIgnoreCase)) { return new MapCheckPosition(position.LocationName, "Song_Enclave", 101.46999f, 7.586237f, 120f, 33f, MapMarkerPositionConfidence.ExactUpstream); } } if ((string.Equals(position.LocationName, "Wish: Volatile Flintbeetles", StringComparison.OrdinalIgnoreCase) || string.Equals(position.LocationName, "Memory Locket: Volatile Flintbeetles", StringComparison.OrdinalIgnoreCase)) && PlayerData.instance != null && PlayerData.instance.blackThreadWorld) { return new MapCheckPosition(position.LocationName, "Bone_10", 18.81f, 16.249998f, 115f, 69f, MapMarkerPositionConfidence.ExactUpstream); } return position; } } internal static class CollectibleSourceManifest { internal sealed class DirectPickupEntry { internal readonly string LocationName; internal readonly ItemType Type; internal readonly string SceneName; internal readonly string NativeItemName; internal readonly string HierarchyPath; internal readonly float X; internal readonly float Y; internal DirectPickupEntry(string locationName, ItemType type, string sceneName, string nativeItemName, string hierarchyPath, float x, float y) { LocationName = locationName; Type = type; SceneName = sceneName; NativeItemName = nativeItemName ?? string.Empty; HierarchyPath = hierarchyPath ?? string.Empty; X = x; Y = y; } } internal sealed class CocoonEntry { internal readonly string LocationName; internal readonly string SceneName; internal readonly float X; internal readonly float Y; internal CocoonEntry(string locationName, string sceneName, float x, float y) { LocationName = locationName; SceneName = sceneName; X = x; Y = y; } } internal sealed class CrawPinEntry { internal readonly string SceneName; internal readonly float X; internal readonly float Y; internal CrawPinEntry(string sceneName, float x, float y) { SceneName = sceneName; X = x; Y = y; } } internal sealed class PollipHeartEntry { internal readonly string LocationName; internal readonly string SceneName; internal readonly float X; internal readonly float Y; internal PollipHeartEntry(string locationName, string sceneName, float x, float y) { LocationName = locationName; SceneName = sceneName; X = x; Y = y; } } internal const string VolatileFlintbeetlesLocket = "Memory Locket: Volatile Flintbeetles"; internal const string CrawSummons = "Craw Summons"; internal static readonly DirectPickupEntry[] DirectPickups = new DirectPickupEntry[9] { new DirectPickupEntry("Key of Indolent", ItemType.MajorKey, "Slab_14", string.Empty, "slab_item_chain/breakable/Collectable Item Pickup (1)", 12.900001f, 6.892f), new DirectPickupEntry("Key of Heretic", ItemType.MajorKey, "Slab_16", string.Empty, "Event Control/Battle Cloaked Scene/Wave 5 - Item/Item Placer/Collectable Item Pickup", 25.54f, 23.67f), new DirectPickupEntry("Key of Heretic", ItemType.MajorKey, "Slab_16", string.Empty, "Event Control/Battle Cloakless Scene/Wave 9 - Item/Item Placer/Collectable Item Pickup", 25.54f, 23.67f), new DirectPickupEntry("Key of Heretic", ItemType.MajorKey, "Slab_16", string.Empty, "Event Control/Battle Completed Scene/Collectable Item Pickup Return", 25.45f, 18.2f), new DirectPickupEntry("Key of Apostate", ItemType.MajorKey, "Aqueduct_04", string.Empty, "Breakable_cage/Collectable Item Pickup Slab Key", 7.570004f, 38.651806f), new DirectPickupEntry("White Key", ItemType.MajorKey, "Song_Enclave", "Ward Key", "Black Thread States/Normal World/Enclave States/Ward Key Scene/Collectable Item Pickup", 102.076f, 6.29f), new DirectPickupEntry("Surgeon's Key", ItemType.MajorKey, "Ward_07", "Ward Boss Key", "Group/Junk Hatch/Collectable Item Pickup", 18.224485f, 6.969246f), new DirectPickupEntry("Surgeon's Key", ItemType.MajorKey, "Ward_07", "Ward Boss Key", "Group/Junk Hatch/Return Corpse/Collectable Item Pickup", 13.134485f, 7.159246f), new DirectPickupEntry("Memory Locket: Volatile Flintbeetles", ItemType.MemoryLocket, "Bone_10", "Crest Socket Unlocker", "Black Thread States Thread Only Variant/Black Thread World/Rock Rollers Quest Not Completed/Collectable Item Pickup Locket", 18.81f, 16.249998f) }; internal static readonly CocoonEntry[] SilkeaterCocoons = new CocoonEntry[9] { new CocoonEntry("Silkeater: Deep Docks", "Dock_14", 23.34f, 9.38f), new CocoonEntry("Silkeater: Greymoor", "Greymoor_04", 19.42f, 145.69f), new CocoonEntry("Silkeater: Exhaust Organ", "Organ_01", 135.53f, 40.37f), new CocoonEntry("Silkeater: Choral Chambers West", "Song_24", 52.99f, 21.53f), new CocoonEntry("Silkeater: Choral Chambers East", "Song_09b", 151.97f, 139.34f), new CocoonEntry("Silkeater: Whispering Vaults", "Library_14", 54.51f, 17.26f), new CocoonEntry("Silkeater: Whiteward", "Ward_04", 24.19633f, 22.264452f), new CocoonEntry("Silkeater: The Cradle", "Tube_Hub", 20.35f, 8.78f), new CocoonEntry("Silkeater: Blasted Steps", "Coral_37", 19.11007f, 12.7f) }; internal static readonly CrawPinEntry[] CrawPins = new CrawPinEntry[11] { new CrawPinEntry("Belltown", 65.72f, 7.36f), new CrawPinEntry("Bellway_03", 44.64f, 9.52f), new CrawPinEntry("Bellway_Shadow", 33.317677f, 22.490593f), new CrawPinEntry("Bone_East_27", 27.06f, 47.39f), new CrawPinEntry("Dust_10", 118.41536f, 44.107395f), new CrawPinEntry("Dust_11", 106.65135f, 3.749151f), new CrawPinEntry("Dust_11", 107.77f, 3.596507f), new CrawPinEntry("Mosstown_03", 17.8f, 56.64f), new CrawPinEntry("Shellwood_01b", 18.995138f, 36.516193f), new CrawPinEntry("Shellwood_08c", 10.94f, 4.77f), new CrawPinEntry("Wisp_04", 53.773636f, 15.50346f) }; internal static readonly PollipHeartEntry[] PollipHearts = new PollipHeartEntry[6] { new PollipHeartEntry("Pollip Heart: Shellwood #1", "Shellwood_02", 73.96f, 81.15f), new PollipHeartEntry("Pollip Heart: Shellwood #2", "Shellwood_20", 42.42f, 36.059998f), new PollipHeartEntry("Pollip Heart: Shellwood #3", "Shellwood_10", 9.409075f, 28.81365f), new PollipHeartEntry("Pollip Heart: Shellwood #4", "Shellwood_26", 32.20691f, 84.48061f), new PollipHeartEntry("Pollip Heart: Shellwood #5", "Shellwood_15", 13.255055f, 7.882488f), new PollipHeartEntry("Pollip Heart: Shellwood #6", "Shellwood_01", 92.34507f, 84.7392f) }; private static readonly Dictionary MossberryByScene = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Tut_01b", "Mossberry: Moss Grotto #1" }, { "Tut_02", "Mossberry: Moss Grotto #2" }, { "Bonetown", "Mossberry: Bone Bottom" }, { "Bone_05b", "Mossberry: Mosshome" }, { "Bonegrave", "Mossberry: Bonegrave" }, { "Weave_03", "Mossberry: Weavenest Atla" }, { "Arborium_04", "Mossberry: Memorium" } }; internal static bool TryGetMossberryLocation(string sceneName, out string locationName) { return MossberryByScene.TryGetValue(sceneName ?? string.Empty, out locationName); } internal static PollipHeartEntry FindPollipHeartSource(string sceneName, string objectName, string hierarchyPath) { if (!string.Equals(objectName, "Nectar Pickup", StringComparison.Ordinal) || !string.Equals(hierarchyPath, "purple_flower_set/Big Flower/Nectar Pickup", StringComparison.OrdinalIgnoreCase)) { return null; } PollipHeartEntry[] pollipHearts = PollipHearts; foreach (PollipHeartEntry pollipHeartEntry in pollipHearts) { if (string.Equals(sceneName, pollipHeartEntry.SceneName, StringComparison.OrdinalIgnoreCase)) { return pollipHeartEntry; } } return null; } internal static DirectPickupEntry FindDirectPickup(string sceneName, string nativeItemName, string hierarchyPath, Vector2 position) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) DirectPickupEntry directPickupEntry = null; DirectPickupEntry[] directPickups = DirectPickups; foreach (DirectPickupEntry directPickupEntry2 in directPickups) { if (!string.Equals(sceneName, directPickupEntry2.SceneName, StringComparison.OrdinalIgnoreCase) || (!string.IsNullOrEmpty(directPickupEntry2.NativeItemName) && !string.Equals(nativeItemName, directPickupEntry2.NativeItemName, StringComparison.Ordinal))) { continue; } bool num = !string.IsNullOrEmpty(directPickupEntry2.HierarchyPath) && string.Equals(hierarchyPath, directPickupEntry2.HierarchyPath, StringComparison.OrdinalIgnoreCase); float num2 = position.x - directPickupEntry2.X; float num3 = position.y - directPickupEntry2.Y; bool flag = num2 * num2 + num3 * num3 <= 0.5625f; if (num || flag) { if (directPickupEntry != null && !string.Equals(directPickupEntry.LocationName, directPickupEntry2.LocationName, StringComparison.Ordinal)) { return null; } directPickupEntry = directPickupEntry2; } } return directPickupEntry; } internal static CocoonEntry FindSilkeaterCocoon(string sceneName, string objectName, Vector2 position) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (!string.Equals(objectName, "Silk Grub Large Cocoon", StringComparison.Ordinal)) { return null; } CocoonEntry[] silkeaterCocoons = SilkeaterCocoons; foreach (CocoonEntry cocoonEntry in silkeaterCocoons) { if (string.Equals(sceneName, cocoonEntry.SceneName, StringComparison.OrdinalIgnoreCase)) { float num = position.x - cocoonEntry.X; float num2 = position.y - cocoonEntry.Y; if (num * num + num2 * num2 <= 0.5625f) { return cocoonEntry; } } } return null; } internal static bool IsCrawPin(string sceneName, string objectName, Vector2 position) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(objectName) || !objectName.StartsWith("craw_court_summons_pin", StringComparison.Ordinal)) { return false; } CrawPinEntry[] crawPins = CrawPins; foreach (CrawPinEntry crawPinEntry in crawPins) { if (string.Equals(sceneName, crawPinEntry.SceneName, StringComparison.OrdinalIgnoreCase)) { float num = position.x - crawPinEntry.X; float num2 = position.y - crawPinEntry.Y; if (num * num + num2 * num2 <= 0.5625f) { return true; } } } return false; } internal static IEnumerable AppendTo(IEnumerable existing) { HashSet names = new HashSet(StringComparer.OrdinalIgnoreCase); if (existing != null) { foreach (Location item in existing) { if (item != null) { names.Add(item.Name); yield return item; } } } DirectPickupEntry[] directPickups = DirectPickups; foreach (DirectPickupEntry directPickupEntry in directPickups) { if (names.Add(directPickupEntry.LocationName)) { yield return new Location(directPickupEntry.LocationName, directPickupEntry.Type, () => false); } } CocoonEntry[] silkeaterCocoons = SilkeaterCocoons; foreach (CocoonEntry cocoonEntry in silkeaterCocoons) { if (names.Add(cocoonEntry.LocationName)) { yield return new Location(cocoonEntry.LocationName, ItemType.Silkeater, () => false); } } foreach (string value in MossberryByScene.Values) { if (names.Add(value)) { yield return new Location(value, ItemType.Mossberry, () => false); } } PollipHeartEntry[] pollipHearts = PollipHearts; foreach (PollipHeartEntry pollipHeartEntry in pollipHearts) { if (names.Add(pollipHeartEntry.LocationName)) { yield return new Location(pollipHeartEntry.LocationName, ItemType.PollipHeart, () => false); } } if (names.Add("Craw Summons")) { yield return new Location("Craw Summons", ItemType.MajorKey, () => false); } } } internal sealed class NativeShopLocation { internal readonly string ShopAssetName; internal readonly string LocationName; internal readonly string PlayerDataFlag; internal readonly ItemType Type; internal NativeShopLocation(string shopAssetName, string locationName, string playerDataFlag, ItemType type) { ShopAssetName = shopAssetName; LocationName = locationName; PlayerDataFlag = playerDataFlag; Type = type; } } internal static class CoreLocationManifest { internal const string CraftingKitPickupAssetName = "Tool Kit Pickup"; internal const string CrowFeathersCraftingKitLocation = "Crafting Kit Source: Crow Feathers"; internal static readonly NativeShopLocation[] ShopLocations = new NativeShopLocation[31] { new NativeShopLocation("Mapper Moss Grotto Map", "Map Purchase: Mosslands", "HasMossGrottoMap", ItemType.Map), new NativeShopLocation("Mapper Boneforest Map", "Map Purchase: The Marrow", "HasBoneforestMap", ItemType.Map), new NativeShopLocation("Mapper Docks Map", "Map Purchase: Deep Docks", "HasDocksMap", ItemType.Map), new NativeShopLocation("Mapper Wilds Map", "Map Purchase: Far Fields", "HasWildsMap", ItemType.Map), new NativeShopLocation("Mapper Crawl Map", "Map Purchase: Wormways", "HasCrawlMap", ItemType.Map), new NativeShopLocation("Mapper Hunters Nest Map", "Map Purchase: Hunter's March", "HasHuntersNestMap", ItemType.Map), new NativeShopLocation("Mapper Greymoor Map", "Map Purchase: Greymoor", "HasGreymoorMap", ItemType.Map), new NativeShopLocation("Mapper Bellhart Map", "Map Purchase: Bellhart", "HasBellhartMap", ItemType.Map), new NativeShopLocation("Mapper Shellwood Map", "Map Purchase: Shellwood", "HasShellwoodMap", ItemType.Map), new NativeShopLocation("Mapper Coral Caverns Map", "Map Purchase: Sands of Karak", "HasCoralMap", ItemType.Map), new NativeShopLocation("Mapper Dustpens Map", "Map Purchase: Sinner's Road", "HasDustpensMap", ItemType.Map), new NativeShopLocation("Mapper Peak Map", "Map Purchase: Mount Fay", "HasPeakMap", ItemType.Map), new NativeShopLocation("Mapper JudgeSteps Map", "Map Purchase: Blasted Steps", "HasJudgeStepsMap", ItemType.Map), new NativeShopLocation("Mapper Shadow Map", "Map Purchase: Bilewater", "HasSwampMap", ItemType.Map), new NativeShopLocation("Mapper Bench Map Pin", "Pin Purchase: Bench", "hasPinBench", ItemType.Pin), new NativeShopLocation("Mapper Tube Map Pin", "Pin Purchase: Ventrica", "hasPinTube", ItemType.Pin), new NativeShopLocation("Mapper Bellway Map Pin", "Pin Purchase: Bellway", "hasPinStag", ItemType.Pin), new NativeShopLocation("Mapper Shop Map Pin", "Pin Purchase: Vendor", "hasPinShop", ItemType.Pin), new NativeShopLocation("Bonebottom Faith Token", "Simple Key: Bone Bottom Shop", "PurchasedBonebottomFaithToken", ItemType.SimpleKey), new NativeShopLocation("Grindle Simple Key", "Simple Key: Bone Bottom Shop", "PurchasedBonebottomFaithToken", ItemType.SimpleKey), new NativeShopLocation("City Merchant Simple Key", "Simple Key: Songclave Shop", "MerchantEnclaveSimpleKey", ItemType.SimpleKey), new NativeShopLocation("Pilgrims Rest Crest Socket Unlocker", "Memory Locket: Pilgrim's Rest Shop", "PurchasedPilgrimsRestMemoryLocket", ItemType.MemoryLocket), new NativeShopLocation("Grindle Crest Socket", "Memory Locket: Pilgrim's Rest Shop", "PurchasedPilgrimsRestMemoryLocket", ItemType.MemoryLocket), new NativeShopLocation("Pilgrims Rest Tool Pouch", "Tool Pouch: Pilgrim's Rest", "PurchasedPilgrimsRestToolPouch", ItemType.ToolPouch), new NativeShopLocation("Grindle Tool Pouch", "Tool Pouch: Pilgrim's Rest", "PurchasedPilgrimsRestToolPouch", ItemType.ToolPouch), new NativeShopLocation("Bellhart Crest Socket", "Memory Locket: Bellhart Shop", "PurchasedBelltownMemoryLocket", ItemType.MemoryLocket), new NativeShopLocation("Bonebottom Tool Metal", "Craftmetal: Bone Bottom Shop", "PurchasedBonebottomToolMetal", ItemType.Craftmetal), new NativeShopLocation("Grindle Tool Metal", "Craftmetal: Bone Bottom Shop", "PurchasedBonebottomToolMetal", ItemType.Craftmetal), new NativeShopLocation("City Merchant Tool Metal", "Craftmetal: Songclave Shop", "MerchantEnclaveToolMetal", ItemType.Craftmetal), new NativeShopLocation("City Merchant Ward Key", "White Key", "MerchantEnclaveWardKey", ItemType.MajorKey), new NativeShopLocation("Architect Key", "Architect's Key", "PurchasedArchitectKey", ItemType.MajorKey) }; internal static readonly string[] CraftingKitLocationNames = new string[4] { "Crafting Kit Source: Forge Tool Kit", "Crafting Kit Source: Architect Tool Kit", "Crafting Kit Source: Grindle Tool Kit", "Crafting Kit Source: Crow Feathers" }; internal static readonly string[] NeedleUpgradeLocationNames = new string[7] { "Pinmaster Plinney: Sharpened Needle", "Pinmaster Plinney: Shining Needle", "Pinmaster Plinney: Hivesteel Needle", "Pinmaster Plinney: Pale Steel Needle", "Pale Oil: Whispering Vaults", "Pale Oil: Great Taste of Pharloom", "Pale Oil: Ecstasy of the End" }; internal static readonly string[] RelicAssetNames = new string[21] { "Weaver Totem Witch", "Psalm Cylinder Library Roof", "Bone Record Wisp Top", "Weaver Totem Bonetown_upper_room", "Librarian Melody Cylinder", "Seal Chit City Merchant", "Psalm Cylinder Ward", "Weaver Record Conductor", "Psalm Cylinder Librarian", "Psalm Cylinder Hang", "Seal Chit Ward Corpse", "Weaver Record Sprint_Challenge", "Psalm Cylinder Grindle", "Weaver Record Weave_08", "Bone Record Understore_Map_Room", "Bone Record Bone_East_14", "Seal Chit Aspid_01", "Seal Chit Silk Siphon", "Bone Record Greymoor_flooded_corridor", "Weaver Totem Slab_Bottom", "Ancient Egg Abyss Middle" }; private static readonly Dictionary ShopLocationsByAsset = BuildShopLocationLookup(); private static readonly Dictionary CraftingKitShopLocationsByAsset = new Dictionary(StringComparer.Ordinal) { { "Forge Tool Kit", "Crafting Kit Source: Forge Tool Kit" }, { "Architect Tool Kit", "Crafting Kit Source: Architect Tool Kit" }, { "Grindle Tool Kit", "Crafting Kit Source: Grindle Tool Kit" } }; private static readonly Dictionary RelicLocationsByAsset = BuildRelicLocationLookup(); internal static Location[] AppendTo(Location[] existingLocations) { List list = new List(existingLocations); HashSet hashSet = new HashSet(list.ConvertAll((Location location) => location.Name), StringComparer.OrdinalIgnoreCase); NativeShopLocation[] shopLocations = ShopLocations; foreach (NativeShopLocation nativeShopLocation in shopLocations) { if (hashSet.Add(nativeShopLocation.LocationName)) { list.Add(new Location(nativeShopLocation.LocationName, nativeShopLocation.Type, null)); } } string[] craftingKitLocationNames = CraftingKitLocationNames; foreach (string name in craftingKitLocationNames) { list.Add(new Location(name, ItemType.Upgrade, null)); } craftingKitLocationNames = NeedleUpgradeLocationNames; foreach (string name2 in craftingKitLocationNames) { list.Add(new Location(name2, ItemType.NeedleUpgrade, null)); } craftingKitLocationNames = RelicAssetNames; foreach (string relicAssetName in craftingKitLocationNames) { list.Add(new Location(GetRelicLocationName(relicAssetName), ItemType.Relic, null)); } AddCompletionLocations(list); return list.ToArray(); } internal static bool TryGetShopLocation(string shopAssetName, out NativeShopLocation location) { if (string.IsNullOrEmpty(shopAssetName)) { location = null; return false; } return ShopLocationsByAsset.TryGetValue(shopAssetName, out location); } internal static bool TryGetCraftingKitShopLocation(string shopAssetName, out string locationName) { if (string.IsNullOrEmpty(shopAssetName)) { locationName = null; return false; } return CraftingKitShopLocationsByAsset.TryGetValue(shopAssetName, out locationName); } internal static bool TryGetRelicLocation(string relicAssetName, out string locationName) { if (string.IsNullOrEmpty(relicAssetName)) { locationName = null; return false; } return RelicLocationsByAsset.TryGetValue(relicAssetName, out locationName); } private static string GetRelicLocationName(string relicAssetName) { return "Relic Pickup: " + relicAssetName; } private static Dictionary BuildShopLocationLookup() { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); NativeShopLocation[] shopLocations = ShopLocations; foreach (NativeShopLocation nativeShopLocation in shopLocations) { dictionary.Add(nativeShopLocation.ShopAssetName, nativeShopLocation); } return dictionary; } private static Dictionary BuildRelicLocationLookup() { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); string[] relicAssetNames = RelicAssetNames; foreach (string text in relicAssetNames) { dictionary.Add(text, GetRelicLocationName(text)); } return dictionary; } private static void AddCompletionLocations(List locations) { locations.Add(new Location("Bell Shrine Completion: bellShrineBoneForest", ItemType.BellShrine, () => PlayerData.instance != null && PlayerData.instance.bellShrineBoneForest)); locations.Add(new Location("Bell Shrine Completion: bellShrineWilds", ItemType.BellShrine, () => PlayerData.instance != null && PlayerData.instance.bellShrineWilds)); locations.Add(new Location("Bell Shrine Completion: bellShrineGreymoor", ItemType.BellShrine, () => PlayerData.instance != null && PlayerData.instance.bellShrineGreymoor)); locations.Add(new Location("Bell Shrine Completion: bellShrineShellwood", ItemType.BellShrine, () => PlayerData.instance != null && PlayerData.instance.bellShrineShellwood)); locations.Add(new Location("Bell Shrine Completion: bellShrineBellhart", ItemType.BellShrine, () => PlayerData.instance != null && PlayerData.instance.bellShrineBellhart)); locations.Add(new Location("Bell Shrine Completion: bellShrineEnclave", ItemType.BellShrine, () => PlayerData.instance != null && PlayerData.instance.bellShrineEnclave)); locations.Add(new Location("Boss Completion: defeatedMossMother", ItemType.Boss, MossMotherWarpSafety.IsLegitimateDefeat)); locations.Add(new Location("Boss Completion: skullKingKilled", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.skullKingKilled)); locations.Add(new Location("Boss Completion: defeatedBellBeast", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedBellBeast)); locations.Add(new Location("Boss Completion: defeatedAntQueen", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedAntQueen)); locations.Add(new Location("Boss Completion: defeatedLace1", ItemType.Boss, () => PlayerData.instance != null && (PlayerData.instance.defeatedLace1 || PlayerData.instance.encounteredLaceBlastedBridge))); locations.Add(new Location("Boss Completion: defeatedSongGolem", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedSongGolem)); locations.Add(new Location("Boss Completion: defeatedDockForemen", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedDockForemen)); locations.Add(new Location("Boss Completion: defeatedVampireGnatBoss", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedVampireGnatBoss)); locations.Add(new Location("Boss Completion: defeatedCrowCourt", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedCrowCourt)); locations.Add(new Location("Boss Completion: defeatedSplinterQueen", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedSplinterQueen)); locations.Add(new Location("Boss Completion: defeatedSeth", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedSeth)); locations.Add(new Location("Boss Completion: defeatedFlowerQueen", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedFlowerQueen)); locations.Add(new Location("Boss Completion: defeatedRoachkeeperChef", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedRoachkeeperChef)); locations.Add(new Location("Boss Completion: defeatedPhantom", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedPhantom)); locations.Add(new Location("Boss Completion: DefeatedSwampShaman", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.DefeatedSwampShaman)); locations.Add(new Location("Boss Completion: defeatedCoralKing", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedCoralKing)); locations.Add(new Location("Boss Completion: defeatedCoralDrillers", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedCoralDrillers)); locations.Add(new Location("Boss Completion: defeatedLastJudge", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedLastJudge)); locations.Add(new Location("Boss Completion: defeatedGreyWarrior", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedGreyWarrior)); locations.Add(new Location("Boss Completion: defeatedFirstWeaver", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedFirstWeaver)); locations.Add(new Location("Boss Completion: defeatedBroodMother", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedBroodMother)); locations.Add(new Location("Boss Completion: defeatedTrobbio", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedTrobbio)); locations.Add(new Location("Boss Completion: defeatedTormentedTrobbio", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedTormentedTrobbio)); locations.Add(new Location("Boss Completion: defeatedCogworkDancers", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedCogworkDancers)); locations.Add(new Location("Boss Completion: defeatedLaceTower", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedLaceTower)); locations.Add(new Location("Boss Completion: defeatedSongChevalierBoss", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedSongChevalierBoss)); locations.Add(new Location("Boss Completion: defeatedWhiteCloverstag", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedWhiteCloverstag)); locations.Add(new Location("Boss Completion: defeatedCloverDancers", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedCloverDancers)); locations.Add(new Location("Boss Completion: defeatedWispPyreEffigy", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedWispPyreEffigy)); locations.Add(new Location("Boss Completion: defeatedAntTrapper", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedAntTrapper)); locations.Add(new Location("Boss Completion: defeatedCoralDrillerSolo", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedCoralDrillerSolo)); locations.Add(new Location("Boss Completion: wardBossDefeated", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.wardBossDefeated)); locations.Add(new Location("Boss Completion: defeatedZapCoreEnemy", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.defeatedZapCoreEnemy)); locations.Add(new Location("Boss Completion: spinnerDefeated", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.spinnerDefeated)); locations.Add(new Location("Boss Completion: roofCrabDefeated", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.roofCrabDefeated)); locations.Add(new Location("Boss Completion: skullKingDefeated", ItemType.Boss, () => PlayerData.instance != null && PlayerData.instance.skullKingDefeated)); } } internal static class CrestNames { internal const string CrestItemPrefix = "Crest: "; internal const string CrestLocationPrefix = "Crest Unlock: "; internal static readonly string[] SupportedStartingCrestKeys = new string[7] { "hunter", "wanderer", "reaper", "beast", "architect", "witch", "shaman" }; internal static bool IsSupportedStartingCrestKey(string key) { return SupportedStartingCrestKeys.Contains(key, StringComparer.Ordinal); } internal static string GetInternalCrestName(string key) { return key switch { "hunter" => "Hunter", "wanderer" => "Wanderer", "reaper" => "Reaper", "beast" => "Warrior", "architect" => "Toolmaster", "witch" => "Witch", "shaman" => "Spell", _ => null, }; } internal static string GetPublicCrestName(string internalName) { if (string.IsNullOrWhiteSpace(internalName)) { return internalName; } if (IsHunterInternalName(internalName)) { return "Hunter"; } return internalName switch { "Warrior" => "Beast", "Toolmaster" => "Architect", "Spell" => "Shaman", _ => internalName, }; } internal static bool IsHunterInternalName(string internalName) { if (!string.IsNullOrEmpty(internalName)) { return internalName.StartsWith("Hunter", StringComparison.OrdinalIgnoreCase); } return false; } internal static string GetItemNameFromInternal(string internalName) { return "Crest: " + GetPublicCrestName(internalName); } internal static string GetLocationNameFromInternal(string internalName) { return "Crest Unlock: " + GetPublicCrestName(internalName); } internal static bool HasReceivedAnyCrest(IEnumerable receivedItems) { return receivedItems?.Any((string itemName) => !string.IsNullOrEmpty(itemName) && itemName.StartsWith("Crest: ", StringComparison.OrdinalIgnoreCase)) ?? false; } } internal static class CurrencyLinkAccounting { internal static int CalculateNativeDeathLoss(int before, int after, int nativeCocoonAmount) { int num = Math.Max(0, before); int result = Math.Max(0, num - Math.Max(0, after)); int num2 = Math.Min(num, Math.Max(0, nativeCocoonAmount)); if (num2 <= 0) { return result; } return num2; } internal static int CalculateDeathDebit(int predictedShared, int nativeDeathLoss) { return CalculateDeathDebit(predictedShared, nativeDeathLoss, suppressSharedMutation: false); } internal static int CalculateDeathDebit(int predictedShared, int nativeDeathLoss, bool suppressSharedMutation) { if (suppressSharedMutation) { return 0; } return Math.Min(Math.Max(0, predictedShared), Math.Max(0, nativeDeathLoss)); } internal static int CalculateConfirmedDeathDebit(int requestedDebit, int originalShared, int resultingShared) { int num = Math.Max(0, originalShared); int num2 = Math.Max(0, resultingShared); int val = Math.Max(0, num - num2); return Math.Min(Math.Max(0, requestedDebit), val); } } internal static class CurrencyLinkManager { internal enum LinkedCurrency { Rosaries, ShellShards } private sealed class LinkState { internal readonly LinkedCurrency Kind; internal readonly string DisplayName; internal readonly string StorageKey; internal readonly CurrencyType CurrencyType; internal readonly int SharedCapacity; internal bool Enabled; internal bool StorageStarted; internal bool SharedValueReady; internal bool WasTemporarilyStored; internal bool TemporaryStorageActive; internal int AuthoritativeShared; internal int PendingSharedDelta; internal int PrivateAmount; internal int LastObserved; internal int OfflineBaseline = -1; internal bool NeedsOfflineReconciliation; internal DataStorageElement SubscribedElement; internal DataStorageUpdatedHandler SubscribedHandler; internal LinkState(LinkedCurrency kind, string displayName, string storageKey, CurrencyType currencyType, int sharedCapacity) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) Kind = kind; DisplayName = displayName; StorageKey = storageKey; CurrencyType = currencyType; SharedCapacity = sharedCapacity; } } private struct QueuedStorageUpdate { internal int Generation; internal LinkedCurrency Kind; internal StorageMutationPurpose Purpose; internal int TransactionId; internal bool HasOriginalValue; internal int OriginalValue; internal int Value; internal bool IsAcknowledgement; internal int RequestedDelta; } private enum StorageMutationPurpose { Ordinary, RosaryDeathDebit } private sealed class RosaryDeathRecord { internal int Sequence; internal PlayerData PlayerData; internal int Before; internal int NativeLoss = -1; internal bool SuppressSharedMutation; internal bool DebitSent; internal bool DebitConfirmed; internal int RequestedDebit; internal int RecoverableAmount; internal bool CocoonAvailable = true; internal bool RecoveryRequested; internal bool RecoveryCredited; } internal struct LocalMutationState { internal LinkedCurrency Kind; internal bool Active; internal int Before; internal bool HasMemorySnapshot; internal int MemorySnapshotBefore; internal LocalMutationState(LinkedCurrency linkedCurrency, int value) { Kind = linkedCurrency; Active = true; Before = value; HasMemorySnapshot = false; MemorySnapshotBefore = 0; } } internal struct RosaryCocoonMutationState { internal bool Active; internal int Sequence; internal int OriginalNativeAmount; } [HarmonyPatch(typeof(ToolItem), "CanReload", new Type[] { })] internal static class ToolItem_CanReload_RosaryLink_Diagnostic_Patch { [HarmonyPostfix] private static void Postfix(ToolItem __instance, bool __result) { LogRosaryCannonReloadDiagnostic(__instance, __result); } } [HarmonyPatch(typeof(HeroController), "Die", new Type[] { typeof(bool), typeof(bool) })] internal static class HeroController_Die_RosaryLink_Patch { [HarmonyPrefix] private static void Prefix() { MarkRosaryDeathStarted(); } } [HarmonyPatch(typeof(HeroController), "CheckDeathCatch")] internal static class HeroController_CheckDeathCatch_RosaryLink_Patch { [HarmonyPostfix] private static void Postfix() { CaptureNativeRosaryDeathResult(); } } [HarmonyPatch(typeof(HeroController), "CocoonBroken", new Type[] { typeof(bool), typeof(bool) })] internal static class HeroController_CocoonBroken_RosaryLink_Patch { [HarmonyPrefix] private static void Prefix(out RosaryCocoonMutationState __state) { BeginRosaryCocoonRecovery(out __state); } [HarmonyFinalizer] private static Exception Finalizer(Exception __exception, RosaryCocoonMutationState __state) { EndRosaryCocoonRecovery(__state, __exception); return __exception; } } [HarmonyPatch(typeof(PlayerData), "AddGeo", new Type[] { typeof(int) })] internal static class PlayerData_AddGeo_Patch { [HarmonyPrefix] private static void Prefix(PlayerData __instance, out LocalMutationState __state) { BeginLocalMutation(Rosaries, __instance, out __state); } [HarmonyPostfix] private static void Postfix(PlayerData __instance, LocalMutationState __state) { EndLocalMutation(__instance, __state); } } [HarmonyPatch(typeof(PlayerData), "TakeGeo", new Type[] { typeof(int) })] internal static class PlayerData_TakeGeo_Patch { [HarmonyPrefix] private static void Prefix(PlayerData __instance, out LocalMutationState __state) { BeginLocalMutation(Rosaries, __instance, out __state); } [HarmonyPostfix] private static void Postfix(PlayerData __instance, LocalMutationState __state) { EndLocalMutation(__instance, __state); } } [HarmonyPatch(typeof(PlayerData), "AddShards", new Type[] { typeof(int) })] internal static class PlayerData_AddShards_Patch { [HarmonyPrefix] private static void Prefix(PlayerData __instance, out LocalMutationState __state) { BeginLocalMutation(ShellShards, __instance, out __state); } [HarmonyPostfix] private static void Postfix(PlayerData __instance, LocalMutationState __state) { EndLocalMutation(__instance, __state); } } [HarmonyPatch(typeof(PlayerData), "TakeShards", new Type[] { typeof(int) })] internal static class PlayerData_TakeShards_Patch { [HarmonyPrefix] private static void Prefix(PlayerData __instance, out LocalMutationState __state) { BeginLocalMutation(ShellShards, __instance, out __state); } [HarmonyPostfix] private static void Postfix(PlayerData __instance, LocalMutationState __state) { EndLocalMutation(__instance, __state); } } [HarmonyPatch(typeof(CurrencyManager), "TempStoreCurrency")] internal static class CurrencyManager_TempStoreCurrency_Patch { [HarmonyPostfix] private static void Postfix() { MarkTemporaryStoreApplied(); } } [HarmonyPatch(typeof(CurrencyManager), "RestoreTempStoredCurrency")] internal static class CurrencyManager_RestoreTempStoredCurrency_Patch { [HarmonyPrefix] private static void Prefix() { ignoredLocalMutationDepth++; } [HarmonyFinalizer] private static Exception Finalizer(Exception __exception) { ignoredLocalMutationDepth = Math.Max(0, ignoredLocalMutationDepth - 1); if (__exception == null) { MarkTemporaryRestoreApplied(); } return __exception; } } [HarmonyPatch(typeof(HeroItemsState), "Apply")] internal static class HeroItemsState_Apply_Patch { [HarmonyPostfix] private static void Postfix() { MarkMemorySnapshotApplied(); } } internal const int RosarySharedCapacity = 9999999; internal const int ShellShardSharedCapacity = 400; internal const int MinimumSharedBalance = -9999999; internal const string RosaryStorageKey = "SilksongRandomizer:RosaryLink:v1:LooseRosaries"; internal const string ShellShardStorageKey = "SilksongRandomizer:ShellShardLink:v1:BaseShards"; private static readonly object QueueLock = new object(); private static readonly Queue StorageUpdates = new Queue(); private static readonly Queue StatusMessages = new Queue(); private static readonly Dictionary RosaryDeaths = new Dictionary(); private static readonly LinkState Rosaries = new LinkState(LinkedCurrency.Rosaries, "Rosary Link", "SilksongRandomizer:RosaryLink:v1:LooseRosaries", (CurrencyType)0, 9999999); private static readonly LinkState ShellShards = new LinkState(LinkedCurrency.ShellShards, "Shell Shard Link", "SilksongRandomizer:ShellShardLink:v1:BaseShards", (CurrencyType)1, 400); private static Action statusReporter; private static ArchipelagoSession session; private static SaveState boundSaveState; private static PlayerData boundPlayerData; private static int generation; private static int ignoredLocalMutationDepth; private static int nextRosaryDeathSequence; private static int activeRosaryCocoonSequence; private const long RosaryCannonReloadDiagnosticIntervalTicks = 20000000L; private static long nextRosaryCannonReloadDiagnosticTicks; internal static bool IsEnabled { get { lock (QueueLock) { return Rosaries.Enabled || ShellShards.Enabled; } } } internal static void Initialize(Action reporter = null) { Reset(); statusReporter = reporter; } internal static bool Configure(ArchipelagoSession connectedSession, bool rosaryLink, bool shellShardLink) { Reset(); if (!rosaryLink && !shellShardLink) { return true; } if (connectedSession == null) { QueueStatus("Currency links could not start because their Archipelago session was missing."); return false; } lock (QueueLock) { session = connectedSession; Rosaries.Enabled = rosaryLink; ShellShards.Enabled = shellShardLink; } if (rosaryLink) { QueueStatus("Rosary Link enabled (experimental): loose Rosaries are shared; strings, bank storage and cocoons remain local."); } if (shellShardLink) { QueueStatus("Shell Shard Link enabled (experimental): the base 400 Shards are shared; Tool Pouch overflow remains individual."); } return true; } internal static void Update() { FlushStatusMessages(); if (!IsEnabled || !HasLiveGameplayContext()) { return; } SaveState instance = SaveState.Instance; PlayerData instance2 = PlayerData.instance; if (instance == null || instance2 == null) { boundSaveState = null; boundPlayerData = null; return; } if (boundSaveState != instance || boundPlayerData != instance2) { bool continuingSession = boundSaveState != null || boundPlayerData != null; BindPlayer(instance, instance2, continuingSession); } TryStartStorage(Rosaries, instance, instance2); TryStartStorage(ShellShards, instance, instance2); ProcessStorageUpdates(); SynchronizeState(Rosaries, instance, instance2); SynchronizeState(ShellShards, instance, instance2); } internal static void Reset() { Unsubscribe(Rosaries); Unsubscribe(ShellShards); lock (QueueLock) { generation++; session = null; StorageUpdates.Clear(); StatusMessages.Clear(); } ResetState(Rosaries); ResetState(ShellShards); boundSaveState = null; boundPlayerData = null; ignoredLocalMutationDepth = 0; RosaryDeaths.Clear(); nextRosaryDeathSequence = 0; activeRosaryCocoonSequence = 0; nextRosaryCannonReloadDiagnosticTicks = 0L; } private static void ResetState(LinkState state) { state.Enabled = false; state.StorageStarted = false; state.SharedValueReady = false; state.WasTemporarilyStored = false; state.TemporaryStorageActive = false; state.AuthoritativeShared = 0; state.PendingSharedDelta = 0; state.PrivateAmount = 0; state.LastObserved = 0; state.OfflineBaseline = -1; state.NeedsOfflineReconciliation = false; state.SubscribedElement = null; state.SubscribedHandler = null; } private static void BindPlayer(SaveState saveState, PlayerData playerData, bool continuingSession) { boundSaveState = saveState; boundPlayerData = playerData; BindState(Rosaries, saveState, playerData, continuingSession); BindState(ShellShards, saveState, playerData, continuingSession); } private static void BindState(LinkState state, SaveState saveState, PlayerData playerData, bool continuingSession) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) state.LastObserved = GetCurrent(state, playerData); state.WasTemporarilyStored = IsTemporarilyStored(state, playerData); state.OfflineBaseline = GetSavedBaseline(state, saveState); state.NeedsOfflineReconciliation = ShouldReconcileOfflineBalance(continuingSession, state.OfflineBaseline); if (!state.NeedsOfflineReconciliation) { state.OfflineBaseline = -1; } if (state.Kind == LinkedCurrency.ShellShards) { int privateCapacity = GetPrivateCapacity(state, playerData); int persistentCurrency = MemorySequenceSync.GetPersistentCurrency(playerData, state.CurrencyType, state.LastObserved); state.PrivateAmount = ((saveState.shellShardLinkPrivateShards >= 0) ? Clamp(saveState.shellShardLinkPrivateShards, 0, privateCapacity) : Clamp(Math.Max(0, persistentCurrency - state.SharedCapacity), 0, privateCapacity)); StorePrivateAmount(state); } } internal static bool CanSynchronizeCurrency(bool hasGameManager, bool isGameplayScene, bool hasHero) { return hasGameManager && isGameplayScene && hasHero; } internal static bool CanApplyRemoteDeath(PlayerData playerData) { if (!Rosaries.Enabled) { return true; } if (playerData != null && playerData == boundPlayerData && Rosaries.SharedValueReady && boundSaveState != null && boundSaveState.rosaryLink) { return !IsTemporarilyStored(Rosaries, playerData); } return false; } internal static bool ShouldReconcileOfflineBalance(bool continuingSession, int offlineBaseline) { if (!continuingSession) { return offlineBaseline >= 0; } return false; } private static bool HasLiveGameplayContext() { GameManager unsafeInstance = GameManager.UnsafeInstance; HeroController unsafeInstance2 = HeroController.UnsafeInstance; return CanSynchronizeCurrency((Object)(object)unsafeInstance != (Object)null, (Object)(object)unsafeInstance != (Object)null && unsafeInstance.IsGameplayScene(), (Object)(object)unsafeInstance2 != (Object)null); } private static void TryStartStorage(LinkState state, SaveState saveState, PlayerData playerData) { //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown //IL_00ab: Unknown result type (might be due to invalid IL or missing references) if (!state.Enabled || state.StorageStarted || !IsOptionEnabled(state, saveState)) { return; } ArchipelagoSession val; int currentGeneration; lock (QueueLock) { if (session == null) { return; } val = session; currentGeneration = generation; } DataStorageElement val2 = null; DataStorageUpdatedHandler val3 = null; try { val2 = val.DataStorage[(Scope)2, state.StorageKey]; val3 = (DataStorageUpdatedHandler)delegate(JToken originalValue, JToken newValue, Dictionary arguments) { QueueStorageValue(currentGeneration, state.Kind, StorageMutationPurpose.Ordinary, 0, originalValue, newValue, isAcknowledgement: false, 0); }; val2.OnValueChanged += val3; int current = MemorySequenceSync.GetPersistentCurrency(playerData, state.CurrencyType, GetCurrent(state, playerData)); if (!MemorySequenceSync.HasRecordedSnapshot(playerData) && IsTemporarilyStored(state, playerData)) { current = GetTemporarilyStored(state, playerData); } if (state.Kind == LinkedCurrency.Rosaries && TryGetCapturedPreDeathBalance(playerData, out var balance)) { current = balance; } int num = CalculateInitialSharedValue(current, state.PrivateAmount, state.OfflineBaseline, state.NeedsOfflineReconciliation, state.SharedCapacity); val2.Initialize(JToken.op_Implicit(num)); state.SubscribedElement = val2; state.SubscribedHandler = val3; state.StorageStarted = true; SendSharedDelta(state, 0); } catch (Exception ex) { if (val2 != null && val3 != null) { try { val2.OnValueChanged -= val3; } catch { } } state.SubscribedElement = null; state.SubscribedHandler = null; state.StorageStarted = false; state.Enabled = false; QueueStatus(state.DisplayName + " could not initialize its shared pool: " + ex.Message); } } private static void ProcessStorageUpdates() { List list = new List(); int num; lock (QueueLock) { num = generation; while (StorageUpdates.Count > 0) { list.Add(StorageUpdates.Dequeue()); } } foreach (QueuedStorageUpdate item in list) { if (item.Generation != num) { continue; } LinkState state = GetState(item.Kind); if (state.Enabled) { if (item.IsAcknowledgement) { state.PendingSharedDelta -= item.RequestedDelta; } state.AuthoritativeShared = Clamp(item.Value, -9999999, state.SharedCapacity); state.SharedValueReady = true; if (item.IsAcknowledgement && item.Purpose == StorageMutationPurpose.RosaryDeathDebit) { ConfirmRosaryDeathDebit(item.TransactionId, item.HasOriginalValue ? item.OriginalValue : item.Value, item.Value); } } } } private static void SynchronizeState(LinkState state, SaveState saveState, PlayerData playerData) { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) if (!state.Enabled || !state.SharedValueReady || !IsOptionEnabled(state, saveState) || (state.Kind == LinkedCurrency.Rosaries && TryProcessRosaryDeath(state, playerData))) { return; } int privateCapacity = GetPrivateCapacity(state, playerData); state.PrivateAmount = Clamp(state.PrivateAmount, 0, privateCapacity); StorePrivateAmount(state); if (IsTemporarilyStored(state, playerData)) { state.WasTemporarilyStored = true; state.LastObserved = GetCurrent(state, playerData); } else if (state.NeedsOfflineReconciliation) { int offlineBaseline = state.OfflineBaseline; state.NeedsOfflineReconciliation = false; state.OfflineBaseline = -1; state.WasTemporarilyStored = false; ReconcileLocalMutation(state, playerData, offlineBaseline, MemorySequenceSync.GetPersistentCurrency(playerData, state.CurrencyType, GetCurrent(state, playerData))); } else if (state.WasTemporarilyStored) { state.WasTemporarilyStored = false; ApplyLinkedValue(state, playerData, refreshCounter: true); RefreshCounter(state, GetCurrent(state, playerData)); } else { int current = GetCurrent(state, playerData); if (current != state.LastObserved) { int snapshotValue; bool hasMemorySnapshot = MemorySequenceSync.TryCaptureCurrency(playerData, state.CurrencyType, out snapshotValue); ReconcileLocalMutation(state, playerData, state.LastObserved, current, hasMemorySnapshot, snapshotValue); } else { ApplyLinkedValue(state, playerData, refreshCounter: true); } } } private static bool BeginLocalMutation(LinkState state, PlayerData playerData, out LocalMutationState mutation) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) mutation = default(LocalMutationState); if (!OwnsLocalMutation(state, playerData)) { return false; } mutation = new LocalMutationState(state.Kind, GetCurrent(state, playerData)); mutation.HasMemorySnapshot = MemorySequenceSync.TryCaptureCurrency(playerData, state.CurrencyType, out var snapshotValue); mutation.MemorySnapshotBefore = snapshotValue; return true; } internal static bool OwnsLocalMutation(PlayerData playerData, CurrencyType currencyType) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 if ((int)currencyType == 0) { return OwnsLocalMutation(Rosaries, playerData); } if ((int)currencyType == 1) { return OwnsLocalMutation(ShellShards, playerData); } return false; } private static bool OwnsLocalMutation(LinkState state, PlayerData playerData) { if (state != null && state.Enabled && state.SharedValueReady && ignoredLocalMutationDepth <= 0 && boundSaveState != null && IsOptionEnabled(state, boundSaveState) && playerData != null) { return playerData == boundPlayerData; } return false; } private static void EndLocalMutation(PlayerData playerData, LocalMutationState mutation) { if (mutation.Active && playerData != null && playerData == boundPlayerData && ignoredLocalMutationDepth <= 0) { LinkState state = GetState(mutation.Kind); int current = GetCurrent(state, playerData); if (current != mutation.Before) { ReconcileLocalMutation(state, playerData, mutation.Before, current, mutation.HasMemorySnapshot, mutation.MemorySnapshotBefore); } else { state.LastObserved = current; } } } private static void ReconcileLocalMutation(LinkState state, PlayerData playerData, int before, int after, bool hasMemorySnapshot = false, int memorySnapshotBefore = 0) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) int predictedShared = GetPredictedShared(state); int privateAmount = state.PrivateAmount; int[] array = CalculateLocalReservoirs(predictedShared, state.PrivateAmount, before, after, state.SharedCapacity, GetPrivateCapacity(state, playerData)); int num = array[0] - predictedShared; state.PrivateAmount = array[1]; StorePrivateAmount(state); if (num != 0 && !SendSharedDelta(state, num)) { state.PrivateAmount = privateAmount; StorePrivateAmount(state); state.LastObserved = after; if (hasMemorySnapshot) { MemorySequenceSync.RebaseCurrencyDelta(playerData, state.CurrencyType, memorySnapshotBefore, before, after); } RefreshCounter(state, after); } else if (IsTemporarilyStored(state, playerData)) { SetCurrent(state, playerData, 0); state.LastObserved = 0; state.WasTemporarilyStored = true; } else { ApplyLinkedValue(state, playerData, after != GetVisibleValue(state, playerData)); } } internal static int[] CalculateLocalReservoirs(int shared, int personal, int before, int after, int sharedCapacity, int personalCapacity) { sharedCapacity = Math.Max(0, sharedCapacity); personalCapacity = Math.Max(0, personalCapacity); shared = Clamp(shared, -9999999, sharedCapacity); personal = Clamp(personal, 0, personalCapacity); long num = (long)after - (long)before; if (num > 0) { int num2 = Math.Max(0, sharedCapacity - shared); int num3 = (int)Math.Min(num, num2); shared += num3; long num4 = num - num3; personal = (int)Math.Min(personalCapacity, personal + num4); } else if (num < 0) { long num5 = -num; int num6 = (int)Math.Min(num5, personal); personal -= num6; num5 -= num6; shared = (int)Math.Max(-9999999L, shared - num5); } return new int[2] { shared, personal }; } internal static int ComposeVisibleCurrency(int shared, int personal, int currentMaximum) { long val = (long)Math.Max(0, shared) + (long)Math.Max(0, personal); return (int)Math.Min(Math.Max(0, currentMaximum), val); } internal static int CalculateInitialSharedValue(int current, int personal, int offlineBaseline, bool needsOfflineReconciliation, int sharedCapacity) { return Clamp(((needsOfflineReconciliation && offlineBaseline >= 0) ? offlineBaseline : current) - Math.Max(0, personal), 0, Math.Max(0, sharedCapacity)); } private static int GetVisibleValue(LinkState state, PlayerData playerData) { return ComposeVisibleCurrency(GetPredictedShared(state), state.PrivateAmount, GetCurrentMaximum(state, playerData)); } private static void ApplyLinkedValue(LinkState state, PlayerData playerData, bool refreshCounter) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (playerData == null || IsTemporarilyStored(state, playerData)) { return; } int visibleValue = GetVisibleValue(state, playerData); if (GetCurrent(state, playerData) != visibleValue) { SetCurrent(state, playerData, visibleValue); if (refreshCounter) { RefreshCounter(state, visibleValue); } } MemorySequenceSync.MirrorCurrency(playerData, state.CurrencyType, visibleValue); state.LastObserved = visibleValue; StoreBaseline(state, visibleValue); } private static bool SendSharedDelta(LinkState state, int delta) { return SendSharedDelta(state, delta, -9999999, StorageMutationPurpose.Ordinary, 0); } private static bool SendSharedDelta(LinkState state, int delta, int minimumSharedBalance, StorageMutationPurpose purpose, int transactionId) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown ArchipelagoSession val; int currentGeneration; lock (QueueLock) { if (!state.Enabled || session == null || !state.StorageStarted) { return false; } val = session; currentGeneration = generation; } DataStorageUpdatedHandler val2 = (DataStorageUpdatedHandler)delegate(JToken originalValue, JToken newValue, Dictionary arguments) { QueueStorageValue(currentGeneration, state.Kind, purpose, transactionId, originalValue, newValue, isAcknowledgement: true, delta); }; state.PendingSharedDelta += delta; try { val.DataStorage[(Scope)2, state.StorageKey] = val.DataStorage[(Scope)2, state.StorageKey] + delta + Operation.Max(minimumSharedBalance) + Operation.Min(state.SharedCapacity) + Callback.Add(val2); return true; } catch (Exception ex) { state.PendingSharedDelta -= delta; state.Enabled = false; Unsubscribe(state); state.SubscribedElement = null; state.SubscribedHandler = null; state.StorageStarted = false; state.SharedValueReady = false; QueueStatus(state.DisplayName + " paused after a shared-pool update failed; your local currency change was preserved and will be reconciled after reconnecting. " + ex.Message); return false; } } private static void QueueStorageValue(int callbackGeneration, LinkedCurrency kind, StorageMutationPurpose purpose, int transactionId, JToken originalValue, JToken value, bool isAcknowledgement, int requestedDelta) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Invalid comparison between Unknown and I4 if (value == null || (int)value.Type == 10) { QueueStatus(GetState(kind).DisplayName + " ignored an empty shared-pool update."); return; } int value2; try { value2 = value.ToObject(); } catch (Exception ex) { QueueStatus(GetState(kind).DisplayName + " received an invalid shared value: " + ex.Message); return; } bool hasOriginalValue = false; int originalValue2 = 0; if (originalValue != null && (int)originalValue.Type != 10) { try { originalValue2 = originalValue.ToObject(); hasOriginalValue = true; } catch { } } lock (QueueLock) { if (callbackGeneration == generation) { StorageUpdates.Enqueue(new QueuedStorageUpdate { Generation = callbackGeneration, Kind = kind, Purpose = purpose, TransactionId = transactionId, HasOriginalValue = hasOriginalValue, OriginalValue = originalValue2, Value = value2, IsAcknowledgement = isAcknowledgement, RequestedDelta = requestedDelta }); } } } private static int GetPredictedShared(LinkState state) { long val = (long)state.AuthoritativeShared + (long)state.PendingSharedDelta; return (int)Math.Min(state.SharedCapacity, Math.Max(-9999999L, val)); } private static int GetPrivateCapacity(LinkState state, PlayerData playerData) { if (state.Kind != LinkedCurrency.ShellShards || playerData == null) { return 0; } return Math.Max(0, GetCurrentMaximum(state, playerData) - 400); } private static int GetCurrentMaximum(LinkState state, PlayerData playerData) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) try { return Gameplay.GetCurrencyCap(state.CurrencyType); } catch { return state.SharedCapacity + ((state.Kind == LinkedCurrency.ShellShards && playerData != null) ? (Math.Max(0, playerData.ToolPouchUpgrades) * 100) : 0); } } private static int GetCurrent(LinkState state, PlayerData playerData) { if (state.Kind != LinkedCurrency.Rosaries) { return playerData.ShellShards; } return playerData.geo; } private static void SetCurrent(LinkState state, PlayerData playerData, int value) { if (state.Kind == LinkedCurrency.Rosaries) { playerData.geo = value; } else { playerData.ShellShards = value; } } private static int GetTemporarilyStored(LinkState state, PlayerData playerData) { if (state.Kind != LinkedCurrency.Rosaries) { return playerData.TempShellShardStore; } return playerData.TempGeoStore; } private static bool IsTemporarilyStored(LinkState state, PlayerData playerData) { if (playerData != null) { if (!state.TemporaryStorageActive) { return GetTemporarilyStored(state, playerData) > 0; } return true; } return false; } private static bool IsMemorySuspended(PlayerData playerData) { return playerData?.HasStoredMemoryState ?? false; } private static bool IsOptionEnabled(LinkState state, SaveState saveState) { if (saveState != null) { if (state.Kind != LinkedCurrency.Rosaries) { return saveState.shellShardLink; } return saveState.rosaryLink; } return false; } private static LinkState GetState(LinkedCurrency kind) { if (kind != LinkedCurrency.Rosaries) { return ShellShards; } return Rosaries; } private static void StorePrivateAmount(LinkState state) { if (state.Kind == LinkedCurrency.ShellShards && boundSaveState != null && boundSaveState.shellShardLink) { boundSaveState.shellShardLinkPrivateShards = Math.Max(0, state.PrivateAmount); } } private static int GetSavedBaseline(LinkState state, SaveState saveState) { if (saveState == null) { return -1; } if (state.Kind != LinkedCurrency.Rosaries) { return saveState.shellShardLinkLastSyncedBalance; } return saveState.rosaryLinkLastSyncedBalance; } private static void StoreBaseline(LinkState state, int value) { if (boundSaveState != null && IsOptionEnabled(state, boundSaveState)) { value = Math.Max(0, value); if (state.Kind == LinkedCurrency.Rosaries) { boundSaveState.rosaryLinkLastSyncedBalance = value; } else { boundSaveState.shellShardLinkLastSyncedBalance = value; } } } private static bool TryGetCapturedPreDeathBalance(PlayerData playerData, out int balance) { balance = 0; if (activeRosaryCocoonSequence == 0 || !RosaryDeaths.TryGetValue(activeRosaryCocoonSequence, out var value) || value.DebitSent || value.PlayerData != playerData) { return false; } balance = value.Before; return true; } private static void MarkRosaryDeathStarted() { if (!Rosaries.Enabled || !PlayerData.HasInstance) { return; } PlayerData instance = PlayerData.instance; if (instance == null) { return; } if (activeRosaryCocoonSequence != 0 && RosaryDeaths.TryGetValue(activeRosaryCocoonSequence, out var value)) { if (value.NativeLoss < 0) { value.NativeLoss = CurrencyLinkAccounting.CalculateNativeDeathLoss(value.Before, instance.geo, instance.HeroCorpseMoneyPool); } value.CocoonAvailable = false; if (value.DebitConfirmed && !value.RecoveryRequested) { RosaryDeaths.Remove(value.Sequence); } } int num = ++nextRosaryDeathSequence; if (num <= 0) { nextRosaryDeathSequence = 1; num = 1; } RosaryDeathRecord rosaryDeathRecord = new RosaryDeathRecord { Sequence = num, PlayerData = instance, Before = Math.Max(0, instance.geo), SuppressSharedMutation = DeathLinkManager.IsRemoteDeathApplicationInFlight }; RosaryDeaths[num] = rosaryDeathRecord; activeRosaryCocoonSequence = num; if (instance == boundPlayerData) { Rosaries.LastObserved = rosaryDeathRecord.Before; } } private static void CaptureNativeRosaryDeathResult() { if (activeRosaryCocoonSequence == 0 || !PlayerData.HasInstance || !RosaryDeaths.TryGetValue(activeRosaryCocoonSequence, out var value) || value.DebitSent) { return; } PlayerData instance = PlayerData.instance; if (instance == value.PlayerData) { value.NativeLoss = CurrencyLinkAccounting.CalculateNativeDeathLoss(value.Before, instance.geo, instance.HeroCorpseMoneyPool); if (value.SuppressSharedMutation) { instance.HeroCorpseMoneyPool = 0; } } } private static bool TryProcessRosaryDeath(LinkState state, PlayerData playerData) { if (TryCreditPendingRosaryRecovery()) { return true; } RosaryDeathRecord value = null; if (activeRosaryCocoonSequence != 0) { RosaryDeaths.TryGetValue(activeRosaryCocoonSequence, out value); if (value != null && (value.DebitSent || value.PlayerData != playerData)) { value = null; } } if (value == null) { foreach (RosaryDeathRecord value2 in RosaryDeaths.Values) { if (!value2.DebitSent && value2.PlayerData == playerData) { value = value2; break; } } } if (value == null) { return false; } int current2 = GetCurrent(state, playerData); if (value.NativeLoss < 0) { value.NativeLoss = CurrencyLinkAccounting.CalculateNativeDeathLoss(value.Before, current2, playerData.HeroCorpseMoneyPool); } state.LastObserved = current2; int num = (value.RequestedDebit = CurrencyLinkAccounting.CalculateDeathDebit(GetPredictedShared(state), value.NativeLoss, value.SuppressSharedMutation)); if (value.SuppressSharedMutation) { value.DebitSent = true; value.DebitConfirmed = true; value.RecoverableAmount = 0; SetTrackedCocoonAmount(value, 0); ApplyLinkedValue(state, playerData, refreshCounter: true); if (value.RecoveryRequested || !value.CocoonAvailable) { RosaryDeaths.Remove(value.Sequence); } return true; } if (num <= 0) { value.DebitSent = true; value.DebitConfirmed = true; value.RecoverableAmount = 0; SetTrackedCocoonAmount(value, 0); ApplyLinkedValue(state, playerData, refreshCounter: true); if (value.RecoveryRequested || !value.CocoonAvailable) { RosaryDeaths.Remove(value.Sequence); } return true; } if (!SendSharedDelta(state, -num, 0, StorageMutationPurpose.RosaryDeathDebit, value.Sequence)) { value.CocoonAvailable = false; if (activeRosaryCocoonSequence == value.Sequence) { activeRosaryCocoonSequence = 0; } RosaryDeaths.Remove(value.Sequence); return true; } value.DebitSent = true; SetTrackedCocoonAmount(value, num); ApplyLinkedValue(state, playerData, refreshCounter: true); return true; } private static void ConfirmRosaryDeathDebit(int sequence, int originalShared, int resultingShared) { if (RosaryDeaths.TryGetValue(sequence, out var value)) { value.DebitConfirmed = true; value.RecoverableAmount = CurrencyLinkAccounting.CalculateConfirmedDeathDebit(value.RequestedDebit, originalShared, resultingShared); SetTrackedCocoonAmount(value, value.RecoverableAmount); if (value.RecoveryRequested) { TryCreditRosaryRecovery(value); } else if (!value.CocoonAvailable) { RosaryDeaths.Remove(value.Sequence); } } } private static void SetTrackedCocoonAmount(RosaryDeathRecord death, int amount) { if (death != null && death.CocoonAvailable && !death.RecoveryRequested && activeRosaryCocoonSequence == death.Sequence && death.PlayerData != null) { death.PlayerData.HeroCorpseMoneyPool = Math.Max(0, amount); } } private static void BeginRosaryCocoonRecovery(out RosaryCocoonMutationState mutation) { mutation = default(RosaryCocoonMutationState); if (!Rosaries.Enabled || activeRosaryCocoonSequence == 0 || !PlayerData.HasInstance || !RosaryDeaths.TryGetValue(activeRosaryCocoonSequence, out var value) || !value.CocoonAvailable) { return; } PlayerData instance = PlayerData.instance; if (instance == value.PlayerData) { int num = Math.Max(0, instance.HeroCorpseMoneyPool); if (value.NativeLoss < 0) { value.NativeLoss = CurrencyLinkAccounting.CalculateNativeDeathLoss(value.Before, instance.geo, num); } mutation = new RosaryCocoonMutationState { Active = true, Sequence = value.Sequence, OriginalNativeAmount = num }; value.RecoveryRequested = true; instance.HeroCorpseMoneyPool = 0; } } private static void EndRosaryCocoonRecovery(RosaryCocoonMutationState mutation, Exception exception) { if (!mutation.Active || !RosaryDeaths.TryGetValue(mutation.Sequence, out var value)) { return; } if (exception != null) { value.RecoveryRequested = false; if (value.PlayerData != null) { value.PlayerData.HeroCorpseMoneyPool = mutation.OriginalNativeAmount; } return; } value.CocoonAvailable = false; if (activeRosaryCocoonSequence == value.Sequence) { activeRosaryCocoonSequence = 0; } if (value.DebitConfirmed) { TryCreditRosaryRecovery(value); } } private static bool TryCreditPendingRosaryRecovery() { foreach (RosaryDeathRecord item in new List(RosaryDeaths.Values)) { if (item.RecoveryRequested && item.DebitConfirmed && !item.RecoveryCredited && TryCreditRosaryRecovery(item)) { return true; } } return false; } private static bool TryCreditRosaryRecovery(RosaryDeathRecord death) { if (death == null || !death.RecoveryRequested || !death.DebitConfirmed || death.RecoveryCredited) { return false; } int num = Math.Max(0, death.RecoverableAmount); if (num == 0) { death.RecoveryCredited = true; RosaryDeaths.Remove(death.Sequence); return true; } if (!Rosaries.Enabled || !Rosaries.SharedValueReady || death.PlayerData == null || death.PlayerData != boundPlayerData || IsTemporarilyStored(Rosaries, death.PlayerData)) { return false; } try { death.RecoveryCredited = true; CurrencyManager.AddCurrency(num, (CurrencyType)0, true); RosaryDeaths.Remove(death.Sequence); return true; } catch (Exception ex) { death.RecoveryCredited = false; QueueStatus("Rosary Link could not restore its confirmed death cocoon yet: " + ex.Message); return false; } } private static void RefreshCounter(LinkState state, int value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) try { CurrencyCounter.ToValue(value, state.CurrencyType); } catch (Exception ex) { LogDirect(state.DisplayName + " could not refresh its currency display: " + ex.Message, warning: true); } } private static void MarkTemporaryStoreApplied() { PlayerData instance = PlayerData.instance; if (instance != null && instance == boundPlayerData) { MarkTemporaryStoreApplied(Rosaries, instance); MarkTemporaryStoreApplied(ShellShards, instance); } } private static void MarkTemporaryStoreApplied(LinkState state, PlayerData playerData) { if (state.Enabled) { state.TemporaryStorageActive = true; if (state.SharedValueReady) { state.WasTemporarilyStored = true; state.LastObserved = GetCurrent(state, playerData); } } } private static void MarkTemporaryRestoreApplied() { PlayerData instance = PlayerData.instance; if (instance != null && instance == boundPlayerData) { MarkTemporaryRestoreApplied(Rosaries, instance); MarkTemporaryRestoreApplied(ShellShards, instance); } } private static void MarkTemporaryRestoreApplied(LinkState state, PlayerData playerData) { if (state.Enabled) { state.TemporaryStorageActive = false; if (state.SharedValueReady) { state.LastObserved = GetCurrent(state, playerData); state.WasTemporarilyStored = true; } } } private static void MarkMemorySnapshotApplied() { PlayerData instance = PlayerData.instance; if (instance != null && instance == boundPlayerData) { MarkMemorySnapshotApplied(Rosaries, instance); MarkMemorySnapshotApplied(ShellShards, instance); } } private static void MarkMemorySnapshotApplied(LinkState state, PlayerData playerData) { if (state.Enabled && state.SharedValueReady) { state.LastObserved = GetCurrent(state, playerData); } } private static void Unsubscribe(LinkState state) { if (state.SubscribedElement == null || state.SubscribedHandler == null) { return; } try { state.SubscribedElement.OnValueChanged -= state.SubscribedHandler; } catch (Exception ex) { LogDirect(state.DisplayName + " cleanup warning: " + ex.Message, warning: true); } } private static void QueueStatus(string message) { if (string.IsNullOrWhiteSpace(message)) { return; } lock (QueueLock) { StatusMessages.Enqueue(message); } } private static void FlushStatusMessages() { List list = new List(); lock (QueueLock) { while (StatusMessages.Count > 0) { list.Add(StatusMessages.Dequeue()); } } foreach (string item in list) { LogDirect(item, warning: false); try { statusReporter?.Invoke(item); } catch { } } } private static void LogDirect(string message, bool warning) { if (RandomizerPlugin.Log != null) { if (warning) { RandomizerPlugin.Log.LogWarning((object)("[RANDOMIZER] " + message)); } else { RandomizerPlugin.Log.LogInfo((object)("[RANDOMIZER] " + message)); } } } private static void LogRosaryCannonReloadDiagnostic(ToolItem tool, bool canReload) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) SaveState instance = SaveState.Instance; if ((Object)(object)tool == (Object)null || !Rosaries.Enabled || instance == null || !instance.rosaryLink || !string.Equals(tool.name, "Rosary Cannon", StringComparison.Ordinal)) { return; } long ticks = DateTime.UtcNow.Ticks; if (ticks < nextRosaryCannonReloadDiagnosticTicks) { return; } nextRosaryCannonReloadDiagnosticTicks = ticks + 20000000; try { PlayerData instance2 = PlayerData.instance; Data savedData = tool.SavedData; int toolStorageAmount = ToolItemManager.GetToolStorageAmount(tool); bool flag = IsMemorySuspended(instance2); bool flag2 = IsTemporarilyStored(Rosaries, instance2); LogDirect("Rosary Cannon reload diagnostic: canReload=" + canReload + ", amountLeft=" + savedData.AmountLeft + ", storage=" + toolStorageAmount + ", nativeGeo=" + (instance2?.geo ?? (-1)) + ", tempGeo=" + (instance2?.TempGeoStore ?? (-1)) + ", memorySuspended=" + flag + ", temporarilyStored=" + flag2 + ", temporaryActive=" + Rosaries.TemporaryStorageActive + ", storageStarted=" + Rosaries.StorageStarted + ", sharedReady=" + Rosaries.SharedValueReady + ", authoritative=" + Rosaries.AuthoritativeShared + ", pending=" + Rosaries.PendingSharedDelta + ", predicted=" + GetPredictedShared(Rosaries) + ", lastObserved=" + Rosaries.LastObserved + ", boundSave=" + (instance == boundSaveState) + ", boundPlayer=" + (instance2 == boundPlayerData) + ", ignoredMutationDepth=" + ignoredLocalMutationDepth, !canReload); } catch (Exception ex) { LogDirect("Rosary Cannon reload diagnostic could not read its runtime state: " + ex.Message, warning: true); } } private static int Clamp(int value, int minimum, int maximum) { return Math.Min(Math.Max(value, minimum), maximum); } } internal static class DeathLinkManager { private const float RemoteDeathStartTimeoutSeconds = 5f; private static readonly object StateLock = new object(); private static readonly Queue QueuedStatusMessages = new Queue(); private static Action statusReporter; private static DeathLinkService service; private static HeroController subscribedHero; private static DeathLink pendingRemoteDeath; private static DeathLink remoteDeathInFlight; private static string sourcePlayer = string.Empty; private static bool enabled; private static bool localDeathReported; private static bool suppressNextLocalDeath; private static float remoteDeathStartedAt; private static string lastReceivedSource = string.Empty; private static string lastReceivedCause = string.Empty; internal static bool IsEnabled { get { lock (StateLock) { return enabled; } } } internal static bool HasPendingRemoteDeath { get { lock (StateLock) { return pendingRemoteDeath != (DeathLink)null || remoteDeathInFlight != (DeathLink)null; } } } internal static bool IsRemoteDeathApplicationInFlight { get { lock (StateLock) { return suppressNextLocalDeath && remoteDeathInFlight != (DeathLink)null; } } } internal static string LastReceivedSource { get { lock (StateLock) { return lastReceivedSource; } } } internal static string LastReceivedCause { get { lock (StateLock) { return lastReceivedCause; } } } internal static void Initialize(Action reporter = null) { Reset(); statusReporter = reporter; } internal static bool Configure(ArchipelagoSession session, string source, bool shouldEnable) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown Reset(); if (!shouldEnable) { return true; } if (session == null || string.IsNullOrWhiteSpace(source)) { QueueStatus("DeathLink could not start because its session or slot name was missing."); return false; } DeathLinkService val = null; try { val = DeathLinkProvider.CreateDeathLinkService(session); val.OnDeathLinkReceived += new DeathLinkReceivedHandler(OnDeathLinkReceived); val.EnableDeathLink(); lock (StateLock) { service = val; sourcePlayer = source.Trim(); enabled = true; } QueueStatus("DeathLink enabled for " + source.Trim() + "."); return true; } catch (Exception ex) { if (val != null) { val.OnDeathLinkReceived -= new DeathLinkReceivedHandler(OnDeathLinkReceived); try { val.DisableDeathLink(); } catch { } } lock (StateLock) { service = null; sourcePlayer = string.Empty; enabled = false; } QueueStatus("DeathLink could not be enabled: " + ex.Message); return false; } } internal static void Update() { FlushQueuedStatus(); bool flag; lock (StateLock) { flag = enabled; } if (!flag) { DetachHero(); return; } SynchronizeHeroSubscription(); RecoverTimedOutRemoteDeath(); ResetLocalDeathLatchAfterRespawn(); if (!TryGetSafeRemoteDeathTarget(out var hero, out var playerData)) { return; } lock (StateLock) { DeathLink val = pendingRemoteDeath; if (val == (DeathLink)null) { return; } pendingRemoteDeath = null; remoteDeathInFlight = val; } suppressNextLocalDeath = true; remoteDeathStartedAt = Time.realtimeSinceStartup; try { playerData.health = 0; try { EventRegister.SendEvent(EventRegisterEvents.HealthUpdate, (GameObject)null); } catch (Exception ex) { QueueStatus("DeathLink health UI refresh failed: " + ex.Message); } hero.CheckDeathCatch(); } catch (Exception ex2) { suppressNextLocalDeath = false; remoteDeathStartedAt = 0f; lock (StateLock) { if (pendingRemoteDeath == (DeathLink)null) { pendingRemoteDeath = remoteDeathInFlight; } remoteDeathInFlight = null; } QueueStatus("DeathLink could not be applied yet: " + ex2.Message); } } internal static void Reset() { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown DeathLinkService val; lock (StateLock) { enabled = false; val = service; service = null; sourcePlayer = string.Empty; pendingRemoteDeath = null; remoteDeathInFlight = null; lastReceivedSource = string.Empty; lastReceivedCause = string.Empty; QueuedStatusMessages.Clear(); } if (val != null) { val.OnDeathLinkReceived -= new DeathLinkReceivedHandler(OnDeathLinkReceived); try { val.DisableDeathLink(); } catch (Exception ex) { LogDirect("DeathLink cleanup warning: " + ex.Message, warning: true); } } DetachHero(); localDeathReported = false; suppressNextLocalDeath = false; remoteDeathStartedAt = 0f; } private static void OnDeathLinkReceived(DeathLink deathLink) { if (deathLink == (DeathLink)null) { return; } string text = (string.IsNullOrWhiteSpace(deathLink.Source) ? "Another player" : deathLink.Source); string text2 = (string.IsNullOrWhiteSpace(deathLink.Cause) ? (text + " died.") : deathLink.Cause); lock (StateLock) { if (enabled) { pendingRemoteDeath = deathLink; lastReceivedSource = text; lastReceivedCause = text2; QueuedStatusMessages.Enqueue("DeathLink received from " + text + ": " + text2); } } } private static void SynchronizeHeroSubscription() { HeroController silentInstance = HeroController.SilentInstance; if (silentInstance == subscribedHero) { return; } DetachHero(); subscribedHero = silentInstance; if ((Object)(object)subscribedHero != (Object)null) { subscribedHero.OnDeath += OnHeroDeath; } localDeathReported = false; suppressNextLocalDeath = false; remoteDeathStartedAt = 0f; lock (StateLock) { remoteDeathInFlight = null; } } private static void DetachHero() { if ((Object)(object)subscribedHero != (Object)null) { subscribedHero.OnDeath -= OnHeroDeath; } subscribedHero = null; } private static void OnHeroDeath() { //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Expected O, but got Unknown if (localDeathReported) { return; } localDeathReported = true; if (suppressNextLocalDeath) { suppressNextLocalDeath = false; remoteDeathStartedAt = 0f; DeathLink val; lock (StateLock) { val = remoteDeathInFlight; remoteDeathInFlight = null; } if (val != (DeathLink)null) { string text = (string.IsNullOrWhiteSpace(val.Source) ? "another player" : val.Source); string text2 = (string.IsNullOrWhiteSpace(val.Cause) ? (text + " died.") : val.Cause); QueueStatus("DeathLink applied from " + text + ": " + text2); } return; } DeathLinkService val2; string text3; lock (StateLock) { if (!enabled || service == null) { return; } val2 = service; text3 = sourcePlayer; } string currentSceneName = GetCurrentSceneName(); string text4 = text3 + " died"; if (!string.IsNullOrWhiteSpace(currentSceneName)) { text4 = text4 + " in " + currentSceneName; } text4 += "."; try { val2.SendDeathLink(new DeathLink(text3, text4)); QueueStatus("DeathLink sent: " + text4); } catch (Exception ex) { QueueStatus("DeathLink could not be sent: " + ex.Message); } } private static bool TryGetSafeRemoteDeathTarget(out HeroController hero, out PlayerData playerData) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Invalid comparison between Unknown and I4 //IL_00bd: Unknown result type (might be due to invalid IL or missing references) hero = subscribedHero; playerData = null; lock (StateLock) { if (!enabled || pendingRemoteDeath == (DeathLink)null || remoteDeathInFlight != (DeathLink)null) { return false; } } GameManager silentInstance = GameManager.SilentInstance; if ((Object)(object)silentInstance == (Object)null || (Object)(object)hero == (Object)null || hero.cState == null || !PlayerData.HasInstance) { return false; } playerData = PlayerData.instance; if ((int)silentInstance.GameState != 4 || silentInstance.isPaused || silentInstance.IsLoadingSceneTransition || silentInstance.IsInSceneTransition || silentInstance.RespawningHero || playerData.disablePause || playerData.isInvincible || (int)hero.transitionState != 0 || hero.cState.transitioning || hero.cState.dead || hero.cState.hazardDeath || hero.cState.hazardRespawning || BossSceneController.IsTransitioning) { return false; } if (!CurrencyLinkManager.CanApplyRemoteDeath(playerData)) { return false; } return hero.CanTakeDamage(); } private static void ResetLocalDeathLatchAfterRespawn() { if (localDeathReported && !suppressNextLocalDeath && !((Object)(object)subscribedHero == (Object)null) && subscribedHero.cState != null && PlayerData.HasInstance) { PlayerData instance = PlayerData.instance; if (!subscribedHero.cState.dead && !subscribedHero.cState.hazardDeath && !subscribedHero.cState.hazardRespawning && instance.health > 0) { localDeathReported = false; } } } private static void RecoverTimedOutRemoteDeath() { if (!suppressNextLocalDeath || Time.realtimeSinceStartup - remoteDeathStartedAt < 5f) { return; } DeathLink val; lock (StateLock) { val = remoteDeathInFlight; remoteDeathInFlight = null; } bool num = (Object)(object)subscribedHero != (Object)null && subscribedHero.cState != null && subscribedHero.cState.dead; suppressNextLocalDeath = false; remoteDeathStartedAt = 0f; if (num || !(val != (DeathLink)null)) { return; } lock (StateLock) { if (pendingRemoteDeath == (DeathLink)null) { pendingRemoteDeath = val; } } QueueStatus("DeathLink death did not start; it will retry when safe."); } private static string GetCurrentSceneName() { //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) GameManager silentInstance = GameManager.SilentInstance; if ((Object)(object)silentInstance != (Object)null && !string.IsNullOrWhiteSpace(silentInstance.sceneName)) { return silentInstance.sceneName; } Scene activeScene = SceneManager.GetActiveScene(); return ((Scene)(ref activeScene)).name; } private static void QueueStatus(string message) { if (string.IsNullOrWhiteSpace(message)) { return; } lock (StateLock) { QueuedStatusMessages.Enqueue(message); } } private static void FlushQueuedStatus() { while (true) { string message; lock (StateLock) { if (QueuedStatusMessages.Count == 0) { break; } message = QueuedStatusMessages.Dequeue(); } LogDirect(message, warning: false); } } private static void LogDirect(string message, bool warning) { try { statusReporter?.Invoke(message); } catch (Exception ex) { Debug.LogWarning((object)("[RANDOMIZER] DeathLink status reporter failed: " + ex.Message)); } if (RandomizerPlugin.Log != null) { if (warning) { RandomizerPlugin.Log.LogWarning((object)("[RANDOMIZER] " + message)); } else { RandomizerPlugin.Log.LogInfo((object)("[RANDOMIZER] " + message)); } } else if (warning) { Debug.LogWarning((object)("[RANDOMIZER] " + message)); } else { Debug.Log((object)("[RANDOMIZER] " + message)); } } } internal static class FastTravelUtil { private enum WarpDestination { BoneBottom, Bellhart, Songclave, Terminus, Greymoor, WidowShrine } private const string BellwayEntryGateName = "door_fastTravelExit"; private const string BellhartSceneName = "Belltown"; private const string BellhartEntryGateName = "door5"; private const string SongclaveBellSceneName = "Bellshrine_Enclave"; private const string SongclaveBellEntryGateName = "left1"; private const string TerminusSceneName = "Tube_Hub"; private const string TerminusEntryGateName = "door_tubeEnter"; private const string ActThreeWakeSceneName = "Song_Enclave"; private const string ActThreeWakeEntryGateName = "door_act3_wakeUp"; private const string GreymoorCaravanSceneName = "Greymoor_08"; private const string GreymoorCaravanEntryGateName = "left2"; internal static bool CanTeleportToPreferredHub(out string reason) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Invalid comparison between Unknown and I4 if (SaveState.Instance == null) { reason = "Load a randomizer save before warping."; return false; } GameManager instance = GameManager.instance; HeroController instance2 = HeroController.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null) { reason = "The F4 warp is only available during gameplay."; return false; } if ((int)instance.GameState != 4 || !instance.IsGameplayScene()) { reason = "Finish the current menu or cutscene before warping."; return false; } PlayerData instance3 = PlayerData.instance; bool flag = IsActThreeWakeEntry(instance, instance2); if (instance3 != null && instance3.blackThreadWorld && (!instance3.act3_wokeUp || !instance3.act3_enclaveWakeSceneCompleted || flag)) { reason = (flag ? "Leave the Act 3 wake-up room before using F4." : "Finish the full Act 3 wake-up sequence before using F4."); return false; } if (instance.IsMemoryScene() && !WidowSequenceSafety.CanRecoverToWidowShrine()) { reason = "The F4 warp is disabled inside memory sequences."; return false; } if (instance.IsInSceneTransition || TransitionPoint.IsTransitionBlocked) { reason = "A scene transition is already in progress."; return false; } if (!instance2.CanInput()) { reason = "Finish the current scripted action before warping."; return false; } reason = string.Empty; return true; } internal static string GetPreferredHubName() { return ResolveDestination() switch { WarpDestination.WidowShrine => "Widow Shrine", WarpDestination.Songclave => "Songclave", WarpDestination.Terminus => "Terminus", WarpDestination.Bellhart => "Bellhart", WarpDestination.Greymoor => "Greymoor", _ => "Bone Bottom", }; } internal static bool TryTeleportToPreferredHub(out string error) { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Expected O, but got Unknown if (!CanTeleportToPreferredHub(out error)) { return false; } if (!SlabCaptureWarpSafety.TryRestoreBeforeRecoveryWarp(out error)) { return false; } WarpDestination warpDestination = ResolveDestination(); string text; string entryGateName; switch (warpDestination) { case WarpDestination.WidowShrine: text = "Belltown_Shrine"; entryGateName = "door_wakeOnGround"; break; case WarpDestination.Songclave: text = "Bellshrine_Enclave"; entryGateName = "left1"; break; case WarpDestination.Terminus: text = "Tube_Hub"; entryGateName = "door_tubeEnter"; break; case WarpDestination.Bellhart: text = "Belltown"; entryGateName = "door5"; break; case WarpDestination.Greymoor: text = "Greymoor_08"; entryGateName = "left2"; break; default: text = FastTravelScenes.GetSceneName((FastTravelLocations)1); entryGateName = "door_fastTravelExit"; break; } if (string.IsNullOrEmpty(text)) { error = "Silksong could not resolve the F4 warp destination."; return false; } bool flag = warpDestination == WarpDestination.BoneBottom && MossMotherWarpSafety.PrepareForBoneBottomWarp(); try { GameManager.instance.BeginSceneTransition(new SceneLoadInfo { SceneName = text, EntryGateName = entryGateName }); } catch { if (flag) { MossMotherWarpSafety.CancelPreparedWarp(); } throw; } return true; } private static WarpDestination ResolveDestination() { if (WidowSequenceSafety.CanRecoverToWidowShrine()) { return WarpDestination.WidowShrine; } PlayerData instance = PlayerData.instance; if (instance != null && instance.blackThreadWorld && instance.act3_wokeUp && instance.act3_enclaveWakeSceneCompleted) { return WarpDestination.Terminus; } if (instance != null && instance.bellShrineEnclave) { return WarpDestination.Songclave; } if (instance != null && instance.spinnerDefeated) { return WarpDestination.Bellhart; } SaveState instance2 = SaveState.Instance; if (instance2 != null && instance2.rodeFleaCaravanToGreymoor) { return WarpDestination.Greymoor; } return WarpDestination.BoneBottom; } private static bool IsActThreeWakeEntry(GameManager gameManager, HeroController hero) { if ((Object)(object)gameManager != (Object)null && (Object)(object)hero != (Object)null && string.Equals(gameManager.GetSceneNameString(), "Song_Enclave", StringComparison.OrdinalIgnoreCase)) { return string.Equals(hero.GetEntryGateName(), "door_act3_wakeUp", StringComparison.Ordinal); } return false; } } internal static class FleaRescueAudio { internal const string VanillaFleaRescueClipAddress = "Assets/Audio/Voices/Fleas/Makoto/Flea_Howl_02.wav"; private const string VanillaFleaRescueClipName = "Flea_Howl_02"; private const string FleaSfxBundlePattern = "sfxstatic_assets_fleacaravan*.bundle"; private const float ResolveRetryDelaySeconds = 0.5f; private const float FailedPlayRetryDelaySeconds = 0.25f; private const float NativeFleaRescueVolume = 1f; private const int MaxPlayAttempts = 3; private static AudioClip fleaHowlClip; private static bool clipResolutionFailed; private static int pendingPlays; private static int failedPlayAttempts; private static float nextPlayTime; private static float nextResolveAttemptTime; internal static void QueueForReceivedFlea() { if (!clipResolutionFailed) { if (pendingPlays < int.MaxValue) { pendingPlays++; } ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogInfo((object)"[RANDOMIZER] Queued the Flea rescue sound for a received AP Flea."); } } } internal static void Update() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) if (pendingPlays <= 0 || !HasStableGameplayContext() || !TryResolveClip()) { return; } HeroController unsafeInstance = HeroController.UnsafeInstance; if (Time.unscaledTime < nextPlayTime || (Object)(object)unsafeInstance == (Object)null) { return; } try { AudioEvent val = AudioEvent.Default; val.Clip = fleaHowlClip; val.Volume = 1f; if ((Object)(object)((AudioEvent)(ref val)).SpawnAndPlayOneShot(Audio.DefaultAudioSourcePrefab, ((Component)unsafeInstance).transform.position, (Action)null) == (Object)null) { failedPlayAttempts++; if (failedPlayAttempts >= 3) { pendingPlays--; failedPlayAttempts = 0; ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)"[RANDOMIZER] Flea rescue sound could not create a native audio source."); } } nextPlayTime = Time.unscaledTime + 0.25f; } else { pendingPlays--; failedPlayAttempts = 0; ManualLogSource log2 = RandomizerPlugin.Log; if (log2 != null) { log2.LogInfo((object)"[RANDOMIZER] Played the Flea rescue sound."); } nextPlayTime = Time.unscaledTime + Mathf.Max(0.1f, fleaHowlClip.length); } } catch (Exception ex) { pendingPlays--; failedPlayAttempts = 0; ManualLogSource log3 = RandomizerPlugin.Log; if (log3 != null) { log3.LogWarning((object)("[RANDOMIZER] Failed to play the Flea rescue sound: " + ex.Message)); } } } internal static void ResetPending() { pendingPlays = 0; failedPlayAttempts = 0; nextPlayTime = 0f; } internal static bool CanResolveFleaAudio(bool hasGameManager, bool isGameplayScene, bool isLoadingSceneTransition, bool isInSceneTransition, bool hasHero) { return hasGameManager && isGameplayScene && !isLoadingSceneTransition && !isInSceneTransition && hasHero; } private static bool HasStableGameplayContext() { GameManager unsafeInstance = GameManager.UnsafeInstance; return CanResolveFleaAudio((Object)(object)unsafeInstance != (Object)null, (Object)(object)unsafeInstance != (Object)null && unsafeInstance.IsGameplayScene(), (Object)(object)unsafeInstance != (Object)null && unsafeInstance.IsLoadingSceneTransition, (Object)(object)unsafeInstance != (Object)null && unsafeInstance.IsInSceneTransition, (Object)(object)HeroController.UnsafeInstance != (Object)null); } private static bool TryResolveClip() { if ((Object)(object)fleaHowlClip != (Object)null) { return true; } return TryResolveFromGameBundle(); } private static bool TryResolveFromGameBundle() { try { return TryResolveFromGameBundleCore(); } catch (Exception ex) { FailLoad("The native Flea rescue sound lookup failed: " + ex.Message); return false; } } private static bool TryResolveFromGameBundleCore() { if ((Object)(object)fleaHowlClip != (Object)null) { return true; } if (clipResolutionFailed) { return false; } if (Time.unscaledTime < nextResolveAttemptTime) { return false; } AudioClip val = FindAlreadyLoadedExpectedClip(); if ((Object)(object)val != (Object)null) { fleaHowlClip = val; ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogInfo((object)"[RANDOMIZER] Reused the already-loaded vanilla Flea rescue sound."); } return true; } AudioClip val2 = FindClipInAlreadyLoadedBundle(); if ((Object)(object)val2 != (Object)null) { fleaHowlClip = val2; ManualLogSource log2 = RandomizerPlugin.Log; if (log2 != null) { log2.LogInfo((object)"[RANDOMIZER] Loaded the Flea rescue sound from a game-owned SFX bundle."); } return true; } string text = Path.Combine(Application.streamingAssetsPath, "aa"); string[] array = (Directory.Exists(text) ? Directory.GetFiles(text, "sfxstatic_assets_fleacaravan*.bundle", SearchOption.AllDirectories) : Array.Empty()); if (array.Length != 1) { FailLoad((array.Length == 0) ? ("Could not find the Flea Caravan SFX bundle under " + text + ".") : ("Found multiple Flea Caravan SFX bundles under " + text + "; refusing to choose one arbitrarily.")); return false; } ManualLogSource log3 = RandomizerPlugin.Log; if (log3 != null) { log3.LogInfo((object)"[RANDOMIZER] Taking a temporary snapshot of the native Flea Caravan SFX bundle."); } AssetBundle val3 = null; AudioClip val4 = null; bool flag = false; try { val3 = AssetBundle.LoadFromFile(array[0]); if ((Object)(object)val3 == (Object)null) { val4 = FindAlreadyLoadedExpectedClip() ?? FindClipInAlreadyLoadedBundle(); flag = (Object)(object)val4 == (Object)null; } else { val4 = LoadExactClip(val3); } } finally { if ((Object)(object)val3 != (Object)null) { val3.Unload(false); } } if (flag) { nextResolveAttemptTime = Time.unscaledTime + 0.5f; return false; } if (!IsExpectedClip(val4)) { FailLoad("The Flea Caravan SFX bundle did not provide Assets/Audio/Voices/Fleas/Makoto/Flea_Howl_02.wav."); return false; } fleaHowlClip = val4; nextResolveAttemptTime = 0f; ManualLogSource log4 = RandomizerPlugin.Log; if (log4 != null) { log4.LogInfo((object)"[RANDOMIZER] Loaded the exact vanilla Flea rescue sound and released the temporary SFX bundle."); } return true; } private static AudioClip FindAlreadyLoadedExpectedClip() { AudioClip[] array = Resources.FindObjectsOfTypeAll(); foreach (AudioClip val in array) { if (IsExpectedClip(val)) { return val; } } return null; } private static AudioClip FindClipInAlreadyLoadedBundle() { foreach (AssetBundle allLoadedAssetBundle in AssetBundle.GetAllLoadedAssetBundles()) { AudioClip val = LoadExactClip(allLoadedAssetBundle); if (IsExpectedClip(val)) { return val; } } return null; } private static AudioClip LoadExactClip(AssetBundle bundle) { if ((Object)(object)bundle == (Object)null) { return null; } string text = null; string[] allAssetNames = bundle.GetAllAssetNames(); if (allAssetNames != null) { string[] array = allAssetNames; foreach (string text2 in array) { if (string.Equals(text2, "Assets/Audio/Voices/Fleas/Makoto/Flea_Howl_02.wav", StringComparison.OrdinalIgnoreCase)) { text = text2; break; } } } if (string.IsNullOrEmpty(text)) { return null; } return bundle.LoadAsset(text); } private static bool IsExpectedClip(AudioClip clip) { if ((Object)(object)clip != (Object)null) { return string.Equals(((Object)clip).name, "Flea_Howl_02", StringComparison.Ordinal); } return false; } private static void FailLoad(string reason) { clipResolutionFailed = true; pendingPlays = 0; ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Could not load the vanilla Flea rescue sound: " + reason)); } } } public enum RandomizationMode { Vanilla, Anywhere, Shuffle } public enum ItemType { Unknown, Skill, Spell, Crest, Tool, Flea, CrestSlot, MaskShard, SpoolFragment, SilkHeart, Bellway, Ventrica, Currency, Upgrade, Map, Pin, Relic, Trap, Boss, BellShrine, Quest, NeedleUpgrade, Event, Resource, SimpleKey, Melody, MemoryLocket, Craftmetal, Mossberry, PollipHeart, Silkeater, MajorKey, ToolPouch } public class Item { public readonly string Name; public readonly ItemType Type; public readonly Action Receive; public readonly bool Repeatable; public Item(string name, ItemType type, Action receive, bool repeatable = false) { Name = ItemSet.GetCanonicalItemName(name); Type = type; Receive = receive; Repeatable = repeatable; } } internal static class ItemGrants { private const int MaxCraftingKitUpgrades = 4; private const int MaxToolPouchUpgrades = 4; private const int MaxDruidsEyeLevel = 2; private const int MaxProgressiveToolLevel = 2; private const int MaxSwiftStepLevel = 2; private const int MaxSilkHeartLevel = 3; private const int MaxNeedleUpgradeLevel = 4; private const string PaleOilAssetName = "Pale_Oil"; private const string RuinedToolAssetName = "Broken SilkShot"; private const string ClawMirrorAssetName = "Dazzle Bind"; private const string DarkMirrorAssetName = "Dazzle Bind Upgraded"; private const string CurveclawAssetName = "Curve Claws"; private const string CurvesickleAssetName = "Curve Claws Upgraded"; public static void GrantDash() { SaveState saveState = RequireSaveState(); saveState.canDash = true; if (!saveState.splitDashAndSprint) { saveState.canSprint = true; } } public static void GrantSprint() { RequireSaveState().canSprint = true; } public static void GrantFaydownCloak() { RequireSaveState().canDoubleJump = true; WispThicketFaydownPatches.SynchronizeActiveScene(); } public static void GrantClingGrip() { RequireSaveState().canWallJump = true; FarFieldsWardenflyPatches.SynchronizeActiveScene(); } public static void GrantProgressiveSwiftStep() { SaveState saveState = RequireSaveState(); int num = Math.Min(saveState.swiftStepLevel + 1, 2); if (num != saveState.swiftStepLevel) { saveState.swiftStepLevel = num; saveState.canSprint = true; if (num >= 2) { saveState.canDash = true; } } } public static void GrantProgressiveSilkheart() { SaveState saveState = RequireSaveState(); saveState.silkHeartLevel = Math.Min(saveState.silkHeartLevel + 1, 3); } public static void GrantRosaries(int amount) { if (!TryGrantMemoryCurrency(amount, (CurrencyType)0)) { RequireHero().AddGeo(amount); } } public static void GrantShellShards(int amount) { if (!TryGrantMemoryCurrency(amount, (CurrencyType)1)) { RequireHero().AddShards(amount); } } private static bool TryGrantMemoryCurrency(int amount, CurrencyType currencyType) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) PlayerData instance = PlayerData.instance; if (!MemorySequenceSync.HasRecordedSnapshot(instance)) { return false; } int num = (((int)currencyType == 0) ? instance.geo : instance.ShellShards); if (!MemorySequenceSync.TryCaptureCurrency(instance, currencyType, out var snapshotValue)) { return false; } bool num2 = CurrencyLinkManager.OwnsLocalMutation(instance, currencyType); CurrencyCounter.RefreshStartCount(currencyType); if ((int)currencyType == 0) { instance.AddGeo(amount); } else { instance.AddShards(amount); } int num3 = (((int)currencyType == 0) ? instance.geo : instance.ShellShards); if (!num2) { MemorySequenceSync.RebaseCurrencyDelta(instance, currencyType, snapshotValue, num, num3); } CurrencyCounter.Add(num3 - num, currencyType); return true; } public static void GrantCollectable(string collectableAssetName) { CollectableItem itemByName = CollectableItemManager.GetItemByName(collectableAssetName); if ((Object)(object)itemByName == (Object)null) { throw new InvalidOperationException("Collectable asset is not ready: " + collectableAssetName); } itemByName.Collect(1, false); } public static void GrantSlabKey(Action grant) { if (grant == null) { throw new ArgumentNullException("grant"); } PlayerData obj = RequirePlayerData(); grant(obj); CollectableItemManager.IncrementVersion(); } public static void GrantWhiteKey() { GrantCollectable("Ward Key"); RequirePlayerData().collectedWardKey = true; CollectableItemManager.IncrementVersion(); } public static void GrantSurgeonsKey() { GrantCollectable("Ward Boss Key"); RequirePlayerData().collectedWardBossKey = true; CollectableItemManager.IncrementVersion(); } public static void GrantArchitectsKey() { GrantCollectable("Architect Key"); } public static void GrantCrawSummons() { GrantCollectable("Craw Summons"); } public static void GrantRuinedTool() { CollectableItem itemByName = CollectableItemManager.GetItemByName("Broken SilkShot"); if ((Object)(object)itemByName == (Object)null) { throw new InvalidOperationException("Ruined Tool collectable asset is not ready."); } if (itemByName.CollectedAmount <= 0) { itemByName.Collect(1, false); } } public static void GrantCraftingKitUpgrade() { PlayerData val = RequirePlayerData(); int num = Math.Min(val.ToolKitUpgrades + 1, 4); if (num != val.ToolKitUpgrades) { val.ToolKitUpgrades = num; CollectableItemManager.IncrementVersion(); } } public static void GrantToolPouchUpgrade() { PlayerData val = RequirePlayerData(); int num = Math.Min(val.ToolPouchUpgrades + 1, 4); if (num != val.ToolPouchUpgrades) { val.ToolPouchUpgrades = num; CollectableItemManager.IncrementVersion(); ToolItemManager.SendEquippedChangedEvent(true); } } public static void GrantProgressiveDruidsEye() { SaveState saveState = RequireSaveState(); int num = Math.Min(saveState.druidsEyeLevel + 1, 2); if (num != saveState.druidsEyeLevel) { ToolItem obj = Utils.FindToolScriptableObject("Mosscreep Tool 1"); ToolItem val = Utils.FindToolScriptableObject("Mosscreep Tool 2"); if ((Object)(object)obj == (Object)null || (Object)(object)val == (Object)null) { throw new InvalidOperationException("Druid's Eye tool assets are not ready."); } saveState.druidsEyeLevel = num; SynchronizeProgressiveDruidsEyeEquips(obj, val); } } public static void GrantProgressiveClawMirror() { SaveState saveState = RequireSaveState(); int num = Math.Min(saveState.clawMirrorLevel + 1, 2); if (num != saveState.clawMirrorLevel) { if (!TryGetProgressiveToolPair("Dazzle Bind", "Dazzle Bind Upgraded", out var baseTool, out var upgradedTool)) { throw new InvalidOperationException("Claw Mirror tool assets are not ready."); } saveState.clawMirrorLevel = num; SynchronizeProgressiveToolPair(baseTool, upgradedTool, num); } } public static void GrantProgressiveCurveclaw() { SaveState saveState = RequireSaveState(); int curveclawLevel = saveState.curveclawLevel; int num = Math.Min(curveclawLevel + 1, 2); if (num != curveclawLevel) { if (!TryGetProgressiveToolPair("Curve Claws", "Curve Claws Upgraded", out var baseTool, out var upgradedTool)) { throw new InvalidOperationException("Curveclaw tool assets are not ready."); } InitializeProgressiveCurveclawStock(baseTool, upgradedTool, curveclawLevel, num); saveState.curveclawLevel = num; SynchronizeProgressiveToolPair(baseTool, upgradedTool, num); } } public static void GrantProgressiveNeedleUpgrade() { SaveState saveState = RequireSaveState(); PlayerData val = RequirePlayerData(); int num = Math.Min(saveState.needleUpgradeLevel + 1, 4); if (num != saveState.needleUpgradeLevel) { saveState.needleUpgradeLevel = num; int val2 = Math.Max(0, Math.Min(4, val.nailUpgrades)); val.nailUpgrades = Math.Max(val2, num); val.InvNailHasNew = true; val.InvPaneHasNew = true; CollectableItemManager.IncrementVersion(); } } public static void GrantKrattFlea() { PlayerData obj = RequirePlayerData(); obj.CaravanLechSaved = true; obj.CaravanLechReturnedToCaravan = true; } public static void GrantVogFlea() { RequirePlayerData().MetTroupeHunterWild = true; } public static void GrantHugeFlea() { RequirePlayerData().tamedGiantFlea = true; } public static void GrantPaleOil() { CollectableItem itemByName = CollectableItemManager.GetItemByName("Pale_Oil"); if ((Object)(object)itemByName == (Object)null) { throw new InvalidOperationException("Pale Oil collectable asset is not ready."); } itemByName.Collect(1, false); } internal static bool TrySynchronizeProgressiveDruidsEyeEquips() { SaveState instance = SaveState.Instance; if (instance == null || instance.druidsEyeLevel <= 0) { return true; } ToolItem val = Utils.FindToolScriptableObject("Mosscreep Tool 1"); ToolItem val2 = Utils.FindToolScriptableObject("Mosscreep Tool 2"); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { return false; } SynchronizeProgressiveDruidsEyeEquips(val, val2); return true; } internal static bool TrySynchronizeProgressiveToolEquips() { SaveState instance = SaveState.Instance; if (instance == null) { return true; } if (!TryGetProgressiveToolPair("Dazzle Bind", "Dazzle Bind Upgraded", out var baseTool, out var upgradedTool) || !TryGetProgressiveToolPair("Curve Claws", "Curve Claws Upgraded", out var baseTool2, out var upgradedTool2)) { return false; } SynchronizeProgressiveToolPair(baseTool, upgradedTool, instance.clawMirrorLevel); SynchronizeProgressiveToolPair(baseTool2, upgradedTool2, instance.curveclawLevel); return true; } internal static bool TryGetProgressiveToolState(ToolItem tool, out int level, out int requiredLevel, out bool isBaseTier) { level = 0; requiredLevel = 0; isBaseTier = false; SaveState instance = SaveState.Instance; if (instance == null || (Object)(object)tool == (Object)null) { return false; } if (string.Equals(tool.name, "Dazzle Bind", StringComparison.OrdinalIgnoreCase)) { level = instance.clawMirrorLevel; requiredLevel = 1; isBaseTier = true; return true; } if (string.Equals(tool.name, "Dazzle Bind Upgraded", StringComparison.OrdinalIgnoreCase)) { level = instance.clawMirrorLevel; requiredLevel = 2; return true; } if (string.Equals(tool.name, "Curve Claws", StringComparison.OrdinalIgnoreCase)) { level = instance.curveclawLevel; requiredLevel = 1; isBaseTier = true; return true; } if (string.Equals(tool.name, "Curve Claws Upgraded", StringComparison.OrdinalIgnoreCase)) { level = instance.curveclawLevel; requiredLevel = 2; return true; } return false; } private static bool TryGetProgressiveToolPair(string baseName, string upgradedName, out ToolItem baseTool, out ToolItem upgradedTool) { baseTool = Utils.FindToolScriptableObject(baseName); upgradedTool = Utils.FindToolScriptableObject(upgradedName); if ((Object)(object)baseTool != (Object)null) { return (Object)(object)upgradedTool != (Object)null; } return false; } private static void SynchronizeProgressiveToolPair(ToolItem baseTool, ToolItem upgradedTool, int level) { if (level <= 0) { ToolItemManager.RemoveToolFromAllCrests(baseTool); ToolItemManager.RemoveToolFromAllCrests(upgradedTool); } else if (level == 1) { ToolItemManager.ReplaceToolEquips(upgradedTool, baseTool); } else { ToolItemManager.ReplaceToolEquips(baseTool, upgradedTool); } ToolItemManager.RefreshEquippedState(); CollectableItemManager.IncrementVersion(); } private static void InitializeProgressiveCurveclawStock(ToolItem baseTool, ToolItem upgradedTool, int previousLevel, int nextLevel) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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_004d: 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_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: 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) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) PlayerData val = RequirePlayerData(); if (previousLevel <= 0 && nextLevel >= 1) { int toolStorageAmount = ToolItemManager.GetToolStorageAmount(baseTool); Data data = ((SerializableNamedList)(object)val.Tools).GetData(baseTool.name); data.AmountLeft = Math.Max(data.AmountLeft, toolStorageAmount); ((SerializableNamedList)(object)val.Tools).SetData(baseTool.name, data); Data savedData = baseTool.SavedData; savedData.AmountLeft = Math.Max(savedData.AmountLeft, toolStorageAmount); baseTool.SavedData = savedData; } if (previousLevel < 2 && nextLevel >= 2) { int val2 = Math.Max(0, baseTool.SavedData.AmountLeft); Data data2 = ((SerializableNamedList)(object)val.Tools).GetData(upgradedTool.name); data2.AmountLeft = Math.Max(data2.AmountLeft, val2); ((SerializableNamedList)(object)val.Tools).SetData(upgradedTool.name, data2); Data savedData2 = upgradedTool.SavedData; savedData2.AmountLeft = Math.Max(savedData2.AmountLeft, val2); upgradedTool.SavedData = savedData2; } } private static void SynchronizeProgressiveDruidsEyeEquips(ToolItem baseEye, ToolItem upgradedEyes) { if (SaveState.Instance.druidsEyeLevel >= 2) { ToolItemManager.ReplaceToolEquips(baseEye, upgradedEyes); } else { ToolItemManager.ReplaceToolEquips(upgradedEyes, baseEye); } ToolItemManager.RefreshEquippedState(); CollectableItemManager.IncrementVersion(); } public static void GrantMap(Action grant) { if (grant == null) { throw new ArgumentNullException("grant"); } PlayerData val = RequirePlayerData(); grant(val); val.mapUpdateQueued = true; val.HasSeenMapUpdated = false; } public static void GrantStartWithMaps() { PlayerData obj = RequirePlayerData(); obj.HasMossGrottoMap = true; obj.HasBoneforestMap = true; obj.HasDocksMap = true; obj.HasWildsMap = true; obj.HasCrawlMap = true; obj.HasHuntersNestMap = true; obj.HasGreymoorMap = true; obj.HasBellhartMap = true; obj.HasShellwoodMap = true; obj.HasJudgeStepsMap = true; obj.HasDustpensMap = true; obj.HasPeakMap = true; obj.HasCoralMap = true; obj.HasSwampMap = true; obj.HasWeavehomeMap = true; obj.HasSongGateMap = true; obj.HasCitadelUnderstoreMap = true; obj.HasHallsMap = true; obj.HasLibraryMap = true; obj.HasWardMap = true; obj.HasCogMap = true; obj.HasArboriumMap = true; obj.HasHangMap = true; obj.HasSlabMap = true; obj.HasAqueductMap = true; obj.HasCradleMap = true; obj.HasAbyssMap = true; obj.mapUpdateQueued = true; obj.HasSeenMapUpdated = false; } public static void GrantMelody(Action grant) { if (grant == null) { throw new ArgumentNullException("grant"); } PlayerData obj = RequirePlayerData(); grant(obj); CollectableItemManager.IncrementVersion(); } public static void GrantBeastlingCall() { SaveState saveState = RequireSaveState(); PlayerData val = RequirePlayerData(); saveState.canUseBeastlingCall = true; val.UnlockedFastTravelTeleport = true; CollectableItemManager.IncrementVersion(); } public static void GrantPin(Action grant) { if (grant == null) { throw new ArgumentNullException("grant"); } PlayerData val = RequirePlayerData(); grant(val); val.HasSeenMapMarkerUpdated = false; } public static void GrantRelic(string assetName) { if (string.IsNullOrWhiteSpace(assetName)) { throw new ArgumentException("Relic asset name is required.", "assetName"); } if ((Object)(object)ManagerSingleton.Instance == (Object)null) { throw new InvalidOperationException("Collectable relic manager is not ready."); } CollectableRelic relic = CollectableRelicManager.GetRelic(assetName); if ((Object)(object)relic == (Object)null) { throw new InvalidOperationException("Collectable relic asset is not ready: " + assetName); } CoreLocationPatches.GrantRelicWithoutChecking(relic, showPopup: false); } private static HeroController RequireHero() { HeroController instance = HeroController.instance; if ((Object)(object)instance == (Object)null) { throw new InvalidOperationException("HeroController is not ready."); } return instance; } private static PlayerData RequirePlayerData() { return PlayerData.instance ?? throw new InvalidOperationException("PlayerData is not ready."); } private static SaveState RequireSaveState() { return SaveState.Instance ?? throw new InvalidOperationException("Randomizer save state is not ready."); } } public class ItemSet { internal const string ProgressiveSilkheartItemName = "Progressive Silkheart"; private static readonly Dictionary CurrentFleaDisplayNames = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Flea: The Marrow", "Flea: The Marrow" }, { "Flea: Deep Docks (Bellway)", "Flea: Deep Docks - Bellway" }, { "Flea: Deep Docks (Weaver Burial Spire)", "Flea: Deep Docks - Weaver Burial Spire" }, { "Flea: Far Fields (Captured)", "Flea: Far Fields - Captured" }, { "Flea: Hunter's March", "Flea: Hunter's March" }, { "Flea: Greymoor (Craw Lake)", "Flea: Greymoor - Craw Lake" }, { "Flea: Greymoor (Tower)", "Flea: Greymoor - Tower" }, { "Flea: Shellwood", "Flea: Shellwood" }, { "Flea: Pilgrim's Rest", "Flea: Pilgrim's Rest" }, { "Flea: Blasted Steps", "Flea: Blasted Steps" }, { "Flea: Sinner's Road", "Flea: Sinner's Road" }, { "Flea: Exhaust Organ", "Flea: Exhaust Organ" }, { "Flea: Bellhart", "Flea: Bellhart" }, { "Flea: Wormways", "Flea: Wormways" }, { "Flea: The Slab (Cell)", "Flea: The Slab - Cell" }, { "Flea: Bilewater (Thieves)", "Flea: Bilewater - Thieves" }, { "Flea: Deep Docks (Mines)", "Flea: Deep Docks - Mines" }, { "Flea: Wisp Thicket", "Flea: Underworks - Wisp Thicket Passage" }, { "Flea: Bilehaven", "Flea: Bilehaven" }, { "Flea: Choral Chambers (Spa)", "Flea: Choral Chambers - Spa" }, { "Flea: Sands of Karak", "Flea: Sands of Karak" }, { "Flea: Mount Fay", "Flea: Mount Fay" }, { "Flea: Songclave", "Flea: Songclave" }, { "Flea: Choral Chambers (Walled Room)", "Flea: Choral Chambers - Walled Room" }, { "Flea: Whispering Vaults", "Flea: Whispering Vaults" }, { "Flea: Underworks", "Flea: Underworks" }, { "Flea: The Slab (Bellway)", "Flea: The Slab - Bellway" }, { "Flea: Greymoor (Kratt)", "Flea: Greymoor - Kratt" }, { "Flea: Putrified Ducts (Vog)", "Flea: Putrified Ducts - Vog" }, { "Flea: Memorium (Huge Flea)", "Flea: Memorium - Huge Flea" } }; internal static readonly Dictionary NativeItemNameAliases = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Skill: Double Jump", "Ability: Faydown Cloak" }, { "Skill: Charge Slash", "Ability: Needle Strike" }, { "Skill: Silk Soar", "Ancestral Art: Silk Soar" }, { "Skill: Wall Jump", "Ancestral Art: Cling Grip" }, { "Skill: Dash", "Ancestral Art: Swift Step" }, { "Skill: Harpoon", "Ancestral Art: Clawline" }, { "Skill: Quill", "Item: Quill" }, { "Skill: Needolin", "Ancestral Art: Needolin" }, { "Skill: Sprint", "Ability: Swift Step (Sprint Only)" }, { "Spell: Silk Spear", "Silk Skill: Silkspear" }, { "Spell: Parry", "Silk Skill: Cross Stitch" }, { "Spell: Silk Boss Needle", "Silk Skill: Pale Nails" }, { "Spell: Silk Charge", "Silk Skill: Sharpdart" }, { "Spell: Silk Bomb", "Silk Skill: Rune Rage" }, { "Spell: Thread Sphere", "Silk Skill: Thread Storm" }, { "Flea: The Marrow (Tangle)", "Flea: The Marrow" }, { "Flea: Deep Docks Bellway (Eepy)", "Flea: Deep Docks (Bellway)" }, { "Flea: Deep Docks Weaver Burial Spire (Squeesh)", "Flea: Deep Docks (Weaver Burial Spire)" }, { "Flea: Far Fields Captured (Thoughtless)", "Flea: Far Fields (Captured)" }, { "Flea: Hunter's March (Yapper)", "Flea: Hunter's March" }, { "Flea: Greymoor Craw Lake (Gwah)", "Flea: Greymoor (Craw Lake)" }, { "Flea: Greymoor Tower (Snoozles)", "Flea: Greymoor (Tower)" }, { "Flea: Shellwood (Shelly)", "Flea: Shellwood" }, { "Flea: Pilgrim's Rest (Sleeby)", "Flea: Pilgrim's Rest" }, { "Flea: Blasted Steps (Nini)", "Flea: Blasted Steps" }, { "Flea: Sinner's Road (Stelf)", "Flea: Sinner's Road" }, { "Flea: Exhaust Organ (Rangle)", "Flea: Exhaust Organ" }, { "Flea: Bellhart (Bellphy)", "Flea: Bellhart" }, { "Flea: Wormways (Snacc)", "Flea: Wormways" }, { "Flea: The Slab Cell (Sway)", "Flea: The Slab (Cell)" }, { "Flea: Bilewater Thieves (Cower)", "Flea: Bilewater (Thieves)" }, { "Flea: Deep Docks Mines (Le Bomba)", "Flea: Deep Docks (Mines)" }, { "Flea: Wisp Thicket (Fidget)", "Flea: Wisp Thicket" }, { "Flea: Bilehaven (Spangle)", "Flea: Bilehaven" }, { "Flea: Choral Cambers Spa (Oowa)", "Flea: Choral Chambers (Spa)" }, { "Flea: Sands of Karak (Crustly)", "Flea: Sands of Karak" }, { "Flea: Mount Fay (Ice Cube)", "Flea: Mount Fay" }, { "Flea: Songclave (Groggy)", "Flea: Songclave" }, { "Flea: Choral Cambers Walled (Mimi)", "Flea: Choral Chambers (Walled Room)" }, { "Flea: Whispering Vaults (Birdy)", "Flea: Whispering Vaults" }, { "Flea: Underworks (Boomy)", "Flea: Underworks" }, { "Flea: The Slab Bellway (Honk Shoo)", "Flea: The Slab (Bellway)" }, { "Hunter Slot: Red 0 -2", "Crest Slot: Hunter (Red 1)" }, { "Hunter Slot: Blue -2 0", "Crest Slot: Hunter (Blue 1)" }, { "Hunter Slot: Yellow 2 0", "Crest Slot: Hunter (Yellow 1)" }, { "Reaper Slot: Red 0 -1", "Crest Slot: Reaper (Red 1)" }, { "Reaper Slot: Blue -1 1", "Crest Slot: Reaper (Blue 1)" }, { "Reaper Slot: Yellow 1 1", "Crest Slot: Reaper (Yellow 1)" }, { "Wanderer Slot: Blue -2 1", "Crest Slot: Wanderer (Blue 1)" }, { "Wanderer Slot: Blue 2 1", "Crest Slot: Wanderer (Blue 2)" }, { "Wanderer Slot: Yellow 0 -2", "Crest Slot: Wanderer (Yellow 1)" }, { "Beast Slot: Yellow -1 0", "Crest Slot: Beast (Yellow 1)" }, { "Beast Slot: Yellow 1 0", "Crest Slot: Beast (Yellow 2)" }, { "Witch Slot: Red 0 -2", "Crest Slot: Witch (Red 1)" }, { "Witch Slot: Blue -1 0", "Crest Slot: Witch (Blue 1)" }, { "Witch Slot: Blue 1 0", "Crest Slot: Witch (Blue 2)" }, { "Architect Slot: Blue -1 2", "Crest Slot: Architect (Blue 1)" }, { "Architect Slot: Yellow 1 2", "Crest Slot: Architect (Yellow 1)" }, { "Architect Slot: Yellow 2 0", "Crest Slot: Architect (Yellow 2)" }, { "Architect Slot: Blue -2 0", "Crest Slot: Architect (Blue 2)" }, { "Shaman Slot: Blue -1 0", "Crest Slot: Shaman (Blue 1)" }, { "Shaman Slot: Blue 1 0", "Crest Slot: Shaman (Blue 2)" }, { "Tool: WebShot Forge", "Tool: Silkshot (Forge Daughter)" }, { "Tool: WebShot Architect", "Tool: Silkshot (Twelfth Architect)" }, { "Tool: WebShot Weaver", "Tool: Silkshot (Original)" }, { "Tool: Zap Imbuement", "Tool: Volt Filament" }, { "Tool: Tack", "Tool: Tacks" }, { "Tool: Poison Pouch", "Tool: Pollip Pouch" }, { "Tool: Silk Snare", "Tool: Snare Setter" }, { "Tool: Wisp Lantern", "Tool: Wispfire Lantern" }, { "Tool: Revenge Crystal", "Tool: Memory Crystal" }, { "Tool: Lightning Rod", "Tool: Voltvessels" }, { "Tool: Tri Pin", "Tool: Threefold Pin" }, { "Tool: Bell Bind", "Tool: Warding Bell" }, { "Tool: Harpoon", "Tool: Longpin" }, { "Tool: Brolly Spike", "Tool: Sawtooth Circlet" }, { "Tool: Dazzle Bind", "Progressive Claw Mirror" }, { "Tool: Dazzle Bind Upgraded", "Progressive Claw Mirror" }, { "Tool: Shakra Ring", "Tool: Throwing Ring" }, { "Tool: Screw Attack", "Tool: Delver's Drill" }, { "Tool: Musician Charm", "Tool: Spider Strings" }, { "Tool: Sprintmaster", "Tool: Silkspeed Anklets" }, { "Tool: Weighted Anklet", "Tool: Weighted Belt" }, { "Tool: Multibind", "Tool: Multibinder" }, { "Tool: Quickbind", "Tool: Injector Band" }, { "Tool: Barbed Wire", "Tool: Barbed Bracelet" }, { "Tool: Conch Drill", "Tool: Conchcutter" }, { "Tool: Pimpilo", "Tool: Pimpillo" }, { "Tool: Cogwork Flier", "Tool: Cogfly" }, { "Tool: Curve Claws", "Progressive Curveclaw" }, { "Tool: Curve Claws Upgraded", "Progressive Curveclaw" }, { "Tool: Cogwork Saw", "Tool: Cogwork Wheel" }, { "Tool: Rosary Magnet", "Tool: Magnetite Brooch" }, { "Tool: White Ring", "Tool: Weavelight" }, { "Tool: Pinstress Tool", "Tool: Pin Badge" }, { "Tool: Extractor", "Tool: Needle Phial" }, { "Tool: Lifeblood Syringe", "Tool: Plasmium Phial" }, { "Tool: Dead Mans Purse", "Tool: Dead Bug's Purse" }, { "Tool: Thief Charm", "Tool: Thief's Mark" }, { "Tool: Thief Claw", "Tool: Snitch Pick" }, { "Tool: Mosscreep Tool 1", "Progressive Druid's Eyes" }, { "Tool: Mosscreep Tool 2", "Progressive Druid's Eyes" }, { "Tool: Flintstone", "Tool: Flintslate" }, { "Tool: Maggot Charm", "Tool: Wreath of Purity" }, { "Tool: Lava Charm", "Tool: Magma Bell" }, { "Tool: Wallcling", "Tool: Ascendant's Grip" }, { "Tool: Longneedle", "Tool: Longclaw" }, { "Tool: Bone Necklace", "Tool: Shard Pendant" }, { "Tool: Flea Charm", "Tool: Egg of Flealia" }, { "Pin: Bench", "Bench Pins" }, { "Pin: Ventrica", "Ventrica Pins" }, { "Pin: Bellway", "Bellway Pins" }, { "Pin: Vendor", "Vendor Pins" }, { "Relic: Weaver Totem Witch", "Relic: Weaver Effigy (Keelal, Shellwood)" }, { "Relic: Psalm Cylinder Library Roof", "Relic: Psalm Cylinder (East Whispering Vaults)" }, { "Relic: Bone Record Wisp Top", "Relic: Bone Scroll (Wisp Thicket)" }, { "Relic: Weaver Totem Bonetown_upper_room", "Relic: Weaver Effigy (Camora, Moss Grotto)" }, { "Relic: Librarian Melody Cylinder", "Relic: Sacred Cylinder" }, { "Relic: Seal Chit City Merchant", "Relic: Choral Commandment (Jubilana)" }, { "Relic: Psalm Cylinder Ward", "Relic: Psalm Cylinder (Underworks)" }, { "Relic: Weaver Record Conductor", "Relic: Rune Harp (High Halls)" }, { "Relic: Psalm Cylinder Librarian", "Relic: Psalm Cylinder (Vaultkeeper Cardinius)" }, { "Relic: Psalm Cylinder Hang", "Relic: Psalm Cylinder (High Halls)" }, { "Relic: Seal Chit Ward Corpse", "Relic: Choral Commandment (Western Whiteward)" }, { "Relic: Weaver Record Sprint_Challenge", "Relic: Rune Harp (Weavenest Cindril)" }, { "Relic: Psalm Cylinder Grindle", "Relic: Psalm Cylinder (Grindle)" }, { "Relic: Weaver Record Weave_08", "Relic: Rune Harp (Weavenest Atla)" }, { "Relic: Bone Record Understore_Map_Room", "Relic: Bone Scroll (Underworks)" }, { "Relic: Bone Record Bone_East_14", "Relic: Bone Scroll (Far Fields)" }, { "Relic: Seal Chit Aspid_01", "Relic: Choral Commandment (Moss Grotto)" }, { "Relic: Seal Chit Silk Siphon", "Relic: Choral Commandment (Eastern Whiteward)" }, { "Relic: Bone Record Greymoor_flooded_corridor", "Relic: Bone Scroll (Greymoor)" }, { "Relic: Weaver Totem Slab_Bottom", "Relic: Weaver Effigy (Atla, The Slab)" }, { "Relic: Ancient Egg Abyss Middle", "Relic: Arcane Egg" } }; public Item[] items = new Item[284] { new Item("Ability: Faydown Cloak", ItemType.Skill, ItemGrants.GrantFaydownCloak), new Item("Ability: Needle Strike", ItemType.Skill, delegate { SaveState.Instance.canChargeSlash = true; }), new Item("Ancestral Art: Silk Soar", ItemType.Skill, delegate { SaveState.Instance.canSilkSoar = true; }), new Item("Ancestral Art: Cling Grip", ItemType.Skill, ItemGrants.GrantClingGrip), new Item("Ability: Drifter's Cloak", ItemType.Skill, delegate { SaveState.Instance.canBrolly = true; }), new Item("Ancestral Art: Swift Step", ItemType.Skill, ItemGrants.GrantDash), new Item("Ability: Swift Step (Sprint Only)", ItemType.Skill, ItemGrants.GrantSprint), new Item("Progressive Swift Step", ItemType.Skill, ItemGrants.GrantProgressiveSwiftStep, repeatable: true), new Item("Ancestral Art: Clawline", ItemType.Skill, delegate { SaveState.Instance.canUseHarpoon = true; }), new Item("Item: Quill", ItemType.Skill, delegate { SaveState.Instance.canUseQuill = true; }), new Item("Progressive Compass", ItemType.Skill, delegate { SaveState.Instance.canUseQuill = true; }), new Item("Ancestral Art: Needolin", ItemType.Skill, delegate { SaveState.Instance.canUseNeedolin = true; }), new Item("Rosaries (60)", ItemType.Currency, delegate { ItemGrants.GrantRosaries(60); }, repeatable: true), new Item("Shell Shards (80)", ItemType.Currency, delegate { ItemGrants.GrantShellShards(80); }, repeatable: true), new Item("Frayed Rosary String", ItemType.Resource, delegate { ItemGrants.GrantCollectable("Rosary_Set_Frayed"); }, repeatable: true), new Item("Rosary String", ItemType.Resource, delegate { ItemGrants.GrantCollectable("Rosary_Set_Small"); }, repeatable: true), new Item("Rosary Necklace", ItemType.Resource, delegate { ItemGrants.GrantCollectable("Rosary_Set_Medium"); }, repeatable: true), new Item("Heavy Rosary Necklace", ItemType.Resource, delegate { ItemGrants.GrantCollectable("Rosary_Set_Large"); }, repeatable: true), new Item("Pale Rosary Necklace", ItemType.Resource, delegate { ItemGrants.GrantCollectable("Rosary_Set_Huge_White"); }, repeatable: true), new Item("Shard Bundle", ItemType.Resource, delegate { ItemGrants.GrantCollectable("Shard Pouch"); }, repeatable: true), new Item("Beast Shard", ItemType.Resource, delegate { ItemGrants.GrantCollectable("Great Shard"); }, repeatable: true), new Item("Pristine Core", ItemType.Resource, delegate { ItemGrants.GrantCollectable("Pristine Core"); }, repeatable: true), new Item("Progressive Crafting Kit", ItemType.Upgrade, ItemGrants.GrantCraftingKitUpgrade, repeatable: true), new Item("Progressive Tool Pouch", ItemType.ToolPouch, ItemGrants.GrantToolPouchUpgrade, repeatable: true), new Item("Progressive Druid's Eyes", ItemType.Upgrade, ItemGrants.GrantProgressiveDruidsEye, repeatable: true), new Item("Progressive Needle Upgrade", ItemType.NeedleUpgrade, ItemGrants.GrantProgressiveNeedleUpgrade, repeatable: true), new Item("Pale Oil", ItemType.NeedleUpgrade, ItemGrants.GrantPaleOil, repeatable: true), new Item("Stagger Trap", ItemType.Trap, TrapManager.TriggerStagger, repeatable: true), new Item("Rosary Spill Trap", ItemType.Trap, TrapManager.TriggerRosarySpill, repeatable: true), new Item("Darkness Trap", ItemType.Trap, TrapManager.TriggerDarkness, repeatable: true), new Item("Cursed Crest Trap", ItemType.Trap, TrapManager.TriggerCursedCrest, repeatable: true), new Item("Muckmaggot Status Trap", ItemType.Trap, TrapManager.TriggerMuckmaggotStatus, repeatable: true), new Item("Naked Trap", ItemType.Trap, NakedTrapManager.Trigger, repeatable: true), new Item("Map: Mosslands", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasMossGrottoMap = true; }); }), new Item("Map: The Marrow", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasBoneforestMap = true; }); }), new Item("Map: Deep Docks", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasDocksMap = true; }); }), new Item("Map: Far Fields", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasWildsMap = true; }); }), new Item("Map: Wormways", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasCrawlMap = true; }); }), new Item("Map: Hunter's March", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasHuntersNestMap = true; }); }), new Item("Map: Greymoor", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasGreymoorMap = true; }); }), new Item("Map: Bellhart", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasBellhartMap = true; }); }), new Item("Map: Shellwood", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasShellwoodMap = true; }); }), new Item("Map: Sands of Karak", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasCoralMap = true; }); }), new Item("Map: Sinner's Road", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasDustpensMap = true; }); }), new Item("Map: Mount Fay", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasPeakMap = true; }); }), new Item("Map: Blasted Steps", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasJudgeStepsMap = true; }); }), new Item("Map: Bilewater", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasSwampMap = true; }); }), new Item("Map: Weavenest Atla", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasWeavehomeMap = true; }); }), new Item("Map: Grand Gate", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasSongGateMap = true; }); }), new Item("Map: Underworks", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasCitadelUnderstoreMap = true; }); }), new Item("Map: Choral Chambers", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasHallsMap = true; }); }), new Item("Map: Whispering Vaults", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasLibraryMap = true; }); }), new Item("Map: Whiteward", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasWardMap = true; }); }), new Item("Map: Cogwork Core", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasCogMap = true; }); }), new Item("Map: Memorium", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasArboriumMap = true; }); }), new Item("Map: High Halls", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasHangMap = true; }); }), new Item("Map: The Slab", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasSlabMap = true; }); }), new Item("Map: Putrified Ducts", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasAqueductMap = true; }); }), new Item("Map: The Cradle", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasCradleMap = true; }); }), new Item("Map: Verdania", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasCloverMap = true; }); }), new Item("Map: The Abyss", ItemType.Map, delegate { ItemGrants.GrantMap(delegate(PlayerData pd) { pd.HasAbyssMap = true; }); }), new Item("Bench Pins", ItemType.Pin, delegate { ItemGrants.GrantPin(delegate(PlayerData pd) { pd.hasPinBench = true; }); }), new Item("Ventrica Pins", ItemType.Pin, delegate { ItemGrants.GrantPin(delegate(PlayerData pd) { pd.hasPinTube = true; }); }), new Item("Bellway Pins", ItemType.Pin, delegate { ItemGrants.GrantPin(delegate(PlayerData pd) { pd.hasPinStag = true; }); }), new Item("Vendor Pins", ItemType.Pin, delegate { ItemGrants.GrantPin(delegate(PlayerData pd) { pd.hasPinShop = true; }); }), new Item("Relic: Weaver Effigy (Keelal, Shellwood)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Weaver Totem Witch"); }), new Item("Relic: Psalm Cylinder (East Whispering Vaults)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Psalm Cylinder Library Roof"); }), new Item("Relic: Bone Scroll (Wisp Thicket)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Bone Record Wisp Top"); }), new Item("Relic: Weaver Effigy (Camora, Moss Grotto)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Weaver Totem Bonetown_upper_room"); }), new Item("Relic: Sacred Cylinder", ItemType.Relic, delegate { ItemGrants.GrantRelic("Librarian Melody Cylinder"); }), new Item("Relic: Choral Commandment (Jubilana)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Seal Chit City Merchant"); }), new Item("Relic: Psalm Cylinder (Underworks)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Psalm Cylinder Ward"); }), new Item("Relic: Rune Harp (High Halls)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Weaver Record Conductor"); }), new Item("Relic: Psalm Cylinder (Vaultkeeper Cardinius)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Psalm Cylinder Librarian"); }), new Item("Relic: Psalm Cylinder (High Halls)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Psalm Cylinder Hang"); }), new Item("Relic: Choral Commandment (Western Whiteward)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Seal Chit Ward Corpse"); }), new Item("Relic: Rune Harp (Weavenest Cindril)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Weaver Record Sprint_Challenge"); }), new Item("Relic: Psalm Cylinder (Grindle)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Psalm Cylinder Grindle"); }), new Item("Relic: Rune Harp (Weavenest Atla)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Weaver Record Weave_08"); }), new Item("Relic: Bone Scroll (Underworks)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Bone Record Understore_Map_Room"); }), new Item("Relic: Bone Scroll (Far Fields)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Bone Record Bone_East_14"); }), new Item("Relic: Choral Commandment (Moss Grotto)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Seal Chit Aspid_01"); }), new Item("Relic: Choral Commandment (Eastern Whiteward)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Seal Chit Silk Siphon"); }), new Item("Relic: Bone Scroll (Greymoor)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Bone Record Greymoor_flooded_corridor"); }), new Item("Relic: Weaver Effigy (Atla, The Slab)", ItemType.Relic, delegate { ItemGrants.GrantRelic("Weaver Totem Slab_Bottom"); }), new Item("Relic: Arcane Egg", ItemType.Relic, delegate { ItemGrants.GrantRelic("Ancient Egg Abyss Middle"); }), new Item("Ruined Tool", ItemType.Tool, ItemGrants.GrantRuinedTool), new Item("Tool: Silkshot (Forge Daughter)", ItemType.Tool, null), new Item("Tool: Silkshot (Twelfth Architect)", ItemType.Tool, null), new Item("Tool: Silkshot (Original)", ItemType.Tool, null), new Item("Tool: Volt Filament", ItemType.Tool, null), new Item("Tool: Tacks", ItemType.Tool, null), new Item("Tool: Pollip Pouch", ItemType.Tool, null), new Item("Tool: Snare Setter", ItemType.Tool, null), new Item("Tool: Wispfire Lantern", ItemType.Tool, null), new Item("Tool: Memory Crystal", ItemType.Tool, null), new Item("Tool: Voltvessels", ItemType.Tool, null), new Item("Tool: Threefold Pin", ItemType.Tool, null), new Item("Tool: Warding Bell", ItemType.Tool, null), new Item("Tool: Rosary Cannon", ItemType.Tool, null), new Item("Tool: Longpin", ItemType.Tool, null), new Item("Tool: Sawtooth Circlet", ItemType.Tool, null), new Item("Progressive Claw Mirror", ItemType.Tool, ItemGrants.GrantProgressiveClawMirror, repeatable: true), new Item("Tool: Throwing Ring", ItemType.Tool, null), new Item("Tool: Delver's Drill", ItemType.Tool, null), new Item("Tool: Quick Sling", ItemType.Tool, null), new Item("Tool: Spider Strings", ItemType.Tool, null), new Item("Tool: Fractured Mask", ItemType.Tool, null), new Item("Tool: Silkspeed Anklets", ItemType.Tool, null), new Item("Tool: Weighted Belt", ItemType.Tool, null), new Item("Tool: Multibinder", ItemType.Tool, null), new Item("Tool: Reserve Bind", ItemType.Tool, null), new Item("Tool: Injector Band", ItemType.Tool, null), new Item("Tool: Barbed Bracelet", ItemType.Tool, null), new Item("Tool: Sting Shard", ItemType.Tool, null), new Item("Tool: Conchcutter", ItemType.Tool, null), new Item("Tool: Pimpillo", ItemType.Tool, null), new Item("Tool: Straight Pin", ItemType.Tool, null), new Item("Tool: Cogfly", ItemType.Tool, null), new Item("Progressive Curveclaw", ItemType.Tool, ItemGrants.GrantProgressiveCurveclaw, repeatable: true), new Item("Tool: Cogwork Wheel", ItemType.Tool, null), new Item("Tool: Flea Brew", ItemType.Tool, null), new Item("Tool: Magnetite Dice", ItemType.Tool, null), new Item("Tool: Magnetite Brooch", ItemType.Tool, null), new Item("Tool: Weavelight", ItemType.Tool, null), new Item("Tool: Pin Badge", ItemType.Tool, null), new Item("Tool: Needle Phial", ItemType.Tool, null), new Item("Tool: Plasmium Phial", ItemType.Tool, null), new Item("Tool: Shell Satchel", ItemType.Tool, null), new Item("Tool: Dead Bug's Purse", ItemType.Tool, null), new Item("Tool: Scuttlebrace", ItemType.Tool, null), new Item("Tool: Thief's Mark", ItemType.Tool, null), new Item("Tool: Compass", ItemType.Tool, null), new Item("Tool: Snitch Pick", ItemType.Tool, null), new Item("Tool: Flintslate", ItemType.Tool, null), new Item("Tool: Wreath of Purity", ItemType.Tool, null), new Item("Tool: Magma Bell", ItemType.Tool, null), new Item("Tool: Ascendant's Grip", ItemType.Tool, null), new Item("Tool: Longclaw", ItemType.Tool, null), new Item("Tool: Shard Pendant", ItemType.Tool, null), new Item("Tool: Spool Extender", ItemType.Tool, null), new Item("Tool: Egg of Flealia", ItemType.Tool, null), new Item("Silk Skill: Silkspear", ItemType.Spell, null), new Item("Silk Skill: Cross Stitch", ItemType.Spell, null), new Item("Silk Skill: Pale Nails", ItemType.Spell, null), new Item("Silk Skill: Sharpdart", ItemType.Spell, null), new Item("Silk Skill: Rune Rage", ItemType.Spell, null), new Item("Silk Skill: Thread Storm", ItemType.Spell, null), new Item("Crest: Witch", ItemType.Crest, delegate { Utils.ForceCrest("Witch"); }), new Item("Crest: Beast", ItemType.Crest, delegate { Utils.ForceCrest("Warrior"); }), new Item("Crest: Architect", ItemType.Crest, delegate { Utils.ForceCrest("Toolmaster"); }), new Item("Crest: Shaman", ItemType.Crest, delegate { Utils.ForceCrest("Spell"); }), new Item("Crest: Reaper", ItemType.Crest, delegate { Utils.ForceCrest("Reaper"); }), new Item("Crest: Wanderer", ItemType.Crest, delegate { Utils.ForceCrest("Wanderer"); }), new Item("Crest: Hunter", ItemType.Crest, delegate { Utils.ForceCrest("Hunter"); }), new Item("Flea: The Marrow", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Bone_06 = true; }), new Item("Flea: Deep Docks (Bellway)", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Dock_16 = true; }), new Item("Flea: Deep Docks (Weaver Burial Spire)", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Bone_East_05 = true; }), new Item("Flea: Far Fields (Captured)", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Bone_East_17b = true; }), new Item("Flea: Hunter's March", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Ant_03 = true; }), new Item("Flea: Greymoor (Craw Lake)", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Greymoor_15b = true; }), new Item("Flea: Greymoor (Tower)", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Greymoor_06 = true; }), new Item("Flea: Shellwood", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Shellwood_03 = true; }), new Item("Flea: Pilgrim's Rest", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Bone_East_10_Church = true; }), new Item("Flea: Blasted Steps", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Coral_35 = true; }), new Item("Flea: Sinner's Road", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Dust_12 = true; }), new Item("Flea: Exhaust Organ", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Dust_09 = true; }), new Item("Flea: Bellhart", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Belltown_04 = true; }), new Item("Flea: Wormways", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Crawl_06 = true; }), new Item("Flea: The Slab (Cell)", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Slab_Cell = true; }), new Item("Flea: Bilewater (Thieves)", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Shadow_28 = true; }), new Item("Flea: Deep Docks (Mines)", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Dock_03d = true; }), new Item("Flea: Wisp Thicket", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Under_23 = true; }), new Item("Flea: Bilehaven", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Shadow_10 = true; }), new Item("Flea: Choral Chambers (Spa)", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Song_14 = true; }), new Item("Flea: Sands of Karak", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Coral_24 = true; }), new Item("Flea: Mount Fay", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Peak_05c = true; }), new Item("Flea: Songclave", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Library_09 = true; }), new Item("Flea: Choral Chambers (Walled Room)", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Song_11 = true; }), new Item("Flea: Whispering Vaults", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Library_01 = true; }), new Item("Flea: Underworks", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Under_21 = true; }), new Item("Flea: The Slab (Bellway)", ItemType.Flea, delegate { SaveState.Instance.SavedFlea_Slab_06 = true; }), new Item("Flea: Greymoor (Kratt)", ItemType.Flea, ItemGrants.GrantKrattFlea), new Item("Flea: Putrified Ducts (Vog)", ItemType.Flea, ItemGrants.GrantVogFlea), new Item("Flea: Memorium (Huge Flea)", ItemType.Flea, ItemGrants.GrantHugeFlea), new Item("Crest Slot: Hunter (Red 1)", ItemType.CrestSlot, null), new Item("Crest Slot: Hunter (Blue 1)", ItemType.CrestSlot, null), new Item("Crest Slot: Hunter (Yellow 1)", ItemType.CrestSlot, null), new Item("Crest Slot: Reaper (Red 1)", ItemType.CrestSlot, null), new Item("Crest Slot: Reaper (Blue 1)", ItemType.CrestSlot, null), new Item("Crest Slot: Reaper (Yellow 1)", ItemType.CrestSlot, null), new Item("Crest Slot: Wanderer (Blue 1)", ItemType.CrestSlot, null), new Item("Crest Slot: Wanderer (Blue 2)", ItemType.CrestSlot, null), new Item("Crest Slot: Wanderer (Yellow 1)", ItemType.CrestSlot, null), new Item("Crest Slot: Beast (Yellow 1)", ItemType.CrestSlot, null), new Item("Crest Slot: Beast (Yellow 2)", ItemType.CrestSlot, null), new Item("Crest Slot: Witch (Red 1)", ItemType.CrestSlot, null), new Item("Crest Slot: Witch (Blue 1)", ItemType.CrestSlot, null), new Item("Crest Slot: Witch (Blue 2)", ItemType.CrestSlot, null), new Item("Crest Slot: Architect (Blue 1)", ItemType.CrestSlot, null), new Item("Crest Slot: Architect (Yellow 1)", ItemType.CrestSlot, null), new Item("Crest Slot: Architect (Yellow 2)", ItemType.CrestSlot, null), new Item("Crest Slot: Architect (Blue 2)", ItemType.CrestSlot, null), new Item("Crest Slot: Shaman (Blue 1)", ItemType.CrestSlot, null), new Item("Crest Slot: Shaman (Blue 2)", ItemType.CrestSlot, null), new Item("Mask Shard #1", ItemType.MaskShard, null), new Item("Mask Shard #2", ItemType.MaskShard, null), new Item("Mask Shard #3", ItemType.MaskShard, null), new Item("Mask Shard #4", ItemType.MaskShard, null), new Item("Mask Shard #5", ItemType.MaskShard, null), new Item("Mask Shard #6", ItemType.MaskShard, null), new Item("Mask Shard #7", ItemType.MaskShard, null), new Item("Mask Shard #8", ItemType.MaskShard, null), new Item("Mask Shard #9", ItemType.MaskShard, null), new Item("Mask Shard #10", ItemType.MaskShard, null), new Item("Mask Shard #11", ItemType.MaskShard, null), new Item("Mask Shard #12", ItemType.MaskShard, null), new Item("Mask Shard #13", ItemType.MaskShard, null), new Item("Mask Shard #14", ItemType.MaskShard, null), new Item("Mask Shard #15", ItemType.MaskShard, null), new Item("Mask Shard #16", ItemType.MaskShard, null), new Item("Mask Shard #17", ItemType.MaskShard, null), new Item("Mask Shard #18", ItemType.MaskShard, null), new Item("Mask Shard #19", ItemType.MaskShard, null), new Item("Mask Shard #20", ItemType.MaskShard, null), new Item("Spool Fragment #1", ItemType.SpoolFragment, null), new Item("Spool Fragment #2", ItemType.SpoolFragment, null), new Item("Spool Fragment #3", ItemType.SpoolFragment, null), new Item("Spool Fragment #4", ItemType.SpoolFragment, null), new Item("Spool Fragment #5", ItemType.SpoolFragment, null), new Item("Spool Fragment #6", ItemType.SpoolFragment, null), new Item("Spool Fragment #7", ItemType.SpoolFragment, null), new Item("Spool Fragment #8", ItemType.SpoolFragment, null), new Item("Spool Fragment #9", ItemType.SpoolFragment, null), new Item("Spool Fragment #10", ItemType.SpoolFragment, null), new Item("Spool Fragment #11", ItemType.SpoolFragment, null), new Item("Spool Fragment #12", ItemType.SpoolFragment, null), new Item("Spool Fragment #13", ItemType.SpoolFragment, null), new Item("Spool Fragment #14", ItemType.SpoolFragment, null), new Item("Spool Fragment #15", ItemType.SpoolFragment, null), new Item("Spool Fragment #16", ItemType.SpoolFragment, null), new Item("Spool Fragment #17", ItemType.SpoolFragment, null), new Item("Spool Fragment #18", ItemType.SpoolFragment, null), new Item("Progressive Silkheart", ItemType.SilkHeart, ItemGrants.GrantProgressiveSilkheart, repeatable: true), new Item("Bellway: Deep Docks", ItemType.Bellway, delegate { SaveState.Instance.UnlockedDocksStation = true; }), new Item("Bellway: Far Fields", ItemType.Bellway, delegate { SaveState.Instance.UnlockedBoneforestEastStation = true; }), new Item("Bellway: Greymoor", ItemType.Bellway, delegate { SaveState.Instance.UnlockedGreymoorStation = true; }), new Item("Bellway: Bellhart", ItemType.Bellway, delegate { SaveState.Instance.UnlockedBelltownStation = true; }), new Item("Bellway: Blasted Steps", ItemType.Bellway, delegate { SaveState.Instance.UnlockedCoralTowerStation = true; }), new Item("Bellway: Grand Bellway", ItemType.Bellway, delegate { SaveState.Instance.UnlockedCityStation = true; }), new Item("Bellway: The Slab", ItemType.Bellway, delegate { SaveState.Instance.UnlockedPeakStation = true; }), new Item("Bellway: Shellwood", ItemType.Bellway, delegate { SaveState.Instance.UnlockedShellwoodStation = true; }), new Item("Bellway: Bilewater", ItemType.Bellway, delegate { SaveState.Instance.UnlockedShadowStation = true; }), new Item("Bellway: Putrified Ducts", ItemType.Bellway, delegate { SaveState.Instance.UnlockedAqueductStation = true; }), new Item("Ventrica: Choral Chambers", ItemType.Ventrica, delegate { SaveState.Instance.UnlockedSongTube = true; }), new Item("Ventrica: Underworks", ItemType.Ventrica, delegate { SaveState.Instance.UnlockedUnderTube = true; }), new Item("Ventrica: Grand Bellway", ItemType.Ventrica, delegate { SaveState.Instance.UnlockedCityBellwayTube = true; }), new Item("Ventrica: High Halls", ItemType.Ventrica, delegate { SaveState.Instance.UnlockedHangTube = true; }), new Item("Ventrica: Songclave", ItemType.Ventrica, delegate { SaveState.Instance.UnlockedEnclaveTube = true; }), new Item("Ventrica: Memorium", ItemType.Ventrica, delegate { SaveState.Instance.UnlockedArboriumTube = true; }), new Item("Victory", ItemType.Unknown, delegate { SaveState.Instance.goalCompleted = true; }), new Item("Rosaries (10)", ItemType.Resource, delegate { ItemGrants.GrantRosaries(10); }, repeatable: true), new Item("Shell Shards (10)", ItemType.Resource, delegate { ItemGrants.GrantShellShards(10); }, repeatable: true), new Item("Simple Key (Wormways)", ItemType.SimpleKey, SimpleKeyDoorManager.GrantWormwaysKey), new Item("Simple Key (Deep Docks)", ItemType.SimpleKey, SimpleKeyDoorManager.GrantDeepDocksKey), new Item("Simple Key (Green Prince)", ItemType.SimpleKey, SimpleKeyDoorManager.GrantGreenPrinceKey), new Item("Simple Key (Rosary Bank)", ItemType.SimpleKey, SimpleKeyDoorManager.GrantRosaryBankKey), new Item("Architect's Melody", ItemType.Melody, delegate { ItemGrants.GrantMelody(delegate(PlayerData pd) { pd.HasMelodyArchitect = true; }); }), new Item("Conductor's Melody", ItemType.Melody, delegate { ItemGrants.GrantMelody(delegate(PlayerData pd) { pd.HasMelodyConductor = true; }); }), new Item("Vaultkeeper's Melody", ItemType.Melody, delegate { ItemGrants.GrantMelody(delegate(PlayerData pd) { pd.HasMelodyLibrarian = true; }); }), new Item("Elegy of the Deep", ItemType.Melody, delegate { ItemGrants.GrantMelody(delegate(PlayerData pd) { pd.hasNeedolinMemoryPowerup = true; }); }), new Item("Beastling Call", ItemType.Melody, ItemGrants.GrantBeastlingCall), new Item("Memory Locket", ItemType.MemoryLocket, delegate { ItemGrants.GrantCollectable("Crest Socket Unlocker"); }, repeatable: true), new Item("Craftmetal", ItemType.Craftmetal, delegate { ItemGrants.GrantCollectable("Tool Metal"); }, repeatable: true), new Item("Mossberry", ItemType.Mossberry, delegate { ItemGrants.GrantCollectable("Mossberry"); }, repeatable: true), new Item("Pollip Heart", ItemType.PollipHeart, delegate { ItemGrants.GrantCollectable("Shell Flower"); }, repeatable: true), new Item("Silkeater", ItemType.Silkeater, delegate { ItemGrants.GrantCollectable("Silk Grub"); }, repeatable: true), new Item("Key of Indolent", ItemType.MajorKey, delegate { ItemGrants.GrantSlabKey(delegate(PlayerData pd) { pd.HasSlabKeyA = true; }); }), new Item("Key of Heretic", ItemType.MajorKey, delegate { ItemGrants.GrantSlabKey(delegate(PlayerData pd) { pd.HasSlabKeyB = true; }); }), new Item("Key of Apostate", ItemType.MajorKey, delegate { ItemGrants.GrantSlabKey(delegate(PlayerData pd) { pd.HasSlabKeyC = true; }); }), new Item("White Key", ItemType.MajorKey, ItemGrants.GrantWhiteKey), new Item("Surgeon's Key", ItemType.MajorKey, ItemGrants.GrantSurgeonsKey), new Item("Architect's Key", ItemType.MajorKey, ItemGrants.GrantArchitectsKey), new Item("Craw Summons", ItemType.MajorKey, ItemGrants.GrantCrawSummons) }; internal static string GetCanonicalItemName(string itemName) { if (string.IsNullOrWhiteSpace(itemName)) { return itemName; } string value; string text = (NativeItemNameAliases.TryGetValue(itemName, out value) ? value : itemName); if (CurrentFleaDisplayNames.TryGetValue(text, out var value2)) { return value2; } string[] array = new string[4] { "Ability: ", "Ancestral Art: ", "Tool: ", "Silk Skill: " }; foreach (string text2 in array) { if (text.StartsWith(text2, StringComparison.OrdinalIgnoreCase)) { return text.Substring(text2.Length); } } return text; } } public class Location { public readonly string SourceName; public readonly string Name; public readonly ItemType Type; public readonly Func Check; public Location(string name, ItemType type, Func check) { SourceName = name; Name = LocationSet.GetCanonicalLocationName(name); Type = type; Check = check; } } public class LocationSet { internal const string DriftersCloakLocationName = "Drifter's Cloak"; internal const string DriftersCloakQuestAssetName = "Brolly Get"; internal static readonly string[] MaskShardLocationNames = new string[20] { "Mask Shard: Pebb (Bone Bottom) / Grindle (Act 3)", "Mask Shard: Wormways", "Mask Shard: Far Fields (Above the Seamstress)", "Mask Shard: Shellwood", "Mask Shard: The Marrow - Deep Docks Passage", "Mask Shard: Weavenest Atla", "Mask Shard: Savage Beastfly", "Mask Shard: Cogwork Core", "Mask Shard: Whispering Vaults", "Mask Shard: Bilewater", "Mask Shard: Far Fields (Skull Cave)", "Mask Shard: The Slab (Key of the Apostate)", "Mask Shard: Mount Fay", "Mask Shard: Wisp Thicket", "Mask Shard: Jubilana (Songclave)", "Mask Shard: Blasted Steps", "Mask Shard: Fastest in Pharloom", "Mask Shard: The Hidden Hunter", "Mask Shard: Dark Hearts", "Mask Shard: Brightvein" }; internal static readonly string[] SpoolFragmentLocationNames = new string[18] { "Spool Fragment: Bone Bottom", "Spool Fragment: Deep Docks (Central)", "Spool Fragment: Greymoor", "Spool Fragment: The Slab", "Spool Fragment: Weavenest Atla", "Spool Fragment: Frey (Bellhart)", "Spool Fragment: Flea Caravan", "Spool Fragment: Cogwork Core", "Spool Fragment: Underworks (East)", "Spool Fragment: Grand Gate", "Spool Fragment: Underworks (Gauntlet)", "Spool Fragment: Whiteward", "Spool Fragment: Balm for the Wounded", "Spool Fragment: Deep Docks (Southeast)", "Spool Fragment: High Halls", "Spool Fragment: Memorium", "Spool Fragment: Grindle (Blasted Steps)", "Spool Fragment: Jubilana (Songclave)" }; internal static readonly string[] SilkHeartLocationNames = new string[3] { "Silk Heart: Bell Beast", "Silk Heart: The Unravelled", "Silk Heart: Lace (Cradle)" }; private static readonly Dictionary CanonicalLocationItemNameCollisions = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Claw Mirror", "Claw Mirror" }, { "Curveclaw", "Curveclaw" } }; private static readonly Dictionary ExplicitLocationRenames = BuildExplicitLocationRenames(); internal static readonly Dictionary SourceLocationNameAliases = BuildSourceLocationNameAliases(); public Location[] Locations = BeastShardSourceManifest.AppendTo(ToolPouchLocationManifest.AppendTo(CardiniusCylinderTurnInManifest.AppendTo(ScroungeRelicTurnInManifest.AppendTo(CollectibleSourceManifest.AppendTo(MinorCacheManifest.AppendTo(MinorPickupManifest.AppendTo(QuestLocationManifest.AppendTo(MaskAndSpoolLocationManifest.AppendTo(CoreLocationManifest.AppendTo(new Location[171] { new Location("Skill Unlock: Double Jump", ItemType.Skill, () => PlayerData.instance.hasDoubleJump), new Location("Skill Unlock: Charge Slash", ItemType.Skill, () => PlayerData.instance.hasChargeSlash), new Location("Skill Unlock: Silk Soar", ItemType.Skill, () => PlayerData.instance.hasSuperJump), new Location("Skill Unlock: Wall Jump", ItemType.Skill, () => PlayerData.instance.hasWalljump), new Location("Skill Unlock: Drifter's Cloak", ItemType.Skill, IsDriftersCloakSourceCompleted), new Location("Skill Unlock: Dash", ItemType.Skill, () => PlayerData.instance.hasDash), new Location("Skill Unlock: Harpoon", ItemType.Skill, () => PlayerData.instance.hasHarpoonDash), new Location("Skill Unlock: Quill", ItemType.Skill, () => PlayerData.instance.hasQuill), new Location("Skill Unlock: Needolin", ItemType.Skill, () => PlayerData.instance.hasNeedolin), new Location("Ruined Tool", ItemType.Tool, null), new Location("Tool Unlock: WebShot Forge", ItemType.Tool, null), new Location("Tool Unlock: WebShot Architect", ItemType.Tool, null), new Location("Tool Unlock: WebShot Weaver", ItemType.Tool, null), new Location("Tool Unlock: Zap Imbuement", ItemType.Tool, null), new Location("Tool Unlock: Tack", ItemType.Tool, null), new Location("Tool Unlock: Poison Pouch", ItemType.Tool, null), new Location("Tool Unlock: Silk Snare", ItemType.Tool, null), new Location("Tool Unlock: Wisp Lantern", ItemType.Tool, null), new Location("Tool Unlock: Revenge Crystal", ItemType.Tool, null), new Location("Tool Unlock: Lightning Rod", ItemType.Tool, null), new Location("Tool Unlock: Tri Pin", ItemType.Tool, null), new Location("Tool Unlock: Bell Bind", ItemType.Tool, null), new Location("Tool Unlock: Rosary Cannon", ItemType.Tool, null), new Location("Tool Unlock: Harpoon", ItemType.Tool, null), new Location("Tool Unlock: Brolly Spike", ItemType.Tool, null), new Location("Tool Unlock: Dazzle Bind", ItemType.Tool, null), new Location("Tool Unlock: Dazzle Bind Upgraded", ItemType.Tool, null), new Location("Tool Unlock: Shakra Ring", ItemType.Tool, null), new Location("Tool Unlock: Screw Attack", ItemType.Tool, null), new Location("Tool Unlock: Quick Sling", ItemType.Tool, null), new Location("Tool Unlock: Musician Charm", ItemType.Tool, null), new Location("Tool Unlock: Fractured Mask", ItemType.Tool, null), new Location("Tool Unlock: Sprintmaster", ItemType.Tool, null), new Location("Tool Unlock: Weighted Anklet", ItemType.Tool, null), new Location("Tool Unlock: Multibind", ItemType.Tool, null), new Location("Tool Unlock: Reserve Bind", ItemType.Tool, null), new Location("Tool Unlock: Quickbind", ItemType.Tool, null), new Location("Tool Unlock: Barbed Wire", ItemType.Tool, null), new Location("Tool Unlock: Sting Shard", ItemType.Tool, null), new Location("Tool Unlock: Conch Drill", ItemType.Tool, null), new Location("Tool Unlock: Pimpilo", ItemType.Tool, null), new Location("Tool Unlock: Straight Pin", ItemType.Tool, null), new Location("Tool Unlock: Cogwork Flier", ItemType.Tool, null), new Location("Tool Unlock: Curve Claws", ItemType.Tool, null), new Location("Tool Unlock: Curve Claws Upgraded", ItemType.Tool, null), new Location("Tool Unlock: Cogwork Saw", ItemType.Tool, null), new Location("Tool Unlock: Flea Brew", ItemType.Tool, null), new Location("Tool Unlock: Magnetite Dice", ItemType.Tool, null), new Location("Tool Unlock: Rosary Magnet", ItemType.Tool, null), new Location("Tool Unlock: White Ring", ItemType.Tool, null), new Location("Tool Unlock: Pinstress Tool", ItemType.Tool, null), new Location("Tool Unlock: Extractor", ItemType.Tool, null), new Location("Tool Unlock: Lifeblood Syringe", ItemType.Tool, null), new Location("Tool Unlock: Shell Satchel", ItemType.Tool, null), new Location("Tool Unlock: Dead Mans Purse", ItemType.Tool, null), new Location("Tool Unlock: Scuttlebrace", ItemType.Tool, null), new Location("Tool Unlock: Thief Charm", ItemType.Tool, null), new Location("Tool Unlock: Compass", ItemType.Tool, null), new Location("Tool Unlock: Thief Claw", ItemType.Tool, null), new Location("Tool Unlock: Mosscreep Tool 1", ItemType.Tool, null), new Location("Tool Unlock: Mosscreep Tool 2", ItemType.Tool, null), new Location("Tool Unlock: Flintstone", ItemType.Tool, null), new Location("Tool Unlock: Maggot Charm", ItemType.Tool, null), new Location("Tool Unlock: Lava Charm", ItemType.Tool, null), new Location("Tool Unlock: Wallcling", ItemType.Tool, null), new Location("Tool Unlock: Longneedle", ItemType.Tool, null), new Location("Tool Unlock: Bone Necklace", ItemType.Tool, null), new Location("Tool Unlock: Spool Extender", ItemType.Tool, null), new Location("Tool Unlock: Flea Charm", ItemType.Tool, null), new Location("Spell Unlock: Silk Spear", ItemType.Spell, null), new Location("Spell Unlock: Parry", ItemType.Spell, null), new Location("Spell Unlock: Silk Boss Needle", ItemType.Spell, null), new Location("Spell Unlock: Silk Charge", ItemType.Spell, null), new Location("Spell Unlock: Silk Bomb", ItemType.Spell, null), new Location("Spell Unlock: Thread Sphere", ItemType.Spell, null), new Location("Crest Unlock: Witch", ItemType.Crest, null), new Location("Crest Unlock: Beast", ItemType.Crest, null), new Location("Crest Unlock: Architect", ItemType.Crest, null), new Location("Crest Unlock: Shaman", ItemType.Crest, null), new Location("Crest Unlock: Reaper", ItemType.Crest, null), new Location("Crest Unlock: Wanderer", ItemType.Crest, null), new Location("Crest Unlock: Hunter", ItemType.Crest, () => (Object)(object)HeroController.instance != (Object)null && HeroController.instance.CanJump()), new Location("Save Flea: The Marrow (Tangle)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Bone_06) { RandomizerPlugin.Instance.ShowFleaMessage("Tangle"); return true; } return false; }), new Location("Save Flea: Deep Docks Bellway (Eepy)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Dock_16) { RandomizerPlugin.Instance.ShowFleaMessage("Eepy"); return true; } return false; }), new Location("Save Flea: Deep Docks Weaver Burial Spire (Squeesh)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Bone_East_05) { RandomizerPlugin.Instance.ShowFleaMessage("Squeesh"); return true; } return false; }), new Location("Save Flea: Far Fields Captured (Thoughtless)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Bone_East_17b) { RandomizerPlugin.Instance.ShowFleaMessage("Thoughtless"); return true; } return false; }), new Location("Save Flea: Hunter's March (Yapper)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Ant_03) { RandomizerPlugin.Instance.ShowFleaMessage("Yapper"); return true; } return false; }), new Location("Save Flea: Greymoor Craw Lake (Gwah)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Greymoor_15b) { RandomizerPlugin.Instance.ShowFleaMessage("Gwah"); return true; } return false; }), new Location("Save Flea: Greymoor Tower (Snoozles)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Greymoor_06) { RandomizerPlugin.Instance.ShowFleaMessage("Snoozles"); return true; } return false; }), new Location("Save Flea: Shellwood (Shelly)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Shellwood_03) { RandomizerPlugin.Instance.ShowFleaMessage("Shelly"); return true; } return false; }), new Location("Save Flea: Pilgrim's Rest (Sleeby)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Bone_East_10_Church) { RandomizerPlugin.Instance.ShowFleaMessage("Sleeby"); return true; } return false; }), new Location("Save Flea: Blasted Steps (Nini)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Coral_35) { RandomizerPlugin.Instance.ShowFleaMessage("Nini"); return true; } return false; }), new Location("Save Flea: Sinner's Road (Stelf)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Dust_12) { RandomizerPlugin.Instance.ShowFleaMessage("Stelf"); return true; } return false; }), new Location("Save Flea: Exhaust Organ (Rangle)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Dust_09) { RandomizerPlugin.Instance.ShowFleaMessage("Rangle"); return true; } return false; }), new Location("Save Flea: Bellhart (Bellphy)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Belltown_04) { RandomizerPlugin.Instance.ShowFleaMessage("Bellphy"); return true; } return false; }), new Location("Save Flea: Wormways (Snacc)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Crawl_06) { RandomizerPlugin.Instance.ShowFleaMessage("Snacc"); return true; } return false; }), new Location("Save Flea: The Slab Cell (Sway)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Slab_Cell) { RandomizerPlugin.Instance.ShowFleaMessage("Sway"); return true; } return false; }), new Location("Save Flea: Bilewater Thieves (Cower)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Shadow_28) { RandomizerPlugin.Instance.ShowFleaMessage("Cower"); return true; } return false; }), new Location("Save Flea: Deep Docks Mines (Le Bomba)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Dock_03d) { RandomizerPlugin.Instance.ShowFleaMessage("Le Bomba"); return true; } return false; }), new Location("Save Flea: Wisp Thicket (Fidget)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Under_23) { RandomizerPlugin.Instance.ShowFleaMessage("Fidget"); return true; } return false; }), new Location("Save Flea: Bilehaven (Spangle)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Shadow_10) { RandomizerPlugin.Instance.ShowFleaMessage("Spangle"); return true; } return false; }), new Location("Save Flea: Choral Cambers Spa (Oowa)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Song_14) { RandomizerPlugin.Instance.ShowFleaMessage("Oowa"); return true; } return false; }), new Location("Save Flea: Sands of Karak (Crustly)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Coral_24) { RandomizerPlugin.Instance.ShowFleaMessage("Crustly"); return true; } return false; }), new Location("Save Flea: Mount Fay (Ice Cube)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Peak_05c) { RandomizerPlugin.Instance.ShowFleaMessage("Ice Cube"); return true; } return false; }), new Location("Save Flea: Songclave (Groggy)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Library_09) { RandomizerPlugin.Instance.ShowFleaMessage("Groggy"); return true; } return false; }), new Location("Save Flea: Choral Cambers Walled (Mimi)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Song_11) { RandomizerPlugin.Instance.ShowFleaMessage("Mimi"); return true; } return false; }), new Location("Save Flea: Whispering Vaults (Birdy)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Library_01) { RandomizerPlugin.Instance.ShowFleaMessage("Birdy"); return true; } return false; }), new Location("Save Flea: Underworks (Boomy)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Under_21) { RandomizerPlugin.Instance.ShowFleaMessage("Boomy"); return true; } return false; }), new Location("Save Flea: The Slab Bellway (Honk Shoo)", ItemType.Flea, delegate { if (PlayerData.instance.SavedFlea_Slab_06) { RandomizerPlugin.Instance.ShowFleaMessage("Honk Shoo"); return true; } return false; }), new Location("Flea: Greymoor - Kratt", ItemType.Flea, null), new Location("Flea: Putrified Ducts - Vog", ItemType.Flea, null), new Location("Flea: Memorium - Huge Flea", ItemType.Flea, null), new Location("Hunter Slot Unlock: Red 0 -2", ItemType.CrestSlot, null), new Location("Hunter Slot Unlock: Blue -2 0", ItemType.CrestSlot, null), new Location("Hunter Slot Unlock: Yellow 2 0", ItemType.CrestSlot, null), new Location("Reaper Slot Unlock: Red 0 -1", ItemType.CrestSlot, null), new Location("Reaper Slot Unlock: Blue -1 1", ItemType.CrestSlot, null), new Location("Reaper Slot Unlock: Yellow 1 1", ItemType.CrestSlot, null), new Location("Wanderer Slot Unlock: Blue -2 1", ItemType.CrestSlot, null), new Location("Wanderer Slot Unlock: Blue 2 1", ItemType.CrestSlot, null), new Location("Wanderer Slot Unlock: Yellow 0 -2", ItemType.CrestSlot, null), new Location("Beast Slot Unlock: Yellow -1 0", ItemType.CrestSlot, null), new Location("Beast Slot Unlock: Yellow 1 0", ItemType.CrestSlot, null), new Location("Witch Slot Unlock: Red 0 -2", ItemType.CrestSlot, null), new Location("Witch Slot Unlock: Blue -1 0", ItemType.CrestSlot, null), new Location("Witch Slot Unlock: Blue 1 0", ItemType.CrestSlot, null), new Location("Architect Slot Unlock: Blue -1 2", ItemType.CrestSlot, null), new Location("Architect Slot Unlock: Yellow 1 2", ItemType.CrestSlot, null), new Location("Architect Slot Unlock: Yellow 2 0", ItemType.CrestSlot, null), new Location("Architect Slot Unlock: Blue -2 0", ItemType.CrestSlot, null), new Location("Shaman Slot Unlock: Blue -1 0", ItemType.CrestSlot, null), new Location("Shaman Slot Unlock: Blue 1 0", ItemType.CrestSlot, null), new Location("Silk Heart Unlock #1", ItemType.SilkHeart, () => PlayerData.instance.scenesVisited != null && PlayerData.instance.scenesVisited.Contains("Memory_Silk_Heart_BellBeast")), new Location("Silk Heart Unlock #2", ItemType.SilkHeart, () => PlayerData.instance.scenesVisited != null && PlayerData.instance.scenesVisited.Contains("Memory_Silk_Heart_WardBoss")), new Location("Silk Heart Unlock #3", ItemType.SilkHeart, () => PlayerData.instance.scenesVisited != null && PlayerData.instance.scenesVisited.Contains("Memory_Silk_Heart_LaceTower")), new Location("Bellway Unlock: Deep Docks", ItemType.Bellway, () => PlayerData.instance.UnlockedDocksStation), new Location("Bellway Unlock: Far Fields", ItemType.Bellway, () => PlayerData.instance.UnlockedBoneforestEastStation), new Location("Bellway Unlock: Greymoor", ItemType.Bellway, () => PlayerData.instance.UnlockedGreymoorStation), new Location("Bellway Unlock: Bellhart", ItemType.Bellway, () => PlayerData.instance.UnlockedBelltownStation), new Location("Bellway Unlock: Blasted Steps", ItemType.Bellway, () => PlayerData.instance.UnlockedCoralTowerStation), new Location("Bellway Unlock: Grand Bellway", ItemType.Bellway, () => PlayerData.instance.UnlockedCityStation), new Location("Bellway Unlock: The Slab", ItemType.Bellway, () => PlayerData.instance.UnlockedPeakStation), new Location("Bellway Unlock: Shellwood", ItemType.Bellway, () => PlayerData.instance.UnlockedShellwoodStation), new Location("Bellway Unlock: Bilewater", ItemType.Bellway, () => PlayerData.instance.UnlockedShadowStation), new Location("Bellway Unlock: Putrified Ducts", ItemType.Bellway, () => PlayerData.instance.UnlockedAqueductStation), new Location("Ventrica Unlock: Choral Chambers", ItemType.Ventrica, () => PlayerData.instance.UnlockedSongTube), new Location("Ventrica Unlock: Underworks", ItemType.Ventrica, () => PlayerData.instance.UnlockedUnderTube), new Location("Ventrica Unlock: Grand Bellway", ItemType.Ventrica, () => PlayerData.instance.UnlockedCityBellwayTube), new Location("Ventrica Unlock: High Halls", ItemType.Ventrica, () => PlayerData.instance.UnlockedHangTube), new Location("Ventrica Unlock: Songclave", ItemType.Ventrica, () => PlayerData.instance.UnlockedEnclaveTube), new Location("Ventrica Unlock: Memorium", ItemType.Ventrica, () => PlayerData.instance.UnlockedArboriumTube), new Location("Goal", ItemType.Event, null), new Location("Map Pickup: Weavenest Atla", ItemType.Map, null), new Location("Map Purchase: Grand Gate", ItemType.Map, null), new Location("Map Pickup: Underworks", ItemType.Map, null), new Location("Map Purchase: Choral Chambers", ItemType.Map, null), new Location("Map Purchase: Whispering Vaults", ItemType.Map, null), new Location("Map Purchase: Whiteward", ItemType.Map, null), new Location("Map Pickup: Cogwork Core", ItemType.Map, null), new Location("Map Purchase: Memorium", ItemType.Map, null), new Location("Map Purchase: High Halls", ItemType.Map, null), new Location("Map Pickup: The Slab", ItemType.Map, null), new Location("Map Pickup: Putrified Ducts", ItemType.Map, null), new Location("Map Purchase: The Cradle", ItemType.Map, null), new Location("Map Pickup: Verdania", ItemType.Map, null), new Location("Map Pickup: The Abyss", ItemType.Map, null), new Location("Architect's Melody", ItemType.Melody, null), new Location("Conductor's Melody", ItemType.Melody, null), new Location("Vaultkeeper's Melody", ItemType.Melody, null), new Location("Elegy of the Deep", ItemType.Melody, null), new Location("Beastling Call", ItemType.Melody, null) })))))))))).ToArray(); internal static void RestoreDriftersCloakSourceFlag() { PlayerData instance = PlayerData.instance; if (instance != null) { instance.hasBrolly = true; } } internal static bool IsDriftersCloakSourceCompleted() { //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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) PlayerData instance = PlayerData.instance; if (instance == null) { return false; } if (instance.hasBrolly) { return true; } if (instance.QuestCompletionData == null) { return false; } Completion data = ((SerializableNamedList)(object)instance.QuestCompletionData).GetData("Brolly Get"); int num; if (!data.IsCompleted) { num = (data.WasEverCompleted ? 1 : 0); if (num == 0) { goto IL_0049; } } else { num = 1; } RestoreDriftersCloakSourceFlag(); goto IL_0049; IL_0049: return (byte)num != 0; } private static Dictionary BuildExplicitLocationRenames() { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Skill Unlock: Drifter's Cloak", "Drifter's Cloak" }, { "Bell Shrine Completion: bellShrineBoneForest", "Bellshrine: The Marrow" }, { "Bell Shrine Completion: bellShrineWilds", "Bellshrine: Deep Docks" }, { "Bell Shrine Completion: bellShrineGreymoor", "Bellshrine: Greymoor" }, { "Bell Shrine Completion: bellShrineShellwood", "Bellshrine: Shellwood" }, { "Bell Shrine Completion: bellShrineBellhart", "Bellshrine: Bellhart" }, { "Bell Shrine Completion: bellShrineEnclave", "Bellshrine: Songclave" }, { "Boss Completion: defeatedMossMother", "Boss: Moss Mother" }, { "Boss Completion: skullKingKilled", "Boss: Skull Tyrant (Bone Bottom)" }, { "Boss Completion: defeatedBellBeast", "Boss: Bell Beast" }, { "Boss Completion: defeatedAntQueen", "Boss: Skarrsinger Karmelita" }, { "Boss Completion: defeatedLace1", "Boss: Lace (Deep Docks)" }, { "Boss Completion: defeatedSongGolem", "Boss: Fourth Chorus" }, { "Boss Completion: defeatedDockForemen", "Boss: Forebrothers Signis & Gron" }, { "Boss Completion: defeatedVampireGnatBoss", "Boss: Moorwing" }, { "Boss Completion: defeatedCrowCourt", "Boss: Crawfather" }, { "Boss Completion: defeatedSplinterQueen", "Boss: Sister Splinter" }, { "Boss Completion: defeatedSeth", "Boss: Shrine Guardian Seth" }, { "Boss Completion: defeatedFlowerQueen", "Boss: Nyleth" }, { "Boss Completion: defeatedRoachkeeperChef", "Boss: Disgraced Chef Lugoli" }, { "Boss Completion: defeatedPhantom", "Boss: Phantom" }, { "Boss Completion: DefeatedSwampShaman", "Boss: Groal the Great" }, { "Boss Completion: defeatedCoralKing", "Boss: Crust King Khann" }, { "Boss Completion: defeatedCoralDrillers", "Boss: Great Conchflies" }, { "Boss Completion: defeatedLastJudge", "Boss: Last Judge" }, { "Boss Completion: defeatedGreyWarrior", "Boss: Watcher at the Edge" }, { "Boss Completion: defeatedFirstWeaver", "Boss: First Sinner" }, { "Boss Completion: defeatedBroodMother", "Boss: Broodmother" }, { "Boss Completion: defeatedTrobbio", "Boss: Trobbio" }, { "Boss Completion: defeatedTormentedTrobbio", "Boss: Tormented Trobbio" }, { "Boss Completion: defeatedCogworkDancers", "Boss: Cogwork Dancers" }, { "Boss Completion: defeatedLaceTower", "Boss: Lace (Cradle)" }, { "Boss Completion: defeatedSongChevalierBoss", "Boss: Second Sentinel" }, { "Boss Completion: defeatedWhiteCloverstag", "Boss: Palestag" }, { "Boss Completion: defeatedCloverDancers", "Boss: Clover Dancers" }, { "Boss Completion: defeatedWispPyreEffigy", "Boss: Father of the Flame" }, { "Boss Completion: defeatedAntTrapper", "Boss: Gurr the Outcast" }, { "Boss Completion: defeatedCoralDrillerSolo", "Boss: Raging Conchfly" }, { "Boss Completion: wardBossDefeated", "Boss: The Unravelled" }, { "Boss Completion: defeatedZapCoreEnemy", "Boss: Voltvyrm" }, { "Boss Completion: spinnerDefeated", "Boss: Widow" }, { "Boss Completion: roofCrabDefeated", "Boss: Craggler" }, { "Boss Completion: skullKingDefeated", "Boss: Skull Tyrant (The Marrow)" }, { "Crafting Kit Source: Forge Tool Kit", "Crafting Kit: Forge Daughter" }, { "Crafting Kit Source: Architect Tool Kit", "Crafting Kit: Twelfth Architect" }, { "Crafting Kit Source: Grindle Tool Kit", "Crafting Kit: Grindle" }, { "Crafting Kit Source: Crow Feathers", "Crafting Kit: Crawbug Clearing (Creige)" }, { "Quest Completion: A Pinsmiths Tools", "Wish: Pinmaster's Oil" }, { "Quest Completion: Belltown House Start", "Wish: Restoration of Bellhart" }, { "Quest Completion: Belltown House Mid", "Wish: Bellhart's Glory" }, { "Quest Completion: Building Materials", "Wish: Bone Bottom Repairs" }, { "Quest Completion: Building Materials (Bridge)", "Wish: A Lifesaving Bridge" }, { "Quest Completion: Building Materials (Statue)", "Wish: An Icon of Hope" }, { "Quest Completion: Courier Delivery Bonebottom", "Wish: Bone Bottom Supplies" }, { "Quest Completion: Courier Delivery Dustpens Slave", "Wish: Queen's Egg" }, { "Quest Completion: Courier Delivery Fixer", "Wish: Survivor's Camp Supplies" }, { "Quest Completion: Courier Delivery Fleatopia", "Wish: Fleatopia Supplies" }, { "Quest Completion: Courier Delivery Mask Maker", "Wish: Liquid Lacquer" }, { "Quest Completion: Courier Delivery Pilgrims Rest", "Wish: Pilgrim's Rest Supplies" }, { "Quest Completion: Courier Delivery Songclave", "Wish: Songclave Supplies" }, { "Quest Completion: Extractor Blue Worms", "Wish: Advanced Alchemy" }, { "Quest Completion: Fine Pins", "Wish: Fine Pins" }, { "Quest Completion: Garmond Black Threaded", "Wish: Hero's Call" }, { "Quest Completion: Great Gourmand", "Wish: Great Taste of Pharloom" }, { "Quest Completion: Journal", "Wish: Bugs of Pharloom" }, { "Quest Completion: Mr Mushroom", "Wish: Passing of the Age" }, { "Quest Completion: Pilgrim Rags", "Wish: Garb of the Pilgrims" }, { "Quest Completion: Rock Rollers", "Wish: Volatile Flintbeetles" }, { "Quest Completion: Save City Merchant", "Wish: The Wandering Merchant" }, { "Quest Completion: Save City Merchant Bridge", "Wish: The Lost Merchant" }, { "Quest Completion: Save Courier Short", "Wish: My Missing Courier" }, { "Quest Completion: Save Courier Tall", "Wish: My Missing Brother" }, { "Quest Completion: Save Sherma", "Wish: Balm for the Wounded" }, { "Quest Completion: Shiny Bell Goomba", "Wish: Silver Bells" }, { "Quest Completion: Skull King", "Wish: The Terrible Tyrant" }, { "Quest Completion: Song Pilgrim Cloaks", "Wish: Cloaks of the Choir" }, { "Quest Completion: Songclave Donation 1", "Wish: Building Up Songclave" }, { "Quest Completion: Songclave Donation 2", "Wish: Strengthening Songclave" }, { "Quest Completion: Steel Sentinel Pt2", "Wish: A Vassal Lost" }, { "Tool Unlock: Mosscreep Tool 1", "Druid's Eye" }, { "Tool Unlock: Mosscreep Tool 2", "Druid's Eyes" }, { "Tool Unlock: Dazzle Bind", "Claw Mirror" }, { "Tool Unlock: Dazzle Bind Upgraded", "Dark Mirror" }, { "Tool Unlock: Curve Claws", "Curveclaw" }, { "Tool Unlock: Curve Claws Upgraded", "Curvesickle" } }; for (int i = 0; i < MaskShardLocationNames.Length; i++) { string value = MaskShardLocationNames[i]; dictionary.Add("Mask Shard Unlock #" + (i + 1), value); } for (int j = 0; j < SpoolFragmentLocationNames.Length; j++) { string value2 = SpoolFragmentLocationNames[j]; dictionary.Add("Spool Fragment Unlock #" + (j + 1), value2); } for (int k = 0; k < SilkHeartLocationNames.Length; k++) { string value3 = SilkHeartLocationNames[k]; dictionary.Add("Silk Heart Unlock #" + (k + 1), value3); } return dictionary; } internal static string GetCanonicalLocationName(string locationName) { if (string.IsNullOrWhiteSpace(locationName)) { return locationName; } string text = locationName; HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); while (hashSet.Add(text)) { if (CanonicalLocationItemNameCollisions.TryGetValue(text, out var value)) { return value; } string value2; string value3; string text2 = (ExplicitLocationRenames.TryGetValue(text, out value2) ? value2 : ((SourceLocationNameAliases == null || !SourceLocationNameAliases.TryGetValue(text, out value3)) ? (ConvertPairedLocationName(text) ?? ItemSet.GetCanonicalItemName(text)) : value3)); if (CanonicalLocationItemNameCollisions.TryGetValue(text2, out value)) { return value; } string canonicalItemName = ItemSet.GetCanonicalItemName(text2); if (string.Equals(text, canonicalItemName, StringComparison.OrdinalIgnoreCase)) { return canonicalItemName; } text = canonicalItemName; } return text; } internal static string[] GetRoomLocationNameCandidates(string locationName) { return new string[1] { GetCanonicalLocationName(locationName) }; } private static string ConvertPairedLocationName(string locationName) { Tuple[] array = new Tuple[5] { Tuple.Create("Skill Unlock: ", "Skill: "), Tuple.Create("Tool Unlock: ", "Tool: "), Tuple.Create("Spell Unlock: ", "Spell: "), Tuple.Create("Crest Unlock: ", "Crest: "), Tuple.Create("Save Flea: ", "Flea: ") }; foreach (Tuple tuple in array) { if (locationName.StartsWith(tuple.Item1, StringComparison.OrdinalIgnoreCase)) { return ItemSet.GetCanonicalItemName(tuple.Item2 + locationName.Substring(tuple.Item1.Length)); } } int num = locationName.IndexOf(" Slot Unlock: ", StringComparison.OrdinalIgnoreCase); if (num > 0) { return ItemSet.GetCanonicalItemName(locationName.Substring(0, num) + " Slot: " + locationName.Substring(num + " Slot Unlock: ".Length)); } array = new Tuple[5] { Tuple.Create("Mask Shard Unlock #", "Mask Shard #"), Tuple.Create("Spool Fragment Unlock #", "Spool Fragment #"), Tuple.Create("Silk Heart Unlock #", "Silk Heart #"), Tuple.Create("Bellway Unlock: ", "Bellway: "), Tuple.Create("Ventrica Unlock: ", "Ventrica: ") }; foreach (Tuple tuple2 in array) { if (locationName.StartsWith(tuple2.Item1, StringComparison.OrdinalIgnoreCase)) { return tuple2.Item2 + locationName.Substring(tuple2.Item1.Length); } } if (locationName.StartsWith("Pin Purchase: ", StringComparison.OrdinalIgnoreCase)) { string text = locationName.Substring("Pin Purchase: ".Length); if (text.EndsWith(" Pins", StringComparison.OrdinalIgnoreCase)) { return locationName; } return "Pin Purchase: " + ItemSet.GetCanonicalItemName("Pin: " + text); } if (locationName.StartsWith("Relic Pickup: ", StringComparison.OrdinalIgnoreCase)) { return ItemSet.GetCanonicalItemName("Relic: " + locationName.Substring("Relic Pickup: ".Length)); } return null; } private static Dictionary BuildSourceLocationNameAliases() { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); Location[] locations = new LocationSet().Locations; foreach (Location location in locations) { if (!string.Equals(location.SourceName, location.Name, StringComparison.Ordinal)) { dictionary[location.SourceName] = location.Name; } } return dictionary; } } internal enum LogicAuditCloakStage { Cloakless, Normal, Drifters, Faydown } internal static class LogicAuditCloakManager { private const string CloaklessCrestName = "Cloakless"; private static bool overrideActive; private static LogicAuditCloakStage currentStage; private static bool hasCrestSnapshot; private static string snapshotCurrentCrest = string.Empty; private static string snapshotPreviousCrest = string.Empty; private static bool snapshotWasTemporary; private static bool resumeCloaklessWhenSafe; internal static bool IsOverrideActive { get { if (overrideActive && SaveState.Instance != null) { return SaveState.Instance.logicAuditMode; } return false; } } internal static LogicAuditCloakStage GetNextStage(LogicAuditCloakStage stage) { return stage switch { LogicAuditCloakStage.Cloakless => LogicAuditCloakStage.Normal, LogicAuditCloakStage.Normal => LogicAuditCloakStage.Drifters, LogicAuditCloakStage.Drifters => LogicAuditCloakStage.Faydown, _ => LogicAuditCloakStage.Cloakless, }; } internal static bool AllowsBrolly(LogicAuditCloakStage stage) { if (stage != LogicAuditCloakStage.Drifters) { return stage == LogicAuditCloakStage.Faydown; } return true; } internal static bool AllowsDoubleJump(LogicAuditCloakStage stage) { return stage == LogicAuditCloakStage.Faydown; } internal static bool ResolveBrollyOwnership(bool owned) { if (!IsOverrideActive || resumeCloaklessWhenSafe) { return owned; } return AllowsBrolly(currentStage); } internal static bool ResolveDoubleJumpOwnership(bool owned) { if (!IsOverrideActive || resumeCloaklessWhenSafe) { return owned; } return AllowsDoubleJump(currentStage); } internal static string GetDisplayName() { return (IsOverrideActive ? currentStage : InferOwnedStage()) switch { LogicAuditCloakStage.Cloakless => "Cloakless", LogicAuditCloakStage.Normal => "Normal", LogicAuditCloakStage.Drifters => "Drifter's", _ => "Faydown", }; } internal static string GetOverlayText() { return "F8 Cloak: " + GetDisplayName(); } internal static bool CanCycle(bool logicAuditMode, bool connectionGuiOpen, bool gameplayReady) { return logicAuditMode && !connectionGuiOpen && gameplayReady; } internal static bool TryCycle() { SaveState instance = SaveState.Instance; if (instance == null || !instance.logicAuditMode) { return false; } UpdateIntegrity(); if (!CanUseHotkey(out var reason)) { Warn("F8 cloak cycle is unavailable: " + reason); return false; } LogicAuditCloakStage logicAuditCloakStage = (IsOverrideActive ? currentStage : InferOwnedStage()); LogicAuditCloakStage nextStage = GetNextStage(logicAuditCloakStage); if (nextStage == LogicAuditCloakStage.Cloakless) { if (!TryEnterCloakless()) { return false; } } else { if (logicAuditCloakStage == LogicAuditCloakStage.Cloakless && hasCrestSnapshot && !TryRestoreCrest(preserveSnapshot: false)) { Warn("F8 cloak cycle is waiting for the previous crest to become available."); return false; } overrideActive = true; currentStage = nextStage; resumeCloaklessWhenSafe = false; } ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogMessage((object)("[RANDOMIZER] Logic Audit cloak: " + GetDisplayName())); } return true; } internal static void UpdateIntegrity() { if (!overrideActive) { return; } SaveState instance = SaveState.Instance; PlayerData instance2 = PlayerData.instance; if (instance == null || !instance.logicAuditMode || instance2 == null) { Reset(); return; } bool flag = currentStage == LogicAuditCloakStage.Cloakless && hasCrestSnapshot; if (TrapManager.IsCursedCrestActive || instance2.IsAnyCursed || (instance2.IsCurrentCrestTemp && !flag)) { ClearTransientFields(); return; } if (resumeCloaklessWhenSafe) { if (CanUseHotkey(out var _) && TryResumeCloakless()) { resumeCloaklessWhenSafe = false; } return; } bool flag2 = string.Equals(instance2.CurrentCrestID, "Cloakless", StringComparison.Ordinal); if (currentStage == LogicAuditCloakStage.Cloakless) { if (!hasCrestSnapshot || !flag2) { ClearTransientFields(); } } else if (flag2) { ClearTransientFields(); } } internal static void PrepareForSave() { try { if (!overrideActive || currentStage != LogicAuditCloakStage.Cloakless || !hasCrestSnapshot) { return; } if (TryRestoreCrest(preserveSnapshot: true)) { if (overrideActive && hasCrestSnapshot) { resumeCloaklessWhenSafe = true; } } else { ForceRestoreSnapshotFields(); ClearTransientFields(); } } catch (Exception ex) { Warn("F8 cloak save preparation failed: " + ex.Message); ForceRestoreSnapshotFields(); ClearTransientFields(); } } internal static void ResumeAfterSave() { UpdateIntegrity(); } internal static void Reset() { try { if (hasCrestSnapshot && !TryRestoreCrest(preserveSnapshot: false)) { ForceRestoreSnapshotFields(); } } catch (Exception ex) { Warn("F8 cloak cleanup failed: " + ex.Message); ForceRestoreSnapshotFields(); } finally { ClearTransientFields(); } } private static LogicAuditCloakStage InferOwnedStage() { PlayerData instance = PlayerData.instance; bool isCloakless = instance != null && string.Equals(instance.CurrentCrestID, "Cloakless", StringComparison.Ordinal); SaveState instance2 = SaveState.Instance; int num; int num2; if (instance2 != null) { num = (instance2.IsRandomized(ItemType.Skill) ? 1 : 0); if (num != 0) { num2 = (instance2.canDoubleJump ? 1 : 0); goto IL_0047; } } else { num = 0; } num2 = ((instance?.hasDoubleJump ?? false) ? 1 : 0); goto IL_0047; IL_0047: bool hasDoubleJump = (byte)num2 != 0; bool hasBrolly = ((num != 0) ? instance2.canBrolly : (instance?.hasBrolly ?? false)); return InferOwnedStage(isCloakless, hasBrolly, hasDoubleJump); } internal static LogicAuditCloakStage InferOwnedStage(bool isCloakless, bool hasBrolly, bool hasDoubleJump) { if (isCloakless) { return LogicAuditCloakStage.Cloakless; } if (hasDoubleJump) { return LogicAuditCloakStage.Faydown; } if (!hasBrolly) { return LogicAuditCloakStage.Normal; } return LogicAuditCloakStage.Drifters; } private static bool CanUseHotkey(out string reason) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Invalid comparison between Unknown and I4 //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Invalid comparison between Unknown and I4 GameManager silentInstance = GameManager.SilentInstance; HeroController instance = HeroController.instance; PlayerData instance2 = PlayerData.instance; if ((Object)(object)silentInstance == (Object)null || (Object)(object)instance == (Object)null || instance2 == null) { reason = "load into gameplay first"; return false; } bool gameplayReady = (int)silentInstance.GameState == 4 && silentInstance.IsGameplayScene() && !silentInstance.isPaused && !silentInstance.IsLoadingSceneTransition && !silentInstance.IsInSceneTransition && !TransitionPoint.IsTransitionBlocked && !BossSceneController.IsTransitioning && !instance2.HasStoredMemoryState && instance.cState != null && !instance.cState.transitioning && !instance.cState.dead && !instance.cState.hazardDeath && !instance.cState.hazardRespawning && !instance.controlReqlinquished && (int)instance.hero_state != 7 && instance.CanInput(); if (!CanCycle(SaveState.Instance != null && SaveState.Instance.logicAuditMode, connectionGuiOpen: false, gameplayReady)) { reason = "finish the current menu, transition, or cutscene"; return false; } if (TrapManager.IsCursedCrestActive || instance2.IsAnyCursed) { reason = "a cursed crest currently owns the crest state"; return false; } if (instance2.IsCurrentCrestTemp && (!hasCrestSnapshot || currentStage != LogicAuditCloakStage.Cloakless)) { reason = "a native temporary crest sequence is active"; return false; } if (!overrideActive && string.Equals(instance2.CurrentCrestID, "Cloakless", StringComparison.Ordinal)) { reason = "a native Cloakless story sequence is active"; return false; } reason = string.Empty; return true; } private static bool TryEnterCloakless() { PlayerData instance = PlayerData.instance; if (instance == null || instance.IsCurrentCrestTemp || instance.IsAnyCursed || TrapManager.IsCursedCrestActive) { return false; } try { ToolCrest crestByName = ToolItemManager.GetCrestByName(instance.CurrentCrestID); ToolCrest crestByName2 = ToolItemManager.GetCrestByName("Cloakless"); if ((Object)(object)crestByName == (Object)null || (Object)(object)crestByName2 == (Object)null) { Warn("F8 cloak cycle could not resolve native crest assets."); return false; } snapshotCurrentCrest = instance.CurrentCrestID; snapshotPreviousCrest = instance.PreviousCrestID ?? string.Empty; snapshotWasTemporary = instance.IsCurrentCrestTemp; hasCrestSnapshot = true; if (!ToolPatches.SetRandomizerCrest(crestByName2, markTemporary: true) || !string.Equals(instance.CurrentCrestID, "Cloakless", StringComparison.Ordinal)) { ForceRestoreSnapshotFields(); ClearTransientFields(); Warn("F8 cloak cycle could not enter the native Cloakless state."); return false; } overrideActive = true; currentStage = LogicAuditCloakStage.Cloakless; resumeCloaklessWhenSafe = false; return true; } catch (Exception ex) { Warn("F8 cloak cycle could not enter Cloakless: " + ex.Message); ForceRestoreSnapshotFields(); ClearTransientFields(); return false; } } private static bool TryResumeCloakless() { PlayerData instance = PlayerData.instance; if (!hasCrestSnapshot || instance == null) { ClearTransientFields(); return false; } if (instance.IsAnyCursed || TrapManager.IsCursedCrestActive) { return false; } if (string.Equals(instance.CurrentCrestID, "Cloakless", StringComparison.Ordinal)) { return true; } if (!string.Equals(instance.CurrentCrestID, snapshotCurrentCrest, StringComparison.Ordinal) || instance.IsCurrentCrestTemp) { ClearTransientFields(); return false; } ToolCrest crestByName = ToolItemManager.GetCrestByName("Cloakless"); if ((Object)(object)crestByName == (Object)null) { return false; } try { if (!ToolPatches.SetRandomizerCrest(crestByName, markTemporary: true) || !string.Equals(instance.CurrentCrestID, "Cloakless", StringComparison.Ordinal)) { ForceRestoreSnapshotFields(); return false; } return true; } catch (Exception ex) { Warn("F8 cloak resume failed: " + ex.Message); ForceRestoreSnapshotFields(); return false; } } private static bool TryRestoreCrest(bool preserveSnapshot) { if (!hasCrestSnapshot) { return true; } try { PlayerData instance = PlayerData.instance; if (instance == null) { return false; } if (!string.Equals(instance.CurrentCrestID, "Cloakless", StringComparison.Ordinal) && !string.Equals(instance.CurrentCrestID, snapshotCurrentCrest, StringComparison.Ordinal)) { ClearTransientFields(); return false; } ToolCrest crestByName = ToolItemManager.GetCrestByName(snapshotCurrentCrest); if ((Object)(object)crestByName == (Object)null || !ToolPatches.SetRandomizerCrest(crestByName, snapshotWasTemporary)) { return false; } instance.PreviousCrestID = snapshotPreviousCrest; instance.IsCurrentCrestTemp = snapshotWasTemporary; ToolItemManager.RefreshEquippedState(); if (!preserveSnapshot) { ClearCrestSnapshot(); } return true; } catch (Exception ex) { Warn("F8 cloak restoration failed: " + ex.Message); return false; } } private static bool ForceRestoreSnapshotFields() { PlayerData instance = PlayerData.instance; if (!hasCrestSnapshot || instance == null) { return false; } try { if (!string.Equals(instance.CurrentCrestID, snapshotCurrentCrest, StringComparison.Ordinal)) { ToolPatches.PrepareHeroForCrestChange(); } instance.CurrentCrestID = snapshotCurrentCrest; instance.PreviousCrestID = snapshotPreviousCrest; instance.IsCurrentCrestTemp = snapshotWasTemporary; ToolItemManager.RefreshEquippedState(); ToolItemManager.SendEquippedChangedEvent(true); ToolPatches.ResetHeroInputAfterCrestChange(); return string.Equals(instance.CurrentCrestID, snapshotCurrentCrest, StringComparison.Ordinal); } catch (Exception ex) { Warn("F8 cloak save repair restored fields but not visuals: " + ex.Message); return string.Equals(instance.CurrentCrestID, snapshotCurrentCrest, StringComparison.Ordinal); } } private static void ClearTransientFields() { overrideActive = false; currentStage = LogicAuditCloakStage.Normal; resumeCloaklessWhenSafe = false; ClearCrestSnapshot(); } private static void ClearCrestSnapshot() { hasCrestSnapshot = false; snapshotCurrentCrest = string.Empty; snapshotPreviousCrest = string.Empty; snapshotWasTemporary = false; } private static void Warn(string message) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] " + message)); } } } internal enum MapCheckReachability { Unknown, Reachable, Unreachable } internal static class MapLogicEvaluator { private sealed class LogicPayload { [JsonProperty("easy_skips")] internal bool EasySkips { get; set; } [JsonProperty("requirements")] internal Dictionary Requirements { get; set; } [JsonProperty("abstract_requirements")] internal Dictionary AbstractRequirements { get; set; } [JsonProperty("logic_item_dependencies")] internal Dictionary> LogicItemDependencies { get; set; } } private sealed class LogicRequirement { [JsonProperty("all_of")] internal List AllOf { get; set; } [JsonProperty("any_of")] internal List AnyOf { get; set; } [JsonProperty("require_any_crest")] internal bool RequireAnyCrest { get; set; } [JsonProperty("require_silk_spear")] internal bool RequireSilkSpear { get; set; } [JsonProperty("requires_easy_skips")] internal bool RequiresEasySkips { get; set; } [JsonProperty("path")] internal string Path { get; set; } [JsonProperty("item_counts")] internal List ItemCounts { get; set; } [JsonProperty("alternatives")] internal List Alternatives { get; set; } [JsonProperty("logic_unknown")] internal bool LogicUnknown { get; set; } } private sealed class LogicItemCount { [JsonProperty("items")] internal List Items { get; set; } [JsonProperty("minimum")] internal int Minimum { get; set; } } private sealed class ParsedPayload { internal readonly Dictionary Requirements; internal readonly Dictionary AbstractRequirements; internal readonly Dictionary> Dependencies; internal readonly bool EasySkips; internal ParsedPayload(LogicPayload payload) { Requirements = CanonicalizeLocationKeys(payload?.Requirements); AbstractRequirements = payload?.AbstractRequirements ?? new Dictionary(StringComparer.Ordinal); Dependencies = CanonicalizeDependencyKeys(payload?.LogicItemDependencies); EasySkips = payload?.EasySkips ?? false; } } private const int MaxMossberryCount = 7; private const int MaxPollipHeartCount = 6; private const int MaxPaleOilCount = 3; private static readonly string[] CrestItemNames = new string[7] { "Crest: Hunter", "Crest: Wanderer", "Crest: Reaper", "Crest: Beast", "Crest: Architect", "Crest: Witch", "Crest: Shaman" }; private static readonly string[] NonArchitectCrestItemNames = CrestItemNames.Where((string name) => !string.Equals(name, "Crest: Architect", StringComparison.OrdinalIgnoreCase)).ToArray(); private const string SwiftStepItemName = "Swift Step"; private static string cachedJson = string.Empty; private static ParsedPayload cachedPayload; private static bool loggedPayloadFailure; internal static MapCheckReachability Evaluate(SaveState state, string locationName) { IReadOnlyDictionary readOnlyDictionary = EvaluateAll(state, new string[1] { locationName }); string canonicalLocationName = LocationSet.GetCanonicalLocationName(locationName); if (string.IsNullOrWhiteSpace(canonicalLocationName) || !readOnlyDictionary.TryGetValue(canonicalLocationName, out var value)) { return MapCheckReachability.Unknown; } return value; } internal static IReadOnlyDictionary EvaluateAll(SaveState state, IEnumerable locationNames) { string[] array = (from name in (locationNames ?? Enumerable.Empty()).Select(LocationSet.GetCanonicalLocationName) where !string.IsNullOrWhiteSpace(name) select name).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); Dictionary dictionary = array.ToDictionary((string name) => name, (string _) => MapCheckReachability.Unknown, StringComparer.OrdinalIgnoreCase); ParsedPayload payload = GetPayload(state); if (payload == null) { return dictionary; } Dictionary inventory = BuildInventoryCounts(state); Dictionary abstractValues = ResolveAbstractRequirements(payload, inventory); bool hasCrest = CrestItemNames.Any((string itemName) => CountItem(inventory, itemName) > 0); bool hasSilkSpear = CountItem(inventory, "Silkspear") > 0 && NonArchitectCrestItemNames.Any((string itemName) => CountItem(inventory, itemName) > 0); string[] array2 = array; foreach (string key in array2) { if (payload.Requirements.TryGetValue(key, out var value)) { dictionary[key] = (SatisfiesGroup(value, abstractValues, inventory, hasCrest, hasSilkSpear, payload.Dependencies, payload.EasySkips) ? MapCheckReachability.Reachable : MapCheckReachability.Unreachable); } } return dictionary; } internal static bool TryGetLocationPath(SaveState state, string locationName, out string path) { path = string.Empty; ParsedPayload payload = GetPayload(state); string canonicalLocationName = LocationSet.GetCanonicalLocationName(locationName); if (payload == null || string.IsNullOrWhiteSpace(canonicalLocationName) || !payload.Requirements.TryGetValue(canonicalLocationName, out var value)) { return false; } foreach (LogicRequirement alternative in GetAlternatives(value)) { if (!string.IsNullOrWhiteSpace(alternative?.Path)) { path = alternative.Path.Trim(); return true; } } return false; } internal static bool RequiresLogicVerification(SaveState state, string locationName) { ParsedPayload payload = GetPayload(state); string canonicalLocationName = LocationSet.GetCanonicalLocationName(locationName); if (payload != null && !string.IsNullOrWhiteSpace(canonicalLocationName) && payload.Requirements.TryGetValue(canonicalLocationName, out var value)) { return value.LogicUnknown; } return false; } private static ParsedPayload GetPayload(SaveState state) { string text = state?.mapLogicPayloadJson ?? string.Empty; if (string.IsNullOrWhiteSpace(text)) { return null; } if (string.Equals(text, cachedJson, StringComparison.Ordinal)) { return cachedPayload; } cachedJson = text; cachedPayload = null; try { LogicPayload logicPayload = JsonConvert.DeserializeObject(text); if (logicPayload?.Requirements == null || logicPayload.Requirements.Count == 0 || logicPayload.AbstractRequirements == null || logicPayload.AbstractRequirements.Count == 0 || logicPayload.LogicItemDependencies == null) { return null; } cachedPayload = new ParsedPayload(logicPayload); loggedPayloadFailure = false; } catch (Exception ex) { if (!loggedPayloadFailure) { loggedPayloadFailure = true; ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] The room's check-map logic payload could not be read; marker reachability will remain neutral: " + ex.Message)); } } } return cachedPayload; } private static Dictionary CanonicalizeLocationKeys(Dictionary source) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (source == null) { return dictionary; } foreach (KeyValuePair item in source) { string canonicalLocationName = LocationSet.GetCanonicalLocationName(item.Key); if (!string.IsNullOrWhiteSpace(canonicalLocationName)) { dictionary[canonicalLocationName] = item.Value; } } return dictionary; } private static Dictionary> CanonicalizeDependencyKeys(Dictionary> source) { Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); if (source == null) { return dictionary; } foreach (KeyValuePair> item in source) { string canonicalItemName = ItemSet.GetCanonicalItemName(item.Key); dictionary[canonicalItemName] = (from name in (item.Value ?? new List()).Select(ItemSet.GetCanonicalItemName) where !string.IsNullOrWhiteSpace(name) select name).ToList(); } return dictionary; } private static Dictionary BuildInventoryCounts(SaveState state) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); IReadOnlyDictionary readOnlyDictionary = Archipelago.Instance?.GetReceivedItemCounts(); if (readOnlyDictionary != null) { foreach (KeyValuePair item in readOnlyDictionary) { AddCount(dictionary, item.Key, item.Value); } } if (state?.receivedItems != null) { foreach (string receivedItem in state.receivedItems) { SetMinimumCount(dictionary, receivedItem, 1); } } if (state != null) { SetMinimumCount(dictionary, "Progressive Swift Step", state.swiftStepLevel); SetMinimumCount(dictionary, "Progressive Druid's Eyes", state.druidsEyeLevel); SetMinimumCount(dictionary, "Progressive Needle Upgrade", state.needleUpgradeLevel); SetMinimumCount(dictionary, "Progressive Silkheart", state.silkHeartLevel); GameManager unsafeInstance = GameManager.UnsafeInstance; PlayerData val = (((Object)(object)unsafeInstance == (Object)null) ? null : unsafeInstance.playerData); if (val != null) { SetMinimumCount(dictionary, "Progressive Crafting Kit", val.ToolKitUpgrades); SetMinimumCount(dictionary, "Progressive Tool Pouch", val.ToolPouchUpgrades); SetMinimumCount(dictionary, "Mossberry", GetPersistedMossberryCount(val, state)); SetMinimumCount(dictionary, "Pollip Heart", GetPersistedPollipHeartCount(val, state)); SetMinimumCount(dictionary, "Pale Oil", GetPersistedPaleOilCount(val, state)); } AddNativeSkillState(dictionary, state); AddNativeEquipmentState(dictionary, state); AddNativeWorldState(dictionary, state); } return dictionary; } private static int GetPersistedCollectableCount(PlayerData playerData, string assetName, int maximum) { //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_0022: 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) if (playerData?.Collectables == null || maximum <= 0) { return 0; } Data data = ((SerializableNamedList)(object)playerData.Collectables).GetData(assetName); long val = (long)Math.Max(0, data.Amount) + (long)Math.Max(0, data.AmountWhileHidden); return (int)Math.Min(maximum, val); } private static int GetPersistedMossberryCount(PlayerData playerData, SaveState state) { int persistedCollectableCount = GetPersistedCollectableCount(playerData, "Mossberry", 7); persistedCollectableCount += Math.Max(0, Math.Min(3, playerData.druidMossBerriesSold)); if (state.IsLocationChecked("Tool Unlock: Mosscreep Tool 1")) { persistedCollectableCount += 3; } if (state.IsLocationChecked("Tool Unlock: Mosscreep Tool 2")) { persistedCollectableCount++; } return Math.Min(7, persistedCollectableCount); } private static int GetPersistedPaleOilCount(PlayerData playerData, SaveState state) { int persistedCollectableCount = GetPersistedCollectableCount(playerData, "Pale_Oil", 3); int num = 0; if (state.IsLocationChecked("Pinmaster Plinney: Sharpened Needle") && state.IsLocationChecked("Pinmaster Plinney: Shining Needle")) { num = 1; if (state.IsLocationChecked("Pinmaster Plinney: Hivesteel Needle")) { num = 2; if (state.IsLocationChecked("Pinmaster Plinney: Pale Steel Needle")) { num = 3; } } } return Math.Min(3, persistedCollectableCount + num); } private static int GetPersistedPollipHeartCount(PlayerData playerData, SaveState state) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) int num = GetPersistedCollectableCount(playerData, "Shell Flower", 6); bool flag = false; if (playerData?.QuestCompletionData != null) { Completion data = ((SerializableNamedList)(object)playerData.QuestCompletionData).GetData("Shell Flowers"); flag = data.IsCompleted || data.WasEverCompleted; } if (flag || (state != null && state.IsLocationChecked("Tool Unlock: Poison Pouch"))) { num += 6; } return Math.Min(6, num); } private static void AddNativeSkillState(Dictionary counts, SaveState state) { if (!state.IsRandomized(ItemType.Skill)) { PlayerData instance = PlayerData.instance; if (state.canDoubleJump || (instance != null && instance.hasDoubleJump)) { SetMinimumCount(counts, "Faydown Cloak", 1); } if (state.canChargeSlash || (instance != null && instance.hasChargeSlash)) { SetMinimumCount(counts, "Needle Strike", 1); } if (state.canSilkSoar || (instance != null && instance.hasSuperJump)) { SetMinimumCount(counts, "Silk Soar", 1); } if (state.canWallJump || (instance != null && instance.hasWalljump)) { SetMinimumCount(counts, "Cling Grip", 1); } if (state.canBrolly || (instance != null && instance.hasBrolly)) { SetMinimumCount(counts, "Drifter's Cloak", 1); } if (state.splitDashAndSprint) { SetMinimumCount(counts, "Progressive Swift Step", state.swiftStepLevel); } else if (state.canDash || (instance != null && instance.hasDash)) { SetMinimumCount(counts, "Swift Step", 1); } if (state.canUseHarpoon || (instance != null && instance.hasHarpoonDash)) { SetMinimumCount(counts, "Clawline", 1); } if (state.canUseNeedolin || (instance != null && instance.hasNeedolin)) { SetMinimumCount(counts, "Needolin", 1); } if (state.canUseQuill || (instance != null && instance.hasQuill)) { SetMinimumCount(counts, "Quill", 1); } } } private static void AddNativeEquipmentState(Dictionary counts, SaveState state) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Invalid comparison between Unknown and I4 try { ToolItem[] array = Resources.FindObjectsOfTypeAll(); foreach (ToolItem val in array) { if (!((Object)(object)val == (Object)null) && val.IsUnlocked) { ItemType itemType = (((int)val.Type == 3) ? ItemType.Spell : ItemType.Tool); if (!state.IsRandomized(itemType)) { SetMinimumCount(counts, ((itemType == ItemType.Spell) ? "Spell: " : "Tool: ") + val.name, 1); } } } if (state.IsRandomized(ItemType.Crest)) { return; } foreach (ToolCrest allCrest in ToolItemManager.GetAllCrests()) { if ((Object)(object)allCrest != (Object)null && allCrest.IsBaseVersion && allCrest.IsUnlocked) { SetMinimumCount(counts, CrestNames.GetItemNameFromInternal(allCrest.name), 1); } } } catch (Exception ex) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogDebug((object)("[RANDOMIZER] Native equipment was not ready for check-map logic: " + ex.Message)); } } } private static void AddNativeWorldState(Dictionary counts, SaveState state) { if (state.IsRandomized(ItemType.SilkHeart) && state.IsRandomized(ItemType.Map) && state.IsRandomized(ItemType.Flea) && state.IsRandomized(ItemType.Bellway) && state.IsRandomized(ItemType.Ventrica)) { return; } PlayerData instance = PlayerData.instance; if (instance != null) { if (!state.IsRandomized(ItemType.SilkHeart)) { int count = Math.Max(0, Math.Min(3, instance.silkRegenMax)); SetMinimumCount(counts, "Progressive Silkheart", count); } if (!state.IsRandomized(ItemType.Map)) { AddNativeMaps(counts, instance); } if (!state.IsRandomized(ItemType.Flea)) { AddNativeFleas(counts, instance); } if (!state.IsRandomized(ItemType.Bellway)) { AddNativeBellways(counts, instance); } if (!state.IsRandomized(ItemType.Ventrica)) { AddNativeVentrica(counts, instance); } } } private static void AddNativeMaps(Dictionary counts, PlayerData playerData) { SetIfTrue(counts, "Map: Mosslands", playerData.HasMossGrottoMap); SetIfTrue(counts, "Map: The Marrow", playerData.HasBoneforestMap); SetIfTrue(counts, "Map: Deep Docks", playerData.HasDocksMap); SetIfTrue(counts, "Map: Far Fields", playerData.HasWildsMap); SetIfTrue(counts, "Map: Wormways", playerData.HasCrawlMap); SetIfTrue(counts, "Map: Hunter's March", playerData.HasHuntersNestMap); SetIfTrue(counts, "Map: Greymoor", playerData.HasGreymoorMap); SetIfTrue(counts, "Map: Bellhart", playerData.HasBellhartMap); SetIfTrue(counts, "Map: Shellwood", playerData.HasShellwoodMap); SetIfTrue(counts, "Map: Blasted Steps", playerData.HasJudgeStepsMap); SetIfTrue(counts, "Map: Sinner's Road", playerData.HasDustpensMap); SetIfTrue(counts, "Map: Mount Fay", playerData.HasPeakMap); SetIfTrue(counts, "Map: Sands of Karak", playerData.HasCoralMap); SetIfTrue(counts, "Map: Bilewater", playerData.HasSwampMap); } private static void AddNativeFleas(Dictionary counts, PlayerData playerData) { SetIfTrue(counts, "Flea: The Marrow", playerData.SavedFlea_Bone_06); SetIfTrue(counts, "Flea: Deep Docks - Bellway", playerData.SavedFlea_Dock_16); SetIfTrue(counts, "Flea: Deep Docks - Weaver Burial Spire", playerData.SavedFlea_Bone_East_05); SetIfTrue(counts, "Flea: Far Fields - Captured", playerData.SavedFlea_Bone_East_17b); SetIfTrue(counts, "Flea: Hunter's March", playerData.SavedFlea_Ant_03); SetIfTrue(counts, "Flea: Greymoor - Craw Lake", playerData.SavedFlea_Greymoor_15b); SetIfTrue(counts, "Flea: Greymoor - Tower", playerData.SavedFlea_Greymoor_06); SetIfTrue(counts, "Flea: Shellwood", playerData.SavedFlea_Shellwood_03); SetIfTrue(counts, "Flea: Pilgrim's Rest", playerData.SavedFlea_Bone_East_10_Church); SetIfTrue(counts, "Flea: Blasted Steps", playerData.SavedFlea_Coral_35); SetIfTrue(counts, "Flea: Sinner's Road", playerData.SavedFlea_Dust_12); SetIfTrue(counts, "Flea: Exhaust Organ", playerData.SavedFlea_Dust_09); SetIfTrue(counts, "Flea: Bellhart", playerData.SavedFlea_Belltown_04); SetIfTrue(counts, "Flea: Wormways", playerData.SavedFlea_Crawl_06); SetIfTrue(counts, "Flea: The Slab - Cell", playerData.SavedFlea_Slab_Cell); SetIfTrue(counts, "Flea: Bilewater - Thieves", playerData.SavedFlea_Shadow_28); SetIfTrue(counts, "Flea: Deep Docks - Mines", playerData.SavedFlea_Dock_03d); SetIfTrue(counts, "Flea: Underworks - Wisp Thicket Passage", playerData.SavedFlea_Under_23); SetIfTrue(counts, "Flea: Bilehaven", playerData.SavedFlea_Shadow_10); SetIfTrue(counts, "Flea: Choral Chambers - Spa", playerData.SavedFlea_Song_14); SetIfTrue(counts, "Flea: Sands of Karak", playerData.SavedFlea_Coral_24); SetIfTrue(counts, "Flea: Mount Fay", playerData.SavedFlea_Peak_05c); SetIfTrue(counts, "Flea: Songclave", playerData.SavedFlea_Library_09); SetIfTrue(counts, "Flea: Choral Chambers - Walled Room", playerData.SavedFlea_Song_11); SetIfTrue(counts, "Flea: Whispering Vaults", playerData.SavedFlea_Library_01); SetIfTrue(counts, "Flea: Underworks", playerData.SavedFlea_Under_21); SetIfTrue(counts, "Flea: The Slab - Bellway", playerData.SavedFlea_Slab_06); SetIfTrue(counts, "Flea: Greymoor - Kratt", playerData.CaravanLechSaved); SetIfTrue(counts, "Flea: Putrified Ducts - Vog", playerData.MetTroupeHunterWild); SetIfTrue(counts, "Flea: Memorium - Huge Flea", playerData.tamedGiantFlea); } private static void AddNativeBellways(Dictionary counts, PlayerData playerData) { SetIfTrue(counts, "Bellway: Deep Docks", playerData.UnlockedDocksStation); SetIfTrue(counts, "Bellway: Far Fields", playerData.UnlockedBoneforestEastStation); SetIfTrue(counts, "Bellway: Greymoor", playerData.UnlockedGreymoorStation); SetIfTrue(counts, "Bellway: Bellhart", playerData.UnlockedBelltownStation); SetIfTrue(counts, "Bellway: Blasted Steps", playerData.UnlockedCoralTowerStation); SetIfTrue(counts, "Bellway: Grand Bellway", playerData.UnlockedCityStation); SetIfTrue(counts, "Bellway: The Slab", playerData.UnlockedPeakStation); SetIfTrue(counts, "Bellway: Shellwood", playerData.UnlockedShellwoodStation); SetIfTrue(counts, "Bellway: Bilewater", playerData.UnlockedShadowStation); SetIfTrue(counts, "Bellway: Putrified Ducts", playerData.UnlockedAqueductStation); } private static void AddNativeVentrica(Dictionary counts, PlayerData playerData) { SetIfTrue(counts, "Ventrica: Choral Chambers", playerData.UnlockedSongTube); SetIfTrue(counts, "Ventrica: Underworks", playerData.UnlockedUnderTube); SetIfTrue(counts, "Ventrica: Grand Bellway", playerData.UnlockedCityBellwayTube); SetIfTrue(counts, "Ventrica: High Halls", playerData.UnlockedHangTube); SetIfTrue(counts, "Ventrica: Songclave", playerData.UnlockedEnclaveTube); SetIfTrue(counts, "Ventrica: Memorium", playerData.UnlockedArboriumTube); } private static Dictionary ResolveAbstractRequirements(ParsedPayload payload, Dictionary inventory) { Dictionary dictionary = payload.AbstractRequirements.Keys.ToDictionary((string name) => name, (string _) => false, StringComparer.Ordinal); bool hasCrest = CrestItemNames.Any((string itemName) => CountItem(inventory, itemName) > 0); bool hasSilkSpear = CountItem(inventory, "Silkspear") > 0 && NonArchitectCrestItemNames.Any((string itemName) => CountItem(inventory, itemName) > 0); bool flag = true; while (flag) { flag = false; foreach (KeyValuePair abstractRequirement in payload.AbstractRequirements) { if (!dictionary[abstractRequirement.Key] && SatisfiesGroup(abstractRequirement.Value, dictionary, inventory, hasCrest, hasSilkSpear, payload.Dependencies, payload.EasySkips)) { dictionary[abstractRequirement.Key] = true; flag = true; } } } return dictionary; } private static bool SatisfiesGroup(LogicRequirement group, IReadOnlyDictionary abstractValues, Dictionary inventory, bool hasCrest, bool hasSilkSpear, IReadOnlyDictionary> dependencies, bool easySkipsEnabled) { return GetAlternatives(group).Any((LogicRequirement alternative) => SatisfiesRequirement(alternative, abstractValues, inventory, hasCrest, hasSilkSpear, dependencies, easySkipsEnabled)); } private static IEnumerable GetAlternatives(LogicRequirement group) { if (group?.Alternatives != null) { return group.Alternatives.Where((LogicRequirement alternative) => alternative != null); } if (group != null) { return new LogicRequirement[1] { group }; } return Enumerable.Empty(); } private static bool SatisfiesRequirement(LogicRequirement requirement, IReadOnlyDictionary abstractValues, Dictionary inventory, bool hasCrest, bool hasSilkSpear, IReadOnlyDictionary> dependencies, bool easySkipsEnabled) { if (requirement == null || (requirement.RequiresEasySkips && !easySkipsEnabled) || (requirement.RequireAnyCrest && !hasCrest) || (requirement.RequireSilkSpear && !hasSilkSpear)) { return false; } if ((requirement.AllOf ?? new List()).Any((string name) => !HasNamedRequirement(name, abstractValues, inventory, dependencies))) { return false; } if (requirement.AnyOf != null && requirement.AnyOf.Count > 0 && !requirement.AnyOf.Any((string name) => HasNamedRequirement(name, abstractValues, inventory, dependencies))) { return false; } if ((requirement.ItemCounts ?? new List()).Any((LogicItemCount itemCount) => (itemCount?.Items ?? new List()).Sum((string name) => CountItem(inventory, name)) < Math.Max(0, itemCount?.Minimum ?? 0))) { return false; } return true; } private static bool HasNamedRequirement(string name, IReadOnlyDictionary abstractValues, Dictionary inventory, IReadOnlyDictionary> dependencies) { if (string.IsNullOrWhiteSpace(name)) { return false; } if (abstractValues.TryGetValue(name, out var value)) { return value; } string canonicalItemName = ItemSet.GetCanonicalItemName(name); List value2; bool flag = string.Equals(canonicalItemName, "Swift Step", StringComparison.OrdinalIgnoreCase) && dependencies.TryGetValue(canonicalItemName, out value2) && value2.Count > 0; if (CountItem(inventory, canonicalItemName) <= 0 && !flag) { return false; } if (!dependencies.TryGetValue(canonicalItemName, out var value3)) { return true; } return value3.Where((string dependency) => !string.IsNullOrWhiteSpace(dependency)).GroupBy(ItemSet.GetCanonicalItemName, StringComparer.OrdinalIgnoreCase).All((IGrouping group) => CountItem(inventory, group.Key) >= group.Count()); } private static int CountItem(IReadOnlyDictionary counts, string itemName) { if (counts == null || string.IsNullOrWhiteSpace(itemName)) { return 0; } string canonicalItemName = ItemSet.GetCanonicalItemName(itemName); if (!counts.TryGetValue(canonicalItemName, out var value)) { return 0; } return value; } private static void AddCount(Dictionary counts, string itemName, int count) { if (counts != null && !string.IsNullOrWhiteSpace(itemName) && count > 0) { string canonicalItemName = ItemSet.GetCanonicalItemName(itemName); counts.TryGetValue(canonicalItemName, out var value); counts[canonicalItemName] = value + count; } } private static void SetMinimumCount(Dictionary counts, string itemName, int count) { if (counts != null && !string.IsNullOrWhiteSpace(itemName) && count > 0) { string canonicalItemName = ItemSet.GetCanonicalItemName(itemName); counts.TryGetValue(canonicalItemName, out var value); counts[canonicalItemName] = Math.Max(value, count); } } private static void SetIfTrue(Dictionary counts, string itemName, bool isAvailable) { if (isAvailable) { SetMinimumCount(counts, itemName, 1); } } } internal static class MaskAndSpoolLocationManifest { internal sealed class Source { internal readonly string SourceLocationName; internal readonly ItemType Type; internal readonly string SceneName; internal readonly string PersistentBoolId; internal readonly string PlayerDataBool; internal readonly string QuestAssetName; internal readonly string PlayerDataInt; internal readonly int MinimumIntValue; internal string LocationName => LocationSet.GetCanonicalLocationName(SourceLocationName); internal bool IsPhysicalSceneSource { get { if (!string.IsNullOrEmpty(SceneName)) { return !string.IsNullOrEmpty(PersistentBoolId); } return false; } } internal Source(string sourceLocationName, ItemType type, string sceneName = null, string persistentBoolId = null, string playerDataBool = null, string questAssetName = null, string playerDataInt = null, int minimumIntValue = 0) { SourceLocationName = sourceLocationName; Type = type; SceneName = sceneName; PersistentBoolId = persistentBoolId; PlayerDataBool = playerDataBool; QuestAssetName = questAssetName; PlayerDataInt = playerDataInt; MinimumIntValue = minimumIntValue; } } internal static readonly Source[] Sources = new Source[38] { new Source("Mask Shard Unlock #1", ItemType.MaskShard, null, null, "PurchasedBonebottomHeartPiece"), new Source("Mask Shard Unlock #2", ItemType.MaskShard, "Crawl_02", "Heart Piece"), new Source("Mask Shard Unlock #3", ItemType.MaskShard, "Bone_East_20", "Heart Piece"), new Source("Mask Shard Unlock #4", ItemType.MaskShard, "Shellwood_14", "Heart Piece"), new Source("Mask Shard Unlock #5", ItemType.MaskShard, "Dock_08", "Heart Piece"), new Source("Mask Shard Unlock #6", ItemType.MaskShard, "Weave_05b", "Heart Piece"), new Source("Mask Shard Unlock #7", ItemType.MaskShard, null, null, null, "Beastfly Hunt"), new Source("Mask Shard Unlock #8", ItemType.MaskShard, "Song_09", "Heart Piece"), new Source("Mask Shard Unlock #9", ItemType.MaskShard, "Library_05", "Heart Piece"), new Source("Mask Shard Unlock #10", ItemType.MaskShard, "Shadow_13", "Heart Piece"), new Source("Mask Shard Unlock #11", ItemType.MaskShard, "Bone_East_LavaChallenge", "Heart Piece (1)"), new Source("Mask Shard Unlock #12", ItemType.MaskShard, "Slab_17", "Heart Piece"), new Source("Mask Shard Unlock #13", ItemType.MaskShard, "Peak_04c", "Heart Piece"), new Source("Mask Shard Unlock #14", ItemType.MaskShard, "Wisp_07", "Heart Piece"), new Source("Mask Shard Unlock #15", ItemType.MaskShard, null, null, "MerchantEnclaveShellFragment"), new Source("Mask Shard Unlock #16", ItemType.MaskShard, "Coral_19b", "Heart Piece"), new Source("Mask Shard Unlock #17", ItemType.MaskShard, null, null, null, "Sprintmaster Race"), new Source("Mask Shard Unlock #18", ItemType.MaskShard, null, null, null, "Ant Trapper"), new Source("Mask Shard Unlock #19", ItemType.MaskShard, null, null, null, "Destroy Thread Cores"), new Source("Mask Shard Unlock #20", ItemType.MaskShard, "Peak_06", "Heart Piece"), new Source("Spool Fragment Unlock #1", ItemType.SpoolFragment, "Bone_11b", "Silk Spool"), new Source("Spool Fragment Unlock #2", ItemType.SpoolFragment, "Bone_East_13", "Silk Spool"), new Source("Spool Fragment Unlock #3", ItemType.SpoolFragment, "Greymoor_02", "Silk Spool"), new Source("Spool Fragment Unlock #4", ItemType.SpoolFragment, "Peak_01", "Silk Spool"), new Source("Spool Fragment Unlock #5", ItemType.SpoolFragment, "Weave_11", "Silk Spool"), new Source("Spool Fragment Unlock #6", ItemType.SpoolFragment, null, null, "PurchasedBelltownSpoolSegment"), new Source("Spool Fragment Unlock #7", ItemType.SpoolFragment, null, null, "MetCaravanTroupeLeaderJudge"), new Source("Spool Fragment Unlock #8", ItemType.SpoolFragment, "Cog_07", "Silk Spool"), new Source("Spool Fragment Unlock #9", ItemType.SpoolFragment, "Library_11b", "Silk Spool"), new Source("Spool Fragment Unlock #10", ItemType.SpoolFragment, "Song_19_entrance", "Silk Spool"), new Source("Spool Fragment Unlock #11", ItemType.SpoolFragment, "Under_10", "Silk Spool"), new Source("Spool Fragment Unlock #12", ItemType.SpoolFragment, "Ward_01", "Silk Spool"), new Source("Spool Fragment Unlock #13", ItemType.SpoolFragment, null, null, null, "Save Sherma"), new Source("Spool Fragment Unlock #14", ItemType.SpoolFragment, "Dock_03c", "Silk Spool"), new Source("Spool Fragment Unlock #15", ItemType.SpoolFragment, "Hang_03_top", "Silk Spool"), new Source("Spool Fragment Unlock #16", ItemType.SpoolFragment, "Arborium_09", "Silk Spool"), new Source("Spool Fragment Unlock #17", ItemType.SpoolFragment, null, null, "purchasedGrindleSpoolPiece"), new Source("Spool Fragment Unlock #18", ItemType.SpoolFragment, null, null, "MerchantEnclaveSpoolPiece") }; internal static bool TryGetQuestSource(string questAssetName, out string locationName, out ItemType type) { if (!string.IsNullOrWhiteSpace(questAssetName)) { Source[] sources = Sources; foreach (Source source in sources) { if (string.Equals(source.QuestAssetName, questAssetName, StringComparison.Ordinal)) { locationName = source.LocationName; type = source.Type; return true; } } } locationName = null; type = ItemType.Unknown; return false; } internal static Location[] AppendTo(Location[] existingLocations) { List list = new List(existingLocations); Source[] sources = Sources; foreach (Source source in sources) { Source capturedSource = source; list.Add(new Location(capturedSource.SourceLocationName, capturedSource.Type, () => IsCollected(capturedSource))); } return list.ToArray(); } internal static bool TryGetPhysicalLocation(ItemType type, string sceneName, out string locationName) { return TryGetPhysicalLocation(type, sceneName, null, out locationName); } internal static bool TryGetPhysicalLocation(ItemType type, string sceneName, string persistentBoolId, out string locationName) { locationName = null; if (string.IsNullOrEmpty(sceneName)) { return false; } Source[] sources = Sources; foreach (Source source in sources) { if (source.Type == type && source.IsPhysicalSceneSource && string.Equals(source.SceneName, sceneName, StringComparison.OrdinalIgnoreCase) && (string.IsNullOrEmpty(persistentBoolId) || string.Equals(source.PersistentBoolId, persistentBoolId, StringComparison.Ordinal))) { locationName = source.LocationName; return true; } } return false; } private static bool IsCollected(Source source) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) if (source == null) { return false; } if (source.IsPhysicalSceneSource) { SceneData instance = SceneData.instance; if (instance != null) { return instance.PersistentBools.GetValueOrDefault(source.SceneName, source.PersistentBoolId); } return false; } PlayerData instance2 = PlayerData.instance; if (instance2 == null) { return false; } if (!string.IsNullOrEmpty(source.PlayerDataBool)) { return instance2.GetBool(source.PlayerDataBool); } if (!string.IsNullOrEmpty(source.PlayerDataInt)) { return instance2.GetInt(source.PlayerDataInt) >= source.MinimumIntValue; } if (!string.IsNullOrEmpty(source.QuestAssetName) && instance2.QuestCompletionData != null) { Completion data = ((SerializableNamedList)(object)instance2.QuestCompletionData).GetData(source.QuestAssetName); if (!data.IsCompleted) { return data.WasEverCompleted; } return true; } return false; } } internal static class MelodyLocationManifest { internal sealed class Entry { internal readonly string LocationName; internal readonly string SceneName; internal readonly string ObjectName; internal readonly string FsmName; internal readonly float X; internal readonly float Y; internal readonly float SceneWidth; internal readonly float SceneHeight; internal Entry(string locationName, string sceneName, string objectName, string fsmName, float x, float y, float sceneWidth, float sceneHeight) { LocationName = locationName; SceneName = sceneName; ObjectName = objectName; FsmName = fsmName; X = x; Y = y; SceneWidth = sceneWidth; SceneHeight = sceneHeight; } } internal const string ArchitectsMelody = "Architect's Melody"; internal const string ConductorsMelody = "Conductor's Melody"; internal const string VaultkeepersMelody = "Vaultkeeper's Melody"; internal const string ElegyOfTheDeep = "Elegy of the Deep"; internal const string BeastlingCall = "Beastling Call"; internal static readonly Entry[] Entries = new Entry[5] { new Entry("Architect's Melody", "Cog_09", "puzzle cylinders", "Cylinder States", 29.8558f, 68.61611f, 72f, 117f), new Entry("Conductor's Melody", "Hang_12", "Last Conductor NPC", "Dialogue", 12.171513f, 8.097562f, 64f, 19f), new Entry("Vaultkeeper's Melody", "Library_08", "Librarian", "Dialogue", 29.493046f, 11.731146f, 115f, 40f), new Entry("Elegy of the Deep", "Tut_04", "Snail Shamans Set", "Dialogue", 47.96f, 6.741211f, 87f, 36f), new Entry("Beastling Call", "Bellway_Centipede_Arena", "Bell Beast DefeatedCentipede NPC", "Control", 139.36f, 10.03f, 191f, 43f) }; internal static bool TryGet(string sceneName, string objectName, string fsmName, out Entry entry) { Entry[] entries = Entries; foreach (Entry entry2 in entries) { if (string.Equals(entry2.SceneName, sceneName, StringComparison.OrdinalIgnoreCase) && string.Equals(entry2.ObjectName, objectName, StringComparison.Ordinal) && string.Equals(entry2.FsmName, fsmName, StringComparison.Ordinal)) { entry = entry2; return true; } } entry = null; return false; } } internal static class MemorySequenceSync { internal static bool HasRecordedSnapshot(PlayerData playerData) { if (playerData != null && playerData.HasStoredMemoryState) { return playerData.PreMemoryState.IsRecorded; } return false; } internal static bool CanApplyDurableReceipt(PlayerData playerData, HeroController hero, Item item) { if (!HasRecordedSnapshot(playerData) || (Object)(object)hero == (Object)null || item == null || item.Type == ItemType.Trap || item.Type == ItemType.Crest) { return false; } GameManager silentInstance = GameManager.SilentInstance; if ((Object)(object)silentInstance != (Object)null && silentInstance.IsGameplayScene()) { return !silentInstance.isPaused; } return false; } internal static bool TryCaptureCurrency(PlayerData playerData, CurrencyType currencyType, out int snapshotValue) { //IL_000b: 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_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: 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) //IL_0020: Unknown result type (might be due to invalid IL or missing references) snapshotValue = 0; if (!HasRecordedSnapshot(playerData) || !IsSupportedCurrency(currencyType)) { return false; } HeroItemsState preMemoryState = playerData.PreMemoryState; snapshotValue = (((int)currencyType == 0) ? preMemoryState.Rosaries : preMemoryState.ShellShards); return true; } internal static int GetPersistentCurrency(PlayerData playerData, CurrencyType currencyType, int liveValue) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if (!TryCaptureCurrency(playerData, currencyType, out var snapshotValue)) { return liveValue; } return snapshotValue; } internal static void RebaseCurrencyDelta(PlayerData playerData, CurrencyType currencyType, int snapshotBefore, int liveBefore, int liveAfter) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (HasRecordedSnapshot(playerData) && IsSupportedCurrency(currencyType)) { int currencyMaximum = GetCurrencyMaximum(currencyType); int value = RebaseValue(snapshotBefore, liveBefore, liveAfter, currencyMaximum); MirrorCurrency(playerData, currencyType, value); } } internal static void MirrorCurrency(PlayerData playerData, CurrencyType currencyType, int value) { //IL_0008: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) if (HasRecordedSnapshot(playerData) && IsSupportedCurrency(currencyType)) { value = Clamp(value, 0, GetCurrencyMaximum(currencyType)); HeroItemsState preMemoryState = playerData.PreMemoryState; if ((int)currencyType == 0) { preMemoryState.Rosaries = value; } else { preMemoryState.ShellShards = value; } playerData.PreMemoryState = preMemoryState; } } internal static bool TryCaptureSilk(PlayerData playerData, out int snapshotValue) { snapshotValue = 0; if (!HasRecordedSnapshot(playerData)) { return false; } snapshotValue = playerData.PreMemoryState.Silk; return true; } internal static int GetPersistentSilk(PlayerData playerData, int liveValue) { if (!TryCaptureSilk(playerData, out var snapshotValue)) { return liveValue; } return snapshotValue; } internal static void RebaseSilkDelta(PlayerData playerData, int snapshotBefore, int liveBefore, int liveAfter) { if (HasRecordedSnapshot(playerData)) { int maximum; try { maximum = Math.Max(0, playerData.CurrentSilkMax); } catch { maximum = Math.Max(0, playerData.silkMax); } MirrorSilk(playerData, RebaseValue(snapshotBefore, liveBefore, liveAfter, maximum)); } } internal static void MirrorSilk(PlayerData playerData, int value) { //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_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (HasRecordedSnapshot(playerData)) { int maximum; try { maximum = Math.Max(0, playerData.CurrentSilkMax); } catch { maximum = Math.Max(0, playerData.silkMax); } HeroItemsState preMemoryState = playerData.PreMemoryState; preMemoryState.Silk = Clamp(value, 0, maximum); playerData.PreMemoryState = preMemoryState; } } internal static void SynchronizeMaskHealth(PlayerData playerData, int desiredMaxHealth, bool refillNewMask) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (HasRecordedSnapshot(playerData)) { desiredMaxHealth = Math.Max(0, desiredMaxHealth); HeroItemsState preMemoryState = playerData.PreMemoryState; preMemoryState.Health = (refillNewMask ? desiredMaxHealth : Clamp(preMemoryState.Health, 0, desiredMaxHealth)); playerData.PreMemoryState = preMemoryState; } } internal static int RebaseValue(int snapshotBefore, int liveBefore, int liveAfter, int maximum) { long val = snapshotBefore + ((long)liveAfter - (long)liveBefore); return (int)Math.Min(Math.Max(0, maximum), Math.Max(0L, val)); } private static int GetCurrencyMaximum(CurrencyType currencyType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) try { return Math.Max(0, Gameplay.GetCurrencyCap(currencyType)); } catch { return int.MaxValue; } } private static bool IsSupportedCurrency(CurrencyType currencyType) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Invalid comparison between Unknown and I4 if ((int)currencyType != 0) { return (int)currencyType == 1; } return true; } private static int Clamp(int value, int minimum, int maximum) { return Math.Min(Math.Max(value, minimum), maximum); } } internal static class MinorCacheManifest { internal enum SourceKind { GeoRock, BreakableHolder, RosaryCache } internal sealed class Entry { internal readonly string LocationName; internal readonly string SceneName; internal readonly string ObjectName; internal readonly SourceKind Kind; internal readonly float X; internal readonly float Y; internal readonly float SceneWidth; internal readonly float SceneHeight; internal Entry(string locationName, string sceneName, string objectName, SourceKind kind, float x, float y, float sceneWidth, float sceneHeight) { LocationName = locationName; SceneName = sceneName; ObjectName = objectName; Kind = kind; X = x; Y = y; SceneWidth = sceneWidth; SceneHeight = sceneHeight; } } internal static readonly Entry[] Entries = new Entry[339] { new Entry("Rosary Cache: Bellhart #1", "Belltown_04", "rosary_string_medium", SourceKind.RosaryCache, 62.35f, 22.91103f, 85f, 97f), new Entry("Rosary Cache: Bellhart #2", "Belltown_04", "rosary_string_small", SourceKind.RosaryCache, 63.709732f, 23.371956f, 85f, 97f), new Entry("Rosary Cache: Bellhart #3", "Belltown_04", "rosary_cache_bell_ground", SourceKind.RosaryCache, 76.93f, 47.61f, 85f, 97f), new Entry("Rosary Cache: Bellhart #4", "Belltown_07", "rosary_cache_hang_bell", SourceKind.RosaryCache, 24.28f, 25.24f, 70f, 34f), new Entry("Rosary Cache: Bellhart #5", "Belltown_basement_03", "rosary_cache_bell_ground (1)", SourceKind.RosaryCache, 70.93221f, 5.155448f, 150f, 150f), new Entry("Rosary Cache: Bellhart #6", "Belltown_basement_03", "rosary_cache_bell_ground", SourceKind.RosaryCache, 71.310005f, 115.44f, 150f, 150f), new Entry("Rosary Cache: Bellhart #7", "Belltown_basement_03", "rosary_cache_hang_bell", SourceKind.RosaryCache, 110.48037f, 67.520004f, 150f, 150f), new Entry("Rosary Cache: Bilewater #1", "Shadow_05", "Geo Rock Deepnest", SourceKind.GeoRock, 34.964455f, 16.89f, 212f, 55f), new Entry("Rosary Cache: Bilewater #2", "Shadow_05", "rosary_string_small_half", SourceKind.RosaryCache, 102.54917f, 33.72525f, 212f, 55f), new Entry("Rosary Cache: Bilewater #3", "Shadow_14", "Geo Rock 1", SourceKind.GeoRock, 25.14f, 61.94682f, 29f, 71f), new Entry("Rosary Cache: Bilewater #4", "Shadow_19", "Geo Rock 3", SourceKind.GeoRock, 36.4f, 87.41f, 80f, 96f), new Entry("Rosary Cache: Bilewater #5", "Shadow_19", "Geo Rock 1", SourceKind.GeoRock, 40.05f, 87.09f, 80f, 96f), new Entry("Rosary Cache: Bilewater #6", "Shadow_20", "Geo Rock 1", SourceKind.GeoRock, 169.965f, 2.960163f, 230f, 65f), new Entry("Rosary Cache: Bilewater #7", "Shadow_20", "Geo Rock 3", SourceKind.GeoRock, 173.33f, 3.348672f, 230f, 65f), new Entry("Rosary Cache: Blasted Steps", "Coral_12", "Geo Rock Deepnest", SourceKind.GeoRock, 86.77f, 33.59f, 94f, 78f), new Entry("Rosary Cache: Bone Bottom #4", "Bone_11", "rosary_string_small", SourceKind.RosaryCache, 8.85f, 11.72f, 120f, 31f), new Entry("Rosary Cache: Bone Bottom #5", "Bone_11", "rosary_string_small_mixed", SourceKind.RosaryCache, 9.73f, 12.47f, 120f, 31f), new Entry("Rosary Cache: Bone Bottom #6", "Bonegrave", "rosary_string_small_half", SourceKind.RosaryCache, 269.854f, 78.10092f, 315f, 82f), new Entry("Rosary Cache: Bone Bottom #7", "Bonegrave", "rosary_string_small", SourceKind.RosaryCache, 270.7926f, 76.73571f, 315f, 82f), new Entry("Rosary Cache: Bone Bottom #8", "Bonetown", "rosary_string_small_half", SourceKind.RosaryCache, 225.37f, 85.97f, 315f, 90f), new Entry("Rosary Cache: Bone Bottom #9", "Bonetown", "rosary_string_medium", SourceKind.RosaryCache, 226.42003f, 84.13425f, 315f, 90f), new Entry("Rosary Cache: Bonegrave #1", "Chapel_Wanderer", "rosary_string_small_half (1)", SourceKind.RosaryCache, 8.254986f, 106.69f, 87f, 140f), new Entry("Rosary Cache: Bonegrave #2", "Chapel_Wanderer", "rosary_string_small_half (4)", SourceKind.RosaryCache, 11.36f, 106.69f, 87f, 140f), new Entry("Rosary Cache: Bonegrave #3", "Chapel_Wanderer", "rosary_string_small_half (5)", SourceKind.RosaryCache, 79.526115f, 110.79f, 87f, 140f), new Entry("Rosary Cache: Bonegrave #4", "Chapel_Wanderer", "rosary_string_medium", SourceKind.RosaryCache, 80.72964f, 109.59193f, 87f, 140f), new Entry("Rosary Cache: The Marrow (Mosslands Passage) #1", "Bone_01b", "rosary_string_small", SourceKind.RosaryCache, 13.015055f, 87.43103f, 38f, 91f), new Entry("Rosary Cache: The Marrow (Mosslands Passage) #2", "Bone_01b", "rosary_string_small_half", SourceKind.RosaryCache, 14.1f, 88f, 38f, 91f), new Entry("Rosary Cache: Choral Chambers #1", "Song_01", "rosary_string_small (1)", SourceKind.RosaryCache, 19.39f, 84.69f, 162f, 150f), new Entry("Rosary Cache: Choral Chambers #2", "Song_01", "rosary_string_medium", SourceKind.RosaryCache, 20.163073f, 85.338806f, 162f, 150f), new Entry("Rosary Cache: Choral Chambers #3", "Song_01", "rosary_string_small_half", SourceKind.RosaryCache, 109.45238f, 134.36581f, 162f, 150f), new Entry("Rosary Cache: Choral Chambers #4", "Song_01", "rosary_string_small", SourceKind.RosaryCache, 110.476265f, 132.2327f, 162f, 150f), new Entry("Rosary Cache: Choral Chambers #5", "Song_01b", "rosary_string_small_mixed", SourceKind.RosaryCache, 12.72f, 10.74f, 124f, 19f), new Entry("Rosary Cache: Choral Chambers #6", "Song_01b", "rosary_string_small", SourceKind.RosaryCache, 13.653358f, 10.540417f, 124f, 19f), new Entry("Rosary Cache: Choral Chambers #7", "Song_01b", "rosary_string_medium", SourceKind.RosaryCache, 16.811474f, 10.903366f, 124f, 19f), new Entry("Rosary Cache: Choral Chambers #8", "Song_03", "rosary_string_small_half (1)", SourceKind.RosaryCache, 39.53f, 34.79f, 140f, 40f), new Entry("Rosary Cache: Choral Chambers #9", "Song_03", "rosary_string_small", SourceKind.RosaryCache, 40.55176f, 34.195667f, 140f, 40f), new Entry("Rosary Cache: Choral Chambers #10", "Song_07", "rosary_string_medium", SourceKind.RosaryCache, 3.12379f, 8.712918f, 80f, 24f), new Entry("Rosary Cache: Choral Chambers #11", "Song_09", "rosary_string_small_half", SourceKind.RosaryCache, 9.866534f, 61.636f, 50f, 85f), new Entry("Rosary Cache: Choral Chambers #12", "Song_09", "rosary_string_small", SourceKind.RosaryCache, 10.7772f, 59.84427f, 50f, 85f), new Entry("Rosary Cache: Choral Chambers #13", "Song_09", "rosary_string_small_half (1)", SourceKind.RosaryCache, 11.72f, 60.65f, 50f, 85f), new Entry("Rosary Cache: Choral Chambers #14", "Song_11", "rosary_string_small_half", SourceKind.RosaryCache, 43.35f, 52.59f, 60f, 188f), new Entry("Rosary Cache: Choral Chambers #15", "Song_11", "rosary_string_small", SourceKind.RosaryCache, 45.710358f, 50.41812f, 60f, 188f), new Entry("Rosary Cache: Choral Chambers #16", "Song_11", "rosary_string_small_half (1)", SourceKind.RosaryCache, 46.74f, 50.99f, 60f, 188f), new Entry("Rosary Cache: Choral Chambers #17", "Song_15", "rosary_string_medium", SourceKind.RosaryCache, 5.159665f, 10.04f, 85f, 41f), new Entry("Rosary Cache: Choral Chambers #18", "Song_15", "rosary_string_small", SourceKind.RosaryCache, 6.429957f, 10.543651f, 85f, 41f), new Entry("Rosary Cache: Choral Chambers #19", "Song_15", "rosary_string_small_half", SourceKind.RosaryCache, 7.745812f, 12.03894f, 85f, 41f), new Entry("Rosary Cache: Deep Docks #1", "Dock_02", "rosary_string_small_half (1)", SourceKind.RosaryCache, 103.04f, 57.54f, 125f, 101f), new Entry("Rosary Cache: Deep Docks #2", "Dock_02", "rosary_string_small_half (2)", SourceKind.RosaryCache, 103.92f, 58.09f, 125f, 101f), new Entry("Rosary Cache: Deep Docks #3", "Dock_06_Church", "rosary_string_small (1)", SourceKind.RosaryCache, 26.661207f, 29.582787f, 75f, 35f), new Entry("Rosary Cache: Deep Docks #4", "Dock_06_Church", "rosary_string_small_half (1)", SourceKind.RosaryCache, 29.077658f, 31.852787f, 75f, 35f), new Entry("Rosary Cache: Deep Docks #5", "Dock_06_Church", "rosary_string_small", SourceKind.RosaryCache, 40.647335f, 29.802464f, 75f, 35f), new Entry("Rosary Cache: Deep Docks #6", "Dock_06_Church", "rosary_string_small_half", SourceKind.RosaryCache, 41.892174f, 30.571337f, 75f, 35f), new Entry("Rosary Cache: Far Fields #1", "Bone_East_07", "rosary_string_small_half", SourceKind.RosaryCache, 10.794569f, 168.57463f, 40f, 190f), new Entry("Rosary Cache: Far Fields #2", "Bone_East_08", "Geo Rock 2", SourceKind.GeoRock, 58.67f, 21.1f, 150f, 50f), new Entry("Rosary Cache: Far Fields #3", "Bone_East_09b", "ant_rosary_string (1)", SourceKind.BreakableHolder, 61.83f, 142.71f, 100f, 200f), new Entry("Rosary Cache: Far Fields #4", "Bone_East_09b", "ant_rosary_string", SourceKind.BreakableHolder, 63.99f, 143.14f, 100f, 200f), new Entry("Rosary Cache: Far Fields #5", "Bone_East_14", "rosary_string_medium", SourceKind.RosaryCache, 87.36f, 42.39f, 140f, 80f), new Entry("Rosary Cache: Far Fields #6", "Bone_East_14", "rosary_string_small", SourceKind.RosaryCache, 88.28505f, 42.73f, 140f, 80f), new Entry("Rosary Cache: Far Fields #7", "Bone_East_14b", "rosary_string_small", SourceKind.RosaryCache, 249.53813f, 58.81552f, 309f, 75f), new Entry("Rosary Cache: Far Fields #8", "Bone_East_14b", "rosary_string_small_half", SourceKind.RosaryCache, 250.43f, 59.589996f, 309f, 75f), new Entry("Rosary Cache: Far Fields #9", "Bone_East_15", "Geo Rock 1", SourceKind.GeoRock, 45.485546f, 7.985728f, 173f, 110f), new Entry("Rosary Cache: Far Fields #10", "Bone_East_15", "Geo Rock 2", SourceKind.GeoRock, 48.47f, 8.21f, 173f, 110f), new Entry("Rosary Cache: Far Fields #11", "Bone_East_16", "ant_rosary_string (2)", SourceKind.BreakableHolder, 9f, 18.59f, 57f, 30f), new Entry("Rosary Cache: Far Fields #12", "Bone_East_16", "ant_rosary_string", SourceKind.BreakableHolder, 11.41f, 18.84f, 57f, 30f), new Entry("Rosary Cache: Far Fields #13", "Bone_East_16", "ant_rosary_string (1)", SourceKind.BreakableHolder, 13.05f, 18.37f, 57f, 30f), new Entry("Rosary Cache: Far Fields #14", "Bone_East_17", "ant_rosary_string (1)", SourceKind.BreakableHolder, 8.98f, 85.09f, 115f, 107f), new Entry("Rosary Cache: Far Fields #15", "Bone_East_17", "ant_rosary_string", SourceKind.BreakableHolder, 10.21f, 84.09f, 115f, 107f), new Entry("Rosary Cache: Far Fields #16", "Bone_East_17b", "ant_rosary_string (1)", SourceKind.BreakableHolder, 10.88f, 32.08f, 115f, 64f), new Entry("Rosary Cache: Far Fields #17", "Bone_East_17b", "ant_rosary_string", SourceKind.BreakableHolder, 11.920928f, 32.25f, 115f, 64f), new Entry("Rosary Cache: Far Fields #18", "Bone_East_18", "ant_rosary_string", SourceKind.BreakableHolder, 139.87f, 38.38f, 178f, 62f), new Entry("Rosary Cache: Far Fields #19", "Bone_East_18b", "ant_item_string", SourceKind.BreakableHolder, 107.48079f, 19.20575f, 327f, 108f), new Entry("Rosary Cache: Far Fields #20", "Bone_East_24", "ant_rosary_string (1)", SourceKind.BreakableHolder, 245.94f, 67.81f, 263f, 76f), new Entry("Rosary Cache: Far Fields #21", "Bone_East_24", "ant_rosary_string_large", SourceKind.BreakableHolder, 247.65f, 67.75f, 263f, 76f), new Entry("Pale Rosary Necklace: Far Fields", "Bone_East_24", "ant_item_string", SourceKind.BreakableHolder, 248.93f, 67.41f, 263f, 76f), new Entry("Rosary Cache: Greymoor #1", "Greymoor_01", "Geo Rock Deepnest", SourceKind.GeoRock, 8.55f, 16.88f, 130f, 52f), new Entry("Rosary Cache: Greymoor #2", "Greymoor_02", "rosary_string_small", SourceKind.RosaryCache, 52.01f, 95.12f, 90f, 150f), new Entry("Rosary Cache: Greymoor #3", "Greymoor_02", "rosary_string_small_half", SourceKind.RosaryCache, 52.948353f, 96.339836f, 90f, 150f), new Entry("Rosary Cache: Greymoor #4", "Greymoor_03", "rosary_string_small", SourceKind.RosaryCache, 89.76701f, 100.71553f, 120f, 140f), new Entry("Rosary Cache: Greymoor #5", "Greymoor_03", "rosary_string_small_half", SourceKind.RosaryCache, 91.11f, 101.85f, 120f, 140f), new Entry("Rosary Cache: Greymoor #6", "Greymoor_04", "Geo Rock Deepnest", SourceKind.GeoRock, 32.72f, 35.76f, 40f, 170f), new Entry("Rosary Cache: Greymoor #7", "Greymoor_05", "rosary_string_small_half", SourceKind.RosaryCache, 6.62f, 24.71f, 110f, 75f), new Entry("Rosary Cache: Greymoor #8", "Greymoor_05", "rosary_string_small_half (1)", SourceKind.RosaryCache, 7.35f, 25.95f, 110f, 75f), new Entry("Rosary Cache: Greymoor #9", "Greymoor_05", "Geo Rock Deepnest", SourceKind.GeoRock, 95.09f, 61.82f, 110f, 75f), new Entry("Rosary Cache: Greymoor #10", "Greymoor_06", "Geo Rock Deepnest", SourceKind.GeoRock, 6.369603f, 78.80082f, 40f, 207f), new Entry("Rosary Cache: Greymoor #11", "Greymoor_07", "rosary_string_small", SourceKind.RosaryCache, 27.329424f, 15.292844f, 75f, 100f), new Entry("Rosary Cache: Greymoor #12", "Greymoor_07", "rosary_string_small_half (1)", SourceKind.RosaryCache, 28.433817f, 16.25f, 75f, 100f), new Entry("Rosary Cache: Greymoor #13", "Greymoor_07", "Geo Rock Deepnest", SourceKind.GeoRock, 68.53183f, 71.848854f, 75f, 100f), new Entry("Rosary Cache: Greymoor #14", "Greymoor_08", "rosary_string_small_half", SourceKind.RosaryCache, 140.08247f, 33.81587f, 161f, 38f), new Entry("Rosary Cache: Greymoor #15", "Greymoor_11", "rosary_string_small", SourceKind.RosaryCache, 25.06f, 55.67647f, 110f, 70f), new Entry("Rosary Cache: Greymoor #16", "Greymoor_11", "rosary_string_small_half", SourceKind.RosaryCache, 25.897402f, 57.08564f, 110f, 70f), new Entry("Rosary Cache: Greymoor #17", "Greymoor_11", "Geo Rock Deepnest (1)", SourceKind.GeoRock, 64.61f, 50.88f, 110f, 70f), new Entry("Rosary Cache: Greymoor #18", "Greymoor_15", "Geo Rock 1", SourceKind.GeoRock, 57.288216f, 74.01197f, 78f, 84f), new Entry("Rosary Cache: Greymoor #19", "Greymoor_15b", "rosary_string_small_half (3)", SourceKind.RosaryCache, 182.98f, 103.65f, 220f, 141f), new Entry("Rosary Cache: Greymoor #20", "Greymoor_15b", "rosary_string_small_half (2)", SourceKind.RosaryCache, 197.47f, 44.2f, 220f, 141f), new Entry("Rosary Cache: Greymoor #21", "Greymoor_15b", "rosary_string_small_half (1)", SourceKind.RosaryCache, 204.05f, 69.67f, 220f, 141f), new Entry("Rosary Cache: Greymoor #22", "Greymoor_15b", "rosary_string_small", SourceKind.RosaryCache, 205.7346f, 67.43042f, 220f, 141f), new Entry("Rosary Cache: Greymoor #23", "Greymoor_16", "rosary_string_medium", SourceKind.RosaryCache, 160.61f, 24.84f, 171f, 60f), new Entry("Rosary Cache: Greymoor #24", "Greymoor_16", "rosary_string_small", SourceKind.RosaryCache, 161.48451f, 25.5659f, 171f, 60f), new Entry("Rosary Cache: Greymoor #25", "Greymoor_16", "rosary_string_small_half", SourceKind.RosaryCache, 162.37819f, 26.58575f, 171f, 60f), new Entry("Rosary Cache: Greymoor #26", "Greymoor_22", "rosary_string_small_half", SourceKind.RosaryCache, 88.57f, 31.712984f, 110f, 50f), new Entry("Rosary Cache: Greymoor #27", "Greymoor_22", "rosary_string_small", SourceKind.RosaryCache, 104.60347f, 33.570705f, 110f, 50f), new Entry("Rosary Cache: Greymoor #28", "Room_CrowCourt", "rosary_string_small_half", SourceKind.RosaryCache, 24.13f, 26.933224f, 70f, 80f), new Entry("Rosary Cache: Greymoor #29", "Room_CrowCourt", "rosary_string_small_half (8)", SourceKind.RosaryCache, 24.67125f, 38.093536f, 70f, 80f), new Entry("Rosary Cache: Greymoor #30", "Room_CrowCourt", "rosary_string_small_half (7)", SourceKind.RosaryCache, 25.72125f, 37.683537f, 70f, 80f), new Entry("Rosary Cache: Greymoor #31", "Room_CrowCourt", "rosary_string_medium", SourceKind.RosaryCache, 26.33f, 25.54f, 70f, 80f), new Entry("Rosary Cache: Greymoor #32", "Room_CrowCourt", "rosary_string_small", SourceKind.RosaryCache, 31.99125f, 25.643538f, 70f, 80f), new Entry("Rosary Cache: Greymoor #33", "Room_CrowCourt", "rosary_string_small_half (1)", SourceKind.RosaryCache, 33.50125f, 26.933224f, 70f, 80f), new Entry("Rosary Cache: Greymoor #34", "Room_CrowCourt", "rosary_string_small_half (6)", SourceKind.RosaryCache, 46.95f, 38.88f, 70f, 80f), new Entry("Rosary Cache: Greymoor #35", "Room_CrowCourt_02", "rosary_string_small_half (2)", SourceKind.RosaryCache, 8.54f, 81.09f, 70f, 92f), new Entry("Rosary Cache: Greymoor #36", "Room_CrowCourt_02", "rosary_string_small_half (3)", SourceKind.RosaryCache, 17.69f, 53.17f, 70f, 92f), new Entry("Rosary Cache: Greymoor #37", "Room_CrowCourt_02", "rosary_string_small", SourceKind.RosaryCache, 19.296217f, 50.520546f, 70f, 92f), new Entry("Rosary Cache: High Halls #1", "Hang_06_bank", "rosary_string_small", SourceKind.RosaryCache, 80.924416f, 40.69288f, 129f, 60f), new Entry("Rosary Cache: High Halls #2", "Hang_06_bank", "rosary_string_large_mixed Variant", SourceKind.RosaryCache, 81.82376f, 40.14687f, 129f, 60f), new Entry("Rosary Cache: High Halls #3", "Hang_06_bank", "rosary_string_small_half", SourceKind.RosaryCache, 85.42288f, 41.89263f, 129f, 60f), new Entry("Rosary Cache: High Halls #4", "Hang_06_bank", "rosary_string_medium", SourceKind.RosaryCache, 86.3822f, 40.613537f, 129f, 60f), new Entry("Rosary Cache: High Halls #5", "Hang_06_bank", "rosary_string_small_half (1)", SourceKind.RosaryCache, 91.59f, 32.09f, 129f, 60f), new Entry("Rosary Cache: High Halls #6", "Hang_06_bank", "rosary_string_large_full Variant", SourceKind.RosaryCache, 91.982346f, 39.62599f, 129f, 60f), new Entry("Rosary Cache: Hunter's March #1", "Ant_04_left", "ant_rosary_string", SourceKind.BreakableHolder, 6.06f, 30.68f, 145f, 37f), new Entry("Rosary Cache: Hunter's March #2", "Ant_04_left", "ant_rosary_string (1)", SourceKind.BreakableHolder, 7.87f, 30.68f, 145f, 37f), new Entry("Rosary Necklace: Hunter's March", "Ant_04_left", "ant_item_string", SourceKind.BreakableHolder, 85.02f, 26.99f, 145f, 37f), new Entry("Rosary Cache: Hunter's March #4", "Ant_21", "ant_rosary_string_large", SourceKind.BreakableHolder, 42.11f, 76.44f, 138f, 89f), new Entry("Rosary Cache: Hunter's March #5", "Ant_21", "ant_rosary_string (3)", SourceKind.BreakableHolder, 43.46f, 76.34f, 138f, 89f), new Entry("Rosary Cache: Hunter's March #6", "Ant_21", "ant_rosary_string_medium (1)", SourceKind.BreakableHolder, 46.54f, 76.37f, 138f, 89f), new Entry("Rosary Cache: Hunter's March #7", "Ant_21", "ant_rosary_string_medium (3)", SourceKind.BreakableHolder, 47.65f, 76.7f, 138f, 89f), new Entry("Rosary Cache: Hunter's March #8", "Ant_21", "ant_rosary_string", SourceKind.BreakableHolder, 49.78f, 76.25f, 138f, 89f), new Entry("Rosary Cache: Hunter's March #9", "Ant_21", "ant_rosary_string_large (1)", SourceKind.BreakableHolder, 51f, 76.03f, 138f, 89f), new Entry("Rosary Cache: Hunter's March #10", "Ant_21", "ant_rosary_string_medium Variant", SourceKind.BreakableHolder, 53.67f, 76.28f, 138f, 89f), new Entry("Rosary Cache: Moss Grotto", "Aspid_01", "Geo Rock 2", SourceKind.GeoRock, 8.89f, 7.27f, 80f, 273f), new Entry("Rosary Cache: Mosshome #1", "Mosstown_01", "rosary_string_small (1)", SourceKind.RosaryCache, 47.49f, 25.6f, 140f, 30f), new Entry("Rosary Cache: Mosshome #2", "Mosstown_01", "rosary_string_small (2)", SourceKind.RosaryCache, 48.38f, 25.3f, 140f, 30f), new Entry("Rosary Cache: Mosshome #3", "Mosstown_02", "rosary_string_small_half", SourceKind.RosaryCache, 153.17001f, 60.030003f, 160f, 65f), new Entry("Rosary Cache: Mosshome #4", "Mosstown_02", "rosary_string_small", SourceKind.RosaryCache, 154.19572f, 58.93504f, 160f, 65f), new Entry("Rosary Cache: Mount Fay #1", "Bellway_Peak", "rosary_string_medium (1)", SourceKind.RosaryCache, 8.789767f, 40.87941f, 29f, 55f), new Entry("Rosary Cache: Mount Fay #2", "Bellway_Peak", "rosary_string_medium", SourceKind.RosaryCache, 9.862885f, 40.22675f, 29f, 55f), new Entry("Rosary Cache: Mount Fay #3", "Bellway_Peak", "rosary_string_small_mixed", SourceKind.RosaryCache, 10.96f, 41.82304f, 29f, 55f), new Entry("Rosary Cache: Mount Fay #4", "Bellway_Peak_02", "rosary_cache_hang", SourceKind.RosaryCache, 24.876995f, 27.75f, 101f, 37f), new Entry("Rosary Cache: Putrified Ducts #1", "Aqueduct_08", "Geo Rock 1", SourceKind.GeoRock, 36.23f, 4.08f, 60f, 20f), new Entry("Rosary Cache: Putrified Ducts #2", "Aqueduct_08", "Geo Rock 3", SourceKind.GeoRock, 39.37f, 4.317554f, 60f, 20f), new Entry("Rosary Cache: Putrified Ducts #3", "Aqueduct_08", "Geo Rock 1 (1)", SourceKind.GeoRock, 47.27f, 4.08f, 60f, 20f), new Entry("Rosary Cache: Shellwood", "Shellwood_26", "Geo Rock 3", SourceKind.GeoRock, 98.604f, 75.319f, 135f, 130f), new Entry("Rosary Cache: Sinner's Road #1", "Dust_02", "Geo Rock Deepnest", SourceKind.GeoRock, 3.64f, 7.82f, 40f, 130f), new Entry("Rosary Cache: Sinner's Road #2", "Dust_02", "rosary_string_small_half", SourceKind.RosaryCache, 22.493637f, 72.060005f, 40f, 130f), new Entry("Rosary Cache: Sinner's Road #3", "Dust_02", "rosary_string_small_half (1)", SourceKind.RosaryCache, 23.25f, 72.46f, 40f, 130f), new Entry("Rosary Cache: Sinner's Road #4", "Dust_03", "rosary_string_small_half", SourceKind.RosaryCache, 68.5683f, 23.115767f, 145f, 40f), new Entry("Rosary Cache: Sinner's Road #5", "Dust_06", "rosary_string_medium", SourceKind.RosaryCache, 10.926036f, 82.345825f, 30f, 190f), new Entry("Rosary Cache: Sinner's Road #6", "Dust_06", "rosary_string_small", SourceKind.RosaryCache, 11.8f, 83.66f, 30f, 190f), new Entry("Rosary Cache: Sinner's Road #7", "Dust_06", "rosary_string_small_half", SourceKind.RosaryCache, 12.62f, 84.72f, 30f, 190f), new Entry("Rosary Cache: Sinner's Road #8", "Dust_10", "Geo Rock Deepnest", SourceKind.GeoRock, 145.12f, 53.66f, 160f, 80f), new Entry("Rosary Cache: The Marrow #1", "Bone_01", "rosary_string_small_half (2)", SourceKind.RosaryCache, 116.73f, 13.76f, 130f, 91f), new Entry("Rosary Cache: The Marrow #2", "Bone_01", "rosary_string_small", SourceKind.RosaryCache, 117.59f, 12.95f, 130f, 91f), new Entry("Rosary Cache: The Marrow #3", "Bone_01c", "rosary_string_small_half (1)", SourceKind.RosaryCache, 138.47f, 13.13f, 198f, 91f), new Entry("Rosary Cache: The Marrow #4", "Bone_01c", "rosary_string_medium", SourceKind.RosaryCache, 139.43f, 11.74f, 198f, 91f), new Entry("Rosary Cache: The Marrow #5", "Bone_01c", "Geo Rock 1 (1)", SourceKind.GeoRock, 180.03f, 57.984245f, 198f, 91f), new Entry("Rosary Cache: The Marrow #6", "Bone_01c", "Geo Rock 3", SourceKind.GeoRock, 184.05f, 58.46f, 198f, 91f), new Entry("Rosary Cache: The Marrow #7", "Bone_04", "Geo Rock 3", SourceKind.GeoRock, 205.03023f, 5.261952f, 233f, 31f), new Entry("Rosary Cache: The Marrow #8", "Bone_08", "Geo Rock 2", SourceKind.GeoRock, 28.79f, 47.13f, 40f, 95f), new Entry("Rosary Cache: The Marrow #9", "Bone_08", "Geo Rock 1", SourceKind.GeoRock, 33.160004f, 47.074722f, 40f, 95f), new Entry("Rosary Cache: The Marrow #10", "Bone_14", "Geo Rock 1", SourceKind.GeoRock, 129.07f, 8.01f, 135f, 38f), new Entry("Rosary Cache: The Marrow #11", "Bone_16", "Geo Rock 1", SourceKind.GeoRock, 28.46f, 45.02278f, 135f, 75f), new Entry("Rosary Cache: The Marrow #12", "Bone_16", "Geo Rock 2", SourceKind.GeoRock, 31.488272f, 45.182293f, 135f, 75f), new Entry("Rosary Cache: The Marrow #13", "Bone_16", "Geo Rock 2 (1)", SourceKind.GeoRock, 112.02f, 66.144394f, 135f, 75f), new Entry("Rosary Cache: The Marrow #14", "Bone_19", "rosary_string_small_half", SourceKind.RosaryCache, 29.218857f, 18.17443f, 146f, 27f), new Entry("Rosary Cache: The Marrow #15", "Bone_19", "rosary_string_small", SourceKind.RosaryCache, 30.319717f, 17.46211f, 146f, 27f), new Entry("Rosary Cache: The Marrow #16", "Bone_19", "rosary_string_small_half (1)", SourceKind.RosaryCache, 118.14f, 22.31f, 146f, 27f), new Entry("Rosary Cache: The Slab #1", "Slab_18", "rosary_string_small_half", SourceKind.RosaryCache, 16.36f, 25.54f, 100f, 76f), new Entry("Rosary Cache: The Slab #2", "Slab_19b", "rosary_string_medium", SourceKind.RosaryCache, 20.055536f, 17.021246f, 44f, 36f), new Entry("Rosary Cache: The Slab #3", "Slab_19b", "rosary_string_small", SourceKind.RosaryCache, 21.17f, 16.66f, 44f, 36f), new Entry("Rosary Cache: The Slab #4", "Slab_19b", "rosary_string_small (1)", SourceKind.RosaryCache, 23.13f, 15.01f, 44f, 36f), new Entry("Rosary Cache: The Slab #5", "Slab_19b", "rosary_string_small_half", SourceKind.RosaryCache, 28.055967f, 12.675937f, 44f, 36f), new Entry("Rosary Cache: Underworks #1", "Under_07", "rosary_string_small_half", SourceKind.RosaryCache, 78.05558f, 20.11f, 85f, 27f), new Entry("Rosary Cache: Underworks #2", "Under_07c", "rosary_string_small_half", SourceKind.RosaryCache, 18.199444f, 73f, 85f, 106f), new Entry("Rosary Cache: Underworks #3", "Under_07c", "rosary_string_small_half (1)", SourceKind.RosaryCache, 19.11f, 72.277f, 85f, 106f), new Entry("Rosary Cache: Underworks #4", "Under_23", "rosary_string_small_half", SourceKind.RosaryCache, 38.47f, 5.35f, 160f, 35f), new Entry("Rosary Cache: Whispering Vaults #1", "Library_04", "rosary_bucket_library", SourceKind.RosaryCache, 51.39f, 78.98f, 67f, 220f), new Entry("Rosary Cache: Whispering Vaults #2", "Library_06", "rosary_string_small", SourceKind.RosaryCache, 36.16f, 67.13f, 66f, 78f), new Entry("Rosary Cache: Whispering Vaults #3", "Library_06", "rosary_string_small_half", SourceKind.RosaryCache, 37.163895f, 68.371895f, 66f, 78f), new Entry("Rosary Cache: Whispering Vaults #4", "Library_06", "rosary_bucket_library", SourceKind.RosaryCache, 38.83815f, 64.691734f, 66f, 78f), new Entry("Rosary Cache: Whispering Vaults #5", "Library_07", "rosary_cache_bell_ground", SourceKind.RosaryCache, 39.55f, 138.46f, 79f, 172f), new Entry("Rosary Cache: Whispering Vaults #6", "Library_08", "rosary_string_small_half", SourceKind.RosaryCache, 107.91712f, 37.421135f, 115f, 40f), new Entry("Rosary Cache: Whispering Vaults #7", "Library_08", "rosary_string_small", SourceKind.RosaryCache, 109.15f, 35.59f, 115f, 40f), new Entry("Rosary Cache: Whiteward #1", "Ward_06", "rosary_string_medium", SourceKind.RosaryCache, 71.9f, 51.74f, 120f, 60f), new Entry("Rosary Cache: Whiteward #2", "Ward_06", "rosary_string_small_half", SourceKind.RosaryCache, 72.832504f, 53.148365f, 120f, 60f), new Entry("Shell Shard Cache: Bilewater #1", "Shadow_10", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 165.35f, 5.59f, 210f, 70f), new Entry("Shell Shard Cache: Bilewater #2", "Shadow_10", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 168.17f, 5.18f, 210f, 70f), new Entry("Shell Shard Cache: Bilewater #3", "Shadow_11", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 4.87f, 11.67f, 29f, 112f), new Entry("Shell Shard Cache: Bilewater #4", "Shadow_11", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 6.44f, 14.73f, 29f, 112f), new Entry("Shell Shard Cache: Bilewater #5", "Shadow_25", "Shell Shard Fossil Mid Variant", SourceKind.BreakableHolder, 8.9f, 31.24f, 29f, 36f), new Entry("Shell Shard Cache: Bilewater #6", "Shadow_25", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 15.269692f, 31.737f, 29f, 36f), new Entry("Shell Shard Cache: Bilewater #7", "Shadow_25", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 17.688f, 30.577f, 29f, 36f), new Entry("Shell Shard Cache: Blasted Steps #1", "Coral_02", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 50.89f, 12.73f, 245f, 80f), new Entry("Shell Shard Cache: Blasted Steps #2", "Coral_02", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 54.7f, 12.48f, 245f, 80f), new Entry("Shell Shard Cache: Blasted Steps #3", "Coral_02", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 56.41f, 11.08f, 245f, 80f), new Entry("Shell Shard Cache: Blasted Steps #4", "Coral_36", "Shell Shard Fossil Beast", SourceKind.BreakableHolder, 19.77f, 48.58f, 70f, 58f), new Entry("Shell Shard Cache: Blasted Steps #5", "Coral_36", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 29.62321f, 51.727047f, 70f, 58f), new Entry("Shell Shard Cache: Bone Bottom", "Bonetown", "Shell Shard Fossil Big", SourceKind.BreakableHolder, 215.85423f, 9.2123f, 315f, 90f), new Entry("Shell Shard Cache: Choral Chambers", "Song_01", "Song Shard Barrel", SourceKind.BreakableHolder, 71.1f, 110.02f, 162f, 150f), new Entry("Shell Shard Cache: Deep Docks #1", "Bone_East_13", "Shell Shard Fossil Dock (2)", SourceKind.BreakableHolder, 74.1f, 4.65f, 128f, 35f), new Entry("Shell Shard Cache: Deep Docks #2", "Bone_East_13", "Shell Shard Fossil Dock (3)", SourceKind.BreakableHolder, 76.28f, 4.52f, 128f, 35f), new Entry("Shell Shard Cache: Deep Docks #3", "Bone_East_13", "Shell Shard Fossil Dock (1)", SourceKind.BreakableHolder, 90.98f, 4.69f, 128f, 35f), new Entry("Shell Shard Cache: Deep Docks #4", "Dock_01", "Song Shard Barrel", SourceKind.BreakableHolder, 30.52f, 36.04f, 35f, 84f), new Entry("Shell Shard Cache: Deep Docks #5", "Dock_02", "Song Shard Barrel", SourceKind.BreakableHolder, 119.95f, 79.03362f, 125f, 101f), new Entry("Shell Shard Cache: Deep Docks #6", "Dock_02b", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 26.45f, 36.73f, 94f, 120f), new Entry("Shell Shard Cache: Deep Docks #7", "Dock_02b", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 27.77f, 37.51f, 94f, 120f), new Entry("Shell Shard Cache: Deep Docks #8", "Dock_02b", "Song Shard Barrel", SourceKind.BreakableHolder, 31.94f, 32.179817f, 94f, 120f), new Entry("Shell Shard Cache: Deep Docks #9", "Dock_02b", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 32.59f, 37.35f, 94f, 120f), new Entry("Shell Shard Cache: Deep Docks #10", "Room_Forge", "Song Shard Barrel", SourceKind.BreakableHolder, 4.53f, 20.03315f, 140f, 69f), new Entry("Shell Shard Cache: Far Fields #1", "Bone_East_02b", "Shell Shard Fossil Mid", SourceKind.BreakableHolder, 293.44f, 19.04f, 330f, 35f), new Entry("Shell Shard Cache: Far Fields #2", "Bone_East_18", "ant_shell_shard_string (1)", SourceKind.BreakableHolder, 135.04f, 38.87f, 178f, 62f), new Entry("Shell Shard Cache: Far Fields #3", "Bone_East_18", "ant_shell_shard_string", SourceKind.BreakableHolder, 137.98f, 38.89f, 178f, 62f), new Entry("Shell Shard Cache: Far Fields #4", "Bone_East_24", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 16.931171f, 19.613482f, 263f, 76f), new Entry("Shell Shard Cache: Far Fields #5", "Bone_East_24", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 17.44f, 22.29f, 263f, 76f), new Entry("Shell Shard Cache: Far Fields #6", "Bone_East_24", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 107.39f, 51.3f, 263f, 76f), new Entry("Shell Shard Cache: Far Fields #7", "Bone_East_24", "Shell Shard Fossil Mid Variant", SourceKind.BreakableHolder, 194.63f, 8.51f, 263f, 76f), new Entry("Shell Shard Cache: Far Fields #8", "Bone_East_LavaChallenge", "Shell Shard Fossil Mid", SourceKind.BreakableHolder, 14.64f, 193.01f, 29f, 289f), new Entry("Shell Shard Cache: Grand Gate", "Under_27", "Song Shard Barrel", SourceKind.BreakableHolder, 16.53f, 28.13f, 130f, 90f), new Entry("Shell Shard Cache: Greymoor #1", "Greymoor_16", "Shell Shard Fossil Tiny Egg (1)", SourceKind.BreakableHolder, 90.17999f, 13.09f, 171f, 60f), new Entry("Shell Shard Cache: Greymoor #2", "Greymoor_16", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 91.78f, 14.560001f, 171f, 60f), new Entry("Shell Shard Cache: Greymoor #3", "Greymoor_17", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 11.378024f, 29.101122f, 57f, 65f), new Entry("Shell Shard Cache: Greymoor #4", "Greymoor_17", "Shell Shard Fossil Tiny Egg (3)", SourceKind.BreakableHolder, 19.560001f, 24.63f, 57f, 65f), new Entry("Shell Shard Cache: Greymoor #5", "Greymoor_17", "Shell Shard Fossil Tiny Front (1)", SourceKind.BreakableHolder, 21.199888f, 24.289999f, 57f, 65f), new Entry("Shell Shard Cache: Greymoor #6", "Greymoor_17", "Shell Shard Fossil Tiny Egg (1)", SourceKind.BreakableHolder, 21.630001f, 35.32f, 57f, 65f), new Entry("Shell Shard Cache: Greymoor #7", "Greymoor_17", "Shell Shard Fossil Tiny Front (2)", SourceKind.BreakableHolder, 23.220001f, 30.609999f, 57f, 65f), new Entry("Shell Shard Cache: High Halls #1", "Hang_09", "Song Shard Under Chest", SourceKind.BreakableHolder, 105.1f, 16.729286f, 114f, 48f), new Entry("Shell Shard Cache: High Halls #2", "Hang_15", "Song Shard Under Chest", SourceKind.BreakableHolder, 70.131f, 13.188f, 93f, 48f), new Entry("Shell Shard Cache: Hunter's March #1", "Ant_02", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 71.51f, 16.82f, 130f, 25f), new Entry("Shell Shard Cache: Hunter's March #2", "Ant_02", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 72.9f, 15.04f, 130f, 25f), new Entry("Shell Shard Cache: Hunter's March #3", "Ant_04", "ant_shell_shard_string (2)", SourceKind.BreakableHolder, 333.02008f, 21.040003f, 384f, 34f), new Entry("Shell Shard Cache: Hunter's March #4", "Ant_04", "ant_shell_shard_string (1)", SourceKind.BreakableHolder, 335.24005f, 20.970007f, 384f, 34f), new Entry("Shell Shard Cache: Hunter's March #5", "Ant_Merchant", "ant_shell_shard_string", SourceKind.BreakableHolder, 132.2f, 17.75f, 152f, 30f), new Entry("Shell Shard Cache: Hunter's March #6", "Ant_Merchant", "ant_shell_shard_string (2)", SourceKind.BreakableHolder, 132.76f, 10.37f, 152f, 30f), new Entry("Shell Shard Cache: Hunter's March #7", "Ant_Merchant", "ant_shell_shard_string (1)", SourceKind.BreakableHolder, 134.69f, 17.8f, 152f, 30f), new Entry("Shell Shard Cache: Hunter's March #8", "Ant_Merchant", "ant_shell_shard_string (3)", SourceKind.BreakableHolder, 141.1f, 17.63f, 152f, 30f), new Entry("Shell Shard Cache: Memorium #1", "Arborium_06", "Shell Shard Fossil Coral Conch", SourceKind.BreakableHolder, 5.69f, 18.36f, 132f, 29f), new Entry("Shell Shard Cache: Memorium #2", "Arborium_06", "Shell Shard Fossil Coral Conch (1)", SourceKind.BreakableHolder, 70.75f, 25.04f, 132f, 29f), new Entry("Shell Shard Cache: Moss Grotto #1", "Tut_01", "Shell Shard Fossil Large Uni", SourceKind.BreakableHolder, 79.61f, 9.84f, 120f, 120f), new Entry("Shell Shard Cache: Moss Grotto #2", "Tut_01b", "Shell Shard Fossil Mid (1)", SourceKind.BreakableHolder, 86.48f, 44.112625f, 160f, 99f), new Entry("Shell Shard Cache: Moss Grotto #3", "Tut_01b", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 144.45f, 51.45f, 160f, 99f), new Entry("Shell Shard Cache: Moss Grotto #4", "Tut_01b", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 145.3f, 54.6f, 160f, 99f), new Entry("Shell Shard Cache: Moss Grotto #5", "Tut_02", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 13.32f, 37.31f, 150f, 59f), new Entry("Shell Shard Cache: Moss Grotto #6", "Tut_02", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 14.12f, 32.42f, 150f, 59f), new Entry("Shell Shard Cache: Moss Grotto #7", "Tut_02", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 14.71f, 36.06f, 150f, 59f), new Entry("Shell Shard Cache: Mount Fay #1", "Peak_01", "Shell Shard Fossil Tiny Bumpy (1)", SourceKind.BreakableHolder, 94.56f, 110.33f, 100f, 300f), new Entry("Shell Shard Cache: Mount Fay #2", "Peak_01", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 94.58f, 112.35f, 100f, 300f), new Entry("Shell Shard Cache: Mount Fay #3", "Peak_06", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 8.89f, 44.17f, 51f, 233f), new Entry("Shell Shard Cache: Mount Fay #4", "Peak_06", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 10.68f, 45.42f, 51f, 233f), new Entry("Shell Shard Cache: Mount Fay #5", "Peak_07", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 30.35f, 54.58f, 115f, 150f), new Entry("Shell Shard Cache: Mount Fay #6", "Peak_07", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 32.15f, 51.63f, 115f, 150f), new Entry("Shell Shard Cache: Mount Fay #7", "Peak_07", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 33.16f, 49.15f, 115f, 150f), new Entry("Shell Shard Cache: Putrified Ducts #1", "Aqueduct_01", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 212.43f, 16.92f, 265f, 42f), new Entry("Shell Shard Cache: Putrified Ducts #2", "Aqueduct_01", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 212.63f, 19.64f, 265f, 42f), new Entry("Shell Shard Cache: Putrified Ducts #3", "Aqueduct_04", "Shell Shard Fossil Tiny Egg (2)", SourceKind.BreakableHolder, 12.67f, 15.02f, 185f, 60f), new Entry("Shell Shard Cache: Putrified Ducts #4", "Aqueduct_04", "Shell Shard Fossil Tiny Egg (1)", SourceKind.BreakableHolder, 15.72f, 17.12f, 185f, 60f), new Entry("Shell Shard Cache: Putrified Ducts #5", "Aqueduct_04", "Shell Shard Fossil Tiny Front (1)", SourceKind.BreakableHolder, 17.75f, 16.96f, 185f, 60f), new Entry("Shell Shard Cache: Putrified Ducts #6", "Aqueduct_04", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 75.24f, 55.73f, 185f, 60f), new Entry("Shell Shard Cache: Putrified Ducts #7", "Aqueduct_04", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 79.24f, 54.3f, 185f, 60f), new Entry("Shell Shard Cache: Putrified Ducts #8", "Aqueduct_05", "Shell Shard Fossil Dustpen Tall (2)", SourceKind.BreakableHolder, 207.33f, 34.23f, 332f, 100f), new Entry("Shell Shard Cache: Putrified Ducts #9", "Aqueduct_05", "Shell Shard Fossil Dustpen Tall (1)", SourceKind.BreakableHolder, 252.75f, 38.96f, 332f, 100f), new Entry("Shell Shard Cache: Putrified Ducts #10", "Aqueduct_05", "Shell Shard Fossil Dustpen Short", SourceKind.BreakableHolder, 254.16f, 39.6f, 332f, 100f), new Entry("Shell Shard Cache: Putrified Ducts #11", "Aqueduct_05", "Shell Shard Fossil Dustpen Tall", SourceKind.BreakableHolder, 258.83585f, 40.15375f, 332f, 100f), new Entry("Shell Shard Cache: Sands of Karak #1", "Coral_24", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 124.52f, 23.06f, 183f, 55f), new Entry("Shell Shard Cache: Sands of Karak #2", "Coral_24", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 126.8f, 23.46f, 183f, 55f), new Entry("Shell Shard Cache: Sands of Karak #3", "Coral_27", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 57.259903f, 26.78f, 202f, 46f), new Entry("Shell Shard Cache: Sands of Karak #4", "Coral_27", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 59.120003f, 25.4f, 202f, 46f), new Entry("Shell Shard Cache: Sands of Karak #5", "Coral_38", "Shell Shard Fossil Tiny Front (2)", SourceKind.BreakableHolder, 58.18f, 48.18f, 131f, 95f), new Entry("Shell Shard Cache: Sands of Karak #6", "Coral_38", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 60.16f, 48.54f, 131f, 95f), new Entry("Shell Shard Cache: Sands of Karak #7", "Coral_38", "Shell Shard Fossil Tiny Front (1)", SourceKind.BreakableHolder, 60.38f, 40.160004f, 131f, 95f), new Entry("Shell Shard Cache: Sands of Karak #8", "Coral_38", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 66.86f, 44.500004f, 131f, 95f), new Entry("Shell Shard Cache: Sands of Karak #9", "Coral_40", "Shell Shard Fossil Coral Conch", SourceKind.BreakableHolder, 16.355982f, 20.88f, 60f, 24f), new Entry("Shell Shard Cache: Sands of Karak #10", "Coral_40", "Shell Shard Fossil Coral Conch (1)", SourceKind.BreakableHolder, 19.225977f, 20.28f, 60f, 24f), new Entry("Shell Shard Cache: Sands of Karak #11", "Coral_41", "Shell Shard Fossil Coral Conch", SourceKind.BreakableHolder, 142.09305f, 59.111862f, 153f, 68f), new Entry("Shell Shard Cache: Shellwood #1", "Mosstown_03", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 6.55f, 86.61f, 34f, 146f), new Entry("Shell Shard Cache: Shellwood #2", "Mosstown_03", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 8.68f, 86.46f, 34f, 146f), new Entry("Shell Shard Cache: Shellwood #3", "Mosstown_03", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 11.47f, 86.82f, 34f, 146f), new Entry("Shell Shard Cache: Shellwood #4", "Shellwood_01", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 106.61f, 23.27f, 132f, 98f), new Entry("Shell Shard Cache: Shellwood #5", "Shellwood_01", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 108.26f, 23.35f, 132f, 98f), new Entry("Shell Shard Cache: Shellwood #6", "Shellwood_01", "Shell Shard Fossil Tiny Bumpy (1)", SourceKind.BreakableHolder, 111.61f, 26.21f, 132f, 98f), new Entry("Shell Shard Cache: Shellwood #7", "Shellwood_11", "Shell Shard Fossil Tiny Bumpy (2)", SourceKind.BreakableHolder, 77.46f, 68.03f, 102f, 78f), new Entry("Shell Shard Cache: Shellwood #8", "Shellwood_11", "Shell Shard Fossil Mid", SourceKind.BreakableHolder, 77.59f, 72.09f, 102f, 78f), new Entry("Shell Shard Cache: Shellwood #9", "Shellwood_11", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 79.27f, 75.91f, 102f, 78f), new Entry("Shell Shard Cache: Shellwood #10", "Shellwood_11", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 81.94f, 72.69f, 102f, 78f), new Entry("Shell Shard Cache: Shellwood #11", "Shellwood_11", "Shell Shard Fossil Tiny Bumpy (1)", SourceKind.BreakableHolder, 82.84f, 69.88f, 102f, 78f), new Entry("Shell Shard Cache: Shellwood #12", "Shellwood_25", "Shell Shard Fossil Tiny Egg (1)", SourceKind.BreakableHolder, 31.46f, 11.654746f, 290f, 40f), new Entry("Shell Shard Cache: Sinner's Road #1", "Dust_03", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 96.778656f, 28.95f, 145f, 40f), new Entry("Shell Shard Cache: Sinner's Road #2", "Dust_03", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 102.44f, 30.94f, 145f, 40f), new Entry("Shell Shard Cache: Sinner's Road #3", "Dust_03", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 105.06f, 29.3f, 145f, 40f), new Entry("Shell Shard Cache: Sinner's Road #4", "Dust_04", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 77.01f, 55.99f, 110f, 90f), new Entry("Shell Shard Cache: Sinner's Road #5", "Dust_04", "Shell Shard Fossil Tiny Bumpy (1)", SourceKind.BreakableHolder, 78.54f, 55.03f, 110f, 90f), new Entry("Shell Shard Cache: Sinner's Road #6", "Dust_05", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 17.76f, 10.06f, 352f, 31f), new Entry("Shell Shard Cache: Sinner's Road #7", "Dust_05", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 19.78f, 10.09f, 352f, 31f), new Entry("Shell Shard Cache: The Abyss #1", "Abyss_03", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 70.94f, 13.29f, 82f, 85f), new Entry("Shell Shard Cache: The Abyss #2", "Abyss_03", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 73.14f, 14.68f, 82f, 85f), new Entry("Shell Shard Cache: The Abyss #3", "Abyss_03", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 75.19f, 8.53f, 82f, 85f), new Entry("Shell Shard Cache: The Abyss #4", "Abyss_03", "Shell Shard Fossil Tiny Front (1)", SourceKind.BreakableHolder, 76.95f, 13.9f, 82f, 85f), new Entry("Shell Shard Cache: The Abyss #5", "Abyss_05", "Shell Shard Fossil Mid Variant", SourceKind.BreakableHolder, 51.604824f, 55.812305f, 190f, 105f), new Entry("Shell Shard Cache: The Abyss #6", "Abyss_05", "Shell Shard Fossil Tiny Egg (1)", SourceKind.BreakableHolder, 56.523586f, 58.39f, 190f, 105f), new Entry("Shell Shard Cache: The Abyss #7", "Abyss_05", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 137.09f, 51.16f, 190f, 105f), new Entry("Shell Shard Cache: The Abyss #8", "Abyss_05", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 138.95f, 49.77f, 190f, 105f), new Entry("Shell Shard Cache: The Abyss #9", "Abyss_05", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 140.42f, 47.74f, 190f, 105f), new Entry("Shell Shard Cache: The Cradle", "Cradle_03", "Shell Shard Fossil Mid Variant", SourceKind.BreakableHolder, 74.3f, 84.06f, 80f, 160f), new Entry("Shell Shard Cache: The Marrow #1", "Bone_01", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 48.2f, 74.93f, 130f, 91f), new Entry("Shell Shard Cache: The Marrow #2", "Bone_04", "Shell Shard Fossil Tiny Front (2)", SourceKind.BreakableHolder, 46.7f, 8.48f, 233f, 31f), new Entry("Shell Shard Cache: The Marrow #3", "Bone_04", "Shell Shard Fossil Tiny Front (3)", SourceKind.BreakableHolder, 48.49f, 7.64f, 233f, 31f), new Entry("Shell Shard Cache: The Marrow #4", "Bone_07", "Shell Shard Fossil Large Uni", SourceKind.BreakableHolder, 49.37246f, 40.99614f, 92f, 74f), new Entry("Shell Shard Cache: The Marrow #5", "Bone_14", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 101.7f, 35.16f, 135f, 38f), new Entry("Shell Shard Cache: The Marrow #6", "Bone_14", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 103.38f, 35.59f, 135f, 38f), new Entry("Shell Shard Cache: The Slab #1", "Slab_05", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 49.77f, 33.01f, 100f, 36f), new Entry("Shell Shard Cache: The Slab #2", "Slab_05", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 52.3f, 31.74f, 100f, 36f), new Entry("Shell Shard Cache: The Slab #3", "Slab_05", "Shell Shard Fossil Tiny Front (1)", SourceKind.BreakableHolder, 55.77f, 28.51f, 100f, 36f), new Entry("Shell Shard Cache: The Slab #4", "Slab_12", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 23.47f, 16.07f, 110f, 40f), new Entry("Shell Shard Cache: The Slab #5", "Slab_12", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 24.669224f, 18.38f, 110f, 40f), new Entry("Shell Shard Cache: The Slab #6", "Slab_18", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 82.24f, 70.31f, 100f, 76f), new Entry("Shell Shard Cache: The Slab #7", "Slab_18", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 82.9f, 72.41f, 100f, 76f), new Entry("Shell Shard Cache: Underworks #1", "Library_11b", "Song Shard Barrel (1)", SourceKind.BreakableHolder, 24.38f, 88.99f, 115f, 158f), new Entry("Shell Shard Cache: Underworks #2", "Library_11b", "Song Shard Barrel", SourceKind.BreakableHolder, 27.66f, 88.99f, 115f, 158f), new Entry("Shell Shard Cache: Underworks #3", "Library_11b", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 29.89f, 139.02f, 115f, 158f), new Entry("Shell Shard Cache: Underworks #4", "Library_11b", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 39.26f, 144.99f, 115f, 158f), new Entry("Shell Shard Cache: Underworks #5", "Library_11b", "Shell Shard Fossil Tiny Egg (2)", SourceKind.BreakableHolder, 41.77f, 143.55f, 115f, 158f), new Entry("Shell Shard Cache: Underworks #6", "Library_11b", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 41.91f, 141.16f, 115f, 158f), new Entry("Shell Shard Cache: Underworks #7", "Library_12", "Shell Shard Fossil Dustpen Short", SourceKind.BreakableHolder, 39.49f, 13.48f, 136f, 116f), new Entry("Shell Shard Cache: Underworks #8", "Library_12", "Shell Shard Fossil Dustpen Tall", SourceKind.BreakableHolder, 40.88f, 13.48f, 136f, 116f), new Entry("Shell Shard Cache: Underworks #9", "Library_12", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 44.81f, 104.31f, 136f, 116f), new Entry("Shell Shard Cache: Underworks #10", "Library_12", "Shell Shard Fossil Tiny Front", SourceKind.BreakableHolder, 47.19f, 105.13f, 136f, 116f), new Entry("Shell Shard Cache: Underworks #11", "Library_12b", "Song Shard Under Chest", SourceKind.BreakableHolder, 163.79f, 17.036383f, 202f, 100f), new Entry("Shell Shard Cache: Underworks #12", "Under_03b", "Song Shard Under Chest", SourceKind.BreakableHolder, 9.86f, 14.01f, 95f, 32f), new Entry("Shell Shard Cache: Underworks #13", "Under_04", "Song Shard Under Chest", SourceKind.BreakableHolder, 7.889386f, 21.999416f, 120f, 32f), new Entry("Shell Shard Cache: Underworks #14", "Under_07c", "Song Shard Under Chest (1)", SourceKind.BreakableHolder, 46.47f, 63.72f, 85f, 106f), new Entry("Shell Shard Cache: Underworks #15", "Under_08", "Song Shard Under Chest", SourceKind.BreakableHolder, 101.27f, 28.85464f, 135f, 58f), new Entry("Shell Shard Cache: Underworks #16", "Under_17", "Song Shard Under Chest", SourceKind.BreakableHolder, 55.77f, 3.32f, 164f, 48f), new Entry("Shell Shard Cache: Whispering Vaults #1", "Library_07", "Song Shard Under Chest", SourceKind.BreakableHolder, 55.5f, 126.61f, 79f, 172f), new Entry("Shell Shard Cache: Whispering Vaults #2", "Library_13", "Song Shard Barrel", SourceKind.BreakableHolder, 24.47f, 29.17f, 124f, 56f), new Entry("Shell Shard Cache: Wisp Thicket #1", "Wisp_09", "Shell Shard Fossil Tiny Egg (2)", SourceKind.BreakableHolder, 4.02f, 20.59f, 100f, 39f), new Entry("Shell Shard Cache: Wisp Thicket #2", "Wisp_09", "Shell Shard Fossil Tiny Egg (3)", SourceKind.BreakableHolder, 4.63f, 24.14f, 100f, 39f), new Entry("Shell Shard Cache: Wisp Thicket #3", "Wisp_09", "Shell Shard Fossil Tiny Bumpy (1)", SourceKind.BreakableHolder, 7.75f, 18.61f, 100f, 39f), new Entry("Shell Shard Cache: Wisp Thicket #4", "Wisp_09", "Shell Shard Fossil Mid Variant", SourceKind.BreakableHolder, 10.6f, 24.85f, 100f, 39f), new Entry("Shell Shard Cache: Wisp Thicket #5", "Wisp_09", "Shell Shard Fossil Tiny Egg (1)", SourceKind.BreakableHolder, 16.03f, 24.29f, 100f, 39f), new Entry("Shell Shard Cache: Wisp Thicket #6", "Wisp_09", "Shell Shard Fossil Tiny Egg", SourceKind.BreakableHolder, 72.77f, 5.4f, 100f, 39f), new Entry("Shell Shard Cache: Wisp Thicket #7", "Wisp_09", "Shell Shard Fossil Tiny Bumpy", SourceKind.BreakableHolder, 72.79f, 7.62f, 100f, 39f), new Entry("Rosary Cache: Deep Docks #7", "Dock_01", "rosary_string_small_half", SourceKind.RosaryCache, 7.523623f, 28.748386f, 35f, 84f), new Entry("Rosary Cache: Deep Docks #8", "Dock_01", "rosary_string_medium", SourceKind.RosaryCache, 8.45f, 27.86f, 35f, 84f) }; internal static Location[] AppendTo(IEnumerable existing) { return existing.Concat(Entries.Select((Entry entry) => new Location(entry.LocationName, ItemType.Resource, null))).ToArray(); } } internal static class MinorPickupManifest { internal sealed class Entry { internal readonly string LocationName; internal readonly string SceneName; internal readonly string AssetName; internal readonly float X; internal readonly float Y; internal readonly string HierarchyPath; internal readonly ItemType Type; internal Entry(string locationName, string sceneName, string assetName, float x, float y, string hierarchyPath = "", ItemType type = ItemType.Resource) { LocationName = locationName; SceneName = sceneName; AssetName = assetName; X = x; Y = y; HierarchyPath = hierarchyPath ?? string.Empty; Type = type; } } internal static readonly Entry[] Entries = new Entry[72] { new Entry("Frayed Rosary String: Putrified Ducts", "aqueduct_01", "Rosary_Set_Frayed", 256.19f, 14.67f), new Entry("Rosary Necklace: Fleatopia", "aqueduct_05_festival", "Rosary_Set_Medium", 152.783f, 16.2396f), new Entry("Rosary String: Fleatopia", "aqueduct_05_festival", "Rosary_Set_Small", 154.043f, 15.9796f), new Entry("Shard Bundle: Memorium", "arborium_11", "Shard Pouch", 211.22f, 10.27f), new Entry("Frayed Rosary String: The Marrow (Flea Caravan Passage)", "bone_10", "Rosary_Set_Frayed", 98.9849f, 41.3206f), new Entry("Frayed Rosary String: Deep Docks", "bone_east_04b", "Rosary_Set_Frayed", 6.462f, 87.1857f), new Entry("Rosary String: Far Fields", "bone_east_14", "Rosary_Set_Small", 13.82f, 7.461f), new Entry("Pristine Core: Cogwork Core", "cog_07", "Pristine Core", 0f, 0f, "Battle Scene Test/Battle Scene/Wave 2 - Item/Item Placer/Collectable Item Pickup"), new Entry("Shard Bundle: Cogwork Core", "cog_10", "Shard Pouch", 6.78f, 44.67f), new Entry("Frayed Rosary String: Blasted Steps", "coral_03", "Rosary_Set_Frayed", 38.64f, 5.53f), new Entry("Beast Shard: Blasted Steps", "coral_36", "Great Shard", 20.0542f, 48.5738f), new Entry("Frayed Rosary String: Wormways", "crawl_02", "Rosary_Set_Frayed", 3.14f, 142.3655f), new Entry("Shard Bundle: Deep Docks #1", "dock_02", "Shard Pouch", 38.79f, 3.5175f), new Entry("Beast Shard: Deep Docks", "dock_11", "Great Shard", 100.63f, 6.28f), new Entry("Frayed Rosary String: Sinner's Road", "dust_01", "Rosary_Set_Frayed", 76.43f, 2.7178f), new Entry("Shard Bundle: Sinner's Road", "dust_06", "Shard Pouch", 26.11f, 96.25f), new Entry("Shard Bundle: Greymoor #1", "greymoor_05", "Shard Pouch", 59.01f, 62.74f), new Entry("Shard Bundle: Greymoor #2", "greymoor_12", "Shard Pouch", 61.29f, 14.4f), new Entry("Frayed Rosary String: Greymoor #1", "greymoor_15", "Rosary_Set_Frayed", 64.41f, 24.21f), new Entry("Frayed Rosary String: Greymoor #2", "greymoor_15b", "Rosary_Set_Frayed", 91.86f, 35.26f), new Entry("Pale Rosary Necklace: High Halls", "hang_06_bank", "Rosary_Set_Huge_White", 73.41f, 35.3895f), new Entry("Frayed Rosary String: High Halls", "hang_16", "Rosary_Set_Frayed", 68.15f, 6.38f), new Entry("Heavy Rosary Necklace: Whispering Vaults", "library_02", "Rosary_Set_Large", 76.437f, 48.02f), new Entry("Heavy Rosary Necklace: Songclave", "library_09", "Rosary_Set_Large", 14.08f, 36.42f), new Entry("Shard Bundle: Whispering Vaults", "library_12", "Shard Pouch", 126.39f, 24.4143f), new Entry("Frayed Rosary String: Bone Bottom (Silkspear Passage)", "mosstown_02", "Rosary_Set_Frayed", 35.32f, 40.27f), new Entry("Shard Bundle: Deep Docks #2", "room_forge", "Shard Pouch", 5.08f, 37.38f), new Entry("Frayed Rosary String: Bilewater", "shadow_02", "Rosary_Set_Frayed", 49.21f, 161.48f), new Entry("Frayed Rosary String: Shellwood", "shellwood_01", "Rosary_Set_Frayed", 48.99f, 23.27f), new Entry("Rosary String: Shellwood #1", "shellwood_01b", "Rosary_Set_Small", 33.13f, 72.38f), new Entry("Shard Bundle: Shellwood", "shellwood_13", "Shard Pouch", 82.2928f, 67.2101f), new Entry("Rosary String: Shellwood #2", "shellwood_25", "Rosary_Set_Small", 83.3f, 25.4172f), new Entry("Frayed Rosary String: The Slab #1", "slab_02", "Rosary_Set_Frayed", 53.4572f, 21.2107f), new Entry("Shard Bundle: The Slab", "slab_04", "Shard Pouch", 9.4863f, 15.6907f), new Entry("Frayed Rosary String: The Slab #2", "slab_18", "Rosary_Set_Frayed", 17.2344f, 21.2003f), new Entry("Frayed Rosary String: The Slab #3", "slab_22", "Rosary_Set_Frayed", 28.3f, 30.39f), new Entry("Heavy Rosary Necklace: Choral Chambers", "song_04", "Rosary_Set_Large", 121.49f, 37.57f), new Entry("Rosary Necklace: Choral Chambers #1", "song_07", "Rosary_Set_Medium", 3.83f, 4.91f), new Entry("Rosary Necklace: Choral Chambers #2", "song_09", "Rosary_Set_Medium", 39.38f, 8.2749f), new Entry("Frayed Rosary String: Moss Grotto", "tut_01", "Rosary_Set_Frayed", 43.96f, 20.43f), new Entry("Shard Bundle: Underworks #1", "under_03", "Shard Pouch", 4.5876f, 6.2287f), new Entry("Frayed Rosary String: Underworks #1", "under_07c", "Rosary_Set_Frayed", 75.57f, 76.08f), new Entry("Frayed Rosary String: Underworks #2", "under_12", "Rosary_Set_Frayed", 33.13f, 10.29f), new Entry("Pristine Core: Underworks", "under_17", "Pristine Core", 75.44f, 23.37f), new Entry("Shard Bundle: Underworks #2", "under_18", "Shard Pouch", 34.14f, 34.3f), new Entry("Rosary Necklace: Wisp Thicket", "wisp_02", "Rosary_Set_Medium", 111.32f, 37.99f), new Entry("Frayed Rosary String: Greymoor #3", "wisp_03", "Rosary_Set_Frayed", 62.89f, 35.37f), new Entry("Simple Key: Roachkeeper", "dust_06", "Simple Key", 6.01f, 179.63f, "Roachkeeper Key Control/Collectable Item SimpleKey", ItemType.SimpleKey), new Entry("Simple Key: Sands of Karak East Bench", "bellshrine_coral", "Simple Key", 28.32f, 16.54f, "", ItemType.SimpleKey), new Entry("Memory Locket: Hunter's March", "ant_20", "Crest Socket Unlocker", 147.39f, 13.79f, "Enemy Break Cage (2)/Corpse/Collectable Item Pickup", ItemType.MemoryLocket), new Entry("Memory Locket: Greymoor", "greymoor_16", "Crest Socket Unlocker", 130.9f, 51.48f, "", ItemType.MemoryLocket), new Entry("Memory Locket: Halfway Home", "halfway_01", "Crest Socket Unlocker", 8.292786f, 13f, "", ItemType.MemoryLocket), new Entry("Memory Locket: Bellhart Roof", "belltown", "Crest Socket Unlocker", 59.168686f, 66.165f, "", ItemType.MemoryLocket), new Entry("Memory Locket: The Marrow", "bone_18", "Crest Socket Unlocker", 38.15f, 20.51f, "", ItemType.MemoryLocket), new Entry("Memory Locket: Choral Chambers", "bellway_city", "Crest Socket Unlocker", 67.57f, 24.29f, "", ItemType.MemoryLocket), new Entry("Memory Locket: Wormways", "crawl_09", "Crest Socket Unlocker", 130.49f, 3.34f, "", ItemType.MemoryLocket), new Entry("Memory Locket: Blasted Steps", "coral_02", "Crest Socket Unlocker", 202.32f, 43.56f, "", ItemType.MemoryLocket), new Entry("Memory Locket: Underworks", "under_08", "Crest Socket Unlocker", 61.07f, 16.47f, "", ItemType.MemoryLocket), new Entry("Memory Locket: Whispering Vaults", "library_08", "Crest Socket Unlocker", 105.34f, 33.48f, "", ItemType.MemoryLocket), new Entry("Memory Locket: Bilewater West", "shadow_20", "Crest Socket Unlocker", 17.713f, 23.86f, "", ItemType.MemoryLocket), new Entry("Memory Locket: Deep Docks", "dock_13", "Crest Socket Unlocker", 15.26f, 3.29f, "", ItemType.MemoryLocket), new Entry("Memory Locket: Bilewater East", "shadow_27", "Crest Socket Unlocker", 195.09f, 11.76f, "", ItemType.MemoryLocket), new Entry("Memory Locket: The Slab", "slab_cell_quiet", "Crest Socket Unlocker", 42.441864f, 30.4f, "", ItemType.MemoryLocket), new Entry("Memory Locket: Memorium", "arborium_05", "Crest Socket Unlocker", 3.95f, 7.27f, "", ItemType.MemoryLocket), new Entry("Memory Locket: Far Fields (Act 3)", "bone_east_25", "Crest Socket Unlocker", 151.95251f, 6.346909f, "", ItemType.MemoryLocket), new Entry("Memory Locket: Sands of Karak", "coral_23", "Crest Socket Unlocker", 90.740005f, 50.4f, "GameObject (3)/Collectable Item Pickup", ItemType.MemoryLocket), new Entry("Craftmetal: The Marrow", "bone_07", "Tool Metal", 41.31f, 4.98f, "Explode Sequence/tool_metal_deposit/Collectable Item Pickup - Tool Metal", ItemType.Craftmetal), new Entry("Craftmetal: Deep Docks", "dock_03", "Tool Metal", 7.7f, 81.89f, "City Shard Chest/Item/Collectable Item Pickup", ItemType.Craftmetal), new Entry("Craftmetal: Blasted Steps", "coral_32", "Tool Metal", 74.51f, 9.06f, "tool_metal_deposit/Collectable Item Pickup - Tool Metal", ItemType.Craftmetal), new Entry("Craftmetal: Underworks", "under_19b", "Tool Metal", 7.57f, 7.18f, "tool_metal_deposit/Collectable Item Pickup - Tool Metal", ItemType.Craftmetal), new Entry("Craftmetal: Putrified Ducts", "aqueduct_05", "Tool Metal", 328.9f, 16.35f, "tool_metal_deposit/Collectable Item Pickup - Tool Metal", ItemType.Craftmetal), new Entry("Craftmetal: Wisp Thicket", "wisp_05", "Tool Metal", 46.40615f, 59.93595f, "sc_cart_plat/Art/tool_metal_deposit/Collectable Item Pickup - Tool Metal", ItemType.Craftmetal) }; internal static Location[] AppendTo(IEnumerable existing) { return existing.Concat(Entries.Select((Entry entry) => new Location(entry.LocationName, entry.Type, (entry.Type == ItemType.Craftmetal) ? ((Func)(() => IsCraftmetalCollected(entry))) : null))).ToArray(); } private static bool IsCraftmetalCollected(Entry entry) { if (entry == null || string.IsNullOrEmpty(entry.SceneName) || string.IsNullOrEmpty(entry.HierarchyPath)) { return false; } SceneData instance = SceneData.instance; if (instance == null) { return false; } int num = entry.HierarchyPath.LastIndexOf('/'); string text = ((num >= 0) ? entry.HierarchyPath.Substring(num + 1) : entry.HierarchyPath); string text2 = char.ToUpperInvariant(entry.SceneName[0]) + entry.SceneName.Substring(1); return instance.PersistentBools.GetValueOrDefault(text2, text); } } internal static class NakedTrapManager { internal const float DurationSeconds = 120f; private const string CloaklessCrestName = "Cloakless"; private static bool pending; private static bool active; private static bool suspendedForSave; private static bool internalCrestWrite; private static float deadline; private static float pendingDuration = 120f; private static string restoreCrestInternalName = string.Empty; private static string restorePreviousCrestInternalName = string.Empty; private static bool restoreCrestWasTemporary; internal static bool IsActive { get { if (active) { return !suspendedForSave; } return false; } } internal static bool HasState { get { if (!active && !pending) { return suspendedForSave; } return true; } } internal static bool SuppressesCloakAbilities { get { if (IsActive) { return IsNativeCloaklessEquipped(); } return false; } } internal static bool CanProcessReceivedItems(HeroController hero) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Invalid comparison between Unknown and I4 GameManager silentInstance = GameManager.SilentInstance; PlayerData instance = PlayerData.instance; if (IsActive && (Object)(object)hero != (Object)null && (Object)(object)silentInstance != (Object)null && instance != null && (int)silentInstance.GameState == 4 && silentInstance.IsGameplayScene() && !silentInstance.isPaused && !silentInstance.IsLoadingSceneTransition && !silentInstance.IsInSceneTransition && !TransitionPoint.IsTransitionBlocked && !BossSceneController.IsTransitioning && !instance.HasStoredMemoryState && hero.cState != null && !hero.cState.transitioning && !hero.cState.dead && !hero.cState.hazardDeath && !hero.cState.hazardRespawning && !hero.controlReqlinquished && (int)hero.hero_state != 7) { return hero.CanInput(); } return false; } internal static void Trigger() { try { if (active) { deadline = Time.unscaledTime + 120f; return; } LogicAuditCloakManager.Reset(); pending = true; pendingDuration = 120f; TryStartPending(); } catch (Exception exception) { Warn("Naked Trap is waiting for a safe crest state", exception); } } internal static void Update() { PlayerData instance = PlayerData.instance; if (HasState && instance != null && instance.atBench) { Reset(); return; } if (active && !suspendedForSave) { UpdateActiveEffect(); } if (pending && !active && !suspendedForSave) { TryStartPending(); } } internal static bool TryDeferRandomizerCrestChange(ToolCrest crest, bool markTemporary) { if (!IsActive || internalCrestWrite || (Object)(object)crest == (Object)null) { return false; } string name = crest.name; if (string.IsNullOrEmpty(name) || string.Equals(name, "Cloakless", StringComparison.Ordinal)) { return false; } if (markTemporary) { PrepareForNativeTemporaryCrest(markTemporary: true); return false; } RememberRequestedRestoreCrest(name, markTemporary: false); return true; } internal static void PrepareForNativeTemporaryCrest(bool markTemporary) { if (markTemporary && IsActive) { float num = Math.Max(0f, deadline - Time.unscaledTime); if (TryRestorePhysicalCrest()) { active = false; pending = num > 0f; pendingDuration = num; ClearRestoreSnapshot(); } } } internal static void PrepareForSave() { if (active && !suspendedForSave) { float num = Math.Max(0f, deadline - Time.unscaledTime); if (!TryRestorePhysicalCrest()) { ForceRestoreSnapshotFields(); } active = false; suspendedForSave = num > 0f; pending = suspendedForSave; pendingDuration = num; ClearRestoreSnapshot(); } } internal static void ResumeAfterSave() { if (suspendedForSave) { suspendedForSave = false; TryStartPending(); } } internal static void Reset() { try { if (active && !TryRestorePhysicalCrest()) { ForceRestoreSnapshotFields(); } } catch (Exception exception) { Warn("Naked Trap cleanup failed", exception); ForceRestoreSnapshotFields(); } finally { ClearAllState(); } } private static void UpdateActiveEffect() { PlayerData instance = PlayerData.instance; if (instance == null) { return; } bool flag = string.Equals(instance.CurrentCrestID, "Cloakless", StringComparison.Ordinal); if (!flag) { if (instance.IsAnyCursed || instance.IsCurrentCrestTemp || TrapManager.IsCursedCrestActive) { SuspendForNativeCrestOwner(); return; } RememberRequestedRestoreCrest(instance.CurrentCrestID, markTemporary: false); } if (Time.unscaledTime >= deadline) { FinishEffect(); } else if (!flag) { if (!TryEquipCloakless()) { SuspendForNativeCrestOwner(); } } else { instance.IsCurrentCrestTemp = true; } } private static void TryStartPending() { if (!pending || active || suspendedForSave || TrapManager.IsCursedCrestActive) { return; } PlayerData instance = PlayerData.instance; if (instance == null || instance.IsAnyCursed || instance.IsCurrentCrestTemp || string.IsNullOrEmpty(instance.CurrentCrestID) || string.Equals(instance.CurrentCrestID, "Cloakless", StringComparison.Ordinal)) { return; } ToolCrest crestByName = ToolItemManager.GetCrestByName(instance.CurrentCrestID); ToolCrest cloaklessCrest = GetCloaklessCrest(); if (!((Object)(object)crestByName == (Object)null) && !((Object)(object)cloaklessCrest == (Object)null)) { restoreCrestInternalName = instance.CurrentCrestID; restorePreviousCrestInternalName = instance.PreviousCrestID ?? string.Empty; restoreCrestWasTemporary = instance.IsCurrentCrestTemp; if (!TryEquipCloakless()) { ClearRestoreSnapshot(); return; } active = true; pending = false; deadline = Time.unscaledTime + Math.Max(0f, pendingDuration); pendingDuration = 120f; } } private static bool TryEquipCloakless() { ToolCrest cloaklessCrest = GetCloaklessCrest(); if ((Object)(object)cloaklessCrest == (Object)null) { return false; } try { internalCrestWrite = true; return ToolPatches.SetRandomizerCrest(cloaklessCrest, markTemporary: true) && IsNativeCloaklessEquipped(); } finally { internalCrestWrite = false; } } private static void RememberRequestedRestoreCrest(string crestName, bool markTemporary) { if (!string.IsNullOrEmpty(crestName) && !string.Equals(crestName, "Cloakless", StringComparison.Ordinal) && !string.Equals(crestName, restoreCrestInternalName, StringComparison.Ordinal)) { restorePreviousCrestInternalName = restoreCrestInternalName; restoreCrestInternalName = crestName; restoreCrestWasTemporary = markTemporary; } } private static void FinishEffect() { if (TryRestorePhysicalCrest()) { ClearAllState(); } } private static bool TryRestorePhysicalCrest() { if (string.IsNullOrEmpty(restoreCrestInternalName)) { return true; } PlayerData instance = PlayerData.instance; if (instance == null) { return false; } if (!IsNativeCloaklessEquipped()) { if (!instance.IsCurrentCrestTemp) { return !instance.IsAnyCursed; } return false; } ToolCrest crestByName = ToolItemManager.GetCrestByName(restoreCrestInternalName); if ((Object)(object)crestByName == (Object)null) { return false; } try { internalCrestWrite = true; if (!ToolPatches.SetRandomizerCrest(crestByName, restoreCrestWasTemporary)) { return false; } } finally { internalCrestWrite = false; } instance.PreviousCrestID = restorePreviousCrestInternalName; instance.IsCurrentCrestTemp = restoreCrestWasTemporary; return string.Equals(instance.CurrentCrestID, restoreCrestInternalName, StringComparison.Ordinal); } private static void SuspendForNativeCrestOwner() { float num = Math.Max(0f, deadline - Time.unscaledTime); active = false; pending = num > 0f; pendingDuration = num; ClearRestoreSnapshot(); } private static void ForceRestoreSnapshotFields() { PlayerData instance = PlayerData.instance; if (instance == null || string.IsNullOrEmpty(restoreCrestInternalName) || !IsNativeCloaklessEquipped()) { return; } try { internalCrestWrite = true; ToolPatches.PrepareHeroForCrestChange(); instance.CurrentCrestID = restoreCrestInternalName; instance.PreviousCrestID = restorePreviousCrestInternalName; instance.IsCurrentCrestTemp = restoreCrestWasTemporary; ToolItemManager.RefreshEquippedState(); ToolItemManager.SendEquippedChangedEvent(true); ToolPatches.ResetHeroInputAfterCrestChange(); } catch (Exception exception) { Warn("Naked Trap save fallback could not refresh the crest", exception); } finally { internalCrestWrite = false; } } private static ToolCrest GetCloaklessCrest() { ToolCrest cloaklessCrest = Gameplay.CloaklessCrest; if (!((Object)(object)cloaklessCrest != (Object)null)) { return ToolItemManager.GetCrestByName("Cloakless"); } return cloaklessCrest; } private static bool IsNativeCloaklessEquipped() { PlayerData instance = PlayerData.instance; if (instance != null) { return string.Equals(instance.CurrentCrestID, "Cloakless", StringComparison.Ordinal); } return false; } private static void ClearAllState() { pending = false; active = false; suspendedForSave = false; internalCrestWrite = false; deadline = 0f; pendingDuration = 120f; ClearRestoreSnapshot(); } private static void ClearRestoreSnapshot() { restoreCrestInternalName = string.Empty; restorePreviousCrestInternalName = string.Empty; restoreCrestWasTemporary = false; } private static void Warn(string message, Exception exception = null) { string text = ((exception == null) ? message : (message + ": " + exception.Message)); ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] " + text)); } } } [HarmonyPatch(typeof(ToolItemManager), "AutoEquip", new Type[] { typeof(ToolCrest), typeof(bool), typeof(bool) })] internal static class NakedTrapNativeTemporaryCrestPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(bool markTemp) { NakedTrapManager.PrepareForNativeTemporaryCrest(markTemp); } } [BepInPlugin("moriko.silksong.randomizer", "Randomizer", "0.4.2")] public class RandomizerPlugin : BaseUnityPlugin { private struct QueuedReceivedItem { public int Index; public string Name; public ItemFlags Flags; } private struct QueuedUnlockPopup { public string Text; public ItemFlags Flags; } public const string PluginGuid = "moriko.silksong.randomizer"; public const string PluginName = "Randomizer"; public const string PluginVersion = "0.4.2"; internal static ManualLogSource Log; private const float ConnectionWindowWidth = 460f; private const float ConnectionWindowHeight = 350f; private bool showConnectionGui; private bool connectionGuiDismissed; private bool cursorStateCaptured; private bool previousCursorVisible; private bool isConnecting; private CursorLockMode previousCursorLockState; private Rect connectionWindowRect = new Rect(0f, 0f, 460f, 350f); private string connectionHost = "localhost"; private string connectionPort = "38281"; private string connectionSlot = string.Empty; private string connectionPassword = string.Empty; private ConfigEntry savedConnectionHost; private ConfigEntry savedConnectionPort; private ConfigEntry savedConnectionSlot; private ConfigEntry mapMarkerTooltipFontSize; private string connectionStatus = string.Empty; private volatile bool reopenConnectionGuiRequested; private volatile bool resetTransientEffectsRequested; private Archipelago subscribedArchipelago; private readonly object unlockQueueLock = new object(); private readonly Queue unlockQueue = new Queue(); private readonly object receivedItemQueueLock = new object(); private readonly Queue receivedItemQueue = new Queue(); private readonly object connectionStatusQueueLock = new object(); private readonly Queue connectionStatusQueue = new Queue(); private float delay; private float nextItemRetryTime; private const int MaxBootstrapNoOpItemsPerFrame = 8; public static RandomizerPlugin Instance { get; private set; } public static bool OverrideUnlock { get; set; } = true; public Sprite ArchipelagoIcon { get; private set; } public Sprite FleaIcon { get; private set; } public Sprite FillerIcon { get; private set; } public Sprite UsefulIcon { get; private set; } public Sprite ProgressionIcon { get; private set; } public Sprite TrapIcon { get; private set; } public Sprite MapCheckIcon { get; private set; } public Sprite MapCheckOutlineIcon { get; private set; } public Sprite LogicUnknownIcon { get; private set; } public Sprite LogicUnknownOutlineIcon { get; private set; } internal int MapMarkerTooltipFontSize { get { if (mapMarkerTooltipFontSize != null) { return Mathf.Clamp(mapMarkerTooltipFontSize.Value, 10, 32); } return 14; } } private void Awake() { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown //IL_0086: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; LoadSavedConnectionDetails(); LoadClientDisplaySettings(); DeathLinkManager.Initialize(QueueConnectionStatus); SilkLinkManager.Initialize(QueueConnectionStatus); CurrencyLinkManager.Initialize(QueueConnectionStatus); LoadArchipelagoIcon(); LoadCheckClassificationIcons(); LoadFleaIcon(); try { Harmony val = new Harmony("moriko.silksong.randomizer"); PatchAssemblySafely(val); Log.LogInfo((object)"Randomizer 0.4.2 loaded."); DumpPatchState(val); } catch (Exception arg) { Log.LogError((object)$"Failed to patch: {arg}"); } } private void LoadSavedConnectionDetails() { savedConnectionHost = ((BaseUnityPlugin)this).Config.Bind("Archipelago Connection", "Host", "localhost", "Host used by the last successful Archipelago connection."); savedConnectionPort = ((BaseUnityPlugin)this).Config.Bind("Archipelago Connection", "Port", "38281", "Port used by the last successful Archipelago connection."); savedConnectionSlot = ((BaseUnityPlugin)this).Config.Bind("Archipelago Connection", "Slot", string.Empty, "Slot used by the last successful Archipelago connection."); connectionHost = savedConnectionHost.Value ?? "localhost"; connectionPort = savedConnectionPort.Value ?? "38281"; connectionSlot = savedConnectionSlot.Value ?? string.Empty; } private void LoadClientDisplaySettings() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown mapMarkerTooltipFontSize = ((BaseUnityPlugin)this).Config.Bind("Map Markers", "Tooltip Font Size", 14, new ConfigDescription("Text size used for check names shown while focusing a map marker.", (AcceptableValueBase)(object)new AcceptableValueRange(10, 32), Array.Empty())); } private void SaveSuccessfulConnectionDetails(string host, int port, string slot) { connectionHost = host; connectionPort = port.ToString(CultureInfo.InvariantCulture); connectionSlot = slot; savedConnectionHost.Value = connectionHost; savedConnectionPort.Value = connectionPort; savedConnectionSlot.Value = connectionSlot; ((BaseUnityPlugin)this).Config.Save(); } private void LoadArchipelagoIcon() { //IL_00ea: 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) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) string pluginAssetPath = GetPluginAssetPath("ArchipelagoIcon.png"); if (!File.Exists(pluginAssetPath)) { ArchipelagoIcon = CreateFallbackIcon(new Color(0.16f, 0.62f, 0.9f, 1f)); Log.LogInfo((object)"[RANDOMIZER] ArchipelagoIcon.png not found; using a generated fallback icon."); return; } try { byte[] array = File.ReadAllBytes(pluginAssetPath); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val, array)) { ArchipelagoIcon = CreateFallbackIcon(new Color(0.16f, 0.62f, 0.9f, 1f)); Log.LogWarning((object)"[RANDOMIZER] Failed to load ArchipelagoIcon.png; using a generated fallback icon."); } else { ArchipelagoIcon = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f)); } } catch (Exception ex) { ArchipelagoIcon = CreateFallbackIcon(new Color(0.16f, 0.62f, 0.9f, 1f)); Log.LogWarning((object)("[RANDOMIZER] Failed to load ArchipelagoIcon.png; using a generated fallback icon: " + ex)); } } private static void PatchAssemblySafely(Harmony harmony) { Type[] array = (from type2 in AccessTools.GetTypesFromAssembly(Assembly.GetExecutingAssembly()) where type2.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Length != 0 select type2).OrderBy((Type type2) => type2.FullName, StringComparer.Ordinal).ToArray(); int num = 0; int num2 = 0; Type[] array2 = array; foreach (Type type in array2) { try { harmony.CreateClassProcessor(type).Patch(); num++; } catch (Exception ex) { num2++; ManualLogSource log = Log; if (log != null) { log.LogError((object)("[RANDOMIZER] Harmony patch group failed: " + type.FullName + ": " + ex)); } } } if (num2 > 0) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogError((object)("[RANDOMIZER] Loaded " + num + " Harmony patch groups, but " + num2 + " failed. Features from the failed groups are disabled; remaining patch groups were still applied.")); } } else { ManualLogSource log3 = Log; if (log3 != null) { log3.LogInfo((object)("[RANDOMIZER] Loaded all " + num + " Harmony patch groups.")); } } } private void LoadFleaIcon() { //IL_00ea: 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) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) string pluginAssetPath = GetPluginAssetPath("ArchipelagoFleaIcon.png"); if (!File.Exists(pluginAssetPath)) { FleaIcon = CreateFallbackIcon(new Color(0.96f, 0.69f, 0.2f, 1f)); Log.LogInfo((object)"[RANDOMIZER] ArchipelagoFleaIcon.png not found; using a generated fallback icon."); return; } try { byte[] array = File.ReadAllBytes(pluginAssetPath); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val, array)) { FleaIcon = CreateFallbackIcon(new Color(0.96f, 0.69f, 0.2f, 1f)); Log.LogWarning((object)"[RANDOMIZER] Failed to load ArchipelagoFleaIcon.png; using a generated fallback icon."); } else { FleaIcon = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f)); } } catch (Exception ex) { FleaIcon = CreateFallbackIcon(new Color(0.96f, 0.69f, 0.2f, 1f)); Log.LogWarning((object)("[RANDOMIZER] Failed to load ArchipelagoFleaIcon.png; using a generated fallback icon: " + ex)); } } private void LoadCheckClassificationIcons() { FillerIcon = LoadCheckClassificationIcon("Filler.png", ArchipelagoIcon); UsefulIcon = LoadCheckClassificationIcon("Useful.png", ArchipelagoIcon); ProgressionIcon = LoadCheckClassificationIcon("Progression.png", ArchipelagoIcon); TrapIcon = LoadCheckClassificationIcon("Trap.png", ArchipelagoIcon); MapCheckIcon = LoadCheckClassificationIcon("MapCheck.png", ArchipelagoIcon); LogicUnknownIcon = LoadCheckClassificationIcon("LogicUnknown.png", MapCheckIcon ?? ArchipelagoIcon); MapCheckOutlineIcon = CreateWhiteOutlineIcon(MapCheckIcon); LogicUnknownOutlineIcon = CreateWhiteOutlineIcon(LogicUnknownIcon); } private Sprite LoadCheckClassificationIcon(string fileName, Sprite fallback) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) string text = Path.Combine("CheckIcons", fileName); string pluginAssetPath = GetPluginAssetPath(text); if (!File.Exists(pluginAssetPath)) { Log.LogWarning((object)("[RANDOMIZER] " + text + " not found; using the generic AP icon.")); return fallback; } try { byte[] array = File.ReadAllBytes(pluginAssetPath); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val, array)) { Log.LogWarning((object)("[RANDOMIZER] Failed to load " + text + "; using the generic AP icon.")); return fallback; } ((Texture)val).filterMode = (FilterMode)0; return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f)); } catch (Exception ex) { Log.LogWarning((object)("[RANDOMIZER] Failed to load " + text + "; using the generic AP icon: " + ex)); return fallback; } } public Sprite GetItemClassificationIcon(ItemFlags flags) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) if ((flags & 1) != 0) { return ProgressionIcon ?? ArchipelagoIcon; } if ((flags & 2) != 0) { return UsefulIcon ?? ArchipelagoIcon; } if ((flags & 4) != 0) { return TrapIcon ?? ArchipelagoIcon; } if ((int)flags != 0) { return ArchipelagoIcon; } return FillerIcon ?? ArchipelagoIcon; } private static Sprite CreateWhiteOutlineIcon(Sprite source) { //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_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Expected O, but got Unknown //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)source == (Object)null || (Object)(object)source.texture == (Object)null) { return null; } try { Rect rect = source.rect; int num = Mathf.RoundToInt(((Rect)(ref rect)).x); int num2 = Mathf.RoundToInt(((Rect)(ref rect)).y); int num3 = Mathf.RoundToInt(((Rect)(ref rect)).width); int num4 = Mathf.RoundToInt(((Rect)(ref rect)).height); Color[] pixels = source.texture.GetPixels(num, num2, num3, num4); Color[] array = (Color[])(object)new Color[pixels.Length]; int num5 = Mathf.Max(1, Mathf.RoundToInt((float)Mathf.Max(num3, num4) / 24f)); for (int i = 0; i < num4; i++) { for (int j = 0; j < num3; j++) { int num6 = i * num3 + j; if (pixels[num6].a > 0.05f) { array[num6] = Color.clear; continue; } float num7 = 0f; for (int k = -num5; k <= num5; k++) { if (!(num7 <= 0.05f)) { break; } int num8 = i + k; if (num8 < 0 || num8 >= num4) { continue; } for (int l = -num5; l <= num5; l++) { int num9 = j + l; if (num9 >= 0 && num9 < num3) { num7 = Mathf.Max(num7, pixels[num8 * num3 + num9].a); } } } array[num6] = new Color(1f, 1f, 1f, num7); } } Texture2D val = new Texture2D(num3, num4, (TextureFormat)4, false) { filterMode = (FilterMode)0 }; val.SetPixels(array); val.Apply(); return Sprite.Create(val, new Rect(0f, 0f, (float)num3, (float)num4), source.pivot / ((Rect)(ref rect)).size, source.pixelsPerUnit); } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Could not generate the white map-check hint outline: " + ex.Message)); } return null; } } private static string GetPluginAssetPath(string fileName) { string directoryName = Path.GetDirectoryName(typeof(RandomizerPlugin).Assembly.Location); return Path.Combine(string.IsNullOrEmpty(directoryName) ? Paths.PluginPath : directoryName, fileName); } private static Sprite CreateFallbackIcon(Color color) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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) Texture2D val = new Texture2D(16, 16, (TextureFormat)4, false); Color[] array = (Color[])(object)new Color[256]; float num = 7.5f; float num2 = 42.25f; for (int i = 0; i < 16; i++) { for (int j = 0; j < 16; j++) { float num3 = (float)j - num; float num4 = (float)i - num; array[i * 16 + j] = ((num3 * num3 + num4 * num4 <= num2) ? color : Color.clear); } } val.SetPixels(array); val.Apply(); ((Texture)val).filterMode = (FilterMode)0; return Sprite.Create(val, new Rect(0f, 0f, 16f, 16f), new Vector2(0.5f, 0.5f)); } private IEnumerator Start() { yield return (object)new WaitForSecondsRealtime(5f); GetOrCreateArchipelago(); while ((Archipelago.Instance == null || !Archipelago.Instance.Connected) && !connectionGuiDismissed) { ShowConnectionGui(); Time.timeScale = 0f; yield return (object)new WaitForSecondsRealtime(1f); } Time.timeScale = 1f; HideConnectionGui(restoreCursor: true); while (true) { SaveState activeState = SaveState.Instance; if (activeState != null) { Location[] locations = activeState.locations.Locations; foreach (Location location in locations) { if (activeState != SaveState.Instance) { break; } try { if (location.Check != null && activeState.IsRandomized(location.Type) && activeState.IsLocationInSeed(location.Name) && !activeState.checkedLocations.Contains(location.Name) && location.Check()) { activeState.CheckLocation(location.Name); } } catch (Exception ex) { Log.LogWarning((object)("[RANDOMIZER] Location check failed for " + location.Name + ": " + ex.Message)); } yield return null; } } yield return null; } } private void Update() { if (resetTransientEffectsRequested) { resetTransientEffectsRequested = false; TrapManager.ResetTransientEffects(); } TrapManager.Update(); LogicAuditCloakManager.UpdateIntegrity(); SlabCaptureWarpSafety.Update(); MossMotherWarpSafety.Update(); BellhomePhaseManager.Update(); DeathLinkManager.Update(); SilkLinkManager.Update(); CurrencyLinkManager.Update(); FleaRescueAudio.Update(); FleaPatches.Update(); WandererChapelPatches.Update(); ProcessConnectionStatusQueue(); ProcessQueuedReceivedItems(); ProcessQueuedUnlockPopups(); ShopPatches.ProcessQueuedHintRefreshes(); if (reopenConnectionGuiRequested) { reopenConnectionGuiRequested = false; ShowConnectionGui(); } if (Input.GetKeyDown((KeyCode)284)) { if (showConnectionGui) { connectionGuiDismissed = true; HideConnectionGui(restoreCursor: true); } else { connectionGuiDismissed = false; ShowConnectionGui(); } } if (!showConnectionGui && Input.GetKeyDown((KeyCode)285)) { TryWarpToPreferredHub(); } if (!showConnectionGui && Input.GetKeyDown((KeyCode)289)) { LogicAuditCloakManager.TryCycle(); } } private void OnDisable() { LogicAuditCloakManager.Reset(); TrapManager.ResetTransientEffects(); DeathLinkManager.Reset(); SilkLinkManager.Reset(); CurrencyLinkManager.Reset(); FleaRescueAudio.ResetPending(); } private Archipelago GetOrCreateArchipelago() { Archipelago archipelago = Archipelago.Instance ?? new Archipelago(); if (subscribedArchipelago != archipelago) { if (subscribedArchipelago != null) { subscribedArchipelago.ConnectionStatusChanged -= QueueConnectionStatus; subscribedArchipelago.OnItemSent -= QueueUnlockPopup; } subscribedArchipelago = archipelago; subscribedArchipelago.ConnectionStatusChanged += QueueConnectionStatus; subscribedArchipelago.OnItemSent += QueueUnlockPopup; } return archipelago; } private void QueueConnectionStatus(string status) { if (!string.IsNullOrWhiteSpace(status)) { lock (connectionStatusQueueLock) { connectionStatusQueue.Enqueue(status); } if (status.StartsWith("Disconnected", StringComparison.OrdinalIgnoreCase) || status.StartsWith("Archipelago network error", StringComparison.OrdinalIgnoreCase)) { reopenConnectionGuiRequested = true; resetTransientEffectsRequested = true; } } } private void ProcessConnectionStatusQueue() { lock (connectionStatusQueueLock) { while (connectionStatusQueue.Count > 0) { connectionStatus = connectionStatusQueue.Dequeue(); } } } public void ReportBlockingError(string message) { QueueConnectionStatus(string.IsNullOrWhiteSpace(message) ? "Randomizer error." : message); connectionGuiDismissed = false; reopenConnectionGuiRequested = true; Log.LogError((object)("[RANDOMIZER] " + message)); } public void ClearPendingGameplayQueues() { LogicAuditCloakManager.Reset(); TrapManager.ResetTransientEffects(); lock (receivedItemQueueLock) { receivedItemQueue.Clear(); } lock (unlockQueueLock) { unlockQueue.Clear(); delay = 0f; } nextItemRetryTime = 0f; } public void QueueReceivedItem(int itemIndex, string itemName, ItemFlags flags) { //IL_003e: 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) if (itemIndex < 0 || string.IsNullOrWhiteSpace(itemName)) { return; } lock (receivedItemQueueLock) { receivedItemQueue.Enqueue(new QueuedReceivedItem { Index = itemIndex, Name = itemName, Flags = flags }); } } public void QueueUnlockPopup(string text, ItemFlags flags) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(text)) { return; } lock (unlockQueueLock) { unlockQueue.Enqueue(new QueuedUnlockPopup { Text = text, Flags = flags }); delay = 0.25f; } } private void ProcessQueuedReceivedItems() { for (int i = 0; i < 8; i++) { if (!ProcessQueuedReceivedItem()) { break; } } } private bool ProcessQueuedReceivedItem() { //IL_03b7: Unknown result type (might be due to invalid IL or missing references) SaveState instance = SaveState.Instance; if (instance == null) { return false; } TryBootstrapStartWithMaps(instance); if (Time.unscaledTime < nextItemRetryTime) { return false; } QueuedReceivedItem queuedReceivedItem; lock (receivedItemQueueLock) { if (receivedItemQueue.Count <= 0) { return false; } queuedReceivedItem = receivedItemQueue.Peek(); } if (queuedReceivedItem.Index < instance.receivedItemIndex) { lock (receivedItemQueueLock) { receivedItemQueue.Dequeue(); } return instance.IsStartWithMapsBootstrapItem(queuedReceivedItem.Name); } if (queuedReceivedItem.Index > instance.receivedItemIndex) { Log.LogWarning((object)("[RANDOMIZER] AP item queue skipped index " + instance.receivedItemIndex + "; rebuilding the queue.")); lock (receivedItemQueueLock) { receivedItemQueue.Clear(); } if (Archipelago.Instance != null && Archipelago.Instance.Connected) { Archipelago.Instance.ResetReceivedItemQueueCursor(); Archipelago.Instance.Resynchronize(); } nextItemRetryTime = Time.unscaledTime + 0.25f; return false; } Item item = instance.GetItem(queuedReceivedItem.Name); string text = ((item == null) ? queuedReceivedItem.Name : item.Name); bool flag = instance.receivedItems.Contains(text) && (item == null || !item.Repeatable); if (item == null && !instance.receivedItems.Contains(text)) { ReportBlockingError("AP sent unknown item '" + queuedReceivedItem.Name + "'."); nextItemRetryTime = Time.unscaledTime + 5f; return false; } bool flag2 = !flag && item != null && item.Type == ItemType.MaskShard; bool flag3 = !flag && ConsumableToolPatches.RequiresInitialFill(instance, text); int num; if (!flag && (item.Receive != null || flag2 || flag3)) { PlayerData instance2 = PlayerData.instance; HeroController instance3 = HeroController.instance; bool flag4 = MemorySequenceSync.CanApplyDurableReceipt(instance2, instance3, item); if (instance2 != null) { if (item.Type != ItemType.Crest) { if ((Object)(object)instance3 != (Object)null) { num = ((instance3.CanAttack() || NakedTrapManager.CanProcessReceivedItems(instance3) || flag4) ? 1 : 0); goto IL_0273; } } else if (!instance2.HasStoredMemoryState && !SlabCaptureWarpSafety.IsActiveSlabCaptureCrest(instance2)) { num = (StartingCrestFix.IsCrestRuntimeReady() ? 1 : 0); goto IL_0273; } } goto IL_0275; } goto IL_02fd; IL_0273: if (num == 0) { goto IL_0275; } try { item.Receive?.Invoke(); } catch (Exception ex) { nextItemRetryTime = Time.unscaledTime + 1f; Log.LogError((object)("[RANDOMIZER] Failed to apply item " + queuedReceivedItem.Name + " at index " + queuedReceivedItem.Index + "; it will be retried: " + ex)); return false; } goto IL_02fd; IL_0275: return false; IL_02fd: try { bool flag5 = instance.CommitReceivedItemAtIndex(queuedReceivedItem.Index, text, item?.Repeatable ?? false); if (flag5 && item != null && item.Type == ItemType.MaskShard && !MaskShardsPatches.SynchronizeReceivedMaskShards(refillNewMask: true)) { Log.LogError((object)"[RANDOMIZER] Mask Shard was recorded, but its native health state was not ready. It will be reconciled on load."); } if (flag5 && flag3 && !ConsumableToolPatches.TryInitializeReceivedConsumable(instance, text)) { ((MonoBehaviour)this).StartCoroutine(ConsumableToolPatches.SynchronizeReceivedConsumables(instance)); } if (flag5 && item != null && item.Type == ItemType.SpoolFragment) { SpoolFragmentPatches.RefreshReceivedSpoolHud(); } if (item != null && item.Type == ItemType.Flea) { FleaRescueAudio.QueueForReceivedFlea(); } lock (receivedItemQueueLock) { receivedItemQueue.Dequeue(); } if (flag5) { QueueUnlockPopup(text, queuedReceivedItem.Flags); } return flag && instance.IsStartWithMapsBootstrapItem(text); } catch (Exception ex2) { nextItemRetryTime = Time.unscaledTime + 1f; Log.LogError((object)("[RANDOMIZER] Failed to commit item " + queuedReceivedItem.Name + " at index " + queuedReceivedItem.Index + "; it will be retried: " + ex2)); return false; } } private void TryBootstrapStartWithMaps(SaveState saveState) { if (saveState == null || !saveState.NeedsStartWithMapsBootstrap()) { return; } PlayerData instance = PlayerData.instance; HeroController instance2 = HeroController.instance; if (instance == null || instance.HasStoredMemoryState || (Object)(object)instance2 == (Object)null || !instance2.CanAttack()) { return; } try { ItemGrants.GrantStartWithMaps(); saveState.RecordStartWithMapsBootstrapItems(); } catch (Exception ex) { nextItemRetryTime = Time.unscaledTime + 1f; Log.LogError((object)("[RANDOMIZER] Failed to apply the starting maps; they will be retried: " + ex)); } } private void ProcessQueuedUnlockPopups() { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) if (SaveState.Instance == null || (Object)(object)HeroController.instance == (Object)null || !HeroController.instance.CanAttack()) { return; } QueuedUnlockPopup queuedUnlockPopup = default(QueuedUnlockPopup); lock (unlockQueueLock) { if (delay > 0f) { delay -= Time.deltaTime; return; } if (unlockQueue.Count <= 0) { return; } queuedUnlockPopup = unlockQueue.Dequeue(); } CollectableUIMsg.Spawn((ICollectableUIMsgItem)(object)new UIMsgDisplay { Name = queuedUnlockPopup.Text, Icon = GetItemClassificationIcon(queuedUnlockPopup.Flags), IconScale = 1f, RepresentingObject = null }, (CollectableUIMsg)null, false); } public void ShowFleaMessage(string fleaName) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) CollectableUIMsg.Spawn((ICollectableUIMsgItem)(object)new UIMsgDisplay { Name = "Flea rescued", Icon = FleaIcon, IconScale = 1f, RepresentingObject = null }, (CollectableUIMsg)null, false); } private void OnGUI() { //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown //IL_00ae: Unknown result type (might be due to invalid IL or missing references) CheckMapMarkerManager.DrawTooltip(); DrawLogicAuditRoomId(); if (showConnectionGui) { UnlockCursorForConnectionGui(); DrawConnectionBackground(); ((Rect)(ref connectionWindowRect)).x = Mathf.Max(0f, ((float)Screen.width - 460f) * 0.5f); ((Rect)(ref connectionWindowRect)).y = Mathf.Max(0f, ((float)Screen.height - 350f) * 0.5f); ((Rect)(ref connectionWindowRect)).width = 460f; ((Rect)(ref connectionWindowRect)).height = 350f; GUILayout.Window(((Object)this).GetInstanceID(), connectionWindowRect, new WindowFunction(DrawConnectionWindow), "Archipelago", Array.Empty()); } } private static void DrawLogicAuditRoomId() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) SaveState instance = SaveState.Instance; GameManager silentInstance = GameManager.SilentInstance; if (instance != null && instance.logicAuditMode && !((Object)(object)silentInstance == (Object)null) && !string.IsNullOrWhiteSpace(silentInstance.sceneName)) { int depth = GUI.depth; GUI.depth = -1000; GUI.Box(new Rect(8f, 8f, 230f, 28f), "Room ID: " + silentInstance.sceneName); GUI.Box(new Rect(8f, 40f, 230f, 28f), LogicAuditCloakManager.GetOverlayText()); GUI.depth = depth; } } private void ShowConnectionGui() { if (!showConnectionGui) { showConnectionGui = true; CaptureCursorState(); } } private void HideConnectionGui(bool restoreCursor) { if (showConnectionGui) { showConnectionGui = false; if (restoreCursor) { RestoreCursorState(); } } } private void CaptureCursorState() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (!cursorStateCaptured) { previousCursorVisible = Cursor.visible; previousCursorLockState = Cursor.lockState; cursorStateCaptured = true; } } private void UnlockCursorForConnectionGui() { CaptureCursorState(); Cursor.visible = true; Cursor.lockState = (CursorLockMode)0; } private void RestoreCursorState() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (cursorStateCaptured) { Cursor.visible = previousCursorVisible; Cursor.lockState = previousCursorLockState; cursorStateCaptured = false; } } private static void DrawConnectionBackground() { //IL_0000: 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_0039: Unknown result type (might be due to invalid IL or missing references) Color color = GUI.color; GUI.color = new Color(0f, 0f, 0f, 0.85f); GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)Texture2D.whiteTexture); GUI.color = color; } private void DrawConnectionWindow(int windowId) { GUILayout.BeginVertical(Array.Empty()); DrawConnectionTab(); GUILayout.EndVertical(); GUI.DragWindow(); } private void DrawConnectionTab() { bool flag = Archipelago.Instance != null && Archipelago.Instance.Connected; GUILayout.Label(flag ? "Connected. You can reconnect or switch servers below." : "Connect to an Archipelago room before playing this randomizer save.", Array.Empty()); GUILayout.Space(8f); GUILayout.Label("Host", Array.Empty()); connectionHost = GUILayout.TextField(connectionHost ?? string.Empty, Array.Empty()); GUILayout.Label("Port", Array.Empty()); connectionPort = GUILayout.TextField(connectionPort ?? string.Empty, Array.Empty()); GUILayout.Label("Slot", Array.Empty()); connectionSlot = GUILayout.TextField(connectionSlot ?? string.Empty, Array.Empty()); GUILayout.Label("Password", Array.Empty()); connectionPassword = GUILayout.PasswordField(connectionPassword ?? string.Empty, '*', Array.Empty()); if (!string.IsNullOrEmpty(connectionStatus)) { GUILayout.Space(8f); GUILayout.Label(connectionStatus, Array.Empty()); } GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(Array.Empty()); GUI.enabled = !isConnecting; if (GUILayout.Button(isConnecting ? "Connecting..." : (flag ? "Reconnect / Switch" : "Connect"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(32f) })) { TryConnectFromGui(); } GUI.enabled = !isConnecting && FastTravelUtil.CanTeleportToPreferredHub(out var _); if (GUILayout.Button(FastTravelUtil.GetPreferredHubName() + " (F4)", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(32f) })) { TryWarpToPreferredHub(); } GUI.enabled = !isConnecting; if (GUILayout.Button(flag ? "Close" : "Play Offline", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(32f) })) { connectionGuiDismissed = true; connectionStatus = string.Empty; HideConnectionGui(restoreCursor: true); } GUI.enabled = true; GUILayout.EndHorizontal(); } private void TryWarpToPreferredHub() { try { if (!FastTravelUtil.TryTeleportToPreferredHub(out var error)) { Log.LogWarning((object)("[RANDOMIZER] " + error)); if (showConnectionGui) { connectionStatus = error; } } else { connectionGuiDismissed = true; connectionStatus = string.Empty; HideConnectionGui(restoreCursor: true); Log.LogInfo((object)("[RANDOMIZER] Warping to " + FastTravelUtil.GetPreferredHubName() + ".")); } } catch (Exception ex) { Log.LogWarning((object)("[RANDOMIZER] F4 warp failed; no transition was started. " + ex)); if (showConnectionGui) { connectionStatus = "F4 warp failed; no transition was started."; } } } private void TryConnectFromGui() { if (!int.TryParse(connectionPort, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result < 1 || result > 65535) { connectionStatus = "Enter a valid port between 1 and 65535."; return; } if (string.IsNullOrWhiteSpace(connectionHost)) { connectionStatus = "Enter the Archipelago host."; return; } if (string.IsNullOrWhiteSpace(connectionSlot)) { connectionStatus = "Enter your slot name."; return; } isConnecting = true; connectionStatus = "Connecting..."; ((MonoBehaviour)this).StartCoroutine(ConnectFromGuiRoutine(connectionHost.Trim(), result, connectionSlot.Trim(), string.IsNullOrEmpty(connectionPassword) ? null : connectionPassword)); } private IEnumerator ConnectFromGuiRoutine(string host, int port, string slot, string password) { Task connectionTask; try { Archipelago archipelago = GetOrCreateArchipelago(); connectionTask = Task.Run(() => archipelago.Connect(host, port, slot, password)); } catch (Exception ex) { connectionStatus = "Connection failed: " + ex.Message; Log.LogWarning((object)("[RANDOMIZER] Archipelago connection failed: " + ex)); isConnecting = false; yield break; } while (!connectionTask.IsCompleted) { yield return null; } try { if (connectionTask.IsFaulted) { throw (connectionTask.Exception == null) ? new Exception("Unknown connection failure.") : connectionTask.Exception.GetBaseException(); } if (connectionTask.Result) { Archipelago orCreateArchipelago = GetOrCreateArchipelago(); if (orCreateArchipelago.CompleteConnectionSync()) { SaveSuccessfulConnectionDetails(host, port, slot); connectionStatus = "Connected."; connectionGuiDismissed = false; HideConnectionGui(restoreCursor: true); } else { connectionStatus = (string.IsNullOrWhiteSpace(orCreateArchipelago.LastError) ? "Connection validation failed." : orCreateArchipelago.LastError); ShowConnectionGui(); } } else { string text = ((Archipelago.Instance == null) ? string.Empty : Archipelago.Instance.LastError); connectionStatus = (string.IsNullOrWhiteSpace(text) ? "Connection failed. Check the host, port, slot, and password." : text); } } catch (Exception ex2) { connectionStatus = "Connection failed: " + ex2.Message; Log.LogWarning((object)("[RANDOMIZER] Archipelago connection failed: " + ex2)); } finally { isConnecting = false; } } private static void DumpPatchState(Harmony h) { foreach (MethodBase item in h.GetPatchedMethods().ToList()) { Patches patchInfo = Harmony.GetPatchInfo(item); if (patchInfo != null && patchInfo.Owners != null && patchInfo.Owners.Contains("moriko.silksong.randomizer")) { int num = patchInfo.Prefixes?.Count ?? 0; int num2 = patchInfo.Postfixes?.Count ?? 0; int num3 = patchInfo.Transpilers?.Count ?? 0; Log.LogInfo((object)$"[Harmony] Patched: {item.DeclaringType?.FullName}.{item.Name} (prefixes={num}, postfixes={num2}, transpilers={num3})"); } } if (!Harmony.HasAnyPatches("moriko.silksong.randomizer")) { Log.LogError((object)"[Harmony] No patches registered for our ID!"); } } } internal sealed class QuestLocationDefinition { internal readonly string AssetName; internal readonly string LocationName; internal QuestLocationDefinition(string assetName) { AssetName = assetName; LocationName = "Quest Completion: " + assetName; } } internal static class QuestLocationManifest { private static readonly HashSet DonationsWithoutVanillaReward = new HashSet(StringComparer.Ordinal) { "Belltown House Start", "Belltown House Mid", "Building Materials", "Building Materials (Bridge)", "Building Materials (Statue)", "Songclave Donation 1", "Songclave Donation 2" }; internal static readonly QuestLocationDefinition[] QuestLocations = new QuestLocationDefinition[32] { new QuestLocationDefinition("A Pinsmiths Tools"), new QuestLocationDefinition("Belltown House Start"), new QuestLocationDefinition("Belltown House Mid"), new QuestLocationDefinition("Building Materials"), new QuestLocationDefinition("Building Materials (Bridge)"), new QuestLocationDefinition("Building Materials (Statue)"), new QuestLocationDefinition("Courier Delivery Bonebottom"), new QuestLocationDefinition("Courier Delivery Dustpens Slave"), new QuestLocationDefinition("Courier Delivery Fixer"), new QuestLocationDefinition("Courier Delivery Fleatopia"), new QuestLocationDefinition("Courier Delivery Mask Maker"), new QuestLocationDefinition("Courier Delivery Pilgrims Rest"), new QuestLocationDefinition("Courier Delivery Songclave"), new QuestLocationDefinition("Extractor Blue Worms"), new QuestLocationDefinition("Fine Pins"), new QuestLocationDefinition("Garmond Black Threaded"), new QuestLocationDefinition("Great Gourmand"), new QuestLocationDefinition("Journal"), new QuestLocationDefinition("Mr Mushroom"), new QuestLocationDefinition("Pilgrim Rags"), new QuestLocationDefinition("Rock Rollers"), new QuestLocationDefinition("Save City Merchant"), new QuestLocationDefinition("Save City Merchant Bridge"), new QuestLocationDefinition("Save Courier Short"), new QuestLocationDefinition("Save Courier Tall"), new QuestLocationDefinition("Save Sherma"), new QuestLocationDefinition("Shiny Bell Goomba"), new QuestLocationDefinition("Skull King"), new QuestLocationDefinition("Song Pilgrim Cloaks"), new QuestLocationDefinition("Songclave Donation 1"), new QuestLocationDefinition("Songclave Donation 2"), new QuestLocationDefinition("Steel Sentinel Pt2") }; private static readonly Dictionary ReplaceableVanillaRewards = new Dictionary(StringComparer.Ordinal) { { "Courier Delivery Bonebottom", "Money Reward" }, { "Courier Delivery Dustpens Slave", "Money Reward" }, { "Courier Delivery Fixer", "Money Reward" }, { "Courier Delivery Fleatopia", "Money Reward" }, { "Courier Delivery Mask Maker", "Money Reward" }, { "Courier Delivery Pilgrims Rest", "Money Reward" }, { "Courier Delivery Songclave", "Money Reward" }, { "Fine Pins", "Rosary_Set_Large" }, { "Pilgrim Rags", "Rosary_Set_Medium" }, { "Shiny Bell Goomba", "Rosary_Set_Medium" }, { "Skull King", "Rosary_Set_Large" }, { "Song Pilgrim Cloaks", "Rosary_Set_Large" } }; internal static Location[] AppendTo(Location[] existingLocations) { List list = new List(existingLocations); QuestLocationDefinition[] questLocations = QuestLocations; foreach (QuestLocationDefinition questLocationDefinition in questLocations) { string assetName = questLocationDefinition.AssetName; list.Add(new Location(questLocationDefinition.LocationName, ItemType.Quest, () => IsQuestCompleted(assetName))); } return list.ToArray(); } internal static bool TryGetLocationName(string assetName, out string locationName) { if (!string.IsNullOrWhiteSpace(assetName)) { QuestLocationDefinition[] questLocations = QuestLocations; foreach (QuestLocationDefinition questLocationDefinition in questLocations) { if (string.Equals(questLocationDefinition.AssetName, assetName, StringComparison.Ordinal)) { locationName = LocationSet.GetCanonicalLocationName(questLocationDefinition.LocationName); return true; } } } locationName = null; return false; } internal static bool IsDonationWithoutVanillaReward(string assetName) { if (!string.IsNullOrWhiteSpace(assetName)) { return DonationsWithoutVanillaReward.Contains(assetName); } return false; } internal static bool TryGetReplaceableVanillaRewardLocation(string assetName, string nativeRewardAssetName, out string locationName) { if (!string.IsNullOrWhiteSpace(assetName) && !string.IsNullOrWhiteSpace(nativeRewardAssetName) && ReplaceableVanillaRewards.TryGetValue(assetName, out var value) && string.Equals(value, nativeRewardAssetName, StringComparison.Ordinal) && TryGetLocationName(assetName, out locationName)) { return true; } locationName = null; return false; } internal static bool IsQuestCompleted(string assetName) { //IL_0037: 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_003d: 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) SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Quest) || string.IsNullOrWhiteSpace(assetName)) { return false; } PlayerData instance2 = PlayerData.instance; if (instance2 == null || instance2.QuestCompletionData == null) { return false; } Completion data = ((SerializableNamedList)(object)instance2.QuestCompletionData).GetData(assetName); if (!data.IsCompleted) { return data.WasEverCompleted; } return true; } } [Serializable] public class SaveState { [Serializable] public class HintData { public string locationName; public string user; public string item; public ItemFlags flags; } [Serializable] public class PurchasePriceData { public string key = string.Empty; public int price; public PurchasePriceData() { } public PurchasePriceData(string key, int price) { this.key = key ?? string.Empty; this.price = price; } } public const int CurrentSchemaVersion = 22; internal static readonly string[] StartWithMapsItemNames = new string[27] { "Map: Mosslands", "Map: The Marrow", "Map: Deep Docks", "Map: Far Fields", "Map: Wormways", "Map: Hunter's March", "Map: Greymoor", "Map: Bellhart", "Map: Shellwood", "Map: Blasted Steps", "Map: Sinner's Road", "Map: Mount Fay", "Map: Sands of Karak", "Map: Bilewater", "Map: Weavenest Atla", "Map: Grand Gate", "Map: Underworks", "Map: Choral Chambers", "Map: Whispering Vaults", "Map: Whiteward", "Map: Cogwork Core", "Map: Memorium", "Map: High Halls", "Map: The Slab", "Map: Putrified Ducts", "Map: The Cradle", "Map: The Abyss" }; private static readonly HashSet StartWithMapsItemNameSet = new HashSet(StartWithMapsItemNames, StringComparer.OrdinalIgnoreCase); public int schemaVersion = 22; public string roomSeed = string.Empty; public string slotName = string.Empty; public int team = -1; public int slot = -1; public string worldVersion = string.Empty; public string goal = string.Empty; public int fleaHuntGoalCount = 20; public string startingLocation = string.Empty; public bool startingLocationApplied; public string startingCrest = string.Empty; public bool splitDashAndSprint; public bool randomizeNeedleUpgrades; public bool startWithMaps; public bool automaticCompass; public CheckMapMarkerMode checkMapMarkers; public string bellwayAccess = "bell_beast_required"; public string enemyRosaryMultiplier = "x1"; public string enemyShardMultiplier = "x1"; public string normalShopPrices = "vanilla"; public string bellwayPrices = "vanilla"; public string mapPrices = "vanilla"; public string pinPrices = "vanilla"; public string upgradePrices = "vanilla"; public string donationPrices = "vanilla"; public List purchasePrices = new List(); [NonSerialized] [XmlIgnore] private Dictionary purchasePriceLookup; public bool fasterDialogue; public bool deathLink; public bool silkLink; [NonSerialized] [XmlIgnore] public int silkLinkPrivateSilk = -1; public int silkLinkLastSyncedBalance = -1; public bool rosaryLink; public bool shellShardLink; public int rosaryLinkLastSyncedBalance = -1; public int shellShardLinkLastSyncedBalance = -1; public int shellShardLinkPrivateShards = -1; public bool individualRelicTurnIns; public bool logicAuditMode; public bool rodeFleaCaravanToGreymoor; public bool bellhomePhaseToggleUnlocked; public bool mossMotherBypassedByBoneBottomWarp; public string mapLogicPayloadJson = string.Empty; public bool questSanity; public RandomizationMode skillRandomization = RandomizationMode.Anywhere; public RandomizationMode toolRandomization = RandomizationMode.Anywhere; public RandomizationMode silkSkillRandomization = RandomizationMode.Anywhere; public RandomizationMode crestRandomization = RandomizationMode.Anywhere; public RandomizationMode fleaRandomization = RandomizationMode.Anywhere; public RandomizationMode crestSlotRandomization = RandomizationMode.Anywhere; public RandomizationMode maskShardRandomization = RandomizationMode.Anywhere; public RandomizationMode spoolFragmentRandomization = RandomizationMode.Anywhere; public RandomizationMode silkHeartRandomization = RandomizationMode.Anywhere; public RandomizationMode bellwayRandomization = RandomizationMode.Anywhere; public RandomizationMode ventricaRandomization = RandomizationMode.Anywhere; public RandomizationMode mapRandomization = RandomizationMode.Anywhere; public RandomizationMode melodyRandomization; public RandomizationMode pinRandomization = RandomizationMode.Anywhere; public RandomizationMode relicRandomization = RandomizationMode.Anywhere; public RandomizationMode craftingKitRandomization = RandomizationMode.Anywhere; public RandomizationMode minorPickupRandomization; public RandomizationMode simpleKeyRandomization; public RandomizationMode memoryLocketRandomization; public RandomizationMode craftmetalRandomization; public RandomizationMode mossberryRandomization; public RandomizationMode pollipHeartRandomization; public RandomizationMode silkeaterRandomization; public RandomizationMode majorKeyRandomization; public RandomizationMode toolPouchRandomization; public RandomizationMode bossSanity = RandomizationMode.Anywhere; public RandomizationMode bellShrineSanity = RandomizationMode.Anywhere; public RandomizationMode questSanityMode = RandomizationMode.Anywhere; public bool goalCompleted; public int receivedItemIndex; public bool canDoubleJump; public bool canChargeSlash; public bool canSilkSoar; public bool canWallJump; public bool canBrolly; public bool canDash; public bool canSprint; public bool canUseHarpoon; public bool canUseNeedolin; public bool canUseQuill; public int swiftStepLevel; public int druidsEyeLevel; public int clawMirrorLevel; public int curveclawLevel; public int silkHeartLevel; public int needleUpgradeLevel; public bool fleaBrewInitialFillApplied; public bool plasmiumPhialInitialFillApplied; public bool SavedFlea_Bone_06; public bool SavedFlea_Dock_16; public bool SavedFlea_Bone_East_05; public bool SavedFlea_Bone_East_17b; public bool SavedFlea_Ant_03; public bool SavedFlea_Greymoor_15b; public bool SavedFlea_Greymoor_06; public bool SavedFlea_Shellwood_03; public bool SavedFlea_Bone_East_10_Church; public bool SavedFlea_Coral_35; public bool SavedFlea_Dust_12; public bool SavedFlea_Dust_09; public bool SavedFlea_Belltown_04; public bool SavedFlea_Crawl_06; public bool SavedFlea_Slab_Cell; public bool SavedFlea_Shadow_28; public bool SavedFlea_Dock_03d; public bool SavedFlea_Under_23; public bool SavedFlea_Shadow_10; public bool SavedFlea_Song_14; public bool SavedFlea_Coral_24; public bool SavedFlea_Peak_05c; public bool SavedFlea_Library_09; public bool SavedFlea_Song_11; public bool SavedFlea_Library_01; public bool SavedFlea_Under_21; public bool SavedFlea_Slab_06; public bool UnlockedDocksStation; public bool UnlockedBoneforestEastStation; public bool UnlockedGreymoorStation; public bool UnlockedBelltownStation; public bool UnlockedCoralTowerStation; public bool UnlockedCityStation; public bool UnlockedPeakStation; public bool UnlockedShellwoodStation; public bool UnlockedShadowStation; public bool UnlockedAqueductStation; public bool bellCentipedeAppeared; public bool canUseBeastlingCall; public bool bellEaterResolved; public bool UnlockedSongTube; public bool UnlockedUnderTube; public bool UnlockedCityBellwayTube; public bool UnlockedHangTube; public bool UnlockedEnclaveTube; public bool UnlockedArboriumTube; [NonSerialized] [XmlIgnore] public ItemSet items = new ItemSet(); [NonSerialized] [XmlIgnore] public LocationSet locations = new LocationSet(); public HashSet receivedItems = new HashSet(StringComparer.OrdinalIgnoreCase); public HashSet checkedLocations = new HashSet(StringComparer.OrdinalIgnoreCase); public HashSet roomLocationNames = new HashSet(StringComparer.OrdinalIgnoreCase); public List receivedHints = new List(); public static SaveState Instance { get; set; } public bool IsRoomBound { get { if (!string.IsNullOrWhiteSpace(roomSeed) && team >= 0) { return slot >= 0; } return false; } } public bool HasWorldVersionBinding => Archipelago.IsSupportedWorldVersion(worldVersion); public bool HasGoalBinding { get { if (Archipelago.IsSupportedGoal(goal)) { if (string.Equals(goal, "flea_hunt", StringComparison.Ordinal)) { return Archipelago.IsSupportedFleaHuntGoalCount(fleaHuntGoalCount); } return true; } return false; } } public bool HasStartingCrestBinding => CrestNames.IsSupportedStartingCrestKey(startingCrest); public bool HasStartingLocationBinding => Archipelago.IsSupportedStartingLocation(startingLocation); [XmlIgnore] public bool AllowsBellwaysBeforeBellBeast => string.Equals(bellwayAccess, "randomized_stations", StringComparison.Ordinal); public void InitializeAfterLoad() { int num = schemaVersion; if (num < 7) { skillRandomization = RandomizationMode.Anywhere; toolRandomization = RandomizationMode.Anywhere; silkSkillRandomization = RandomizationMode.Anywhere; crestRandomization = RandomizationMode.Anywhere; fleaRandomization = RandomizationMode.Anywhere; crestSlotRandomization = RandomizationMode.Anywhere; maskShardRandomization = RandomizationMode.Anywhere; spoolFragmentRandomization = RandomizationMode.Anywhere; silkHeartRandomization = RandomizationMode.Anywhere; bellwayRandomization = RandomizationMode.Anywhere; ventricaRandomization = RandomizationMode.Anywhere; mapRandomization = RandomizationMode.Anywhere; pinRandomization = RandomizationMode.Anywhere; relicRandomization = RandomizationMode.Anywhere; craftingKitRandomization = RandomizationMode.Anywhere; bossSanity = RandomizationMode.Anywhere; bellShrineSanity = RandomizationMode.Anywhere; questSanityMode = (questSanity ? RandomizationMode.Anywhere : RandomizationMode.Vanilla); } if (num < 5 && canDash) { canSprint = true; } if (num < 8) { randomizeNeedleUpgrades = false; swiftStepLevel = (canSprint ? ((!canDash) ? 1 : 2) : 0); } if (num < 10) { bellwayAccess = "bell_beast_required"; } if (num < 11) { simpleKeyRandomization = RandomizationMode.Vanilla; } if (num < 12) { logicAuditMode = false; bellhomePhaseToggleUnlocked = false; } if (num < 13) { melodyRandomization = RandomizationMode.Vanilla; canUseBeastlingCall = false; bellEaterResolved = false; } if (num < 14) { silkLink = false; silkLinkLastSyncedBalance = -1; } if (num < 15) { rosaryLink = false; shellShardLink = false; rosaryLinkLastSyncedBalance = -1; shellShardLinkLastSyncedBalance = -1; shellShardLinkPrivateShards = -1; } if (num < 16) { memoryLocketRandomization = RandomizationMode.Vanilla; craftmetalRandomization = RandomizationMode.Vanilla; mossberryRandomization = RandomizationMode.Vanilla; silkeaterRandomization = RandomizationMode.Vanilla; majorKeyRandomization = RandomizationMode.Vanilla; } if (num < 17) { startingLocation = string.Empty; startingLocationApplied = false; } if (num < 18) { normalShopPrices = "vanilla"; bellwayPrices = "vanilla"; mapPrices = "vanilla"; pinPrices = "vanilla"; upgradePrices = "vanilla"; donationPrices = "vanilla"; purchasePrices = new List(); } if (num < 19) { individualRelicTurnIns = false; } if (num < 21) { toolPouchRandomization = RandomizationMode.Vanilla; } if (num < 22) { pollipHeartRandomization = RandomizationMode.Vanilla; } schemaVersion = 22; roomSeed = roomSeed ?? string.Empty; slotName = slotName ?? string.Empty; worldVersion = worldVersion ?? string.Empty; goal = goal ?? string.Empty; mapLogicPayloadJson = mapLogicPayloadJson ?? string.Empty; if (!Archipelago.IsSupportedFleaHuntGoalCount(fleaHuntGoalCount)) { fleaHuntGoalCount = 20; } startingCrest = startingCrest ?? string.Empty; startingLocation = startingLocation ?? string.Empty; if (!Archipelago.IsSupportedBellwayAccess(bellwayAccess)) { bellwayAccess = "bell_beast_required"; } if (!Archipelago.IsSupportedEnemyRosaryMultiplier(enemyRosaryMultiplier)) { enemyRosaryMultiplier = "x1"; } if (!Archipelago.IsSupportedEnemyShardMultiplier(enemyShardMultiplier)) { enemyShardMultiplier = "x1"; } normalShopPrices = NormalizePurchasePriceMode(normalShopPrices); bellwayPrices = NormalizePurchasePriceMode(bellwayPrices); mapPrices = NormalizePurchasePriceMode(mapPrices); pinPrices = NormalizePurchasePriceMode(pinPrices); upgradePrices = NormalizePurchasePriceMode(upgradePrices); donationPrices = NormalizePurchasePriceMode(donationPrices); SetPurchasePrices((from @group in (purchasePrices ?? new List()).Where((PurchasePriceData entry) => entry != null).GroupBy((PurchasePriceData entry) => entry.key ?? string.Empty, StringComparer.Ordinal) where !string.IsNullOrWhiteSpace(@group.Key) select @group).ToDictionary, string, int>((IGrouping group) => group.Key, (IGrouping group) => Math.Max(0, group.Last().price), StringComparer.Ordinal)); if (checkMapMarkers < CheckMapMarkerMode.Off || checkMapMarkers > CheckMapMarkerMode.All) { checkMapMarkers = CheckMapMarkerMode.Off; } receivedItems = new HashSet((receivedItems ?? new HashSet()).Select(ItemSet.GetCanonicalItemName), StringComparer.OrdinalIgnoreCase); if (receivedItems.Contains("Progressive Silkheart")) { silkHeartLevel = Math.Max(silkHeartLevel, 1); } silkHeartLevel = Math.Max(0, Math.Min(3, silkHeartLevel)); druidsEyeLevel = Math.Max(0, Math.Min(2, druidsEyeLevel)); clawMirrorLevel = Math.Max(0, Math.Min(2, clawMirrorLevel)); curveclawLevel = Math.Max(0, Math.Min(2, curveclawLevel)); swiftStepLevel = Math.Max(0, Math.Min(2, swiftStepLevel)); needleUpgradeLevel = Math.Max(0, Math.Min(4, needleUpgradeLevel)); if (splitDashAndSprint) { if (swiftStepLevel >= 1) { canSprint = true; } if (swiftStepLevel >= 2) { canDash = true; } } checkedLocations = new HashSet((checkedLocations ?? new HashSet()).Select(LocationSet.GetCanonicalLocationName), StringComparer.OrdinalIgnoreCase); roomLocationNames = new HashSet(from name in (roomLocationNames ?? new HashSet()).Select(LocationSet.GetCanonicalLocationName) where !string.IsNullOrWhiteSpace(name) select name, StringComparer.OrdinalIgnoreCase); receivedHints = receivedHints ?? new List(); foreach (HintData item in receivedHints.Where((HintData hint) => hint != null)) { item.locationName = LocationSet.GetCanonicalLocationName(item.locationName); item.item = ItemSet.GetCanonicalItemName(item.item); } items = new ItemSet(); locations = new LocationSet(); questSanity = IsRandomized(ItemType.Quest); } private static string NormalizePurchasePriceMode(string mode) { if (!Archipelago.IsSupportedPurchasePriceMode(mode)) { return "vanilla"; } return mode; } private void SetPurchasePrices(IEnumerable> prices) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if (prices != null) { foreach (KeyValuePair price in prices) { if (!string.IsNullOrWhiteSpace(price.Key) && price.Value >= 0) { dictionary[price.Key] = price.Value; } } } purchasePrices = (from entry in dictionary.OrderBy, string>((KeyValuePair entry) => entry.Key, StringComparer.Ordinal) select new PurchasePriceData(entry.Key, entry.Value)).ToList(); purchasePriceLookup = dictionary; } private Dictionary GetPurchasePriceLookup() { if (purchasePriceLookup != null) { return purchasePriceLookup; } purchasePriceLookup = new Dictionary(StringComparer.Ordinal); foreach (PurchasePriceData item in purchasePrices ?? new List()) { if (item != null && !string.IsNullOrWhiteSpace(item.key) && item.price >= 0) { purchasePriceLookup[item.key] = item.price; } } return purchasePriceLookup; } public bool TryGetPurchasePrice(string key, out int price) { price = 0; if (!string.IsNullOrWhiteSpace(key)) { return GetPurchasePriceLookup().TryGetValue(key, out price); } return false; } private bool PurchasePriceSettingsMatch(Archipelago archipelago) { if (archipelago == null || !string.Equals(normalShopPrices, archipelago.NormalShopPrices, StringComparison.Ordinal) || !string.Equals(bellwayPrices, archipelago.BellwayPrices, StringComparison.Ordinal) || !string.Equals(mapPrices, archipelago.MapPrices, StringComparison.Ordinal) || !string.Equals(pinPrices, archipelago.PinPrices, StringComparison.Ordinal) || !string.Equals(upgradePrices, archipelago.UpgradePrices, StringComparison.Ordinal) || !string.Equals(donationPrices, archipelago.DonationPrices, StringComparison.Ordinal)) { return false; } Dictionary dictionary = GetPurchasePriceLookup(); IReadOnlyDictionary roomPrices = archipelago.PurchasePrices; int value; if (roomPrices != null && dictionary.Count == roomPrices.Count) { return dictionary.All((KeyValuePair entry) => roomPrices.TryGetValue(entry.Key, out value) && value == entry.Value); } return false; } public void BindToRoom(Archipelago archipelago) { if (archipelago != null && archipelago.Connected) { roomSeed = archipelago.RoomSeed ?? string.Empty; slotName = archipelago.SlotName ?? string.Empty; team = archipelago.Team; slot = archipelago.Slot; worldVersion = archipelago.WorldVersion ?? string.Empty; goal = archipelago.Goal ?? string.Empty; fleaHuntGoalCount = archipelago.FleaHuntGoalCount; startingLocation = archipelago.StartingLocation ?? string.Empty; startingCrest = archipelago.StartingCrest ?? string.Empty; splitDashAndSprint = archipelago.SplitDashAndSprint; randomizeNeedleUpgrades = archipelago.RandomizeNeedleUpgrades; startWithMaps = archipelago.StartWithMaps; automaticCompass = archipelago.AutomaticCompass; checkMapMarkers = archipelago.CheckMapMarkers; bellwayAccess = archipelago.BellwayAccess; enemyRosaryMultiplier = archipelago.EnemyRosaryMultiplier; enemyShardMultiplier = archipelago.EnemyShardMultiplier; normalShopPrices = archipelago.NormalShopPrices; bellwayPrices = archipelago.BellwayPrices; mapPrices = archipelago.MapPrices; pinPrices = archipelago.PinPrices; upgradePrices = archipelago.UpgradePrices; donationPrices = archipelago.DonationPrices; SetPurchasePrices(archipelago.PurchasePrices); fasterDialogue = archipelago.FasterDialogue; deathLink = archipelago.DeathLink; silkLink = archipelago.SilkLink; rosaryLink = archipelago.RosaryLink; shellShardLink = archipelago.ShellShardLink; individualRelicTurnIns = archipelago.IndividualRelicTurnIns; logicAuditMode = archipelago.LogicAuditMode; if (logicAuditMode) { bellhomePhaseToggleUnlocked = true; } mapLogicPayloadJson = archipelago.MapLogicPayloadJson ?? string.Empty; skillRandomization = archipelago.SkillRandomization; toolRandomization = archipelago.ToolRandomization; silkSkillRandomization = archipelago.SilkSkillRandomization; crestRandomization = archipelago.CrestRandomization; fleaRandomization = archipelago.FleaRandomization; crestSlotRandomization = archipelago.CrestSlotRandomization; maskShardRandomization = archipelago.MaskShardRandomization; spoolFragmentRandomization = archipelago.SpoolFragmentRandomization; silkHeartRandomization = archipelago.SilkHeartRandomization; bellwayRandomization = archipelago.BellwayRandomization; ventricaRandomization = archipelago.VentricaRandomization; mapRandomization = archipelago.MapRandomization; melodyRandomization = archipelago.MelodyRandomization; pinRandomization = archipelago.PinRandomization; relicRandomization = archipelago.RelicRandomization; craftingKitRandomization = archipelago.CraftingKitRandomization; minorPickupRandomization = archipelago.MinorPickupRandomization; simpleKeyRandomization = archipelago.SimpleKeyRandomization; memoryLocketRandomization = archipelago.MemoryLocketRandomization; craftmetalRandomization = archipelago.CraftmetalRandomization; mossberryRandomization = archipelago.MossberryRandomization; pollipHeartRandomization = archipelago.PollipHeartRandomization; silkeaterRandomization = archipelago.SilkeaterRandomization; majorKeyRandomization = archipelago.MajorKeyRandomization; toolPouchRandomization = archipelago.ToolPouchRandomization; bossSanity = archipelago.BossSanity; bellShrineSanity = archipelago.BellShrineSanity; questSanityMode = archipelago.QuestSanity; questSanity = IsRandomized(ItemType.Quest); SetRoomLocationNames(archipelago.GetRoomLocationNames()); schemaVersion = 22; } } public bool MatchesRoom(Archipelago archipelago) { if (!IsRoomBound || archipelago == null) { return true; } if (string.Equals(roomSeed, archipelago.RoomSeed, StringComparison.Ordinal) && team == archipelago.Team && slot == archipelago.Slot && string.Equals(worldVersion, archipelago.WorldVersion, StringComparison.Ordinal) && string.Equals(goal, archipelago.Goal, StringComparison.Ordinal) && (!string.Equals(goal, "flea_hunt", StringComparison.Ordinal) || fleaHuntGoalCount == archipelago.FleaHuntGoalCount) && string.Equals(startingLocation, archipelago.StartingLocation, StringComparison.Ordinal) && string.Equals(startingCrest, archipelago.StartingCrest, StringComparison.Ordinal) && splitDashAndSprint == archipelago.SplitDashAndSprint && randomizeNeedleUpgrades == archipelago.RandomizeNeedleUpgrades && startWithMaps == archipelago.StartWithMaps && automaticCompass == archipelago.AutomaticCompass && checkMapMarkers == archipelago.CheckMapMarkers && string.Equals(bellwayAccess, archipelago.BellwayAccess, StringComparison.Ordinal) && string.Equals(enemyRosaryMultiplier, archipelago.EnemyRosaryMultiplier, StringComparison.Ordinal) && string.Equals(enemyShardMultiplier, archipelago.EnemyShardMultiplier, StringComparison.Ordinal) && PurchasePriceSettingsMatch(archipelago) && fasterDialogue == archipelago.FasterDialogue && deathLink == archipelago.DeathLink && silkLink == archipelago.SilkLink && rosaryLink == archipelago.RosaryLink && shellShardLink == archipelago.ShellShardLink && individualRelicTurnIns == archipelago.IndividualRelicTurnIns) { return RandomizationModesMatch(archipelago); } return false; } private bool RandomizationModesMatch(Archipelago archipelago) { string optionName; RandomizationMode savedMode; RandomizationMode roomMode; return !TryGetRandomizationModeMismatch(archipelago, out optionName, out savedMode, out roomMode); } private bool TryGetRandomizationModeMismatch(Archipelago archipelago, out string optionName, out RandomizationMode savedMode, out RandomizationMode roomMode) { optionName = string.Empty; savedMode = RandomizationMode.Anywhere; roomMode = RandomizationMode.Anywhere; if (archipelago == null) { return false; } Tuple[] array = new Tuple[28] { Tuple.Create("skill_randomization", skillRandomization, archipelago.SkillRandomization), Tuple.Create("tool_randomization", toolRandomization, archipelago.ToolRandomization), Tuple.Create("silk_skill_randomization", silkSkillRandomization, archipelago.SilkSkillRandomization), Tuple.Create("crest_randomization", crestRandomization, archipelago.CrestRandomization), Tuple.Create("flea_randomization", fleaRandomization, archipelago.FleaRandomization), Tuple.Create("crest_slot_randomization", crestSlotRandomization, archipelago.CrestSlotRandomization), Tuple.Create("mask_shard_randomization", maskShardRandomization, archipelago.MaskShardRandomization), Tuple.Create("spool_fragment_randomization", spoolFragmentRandomization, archipelago.SpoolFragmentRandomization), Tuple.Create("silk_heart_randomization", silkHeartRandomization, archipelago.SilkHeartRandomization), Tuple.Create("bellway_randomization", bellwayRandomization, archipelago.BellwayRandomization), Tuple.Create("ventrica_randomization", ventricaRandomization, archipelago.VentricaRandomization), Tuple.Create("map_randomization", mapRandomization, archipelago.MapRandomization), Tuple.Create("melody_randomization", melodyRandomization, archipelago.MelodyRandomization), Tuple.Create("pin_randomization", pinRandomization, archipelago.PinRandomization), Tuple.Create("relic_randomization", relicRandomization, archipelago.RelicRandomization), Tuple.Create("crafting_kit_randomization", craftingKitRandomization, archipelago.CraftingKitRandomization), Tuple.Create("minor_pickup_randomization", minorPickupRandomization, archipelago.MinorPickupRandomization), Tuple.Create("simple_key_randomization", simpleKeyRandomization, archipelago.SimpleKeyRandomization), Tuple.Create("memory_locket_randomization", memoryLocketRandomization, archipelago.MemoryLocketRandomization), Tuple.Create("craftmetal_randomization", craftmetalRandomization, archipelago.CraftmetalRandomization), Tuple.Create("mossberry_randomization", mossberryRandomization, archipelago.MossberryRandomization), Tuple.Create("pollip_heart_randomization", pollipHeartRandomization, archipelago.PollipHeartRandomization), Tuple.Create("silkeater_randomization", silkeaterRandomization, archipelago.SilkeaterRandomization), Tuple.Create("major_key_randomization", majorKeyRandomization, archipelago.MajorKeyRandomization), Tuple.Create("tool_pouch_randomization", toolPouchRandomization, archipelago.ToolPouchRandomization), Tuple.Create("boss_sanity", bossSanity, archipelago.BossSanity), Tuple.Create("bell_shrine_sanity", bellShrineSanity, archipelago.BellShrineSanity), Tuple.Create("quest_sanity", questSanityMode, archipelago.QuestSanity) }; foreach (Tuple tuple in array) { if (tuple.Item2 != tuple.Item3) { optionName = tuple.Item1; savedMode = tuple.Item2; roomMode = tuple.Item3; return true; } } return false; } private bool TryGetPurchasePriceModeMismatch(Archipelago archipelago, out string optionName, out string savedMode, out string roomMode) { optionName = string.Empty; savedMode = string.Empty; roomMode = string.Empty; if (archipelago == null) { return false; } Tuple[] array = new Tuple[6] { Tuple.Create("normal_shop_prices", normalShopPrices, archipelago.NormalShopPrices), Tuple.Create("bellway_prices", bellwayPrices, archipelago.BellwayPrices), Tuple.Create("map_prices", mapPrices, archipelago.MapPrices), Tuple.Create("pin_prices", pinPrices, archipelago.PinPrices), Tuple.Create("upgrade_prices", upgradePrices, archipelago.UpgradePrices), Tuple.Create("donation_prices", donationPrices, archipelago.DonationPrices) }; foreach (Tuple tuple in array) { if (!string.Equals(tuple.Item2, tuple.Item3, StringComparison.Ordinal)) { optionName = tuple.Item1; savedMode = tuple.Item2; roomMode = tuple.Item3; return true; } } return false; } public string GetRoomMismatchMessage(Archipelago archipelago) { bool flag = archipelago != null && string.Equals(roomSeed, archipelago.RoomSeed, StringComparison.Ordinal) && team == archipelago.Team && slot == archipelago.Slot; if (flag && !string.Equals(worldVersion, archipelago.WorldVersion, StringComparison.Ordinal)) { return "This randomizer save uses APWorld version '" + (string.IsNullOrWhiteSpace(worldVersion) ? "missing" : worldVersion) + "', but the current slot uses '" + (string.IsNullOrWhiteSpace(archipelago.WorldVersion) ? "missing" : archipelago.WorldVersion) + "'. Generate a new seed with the current APWorld and start a new randomizer save."; } if (flag && !string.Equals(goal, archipelago.Goal, StringComparison.Ordinal)) { return "This randomizer save uses AP goal '" + (string.IsNullOrWhiteSpace(goal) ? "missing" : goal) + "', but the current slot uses '" + (string.IsNullOrWhiteSpace(archipelago.Goal) ? "missing" : archipelago.Goal) + "'. Start or load the save created for this slot's goal."; } if (flag && string.Equals(goal, "flea_hunt", StringComparison.Ordinal) && fleaHuntGoalCount != archipelago.FleaHuntGoalCount) { return "This randomizer save uses flea_hunt_count '" + fleaHuntGoalCount + "', but the current slot uses '" + archipelago.FleaHuntGoalCount + "'. Start or load the save created for this slot's settings."; } if (flag && !string.Equals(startingLocation, archipelago.StartingLocation, StringComparison.Ordinal)) { return "This randomizer save uses starting location '" + (string.IsNullOrWhiteSpace(startingLocation) ? "missing" : startingLocation) + "', but the current slot uses '" + ((archipelago == null || string.IsNullOrWhiteSpace(archipelago.StartingLocation)) ? "missing" : archipelago.StartingLocation) + "'. Start or load the save created for this slot's settings."; } if (flag && !string.Equals(startingCrest, archipelago.StartingCrest, StringComparison.Ordinal)) { return "This randomizer save uses starting crest '" + (string.IsNullOrWhiteSpace(startingCrest) ? "missing" : startingCrest) + "', but the current slot uses '" + ((archipelago == null || string.IsNullOrWhiteSpace(archipelago.StartingCrest)) ? "missing" : archipelago.StartingCrest) + "'. Start or load the save created for this slot's settings."; } if (flag && splitDashAndSprint != archipelago.SplitDashAndSprint) { return "This randomizer save uses split_dash_and_sprint '" + splitDashAndSprint.ToString().ToLowerInvariant() + "', but the current slot uses '" + archipelago.SplitDashAndSprint.ToString().ToLowerInvariant() + "'. Start or load the save created for this slot's settings."; } if (flag && randomizeNeedleUpgrades != archipelago.RandomizeNeedleUpgrades) { return "This randomizer save uses randomize_needle_upgrades '" + randomizeNeedleUpgrades.ToString().ToLowerInvariant() + "', but the current slot uses '" + archipelago.RandomizeNeedleUpgrades.ToString().ToLowerInvariant() + "'. Start or load the save created for this slot's settings."; } if (flag && startWithMaps != archipelago.StartWithMaps) { return GetBooleanSettingMismatchMessage("start_with_maps", startWithMaps, archipelago.StartWithMaps); } if (flag && automaticCompass != archipelago.AutomaticCompass) { return GetBooleanSettingMismatchMessage("automatic_compass", automaticCompass, archipelago.AutomaticCompass); } if (flag && checkMapMarkers != archipelago.CheckMapMarkers) { return "This randomizer save uses check_map_markers '" + GetCheckMapMarkerModeName(checkMapMarkers) + "', but the current slot uses '" + GetCheckMapMarkerModeName(archipelago.CheckMapMarkers) + "'. Start or load the save created for this slot's settings."; } if (flag && !string.Equals(bellwayAccess, archipelago.BellwayAccess, StringComparison.Ordinal)) { return "This randomizer save uses bellway_access '" + bellwayAccess + "', but the current slot uses '" + archipelago.BellwayAccess + "'. Start or load the save created for this slot's settings."; } if (flag && !string.Equals(enemyRosaryMultiplier, archipelago.EnemyRosaryMultiplier, StringComparison.Ordinal)) { return "This randomizer save uses enemy_rosary_multiplier '" + enemyRosaryMultiplier + "', but the current slot uses '" + archipelago.EnemyRosaryMultiplier + "'. Start or load the save created for this slot's settings."; } if (flag && !string.Equals(enemyShardMultiplier, archipelago.EnemyShardMultiplier, StringComparison.Ordinal)) { return "This randomizer save uses enemy_shard_multiplier '" + enemyShardMultiplier + "', but the current slot uses '" + archipelago.EnemyShardMultiplier + "'. Start or load the save created for this slot's settings."; } if (flag && TryGetPurchasePriceModeMismatch(archipelago, out var optionName, out var savedMode, out var roomMode)) { return "This randomizer save uses " + optionName + " '" + savedMode + "', but the current slot uses '" + roomMode + "'. Start or load the save created for this slot's settings."; } if (flag && !PurchasePriceSettingsMatch(archipelago)) { return "This randomizer save's resolved purchase prices do not match the current slot. Start or load the save created for this slot's settings."; } if (flag && fasterDialogue != archipelago.FasterDialogue) { return GetBooleanSettingMismatchMessage("faster_dialogue", fasterDialogue, archipelago.FasterDialogue); } if (flag && deathLink != archipelago.DeathLink) { return GetBooleanSettingMismatchMessage("death_link", deathLink, archipelago.DeathLink); } if (flag && silkLink != archipelago.SilkLink) { return GetBooleanSettingMismatchMessage("silk_link", silkLink, archipelago.SilkLink); } if (flag && rosaryLink != archipelago.RosaryLink) { return GetBooleanSettingMismatchMessage("rosary_link", rosaryLink, archipelago.RosaryLink); } if (flag && shellShardLink != archipelago.ShellShardLink) { return GetBooleanSettingMismatchMessage("shell_shard_link", shellShardLink, archipelago.ShellShardLink); } if (flag && individualRelicTurnIns != archipelago.IndividualRelicTurnIns) { return GetBooleanSettingMismatchMessage("individual_relic_turn_ins", individualRelicTurnIns, archipelago.IndividualRelicTurnIns); } if (flag && TryGetRandomizationModeMismatch(archipelago, out var optionName2, out var savedMode2, out var roomMode2)) { return "This randomizer save uses " + optionName2 + " '" + savedMode2.ToString().ToLowerInvariant() + "', but the current slot uses '" + roomMode2.ToString().ToLowerInvariant() + "'. Start or load the save created for this slot's settings."; } string text = (string.IsNullOrWhiteSpace(slotName) ? slot.ToString() : slotName); string text2 = ((archipelago == null || string.IsNullOrWhiteSpace(archipelago.SlotName)) ? "unknown" : archipelago.SlotName); return "This randomizer save belongs to AP seed '" + roomSeed + "', slot '" + text + "', but the current connection is seed '" + ((archipelago == null) ? "unknown" : archipelago.RoomSeed) + "', slot '" + text2 + "'."; } private static string GetBooleanSettingMismatchMessage(string optionName, bool savedValue, bool roomValue) { return "This randomizer save uses " + optionName + " '" + savedValue.ToString().ToLowerInvariant() + "', but the current slot uses '" + roomValue.ToString().ToLowerInvariant() + "'. Start or load the save created for this slot's settings."; } private static string GetCheckMapMarkerModeName(CheckMapMarkerMode mode) { return mode switch { CheckMapMarkerMode.MappedRooms => "mapped_rooms", CheckMapMarkerMode.OwnedMaps => "owned_maps", CheckMapMarkerMode.All => "all", _ => "off", }; } public Item GetItem(string itemName) { if (string.IsNullOrWhiteSpace(itemName)) { return null; } string canonicalName = ItemSet.GetCanonicalItemName(itemName); return items.items.FirstOrDefault((Item x) => string.Equals(x.Name, canonicalName, StringComparison.OrdinalIgnoreCase)); } public RandomizationMode GetRandomizationMode(ItemType type) { switch (type) { case ItemType.Skill: return skillRandomization; case ItemType.Tool: return toolRandomization; case ItemType.Spell: return silkSkillRandomization; case ItemType.Crest: return crestRandomization; case ItemType.Flea: return fleaRandomization; case ItemType.CrestSlot: return crestSlotRandomization; case ItemType.MaskShard: return maskShardRandomization; case ItemType.SpoolFragment: return spoolFragmentRandomization; case ItemType.SilkHeart: return silkHeartRandomization; case ItemType.Bellway: return bellwayRandomization; case ItemType.Ventrica: return ventricaRandomization; case ItemType.Map: return mapRandomization; case ItemType.Melody: return melodyRandomization; case ItemType.Pin: return pinRandomization; case ItemType.Relic: return relicRandomization; case ItemType.Upgrade: return craftingKitRandomization; case ItemType.Resource: return minorPickupRandomization; case ItemType.SimpleKey: return simpleKeyRandomization; case ItemType.MemoryLocket: return memoryLocketRandomization; case ItemType.Craftmetal: return craftmetalRandomization; case ItemType.Mossberry: return mossberryRandomization; case ItemType.PollipHeart: return pollipHeartRandomization; case ItemType.Silkeater: return silkeaterRandomization; case ItemType.MajorKey: return majorKeyRandomization; case ItemType.ToolPouch: return toolPouchRandomization; case ItemType.Boss: return bossSanity; case ItemType.BellShrine: return bellShrineSanity; case ItemType.Quest: return questSanityMode; case ItemType.NeedleUpgrade: if (!randomizeNeedleUpgrades) { return RandomizationMode.Vanilla; } return RandomizationMode.Anywhere; default: return RandomizationMode.Anywhere; } } public bool IsRandomized(ItemType type) { if (type == ItemType.Map && startWithMaps) { return true; } if (type == ItemType.Flea && string.Equals(goal, "flea_hunt", StringComparison.Ordinal)) { return true; } return GetRandomizationMode(type) != RandomizationMode.Vanilla; } internal bool NeedsStartWithMapsBootstrap() { if (!startWithMaps) { return false; } if (receivedItems == null) { return true; } return StartWithMapsItemNames.Any((string itemName) => !receivedItems.Contains(itemName)); } internal bool RecordStartWithMapsBootstrapItems() { if (!startWithMaps) { return false; } if (receivedItems == null) { receivedItems = new HashSet(StringComparer.OrdinalIgnoreCase); } bool flag = false; string[] startWithMapsItemNames = StartWithMapsItemNames; foreach (string item in startWithMapsItemNames) { flag |= receivedItems.Add(item); } return flag; } internal bool IsStartWithMapsBootstrapItem(string itemName) { if (!startWithMaps || string.IsNullOrWhiteSpace(itemName)) { return false; } return StartWithMapsItemNameSet.Contains(ItemSet.GetCanonicalItemName(itemName)); } public bool IsLocationEnabled(string locationName) { string canonicalName = LocationSet.GetCanonicalLocationName(locationName); if (string.Equals(canonicalName, "Goal", StringComparison.OrdinalIgnoreCase)) { return true; } if (randomizeNeedleUpgrades && string.Equals(canonicalName, "Wish: Pinmaster's Oil", StringComparison.OrdinalIgnoreCase)) { return false; } if (IsRandomized(ItemType.ToolPouch) && string.Equals(canonicalName, "Wish: Bugs of Pharloom", StringComparison.OrdinalIgnoreCase)) { return false; } if (ScroungeRelicTurnInManifest.IsTurnInLocation(canonicalName) || CardiniusCylinderTurnInManifest.IsTurnInLocation(canonicalName)) { return individualRelicTurnIns; } if (FleaPatches.IsNamedNpcFleaLocation(canonicalName)) { return IsLocationInSeed(canonicalName); } if (automaticCompass && string.Equals(canonicalName, "Compass", StringComparison.OrdinalIgnoreCase)) { return true; } Location location = ((locations == null) ? null : locations.Locations.FirstOrDefault((Location candidate) => string.Equals(candidate.Name, canonicalName, StringComparison.OrdinalIgnoreCase))); if (location != null) { return IsRandomized(location.Type); } return true; } public bool IsLocationChecked(string locationName) { if (!string.IsNullOrWhiteSpace(locationName)) { return checkedLocations.Contains(LocationSet.GetCanonicalLocationName(locationName)); } return false; } public void SetRoomLocationNames(IEnumerable locationNames) { roomLocationNames = new HashSet(from name in (locationNames ?? Enumerable.Empty()).Select(LocationSet.GetCanonicalLocationName) where !string.IsNullOrWhiteSpace(name) select name, StringComparer.OrdinalIgnoreCase); } public bool IsLocationInSeed(string locationName) { string canonicalLocationName = LocationSet.GetCanonicalLocationName(locationName); if (string.IsNullOrWhiteSpace(canonicalLocationName)) { return false; } if (roomLocationNames != null && roomLocationNames.Count > 0) { return roomLocationNames.Contains(canonicalLocationName); } Archipelago instance = Archipelago.Instance; if (instance != null && instance.Connected) { return instance.IsLocationInRoom(canonicalLocationName); } return false; } public void CheckLocation(string locationName) { string canonicalLocationName = LocationSet.GetCanonicalLocationName(locationName); if (!string.IsNullOrWhiteSpace(canonicalLocationName) && IsLocationEnabled(canonicalLocationName) && checkedLocations.Add(canonicalLocationName)) { if (string.Equals(canonicalLocationName, "Goal", StringComparison.OrdinalIgnoreCase)) { goalCompleted = true; } if (Archipelago.Instance != null) { Archipelago.Instance.UnlockLocation(canonicalLocationName); } } } public bool CommitReceivedItemAtIndex(int itemIndex, string itemName, bool repeatable = false) { if (itemIndex < receivedItemIndex) { return false; } if (itemIndex > receivedItemIndex) { throw new InvalidOperationException("Received AP item out of order. Expected index " + receivedItemIndex + ", got " + itemIndex + "."); } string canonicalItemName = ItemSet.GetCanonicalItemName(itemName); bool flag = receivedItems.Add(canonicalItemName); receivedItemIndex++; TryCompleteFleaHuntGoal(); return repeatable || flag; } public int GetReceivedFleaCount() { if (items == null || items.items == null || receivedItems == null) { return 0; } return items.items.Count((Item item) => item.Type == ItemType.Flea && receivedItems.Contains(item.Name)); } public int GetFleaHuntProgressCount() { if (IsRandomized(ItemType.Flea)) { return GetReceivedFleaCount(); } PlayerData instance = PlayerData.instance; if (instance == null) { return 0; } int num = Math.Max(0, Math.Min(27, instance.SavedFleasCount)); num += (HasReceivedItemOrNativeFlea("Flea: Greymoor - Kratt", instance.CaravanLechSaved) ? 1 : 0); num += (HasReceivedItemOrNativeFlea("Flea: Putrified Ducts - Vog", instance.MetTroupeHunterWild) ? 1 : 0); num += (HasReceivedItemOrNativeFlea("Flea: Memorium - Huge Flea", instance.tamedGiantFlea) ? 1 : 0); return Math.Min(30, num); } private bool HasReceivedItemOrNativeFlea(string itemName, bool nativeFlag) { if (!nativeFlag) { if (receivedItems != null) { return receivedItems.Contains(ItemSet.GetCanonicalItemName(itemName)); } return false; } return true; } public bool TryCompleteFleaHuntGoal() { if (goalCompleted || !string.Equals(goal, "flea_hunt", StringComparison.Ordinal) || !Archipelago.IsSupportedFleaHuntGoalCount(fleaHuntGoalCount) || GetFleaHuntProgressCount() < fleaHuntGoalCount) { return false; } CheckLocation("Goal"); return goalCompleted; } internal bool CacheHint(HintData hint) { if (hint == null || string.IsNullOrWhiteSpace(hint.locationName)) { return false; } string canonicalLocationName = LocationSet.GetCanonicalLocationName(hint.locationName); if (receivedHints == null) { receivedHints = new List(); } if (receivedHints.Any((HintData existingHint) => existingHint != null && string.Equals(existingHint.locationName, canonicalLocationName, StringComparison.OrdinalIgnoreCase))) { return false; } hint.locationName = canonicalLocationName; receivedHints.Add(hint); return true; } public bool GetHint(string locationName, out string user, out string item, out ItemFlags flags, bool allowNetworkRequest = true) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected I4, but got Unknown user = null; item = null; flags = (ItemFlags)0; if (string.IsNullOrWhiteSpace(locationName)) { return false; } string canonicalLocationName = LocationSet.GetCanonicalLocationName(locationName); if (receivedHints == null) { receivedHints = new List(); } HintData hintData = receivedHints.FirstOrDefault((HintData x) => string.Equals(x.locationName, canonicalLocationName, StringComparison.OrdinalIgnoreCase)); if (hintData == null && allowNetworkRequest && Archipelago.Instance != null && Archipelago.Instance.Connected) { hintData = Archipelago.Instance.GetHint(canonicalLocationName); if (hintData != null) { CacheHint(hintData); } } if (hintData == null) { return false; } user = hintData.user; item = hintData.item; flags = (ItemFlags)(int)hintData.flags; return true; } } internal sealed class ScroungeRelicTurnInEntry { internal readonly string AssetName; internal readonly string LocationName; internal ScroungeRelicTurnInEntry(string assetName, string relicDisplayName) { AssetName = assetName; LocationName = "Relic Turn-in: " + relicDisplayName; } } internal static class ScroungeRelicTurnInManifest { internal const string SceneName = "Belltown_Room_Relic"; internal const string OwnerObjectName = "Relic Dealer NPC"; internal static readonly ScroungeRelicTurnInEntry[] Entries = new ScroungeRelicTurnInEntry[15] { new ScroungeRelicTurnInEntry("Weaver Totem Witch", "Weaver Effigy (Keelal, Shellwood)"), new ScroungeRelicTurnInEntry("Bone Record Wisp Top", "Bone Scroll (Wisp Thicket)"), new ScroungeRelicTurnInEntry("Weaver Totem Bonetown_upper_room", "Weaver Effigy (Camora, Moss Grotto)"), new ScroungeRelicTurnInEntry("Seal Chit City Merchant", "Choral Commandment (Jubilana)"), new ScroungeRelicTurnInEntry("Weaver Record Conductor", "Rune Harp (High Halls)"), new ScroungeRelicTurnInEntry("Seal Chit Ward Corpse", "Choral Commandment (Western Whiteward)"), new ScroungeRelicTurnInEntry("Weaver Record Sprint_Challenge", "Rune Harp (Weavenest Cindril)"), new ScroungeRelicTurnInEntry("Weaver Record Weave_08", "Rune Harp (Weavenest Atla)"), new ScroungeRelicTurnInEntry("Bone Record Understore_Map_Room", "Bone Scroll (Underworks)"), new ScroungeRelicTurnInEntry("Bone Record Bone_East_14", "Bone Scroll (Far Fields)"), new ScroungeRelicTurnInEntry("Seal Chit Aspid_01", "Choral Commandment (Moss Grotto)"), new ScroungeRelicTurnInEntry("Seal Chit Silk Siphon", "Choral Commandment (Eastern Whiteward)"), new ScroungeRelicTurnInEntry("Bone Record Greymoor_flooded_corridor", "Bone Scroll (Greymoor)"), new ScroungeRelicTurnInEntry("Weaver Totem Slab_Bottom", "Weaver Effigy (Atla, The Slab)"), new ScroungeRelicTurnInEntry("Ancient Egg Abyss Middle", "Arcane Egg") }; private static readonly Dictionary LocationsByAsset = BuildLocationsByAsset(); private static readonly HashSet LocationNames = BuildLocationNames(); internal static IEnumerable AppendTo(IEnumerable existingLocations) { List list = new List(existingLocations); ScroungeRelicTurnInEntry[] entries = Entries; foreach (ScroungeRelicTurnInEntry scroungeRelicTurnInEntry in entries) { ScroungeRelicTurnInEntry capturedEntry = scroungeRelicTurnInEntry; list.Add(new Location(capturedEntry.LocationName, ItemType.Event, () => IsDeposited(capturedEntry.AssetName))); } return list.ToArray(); } internal static bool IsTurnInLocation(string locationName) { if (!string.IsNullOrWhiteSpace(locationName)) { return LocationNames.Contains(LocationSet.GetCanonicalLocationName(locationName)); } return false; } internal static bool TryGetLocationName(string assetName, out string locationName) { if (string.IsNullOrWhiteSpace(assetName)) { locationName = null; return false; } return LocationsByAsset.TryGetValue(assetName, out locationName); } internal static bool IsDeposited(string assetName) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) SaveState instance = SaveState.Instance; if (instance == null || !instance.individualRelicTurnIns || (Object)(object)ManagerSingleton.Instance == (Object)null || !LocationsByAsset.ContainsKey(assetName)) { return false; } CollectableRelic relic = CollectableRelicManager.GetRelic(assetName); if ((Object)(object)relic != (Object)null) { return relic.SavedData.IsDeposited; } return false; } private static Dictionary BuildLocationsByAsset() { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); ScroungeRelicTurnInEntry[] entries = Entries; foreach (ScroungeRelicTurnInEntry scroungeRelicTurnInEntry in entries) { dictionary.Add(scroungeRelicTurnInEntry.AssetName, scroungeRelicTurnInEntry.LocationName); } return dictionary; } private static HashSet BuildLocationNames() { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); ScroungeRelicTurnInEntry[] entries = Entries; foreach (ScroungeRelicTurnInEntry scroungeRelicTurnInEntry in entries) { hashSet.Add(scroungeRelicTurnInEntry.LocationName); } return hashSet; } } internal static class SilkLinkManager { private struct QueuedStorageUpdate { internal int Generation; internal int Value; internal bool IsAcknowledgement; internal int RequestedDelta; } internal struct LocalMutationState { internal bool Active; internal int Before; internal bool HasMemorySnapshot; internal int MemorySnapshotBefore; } [HarmonyPatch(typeof(PlayerData), "AddSilk", new Type[] { typeof(int) })] internal static class PlayerData_AddSilk_Patch { [HarmonyPrefix] private static void Prefix(PlayerData __instance, out LocalMutationState __state) { BeginLocalMutation(__instance, out __state); } [HarmonyPostfix] private static void Postfix(PlayerData __instance, LocalMutationState __state) { EndLocalMutation(__instance, __state); } } [HarmonyPatch(typeof(PlayerData), "TakeSilk", new Type[] { typeof(int) })] internal static class PlayerData_TakeSilk_Patch { [HarmonyPrefix] private static void Prefix(PlayerData __instance, out LocalMutationState __state) { BeginLocalMutation(__instance, out __state); } [HarmonyPostfix] private static void Postfix(PlayerData __instance, LocalMutationState __state) { EndLocalMutation(__instance, __state); } } internal const int SharedSilkCapacity = 9; internal const string StorageKey = "SilksongRandomizer:SilkLink:v1:BaseSilk"; private static readonly object QueueLock = new object(); private static readonly Queue StorageUpdates = new Queue(); private static readonly Queue StatusMessages = new Queue(); private static Action statusReporter; private static ArchipelagoSession session; private static DataStorageElement subscribedElement; private static DataStorageUpdatedHandler subscribedHandler; private static HeroController subscribedHero; private static SaveState boundSaveState; private static PlayerData boundPlayerData; private static int generation; private static int authoritativeSharedSilk; private static int pendingSharedDelta; private static int privateSilk; private static bool enabled; private static bool storageStarted; private static bool sharedValueReady; private static bool resetWhenReady; private static bool pausedAfterSendFailure; private static int offlineBaseline = -1; private static bool needsOfflineReconciliation; internal static bool IsEnabled { get { lock (QueueLock) { return enabled; } } } internal static bool IsSynchronized { get { if (IsEnabled) { return sharedValueReady; } return false; } } internal static int SharedSilk { get { if (!sharedValueReady) { return 0; } return GetPredictedSharedSilk(); } } internal static int PrivateSilk => privateSilk; internal static void Initialize(Action reporter = null) { Reset(); statusReporter = reporter; } internal static bool Configure(ArchipelagoSession connectedSession, bool shouldEnable) { Reset(); if (!shouldEnable) { return true; } if (connectedSession == null) { QueueStatus("Silk Link could not start because its Archipelago session was missing."); return false; } lock (QueueLock) { session = connectedSession; enabled = true; } QueueStatus("Silk Link enabled (experimental): the base nine Silk are shared; upgraded capacity remains individual."); return true; } internal static void Update() { FlushStatusMessages(); if (!IsEnabled) { DetachHero(); } else { if (!HasLiveGameplayContext()) { return; } SynchronizeHeroSubscription(); SaveState instance = SaveState.Instance; PlayerData instance2 = PlayerData.instance; if (instance == null || !instance.silkLink || instance2 == null) { boundSaveState = null; boundPlayerData = null; return; } if (boundSaveState != instance || boundPlayerData != instance2) { bool continuingSession = boundSaveState != null || boundPlayerData != null; BindPlayer(instance, instance2, continuingSession); } if (!storageStarted && !TryStartStorage(instance2)) { return; } ProcessStorageUpdates(instance2); if (!sharedValueReady || pausedAfterSendFailure) { return; } if (resetWhenReady) { resetWhenReady = false; ResetSharedSilkForDeath(instance2); } else if (!IsLinkSuspended(instance2)) { if (needsOfflineReconciliation) { int before = offlineBaseline; needsOfflineReconciliation = false; offlineBaseline = -1; ReconcileLocalMutation(instance2, before, MemorySequenceSync.GetPersistentSilk(instance2, instance2.silk)); } else { privateSilk = Math.Min(privateSilk, GetPrivateCapacity(instance2)); StorePrivateSilk(); ApplyLinkedSilk(instance2); } } } } internal static bool CanSynchronizeSilk(bool hasGameManager, bool isGameplayScene, bool isLoadingSceneTransition, bool isInSceneTransition, bool hasHero, bool hasToolManager) { return hasGameManager && isGameplayScene && !isLoadingSceneTransition && !isInSceneTransition && hasHero && hasToolManager; } private static bool HasLiveGameplayContext() { GameManager unsafeInstance = GameManager.UnsafeInstance; return CanSynchronizeSilk((Object)(object)unsafeInstance != (Object)null, (Object)(object)unsafeInstance != (Object)null && unsafeInstance.IsGameplayScene(), (Object)(object)unsafeInstance != (Object)null && unsafeInstance.IsLoadingSceneTransition, (Object)(object)unsafeInstance != (Object)null && unsafeInstance.IsInSceneTransition, (Object)(object)HeroController.UnsafeInstance != (Object)null, (Object)(object)ManagerSingleton.UnsafeInstance != (Object)null); } internal static void Reset() { DataStorageElement val = subscribedElement; DataStorageUpdatedHandler val2 = subscribedHandler; lock (QueueLock) { generation++; enabled = false; session = null; StorageUpdates.Clear(); StatusMessages.Clear(); } if (val != null && val2 != null) { try { val.OnValueChanged -= val2; } catch (Exception ex) { LogDirect("Silk Link cleanup warning: " + ex.Message, warning: true); } } subscribedElement = null; subscribedHandler = null; storageStarted = false; sharedValueReady = false; resetWhenReady = false; pausedAfterSendFailure = false; offlineBaseline = -1; needsOfflineReconciliation = false; authoritativeSharedSilk = 0; pendingSharedDelta = 0; privateSilk = 0; boundSaveState = null; boundPlayerData = null; DetachHero(); } internal static bool BeginLocalMutation(PlayerData playerData, out LocalMutationState state) { state = default(LocalMutationState); if (!IsEnabled || !sharedValueReady || pausedAfterSendFailure || playerData == null || playerData != boundPlayerData || IsLinkSuspended(playerData)) { return false; } state.Active = true; state.Before = playerData.silk; state.HasMemorySnapshot = MemorySequenceSync.TryCaptureSilk(playerData, out var snapshotValue); state.MemorySnapshotBefore = snapshotValue; return true; } internal static void EndLocalMutation(PlayerData playerData, LocalMutationState state) { if (state.Active && playerData != null && playerData == boundPlayerData && sharedValueReady && !pausedAfterSendFailure && !IsLinkSuspended(playerData)) { int silk = playerData.silk; if (silk != state.Before) { ReconcileLocalMutation(playerData, state.Before, silk, state.HasMemorySnapshot, state.MemorySnapshotBefore); } } } private static void ReconcileLocalMutation(PlayerData playerData, int before, int after, bool hasMemorySnapshot = false, int memorySnapshotBefore = 0) { if (playerData == null) { return; } int predictedSharedSilk = GetPredictedSharedSilk(); int num = privateSilk; int[] array = CalculateLocalReservoirs(predictedSharedSilk, privateSilk, before, after, GetPrivateCapacity(playerData)); int num2 = array[0] - predictedSharedSilk; privateSilk = array[1]; StorePrivateSilk(); if (num2 != 0 && !SendSharedDelta(num2)) { privateSilk = num; StorePrivateSilk(); if (hasMemorySnapshot) { MemorySequenceSync.RebaseSilkDelta(playerData, memorySnapshotBefore, before, after); } } else { ApplyLinkedSilk(playerData); } } internal static int[] CalculateLocalReservoirs(int shared, int personal, int before, int after, int personalCapacity) { shared = Clamp(shared, 0, 9); personalCapacity = Math.Max(0, personalCapacity); personal = Clamp(personal, 0, personalCapacity); int num = after - before; if (num > 0) { int num2 = Math.Min(num, 9 - shared); shared += num2; int num3 = num - num2; personal = Math.Min(personalCapacity, personal + num3); } else if (num < 0) { int num4 = -num; int num5 = Math.Min(num4, personal); personal -= num5; num4 -= num5; shared = Math.Max(0, shared - num4); } return new int[2] { shared, personal }; } internal static int ComposeVisibleSilk(int shared, int personal, int currentMaximum) { return Math.Min(Math.Max(0, currentMaximum), Clamp(shared, 0, 9) + Math.Max(0, personal)); } internal static bool ShouldReconcileOfflineBalance(bool continuingSession, int runtimePrivateSilk, int savedBaseline) { if (!continuingSession && runtimePrivateSilk >= 0) { return savedBaseline >= 0; } return false; } private static void BindPlayer(SaveState saveState, PlayerData playerData, bool continuingSession) { boundSaveState = saveState; boundPlayerData = playerData; offlineBaseline = saveState.silkLinkLastSyncedBalance; needsOfflineReconciliation = ShouldReconcileOfflineBalance(continuingSession, saveState.silkLinkPrivateSilk, offlineBaseline); if (!needsOfflineReconciliation) { offlineBaseline = -1; } int persistentSilk = MemorySequenceSync.GetPersistentSilk(playerData, playerData.silk); if (saveState.silkLinkPrivateSilk >= 0) { privateSilk = (IsLinkSuspended(playerData) ? saveState.silkLinkPrivateSilk : Clamp(saveState.silkLinkPrivateSilk, 0, GetPrivateCapacity(playerData))); } if (sharedValueReady) { if (saveState.silkLinkPrivateSilk < 0) { privateSilk = Clamp(persistentSilk - GetPredictedSharedSilk(), 0, GetPrivateCapacity(playerData)); StorePrivateSilk(); } if (!needsOfflineReconciliation && !IsLinkSuspended(playerData)) { ApplyLinkedSilk(playerData); } } } private static bool TryStartStorage(PlayerData playerData) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown ArchipelagoSession val; int currentGeneration; lock (QueueLock) { if (!enabled || session == null) { return false; } val = session; currentGeneration = generation; } DataStorageElement val2 = null; DataStorageUpdatedHandler val3 = null; try { val2 = val.DataStorage[(Scope)2, "SilksongRandomizer:SilkLink:v1:BaseSilk"]; val3 = (DataStorageUpdatedHandler)delegate(JToken originalValue, JToken newValue, Dictionary arguments) { QueueStorageValue(currentGeneration, newValue, isAcknowledgement: false, 0); }; val2.OnValueChanged += val3; int persistentSilk = MemorySequenceSync.GetPersistentSilk(playerData, playerData.silk); int num = (needsOfflineReconciliation ? Clamp(offlineBaseline - Math.Max(0, privateSilk), 0, 9) : Clamp(persistentSilk, 0, 9)); val2.Initialize(JToken.op_Implicit(num)); subscribedElement = val2; subscribedHandler = val3; storageStarted = true; SendSharedDelta(0); return true; } catch (Exception ex) { if (val2 != null && val3 != null) { try { val2.OnValueChanged -= val3; } catch { } } subscribedElement = null; subscribedHandler = null; storageStarted = false; lock (QueueLock) { enabled = false; } QueueStatus("Silk Link could not initialize its shared pool: " + ex.Message); return false; } } private static void ProcessStorageUpdates(PlayerData playerData) { List list = new List(); int num; lock (QueueLock) { num = generation; while (StorageUpdates.Count > 0) { list.Add(StorageUpdates.Dequeue()); } } foreach (QueuedStorageUpdate item in list) { if (item.Generation != num) { continue; } if (item.IsAcknowledgement) { pendingSharedDelta -= item.RequestedDelta; } authoritativeSharedSilk = Clamp(item.Value, 0, 9); if (!sharedValueReady) { sharedValueReady = true; if (boundSaveState != null && boundSaveState.silkLinkPrivateSilk >= 0) { privateSilk = (IsLinkSuspended(playerData) ? boundSaveState.silkLinkPrivateSilk : Clamp(boundSaveState.silkLinkPrivateSilk, 0, GetPrivateCapacity(playerData))); } else { privateSilk = Clamp(MemorySequenceSync.GetPersistentSilk(playerData, playerData.silk) - authoritativeSharedSilk, 0, GetPrivateCapacity(playerData)); } StorePrivateSilk(); } } } private static bool SendSharedDelta(int delta) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown ArchipelagoSession val; int currentGeneration; lock (QueueLock) { if (!enabled || session == null || !storageStarted) { return false; } val = session; currentGeneration = generation; } DataStorageUpdatedHandler val2 = (DataStorageUpdatedHandler)delegate(JToken originalValue, JToken newValue, Dictionary arguments) { QueueStorageValue(currentGeneration, newValue, isAcknowledgement: true, delta); }; pendingSharedDelta += delta; try { val.DataStorage[(Scope)2, "SilksongRandomizer:SilkLink:v1:BaseSilk"] = val.DataStorage[(Scope)2, "SilksongRandomizer:SilkLink:v1:BaseSilk"] + delta + Operation.Max(0) + Operation.Min(9) + Callback.Add(val2); return true; } catch (Exception ex) { pendingSharedDelta -= delta; pausedAfterSendFailure = true; QueueStatus("Silk Link paused after a shared-pool update failed; the local Silk result was preserved and will be reconciled after the link reconnects. " + ex.Message); return false; } } private static void QueueStorageValue(int callbackGeneration, JToken value, bool isAcknowledgement, int requestedDelta) { int value2; try { value2 = ((value != null) ? value.ToObject() : 0); } catch (Exception ex) { QueueStatus("Silk Link received an invalid shared value: " + ex.Message); return; } lock (QueueLock) { if (enabled && callbackGeneration == generation) { StorageUpdates.Enqueue(new QueuedStorageUpdate { Generation = callbackGeneration, Value = value2, IsAcknowledgement = isAcknowledgement, RequestedDelta = requestedDelta }); } } } private static int GetPredictedSharedSilk() { return Clamp(authoritativeSharedSilk + pendingSharedDelta, 0, 9); } private static int GetPrivateCapacity(PlayerData playerData) { if (playerData == null) { return 0; } int num; try { num = playerData.CurrentSilkMax; } catch { num = playerData.silkMax; } return Math.Max(0, num - 9); } private static bool IsLinkSuspended(PlayerData playerData) { if (playerData == null) { return true; } try { return playerData.IsAnyCursed || playerData.IsCurrentCrestTemp || playerData.UnlockSilkFinalCutscene || playerData.CurrentSilkMaxBasic < 9; } catch { return true; } } private static void ApplyLinkedSilk(PlayerData playerData) { if (playerData != null && !IsLinkSuspended(playerData)) { int currentSilkMax; try { currentSilkMax = playerData.CurrentSilkMax; } catch { return; } int num = ComposeVisibleSilk(GetPredictedSharedSilk(), privateSilk, currentSilkMax); if (playerData.silk != num) { playerData.silk = num; RefreshSilkDisplay(); } MemorySequenceSync.MirrorSilk(playerData, num); StoreBaseline(num); } } private static void SynchronizeHeroSubscription() { HeroController silentInstance = HeroController.SilentInstance; if (silentInstance != subscribedHero) { DetachHero(); subscribedHero = silentInstance; if ((Object)(object)subscribedHero != (Object)null) { subscribedHero.OnDeath += OnHeroDeath; } } } private static void DetachHero() { if ((Object)(object)subscribedHero != (Object)null) { subscribedHero.OnDeath -= OnHeroDeath; } subscribedHero = null; } private static void OnHeroDeath() { PlayerData instance = PlayerData.instance; privateSilk = 0; StorePrivateSilk(); if (!sharedValueReady) { resetWhenReady = true; if (instance != null) { instance.silk = 0; MemorySequenceSync.MirrorSilk(instance, 0); RefreshSilkDisplay(); } } else { ResetSharedSilkForDeath(instance); } } private static void ResetSharedSilkForDeath(PlayerData playerData) { privateSilk = 0; StorePrivateSilk(); int predictedSharedSilk = GetPredictedSharedSilk(); if (predictedSharedSilk > 0) { SendSharedDelta(-predictedSharedSilk); } if (playerData != null) { playerData.silk = 0; MemorySequenceSync.MirrorSilk(playerData, 0); RefreshSilkDisplay(); } } private static void RefreshSilkDisplay() { try { SilkSpool instance = SilkSpool.Instance; if ((Object)(object)instance != (Object)null) { instance.RefreshSilk(); } } catch (Exception ex) { LogDirect("Silk Link could not refresh the Silk display: " + ex.Message, warning: true); } } private static void QueueStatus(string message) { if (string.IsNullOrWhiteSpace(message)) { return; } lock (QueueLock) { StatusMessages.Enqueue(message); } } private static void StorePrivateSilk() { if (boundSaveState != null && boundSaveState.silkLink) { boundSaveState.silkLinkPrivateSilk = Math.Max(0, privateSilk); } } private static void StoreBaseline(int value) { if (boundSaveState != null && boundSaveState.silkLink) { boundSaveState.silkLinkLastSyncedBalance = Math.Max(0, value); } } private static void FlushStatusMessages() { List list = new List(); lock (QueueLock) { while (StatusMessages.Count > 0) { list.Add(StatusMessages.Dequeue()); } } foreach (string item in list) { LogDirect(item, warning: false); try { statusReporter?.Invoke(item); } catch { } } } private static void LogDirect(string message, bool warning) { if (RandomizerPlugin.Log != null) { if (warning) { RandomizerPlugin.Log.LogWarning((object)("[RANDOMIZER] " + message)); } else { RandomizerPlugin.Log.LogInfo((object)("[RANDOMIZER] " + message)); } } } private static int Clamp(int value, int minimum, int maximum) { return Math.Min(Math.Max(value, minimum), maximum); } } internal static class SimpleKeyDoorManager { private sealed class DoorDefinition { internal readonly string ItemName; internal readonly string SceneName; internal readonly string ObjectName; internal readonly string PersistentId; internal readonly string PlayerDataBool; internal bool UsesPersistentBool => !string.IsNullOrEmpty(PersistentId); internal DoorDefinition(string itemName, string sceneName, string objectName, string persistentId = null, string playerDataBool = null) { ItemName = itemName; SceneName = sceneName; ObjectName = objectName; PersistentId = persistentId; PlayerDataBool = playerDataBool; } } internal const string WormwaysKey = "Simple Key (Wormways)"; internal const string DeepDocksKey = "Simple Key (Deep Docks)"; internal const string GreenPrinceKey = "Simple Key (Green Prince)"; internal const string RosaryBankKey = "Simple Key (Rosary Bank)"; private static readonly DoorDefinition[] Doors = new DoorDefinition[4] { new DoorDefinition("Simple Key (Wormways)", "Crawl_02", "aspid_sealed_gate_stone", "aspid_sealed_gate_stone"), new DoorDefinition("Simple Key (Deep Docks)", "Room_Forge", "song_knight_lock", null, "openedSongGateDocks"), new DoorDefinition("Simple Key (Green Prince)", "Dust_02", "Key Receptactle", null, "UnlockedDustCage"), new DoorDefinition("Simple Key (Rosary Bank)", "Hang_06", "bank_door", "bank_door") }; private static readonly Dictionary DoorsByItem = BuildDoorLookup(); private static readonly FieldInfo IsActivatedField = AccessTools.Field(typeof(ItemReceptacle), "isActivated"); private static readonly MethodInfo StartedActivatedMethod = AccessTools.Method(typeof(ItemReceptacle), "StartedActivated", (Type[])null, (Type[])null); private static readonly FieldInfo InspectEventTargetField = AccessTools.Field(typeof(ItemReceptacle), "inspectEventTarget"); internal static void GrantWormwaysKey() { Grant("Simple Key (Wormways)"); } internal static void GrantDeepDocksKey() { Grant("Simple Key (Deep Docks)"); } internal static void GrantGreenPrinceKey() { Grant("Simple Key (Green Prince)"); } internal static void GrantRosaryBankKey() { Grant("Simple Key (Rosary Bank)"); } internal static bool TrySynchronizeReceivedKeys() { SaveState instance = SaveState.Instance; if (instance == null || instance.receivedItems == null) { return false; } DoorDefinition[] doors = Doors; foreach (DoorDefinition doorDefinition in doors) { if (instance.receivedItems.Contains(doorDefinition.ItemName) && !TryApplyDoor(doorDefinition, out var error)) { Debug.LogWarning((object)("[RANDOMIZER] Could not restore " + doorDefinition.ItemName + " yet: " + error)); return false; } } return true; } private static Dictionary BuildDoorLookup() { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); DoorDefinition[] doors = Doors; foreach (DoorDefinition doorDefinition in doors) { dictionary.Add(doorDefinition.ItemName, doorDefinition); } return dictionary; } private static void Grant(string itemName) { if (!DoorsByItem.TryGetValue(itemName, out var value)) { throw new ArgumentException("Unknown destination-specific Simple Key: " + itemName, "itemName"); } if (!TryApplyDoor(value, out var error)) { throw new InvalidOperationException("Could not apply " + itemName + ": " + error); } } private static bool TryApplyDoor(DoorDefinition door, out string error) { error = null; if (door.UsesPersistentBool) { if (SceneData.instance == null) { error = "SceneData is not ready."; return false; } SceneData.instance.PersistentBools.SetValue(new PersistentItemData { SceneName = door.SceneName, ID = door.PersistentId, Value = true }); } else { if (PlayerData.instance == null) { error = "PlayerData is not ready."; return false; } if (string.Equals(door.PlayerDataBool, "openedSongGateDocks", StringComparison.Ordinal)) { PlayerData.instance.openedSongGateDocks = true; } else { if (!string.Equals(door.PlayerDataBool, "UnlockedDustCage", StringComparison.Ordinal)) { error = "Unsupported PlayerData door flag: " + door.PlayerDataBool; return false; } PlayerData.instance.UnlockedDustCage = true; } } return TryOpenLoadedDoor(door, out error); } private static bool TryOpenLoadedDoor(DoorDefinition door, out string error) { error = null; ItemReceptacle val = FindLoadedReceptacle(door); if ((Object)(object)val == (Object)null) { return true; } if (IsActivatedField == null || StartedActivatedMethod == null) { error = "ItemReceptacle activation members were not found."; return false; } try { if (door.UsesPersistentBool) { PersistentBoolItem val2 = FindPersistentBool(val, door.PersistentId); if ((Object)(object)val2 == (Object)null) { error = "The loaded door's PersistentBoolItem was not found."; return false; } val2.SetValueOverride(true); ((PersistentItem)(object)val2).SaveStateNoCondition(); ((PersistentItem)(object)val2).LoadIfNeverStarted(); } if (!(bool)IsActivatedField.GetValue(val)) { PlayMakerFSM val3 = null; if (string.Equals(door.ItemName, "Simple Key (Green Prince)", StringComparison.Ordinal)) { if (InspectEventTargetField == null) { error = "ItemReceptacle.inspectEventTarget was not found."; return false; } object? value = InspectEventTargetField.GetValue(val); val3 = (PlayMakerFSM)((value is PlayMakerFSM) ? value : null); if ((Object)(object)val3 == (Object)null) { error = "The loaded Green Prince Cell Door FSM was not found."; return false; } } StartedActivatedMethod.Invoke(val, null); if (val3 != null) { val3.SendEvent("UNLOCK"); } IsActivatedField.SetValue(val, true); } return true; } catch (TargetInvocationException ex) { Exception ex2 = ex.InnerException ?? ex; error = ex2.GetType().Name + ": " + ex2.Message; return false; } catch (Exception ex3) { error = ex3.GetType().Name + ": " + ex3.Message; return false; } } private static ItemReceptacle FindLoadedReceptacle(DoorDefinition door) { //IL_002b: 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) ItemReceptacle[] array = Resources.FindObjectsOfTypeAll(); foreach (ItemReceptacle val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject == (Object)null)) { Scene scene = ((Component)val).gameObject.scene; string name = ((Scene)(ref scene)).name; if (!string.IsNullOrEmpty(name) && string.Equals(GameManager.GetBaseSceneName(name), door.SceneName, StringComparison.OrdinalIgnoreCase) && HasObjectInParentChain(((Component)val).transform, door.ObjectName)) { return val; } } } return null; } private static bool HasObjectInParentChain(Transform transform, string objectName) { Transform val = transform; while ((Object)(object)val != (Object)null) { if (string.Equals(((Object)val).name, objectName, StringComparison.Ordinal)) { return true; } val = val.parent; } return false; } private static PersistentBoolItem FindPersistentBool(ItemReceptacle receptacle, string persistentId) { Transform val = ((Component)receptacle).transform; while ((Object)(object)val != (Object)null) { PersistentBoolItem[] components = ((Component)val).GetComponents(); foreach (PersistentBoolItem val2 in components) { if ((Object)(object)val2 != (Object)null && string.Equals(((PersistentItem)(object)val2).ItemData.ID, persistentId, StringComparison.Ordinal)) { return val2; } } val = val.parent; } return null; } } internal static class StartingLocationManager { private const string TutorialSceneName = "Tut_01"; private const string BoneBottomBenchMarkerName = "RestBench"; private const int BenchRespawnType = 1; private static bool isApplying; internal static void ScheduleIfNeeded() { SaveState instance = SaveState.Instance; if (instance == null || instance.startingLocationApplied || isApplying) { return; } if (string.Equals(instance.startingLocation, "vanilla", StringComparison.Ordinal)) { instance.startingLocationApplied = true; } else if (!string.Equals(instance.startingLocation, "bone_bottom", StringComparison.Ordinal)) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogError((object)("[RANDOMIZER] Refusing unknown starting location '" + instance.startingLocation + "'.")); } } else { isApplying = true; ((MonoBehaviour)RandomizerPlugin.Instance).StartCoroutine(ApplyBoneBottomWhenReady(instance)); } } private static IEnumerator ApplyBoneBottomWhenReady(SaveState expectedState) { try { while (SaveState.Instance == expectedState && !expectedState.startingLocationApplied) { GameManager instance = GameManager.instance; string text = (((Object)(object)instance == (Object)null) ? string.Empty : instance.GetSceneNameString()); if (string.Equals(text, ResolveBoneBottomSceneName(), StringComparison.Ordinal)) { MossMotherWarpSafety.RecoverInterruptedBoneBottomWarp(); BindBoneBottomBenchRespawn(text); expectedState.startingLocationApplied = true; break; } if (!string.Equals(text, "Tut_01", StringComparison.Ordinal) || !FastTravelUtil.CanTeleportToPreferredHub(out var _)) { yield return null; continue; } ApplyBoneBottomStart(expectedState); break; } } finally { isApplying = false; } } private static void ApplyBoneBottomStart(SaveState expectedState) { //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown string text = ResolveBoneBottomSceneName(); if (string.IsNullOrWhiteSpace(text)) { throw new InvalidOperationException("Silksong could not resolve the Bone Bottom scene."); } PlayerData instance = PlayerData.instance; if (instance == null) { throw new InvalidOperationException("Silksong player data was unavailable for the start warp."); } string respawnScene = instance.respawnScene; string respawnMarkerName = instance.respawnMarkerName; int respawnType = instance.respawnType; MapZone mapZone = instance.mapZone; bool atBench = instance.atBench; BindBoneBottomBenchRespawn(text); bool flag = MossMotherWarpSafety.PrepareForBoneBottomWarp(); try { GameManager.instance.BeginSceneTransition(new SceneLoadInfo { SceneName = text, EntryGateName = "RestBench" }); expectedState.startingLocationApplied = true; ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogInfo((object)"[RANDOMIZER] Applied Bone Bottom starting location at its native RestBench respawn marker."); } } catch { instance.respawnScene = respawnScene; instance.respawnMarkerName = respawnMarkerName; instance.respawnType = respawnType; instance.mapZone = mapZone; instance.atBench = atBench; if (flag) { MossMotherWarpSafety.CancelPreparedWarp(); } throw; } } private static void BindBoneBottomBenchRespawn(string sceneName) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) PlayerData instance = PlayerData.instance; if (instance != null && !string.IsNullOrWhiteSpace(sceneName)) { instance.respawnScene = sceneName; instance.respawnMarkerName = "RestBench"; instance.respawnType = 1; instance.mapZone = (MapZone)12; instance.atBench = false; } } private static string ResolveBoneBottomSceneName() { return FastTravelScenes.GetSceneName((FastTravelLocations)1); } } internal static class StaticMapManifest { internal sealed class Source { internal readonly string SceneName; internal readonly string HierarchyPath; internal readonly float X; internal readonly float Y; internal readonly float SceneWidth; internal readonly float SceneHeight; internal readonly bool IsActThreeFallback; internal Source(string sceneName, string hierarchyPath, float x, float y, float sceneWidth, float sceneHeight, bool isActThreeFallback = false) { SceneName = sceneName; HierarchyPath = hierarchyPath; X = x; Y = y; SceneWidth = sceneWidth; SceneHeight = sceneHeight; IsActThreeFallback = isActThreeFallback; } } internal sealed class Entry { internal readonly string AssetName; internal readonly string LocationName; internal readonly string ItemName; internal readonly string PlayerDataBool; internal readonly Source[] Sources; internal Entry(string assetName, string locationName, string itemName, string playerDataBool, params Source[] sources) { AssetName = assetName; LocationName = locationName; ItemName = itemName; PlayerDataBool = playerDataBool; Sources = sources ?? Array.Empty(); } internal bool HasSourceScene(string sceneName) { if (string.IsNullOrWhiteSpace(sceneName)) { return false; } Source[] sources = Sources; foreach (Source source in sources) { if (source != null && string.Equals(source.SceneName, sceneName, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } } internal const string CradleLocationName = "Map Purchase: The Cradle"; internal static readonly Entry[] Entries = new Entry[14] { new Entry("Weavehome Map", "Map Pickup: Weavenest Atla", "Map: Weavenest Atla", "HasWeavehomeMap", new Source("Weave_12", "weaver_harp_sign_map/Get Map Inspect", 14.018001f, 11.054f, 55f, 25f)), new Entry("Song Gate Map", "Map Purchase: Grand Gate", "Map: Grand Gate", "HasSongGateMap", new Source("Song_19_entrance", "Map Machine (1)", 40.1f, 7.008f, 75f, 109f)), new Entry("Understore Map", "Map Pickup: Underworks", "Map: Underworks", "HasCitadelUnderstoreMap", new Source("Under_16", "map_collectable/Understore Map Inspect", 65.09584f, 35.32624f, 80f, 60f)), new Entry("Halls Map", "Map Purchase: Choral Chambers", "Map: Choral Chambers", "HasHallsMap", new Source("Bellway_City", "Map Machine", 102.59f, 10.04f, 112f, 36f), new Source("Song_01b", "Map Machine (2)", 102.72004f, 2.859291f, 124f, 19f)), new Entry("Library Map", "Map Purchase: Whispering Vaults", "Map: Whispering Vaults", "HasLibraryMap", new Source("Library_04", "Map Machine (1)", 10.27f, 205.83f, 67f, 220f)), new Entry("Ward Map", "Map Purchase: Whiteward", "Map: Whiteward", "HasWardMap", new Source("Ward_01", "Map Machine (1)", 54.51f, 19.98f, 75f, 111f)), new Entry("Cog Map", "Map Pickup: Cogwork Core", "Map: Cogwork Core", "HasCogMap", new Source("Cog_Bench", "Group/Collectable Item Pickup Child", 26.264f, 29.038498f, 45f, 40f)), new Entry("Arborium Map", "Map Purchase: Memorium", "Map: Memorium", "HasArboriumMap", new Source("Arborium_11", "Map Machine", 13.35f, 11.16f, 222f, 61f)), new Entry("Hang Map", "Map Purchase: High Halls", "Map: High Halls", "HasHangMap", new Source("Hang_06b", "new_scene/Map Machine", 51f, 3.067242f, 62f, 22f)), new Entry("Slab Map", "Map Pickup: The Slab", "Map: The Slab", "HasSlabMap", new Source("Slab_20", "Slab Map Inspect", 86.57f, 14.07f, 94f, 24f)), new Entry("Aqueduct Map", "Map Pickup: Putrified Ducts", "Map: Putrified Ducts", "HasAqueductMap", new Source("Aqueduct_07", "Aqueduct Map Inspect", 28.93f, 28.98f, 41f, 40f)), new Entry("Cradle Map", "Map Purchase: The Cradle", "Map: The Cradle", "HasCradleMap", new Source("Cradle_02", "Group/Map Machine (1)", 54.08f, 67.16782f, 81f, 110f), new Source("Tube_Hub", "Black Thread States/Black Thread World/Collectable Item Pickup", 27.036f, 29.396002f, 135f, 145f, isActThreeFallback: true)), new Entry("Clover Map", "Map Pickup: Verdania", "Map: Verdania", "HasCloverMap", new Source("Clover_20", "Collectable Item Pickup", 133.55f, 16.277496f, 150f, 47f)), new Entry("Abyss Map", "Map Pickup: The Abyss", "Map: The Abyss", "HasAbyssMap", new Source("Abyss_12", "Get Map Inspect", 13.940001f, 26.83f, 33f, 40f)) }; private static readonly Dictionary EntriesByAsset = BuildAssetLookup(); private static readonly Dictionary EntriesByLocation = BuildLocationLookup(); internal static bool TryGetByAssetName(string assetName, out Entry entry) { return EntriesByAsset.TryGetValue(assetName ?? string.Empty, out entry); } internal static bool TryGetByLocationName(string locationName, out Entry entry) { return EntriesByLocation.TryGetValue(locationName ?? string.Empty, out entry); } private static Dictionary BuildAssetLookup() { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); Entry[] entries = Entries; foreach (Entry entry in entries) { dictionary.Add(entry.AssetName, entry); } return dictionary; } private static Dictionary BuildLocationLookup() { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); Entry[] entries = Entries; foreach (Entry entry in entries) { dictionary.Add(entry.LocationName, entry); } return dictionary; } } internal static class ToolPouchLocationManifest { internal const string NativeAssetName = "Tool Pouch Pickup"; internal const string PilgrimsRestLocation = "Tool Pouch: Pilgrim's Rest"; internal const string LoddieLocation = "Tool Pouch: Loddie"; internal const string BugsOfPharloomLocation = "Tool Pouch: Bugs of Pharloom"; internal const string FleatopiaLocation = "Tool Pouch: Fleatopia"; internal const string LoddieScene = "Bone_12"; internal const string LoddieNpcObject = "Lady Bug Large"; internal const string LoddieNpcFsm = "Convo"; internal const string LoddieRewardState = "Reward 1"; internal const string LoddieActThreeObject = "Ladybug Craft Pickup"; internal const string LoddieActThreeFsm = "FSM"; internal const string FleatopiaScene = "Aqueduct_05_caravan"; internal const string FleatopiaNpcObject = "Caravan Troupe Leader Fleatopia NPC"; internal const string FleatopiaNpcFsm = "Dialogue"; internal const string FleatopiaRewardState = "Award Tool Pouch"; internal static readonly string[] LocationNames = new string[4] { "Tool Pouch: Pilgrim's Rest", "Tool Pouch: Loddie", "Tool Pouch: Bugs of Pharloom", "Tool Pouch: Fleatopia" }; internal static IEnumerable AppendTo(IEnumerable existing) { HashSet names = new HashSet(StringComparer.OrdinalIgnoreCase); if (existing != null) { foreach (Location item in existing) { if (item != null) { names.Add(item.Name); yield return item; } } } string[] locationNames = LocationNames; foreach (string text in locationNames) { if (names.Add(text)) { yield return new Location(text, ItemType.ToolPouch, () => false); } } } } internal static class TrapManager { internal const int RosarySpillAmount = 60; internal const float DarknessDurationSeconds = 20f; internal const float CursedCrestDurationSeconds = 120f; private const int DarknessTrapLevel = 2; private static readonly MethodInfo StartRecoilMethod = typeof(HeroController).GetMethod("StartRecoil", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[2] { typeof(CollisionSide), typeof(int) }, null); private static readonly FieldInfo RecoilRoutineField = typeof(HeroController).GetField("recoilRoutine", BindingFlags.Instance | BindingFlags.NonPublic); private static int pendingStaggerCount; private static bool darknessActive; private static bool darknessApplied; private static bool darknessWriteInProgress; private static float darknessDeadline; private static int darknessRestoreLevel; private static float cursedCrestDeadline; private static string cursedCrestInternalName = string.Empty; private static string previousCrestInternalName = string.Empty; private static string previousPreviousCrestInternalName = string.Empty; private static bool previousCrestWasTemporary; private static bool cursedCrestEquipmentRefreshPending; private static bool cursedCrestSilkRefreshPending; private static bool cursedCrestSpoolRefreshPending; private static bool pendingCursedCrest; private static bool muckmaggotActive; private static bool muckmaggotApplied; private static bool muckmaggotWriteInProgress; internal static bool IsCursedCrestActive { get; private set; } private static bool HasCursedCrestState { get { if (!IsCursedCrestActive) { return !string.IsNullOrEmpty(previousCrestInternalName); } return true; } } internal static void TriggerStagger() { if (pendingStaggerCount < int.MaxValue) { pendingStaggerCount++; } TryApplyPendingStagger(); } private static void TryApplyPendingStagger() { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) if (pendingStaggerCount <= 0) { return; } try { GameManager instance = GameManager.instance; HeroController instance2 = HeroController.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null || !instance2.CanTakeControl() || !instance2.CanCustomRecoil()) { return; } if (StartRecoilMethod == null || RecoilRoutineField == null) { pendingStaggerCount--; Warn("Stagger Trap skipped because Hornet's native damage-recoil entry point is unavailable"); return; } CollisionSide val = (CollisionSide)((!HeroControllerAbilityPatchUtil.CState(instance2, "facingRight")) ? 1 : 2); if (!(StartRecoilMethod.Invoke(instance2, new object[2] { val, 1 }) is IEnumerator enumerator)) { pendingStaggerCount--; Warn("Stagger Trap skipped because Hornet's native damage-recoil routine could not be created"); return; } Coroutine value = ((MonoBehaviour)instance2).StartCoroutine(enumerator); RecoilRoutineField.SetValue(instance2, value); CameraShake.Shake((CameraShakeCues)6); pendingStaggerCount--; } catch (Exception exception) { pendingStaggerCount--; Warn("Stagger Trap could not be applied", exception); } } internal static void TriggerRosarySpill() { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) try { PlayerData instance = PlayerData.instance; HeroController instance2 = HeroController.instance; if (instance == null || (Object)(object)instance2 == (Object)null) { return; } int num = Math.Min(60, Math.Max(0, instance.geo)); if (num <= 0) { return; } GameObject smallGeoPrefab = Gameplay.SmallGeoPrefab; if ((Object)(object)smallGeoPrefab == (Object)null) { Warn("Rosary Spill Trap skipped because the native Rosary pickup prefab is not ready"); return; } Config val = new Config { Prefab = smallGeoPrefab, AmountMin = num, AmountMax = num, SpeedMin = 10f, SpeedMax = 40f, AngleMin = 65f, AngleMax = 115f }; List list = new List(num); FlingUtils.SpawnAndFling(val, ((Component)instance2).transform, Vector3.zero, list, -1f); int num2 = Math.Min(num, list.Count); if (num2 > 0) { instance2.TakeGeo(num2); } } catch (Exception exception) { Warn("Rosary Spill Trap could not be applied", exception); } } internal static void TriggerDarkness() { try { float val = Time.unscaledTime + 20f; darknessDeadline = Math.Max(darknessDeadline, val); darknessActive = true; ApplyDarkness(); } catch (Exception exception) { Warn("Darkness Trap is waiting for the scene to become ready", exception); } } internal static void TriggerCursedCrest() { try { if (IsCursedCrestActive) { cursedCrestDeadline = Math.Max(cursedCrestDeadline, Time.unscaledTime + 120f); return; } if (NakedTrapManager.HasState) { pendingCursedCrest = true; return; } LogicAuditCloakManager.Reset(); PlayerData instance = PlayerData.instance; ToolCrest cursedCrest = Gameplay.CursedCrest; if (instance == null || (Object)(object)cursedCrest == (Object)null) { Warn("Cursed Crest Trap skipped because crest data is not ready"); return; } if (SlabCaptureWarpSafety.IsActiveSlabCaptureCrest(instance)) { Warn("Cursed Crest Trap skipped during The Slab's equipment-confiscation sequence"); return; } if (instance.IsAnyCursed || instance.IsCurrentCrestTemp) { Warn("Cursed Crest Trap skipped because a vanilla temporary or cursed crest is already active"); return; } if ((Object)(object)ToolItemManager.GetCrestByName(instance.CurrentCrestID) == (Object)null) { Warn("Cursed Crest Trap skipped because the current crest could not be resolved"); return; } cursedCrestInternalName = cursedCrest.name; previousCrestInternalName = instance.CurrentCrestID; previousPreviousCrestInternalName = instance.PreviousCrestID ?? string.Empty; previousCrestWasTemporary = instance.IsCurrentCrestTemp; SetCrest(cursedCrest, markTemporary: true); if (!string.Equals(instance.CurrentCrestID, cursedCrestInternalName, StringComparison.Ordinal)) { ClearCursedCrestState(); Warn("Cursed Crest Trap was rejected by the crest runtime"); } else { IsCursedCrestActive = true; cursedCrestDeadline = Time.unscaledTime + 120f; RequestCursedCrestRuntimeRefresh(); } } catch (Exception exception) { Warn("Cursed Crest Trap could not be applied", exception); TryRestoreCursedCrest(); } } internal static void PrepareForNativeSlabCapture() { pendingCursedCrest = false; if (HasCursedCrestState && !TryRestoreCursedCrest()) { ForceRestoreCursedCrestFieldsForSave(); } NakedTrapManager.Reset(); } internal static void RelinquishCursedCrestForNativeCurse() { pendingCursedCrest = false; if (HasCursedCrestState) { cursedCrestEquipmentRefreshPending = false; cursedCrestSilkRefreshPending = false; cursedCrestSpoolRefreshPending = false; ClearCursedCrestState(); } } internal static void TriggerMuckmaggotStatus() { try { if (!IsWreathOfPurityEquipped()) { HeroController instance = HeroController.instance; if ((Object)(object)instance == (Object)null) { Warn("Muckmaggot Status Trap skipped because the hero is not ready"); return; } if (!muckmaggotActive && HeroControllerAbilityPatchUtil.CState(instance, "isMaggoted")) { Warn("Muckmaggot Status Trap skipped because Hornet is already natively maggoted"); return; } muckmaggotActive = true; ApplyMuckmaggotStatus(); } } catch (Exception exception) { Warn("Muckmaggot Status Trap could not be applied", exception); } } internal static void Update() { TryApplyPendingStagger(); TryCompleteCursedCrestRuntimeRefresh(); NakedTrapManager.Update(); if (darknessActive) { if (Time.unscaledTime >= darknessDeadline) { RestoreDarkness(); } else { ApplyDarkness(); } } if (muckmaggotActive) { if (IsWreathOfPurityEquipped()) { RestoreMuckmaggotStatus(); } else if (!muckmaggotApplied) { ApplyMuckmaggotStatus(); } else { HeroController instance = HeroController.instance; if ((Object)(object)instance != (Object)null && !HeroControllerAbilityPatchUtil.CState(instance, "isMaggoted")) { ClearMuckmaggotState(); } } } if (!IsCursedCrestActive) { if (HasCursedCrestState) { TryRestoreCursedCrest(); } TryApplyPendingCursedCrest(); return; } PlayerData instance2 = PlayerData.instance; if (instance2 != null && !string.Equals(instance2.CurrentCrestID, cursedCrestInternalName, StringComparison.Ordinal)) { ClearCursedCrestState(); TryApplyPendingCursedCrest(); return; } if (Time.unscaledTime >= cursedCrestDeadline) { TryRestoreCursedCrest(); } TryApplyPendingCursedCrest(); } internal static void PrepareForSave() { if (HasCursedCrestState && !TryRestoreCursedCrest()) { ForceRestoreCursedCrestFieldsForSave(); } NakedTrapManager.PrepareForSave(); } internal static void ResumeAfterSave() { NakedTrapManager.ResumeAfterSave(); } internal static void ResetTransientEffects() { pendingStaggerCount = 0; RestoreDarkness(); RestoreMuckmaggotStatus(); pendingCursedCrest = false; if (HasCursedCrestState) { TryRestoreCursedCrest(); } NakedTrapManager.Reset(); } internal static void ObserveNativeDarknessRequest(ref int requestedLevel) { if (darknessActive && !darknessWriteInProgress) { darknessRestoreLevel = requestedLevel; darknessApplied = true; requestedLevel = 2; } } internal static void ObserveNativeMuckmaggotRequest(bool requested) { if (muckmaggotActive && !muckmaggotWriteInProgress) { ClearMuckmaggotState(); } } private static void ApplyDarkness() { try { int darknessLevel = DarknessRegion.GetDarknessLevel(); if (!darknessApplied || darknessLevel != 2) { darknessRestoreLevel = darknessLevel; } darknessApplied = true; if (darknessLevel != 2) { SetDarknessLevel(2); } } catch (Exception exception) { Warn("Darkness Trap could not update the native vignette", exception); } } private static void RestoreDarkness() { try { if (darknessApplied && DarknessRegion.GetDarknessLevel() == 2) { SetDarknessLevel(darknessRestoreLevel); } } catch (Exception exception) { Warn("Darkness Trap could not restore the native vignette", exception); } finally { darknessActive = false; darknessApplied = false; darknessDeadline = 0f; darknessRestoreLevel = 0; } } private static void ApplyMuckmaggotStatus() { try { HeroController instance = HeroController.instance; if ((Object)(object)instance == (Object)null) { return; } if (HeroControllerAbilityPatchUtil.CState(instance, "isMaggoted")) { if (!muckmaggotApplied) { ClearMuckmaggotState(); } } else { SetMuckmaggotStatus(instance, value: true); muckmaggotApplied = true; } } catch (Exception exception) { Warn("Muckmaggot Status Trap is waiting for the hero status runtime", exception); } } private static void RestoreMuckmaggotStatus() { try { HeroController instance = HeroController.instance; if (muckmaggotApplied && (Object)(object)instance != (Object)null && HeroControllerAbilityPatchUtil.CState(instance, "isMaggoted")) { SetMuckmaggotStatus(instance, value: false); } } catch (Exception exception) { Warn("Muckmaggot Status Trap could not restore the native status", exception); } finally { ClearMuckmaggotState(); } } private static void SetMuckmaggotStatus(HeroController hero, bool value) { muckmaggotWriteInProgress = true; try { hero.SetIsMaggoted(value); } finally { muckmaggotWriteInProgress = false; } } private static bool TryRestoreCursedCrest() { if (!IsCursedCrestActive && string.IsNullOrEmpty(previousCrestInternalName)) { return true; } try { PlayerData instance = PlayerData.instance; if (instance == null) { return false; } if (!string.Equals(instance.CurrentCrestID, cursedCrestInternalName, StringComparison.Ordinal)) { ClearCursedCrestState(); return true; } ToolCrest crestByName = ToolItemManager.GetCrestByName(previousCrestInternalName); if ((Object)(object)crestByName == (Object)null) { Warn("Cursed Crest Trap restore is waiting for the previous crest asset"); return false; } SetCrest(crestByName, previousCrestWasTemporary); if (!string.Equals(instance.CurrentCrestID, previousCrestInternalName, StringComparison.Ordinal)) { return false; } instance.PreviousCrestID = previousPreviousCrestInternalName; instance.IsCurrentCrestTemp = previousCrestWasTemporary; RequestCursedCrestRuntimeRefresh(); ClearCursedCrestState(); return true; } catch (Exception exception) { Warn("Cursed Crest Trap restore failed; it will retry", exception); return false; } } private static void SetCrest(ToolCrest crest, bool markTemporary) { if (!ToolPatches.SetRandomizerCrest(crest, markTemporary, deferRuntimeRefresh: true)) { throw new InvalidOperationException("The native crest data could not be switched."); } } private static void ForceRestoreCursedCrestFieldsForSave() { try { PlayerData instance = PlayerData.instance; if (instance != null && !string.IsNullOrEmpty(previousCrestInternalName)) { instance.CurrentCrestID = previousCrestInternalName; instance.PreviousCrestID = previousPreviousCrestInternalName; instance.IsCurrentCrestTemp = previousCrestWasTemporary; RequestCursedCrestRuntimeRefresh(); } } catch (Exception exception) { Warn("Cursed Crest save fallback failed", exception); } finally { ClearCursedCrestState(); } } private static void SetDarknessLevel(int level) { darknessWriteInProgress = true; try { DarknessRegion.SetDarknessLevel(level); } finally { darknessWriteInProgress = false; } } private static void RequestCursedCrestRuntimeRefresh() { cursedCrestEquipmentRefreshPending = true; cursedCrestSilkRefreshPending = true; cursedCrestSpoolRefreshPending = true; TryCompleteCursedCrestRuntimeRefresh(); } private static void TryCompleteCursedCrestRuntimeRefresh() { if (!cursedCrestEquipmentRefreshPending && !cursedCrestSilkRefreshPending && !cursedCrestSpoolRefreshPending) { return; } HeroController instance = HeroController.instance; if (cursedCrestEquipmentRefreshPending && (Object)(object)instance != (Object)null) { try { ToolItemManager.SendEquippedChangedEvent(true); cursedCrestEquipmentRefreshPending = false; } catch (Exception exception) { Warn("Cursed Crest Trap is waiting to refresh equipment state", exception); return; } } if (cursedCrestSilkRefreshPending && (Object)(object)instance != (Object)null) { try { instance.UpdateSilkCursed(); cursedCrestSilkRefreshPending = false; } catch (Exception exception2) { Warn("Cursed Crest Trap is waiting to refresh silk state", exception2); return; } } try { SilkSpool instance2 = SilkSpool.Instance; if (cursedCrestSpoolRefreshPending && (Object)(object)instance2 != (Object)null) { instance2.DrawSpool(); cursedCrestSpoolRefreshPending = false; } } catch (Exception exception3) { Warn("Cursed Crest Trap is waiting to redraw the silk spool", exception3); } } private static void ClearCursedCrestState() { IsCursedCrestActive = false; cursedCrestDeadline = 0f; cursedCrestInternalName = string.Empty; previousCrestInternalName = string.Empty; previousPreviousCrestInternalName = string.Empty; previousCrestWasTemporary = false; } private static void ClearMuckmaggotState() { muckmaggotActive = false; muckmaggotApplied = false; } private static bool IsWreathOfPurityEquipped() { ToolItem maggotCharm = Gameplay.MaggotCharm; if ((Object)(object)maggotCharm != (Object)null) { return ((ToolBase)maggotCharm).IsEquipped; } return false; } private static void TryApplyPendingCursedCrest() { if (pendingCursedCrest && !IsCursedCrestActive && !HasCursedCrestState && !NakedTrapManager.HasState) { pendingCursedCrest = false; TriggerCursedCrest(); } } private static void Warn(string message, Exception exception = null) { string text = ((exception == null) ? message : (message + ": " + exception.Message)); if (RandomizerPlugin.Log != null) { RandomizerPlugin.Log.LogWarning((object)("[RANDOMIZER] " + text)); } else { Debug.LogWarning((object)("[RANDOMIZER] " + text)); } } } internal static class Utils { internal static string GetHierarchyPath(Transform transform) { if ((Object)(object)transform == (Object)null) { return string.Empty; } List list = new List(); Transform val = transform; while ((Object)(object)val != (Object)null) { list.Add(((Object)val).name); val = val.parent; } list.Reverse(); return string.Join("/", list); } public static bool ForceCrest(string toolName) { ToolCrest crestByName = ToolItemManager.GetCrestByName(toolName); if ((Object)(object)crestByName == (Object)null) { Debug.LogWarning((object)("[RANDOMIZER] Could not find ToolCrest ScriptableObject: " + toolName)); return false; } bool canCrestBeUnlockedByRandomizer = ToolPatches.canCrestBeUnlockedByRandomizer; ToolPatches.canCrestBeUnlockedByRandomizer = true; try { crestByName.Unlock(); return ToolPatches.SetRandomizerCrest(crestByName, markTemporary: false); } finally { ToolPatches.canCrestBeUnlockedByRandomizer = canCrestBeUnlockedByRandomizer; } } public static T FindToolScriptableObject(string toolName) where T : ScriptableObject { T[] array = Resources.FindObjectsOfTypeAll(); foreach (T val in array) { if (!((Object)(object)val == (Object)null) && ((Object)(object)val).name == toolName) { return val; } } foreach (T val2 in array) { if (!((Object)(object)val2 == (Object)null) && string.Equals(((Object)(object)val2).name, toolName, StringComparison.OrdinalIgnoreCase)) { return val2; } } return default(T); } } internal static class WidowSequenceSafety { internal const string WidowShrineSceneName = "Belltown_Shrine"; internal const string NeedolinMemorySceneName = "Memory_Needolin"; internal const string WidowWakeGateName = "door_wakeOnGround"; internal static bool IsInNeedolinMemory() { SaveState instance = SaveState.Instance; GameManager instance2 = GameManager.instance; PlayerData instance3 = PlayerData.instance; if (instance == null || !instance.IsRandomized(ItemType.Skill) || (Object)(object)instance2 == (Object)null || instance3 == null || !instance3.spinnerDefeated || instance3.bellShrineBellhart) { return false; } string text = instance2.sceneName ?? string.Empty; if (!string.Equals(text, "Memory_Needolin", StringComparison.Ordinal)) { return text.StartsWith("Memory_Needolin_", StringComparison.Ordinal); } return true; } internal static bool CanRecoverToWidowShrine() { return IsInNeedolinMemory(); } internal static bool CanUseEmergencySwiftStep(SaveState state) { if (state != null && state.IsRandomized(ItemType.Skill)) { return IsInNeedolinMemory(); } return false; } } } namespace SilksongRandomizer.Patches { internal static class ArchipelagoJsonCompatibilityPatches { [HarmonyPatch(typeof(ArchipelagoPacketConverter), "CanConvert")] private static class ArchipelagoPacketConverter_CanConvert_Patch { [HarmonyPrefix] private static bool Prefix(Type objectType, ref bool __result) { __result = IsArchipelagoPacketType(objectType); return false; } } [HarmonyPatch(typeof(PermissionsEnumConverter), "CanConvert")] private static class PermissionsEnumConverter_CanConvert_Patch { [HarmonyPrefix] private static bool Prefix(Type objectType, ref bool __result) { __result = IsPermissionsType(objectType); return false; } } [HarmonyPatch(typeof(StringEnumConverter), "CanConvert")] private static class StringEnumConverter_CanConvert_Patch { [HarmonyPrefix] private static bool Prefix(Type objectType, ref bool __result) { if (!IsNumericProtocolEnumType(objectType)) { return true; } __result = false; return false; } } internal static bool IsPermissionsType(Type objectType) { return objectType == typeof(Permissions); } internal static bool IsNumericProtocolEnumType(Type objectType) { Type type = Nullable.GetUnderlyingType(objectType) ?? objectType; if (!(type == typeof(ItemsHandlingFlags)) && !(type == typeof(ArchipelagoClientState))) { return type == typeof(HintStatus); } return true; } internal static bool IsArchipelagoPacketType(Type objectType) { return objectType == typeof(ArchipelagoPacketBase); } } [HarmonyPatch] internal static class ArrivalWalkAreaPatches { private const string BoneBottomScene = "Bonetown"; private const string BoneBottomArrivalPath = "Walk Area Init"; private const string BellhartScene = "Belltown"; private const string BellhartCutsceneArrivalPath = "Cutscene States/Cutscene/Walk Area"; private const string BellhartNoCutsceneArrivalPath = "Cutscene States/No Cutscene/Walk Area"; private static readonly HashSet SuppressedWalkAreas = new HashSet(); [HarmonyPatch(typeof(WalkArea), "Awake")] [HarmonyPostfix] private static void AwakePostfix(WalkArea __instance) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null) { return; } Scene scene = ((Component)__instance).gameObject.scene; if (ShouldSuppressFirstArrival(((Scene)(ref scene)).name, Utils.GetHierarchyPath(((Component)__instance).transform), PlayerData.instance) && SuppressedWalkAreas.Add(((Object)__instance).GetInstanceID())) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { string[] obj = new string[5] { "[RANDOMIZER] Suppressing arrival slow-walk callbacks for '", null, null, null, null }; scene = ((Component)__instance).gameObject.scene; obj[1] = ((Scene)(ref scene)).name; obj[2] = "/"; obj[3] = Utils.GetHierarchyPath(((Component)__instance).transform); obj[4] = "'."; log.LogInfo((object)string.Concat(obj)); } } } [HarmonyPatch(typeof(WalkArea), "OnTriggerEnter2D")] [HarmonyPrefix] private static bool OnTriggerEnter2DPrefix(WalkArea __instance) { return ShouldRunNativeTrigger(__instance); } [HarmonyPatch(typeof(WalkArea), "OnTriggerStay2D")] [HarmonyPrefix] private static bool OnTriggerStay2DPrefix(WalkArea __instance) { return ShouldRunNativeTrigger(__instance); } [HarmonyPatch(typeof(WalkArea), "OnDisable")] [HarmonyPostfix] private static void OnDisablePostfix(WalkArea __instance) { if ((Object)(object)__instance != (Object)null) { SuppressedWalkAreas.Remove(((Object)__instance).GetInstanceID()); } } internal static bool ShouldRunNativeTrigger(WalkArea walkArea) { if (!((Object)(object)walkArea == (Object)null)) { return !SuppressedWalkAreas.Contains(((Object)walkArea).GetInstanceID()); } return true; } internal static bool ShouldSuppressFirstArrival(string sceneName, string hierarchyPath, PlayerData playerData) { if (string.IsNullOrEmpty(sceneName) || string.IsNullOrEmpty(hierarchyPath)) { return false; } if (string.Equals(sceneName, "Bonetown", StringComparison.Ordinal)) { return string.Equals(hierarchyPath, "Walk Area Init", StringComparison.Ordinal); } if (!string.Equals(sceneName, "Belltown", StringComparison.Ordinal)) { return false; } if (string.Equals(hierarchyPath, "Cutscene States/Cutscene/Walk Area", StringComparison.Ordinal)) { return true; } if (!string.Equals(hierarchyPath, "Cutscene States/No Cutscene/Walk Area", StringComparison.Ordinal) || playerData == null) { return false; } if (!playerData.spinnerDefeated) { return !playerData.visitedBellhartHaunted; } return !playerData.visitedBellhartSaved; } } internal static class BeastShardSourcePatches { [HarmonyPatch(typeof(HealthManager), "Die", new Type[] { typeof(float?), typeof(AttackTypes), typeof(NailElements), typeof(GameObject), typeof(bool), typeof(float), typeof(bool), typeof(bool) })] private static class ExactEnemyPreDeathPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(HealthManager __instance) { if (BeastShardSourceManifest.TryGetEnemyDropLocation(__instance, out var locationName) && IsActive(locationName) && !TryReplaceConfiguredDrop(__instance, locationName)) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Could not arm scripted Beast Shard check: " + locationName)); } } } } [HarmonyPatch(typeof(HealthManager), "OnAwake")] private static class ExactEnemyRecoveryPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(HealthManager __instance) { TryRecoverConsumedEnemySource(__instance); } } [HarmonyPatch(typeof(Archipelago), "SynchronizeSaveState")] private static class ExactEnemyConnectionRecoveryPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) HealthManager[] array = Resources.FindObjectsOfTypeAll(); foreach (HealthManager val in array) { if (!((Object)(object)val == (Object)null)) { Scene scene = ((Component)val).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { TryRecoverConsumedEnemySource(val); } } } } } [HarmonyPatch(typeof(HealthManager), "SpawnItemDrop", new Type[] { typeof(SavedItem), typeof(int), typeof(CollectableItemPickup), typeof(Transform), typeof(int) })] private static class ExactEnemyDropPatch { [HarmonyPrefix] [HarmonyPriority(0)] private static void Prefix(HealthManager __instance, ref SavedItem dropItem, int count, CollectableItemPickup prefab, int limit) { if (BeastShardSourceManifest.TryGetEnemyDropLocation(__instance, dropItem, count, prefab, limit, out var locationName) && IsActive(locationName)) { dropItem = GetProxy(locationName); } } } [HarmonyPatch(typeof(EnemyDeathEffects), "EmitCorpse")] private static class CragglerCorpseEmissionPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(EnemyDeathEffects __instance, out bool __state) { __state = IsActive("Beast Shard: Craggler") && BeastShardSourceManifest.IsExpectedCraggler(__instance); if (__state) { cragglerCorpseEmissionDepth++; TryReplaceCragglerCorpsePickup(FindPreinstantiatedCragglerCorpsePickup(__instance)); } } [HarmonyPostfix] private static void Postfix(GameObject __result, bool __state) { if (__state && !((Object)(object)__result == (Object)null)) { TryReplaceCragglerCorpsePickup(BeastShardSourceManifest.FindCragglerCorpsePickup(__result)); } } [HarmonyFinalizer] private static Exception Finalizer(Exception __exception, bool __state) { if (__state) { cragglerCorpseEmissionDepth = Math.Max(0, cragglerCorpseEmissionDepth - 1); } return __exception; } } [HarmonyPatch(typeof(CollectableItemPickup), "Awake")] private static class CragglerCorpsePickupAwakePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(CollectableItemPickup __instance) { if (cragglerCorpseEmissionDepth > 0) { TryReplaceCragglerCorpsePickup(__instance); } } } [HarmonyPatch(typeof(SprintRaceController), "get_Reward")] private static class SprintmasterTrackTwoRewardPatch { [HarmonyPostfix] [HarmonyPriority(800)] private static void Postfix(SprintRaceController __instance, ref SavedItem __result) { if (!(SprintRaceEndEventField == null) && !(SprintRaceEndCompleteEventField == null)) { string raceEndEvent = SprintRaceEndEventField.GetValue(__instance) as string; string raceEndCompleteEvent = SprintRaceEndCompleteEventField.GetValue(__instance) as string; if (BeastShardSourceManifest.IsExpectedSprintmasterTrackTwo(__instance, __result, raceEndEvent, raceEndCompleteEvent) && IsActive("Beast Shard: Sprintmaster")) { __result = GetProxy("Beast Shard: Sprintmaster"); } } } } [ThreadStatic] private static int cragglerCorpseEmissionDepth; private static readonly FieldInfo SprintRaceEndEventField = AccessTools.Field(typeof(SprintRaceController), "raceEndEvent"); private static readonly FieldInfo SprintRaceEndCompleteEventField = AccessTools.Field(typeof(SprintRaceController), "raceEndCompleteEvent"); private static readonly FieldInfo CragglerInstantiatedCorpsesField = AccessTools.Field(typeof(EnemyDeathEffects), "instantiatedCorpses"); private static readonly FieldInfo ItemDropGroupsField = AccessTools.Field(typeof(HealthManager), "itemDropGroups"); private static bool IsActive(string locationName) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Resource) && instance.IsLocationEnabled(locationName)) { return instance.IsLocationInSeed(locationName); } return false; } private static SavedItem GetProxy(string locationName) { return MinorPickupPatches.GetProxyItem(locationName); } private static bool TryReplaceConfiguredDrop(HealthManager healthManager, string locationName) { if ((Object)(object)healthManager == (Object)null || ItemDropGroupsField == null) { return false; } if (!(ItemDropGroupsField.GetValue(healthManager) is IEnumerable enumerable)) { return false; } List> list = new List>(); foreach (object item in enumerable) { if (item == null) { continue; } FieldInfo fieldInfo = AccessTools.Field(item.GetType(), "Drops"); IEnumerable enumerable2 = ((fieldInfo == null) ? null : (fieldInfo.GetValue(item) as IEnumerable)); if (enumerable2 == null) { continue; } foreach (object item2 in enumerable2) { if (TryGetExpectedConfiguredDrop(item2, out var itemField)) { list.Add(Tuple.Create(item2, itemField)); } } } if (list.Count != 1) { return false; } list[0].Item2.SetValue(list[0].Item1, GetProxy(locationName)); ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogInfo((object)("[RANDOMIZER] Armed scripted Beast Shard check: " + locationName)); } return true; } private static bool TryGetExpectedConfiguredDrop(object drop, out FieldInfo itemField) { itemField = null; if (drop == null) { return false; } Type type = drop.GetType(); FieldInfo fieldInfo = AccessTools.Field(type, "item"); FieldInfo fieldInfo2 = AccessTools.Field(type, "Amount"); FieldInfo fieldInfo3 = AccessTools.Field(type, "CustomPickupPrefab"); FieldInfo fieldInfo4 = AccessTools.Field(type, "LimitActiveInScene"); if (fieldInfo == null || fieldInfo2 == null || fieldInfo3 == null || fieldInfo4 == null) { return false; } object? value = fieldInfo.GetValue(drop); SavedItem val = (SavedItem)((value is SavedItem) ? value : null); object value2 = fieldInfo2.GetValue(drop); if ((Object)(object)val == (Object)null || value2 == null) { return false; } FieldInfo fieldInfo5 = AccessTools.Field(value2.GetType(), "Start"); FieldInfo fieldInfo6 = AccessTools.Field(value2.GetType(), "End"); if (fieldInfo5 == null || fieldInfo6 == null) { return false; } int num4; if (string.Equals(((Object)val).name, "Great Shard", StringComparison.Ordinal) && fieldInfo5.GetValue(value2) is int num && num == 1 && fieldInfo6.GetValue(value2) is int num2 && num2 == 1 && fieldInfo3.GetValue(drop) == null && fieldInfo4.GetValue(drop) is int num3) { num4 = ((num3 == 0) ? 1 : 0); if (num4 != 0) { itemField = fieldInfo; } } else { num4 = 0; } return (byte)num4 != 0; } private static bool TryRecoverConsumedEnemySource(HealthManager healthManager) { SaveState instance = SaveState.Instance; if ((Object)(object)healthManager == (Object)null || instance == null || !healthManager.isDead || !BeastShardSourceManifest.TryGetEnemyDropLocation(healthManager, out var locationName) || !IsActive(locationName) || instance.IsLocationChecked(locationName)) { return false; } instance.CheckLocation(locationName); ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogInfo((object)("[RANDOMIZER] Recovered consumed Beast Shard check: " + locationName)); } return true; } private static void TryReplaceCragglerCorpsePickup(CollectableItemPickup pickup) { SavedItem item = (((Object)(object)pickup == (Object)null) ? null : pickup.Item); if (IsActive("Beast Shard: Craggler") && BeastShardSourceManifest.IsExpectedCragglerCorpsePickup(pickup, item)) { pickup.SetItem(GetProxy("Beast Shard: Craggler"), true); } } private static CollectableItemPickup FindPreinstantiatedCragglerCorpsePickup(EnemyDeathEffects deathEffects) { if ((Object)(object)deathEffects == (Object)null || CragglerInstantiatedCorpsesField == null) { return null; } if (!(CragglerInstantiatedCorpsesField.GetValue(deathEffects) is GameObject[] array) || array.Length == 0) { return null; } return BeastShardSourceManifest.FindCragglerCorpsePickup(array[0]); } } internal static class BellhomePhaseManager { internal const string BellhomeSceneName = "Belltown_Room_Spare"; internal const string BellhomeEntryGateName = "left1"; private const string BellhartSceneName = "Belltown"; private const string BellhartEntryGateName = "door5"; private const string BellhomeBenchObjectName = "RestBench"; private const string BellhomeBenchFsmName = "Bench Control"; private const string BellhomeBenchRestingStateName = "Resting"; private const string BellhomeDoorLockObjectName = "Door Lock"; private const string BellhomeExteriorRootName = "Hornet House States"; private const string BellhomeExteriorNoneName = "None"; private const string BellhomeExteriorHalfName = "Half"; private const string BellhomeExteriorFullName = "Full"; private static readonly FieldInfo DialogueYesNoBoxInstanceField = AccessTools.Field(typeof(DialogueYesNoBox), "_instance"); private static bool promptOpen; private static bool phaseChangeInProgress; private static GameObject bellhomeExteriorRoot; private static GameObject bellhomeExteriorNone; private static GameObject bellhomeExteriorHalf; private static GameObject bellhomeExteriorFull; internal static void Update() { EnsureBellhomeUnlocked(); SaveState instance = SaveState.Instance; PlayerData instance2 = PlayerData.instance; if (instance != null && instance.IsRoomBound && instance2 != null && (instance.logicAuditMode || (instance2.blackThreadWorld && instance2.act3_wokeUp))) { instance.bellhomePhaseToggleUnlocked = true; } } internal static void EnsureBellhomeUnlocked() { SaveState instance = SaveState.Instance; PlayerData instance2 = PlayerData.instance; if (instance != null && instance.IsRoomBound && instance2 != null) { instance2.BelltownHouseUnlocked = true; EnsureBellhomeExteriorPresent(); } } private static bool EnsureBellhomeExteriorPresent() { GameManager instance = GameManager.instance; if ((Object)(object)instance == (Object)null || !string.Equals(GameManager.GetBaseSceneName(instance.sceneName ?? string.Empty), "Belltown", StringComparison.OrdinalIgnoreCase)) { ClearBellhomeExteriorCache(); return false; } if ((Object)(object)bellhomeExteriorRoot == (Object)null) { ResolveBellhomeExterior(GameObject.Find("Hornet House States")); } return ApplyBellhomeExteriorPresentation(); } internal static bool TryOverrideBellhomeExteriorActivator(TestGameObjectActivator activator) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) SaveState instance = SaveState.Instance; if (instance != null && instance.IsRoomBound && !((Object)(object)activator == (Object)null) && !((Object)(object)((Component)activator).gameObject == (Object)null) && string.Equals(((Object)((Component)activator).gameObject).name, "Hornet House States", StringComparison.Ordinal)) { Scene scene = ((Component)activator).gameObject.scene; if (string.Equals(GameManager.GetBaseSceneName(((Scene)(ref scene)).name), "Belltown", StringComparison.OrdinalIgnoreCase)) { ResolveBellhomeExterior(((Component)activator).gameObject); return ApplyBellhomeExteriorPresentation(); } } return false; } private static bool ApplyBellhomeExteriorPresentation() { if ((Object)(object)bellhomeExteriorRoot == (Object)null || (Object)(object)bellhomeExteriorNone == (Object)null || (Object)(object)bellhomeExteriorHalf == (Object)null || (Object)(object)bellhomeExteriorFull == (Object)null) { return false; } if (!bellhomeExteriorFull.activeSelf) { bellhomeExteriorFull.SetActive(true); } if (bellhomeExteriorNone.activeSelf) { bellhomeExteriorNone.SetActive(false); } if (bellhomeExteriorHalf.activeSelf) { bellhomeExteriorHalf.SetActive(false); } return true; } private static void ResolveBellhomeExterior(GameObject root) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: 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_0087: Expected O, but got Unknown if ((Object)(object)root == (Object)(object)bellhomeExteriorRoot && (Object)(object)bellhomeExteriorNone != (Object)null && (Object)(object)bellhomeExteriorHalf != (Object)null && (Object)(object)bellhomeExteriorFull != (Object)null) { return; } ClearBellhomeExteriorCache(); if ((Object)(object)root == (Object)null) { return; } Scene scene = root.scene; if (!string.Equals(GameManager.GetBaseSceneName(((Scene)(ref scene)).name), "Belltown", StringComparison.OrdinalIgnoreCase)) { return; } bellhomeExteriorRoot = root; foreach (Transform item in root.transform) { Transform val = item; if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject == (Object)null)) { if (string.Equals(((Object)val).name, "None", StringComparison.Ordinal)) { bellhomeExteriorNone = ((Component)val).gameObject; } else if (string.Equals(((Object)val).name, "Half", StringComparison.Ordinal)) { bellhomeExteriorHalf = ((Component)val).gameObject; } else if (string.Equals(((Object)val).name, "Full", StringComparison.Ordinal)) { bellhomeExteriorFull = ((Component)val).gameObject; } } } } private static void ClearBellhomeExteriorCache() { bellhomeExteriorRoot = null; bellhomeExteriorNone = null; bellhomeExteriorHalf = null; bellhomeExteriorFull = null; } internal static bool TryInterceptBellhomeNeedolin(ListenForDreamNail action) { if (!IsBellhomeBenchNeedolinListener(action) || promptOpen || phaseChangeInProgress) { return false; } SaveState instance = SaveState.Instance; PlayerData instance2 = PlayerData.instance; GameManager instance3 = GameManager.instance; if (instance == null || !instance.IsRoomBound || instance2 == null || (Object)(object)instance3 == (Object)null || !instance2.atBench || instance3.isPaused || (!instance.logicAuditMode && !instance.bellhomePhaseToggleUnlocked && !instance2.act3_wokeUp)) { return false; } InputHandler component = ((Component)instance3).GetComponent(); if ((Object)(object)component == (Object)null || component.inputActions == null || !((OneAxisInputControl)component.inputActions.DreamNail).WasPressed || !((OneAxisInputControl)component.inputActions.Dash).IsPressed || !IsDialoguePromptReady()) { return false; } bool targetBlackThreadWorld = !instance2.blackThreadWorld; string prompt = GetPrompt(targetBlackThreadWorld, instance2.act3_wokeUp); component.ForceDreamNailRePress = true; promptOpen = true; try { DialogueYesNoBox.Open((Action)delegate { promptOpen = false; BeginPhaseChange(targetBlackThreadWorld); }, (Action)delegate { promptOpen = false; }, true, prompt, (SavedItem)null); } catch (Exception ex) { promptOpen = false; ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Could not open the Bellhome phase confirmation: " + ex.Message)); } return false; } return true; } internal static bool IsBellhomeDoorLock(ItemReceptacle receptacle) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)receptacle != (Object)null && (Object)(object)((Component)receptacle).gameObject != (Object)null) { Scene scene = ((Component)receptacle).gameObject.scene; if (string.Equals(GameManager.GetBaseSceneName(((Scene)(ref scene)).name), "Belltown", StringComparison.OrdinalIgnoreCase)) { return string.Equals(((Object)((Component)receptacle).gameObject).name, "Door Lock", StringComparison.Ordinal); } } return false; } private static bool IsBellhomeBenchNeedolinListener(ListenForDreamNail action) { //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) if (action != null && (Object)(object)((FsmStateAction)action).Owner != (Object)null && ((FsmStateAction)action).Fsm != null && ((FsmStateAction)action).State != null) { Scene scene = ((FsmStateAction)action).Owner.scene; if (string.Equals(GameManager.GetBaseSceneName(((Scene)(ref scene)).name), "Belltown_Room_Spare", StringComparison.OrdinalIgnoreCase) && string.Equals(((Object)((FsmStateAction)action).Owner).name, "RestBench", StringComparison.Ordinal) && string.Equals(((FsmStateAction)action).Fsm.Name, "Bench Control", StringComparison.Ordinal)) { return string.Equals(((FsmStateAction)action).State.Name, "Resting", StringComparison.Ordinal); } } return false; } private static bool IsDialoguePromptReady() { if (DialogueYesNoBoxInstanceField == null) { return false; } object? value = DialogueYesNoBoxInstanceField.GetValue(null); return (Object)((value is DialogueYesNoBox) ? value : null) != (Object)null; } private static string GetPrompt(bool targetBlackThreadWorld, bool act3WokeUp) { if (!targetBlackThreadWorld) { return "Return Pharloom to its late Act 2 state?\n\nYour Act 3 progress will be preserved."; } if (!act3WokeUp) { return "Switch Pharloom to its Act 3 world state for logic testing?"; } return "Restore the Black Thread world and return to Act 3?\n\nYour Act 3 progress will be preserved."; } private static void BeginPhaseChange(bool targetBlackThreadWorld) { if (!phaseChangeInProgress && !((Object)(object)RandomizerPlugin.Instance == (Object)null)) { phaseChangeInProgress = true; ((MonoBehaviour)RandomizerPlugin.Instance).StartCoroutine(ApplyPhaseChange(targetBlackThreadWorld)); } } private static IEnumerator ApplyPhaseChange(bool targetBlackThreadWorld) { yield return null; SaveState state = SaveState.Instance; PlayerData playerData = PlayerData.instance; GameManager gameManager = GameManager.instance; if (state == null || !state.IsRoomBound || playerData == null || (Object)(object)gameManager == (Object)null || !IsBellhomeSceneLoaded() || !HasBellhomeEntryGate()) { phaseChangeInProgress = false; RandomizerPlugin.Instance?.ReportBlockingError("Bellhome's phase switch could not validate its safe reload point. No story state was changed."); yield break; } bool previousBlackThreadWorld = playerData.blackThreadWorld; bool previousAct3WokeUp = playerData.act3_wokeUp; bool previousToggleUnlocked = state.bellhomePhaseToggleUnlocked; state.bellhomePhaseToggleUnlocked = true; playerData.blackThreadWorld = targetBlackThreadWorld; playerData.act3_wokeUp = true; EnsureBellhomeUnlocked(); bool saveFinished = false; bool saveSucceeded = false; try { gameManager.SaveGame((Action)delegate(bool success) { saveSucceeded = success; saveFinished = true; }); } catch (Exception ex) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogError((object)("[RANDOMIZER] Bellhome phase save failed: " + ex)); } saveFinished = true; } while (!saveFinished) { yield return null; } if (!saveSucceeded) { playerData.blackThreadWorld = previousBlackThreadWorld; playerData.act3_wokeUp = previousAct3WokeUp; state.bellhomePhaseToggleUnlocked = previousToggleUnlocked; phaseChangeInProgress = false; RandomizerPlugin.Instance?.ReportBlockingError("Bellhome's phase switch could not save safely. The story phase was left unchanged."); yield break; } ManualLogSource log2 = RandomizerPlugin.Log; if (log2 != null) { log2.LogInfo((object)("[RANDOMIZER] Bellhome shifted Pharloom to the " + (targetBlackThreadWorld ? "Act 3 Black Thread" : "late Act 2") + " world phase.")); } phaseChangeInProgress = false; gameManager.ChangeToScene("Belltown_Room_Spare", "left1", 0f); } private static bool IsBellhomeSceneLoaded() { GameManager instance = GameManager.instance; if ((Object)(object)instance != (Object)null) { return string.Equals(GameManager.GetBaseSceneName(instance.sceneName ?? string.Empty), "Belltown_Room_Spare", StringComparison.OrdinalIgnoreCase); } return false; } private static bool HasBellhomeEntryGate() { return Resources.FindObjectsOfTypeAll().Any(delegate(TransitionPoint transition) { //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) if ((Object)(object)transition != (Object)null && (Object)(object)((Component)transition).gameObject != (Object)null && ((Component)transition).gameObject.activeInHierarchy) { Scene scene = ((Component)transition).gameObject.scene; if (string.Equals(GameManager.GetBaseSceneName(((Scene)(ref scene)).name), "Belltown_Room_Spare", StringComparison.OrdinalIgnoreCase) && string.Equals(((Object)((Component)transition).gameObject).name, "left1", StringComparison.Ordinal) && string.Equals(transition.targetScene, "Belltown", StringComparison.Ordinal)) { return string.Equals(transition.entryPoint, "door5", StringComparison.Ordinal); } } return false; }); } } [HarmonyPatch(typeof(ListenForDreamNail), "OnUpdate")] internal static class BellhomeNeedolinPhaseTogglePatch { private static bool Prefix(ListenForDreamNail __instance) { return !BellhomePhaseManager.TryInterceptBellhomeNeedolin(__instance); } } [HarmonyPatch(typeof(ItemReceptacle), "Start")] internal static class BellhomeDoorAlwaysUnlockedPatch { private static void Prefix(ItemReceptacle __instance) { if (BellhomePhaseManager.IsBellhomeDoorLock(__instance)) { BellhomePhaseManager.EnsureBellhomeUnlocked(); } } } [HarmonyPatch(typeof(TestGameObjectActivator), "Evaluate")] internal static class BellhomeExteriorAlwaysPresentPatch { private static bool Prefix(TestGameObjectActivator __instance) { return !BellhomePhaseManager.TryOverrideBellhomeExteriorActivator(__instance); } } internal static class CheckMapMarkerManager { private sealed class MarkerRecord { internal readonly List LocationNames; internal readonly GameMapScene Scene; internal readonly GameObject MarkerObject; internal readonly SpriteRenderer Renderer; internal readonly SpriteRenderer OutlineRenderer; internal readonly MapMarkerPositionConfidence Confidence; internal readonly Vector2 MapPosition; internal string LocationName { get { if (LocationNames.Count <= 0) { return string.Empty; } return LocationNames[0]; } } internal MarkerRecord(string locationName, GameMapScene scene, GameObject markerObject, SpriteRenderer renderer, SpriteRenderer outlineRenderer, MapMarkerPositionConfidence confidence, Vector2 mapPosition) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) LocationNames = new List { locationName }; Scene = scene; MarkerObject = markerObject; Renderer = renderer; OutlineRenderer = outlineRenderer; Confidence = confidence; MapPosition = mapPosition; } internal void AddLocation(string locationName) { if (!LocationNames.Any((string name) => string.Equals(name, locationName, StringComparison.OrdinalIgnoreCase))) { LocationNames.Add(locationName); } } internal void RemoveLocation(string locationName) { LocationNames.RemoveAll((string name) => string.Equals(name, locationName, StringComparison.OrdinalIgnoreCase)); } } private sealed class DesiredMarker { internal readonly string LocationName; internal readonly GameMapScene Scene; internal readonly Vector2 MapPosition; internal readonly MapMarkerPositionConfidence Confidence; internal DesiredMarker(string locationName, GameMapScene scene, Vector2 mapPosition, MapMarkerPositionConfidence confidence) { //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) LocationName = locationName; Scene = scene; MapPosition = mapPosition; Confidence = confidence; } } private static readonly Dictionary> MarkersByLocation = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary ReachabilityByLocation = new Dictionary(StringComparer.OrdinalIgnoreCase); private const float AnchorToleranceSquared = 1E-06f; private static readonly FieldInfo MapZoneInfoField = AccessTools.Field(typeof(GameMap), "mapZoneInfo"); private static readonly FieldInfo MarkerParentField = AccessTools.Field(typeof(GameMap), "markerParent"); private static readonly FieldInfo CompassIconField = AccessTools.Field(typeof(GameMap), "compassIcon"); private static readonly FieldInfo MapManagerField = AccessTools.Field(typeof(GameMap), "mapManager"); private static readonly FieldInfo MapCameraField = AccessTools.Field(typeof(InventoryMapManager), "mapCamera"); private static readonly FieldInfo MarkerScrollAreaField = AccessTools.Field(typeof(InventoryMapManager), "markerScrollArea"); private static readonly FieldInfo GameMapMarkerScrollAreaField = AccessTools.Field(typeof(GameMap), "mapMarkerScrollArea"); private static readonly FieldInfo MapMarkerBoundsField = AccessTools.Field(typeof(GameMap), "MapMarkerBounds"); private static readonly FieldInfo ZoomedBoundsField = AccessTools.Field(typeof(GameMap), "ZoomedBounds"); private static GameMap currentMap; private static Transform nativeMarkerParent; private static SpriteRenderer nativePinRenderer; private static bool loggedMapOwnershipFailure; private static bool loggedMarkerHostFailure; private static bool loggedPanBoundsFailure; private static bool loggedBuildSummary; private static bool markerStatesDirty; private static int lastMarkerStateRefreshFrame = -1; private static int lastTooltipHitTestFrame = -1; internal static void Refresh(GameMap map) { if ((Object)(object)map == (Object)null) { Clear(); return; } SaveState instance = SaveState.Instance; if (instance == null || instance.checkMapMarkers == CheckMapMarkerMode.Off) { Clear(); currentMap = map; return; } if ((Object)(object)currentMap == (Object)null || (Object)(object)currentMap != (Object)(object)map) { Clear(); currentMap = map; } BuildMarkers(map, instance); RefreshMarkerStates(map, instance); } private static void RefreshMarkerStates(GameMap map, SaveState state) { if ((Object)(object)map == (Object)null || state == null) { return; } IReadOnlyDictionary readOnlyDictionary = MapLogicEvaluator.EvaluateAll(state, MarkersByLocation.Keys); ReachabilityByLocation.Clear(); foreach (KeyValuePair item in readOnlyDictionary) { ReachabilityByLocation[item.Key] = item.Value; } foreach (MarkerRecord uniqueMarker in GetUniqueMarkers()) { RefreshMarkerState(map, state, uniqueMarker, readOnlyDictionary); } IncludeVisibleMarkersInPanBounds(map); markerStatesDirty = false; lastMarkerStateRefreshFrame = Time.frameCount; } private static void RefreshMarkerState(GameMap map, SaveState state, MarkerRecord marker, IReadOnlyDictionary reachabilityByLocation) { //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)map == (Object)null || state == null || marker == null) { return; } List list = GetActiveLocationNames(state, marker).ToList(); bool flag = ShouldShowMarker(state, map, marker, list.Count > 0); bool logicUnknown = list.Any((string locationName) => MapLogicEvaluator.RequiresLogicVerification(state, locationName)); Sprite markerSprite = GetMarkerSprite(logicUnknown); if ((Object)(object)marker.Renderer != (Object)null && (Object)(object)markerSprite != (Object)null) { marker.Renderer.sprite = markerSprite; SetMarkerWorldScale(((Component)marker.Renderer).transform, map, markerSprite); MapCheckReachability value; float num = ((list.Count > 0 && list.All((string locationName) => reachabilityByLocation != null && reachabilityByLocation.TryGetValue(locationName, out value) && value == MapCheckReachability.Unreachable)) ? 0.48f : 1f); marker.Renderer.color = new Color(1f, 1f, 1f, num); } bool flag2 = list.Any((string locationName) => HasCachedHint(state, locationName)); if ((Object)(object)marker.OutlineRenderer != (Object)null) { Sprite markerOutlineSprite = GetMarkerOutlineSprite(logicUnknown); if ((Object)(object)markerOutlineSprite != (Object)null) { marker.OutlineRenderer.sprite = markerOutlineSprite; } ((Component)marker.OutlineRenderer).gameObject.SetActive(flag && flag2 && (Object)(object)markerOutlineSprite != (Object)null); marker.OutlineRenderer.color = Color.white; } if ((Object)(object)marker.MarkerObject != (Object)null && marker.MarkerObject.activeSelf != flag) { marker.MarkerObject.SetActive(flag); } } internal static void RefreshCurrentMap() { GameMap val = currentMap; if ((Object)(object)val != (Object)null) { Refresh(val); } } internal static void RefreshCurrentMarkerStates() { RequestCurrentMarkerStateRefresh(); ProcessPendingMarkerStateRefresh(currentMap); } internal static void RequestCurrentMarkerStateRefresh() { markerStatesDirty = true; } internal static void ProcessPendingMarkerStateRefresh(GameMap map) { if (!markerStatesDirty || (Object)(object)map == (Object)null || (Object)(object)map != (Object)(object)currentMap || lastMarkerStateRefreshFrame == Time.frameCount) { return; } Camera mapCamera = GetMapCamera(map); if (!((Object)(object)mapCamera == (Object)null) && ((Behaviour)mapCamera).isActiveAndEnabled) { SaveState instance = SaveState.Instance; if (instance != null) { RefreshMarkerStates(map, instance); } } } internal static void RefreshHintedLocation(string locationName) { GameMap val = currentMap; SaveState instance = SaveState.Instance; string canonicalLocationName = LocationSet.GetCanonicalLocationName(locationName); if (!((Object)(object)val == (Object)null) && instance != null && !string.IsNullOrWhiteSpace(canonicalLocationName) && MarkersByLocation.TryGetValue(canonicalLocationName, out var value)) { MarkerRecord[] array = value.Where((MarkerRecord markerRecord) => markerRecord != null).Distinct().ToArray(); foreach (MarkerRecord marker in array) { RefreshMarkerState(val, instance, marker, ReachabilityByLocation); } } } internal static void HideCheckedLocation(string locationName) { GameMap val = currentMap; SaveState instance = SaveState.Instance; string canonicalLocationName = LocationSet.GetCanonicalLocationName(locationName); if (!((Object)(object)val != (Object)null) || instance == null || string.IsNullOrWhiteSpace(canonicalLocationName) || !MarkersByLocation.TryGetValue(canonicalLocationName, out var value)) { return; } MarkerRecord[] array = value.ToArray(); foreach (MarkerRecord markerRecord in array) { if (markerRecord != null) { RefreshMarkerState(val, instance, markerRecord, ReachabilityByLocation); } } } internal static void Clear(GameMap map = null) { if ((Object)(object)map != (Object)null && (Object)(object)currentMap != (Object)null && (Object)(object)map != (Object)(object)currentMap) { return; } foreach (MarkerRecord uniqueMarker in GetUniqueMarkers()) { if ((Object)(object)uniqueMarker.MarkerObject != (Object)null) { Object.Destroy((Object)(object)uniqueMarker.MarkerObject); } } MarkersByLocation.Clear(); ReachabilityByLocation.Clear(); currentMap = null; nativeMarkerParent = null; nativePinRenderer = null; loggedMapOwnershipFailure = false; loggedMarkerHostFailure = false; loggedPanBoundsFailure = false; loggedBuildSummary = false; markerStatesDirty = false; lastMarkerStateRefreshFrame = -1; lastTooltipHitTestFrame = -1; } private static void BuildMarkers(GameMap map, SaveState state) { //IL_02e5: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_0305: Unknown result type (might be due to invalid IL or missing references) //IL_0349: Unknown result type (might be due to invalid IL or missing references) Dictionary dictionary = (from val in ((Component)map).GetComponentsInChildren(true) where (Object)(object)val != (Object)null select val).GroupBy((GameMapScene val) => val.Name, StringComparer.OrdinalIgnoreCase).ToDictionary, string, GameMapScene>((IGrouping group) => group.Key, (IGrouping group) => group.First(), StringComparer.OrdinalIgnoreCase); if (!TryResolveNativeMarkerHost(map)) { if (!loggedMarkerHostFailure) { loggedMarkerHostFailure = true; ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)"[RANDOMIZER] AP map-marker host was not found; check markers cannot be rendered."); } } return; } int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; int num7 = 0; int num8 = 0; List list = CheckMapMarkerManifest.GetPositions().ToList(); List list2 = new List(); foreach (MapCheckPosition item in list) { MapCheckPosition mapCheckPosition = CheckMapMarkerManifest.ResolveCurrentWorldVariant(item); num++; string canonicalName = LocationSet.GetCanonicalLocationName(mapCheckPosition.LocationName); GameMapScene scene; Vector2 mapPosition; if (string.IsNullOrWhiteSpace(canonicalName)) { num3++; } else if (!state.IsLocationEnabled(canonicalName) || !state.IsLocationInSeed(canonicalName) || state.IsLocationChecked(canonicalName)) { num3++; } else if (!dictionary.TryGetValue(mapCheckPosition.SceneName, out scene)) { num4++; } else if (!TryProjectPosition(map, scene, mapCheckPosition, out mapPosition)) { num5++; } else if (!list2.Any((DesiredMarker desired) => string.Equals(desired.LocationName, canonicalName, StringComparison.OrdinalIgnoreCase) && IsSameAnchor(desired.Scene, desired.MapPosition, scene, mapPosition))) { list2.Add(new DesiredMarker(canonicalName, scene, mapPosition, mapCheckPosition.Confidence)); } } KeyValuePair>[] array = MarkersByLocation.ToArray(); for (int num9 = 0; num9 < array.Length; num9++) { KeyValuePair> pair = array[num9]; MarkerRecord[] array2 = pair.Value.ToArray(); foreach (MarkerRecord marker in array2) { if (!list2.Any((DesiredMarker desired) => string.Equals(desired.LocationName, pair.Key, StringComparison.OrdinalIgnoreCase) && IsSameAnchor(desired.Scene, desired.MapPosition, marker.Scene, marker.MapPosition))) { DetachLocation(pair.Key, marker); num8++; } } } foreach (DesiredMarker item2 in list2) { if (TryFindLocationMarkerAtAnchor(item2.LocationName, item2.Scene, item2.MapPosition, out var _)) { num2++; continue; } if (TryFindMarkerAtAnchor(item2.Scene, item2.MapPosition, out var marker3)) { AssociateLocation(item2.LocationName, marker3); num7++; continue; } bool logicUnknown = MapLogicEvaluator.RequiresLogicVerification(state, item2.LocationName); MarkerRecord markerRecord = CreateMarker(map, item2.Scene, item2.LocationName, item2.MapPosition, GetMarkerSprite(logicUnknown), GetMarkerOutlineSprite(logicUnknown), item2.Confidence); if (markerRecord != null) { AssociateLocation(item2.LocationName, markerRecord); num6++; } } if (!loggedBuildSummary && state.roomLocationNames != null && state.roomLocationNames.Count > 0) { loggedBuildSummary = true; ManualLogSource log2 = RandomizerPlugin.Log; if (log2 != null) { log2.LogInfo((object)("[RANDOMIZER] AP map markers: " + GetUniqueMarkers().Count() + " anchors for " + MarkersByLocation.Count + " active checks (" + num6 + " newly created, " + num7 + " grouped, " + num8 + " stale removed) from " + num + " supported positions; " + num3 + " checked/not in seed, " + num4 + " scene misses, " + num5 + " projection misses, " + num2 + " already present; mode=" + state.checkMapMarkers.ToString() + ".")); } } } private static MarkerRecord CreateMarker(GameMap map, GameMapScene scene, string locationName, Vector2 mapPosition, Sprite sprite, Sprite outlineSprite, MapMarkerPositionConfidence confidence) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)map == (Object)null || (Object)(object)scene == (Object)null || (Object)(object)sprite == (Object)null) { return null; } GameObject val = new GameObject("AP Check: " + locationName); val.SetActive(false); val.layer = ((Component)nativePinRenderer).gameObject.layer; val.transform.SetParent(nativeMarkerParent, false); Vector3 val2 = ((Component)map).transform.TransformPoint(new Vector3(mapPosition.x, mapPosition.y, -1f)); val.transform.localPosition = nativeMarkerParent.InverseTransformPoint(val2); SpriteRenderer val3 = val.AddComponent(); val3.sprite = sprite; val3.color = Color.white; if ((Object)(object)nativePinRenderer != (Object)null) { ((Renderer)val3).sharedMaterial = ((Renderer)nativePinRenderer).sharedMaterial; MaterialPropertyBlock val4 = new MaterialPropertyBlock(); ((Renderer)nativePinRenderer).GetPropertyBlock(val4); ((Renderer)val3).SetPropertyBlock(val4); ((Renderer)val3).sortingLayerID = ((Renderer)nativePinRenderer).sortingLayerID; ((Renderer)val3).sortingOrder = Math.Max(20, ((Renderer)nativePinRenderer).sortingOrder + 1); val3.maskInteraction = nativePinRenderer.maskInteraction; val3.spriteSortPoint = nativePinRenderer.spriteSortPoint; } else { ((Renderer)val3).sortingOrder = 20; } SetMarkerWorldScale(val.transform, map, sprite); SpriteRenderer val5 = null; if ((Object)(object)outlineSprite != (Object)null) { GameObject val6 = new GameObject("Hint Outline") { layer = val.layer }; val6.transform.SetParent(val.transform, false); val6.transform.localPosition = new Vector3(0f, 0f, -0.01f); val5 = val6.AddComponent(); val5.sprite = outlineSprite; val5.color = Color.white; ((Renderer)val5).sortingLayerID = ((Renderer)val3).sortingLayerID; ((Renderer)val5).sortingOrder = ((Renderer)val3).sortingOrder + 1; val5.maskInteraction = val3.maskInteraction; val5.spriteSortPoint = val3.spriteSortPoint; val6.SetActive(false); } return new MarkerRecord(locationName, scene, val, val3, val5, confidence, mapPosition); } private static bool TryFindMarkerAtAnchor(GameMapScene scene, Vector2 mapPosition, out MarkerRecord marker) { //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) marker = GetUniqueMarkers().FirstOrDefault((MarkerRecord candidate) => candidate != null && IsSameAnchor(candidate.Scene, candidate.MapPosition, scene, mapPosition)); return marker != null; } private static bool TryFindLocationMarkerAtAnchor(string locationName, GameMapScene scene, Vector2 mapPosition, out MarkerRecord marker) { //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) marker = null; if (!MarkersByLocation.TryGetValue(locationName, out var value)) { return false; } marker = value.FirstOrDefault((MarkerRecord candidate) => candidate != null && IsSameAnchor(candidate.Scene, candidate.MapPosition, scene, mapPosition)); return marker != null; } private static bool IsSameAnchor(GameMapScene leftScene, Vector2 leftPosition, GameMapScene rightScene, Vector2 rightPosition) { //IL_0009: 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_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)leftScene == (Object)(object)rightScene) { Vector2 val = leftPosition - rightPosition; return ((Vector2)(ref val)).sqrMagnitude <= 1E-06f; } return false; } private static void AssociateLocation(string locationName, MarkerRecord marker) { if (!string.IsNullOrWhiteSpace(locationName) && marker != null) { if (!MarkersByLocation.TryGetValue(locationName, out var value)) { value = new List(); MarkersByLocation.Add(locationName, value); } if (!value.Contains(marker)) { value.Add(marker); } marker.AddLocation(locationName); } } private static void DetachLocation(string locationName, MarkerRecord marker) { if (MarkersByLocation.TryGetValue(locationName, out var value)) { value.Remove(marker); if (value.Count == 0) { MarkersByLocation.Remove(locationName); } } if (marker != null) { marker.RemoveLocation(locationName); if (marker.LocationNames.Count == 0 && (Object)(object)marker.MarkerObject != (Object)null) { Object.Destroy((Object)(object)marker.MarkerObject); } } } private static IEnumerable GetUniqueMarkers() { return (from marker in MarkersByLocation.Values.SelectMany((List markers) => markers) where marker != null select marker).Distinct(); } private static IEnumerable GetActiveLocationNames(SaveState state, MarkerRecord marker) { if (state == null || marker == null) { yield break; } foreach (string locationName in marker.LocationNames) { if (state.IsLocationEnabled(locationName) && state.IsLocationInSeed(locationName) && !state.IsLocationChecked(locationName)) { yield return locationName; } } } private static bool TryResolveNativeMarkerHost(GameMap map) { if ((Object)(object)map == (Object)null) { return false; } if ((Object)(object)nativeMarkerParent != (Object)null && (Object)(object)nativePinRenderer != (Object)null) { return true; } object? obj = MarkerParentField?.GetValue(map); nativeMarkerParent = (Transform)((obj is Transform) ? obj : null); object? obj2 = CompassIconField?.GetValue(map); object? obj3 = ((obj2 is GameObject) ? obj2 : null); Transform val = ((obj3 != null) ? ((GameObject)obj3).transform.parent : null); if ((Object)(object)nativeMarkerParent == (Object)null && (Object)(object)val != (Object)null) { nativeMarkerParent = ((IEnumerable)val).Cast().FirstOrDefault((Func)((Transform child) => (Object)(object)child != (Object)null && string.Equals(((Object)child).name, "Map Markers", StringComparison.OrdinalIgnoreCase))); } if ((Object)(object)nativeMarkerParent == (Object)null) { nativeMarkerParent = ((IEnumerable)((Component)map).GetComponentsInChildren(true)).FirstOrDefault((Func)((Transform child) => (Object)(object)child != (Object)null && string.Equals(((Object)child).name, "Map Markers", StringComparison.OrdinalIgnoreCase))); } nativePinRenderer = (from Transform child in ((IEnumerable)nativeMarkerParent)? select (child == null) ? null : ((Component)child).GetComponent()).FirstOrDefault((Func)((SpriteRenderer renderer) => (Object)(object)renderer != (Object)null)); if ((Object)(object)nativePinRenderer == (Object)null) { Transform obj4 = nativeMarkerParent; nativePinRenderer = ((obj4 != null) ? ((IEnumerable)((Component)obj4).GetComponentsInChildren(true)).FirstOrDefault((Func)((SpriteRenderer renderer) => (Object)(object)renderer != (Object)null)) : null); } if ((Object)(object)nativeMarkerParent != (Object)null) { return (Object)(object)nativePinRenderer != (Object)null; } return false; } private static bool TryProjectPosition(GameMap map, GameMapScene scene, MapCheckPosition position, out Vector2 mapPosition) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0115: 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_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) mapPosition = Vector2.zero; if ((Object)(object)map == (Object)null || (Object)(object)scene == (Object)null) { return false; } if (CheckMapMarkerManifest.TryGetDirectMapPosition(position.LocationName, out mapPosition)) { if (IsFinite(mapPosition.x)) { return IsFinite(mapPosition.y); } return false; } if ((Object)(object)((Component)scene).transform.parent == (Object)null) { return false; } Vector3 localPosition = ((Component)scene).transform.localPosition; Vector3 localPosition2 = ((Component)scene).transform.parent.localPosition; Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(localPosition.x + localPosition2.x, localPosition.y + localPosition2.y); if ((Object)(object)scene.BoundsSprite == (Object)null) { mapPosition = val; if (IsFinite(mapPosition.x)) { return IsFinite(mapPosition.y); } return false; } if (position.SceneSize.x <= 0f || position.SceneSize.y <= 0f) { return false; } Bounds bounds = scene.BoundsSprite.bounds; Vector2 val2 = Vector2.op_Implicit(((Bounds)(ref bounds)).size) * Vector2.op_Implicit(((Component)scene).transform.localScale); mapPosition = new Vector2(val.x - val2.x * 0.5f + position.PositionInScene.x / position.SceneSize.x * val2.x, val.y - val2.y * 0.5f + position.PositionInScene.y / position.SceneSize.y * val2.y); if (IsFinite(mapPosition.x)) { return IsFinite(mapPosition.y); } return false; } private static bool ShouldShowMarker(SaveState state, GameMap map, MarkerRecord marker, bool hasActiveLocations) { if (state == null || (Object)(object)map == (Object)null || marker == null || (Object)(object)marker.Scene == (Object)null || !hasActiveLocations) { return false; } return state.checkMapMarkers switch { CheckMapMarkerMode.MappedRooms => marker.Scene.IsMapped, CheckMapMarkerMode.OwnedMaps => IsAreaMapOwned(map, marker.Scene), CheckMapMarkerMode.All => true, _ => false, }; } private static bool IsAreaMapOwned(GameMap map, GameMapScene scene) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected I4, but got Unknown PlayerData instance = PlayerData.instance; if ((Object)(object)map == (Object)null || (Object)(object)scene == (Object)null || instance == null) { return false; } if (instance.mapAllRooms) { return true; } try { Array array = MapZoneInfoField?.GetValue(map) as Array; int num = (int)map.GetMapZoneForScene(((Component)scene).transform); if (array == null || num < 0 || num >= array.Length) { return false; } object value = array.GetValue(num); if (!(AccessTools.Field(value.GetType(), "Parents")?.GetValue(value) is IEnumerable enumerable)) { return false; } foreach (object item in enumerable) { if (item != null) { Type type = item.GetType(); object? obj = AccessTools.Field(type, "Parent")?.GetValue(item); GameObject val = (GameObject)((obj is GameObject) ? obj : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)val.transform != (Object)(object)((Component)scene).transform.parent)) { string text = AccessTools.Field(type, "PlayerDataBool")?.GetValue(item) as string; return !string.IsNullOrWhiteSpace(text) && instance.GetBool(text); } } } } catch (Exception ex) { if (!loggedMapOwnershipFailure) { loggedMapOwnershipFailure = true; ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Could not inspect map ownership; Owned Maps check markers will fail closed: " + ex.Message)); } } } return false; } private static Sprite GetMarkerSprite(bool logicUnknown) { RandomizerPlugin instance = RandomizerPlugin.Instance; if (!logicUnknown) { object obj = instance?.MapCheckIcon; if (obj == null) { if (instance == null) { return null; } obj = instance.ArchipelagoIcon; } return (Sprite)obj; } object obj2 = instance?.LogicUnknownIcon; if (obj2 == null) { obj2 = instance?.MapCheckIcon; if (obj2 == null) { if (instance == null) { return null; } obj2 = instance.ArchipelagoIcon; } } return (Sprite)obj2; } private static Sprite GetMarkerOutlineSprite(bool logicUnknown) { RandomizerPlugin instance = RandomizerPlugin.Instance; if (!logicUnknown) { return instance?.MapCheckOutlineIcon; } object obj = instance?.LogicUnknownOutlineIcon; if (obj == null) { if (instance == null) { return null; } obj = instance.MapCheckOutlineIcon; } return (Sprite)obj; } private static bool HasCachedHint(SaveState state, string locationName) { if (state == null) { return false; } return state.receivedHints?.Any((SaveState.HintData hint) => hint != null && string.Equals(LocationSet.GetCanonicalLocationName(hint.locationName), locationName, StringComparison.OrdinalIgnoreCase)) == true; } internal static void IncludeVisibleMarkersInPanBounds(GameMap map) { //IL_004a: 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_005b: 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) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: 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_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)map == (Object)null || (Object)(object)map != (Object)(object)currentMap || MapMarkerBoundsField == null || GameMapMarkerScrollAreaField == null || ZoomedBoundsField == null) { return; } try { Bounds val = (Bounds)MapMarkerBoundsField.GetValue(map); Bounds val2 = (Bounds)GameMapMarkerScrollAreaField.GetValue(map); if (((Bounds)(ref val2)).size.x <= 0f || ((Bounds)(ref val2)).size.y <= 0f || !IsFinite(((Bounds)(ref val)).center.x) || !IsFinite(((Bounds)(ref val)).center.y)) { return; } Vector3 val3 = ((Bounds)(ref val)).extents - ((Bounds)(ref val2)).extents; val3.x = Mathf.Max(0f, val3.x); val3.y = Mathf.Max(0f, val3.y); val3.z = Mathf.Max(0f, val3.z); Bounds val4 = default(Bounds); ((Bounds)(ref val4))..ctor(((Bounds)(ref val)).center, val3 * 2f); Vector3 val5 = default(Vector3); foreach (MarkerRecord uniqueMarker in GetUniqueMarkers()) { if (!((Object)(object)uniqueMarker?.MarkerObject == (Object)null) && uniqueMarker.MarkerObject.activeInHierarchy) { ((Vector3)(ref val5))..ctor(((Bounds)(ref val2)).center.x - uniqueMarker.MapPosition.x, ((Bounds)(ref val2)).center.y - uniqueMarker.MapPosition.y, ((Bounds)(ref val4)).center.z); ((Bounds)(ref val4)).Encapsulate(val5); } } ZoomedBoundsField.SetValue(map, val4); } catch (Exception ex) { if (!loggedPanBoundsFailure) { loggedPanBoundsFailure = true; ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Could not extend map panning for AP check-marker focus: " + ex.Message)); } } } } internal static void DrawTooltip() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Invalid comparison between Unknown and I4 //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Expected O, but got Unknown //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Expected O, but got Unknown //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Expected O, but got Unknown //IL_020d: 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_0227: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_02a4: Unknown result type (might be due to invalid IL or missing references) //IL_02fa: Unknown result type (might be due to invalid IL or missing references) //IL_02df: Unknown result type (might be due to invalid IL or missing references) //IL_02ff: Unknown result type (might be due to invalid IL or missing references) //IL_030c: Unknown result type (might be due to invalid IL or missing references) //IL_031e: Unknown result type (might be due to invalid IL or missing references) //IL_0330: Unknown result type (might be due to invalid IL or missing references) //IL_0342: Unknown result type (might be due to invalid IL or missing references) //IL_036e: Unknown result type (might be due to invalid IL or missing references) GameMap val = currentMap; if ((Object)(object)val == (Object)null || MarkersByLocation.Count == 0) { return; } Camera mapCamera = GetMapCamera(val); if ((Object)(object)mapCamera == (Object)null || !((Behaviour)mapCamera).isActiveAndEnabled || !TryGetTooltipPointer(val, mapCamera, out var pointer)) { return; } Event current = Event.current; if ((current != null && (int)current.type != 7) || lastTooltipHitTestFrame == Time.frameCount) { return; } lastTooltipHitTestFrame = Time.frameCount; MarkerRecord markerRecord = null; float num = 1156f; foreach (MarkerRecord uniqueMarker in GetUniqueMarkers()) { if ((Object)(object)uniqueMarker?.MarkerObject == (Object)null || !uniqueMarker.MarkerObject.activeInHierarchy) { continue; } Vector3 val2 = mapCamera.WorldToScreenPoint(uniqueMarker.MarkerObject.transform.position); if (!(val2.z <= 0f)) { Vector2 val3 = new Vector2(val2.x, val2.y) - pointer; float sqrMagnitude = ((Vector2)(ref val3)).sqrMagnitude; if (sqrMagnitude <= num) { num = sqrMagnitude; markerRecord = uniqueMarker; } } } if (markerRecord == null) { return; } List list = GetActiveLocationNames(SaveState.Instance, markerRecord).ToList(); if (list.Count != 0) { GUIStyle val4 = new GUIStyle(GUI.skin.box); int fontSize = (((Object)(object)RandomizerPlugin.Instance == (Object)null) ? 14 : RandomizerPlugin.Instance.MapMarkerTooltipFontSize); GUIStyle labelStyle = new GUIStyle(GUI.skin.label) { fontSize = fontSize, wordWrap = false }; float num2 = list.Max((string name) => labelStyle.CalcSize(new GUIContent(name)).x); float num3 = num2 + 18f; float num4 = Math.Max(18f, labelStyle.CalcHeight(new GUIContent("Ag"), Math.Max(1f, num2))); float num5 = num4 * (float)list.Count + 10f; Vector2 val5 = default(Vector2); ((Vector2)(ref val5))..ctor(num3, num5); Vector2 val6 = default(Vector2); ((Vector2)(ref val6))..ctor(pointer.x, (float)Screen.height - pointer.y); Rect val7 = default(Rect); ((Rect)(ref val7))..ctor(Mathf.Clamp(val6.x + 18f, 4f, Mathf.Max(4f, (float)Screen.width - val5.x - 4f)), Mathf.Clamp(val6.y + 18f, 4f, Mathf.Max(4f, (float)Screen.height - val5.y - 4f)), val5.x, val5.y); GUI.Box(val7, GUIContent.none, val4); for (int num6 = 0; num6 < list.Count; num6++) { string text = list[num6]; MapCheckReachability value; Color textColor = (Color)((ReachabilityByLocation.TryGetValue(text, out value) && value == MapCheckReachability.Unreachable) ? new Color(0.58f, 0.58f, 0.58f, 1f) : Color.white); labelStyle.normal.textColor = textColor; labelStyle.hover.textColor = textColor; labelStyle.active.textColor = textColor; labelStyle.focused.textColor = textColor; GUI.Label(new Rect(((Rect)(ref val7)).x + 9f, ((Rect)(ref val7)).y + 5f + (float)num6 * num4, num2, num4), text, labelStyle); } } } private static Camera GetMapCamera(GameMap map) { object? obj = MapManagerField?.GetValue(map); InventoryMapManager val = (InventoryMapManager)((obj is InventoryMapManager) ? obj : null); if ((Object)(object)val == (Object)null) { return null; } object? obj2 = MapCameraField?.GetValue(val); return (Camera)((obj2 is Camera) ? obj2 : null); } private static bool TryGetTooltipPointer(GameMap map, Camera mapCamera, out Vector2 pointer) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: 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_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) pointer = Vector2.zero; if (Cursor.visible) { pointer = Vector2.op_Implicit(Input.mousePosition); return true; } object? obj = MapManagerField?.GetValue(map); InventoryMapManager val = (InventoryMapManager)((obj is InventoryMapManager) ? obj : null); if ((Object)(object)val != (Object)null && MarkerScrollAreaField?.GetValue(val) is Bounds val2 && ((Bounds)(ref val2)).size.x > 0f && ((Bounds)(ref val2)).size.y > 0f) { Vector3 val3 = mapCamera.WorldToScreenPoint(((Bounds)(ref val2)).center); if (val3.z > 0f && IsFinite(val3.x) && IsFinite(val3.y)) { pointer = new Vector2(val3.x, val3.y); return true; } } Rect pixelRect = mapCamera.pixelRect; if (((Rect)(ref pixelRect)).width <= 0f || ((Rect)(ref pixelRect)).height <= 0f) { return false; } pointer = ((Rect)(ref pixelRect)).center; if (IsFinite(pointer.x)) { return IsFinite(pointer.y); } return false; } private static void SetMarkerWorldScale(Transform marker, GameMap map, Sprite sprite) { //IL_002b: 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_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_0044: 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_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: 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_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)marker == (Object)null) && !((Object)(object)map == (Object)null) && !((Object)(object)sprite == (Object)null) && !((Object)(object)marker.parent == (Object)null)) { Bounds bounds = sprite.bounds; float x = ((Bounds)(ref bounds)).size.x; bounds = sprite.bounds; float num = Math.Max(x, ((Bounds)(ref bounds)).size.y); if (!(num <= 0f)) { Vector3 lossyScale = ((Component)map).transform.lossyScale; Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(lossyScale.x * 0.42f / num, lossyScale.y * 0.42f / num, lossyScale.z); Vector3 lossyScale2 = marker.parent.lossyScale; marker.localScale = new Vector3(SafeDivide(val.x, lossyScale2.x), SafeDivide(val.y, lossyScale2.y), SafeDivide(val.z, lossyScale2.z)); } } } private static float SafeDivide(float value, float divisor) { if (!(Math.Abs(divisor) < 0.0001f)) { return value / divisor; } return value; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } [HarmonyPatch(typeof(GameMap), "SetupMap", new Type[] { typeof(bool) })] internal static class CheckMapMarkerSetupPatch { private static void Postfix(GameMap __instance) { CheckMapMarkerManager.Refresh(__instance); } } [HarmonyPatch(typeof(GameMap), "WorldMap")] internal static class CheckMapMarkerWorldMapPatch { private static void Postfix(GameMap __instance) { CheckMapMarkerManager.ProcessPendingMarkerStateRefresh(__instance); CheckMapMarkerManager.IncludeVisibleMarkersInPanBounds(__instance); } } [HarmonyPatch(typeof(GameMap), "Update")] internal static class CheckMapMarkerUpdatePatch { private static void Postfix(GameMap __instance) { CheckMapMarkerManager.ProcessPendingMarkerStateRefresh(__instance); } } [HarmonyPatch(typeof(GameMap), "OnDestroy")] internal static class CheckMapMarkerDestroyPatch { private static void Prefix(GameMap __instance) { CheckMapMarkerManager.Clear(__instance); } } [HarmonyPatch(typeof(SaveState), "CheckLocation")] internal static class CheckMapMarkerLocalCheckPatch { private static void Postfix(string locationName) { CheckMapMarkerManager.HideCheckedLocation(locationName); } } [HarmonyPatch(typeof(SaveState), "CommitReceivedItemAtIndex")] internal static class CheckMapMarkerReceivedItemPatch { private static void Postfix(bool __result) { if (__result) { CheckMapMarkerManager.RequestCurrentMarkerStateRefresh(); } } } [HarmonyPatch(typeof(Archipelago), "SynchronizeSaveState")] internal static class CheckMapMarkerReconcilePatch { private static void Postfix() { CheckMapMarkerManager.RefreshCurrentMap(); } } [HarmonyPatch(typeof(SaveState), "GetHint")] internal static class CheckMapMarkerCachedHintPatch { private static readonly HashSet RefreshedHintLocations = new HashSet(StringComparer.OrdinalIgnoreCase); private static SaveState refreshedHintOwner; private static void Postfix(SaveState __instance, string locationName, bool __result) { if (refreshedHintOwner != __instance) { refreshedHintOwner = __instance; RefreshedHintLocations.Clear(); } string canonicalLocationName = LocationSet.GetCanonicalLocationName(locationName); if (__result && !string.IsNullOrWhiteSpace(canonicalLocationName) && RefreshedHintLocations.Add(canonicalLocationName)) { CheckMapMarkerManager.RefreshHintedLocation(canonicalLocationName); } } } [HarmonyPatch(typeof(GameManager), "SetLoadedGameData", new Type[] { typeof(SaveGameData), typeof(int) })] internal static class CheckMapMarkerLoadPatch { private static void Postfix() { CheckMapMarkerManager.RefreshCurrentMap(); } } [HarmonyPatch(typeof(ToolItem), "get_IsEquipped")] internal static class AutomaticCompassPatch { [HarmonyPatch(typeof(GameMap), "PositionCompassAndCorpse")] private static class QuickMapCompassReadPatch { [HarmonyPrefix] private static void Prefix(out bool __state) { EnterMapCompassRead(out __state); } [HarmonyFinalizer] private static Exception Finalizer(Exception __exception, bool __state) { return ExitMapCompassRead(__exception, __state); } } [HarmonyPatch(typeof(InventoryWideMap), "UpdatePositions")] private static class WideMapCompassReadPatch { [HarmonyPrefix] private static void Prefix(out bool __state) { EnterMapCompassRead(out __state); } [HarmonyFinalizer] private static Exception Finalizer(Exception __exception, bool __state) { return ExitMapCompassRead(__exception, __state); } } [ThreadStatic] private static int automaticCompassMapReadDepth; private static void EnterMapCompassRead(out bool __state) { __state = SaveState.Instance?.automaticCompass ?? false; if (__state) { automaticCompassMapReadDepth++; } } private static Exception ExitMapCompassRead(Exception __exception, bool __state) { if (__state) { automaticCompassMapReadDepth = Math.Max(0, automaticCompassMapReadDepth - 1); } return __exception; } private static bool Prefix(ToolItem __instance, ref bool __result) { SaveState instance = SaveState.Instance; if (instance == null || !instance.automaticCompass || automaticCompassMapReadDepth <= 0) { return true; } ToolItem compassTool = Gameplay.CompassTool; if ((Object)(object)compassTool == (Object)null || __instance != compassTool) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(HealthManager), "SpawnCurrency", new Type[] { typeof(Transform), typeof(float), typeof(float), typeof(float), typeof(float), typeof(int), typeof(int), typeof(int), typeof(int), typeof(bool), typeof(int), typeof(bool) })] internal static class EnemyRosaryMultiplierPatch { private const int ScaleDenominator = 2; private static SaveState roundingState; private static string roundingMultiplier = "x1"; private static int smallRemainder; private static int mediumRemainder; private static int largeRemainder; private static int largeSmoothRemainder; private static void Prefix(ref int smallGeoCount, ref int mediumGeoCount, ref int largeGeoCount, ref int largeSmoothGeoCount) { SaveState instance = SaveState.Instance; if (instance != null) { string enemyRosaryMultiplier = instance.enemyRosaryMultiplier; if (roundingState != instance || !string.Equals(roundingMultiplier, enemyRosaryMultiplier, StringComparison.Ordinal)) { ResetRounding(instance, enemyRosaryMultiplier); } int scaleNumerator = GetScaleNumerator(enemyRosaryMultiplier); if (scaleNumerator != 2) { smallGeoCount = ScaleCountDeterministically(smallGeoCount, scaleNumerator, ref smallRemainder); mediumGeoCount = ScaleCountDeterministically(mediumGeoCount, scaleNumerator, ref mediumRemainder); largeGeoCount = ScaleCountDeterministically(largeGeoCount, scaleNumerator, ref largeRemainder); largeSmoothGeoCount = ScaleCountDeterministically(largeSmoothGeoCount, scaleNumerator, ref largeSmoothRemainder); } } } internal static int GetScaleNumerator(string multiplier) { return multiplier switch { "x1_5" => 3, "x2" => 4, "x3" => 6, _ => 2, }; } internal static int ScaleCountDeterministically(int count, int numerator, ref int remainder) { if (count <= 0 || numerator <= 0) { return count; } long num = (long)count * (long)numerator + remainder; long num2 = num / 2; remainder = (int)(num % 2); if (num2 < int.MaxValue) { return (int)num2; } return int.MaxValue; } private static void ResetRounding(SaveState state, string multiplier) { roundingState = state; roundingMultiplier = multiplier ?? "x1"; smallRemainder = 0; mediumRemainder = 0; largeRemainder = 0; largeSmoothRemainder = 0; } } [HarmonyPatch(typeof(HealthManager), "SpawnCurrency", new Type[] { typeof(Transform), typeof(float), typeof(float), typeof(float), typeof(float), typeof(int), typeof(int), typeof(int), typeof(int), typeof(bool), typeof(int), typeof(bool) })] internal static class EnemyShardMultiplierPatch { private static SaveState roundingState; private static string roundingMultiplier = "x1"; private static int remainder; private static void Prefix(ref int shellShardCount) { SaveState instance = SaveState.Instance; if (instance != null) { string enemyShardMultiplier = instance.enemyShardMultiplier; if (roundingState != instance || !string.Equals(roundingMultiplier, enemyShardMultiplier, StringComparison.Ordinal)) { roundingState = instance; roundingMultiplier = enemyShardMultiplier ?? "x1"; remainder = 0; } int scaleNumerator = EnemyRosaryMultiplierPatch.GetScaleNumerator(enemyShardMultiplier); shellShardCount = EnemyRosaryMultiplierPatch.ScaleCountDeterministically(shellShardCount, scaleNumerator, ref remainder); } } } [HarmonyPatch(typeof(DialogueBox), "Start")] internal static class FasterDialoguePatch { private const float RevealSpeedMultiplier = 2f; private static void Prefix(ref float ___regularRevealSpeed, ref float ___fastRevealSpeed) { SaveState instance = SaveState.Instance; if (instance != null && instance.fasterDialogue) { ___regularRevealSpeed *= 2f; ___fastRevealSpeed *= 2f; } } } internal static class CollectibleSourcePatches { private struct SilkeaterCallState { internal string PreviousLocation; } private sealed class ArchipelagoSourceItem : SavedItem { internal string LocationName; internal ItemType Type; internal bool ShowGenericPresentation; public override bool CanGetMultipleAtOnce => false; public override void Get(bool showPopup = true) { SaveState instance = SaveState.Instance; if (IsActive(instance, LocationName, Type) && !instance.IsLocationChecked(LocationName)) { instance.CheckLocation(LocationName); if (instance.IsLocationChecked(LocationName)) { MarkPhysicalSourceCollected(LocationName); } } } public override bool CanGetMore() { SaveState instance = SaveState.Instance; if (IsActive(instance, LocationName, Type)) { return !instance.IsLocationChecked(LocationName); } return false; } public override Sprite GetPopupIcon() { if (!ShowGenericPresentation) { return null; } object obj = RandomizerPlugin.Instance?.MapCheckIcon; if (obj == null) { RandomizerPlugin instance = RandomizerPlugin.Instance; if (instance == null) { return null; } obj = instance.ArchipelagoIcon; } return (Sprite)obj; } public override string GetPopupName() { if (!ShowGenericPresentation) { return null; } return "Archipelago Item"; } public override int GetSavedAmount() { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsLocationChecked(LocationName)) { return 0; } return 1; } public override bool HasUpgradeIcon() { return false; } public override bool GetTakesHeroControl() { return false; } public override void SetupExtraDescription(GameObject obj) { } public override void SetHasNew(bool hasPopup) { } } [HarmonyPatch(typeof(PersistentBoolItem), "Awake")] private static class PollipHeartPersistencePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(PersistentBoolItem __instance) { //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null) { return true; } Transform val; if (string.Equals(((Object)((Component)__instance).gameObject).name, "Big Flower", StringComparison.Ordinal)) { val = ((Component)__instance).transform.Find("Nectar Pickup"); } else { if (!string.Equals(((Object)((Component)__instance).gameObject).name, "Nectar Pickup", StringComparison.Ordinal) || !((Object)(object)((Component)__instance).transform.parent != (Object)null) || !string.Equals(((Object)((Component)__instance).transform.parent).name, "Big Flower", StringComparison.Ordinal)) { return true; } val = ((Component)__instance).transform; } CollectibleSourceManifest.PollipHeartEntry pollipHeartEntry = FindPollipHeartSource(val); SaveState instance = SaveState.Instance; if (pollipHeartEntry == null || !IsActive(instance, pollipHeartEntry.LocationName, ItemType.PollipHeart)) { return true; } if (!TryGetPollipHeartNativeAction(((Component)val).GetComponent(), out var _, out var _)) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { string[] obj = new string[5] { "[RANDOMIZER] Pollip Heart persistence patch failed closed at ", null, null, null, null }; Scene scene = ((Component)__instance).gameObject.scene; obj[1] = ((Scene)(ref scene)).name; obj[2] = "/"; obj[3] = Utils.GetHierarchyPath(val); obj[4] = ": shipped Control/Collect action 6 did not match Shell Flower."; log.LogWarning((object)string.Concat(obj)); } return true; } TakePollipHeartPersistenceOwnership(val, pollipHeartEntry.LocationName); return false; } } [HarmonyPatch(typeof(CollectableItemPickup), "Awake")] private static class DirectPickupAwakePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(CollectableItemPickup __instance) { SavedItem nativeItem = (((Object)(object)__instance == (Object)null) ? null : __instance.Item); CollectibleSourceManifest.DirectPickupEntry directPickupEntry = FindDirectPickup(__instance, nativeItem); SaveState instance = SaveState.Instance; if (directPickupEntry != null && IsActive(instance, directPickupEntry.LocationName, directPickupEntry.Type)) { if (string.Equals(directPickupEntry.LocationName, "White Key", StringComparison.Ordinal)) { __instance.SetPlayerDataBool(string.Empty); } __instance.SetItem(GetProxyItem(directPickupEntry.LocationName, directPickupEntry.Type), true); } } } [HarmonyPatch(typeof(DeactivateIfPlayerdataTrue), "ForceEvaluate")] private static class ApostateSourceOwnershipGatePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(DeactivateIfPlayerdataTrue __instance) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) SaveState instance = SaveState.Instance; if (!((Object)(object)__instance == (Object)null)) { Scene scene = ((Component)__instance).gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, "Aqueduct_04", StringComparison.OrdinalIgnoreCase) && string.Equals(__instance.boolName, "HasSlabKeyC", StringComparison.Ordinal) && IsActive(instance, "Key of Apostate", ItemType.MajorKey) && !instance.IsLocationChecked("Key of Apostate")) { Vector2 val = Vector2.op_Implicit(((Component)__instance).transform.position); float num = val.x - 7.570004f; float num2 = val.y - 38.651806f; if (num * num + num2 * num2 > 4f) { return true; } return false; } } return true; } } [HarmonyPatch(typeof(ShopItem), "get_IsAvailable")] private static class WhiteKeyFallbackAvailabilityPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(ShopItem __instance, ref bool __result) { SaveState instance = SaveState.Instance; if ((Object)(object)__instance == (Object)null || !string.Equals(((Object)__instance).name, "City Merchant Ward Key", StringComparison.Ordinal) || !IsActive(instance, "White Key", ItemType.MajorKey)) { return true; } __result = !instance.IsLocationChecked("White Key"); return false; } } [HarmonyPatch(typeof(SilkGrubCocoon), "WasHit")] private static class SilkeaterCocoonPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(SilkGrubCocoon __instance, out SilkeaterCallState __state) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) __state = new SilkeaterCallState { PreviousLocation = activeSilkeaterLocation }; activeSilkeaterLocation = null; if (!((Object)(object)__instance == (Object)null)) { Scene scene = ((Component)__instance).gameObject.scene; CollectibleSourceManifest.CocoonEntry cocoonEntry = CollectibleSourceManifest.FindSilkeaterCocoon(((Scene)(ref scene)).name, ((Object)((Component)__instance).gameObject).name, Vector2.op_Implicit(((Component)__instance).transform.position)); if (cocoonEntry != null && IsActive(SaveState.Instance, cocoonEntry.LocationName, ItemType.Silkeater)) { activeSilkeaterLocation = cocoonEntry.LocationName; } } } [HarmonyFinalizer] private static Exception Finalizer(Exception __exception, SilkeaterCallState __state) { activeSilkeaterLocation = __state.PreviousLocation; return __exception; } } [HarmonyPatch(typeof(CollectableItemPickup), "SetItem", new Type[] { typeof(SavedItem), typeof(bool) })] private static class DynamicPickupRewardPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(CollectableItemPickup __instance, ref SavedItem newItem) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)newItem == (Object)null)) { CollectibleSourceManifest.DirectPickupEntry directPickupEntry = FindDirectPickup(__instance, newItem); if (directPickupEntry != null && IsActive(SaveState.Instance, directPickupEntry.LocationName, directPickupEntry.Type)) { newItem = GetProxyItem(directPickupEntry.LocationName, directPickupEntry.Type); } else if (!string.IsNullOrEmpty(activeSilkeaterLocation) && string.Equals(((Object)newItem).name, "Silk Grub", StringComparison.Ordinal) && IsActive(SaveState.Instance, activeSilkeaterLocation, ItemType.Silkeater)) { newItem = GetProxyItem(activeSilkeaterLocation, ItemType.Silkeater); } else if (string.Equals(((Object)newItem).name, "Craw Summons", StringComparison.Ordinal) && IsCrawSummonsPickup(__instance) && IsActive(SaveState.Instance, "Craw Summons", ItemType.MajorKey)) { newItem = GetProxyItem("Craw Summons", ItemType.MajorKey); } } } } [HarmonyPatch(typeof(PlayMakerFSM), "Start")] private static class CollectibleFsmPatch { [HarmonyPrefix] [HarmonyPriority(0)] private static void Prefix(PlayMakerFSM __instance) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)((Component)__instance).gameObject == (Object)null)) { PatchMossberrySource(__instance); PatchPollipHeartSource(__instance); PatchCrawSummonsPin(__instance); } } } private sealed class CompleteMossberryLocation : FsmStateAction { internal readonly string LocationName; private readonly CollectableItemCollect nativeAction; internal CompleteMossberryLocation(string locationName, CollectableItemCollect nativeAction) { LocationName = locationName; this.nativeAction = nativeAction; } public override void OnEnter() { SaveState instance = SaveState.Instance; if (IsActive(instance, LocationName, ItemType.Mossberry)) { if (!instance.IsLocationChecked(LocationName)) { instance.CheckLocation(LocationName); } } else { CollectableItemCollect obj = nativeAction; object obj2; if (obj == null) { obj2 = null; } else { FsmObject item = ((CollectableItemAction)obj).Item; obj2 = ((item != null) ? item.Value : null); } CollectableItem val = (CollectableItem)((obj2 is CollectableItem) ? obj2 : null); if (val != null) { int num = ((nativeAction.Amount == null || ((NamedVariable)nativeAction.Amount).IsNone) ? 1 : nativeAction.Amount.Value); val.Collect(num, true); } } ((FsmStateAction)this).Finish(); } } private sealed class CompletePollipHeartLocation : FsmStateAction { internal readonly string LocationName; private readonly CollectableItemCollect nativeAction; internal CompletePollipHeartLocation(string locationName, CollectableItemCollect nativeAction) { LocationName = locationName; this.nativeAction = nativeAction; } public override void OnEnter() { SaveState instance = SaveState.Instance; if (IsActive(instance, LocationName, ItemType.PollipHeart)) { if (!instance.IsLocationChecked(LocationName)) { instance.CheckLocation(LocationName); } } else { CollectableItemCollect obj = nativeAction; object obj2; if (obj == null) { obj2 = null; } else { FsmObject item = ((CollectableItemAction)obj).Item; obj2 = ((item != null) ? item.Value : null); } CollectableItem val = (CollectableItem)((obj2 is CollectableItem) ? obj2 : null); if (val != null) { int num = ((nativeAction.Amount == null || ((NamedVariable)nativeAction.Amount).IsNone) ? 1 : nativeAction.Amount.Value); val.Collect(num, true); } } ((FsmStateAction)this).Finish(); } } private sealed class CrawSourceGateAction : FsmStateAction { public override void OnEnter() { bool flag = SaveState.Instance?.IsLocationChecked("Craw Summons") ?? false; ((FsmStateAction)this).Fsm.Event(flag ? "TRUE" : "FINISHED"); ((FsmStateAction)this).Finish(); } } [HarmonyPatch(typeof(FullQuestBase), "get_RewardItem")] private static class QuestRewardItemPatch { [HarmonyPostfix] [HarmonyPriority(800)] private static void Postfix(FullQuestBase __instance, ref SavedItem __result) { if ((Object)(object)__instance == (Object)null) { return; } if (string.Equals(((Object)__instance).name, "Journal", StringComparison.Ordinal) && IsActive(SaveState.Instance, "Tool Pouch: Bugs of Pharloom", ItemType.ToolPouch)) { __result = GetProxyItem("Tool Pouch: Bugs of Pharloom", ItemType.ToolPouch, showGenericPresentation: true); return; } if (string.Equals(((Object)__instance).name, "Rock Rollers", StringComparison.Ordinal) && IsActive(SaveState.Instance, "Memory Locket: Volatile Flintbeetles", ItemType.MemoryLocket)) { __result = GetProxyItem("Memory Locket: Volatile Flintbeetles", ItemType.MemoryLocket, showGenericPresentation: true); return; } string nativeRewardAssetName = (((Object)(object)__result == (Object)null) ? null : ((Object)__result).name); if (TryGetActiveQuestLocation(__instance, out var locationName) && QuestLocationManifest.TryGetReplaceableVanillaRewardLocation(((Object)__instance).name, nativeRewardAssetName, out var locationName2) && string.Equals(locationName, locationName2, StringComparison.OrdinalIgnoreCase)) { __result = GetProxyItem(locationName, ItemType.Quest, showGenericPresentation: true); } } } [HarmonyPatch(typeof(FullQuestBase), "get_RewardIcon")] private static class QuestRewardIconPatch { [HarmonyPostfix] private static void Postfix(FullQuestBase __instance, ref Sprite __result) { if (ShouldUseGenericQuestPresentation(__instance)) { __result = RandomizerPlugin.Instance?.MapCheckIcon ?? RandomizerPlugin.Instance?.ArchipelagoIcon; } } } [HarmonyPatch(typeof(FullQuestBase), "get_RewardIconType")] private static class QuestRewardIconTypePatch { [HarmonyPostfix] private static void Postfix(FullQuestBase __instance, ref IconTypes __result) { if (ShouldUseGenericQuestPresentation(__instance)) { __result = (IconTypes)0; } } } private const string MossberryAssetName = "Mossberry"; private const string PollipHeartAssetName = "Shell Flower"; private const string SilkeaterAssetName = "Silk Grub"; private const string RockRollersQuestName = "Rock Rollers"; [ThreadStatic] private static string activeSilkeaterLocation; private static readonly Dictionary ProxyByLocation = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary> QuestRewardSources = new Dictionary>(StringComparer.Ordinal) { { "Brolly Get", Tuple.Create("Drifter's Cloak", ItemType.Skill) }, { "Crow Feathers", Tuple.Create("Crafting Kit Source: Crow Feathers", ItemType.Upgrade) }, { "Extractor Blue", Tuple.Create("Tool Unlock: Lifeblood Syringe", ItemType.Tool) }, { "Great Gourmand", Tuple.Create("Pale Oil: Great Taste of Pharloom", ItemType.NeedleUpgrade) }, { "Huntress Quest", Tuple.Create("Tool Unlock: Longneedle", ItemType.Tool) }, { "Huntress Quest Runt", Tuple.Create("Tool Unlock: Longneedle", ItemType.Tool) }, { "Journal", Tuple.Create("Tool Pouch: Bugs of Pharloom", ItemType.ToolPouch) }, { "Pinstress Battle", Tuple.Create("Tool Unlock: Pinstress Tool", ItemType.Tool) }, { "Roach Killing", Tuple.Create("Tool Unlock: Tack", ItemType.Tool) }, { "Rock Rollers", Tuple.Create("Memory Locket: Volatile Flintbeetles", ItemType.MemoryLocket) }, { "Save the Fleas", Tuple.Create("Tool Unlock: Flea Brew", ItemType.Tool) }, { "Shakra Final Quest", Tuple.Create("Tool Unlock: Shakra Ring", ItemType.Tool) }, { "Shell Flowers", Tuple.Create("Tool Unlock: Poison Pouch", ItemType.Tool) } }; private static bool IsActive(SaveState state, string locationName, ItemType type) { if (state != null && state.IsRandomized(type) && state.IsLocationEnabled(locationName)) { return state.IsLocationInSeed(locationName); } return false; } internal static SavedItem GetProxyItem(string locationName, ItemType type, bool showGenericPresentation = false) { string key = locationName + (showGenericPresentation ? "\0generic" : "\0silent"); if (!ProxyByLocation.TryGetValue(key, out var value) || (Object)(object)value == (Object)null) { value = ScriptableObject.CreateInstance(); ((Object)value).name = "Archipelago Location - " + locationName; value.LocationName = locationName; value.Type = type; value.ShowGenericPresentation = showGenericPresentation; ProxyByLocation[key] = value; } return (SavedItem)(object)value; } private static void MarkPhysicalSourceCollected(string locationName) { if (PlayerData.instance != null && string.Equals(locationName, "White Key", StringComparison.Ordinal)) { PlayerData.instance.collectedWardKey = true; } } private static CollectibleSourceManifest.DirectPickupEntry FindDirectPickup(CollectableItemPickup pickup, SavedItem nativeItem) { //IL_001a: 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) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)pickup == (Object)null || (Object)(object)nativeItem == (Object)null) { return null; } Scene scene = ((Component)pickup).gameObject.scene; return CollectibleSourceManifest.FindDirectPickup(((Scene)(ref scene)).name, ((Object)nativeItem).name, Utils.GetHierarchyPath(((Component)pickup).transform), Vector2.op_Implicit(((Component)pickup).transform.position)); } private static CollectibleSourceManifest.PollipHeartEntry FindPollipHeartSource(Transform pickupTransform) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)pickupTransform == (Object)null) { return null; } Transform parent = pickupTransform.parent; if ((Object)(object)parent == (Object)null || !string.Equals(((Object)parent).name, "Big Flower", StringComparison.Ordinal)) { return null; } Scene scene = ((Component)pickupTransform).gameObject.scene; return CollectibleSourceManifest.FindPollipHeartSource(((Scene)(ref scene)).name, ((Object)pickupTransform).name, Utils.GetHierarchyPath(pickupTransform)); } private static bool TryGetPollipHeartNativeAction(PlayMakerFSM fsm, out FsmState collectState, out CollectableItemCollect nativeAction) { collectState = null; nativeAction = null; if ((Object)(object)fsm == (Object)null || !string.Equals(fsm.FsmName, "Control", StringComparison.Ordinal)) { return false; } Fsm fsm2 = fsm.Fsm; collectState = ((fsm2 != null) ? fsm2.GetState("Collect") : null); FsmState obj = collectState; if (((obj != null) ? obj.Actions : null) == null || collectState.Actions.Length <= 6) { return false; } int num = 0; int num2 = -1; FsmStateAction[] actions = collectState.Actions; foreach (FsmStateAction val in actions) { CollectableItemCollect val2 = (CollectableItemCollect)(object)((val is CollectableItemCollect) ? val : null); if (val2 != null) { FsmObject item = ((CollectableItemAction)val2).Item; Object obj2 = ((item != null) ? item.Value : null); CollectableItem val3 = (CollectableItem)(object)((obj2 is CollectableItem) ? obj2 : null); if (val3 != null && string.Equals(((Object)val3).name, "Shell Flower", StringComparison.Ordinal)) { num2 = Array.IndexOf(collectState.Actions, val); nativeAction = val2; num++; } } } if (num == 1) { return num2 == 6; } return false; } private static void TakePollipHeartPersistenceOwnership(Transform pickupTransform, string locationName) { if ((Object)(object)pickupTransform == (Object)null || (Object)(object)pickupTransform.parent == (Object)null) { return; } Transform parent = pickupTransform.parent; SaveState instance = SaveState.Instance; if (instance != null && instance.IsLocationChecked(locationName)) { ((Component)parent).gameObject.SetActive(false); } PersistentBoolItem[] components = ((Component)parent).GetComponents(); foreach (PersistentBoolItem val in components) { if (!((Object)(object)val == (Object)null)) { ((Behaviour)val).enabled = false; Object.Destroy((Object)(object)val); } } components = ((Component)pickupTransform).GetComponents(); foreach (PersistentBoolItem val2 in components) { if (!((Object)(object)val2 == (Object)null)) { ((Behaviour)val2).enabled = false; Object.Destroy((Object)(object)val2); } } } private static bool IsCrawSummonsPickup(CollectableItemPickup pickup) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_007c: 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) Scene scene = ((Component)pickup).gameObject.scene; string name = ((Scene)(ref scene)).name; Transform val = ((Component)pickup).transform; while ((Object)(object)val != (Object)null) { if (CollectibleSourceManifest.IsCrawPin(name, ((Object)val).name, Vector2.op_Implicit(val.position))) { return true; } val = val.parent; } Vector2 val2 = Vector2.op_Implicit(((Component)pickup).transform.position); CollectibleSourceManifest.CrawPinEntry[] crawPins = CollectibleSourceManifest.CrawPins; foreach (CollectibleSourceManifest.CrawPinEntry crawPinEntry in crawPins) { if (string.Equals(name, crawPinEntry.SceneName, StringComparison.OrdinalIgnoreCase)) { float num = val2.x - crawPinEntry.X; float num2 = val2.y - crawPinEntry.Y; if (num * num + num2 * num2 <= 36f) { return true; } } } return false; } private static void PatchMossberrySource(PlayMakerFSM fsm) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) Scene scene = ((Component)fsm).gameObject.scene; if (!CollectibleSourceManifest.TryGetMossberryLocation(((Scene)(ref scene)).name, out var locationName) || !IsActive(SaveState.Instance, locationName, ItemType.Mossberry)) { return; } Fsm fsm2 = fsm.Fsm; FsmState val = ((fsm2 != null) ? fsm2.GetState("Collect") : null); if (((val != null) ? val.Actions : null) == null) { return; } for (int i = 0; i < val.Actions.Length; i++) { if (val.Actions[i] is CompleteMossberryLocation completeMossberryLocation && string.Equals(completeMossberryLocation.LocationName, locationName, StringComparison.Ordinal)) { return; } } int num = -1; CollectableItemCollect nativeAction = null; for (int j = 0; j < val.Actions.Length; j++) { FsmStateAction obj = val.Actions[j]; CollectableItemCollect val2 = (CollectableItemCollect)(object)((obj is CollectableItemCollect) ? obj : null); if (val2 == null) { continue; } FsmObject item = ((CollectableItemAction)val2).Item; Object obj2 = ((item != null) ? item.Value : null); CollectableItem val3 = (CollectableItem)(object)((obj2 is CollectableItem) ? obj2 : null); if (val3 == null || !string.Equals(((Object)val3).name, "Mossberry", StringComparison.Ordinal)) { continue; } if (num >= 0) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { string[] obj3 = new string[5] { "[RANDOMIZER] Mossberry source patch found more than one native reward action at ", null, null, null, null }; scene = ((Component)fsm).gameObject.scene; obj3[1] = ((Scene)(ref scene)).name; obj3[2] = "/"; obj3[3] = Utils.GetHierarchyPath(((Component)fsm).transform); obj3[4] = "."; log.LogWarning((object)string.Concat(obj3)); } return; } num = j; nativeAction = val2; } if (num >= 0) { CompleteMossberryLocation completeMossberryLocation2 = new CompleteMossberryLocation(locationName, nativeAction); ((FsmStateAction)completeMossberryLocation2).Init(val); val.Actions[num] = (FsmStateAction)(object)completeMossberryLocation2; } } private static void PatchPollipHeartSource(PlayMakerFSM fsm) { //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) CollectibleSourceManifest.PollipHeartEntry pollipHeartEntry = FindPollipHeartSource(((Component)fsm).transform); if (pollipHeartEntry == null || !IsActive(SaveState.Instance, pollipHeartEntry.LocationName, ItemType.PollipHeart)) { return; } Fsm fsm2 = fsm.Fsm; FsmState collectState = ((fsm2 != null) ? fsm2.GetState("Collect") : null); if (((collectState != null) ? collectState.Actions : null) != null && collectState.Actions.Length > 6 && collectState.Actions[6] is CompletePollipHeartLocation completePollipHeartLocation && string.Equals(completePollipHeartLocation.LocationName, pollipHeartEntry.LocationName, StringComparison.Ordinal)) { return; } if (!TryGetPollipHeartNativeAction(fsm, out collectState, out var nativeAction)) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { string[] obj = new string[5] { "[RANDOMIZER] Pollip Heart source patch failed closed at ", null, null, null, null }; Scene scene = ((Component)fsm).gameObject.scene; obj[1] = ((Scene)(ref scene)).name; obj[2] = "/"; obj[3] = Utils.GetHierarchyPath(((Component)fsm).transform); obj[4] = ": expected Shell Flower action 6."; log.LogWarning((object)string.Concat(obj)); } } else { CompletePollipHeartLocation completePollipHeartLocation2 = new CompletePollipHeartLocation(pollipHeartEntry.LocationName, nativeAction); ((FsmStateAction)completePollipHeartLocation2).Init(collectState); collectState.Actions[6] = (FsmStateAction)(object)completePollipHeartLocation2; TakePollipHeartPersistenceOwnership(((Component)fsm).transform, pollipHeartEntry.LocationName); } } private static void PatchCrawSummonsPin(PlayMakerFSM fsm) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) Scene scene = ((Component)fsm).gameObject.scene; if (!CollectibleSourceManifest.IsCrawPin(((Scene)(ref scene)).name, ((Object)((Component)fsm).gameObject).name, Vector2.op_Implicit(((Component)fsm).transform.position)) || !IsActive(SaveState.Instance, "Craw Summons", ItemType.MajorKey)) { return; } Fsm fsm2 = fsm.Fsm; FsmState val = ((fsm2 != null) ? fsm2.GetState("Empty?") : null); if (((val != null) ? val.Actions : null) == null || (val.Actions.Length == 1 && val.Actions[0] is CrawSourceGateAction)) { return; } bool num = val.Actions.Length == 2 && val.Actions[0] is PlayerDataVariableTest && val.Actions[1] is CollectableItemGetData; bool flag = HasTransition(val, "TRUE", "Set Empty") && HasTransition(val, "FINISHED", "Set Pickup"); if (!num || !flag) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { string[] obj = new string[5] { "[RANDOMIZER] Craw Summons pin patch failed closed at ", null, null, null, null }; scene = ((Component)fsm).gameObject.scene; obj[1] = ((Scene)(ref scene)).name; obj[2] = "/"; obj[3] = Utils.GetHierarchyPath(((Component)fsm).transform); obj[4] = ". The Empty? state has an unexpected layout."; log.LogWarning((object)string.Concat(obj)); } } else { CrawSourceGateAction crawSourceGateAction = new CrawSourceGateAction(); ((FsmStateAction)crawSourceGateAction).Init(val); val.Actions = (FsmStateAction[])(object)new FsmStateAction[1] { crawSourceGateAction }; } } private static bool HasTransition(FsmState state, string eventName, string targetState) { if (((state != null) ? state.Transitions : null) == null) { return false; } FsmTransition[] transitions = state.Transitions; foreach (FsmTransition val in transitions) { if (val != null && string.Equals(val.EventName, eventName, StringComparison.Ordinal) && string.Equals(val.ToState, targetState, StringComparison.Ordinal)) { return true; } } return false; } private static bool TryGetActiveQuestLocation(FullQuestBase quest, out string locationName) { if ((Object)(object)quest != (Object)null && QuestLocationManifest.TryGetLocationName(((Object)quest).name, out locationName) && IsActive(SaveState.Instance, locationName, ItemType.Quest)) { return true; } locationName = null; return false; } private static bool TryGetActiveQuestRewardSource(FullQuestBase quest, out string locationName, out ItemType type) { locationName = null; type = ItemType.Unknown; if ((Object)(object)quest == (Object)null) { return false; } if (MaskAndSpoolLocationManifest.TryGetQuestSource(((Object)quest).name, out locationName, out type)) { return IsActive(SaveState.Instance, locationName, type); } if (!QuestRewardSources.TryGetValue(((Object)quest).name, out var value)) { return false; } locationName = value.Item1; type = value.Item2; return IsActive(SaveState.Instance, locationName, type); } private static bool ShouldUseGenericQuestPresentation(FullQuestBase quest) { if ((Object)(object)quest != (Object)null && QuestLocationManifest.IsDonationWithoutVanillaReward(((Object)quest).name)) { return false; } ItemType type; if (!TryGetActiveQuestLocation(quest, out var locationName)) { return TryGetActiveQuestRewardSource(quest, out locationName, out type); } return true; } } internal static class ConsumableToolPatches { internal const string FleaBrewItemName = "Flea Brew"; internal const string PlasmiumPhialItemName = "Plasmium Phial"; private const string FleaBrewToolName = "Flea Brew"; private const string PlasmiumPhialToolName = "Lifeblood Syringe"; internal static bool RequiresInitialFill(SaveState state, string itemName) { if (state == null || !state.IsRandomized(ItemType.Tool)) { return false; } string canonicalItemName = ItemSet.GetCanonicalItemName(itemName); if (string.Equals(canonicalItemName, "Flea Brew", StringComparison.OrdinalIgnoreCase)) { return !state.fleaBrewInitialFillApplied; } if (string.Equals(canonicalItemName, "Plasmium Phial", StringComparison.OrdinalIgnoreCase)) { return !state.plasmiumPhialInitialFillApplied; } return false; } internal static bool TryInitializeReceivedConsumable(SaveState state, string itemName) { //IL_007c: 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_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) if (!RequiresInitialFill(state, itemName)) { return true; } string canonicalItemName = ItemSet.GetCanonicalItemName(itemName); if (state.receivedItems == null || !state.receivedItems.Contains(canonicalItemName)) { return false; } string text = (string.Equals(canonicalItemName, "Flea Brew", StringComparison.OrdinalIgnoreCase) ? "Flea Brew" : "Lifeblood Syringe"); PlayerData instance = PlayerData.instance; if (instance == null) { return false; } ToolItem toolByName = ToolItemManager.GetToolByName(text); ToolItemStatesLiquid val = (ToolItemStatesLiquid)(object)((toolByName is ToolItemStatesLiquid) ? toolByName : null); if ((Object)(object)val == (Object)null) { return false; } try { int toolStorageAmount = ToolItemManager.GetToolStorageAmount((ToolItem)(object)val); Data data = ((SerializableNamedList)(object)instance.Tools).GetData(((ToolItem)val).name); data.AmountLeft = toolStorageAmount; ((SerializableNamedList)(object)instance.Tools).SetData(((ToolItem)val).name, data); Data savedData = ((ToolItem)val).SavedData; savedData.AmountLeft = toolStorageAmount; ((ToolItem)val).SavedData = savedData; val.RefillRefills(false); AttackToolBinding? attackToolBinding = ToolItemManager.GetAttackToolBinding((ToolItem)(object)val); if (attackToolBinding.HasValue) { ToolItemManager.ReportBoundAttackToolUpdated(attackToolBinding.Value); } if (string.Equals(canonicalItemName, "Flea Brew", StringComparison.OrdinalIgnoreCase)) { state.fleaBrewInitialFillApplied = true; } else { state.plasmiumPhialInitialFillApplied = true; } return true; } catch (Exception ex) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Native liquid state for " + canonicalItemName + " was not ready; initialization will be retried: " + ex.Message)); } return false; } } internal static IEnumerator SynchronizeReceivedConsumables(SaveState expectedState) { if (expectedState == null) { yield break; } while (SaveState.Instance == expectedState) { bool num = !expectedState.receivedItems.Contains("Flea Brew") || TryInitializeReceivedConsumable(expectedState, "Flea Brew"); bool flag = !expectedState.receivedItems.Contains("Plasmium Phial") || TryInitializeReceivedConsumable(expectedState, "Plasmium Phial"); if (num && flag) { break; } yield return null; } } } internal static class CoreLocationPatches { [HarmonyPatch(typeof(CollectableRelic), "CanGetMore")] internal static class CollectableRelicCanGetMorePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(CollectableRelic __instance, ref bool __result) { if (relicGrantBypassDepth == 0 && IsUncheckedRelic(__instance)) { __result = true; return false; } return true; } } [HarmonyPatch(typeof(CollectableRelic), "Get", new Type[] { typeof(bool) })] internal static class CollectableRelicGetPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(CollectableRelic __instance) { if (relicGrantBypassDepth > 0) { return true; } SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Relic) || !CoreLocationManifest.TryGetRelicLocation(((Object)__instance).name, out var locationName)) { return true; } instance.CheckLocation(locationName); return false; } } [HarmonyPatch(typeof(CollectableItemPickup), "CheckActivation")] internal static class CollectableItemPickupCheckActivationPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(CollectableItemPickup __instance) { SaveState instance = SaveState.Instance; CollectableRelic val = (CollectableRelic)(((Object)(object)__instance == (Object)null) ? null : /*isinst with value type is only supported in some contexts*/); if (instance == null || !instance.IsRandomized(ItemType.Relic) || (Object)(object)val == (Object)null || !CoreLocationManifest.TryGetRelicLocation(((Object)val).name, out var locationName)) { return true; } bool flag = instance.IsLocationChecked(locationName); __instance.SetActivation(flag); if (flag) { UnityEvent onPickedUp = __instance.OnPickedUp; if (onPickedUp != null) { onPickedUp.Invoke(); } UnityEvent onPreviouslyPickedUp = __instance.OnPreviouslyPickedUp; if (onPreviouslyPickedUp != null) { onPreviouslyPickedUp.Invoke(); } ((Component)__instance).gameObject.SetActive(false); } return false; } } [HarmonyPatch(typeof(ShopItem), "SetPurchased", new Type[] { typeof(Action), typeof(int) })] internal static class CraftingKitShopContextPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(ShopItem __instance, out string __state) { __state = craftingKitShopLocationContext; if (IsRandomized(ItemType.Upgrade) && (Object)(object)__instance != (Object)null && CoreLocationManifest.TryGetCraftingKitShopLocation(((Object)__instance).name, out var locationName)) { craftingKitShopLocationContext = locationName; } } [HarmonyFinalizer] private static Exception Finalizer(Exception __exception, string __state) { craftingKitShopLocationContext = __state; return __exception; } } [HarmonyPatch(typeof(PlayerDataCollectable), "Get", new Type[] { typeof(bool) })] internal static class CraftingKitPickupPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(PlayerDataCollectable __instance) { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Upgrade) || (Object)(object)__instance == (Object)null || !string.Equals(((Object)__instance).name, "Tool Kit Pickup", StringComparison.Ordinal)) { return true; } string locationName = craftingKitShopLocationContext ?? "Crafting Kit Source: Crow Feathers"; instance.CheckLocation(locationName); return false; } } [HarmonyPatch(typeof(ShopItem), "SetPurchased", new Type[] { typeof(Action), typeof(int) })] internal static class ShopItemSetPurchasedPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(ShopItem __instance, Action onComplete) { SaveState instance = SaveState.Instance; if (instance == null || (Object)(object)__instance == (Object)null || !CoreLocationManifest.TryGetShopLocation(((Object)__instance).name, out var location)) { return true; } if (!instance.IsRandomized(location.Type)) { return true; } if (instance.IsLocationChecked(location.LocationName)) { onComplete?.Invoke(); return false; } if (!UsesRosaries(__instance, out var error)) { ReportBlockingPurchaseError(location.LocationName, error); return false; } if (!TryGetPurchaseConditionals(__instance, out var conditionals, out var tryInstantiateMethod, out var error2)) { ReportBlockingPurchaseError(location.LocationName, error2); return false; } try { if (tryInstantiateMethod != null) { foreach (object item in conditionals) { tryInstantiateMethod.Invoke(item, null); } } CurrencyManager.TakeGeo(__instance.Cost); instance.CheckLocation(location.LocationName); CollectableItemManager.IncrementVersion(); onComplete?.Invoke(); } catch (Exception ex) { ReportBlockingPurchaseError(location.LocationName, "The purchase hook failed.", (ex is TargetInvocationException && ex.InnerException != null) ? ex.InnerException : ex); } return false; } } [HarmonyPatch(typeof(ShopItem), "get_IsPurchased")] internal static class ShopItemIsPurchasedPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(ShopItem __instance, ref bool __result) { SaveState instance = SaveState.Instance; if (instance == null || !TryResolveShopLocation(__instance, out var locationName)) { return true; } __result = instance.IsLocationChecked(locationName); return false; } } [HarmonyPatch(typeof(ShopItem), "get_DisplayName")] internal static class ShopItemDisplayNamePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(ShopItem __instance, ref string __result) { if (SaveState.Instance == null || !TryResolveShopPreviewLocation(__instance, out var locationName)) { return true; } if (ShopPatches.TryGetPresentationHint(locationName, out var user, out var item, out var _)) { __result = user + "'s " + item; } else { __result = "AP Item"; } return false; } } [HarmonyPatch(typeof(ShopItem), "get_Description")] internal static class ShopItemDescriptionPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(ShopItem __instance, ref string __result) { //IL_003d: 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_0055: Expected I4, but got Unknown if (SaveState.Instance == null || !TryResolveShopPreviewLocation(__instance, out var locationName)) { return true; } if (!ShopPatches.TryGetPresentationHint(locationName, out var user, out var item, out var flags)) { __result = "Something for someone else, maybe..."; return false; } __result = user + "'s " + item + ".\r\n"; switch (flags - 1) { case 0: __result += "It is very important!"; break; case 1: __result += "Seems useful."; break; case 3: __result += "Seems fun..."; break; default: __result += "Seems not important."; break; } return false; } } [HarmonyPatch(typeof(ShopItem), "get_ItemSprite")] internal static class ShopItemItemSpritePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(ShopItem __instance, ref Sprite __result) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) SaveState instance = SaveState.Instance; RandomizerPlugin instance2 = RandomizerPlugin.Instance; if (instance == null || (Object)(object)instance2 == (Object)null || !TryResolveShopPreviewLocation(__instance, out var locationName)) { return true; } if (ShopPatches.TryGetPresentationHint(locationName, out var _, out var _, out var flags)) { __result = instance2.GetItemClassificationIcon(flags); } else { __result = instance2.MapCheckIcon ?? instance2.ArchipelagoIcon; } return false; } } [ThreadStatic] private static int relicGrantBypassDepth; [ThreadStatic] private static string craftingKitShopLocationContext; private static readonly FieldInfo SpawnConditionalsField = AccessTools.Field(typeof(ShopItem), "spawnOnPurchaseConditionals"); private static readonly PropertyInfo CurrencyTypeProperty = AccessTools.Property(typeof(ShopItem), "CurrencyType"); private static readonly FieldInfo PlayerDataBoolNameField = AccessTools.Field(typeof(ShopItem), "playerDataBoolName"); private const string GrindleSpoolPlayerDataBool = "purchasedGrindleSpoolPiece"; private const string GrindleSpoolItemAsset = "Silk Spool"; private const string GrindleSpoolSourceLocation = "Spool Fragment Unlock #17"; private const string FreySpoolShopAsset = "Belltown Spool Segment"; private const string FreySpoolPlayerDataBool = "PurchasedBelltownSpoolSegment"; private const string FreySpoolSourceLocation = "Spool Fragment Unlock #6"; private static readonly Dictionary MaskShardShopLocations = new Dictionary(StringComparer.Ordinal) { { "Bonebottom Mask Shard", "Mask Shard Unlock #1" }, { "Grindle Mask Shard", "Mask Shard Unlock #1" }, { "City Merchant Heart Piece", "Mask Shard Unlock #15" } }; private static bool IsRandomized(ItemType type) { return SaveState.Instance?.IsRandomized(type) ?? false; } internal static void GrantRelicWithoutChecking(CollectableRelic relic, bool showPopup = true) { if ((Object)(object)relic == (Object)null) { throw new ArgumentNullException("relic"); } if (!CoreLocationManifest.TryGetRelicLocation(((Object)relic).name, out var _)) { throw new ArgumentException("The CollectableRelic is not a registered relic asset: " + ((Object)relic).name, "relic"); } relicGrantBypassDepth++; try { ((SavedItem)relic).Get(showPopup); } finally { relicGrantBypassDepth--; } } private static bool IsUncheckedRelic(CollectableRelic relic) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Relic) && (Object)(object)relic != (Object)null && CoreLocationManifest.TryGetRelicLocation(((Object)relic).name, out var locationName)) { return !instance.IsLocationChecked(locationName); } return false; } internal static bool TrySynchronizeReceivedRelics() { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: 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_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) SaveState instance = SaveState.Instance; if (instance == null || PlayerData.instance == null || (Object)(object)ManagerSingleton.Instance == (Object)null) { return false; } if (!instance.IsRandomized(ItemType.Relic)) { return true; } string[] relicAssetNames = CoreLocationManifest.RelicAssetNames; foreach (string text in relicAssetNames) { CollectableRelic relic = CollectableRelicManager.GetRelic(text); if ((Object)(object)relic == (Object)null || !CoreLocationManifest.TryGetRelicLocation(text, out var locationName)) { return false; } string canonicalLocationName = LocationSet.GetCanonicalLocationName(locationName); bool num = instance.receivedItems != null && instance.receivedItems.Contains(canonicalLocationName); Data savedData = relic.SavedData; if (num) { if (!savedData.IsCollected) { savedData.IsCollected = true; relic.SavedData = savedData; } } else if (savedData.IsCollected || savedData.IsDeposited || savedData.HasSeenInRelicBoard) { savedData.IsCollected = false; savedData.IsDeposited = false; savedData.HasSeenInRelicBoard = false; relic.SavedData = savedData; } } return true; } private static bool TryResolveShopLocation(ShopItem shopItem, out string locationName) { locationName = null; if ((Object)(object)shopItem == (Object)null) { return false; } if (CoreLocationManifest.TryGetShopLocation(((Object)shopItem).name, out var location)) { if (!IsRandomized(location.Type)) { return false; } locationName = location.LocationName; return true; } if (CoreLocationManifest.TryGetCraftingKitShopLocation(((Object)shopItem).name, out locationName)) { return IsRandomized(ItemType.Upgrade); } SavedItem item = shopItem.Item; CollectableRelic val = (CollectableRelic)(object)((item is CollectableRelic) ? item : null); if ((Object)(object)val != (Object)null && IsRandomized(ItemType.Relic)) { return CoreLocationManifest.TryGetRelicLocation(((Object)val).name, out locationName); } return false; } private static bool TryResolveShopPreviewLocation(ShopItem shopItem, out string locationName) { locationName = null; SaveState instance = SaveState.Instance; if (instance != null && (Object)(object)shopItem != (Object)null) { string itemAssetName = (((Object)(object)shopItem.Item == (Object)null) ? null : ((Object)shopItem.Item).name); string playerDataBoolName = ((PlayerDataBoolNameField == null) ? null : (PlayerDataBoolNameField.GetValue(shopItem) as string)); if (TryResolveStableShopPreviewIdentity(((Object)shopItem).name, itemAssetName, playerDataBoolName, instance.IsRandomized(ItemType.MaskShard), instance.IsRandomized(ItemType.SpoolFragment), out locationName)) { return IsShopPreviewLocationInSeed(instance, ref locationName); } } if (TryResolveShopLocation(shopItem, out locationName)) { return IsShopPreviewLocationInSeed(instance, ref locationName); } return false; } private static bool IsShopPreviewLocationInSeed(SaveState state, ref string locationName) { if (state == null || string.IsNullOrWhiteSpace(locationName)) { locationName = null; return false; } locationName = LocationSet.GetCanonicalLocationName(locationName); if (!state.IsLocationEnabled(locationName) || !state.IsLocationInSeed(locationName)) { locationName = null; return false; } return true; } internal static bool TryResolveShopHintLocation(ShopItem shopItem, out string locationName) { return TryResolveShopPreviewLocation(shopItem, out locationName); } private static bool TryResolveStableShopPreviewIdentity(string shopAssetName, string itemAssetName, string playerDataBoolName, bool maskShardsRandomized, bool spoolFragmentsRandomized, out string locationName) { locationName = null; if (maskShardsRandomized && MaskShardShopLocations.TryGetValue(shopAssetName ?? string.Empty, out var value) && string.Equals(itemAssetName, "Heart Piece", StringComparison.Ordinal)) { locationName = LocationSet.GetCanonicalLocationName(value); return true; } if (spoolFragmentsRandomized && string.Equals(shopAssetName, "Belltown Spool Segment", StringComparison.Ordinal) && string.Equals(playerDataBoolName, "PurchasedBelltownSpoolSegment", StringComparison.Ordinal) && string.Equals(itemAssetName, "Silk Spool", StringComparison.Ordinal)) { locationName = LocationSet.GetCanonicalLocationName("Spool Fragment Unlock #6"); return true; } if (spoolFragmentsRandomized && string.Equals(playerDataBoolName, "purchasedGrindleSpoolPiece", StringComparison.Ordinal) && string.Equals(itemAssetName, "Silk Spool", StringComparison.Ordinal)) { locationName = LocationSet.GetCanonicalLocationName("Spool Fragment Unlock #17"); return true; } return false; } private static bool TryGetPurchaseConditionals(ShopItem shopItem, out Array conditionals, out MethodInfo tryInstantiateMethod, out string error) { conditionals = null; tryInstantiateMethod = null; error = null; if (SpawnConditionalsField == null) { error = "ShopItem.spawnOnPurchaseConditionals was not found."; return false; } conditionals = SpawnConditionalsField.GetValue(shopItem) as Array; if (conditionals == null) { error = "ShopItem.spawnOnPurchaseConditionals was null."; return false; } if (conditionals.Length == 0) { return true; } Type elementType = conditionals.GetType().GetElementType(); tryInstantiateMethod = ((elementType == null) ? null : AccessTools.Method(elementType, "TryInstantiate", (Type[])null, (Type[])null)); if (tryInstantiateMethod == null) { error = "ShopItem.ConditionalSpawn.TryInstantiate was not found."; return false; } foreach (object conditional in conditionals) { if (conditional == null) { error = "A ShopItem purchase conditional was null."; return false; } } return true; } private static bool UsesRosaries(ShopItem shopItem, out string error) { error = null; if (CurrencyTypeProperty == null) { error = "ShopItem.CurrencyType was not found."; return false; } object value = CurrencyTypeProperty.GetValue(shopItem, null); if (value == null || Convert.ToInt32(value) != 0) { error = "The registered map or pin shop asset no longer uses Rosaries."; return false; } return true; } private static void ReportBlockingPurchaseError(string locationName, string detail, Exception exception = null) { string text = "Could not safely randomize '" + locationName + "'. No vanilla map or pin was granted. " + detail; if (exception != null) { text = text + " " + exception.Message; } if ((Object)(object)RandomizerPlugin.Instance != (Object)null) { RandomizerPlugin.Instance.ReportBlockingError(text); } else { Debug.LogError((object)("[RANDOMIZER] " + text)); } } } public class CrestPatches { [HarmonyPatch(typeof(InventoryToolCrestList), "CanChangeCrests", new Type[] { })] internal static class InventoryToolCrestList_CanChangeCrests_Patch { [HarmonyPrefix] private static bool Prefix(ref bool __result) { SaveState instance = SaveState.Instance; if (!TrapManager.IsCursedCrestActive && (instance == null || !instance.IsRandomized(ItemType.Crest))) { return true; } __result = !TrapManager.IsCursedCrestActive; return false; } } [HarmonyPatch(typeof(ToolCrest), "Unlock", new Type[] { })] internal static class ToolCrest_Unlock_Patch { [HarmonyPrefix] private static bool Prefix(ToolCrest __instance) { if ((Object)(object)__instance == (Object)null) { return false; } if (SaveState.Instance == null || !SaveState.Instance.IsRandomized(ItemType.Crest) || ToolPatches.canCrestBeUnlockedByRandomizer) { return true; } string text = ""; if (!string.IsNullOrEmpty(__instance.name)) { text = __instance.name; } Debug.Log((object)("[RANDOMIZER] Tried to Unlock: " + text)); if (CrestNames.IsHunterInternalName(text) && !__instance.IsBaseVersion) { return SaveState.Instance.receivedItems.Contains("Crest: Hunter"); } SaveState.Instance.CheckLocation(CrestNames.GetLocationNameFromInternal(text)); return false; } } } internal static class CrestPickupPopupPatches { private enum CompletionMode { SourceSequence, MemoryStateActions } private readonly struct PopupSource { internal readonly string LocationName; internal readonly string CrestInternalName; internal readonly string SceneName; internal readonly string OwnerName; internal readonly string FsmName; internal readonly string StateName; internal readonly CompletionMode Completion; internal PopupSource(string locationName, string crestInternalName, string sceneName, string ownerName, string fsmName, string stateName, CompletionMode completion) { LocationName = locationName; CrestInternalName = crestInternalName; SceneName = sceneName; OwnerName = ownerName; FsmName = fsmName; StateName = stateName; Completion = completion; } internal bool Matches(ShowToolCrestUIMsg action) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) ToolCrest val = (ToolCrest)((action == null || action.Crest == null) ? null : /*isinst with value type is only supported in some contexts*/); if ((Object)(object)val != (Object)null && (Object)(object)((FsmStateAction)action).Owner != (Object)null) { Scene scene = ((FsmStateAction)action).Owner.scene; if (string.Equals(((Scene)(ref scene)).name, SceneName, StringComparison.Ordinal) && string.Equals(((Object)((FsmStateAction)action).Owner).name, OwnerName, StringComparison.Ordinal) && ((FsmStateAction)action).Fsm != null && string.Equals(((FsmStateAction)action).Fsm.Name, FsmName, StringComparison.Ordinal) && ((FsmStateAction)action).State != null && string.Equals(((FsmStateAction)action).State.Name, StateName, StringComparison.Ordinal)) { return string.Equals(val.name, CrestInternalName, StringComparison.Ordinal); } } return false; } } [HarmonyPatch(typeof(ShowToolCrestUIMsg), "OnEnter")] internal static class RandomizedCrestGetMessagePatch { [HarmonyPrefix] private static bool Prefix(ShowToolCrestUIMsg __instance) { if (!TryGetActiveSource(__instance, out var source)) { return true; } if (source.Completion == CompletionMode.SourceSequence) { ((FsmStateAction)__instance).Finish(); return false; } RandomizerPlugin instance = RandomizerPlugin.Instance; if ((Object)(object)instance == (Object)null) { return true; } ((MonoBehaviour)instance).StartCoroutine(CompleteAfterMemoryStateActions(__instance, source)); return false; } } private const float SourceFadeDuration = 2f; private const float SourceWaitDuration = 2.5f; private static readonly PopupSource[] SourceSequencePopups = new PopupSource[6] { new PopupSource("Crest Unlock: Beast", "Warrior", "Ant_19", "Crest Get Shrine", "Control", "Crest Msg", CompletionMode.SourceSequence), new PopupSource("Crest Unlock: Reaper", "Reaper", "Greymoor_20c", "Crest Get Shrine", "Control", "Crest Msg", CompletionMode.SourceSequence), new PopupSource("Crest Unlock: Wanderer", "Wanderer", "Chapel_Wanderer", "Crest Get Shrine", "Control", "Crest Msg", CompletionMode.SourceSequence), new PopupSource("Crest Unlock: Architect", "Toolmaster", "Under_20", "Crest Get Shrine", "Control", "Crest Msg", CompletionMode.SourceSequence), new PopupSource("Crest Unlock: Shaman", "Spell", "Tut_04", "Crest Get Shrine", "Control", "Crest Msg", CompletionMode.SourceSequence), new PopupSource("Crest Unlock: Shaman", "Spell", "Tut_05", "Crest Get Shrine", "Control", "Crest Msg", CompletionMode.SourceSequence) }; private static readonly PopupSource[] MemoryPopups = new PopupSource[5] { new PopupSource("Crest Unlock: Reaper", "Reaper", "Tut_05", "Memory Control", "Memory Control", "Reaper Msg", CompletionMode.MemoryStateActions), new PopupSource("Crest Unlock: Wanderer", "Wanderer", "Tut_05", "Memory Control", "Memory Control", "Wanderer Msg", CompletionMode.MemoryStateActions), new PopupSource("Crest Unlock: Beast", "Warrior", "Tut_05", "Memory Control", "Memory Control", "Beast Msg", CompletionMode.MemoryStateActions), new PopupSource("Crest Unlock: Shaman", "Spell", "Tut_05", "Memory Control", "Memory Control", "Shaman Msg", CompletionMode.MemoryStateActions), new PopupSource("Crest Unlock: Architect", "Toolmaster", "Tut_05", "Memory Control", "Memory Control", "Toolmaster Msg", CompletionMode.MemoryStateActions) }; private static bool IsActiveApLocation(PopupSource source) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Crest) && instance.IsLocationEnabled(source.LocationName)) { return instance.IsLocationInSeed(source.LocationName); } return false; } private static bool TryGetActiveSource(ShowToolCrestUIMsg action, out PopupSource source) { PopupSource[] sourceSequencePopups = SourceSequencePopups; for (int i = 0; i < sourceSequencePopups.Length; i++) { PopupSource popupSource = sourceSequencePopups[i]; if (popupSource.Matches(action) && IsActiveApLocation(popupSource) && HasExactSourceSequenceLifecycle(action)) { source = popupSource; return true; } } sourceSequencePopups = MemoryPopups; for (int i = 0; i < sourceSequencePopups.Length; i++) { PopupSource popupSource2 = sourceSequencePopups[i]; if (popupSource2.Matches(action) && IsActiveApLocation(popupSource2) && HasExactMemoryLifecycle(action)) { source = popupSource2; return true; } } source = default(PopupSource); return false; } private static bool HasFinishedEvent(ShowToolCrestUIMsg action) { if (action != null && action.FinishEvent != null) { return string.Equals(action.FinishEvent.Name, FsmEvent.Finished.Name, StringComparison.Ordinal); } return false; } private static bool HasEmptyFinishEvent(ShowToolCrestUIMsg action) { if (action != null) { if (action.FinishEvent != null) { return string.IsNullOrEmpty(action.FinishEvent.Name); } return true; } return false; } private static bool HasExactSourceSequenceLifecycle(ShowToolCrestUIMsg action) { object obj; if (action == null) { obj = null; } else { FsmState state = ((FsmStateAction)action).State; obj = ((state != null) ? state.Actions : null); } FsmStateAction[] array = (FsmStateAction[])obj; if (array != null && ((FsmStateAction)action).State.IsSequence && array.Length == 3) { FsmStateAction obj2 = array[0]; ScreenFader val = (ScreenFader)(object)((obj2 is ScreenFader) ? obj2 : null); if (val != null) { FsmStateAction obj3 = array[1]; Wait val2 = (Wait)(object)((obj3 is Wait) ? obj3 : null); if (val2 != null && (object)array[2] == action && HasEmptyFinishEvent(action)) { if (Mathf.Approximately(val.duration.Value, 2f) && !val2.realTime) { return Mathf.Approximately(val2.time.Value, 2.5f); } return false; } } } return false; } private static bool HasExactMemoryLifecycle(ShowToolCrestUIMsg action) { object obj; if (action == null) { obj = null; } else { FsmState state = ((FsmStateAction)action).State; obj = ((state != null) ? state.Actions : null); } FsmStateAction[] array = (FsmStateAction[])obj; if (array != null && !((FsmStateAction)action).State.IsSequence && array.Length == 4 && (object)array[0] == action && array[1] is SetStringValue && array[2] is SetStringValue && array[3] is SetPlayerDataBool) { return HasFinishedEvent(action); } return false; } private static bool IsStillInSourceState(ShowToolCrestUIMsg action, PopupSource source) { if (source.Matches(action) && ((FsmStateAction)action).Fsm != null) { return string.Equals(((FsmStateAction)action).Fsm.ActiveStateName, source.StateName, StringComparison.Ordinal); } return false; } private static void CompleteNativeCallback(ShowToolCrestUIMsg action) { ((FsmStateAction)action).Fsm.Event(action.FinishEvent); ((FsmStateAction)action).Finish(); } private static IEnumerator CompleteAfterMemoryStateActions(ShowToolCrestUIMsg action, PopupSource source) { yield return null; if (IsStillInSourceState(action, source)) { CompleteNativeCallback(action); } } } internal class CrestSlotPatches { private readonly struct TextLayout { internal readonly bool AutoSizing; internal readonly bool WordWrapping; internal readonly float FontSize; internal readonly float FontSizeMin; internal readonly float FontSizeMax; internal readonly TextOverflowModes OverflowMode; internal readonly int MaxVisibleLines; internal TextLayout(TextMeshPro text) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) AutoSizing = ((TMP_Text)text).enableAutoSizing; WordWrapping = ((TMP_Text)text).enableWordWrapping; FontSize = ((TMP_Text)text).fontSize; FontSizeMin = ((TMP_Text)text).fontSizeMin; FontSizeMax = ((TMP_Text)text).fontSizeMax; OverflowMode = ((TMP_Text)text).OverflowMode; MaxVisibleLines = ((TMP_Text)text).maxVisibleLines; } internal void Restore(TextMeshPro text) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) ((TMP_Text)text).enableAutoSizing = AutoSizing; ((TMP_Text)text).enableWordWrapping = WordWrapping; ((TMP_Text)text).fontSizeMin = FontSizeMin; ((TMP_Text)text).fontSizeMax = FontSizeMax; ((TMP_Text)text).fontSize = FontSize; ((TMP_Text)text).OverflowMode = OverflowMode; ((TMP_Text)text).maxVisibleLines = MaxVisibleLines; } } private readonly struct CrestSlotKey : IEquatable { private readonly string crestName; private readonly int type; private readonly int x; private readonly int y; internal CrestSlotKey(string crestName, SlotInfo slotInfo) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected I4, but got Unknown //IL_0014: 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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) this.crestName = crestName; type = (int)slotInfo.Type; x = (int)slotInfo.Position.x; y = (int)slotInfo.Position.y; } public bool Equals(CrestSlotKey other) { if (type == other.type && x == other.x && y == other.y) { return string.Equals(crestName, other.crestName, StringComparison.Ordinal); } return false; } public override bool Equals(object obj) { if (obj is CrestSlotKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (((((((crestName != null) ? StringComparer.Ordinal.GetHashCode(crestName) : 0) * 397) ^ type) * 397) ^ x) * 397) ^ y; } } private readonly struct CrestSlotNames { internal readonly string ItemName; internal readonly string LocationName; internal CrestSlotNames(string itemName, string locationName) { ItemName = itemName; LocationName = locationName; } } private sealed class DescriptionRenderState { internal TextLayout OriginalDescriptionLayout; internal TextLayout OriginalNameLayout; internal bool HasOriginalDescriptionLayout; internal bool HasOriginalNameLayout; internal bool HintLayoutApplied; internal InventoryItemSelectable Selectable; internal SaveState OwnerState; internal string LocationName; internal string ItemName; internal string BaseText; internal string RenderedText; internal string RenderedName; internal string HintDisplayName; internal string HintClassification; internal string LocketStatusDetail; internal string LocketStatusText; internal bool LocationChecked; internal bool ItemReceived; internal ToolItem EquippedItem; internal bool CanUnlockSlot; internal bool HintResolved; internal int NextHintPollFrame; } [HarmonyPatch(typeof(InventoryItemManager), "SetDisplay", new Type[] { typeof(InventoryItemSelectable) })] internal static class InventoryItemManager_SetDisplay_Patch { [HarmonyPrefix] public static bool Prefix(InventoryItemManager __instance, InventoryItemSelectable selectable, TextMeshPro ___descriptionText, TextMeshPro ___nameText, out bool __state) { __state = true; if (___descriptionText == null || !TryGetRandomizedApSlot(selectable, out var slot, out var slotNames)) { return true; } DescriptionRenderState descriptionRenderState = GetDescriptionRenderState(___descriptionText, ___nameText); SaveState instance = SaveState.Instance; bool locationChecked = IsLocationChecked(instance, slotNames.LocationName); bool itemReceived = HasReceivedItem(instance, slotNames.ItemName); ToolItem equippedItem = slot.EquippedItem; InventoryItemToolManager val = (InventoryItemToolManager)(object)((__instance is InventoryItemToolManager) ? __instance : null); bool canUnlockSlot = val != null && val.CanUnlockSlot; if (MatchesDisplayState(descriptionRenderState, selectable, instance, slotNames, locationChecked, itemReceived, equippedItem, canUnlockSlot) && string.Equals(((TMP_Text)___descriptionText).text, descriptionRenderState.RenderedText, StringComparison.Ordinal) && (___nameText == null || string.Equals(((TMP_Text)___nameText).text, descriptionRenderState.RenderedName, StringComparison.Ordinal))) { __state = false; return false; } return true; } [HarmonyPostfix] public static void Postfix(InventoryItemManager __instance, InventoryItemSelectable selectable, TextMeshPro ___descriptionText, TextMeshPro ___nameText, bool __state) { //IL_01da: Unknown result type (might be due to invalid IL or missing references) if (___descriptionText == null) { return; } DescriptionRenderState descriptionRenderState = GetDescriptionRenderState(___descriptionText, ___nameText); if (!TryGetRandomizedApSlot(selectable, out var slot, out var slotNames)) { RestoreHintLayouts(___descriptionText, ___nameText, descriptionRenderState); ResetDescriptionIdentity(descriptionRenderState); return; } SaveState instance = SaveState.Instance; bool flag = IsLocationChecked(instance, slotNames.LocationName); bool flag2 = HasReceivedItem(instance, slotNames.ItemName); ToolItem equippedItem = slot.EquippedItem; InventoryItemToolManager val = (InventoryItemToolManager)(object)((__instance is InventoryItemToolManager) ? __instance : null); bool flag3 = val != null && val.CanUnlockSlot; int num; int num2; if (descriptionRenderState.Selectable == selectable && descriptionRenderState.OwnerState == instance && string.Equals(descriptionRenderState.LocationName, slotNames.LocationName, StringComparison.Ordinal)) { num = (string.Equals(descriptionRenderState.ItemName, slotNames.ItemName, StringComparison.Ordinal) ? 1 : 0); if (num != 0 && descriptionRenderState.LocationChecked == flag && descriptionRenderState.ItemReceived == flag2 && descriptionRenderState.EquippedItem == equippedItem) { num2 = ((descriptionRenderState.CanUnlockSlot == flag3) ? 1 : 0); goto IL_00d2; } } else { num = 0; } num2 = 0; goto IL_00d2; IL_00d2: bool flag4 = (byte)num2 != 0; if (num == 0) { RestoreHintLayouts(___descriptionText, ___nameText, descriptionRenderState); descriptionRenderState.Selectable = selectable; descriptionRenderState.OwnerState = instance; descriptionRenderState.LocationName = slotNames.LocationName; descriptionRenderState.ItemName = slotNames.ItemName; descriptionRenderState.HintDisplayName = null; descriptionRenderState.HintClassification = null; descriptionRenderState.HintResolved = false; descriptionRenderState.NextHintPollFrame = 0; } else if (!flag4) { RestoreHintLayouts(___descriptionText, ___nameText, descriptionRenderState); } descriptionRenderState.LocationChecked = flag; descriptionRenderState.ItemReceived = flag2; descriptionRenderState.EquippedItem = equippedItem; descriptionRenderState.CanUnlockSlot = flag3; if (__state) { RestoreHintLayouts(___descriptionText, ___nameText, descriptionRenderState); descriptionRenderState.BaseText = ((TMP_Text)___descriptionText).text ?? string.Empty; descriptionRenderState.RenderedName = ((___nameText == null) ? null : ((TMP_Text)___nameText).text); } else if (descriptionRenderState.BaseText == null) { descriptionRenderState.BaseText = ((TMP_Text)___descriptionText).text ?? string.Empty; } if (!descriptionRenderState.HintResolved && Time.frameCount >= descriptionRenderState.NextHintPollFrame) { if (instance.GetHint(slotNames.LocationName, out var user, out var item, out var flags)) { descriptionRenderState.HintDisplayName = BuildHintDisplayName(user, item); descriptionRenderState.HintClassification = BuildHintClassification(flags); descriptionRenderState.HintResolved = true; } else { bool flag5 = Archipelago.Instance != null && Archipelago.Instance.Connected; descriptionRenderState.NextHintPollFrame = Time.frameCount + (flag5 ? 15 : 60); } } bool flag6 = descriptionRenderState.EquippedItem == null; bool num3 = descriptionRenderState.HintResolved && flag6; bool flag7 = descriptionRenderState.LocationChecked && flag6; bool flag8 = IsUnlockPromptVisible(descriptionRenderState); if (num3 || flag7 || flag8) { ApplyHintLayouts(___descriptionText, ___nameText, descriptionRenderState, flag8); } string text = (num3 ? descriptionRenderState.HintClassification : descriptionRenderState.BaseText); string text2 = (flag8 ? string.Empty : (flag7 ? GetLocketStatusText(descriptionRenderState, text) : text)); string text3 = (num3 ? descriptionRenderState.HintDisplayName : descriptionRenderState.RenderedName); if (!string.Equals(((TMP_Text)___descriptionText).text, text2, StringComparison.Ordinal)) { ((TMP_Text)___descriptionText).text = text2; } if (___nameText != null && !string.Equals(((TMP_Text)___nameText).text, text3, StringComparison.Ordinal)) { ((TMP_Text)___nameText).text = text3; } descriptionRenderState.RenderedText = text2; descriptionRenderState.RenderedName = text3; } } [HarmonyPatch(typeof(InventoryItemToolManager), "SetDisplay", new Type[] { typeof(InventoryItemSelectable) })] internal static class InventoryItemToolManager_SetDisplay_Patch { [HarmonyPostfix] private static void Postfix(InventoryItemSelectable selectable, CrestSocketUnlockInventoryDescription ___slotUnlockDescExtra) { if (TryGetRandomizedApSlot(selectable, out var _, out var slotNames) && IsLocationChecked(SaveState.Instance, slotNames.LocationName) && ___slotUnlockDescExtra != null) { ((Component)___slotUnlockDescExtra).gameObject.SetActive(false); } } } [HarmonyPatch(typeof(InventoryToolCrestSlot), "PlayAnimSmall")] internal static class InventoryToolCrestSlot_PlayAnimSmall_Patch { [HarmonyPrefix] private static void Prefix(InventoryToolCrestSlot __instance, ref int animId) { if (TryGetRandomizedApSlot(__instance, out var slotNames) && IsLocationChecked(SaveState.Instance, slotNames.LocationName)) { animId = FilledSlotAnimation; } } } [HarmonyPatch(typeof(InventoryToolCrestSlot), "get_IsLocked", new Type[] { })] internal static class InventoryToolCrestSlot_IsLocked_Patch { [HarmonyPrefix] private static bool Prefix(InventoryToolCrestSlot __instance, ref bool __result, bool ___isSelected) { if (!TryGetRandomizedApSlot(__instance, out var slotNames)) { return true; } bool flag = Object.op_Implicit((Object)(object)__instance.EquippedItem); SaveState instance = SaveState.Instance; bool flag2 = IsLocationChecked(instance, slotNames.LocationName); if (!___isSelected || flag || flag2) { __result = !HasReceivedItem(instance, slotNames.ItemName); return false; } __result = true; return false; } } [HarmonyPatch(typeof(InventoryToolCrestSlot), "UnlockHoldRoutine")] internal static class InventoryToolCrestSlot_UnlockHoldRoutine_Patch { [HarmonyPostfix] private static void Postfix(InventoryToolCrestSlot __instance, ref IEnumerator __result, bool ___isSelected, InventoryItemToolManager ___manager) { if (TryGetRandomizedApSlot(__instance, out var slotNames)) { bool flag = IsLocationChecked(SaveState.Instance, slotNames.LocationName); bool shouldCheckLocation = ___isSelected && !Object.op_Implicit((Object)(object)__instance.EquippedItem) && !flag; if (___isSelected && !Object.op_Implicit((Object)(object)__instance.EquippedItem) && flag) { __result = EmptyUnlockRoutine(); } else { __result = CheckLocationAfterUnlock(__result, slotNames.LocationName, shouldCheckLocation, ___manager); } } } private static IEnumerator EmptyUnlockRoutine() { yield break; } private static IEnumerator CheckLocationAfterUnlock(IEnumerator original, string locationName, bool shouldCheckLocation, InventoryItemToolManager manager) { while (original.MoveNext()) { yield return original.Current; } if (shouldCheckLocation && !IsLocationChecked(SaveState.Instance, locationName)) { SaveState.Instance.CheckLocation(locationName); if (IsLocationChecked(SaveState.Instance, locationName) && manager != null) { manager.RefreshTools(); } } } } private const int ConnectedHintPollFrames = 15; private const int DisconnectedHintPollFrames = 60; private const string MemoryLocketUsedText = "Memory Locket used"; private static readonly int FilledSlotAnimation = Animator.StringToHash("Filled"); private static readonly Dictionary CrestSlotNameCache = new Dictionary(); private static readonly ConditionalWeakTable DescriptionRenderStates = new ConditionalWeakTable(); private static SaveState cachedLocationOwner; private static LocationSet cachedLocationSet; private static Location[] cachedLocationArray; private static HashSet cachedLocationNames; private static DescriptionRenderState GetDescriptionRenderState(TextMeshPro descriptionText, TextMeshPro nameText) { DescriptionRenderState value = DescriptionRenderStates.GetValue(descriptionText, (TextMeshPro _) => new DescriptionRenderState()); if (!value.HasOriginalDescriptionLayout) { value.OriginalDescriptionLayout = new TextLayout(descriptionText); value.HasOriginalDescriptionLayout = true; } if (nameText != null && !value.HasOriginalNameLayout) { value.OriginalNameLayout = new TextLayout(nameText); value.HasOriginalNameLayout = true; } return value; } private static bool IsUnlockPromptVisible(DescriptionRenderState renderState) { if (renderState.CanUnlockSlot && renderState.EquippedItem == null) { return !renderState.LocationChecked; } return false; } private static string GetLocketStatusText(DescriptionRenderState renderState, string detailText) { detailText = detailText ?? string.Empty; if (!string.Equals(renderState.LocketStatusDetail, detailText, StringComparison.Ordinal) || renderState.LocketStatusText == null) { renderState.LocketStatusDetail = detailText; renderState.LocketStatusText = (string.IsNullOrWhiteSpace(detailText) ? "Memory Locket used" : ("Memory Locket used" + ": " + detailText)); } return renderState.LocketStatusText; } private static string BuildHintDisplayName(string playerName, string itemName) { string text = (string.IsNullOrWhiteSpace(itemName) ? "AP Item" : itemName.Trim()); string text2 = (playerName ?? string.Empty).Trim(); if (!string.IsNullOrEmpty(text2)) { return text + " (" + text2 + ")"; } return text; } private static void ApplyHintLayouts(TextMeshPro descriptionText, TextMeshPro nameText, DescriptionRenderState renderState, bool unlockPromptVisible) { if (!renderState.HintLayoutApplied) { if (nameText != null && renderState.HasOriginalNameLayout) { ((TMP_Text)nameText).enableWordWrapping = false; ((TMP_Text)nameText).enableAutoSizing = true; ((TMP_Text)nameText).fontSizeMax = renderState.OriginalNameLayout.FontSize; ((TMP_Text)nameText).fontSizeMin = Math.Max(10f, renderState.OriginalNameLayout.FontSize * 0.55f); ((TMP_Text)nameText).OverflowMode = (TextOverflowModes)1; ((TMP_Text)nameText).maxVisibleLines = 1; } ((TMP_Text)descriptionText).enableWordWrapping = false; ((TMP_Text)descriptionText).enableAutoSizing = true; if (unlockPromptVisible) { ((TMP_Text)descriptionText).maxVisibleLines = 0; } else { ((TMP_Text)descriptionText).fontSizeMax = renderState.OriginalDescriptionLayout.FontSize; ((TMP_Text)descriptionText).fontSizeMin = Math.Max(8f, renderState.OriginalDescriptionLayout.FontSize * 0.55f); ((TMP_Text)descriptionText).OverflowMode = (TextOverflowModes)1; ((TMP_Text)descriptionText).maxVisibleLines = 1; } renderState.HintLayoutApplied = true; } } private static void RestoreHintLayouts(TextMeshPro descriptionText, TextMeshPro nameText, DescriptionRenderState renderState) { if (renderState.HintLayoutApplied) { renderState.OriginalDescriptionLayout.Restore(descriptionText); if (nameText != null && renderState.HasOriginalNameLayout) { renderState.OriginalNameLayout.Restore(nameText); } renderState.HintLayoutApplied = false; } } private static void ResetDescriptionIdentity(DescriptionRenderState renderState) { renderState.Selectable = null; renderState.OwnerState = null; renderState.LocationName = null; renderState.ItemName = null; renderState.BaseText = null; renderState.RenderedText = null; renderState.RenderedName = null; renderState.HintDisplayName = null; renderState.HintClassification = null; renderState.LocketStatusDetail = null; renderState.LocketStatusText = null; renderState.LocationChecked = false; renderState.ItemReceived = false; renderState.EquippedItem = null; renderState.CanUnlockSlot = false; renderState.HintResolved = false; renderState.NextHintPollFrame = 0; } private static bool MatchesDisplayState(DescriptionRenderState renderState, InventoryItemSelectable selectable, SaveState state, CrestSlotNames slotNames, bool locationChecked, bool itemReceived, ToolItem equippedItem, bool canUnlockSlot) { if (renderState.Selectable == selectable && renderState.OwnerState == state && string.Equals(renderState.LocationName, slotNames.LocationName, StringComparison.Ordinal) && string.Equals(renderState.ItemName, slotNames.ItemName, StringComparison.Ordinal) && renderState.LocationChecked == locationChecked && renderState.ItemReceived == itemReceived && renderState.EquippedItem == equippedItem) { return renderState.CanUnlockSlot == canUnlockSlot; } return false; } private static string BuildHintClassification(ItemFlags flags) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) if ((flags & 1) != 0) { return "Very important"; } if ((flags & 2) != 0) { return "Useful"; } if ((flags & 4) != 0) { return "Trap"; } return "Not important"; } private static bool TryGetRandomizedApSlot(InventoryItemSelectable selectable, out InventoryToolCrestSlot slot, out CrestSlotNames slotNames) { slot = (InventoryToolCrestSlot)(object)((selectable is InventoryToolCrestSlot) ? selectable : null); if (slot == null) { slotNames = default(CrestSlotNames); return false; } return TryGetRandomizedApSlot(slot, out slotNames); } private static bool TryGetRandomizedApSlot(InventoryToolCrestSlot slot, out CrestSlotNames slotNames) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) slotNames = default(CrestSlotNames); SaveState instance = SaveState.Instance; if (slot == null || (Object)(object)slot.Crest == (Object)null || (Object)(object)slot.Crest.CrestData == (Object)null || instance == null || !instance.IsRandomized(ItemType.CrestSlot)) { return false; } slotNames = GetSlotNames(slot.Crest, slot.SlotInfo); return HasApLocation(instance, slotNames.LocationName); } private static bool HasApLocation(SaveState state, string locationName) { if (state == null || state.locations == null || state.locations.Locations == null || string.IsNullOrWhiteSpace(locationName)) { return false; } LocationSet locations = state.locations; Location[] locations2 = locations.Locations; if (cachedLocationOwner != state || cachedLocationSet != locations || cachedLocationArray != locations2 || cachedLocationNames == null) { cachedLocationOwner = state; cachedLocationSet = locations; cachedLocationArray = locations2; cachedLocationNames = new HashSet(StringComparer.OrdinalIgnoreCase); Location[] array = locations2; foreach (Location location in array) { if (location != null && !string.IsNullOrWhiteSpace(location.Name)) { cachedLocationNames.Add(location.Name); } } } return cachedLocationNames.Contains(locationName); } private static bool IsLocationChecked(SaveState state, string canonicalLocationName) { if (state != null && state.checkedLocations != null) { return state.checkedLocations.Contains(canonicalLocationName); } return false; } private static bool HasReceivedItem(SaveState state, string canonicalItemName) { if (state != null && state.receivedItems != null) { return state.receivedItems.Contains(canonicalItemName); } return false; } private static CrestSlotNames GetSlotNames(InventoryToolCrest crest, SlotInfo slotInfo) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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_0044: 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) //IL_0061: Unknown result type (might be due to invalid IL or missing references) string publicCrestName = CrestNames.GetPublicCrestName(((Object)crest).name); CrestSlotKey key = new CrestSlotKey(publicCrestName, slotInfo); if (CrestSlotNameCache.TryGetValue(key, out var value)) { return value; } string text = string.Concat(slotInfo.Type, " ", (int)slotInfo.Position.x, " ", (int)slotInfo.Position.y); string canonicalItemName = ItemSet.GetCanonicalItemName(publicCrestName + " Slot: " + text); string canonicalLocationName = LocationSet.GetCanonicalLocationName(publicCrestName + " Slot Unlock: " + text); value = new CrestSlotNames(canonicalItemName, canonicalLocationName); CrestSlotNameCache[key] = value; return value; } public static string GetSlotNameAsItem(InventoryToolCrest crest, SlotInfo slotInfo) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return GetSlotNames(crest, slotInfo).ItemName; } public static string GetSlotNameAsLocation(InventoryToolCrest crest, SlotInfo slotInfo) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return GetSlotNames(crest, slotInfo).LocationName; } } internal static class FarFieldsWardenflyPatches { [HarmonyPatch(typeof(PlayerDataVariableTest), "OnEnter")] private static class RandomizedClingGripInnerGatePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(PlayerDataVariableTest __instance) { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Skill) || !IsExactCageWallJumpGate(__instance)) { return true; } ((FsmStateAction)__instance).Fsm.Event(instance.canWallJump ? __instance.IsNotExpectedEvent : __instance.IsExpectedEvent); ((FsmStateAction)__instance).Finish(); return false; } } [HarmonyPatch(typeof(PlayerDataBoolTest), "OnEnter")] private static class RandomizedClingGripGreymoorOuterGatePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(PlayerDataBoolTest __instance) { SaveState instance = SaveState.Instance; PlayerData instance2 = PlayerData.instance; if (instance == null || instance2 == null || !instance.IsRandomized(ItemType.Skill) || !IsExactGreymoorOuterWallJumpGate(__instance)) { return true; } bool canWallJump = instance.canWallJump; ((FsmStateAction)__instance).Fsm.Event(canWallJump ? __instance.isTrue : __instance.isFalse); ((FsmStateAction)__instance).Finish(); return false; } } [HarmonyPatch(typeof(TestGameObjectActivator), "Evaluate")] private static class RandomizedClingGripBoneEastOuterGatePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(TestGameObjectActivator __instance) { SaveState instance = SaveState.Instance; PlayerData instance2 = PlayerData.instance; if (instance == null || instance2 == null || !instance.IsRandomized(ItemType.Skill) || !IsExactBoneEastOuterActivator(__instance)) { return true; } ApplyBoneEastOuterGate(__instance, instance, instance2); return false; } } private const string BoneEastScene = "Bone_East_04c"; private const string GreymoorScene = "Greymoor_05"; private const string ShadowScene = "Shadow_21"; private const string StandardCagePath = "Scene Control/Slab Jailer Scene/Slab Fly Large Cage"; private const string GreymoorCagePath = "Scene Control/Slab Jailer Enemy/Slab Fly Large Cage"; private const string CageFsm = "Control"; private const string CageStartState = "Init"; private const string CageGateState = "Is Here?"; private const string CageAbsentState = "Not Here"; private const string WallJumpBool = "hasWalljump"; private const string BlackThreadBool = "blackThreadWorld"; private const string BoneSceneControlPath = "Scene Control"; private const string BoneJailerScenePath = "Scene Control/Slab Jailer Scene"; private const string BoneHuntersScenePath = "Scene Control/Bone Hunters Scene"; private const string GreymoorJailerEnemyPath = "Scene Control/Slab Jailer Enemy"; private const string GreymoorSceneControlPath = "Scene Control"; private const string GreymoorSceneControlFsm = "Scene Control"; private const string GreymoorEnemySuiteState = "Enemy Suite"; private const string GreymoorTerminalState = "End"; private const string FarmersEvent = "FARMERS"; private static readonly HashSet WardenflyScenes = new HashSet(StringComparer.Ordinal) { "Bone_East_04c", "Greymoor_05", "Shadow_21" }; private static readonly FieldInfo BoneActivatorTestField = AccessTools.Field(typeof(TestGameObjectActivator), "playerDataTest"); private static readonly FieldInfo BoneActivateTargetField = AccessTools.Field(typeof(TestGameObjectActivator), "activateGameObject"); private static readonly FieldInfo BoneDeactivateTargetField = AccessTools.Field(typeof(TestGameObjectActivator), "deactivateGameObject"); private static readonly FieldInfo BoneActivateEventField = AccessTools.Field(typeof(TestGameObjectActivator), "activateEventRegister"); private static readonly FieldInfo BoneDeactivateEventField = AccessTools.Field(typeof(TestGameObjectActivator), "deactivateEventRegister"); private static readonly FieldInfo BoneQuestTestsField = AccessTools.Field(typeof(TestGameObjectActivator), "questTests"); private static readonly FieldInfo BoneEquipTestsField = AccessTools.Field(typeof(TestGameObjectActivator), "equipTests"); private static readonly FieldInfo BoneEntryWhitelistField = AccessTools.Field(typeof(TestGameObjectActivator), "entryGateWhitelist"); private static readonly FieldInfo BoneEntryBlacklistField = AccessTools.Field(typeof(TestGameObjectActivator), "entryGateBlacklist"); private static readonly FieldInfo BoneCheckActiveField = AccessTools.Field(typeof(TestGameObjectActivator), "checkActive"); private static readonly FieldInfo BoneExpectedActiveField = AccessTools.Field(typeof(TestGameObjectActivator), "expectedActive"); internal static bool SynchronizeActiveScene() { SaveState instance = SaveState.Instance; PlayerData instance2 = PlayerData.instance; GameManager instance3 = GameManager.instance; if (instance == null || instance2 == null || (Object)(object)instance3 == (Object)null || !instance.IsRandomized(ItemType.Skill) || !instance.canWallJump || !IsNativeReceiptEligible(GameManager.GetBaseSceneName(instance3.sceneName ?? string.Empty), instance2)) { return false; } string baseSceneName = GameManager.GetBaseSceneName(instance3.sceneName ?? string.Empty); if (!WardenflyScenes.Contains(baseSceneName)) { return false; } if (!(string.Equals(baseSceneName, "Bone_East_04c", StringComparison.Ordinal) ? SynchronizeBoneEastOuterGate() : ((!string.Equals(baseSceneName, "Greymoor_05", StringComparison.Ordinal)) ? HasActiveCageParentForScene(baseSceneName) : SynchronizeGreymoorOuterGate()))) { return false; } bool result = false; PlayMakerFSM[] array = Resources.FindObjectsOfTypeAll(); foreach (PlayMakerFSM val in array) { if (!IsExactCageControlFsm(val, baseSceneName) || val.Fsm == null || !IsExactCageParentActive(val, baseSceneName) || !string.Equals(val.Fsm.ActiveStateName, "Not Here", StringComparison.Ordinal)) { continue; } bool activeSelf = ((Component)val).gameObject.activeSelf; try { ((Component)val).gameObject.SetActive(true); if (!((Component)val).gameObject.activeInHierarchy || val.Fsm == null || string.IsNullOrEmpty(val.Fsm.ActiveStateName) || string.Equals(val.Fsm.ActiveStateName, "Not Here", StringComparison.Ordinal)) { if (!activeSelf) { ((Component)val).gameObject.SetActive(false); } continue; } result = true; ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogInfo((object)("[RANDOMIZER] Re-armed the " + baseSceneName + " Wardenfly after receiving randomized Cling Grip.")); } } catch (Exception ex) { if (!activeSelf && (Object)(object)((Component)val).gameObject != (Object)null) { ((Component)val).gameObject.SetActive(false); } ManualLogSource log2 = RandomizerPlugin.Log; if (log2 != null) { log2.LogWarning((object)("[RANDOMIZER] " + baseSceneName + " Wardenfly synchronization failed closed: " + ex.Message)); } return result; } } return result; } private static bool SynchronizeBoneEastOuterGate() { TestGameObjectActivator[] array = Resources.FindObjectsOfTypeAll(); foreach (TestGameObjectActivator activator in array) { if (!IsExactBoneEastOuterActivator(activator)) { continue; } try { ApplyBoneEastOuterGate(activator, SaveState.Instance, PlayerData.instance); GameObject boneActivateTarget = GetBoneActivateTarget(activator); return boneActivateTarget != null && boneActivateTarget.activeInHierarchy; } catch (Exception ex) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Bone East Wardenfly outer-gate synchronization failed closed: " + ex.Message)); } return false; } } return false; } private static void ApplyBoneEastOuterGate(TestGameObjectActivator activator, SaveState state, PlayerData playerData) { GameObject boneActivateTarget = GetBoneActivateTarget(activator); GameObject boneDeactivateTarget = GetBoneDeactivateTarget(activator); bool flag = state.canWallJump && !playerData.blackThreadWorld && !playerData.boneEastJailerClearedOut && !playerData.slab_cloak_battle_completed && !playerData.visitedUpperSlab; boneActivateTarget.SetActive(flag); boneDeactivateTarget.SetActive(!flag); } private static bool SynchronizeGreymoorOuterGate() { if (HasActiveCageParentForScene("Greymoor_05")) { return true; } PlayMakerFSM[] array = Resources.FindObjectsOfTypeAll(); foreach (PlayMakerFSM val in array) { if (!IsExactGreymoorSceneControlFsm(val) || val.Fsm == null || !string.Equals(val.Fsm.ActiveStateName, "End", StringComparison.Ordinal)) { continue; } try { val.SetState("Enemy Suite"); return HasActiveCageParentForScene("Greymoor_05"); } catch (Exception ex) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Greymoor Wardenfly outer-gate synchronization failed closed: " + ex.Message)); } return false; } } return false; } private static bool IsNativeReceiptEligible(string sceneName, PlayerData playerData) { if (playerData == null || !playerData.UnlockedFastTravel || playerData.blackThreadWorld || playerData.visitedUpperSlab || playerData.slab_cloak_battle_completed) { return false; } if (string.Equals(sceneName, "Bone_East_04c", StringComparison.Ordinal)) { if (!playerData.boneEastJailerKilled && !playerData.boneEastJailerClearedOut) { return !playerData.CurseKilledFlyBoneEast; } return false; } if (string.Equals(sceneName, "Greymoor_05", StringComparison.Ordinal)) { if (playerData.defeatedVampireGnatBoss && playerData.citadelWoken && !playerData.greymoor05_clearedOut && !playerData.greymoor05_killedJailer) { return !playerData.CurseKilledFlyGreymoor; } return false; } if (string.Equals(sceneName, "Shadow_21", StringComparison.Ordinal)) { return !playerData.CurseKilledFlySwamp; } return false; } private static bool HasActiveCageParentForScene(string sceneName) { PlayMakerFSM[] array = Resources.FindObjectsOfTypeAll(); foreach (PlayMakerFSM fsm in array) { if (IsExactCageControlFsm(fsm, sceneName) && IsExactCageParentActive(fsm, sceneName)) { return true; } } return false; } private static bool IsExactCageParentActive(PlayMakerFSM fsm, string sceneName) { object obj; if (fsm == null) { obj = null; } else { Transform transform = ((Component)fsm).transform; obj = ((transform != null) ? transform.parent : null); } Transform val = (Transform)obj; if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy) { return false; } string b = (string.Equals(sceneName, "Greymoor_05", StringComparison.Ordinal) ? "Scene Control/Slab Jailer Enemy" : "Scene Control/Slab Jailer Scene"); return string.Equals(Utils.GetHierarchyPath(val), b, StringComparison.Ordinal); } private static bool IsExactCageWallJumpGate(PlayerDataVariableTest action) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Invalid comparison between Unknown and I4 //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Invalid comparison between Unknown and I4 if (action == null || !((FsmStateAction)action).Enabled || action.VariableName == null || !string.Equals(action.VariableName.Value, "hasWalljump", StringComparison.Ordinal) || action.ExpectedValue == null || (int)action.ExpectedValue.Type != 2 || action.ExpectedValue.useVariable || action.ExpectedValue.boolValue || !IsNamedLocalEvent(action.IsExpectedEvent, "TRUE") || !IsNamedLocalEvent(action.IsNotExpectedEvent, "FALSE") || !IsExactCageControlAction((FsmStateAction)(object)action)) { return false; } FsmStateAction[] actions = ((FsmStateAction)action).State.Actions; if (actions != null && actions.Length == 2 && (object)actions[0] == action) { FsmStateAction obj = actions[1]; PlayerDataVariableTest val = (PlayerDataVariableTest)(object)((obj is PlayerDataVariableTest) ? obj : null); if (val != null) { if (val.VariableName != null && ((FsmStateAction)val).Enabled && string.Equals(val.VariableName.Value, "blackThreadWorld", StringComparison.Ordinal) && val.ExpectedValue != null && (int)val.ExpectedValue.Type == 2 && !val.ExpectedValue.useVariable && val.ExpectedValue.boolValue && IsNamedLocalEvent(val.IsExpectedEvent, "TRUE")) { return IsNamedLocalEvent(val.IsNotExpectedEvent, "FALSE"); } return false; } } return false; } private static bool IsExactGreymoorOuterWallJumpGate(PlayerDataBoolTest action) { if (action == null || action.boolName == null || !string.Equals(action.boolName.Value, "hasWalljump", StringComparison.Ordinal) || !IsNoEvent(action.isTrue) || !IsNamedLocalEvent(action.isFalse, "FARMERS") || !IsExactActionIdentity((FsmStateAction)(object)action, "Greymoor_05", "Scene Control", "Scene Control", "Enemy Suite")) { return false; } FsmStateAction[] actions = ((FsmStateAction)action).State.Actions; if (actions != null && actions.Length == 7 && (object)actions[0] == action && IsExactBoolAction(actions[1], "blackThreadWorld", "FARMERS", null, enabled: true) && IsExactBoolAction(actions[2], "citadelWoken", null, "FARMERS", enabled: true) && IsExactBoolAction(actions[3], "greymoor05_killedJailer", "FARMERS", null, enabled: true) && IsExactBoolAction(actions[4], "previouslyVisitedGreymoor_05", null, "FARMERS", enabled: false) && IsExactBoolAction(actions[5], "visitedUpperSlab", "FARMERS", null, enabled: true)) { return IsExactBoolAction(actions[6], "slab_cloak_battle_completed", "FARMERS", "JAILER", enabled: true); } return false; } private static bool IsExactBoolAction(FsmStateAction action, string fieldName, string trueEvent, string falseEvent, bool enabled) { PlayerDataBoolTest val = (PlayerDataBoolTest)(object)((action is PlayerDataBoolTest) ? action : null); if (val == null || ((FsmStateAction)val).Enabled != enabled || val.boolName == null || !string.Equals(val.boolName.Value, fieldName, StringComparison.Ordinal)) { return false; } if (IsExpectedEvent(val.isTrue, trueEvent)) { return IsExpectedEvent(val.isFalse, falseEvent); } return false; } private static bool IsExpectedEvent(FsmEvent fsmEvent, string expectedName) { if (expectedName != null) { return IsNamedLocalEvent(fsmEvent, expectedName); } return IsNoEvent(fsmEvent); } private static bool IsNoEvent(FsmEvent fsmEvent) { if (fsmEvent != null) { return string.IsNullOrEmpty(fsmEvent.Name); } return true; } private static bool IsNamedLocalEvent(FsmEvent fsmEvent, string expectedName) { if (fsmEvent != null && !fsmEvent.IsSystemEvent && !fsmEvent.IsGlobal) { return string.Equals(fsmEvent.Name, expectedName, StringComparison.Ordinal); } return false; } private static bool IsExactBoneEastOuterActivator(TestGameObjectActivator activator) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)activator == (Object)null) && !((Object)(object)((Component)activator).gameObject == (Object)null)) { Scene scene = ((Component)activator).gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, "Bone_East_04c", StringComparison.Ordinal) && string.Equals(Utils.GetHierarchyPath(((Component)activator).transform), "Scene Control", StringComparison.Ordinal) && HasExactPath(GetBoneActivateTarget(activator), "Bone_East_04c", "Scene Control/Slab Jailer Scene") && HasExactPath(GetBoneDeactivateTarget(activator), "Bone_East_04c", "Scene Control/Bone Hunters Scene") && HasExactBoneActivatorSideData(activator)) { object? obj = BoneActivatorTestField?.GetValue(activator); return HasExactBoneEastOuterTest((PlayerDataTest)((obj is PlayerDataTest) ? obj : null)); } } return false; } private static bool HasExactBoneActivatorSideData(TestGameObjectActivator activator) { if (BoneActivateEventField != null && BoneDeactivateEventField != null && BoneQuestTestsField != null && BoneEquipTestsField != null && BoneEntryWhitelistField != null && BoneEntryBlacklistField != null && BoneCheckActiveField != null && BoneExpectedActiveField != null && string.IsNullOrEmpty(BoneActivateEventField.GetValue(activator) as string) && string.IsNullOrEmpty(BoneDeactivateEventField.GetValue(activator) as string) && IsEmptyArrayField(BoneQuestTestsField, activator) && IsEmptyArrayField(BoneEquipTestsField, activator) && IsEmptyArrayField(BoneEntryWhitelistField, activator) && IsEmptyArrayField(BoneEntryBlacklistField, activator) && BoneCheckActiveField.GetValue(activator) == null && BoneExpectedActiveField.GetValue(activator) is bool flag) { return !flag; } return false; } private static bool IsEmptyArrayField(FieldInfo field, object instance) { if (field.GetValue(instance) is Array array) { return array.Length == 0; } return false; } private static bool HasExactBoneEastOuterTest(PlayerDataTest test) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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_0095: Unknown result type (might be due to invalid IL or missing references) if (test == null || test.TestGroups == null || test.TestGroups.Length != 1 || test.TestGroups[0].Tests == null) { return false; } Test[] tests = test.TestGroups[0].Tests; if (tests.Length == 5 && IsExactBoolCondition(tests[0], "hasWalljump", expectedValue: true) && IsExactBoolCondition(tests[1], "blackThreadWorld", expectedValue: false) && IsExactBoolCondition(tests[2], "boneEastJailerClearedOut", expectedValue: false) && IsExactBoolCondition(tests[3], "slab_cloak_battle_completed", expectedValue: false)) { return IsExactBoolCondition(tests[4], "visitedUpperSlab", expectedValue: false); } return false; } private static bool IsExactBoolCondition(Test condition, string fieldName, bool expectedValue) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if ((int)condition.Type == 0 && string.Equals(condition.FieldName, fieldName, StringComparison.Ordinal)) { return condition.BoolValue == expectedValue; } return false; } private static GameObject GetBoneActivateTarget(TestGameObjectActivator activator) { object? obj = BoneActivateTargetField?.GetValue(activator); return (GameObject)((obj is GameObject) ? obj : null); } private static GameObject GetBoneDeactivateTarget(TestGameObjectActivator activator) { object? obj = BoneDeactivateTargetField?.GetValue(activator); return (GameObject)((obj is GameObject) ? obj : null); } private static bool HasExactPath(GameObject gameObject, string sceneName, string hierarchyPath) { //IL_000a: 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 ((Object)(object)gameObject != (Object)null) { Scene scene = gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, sceneName, StringComparison.Ordinal)) { return string.Equals(Utils.GetHierarchyPath(gameObject.transform), hierarchyPath, StringComparison.Ordinal); } } return false; } private static bool IsExactCageControlAction(FsmStateAction action) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0051: Unknown result type (might be due to invalid IL or missing references) if (action != null && (Object)(object)action.Owner != (Object)null) { HashSet wardenflyScenes = WardenflyScenes; Scene scene = action.Owner.scene; if (wardenflyScenes.Contains(((Scene)(ref scene)).name)) { string hierarchyPath = Utils.GetHierarchyPath(action.Owner.transform); scene = action.Owner.scene; if (string.Equals(hierarchyPath, GetExpectedCagePath(((Scene)(ref scene)).name), StringComparison.Ordinal) && action.Fsm != null && string.Equals(action.Fsm.Name, "Control", StringComparison.Ordinal) && action.State != null) { return string.Equals(action.State.Name, "Is Here?", StringComparison.Ordinal); } } } return false; } private static bool IsExactActionIdentity(FsmStateAction action, string sceneName, string hierarchyPath, string fsmName, string stateName) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (action != null && (Object)(object)action.Owner != (Object)null) { Scene scene = action.Owner.scene; if (string.Equals(((Scene)(ref scene)).name, sceneName, StringComparison.Ordinal) && string.Equals(Utils.GetHierarchyPath(action.Owner.transform), hierarchyPath, StringComparison.Ordinal) && action.Fsm != null && string.Equals(action.Fsm.Name, fsmName, StringComparison.Ordinal) && action.State != null) { return string.Equals(action.State.Name, stateName, StringComparison.Ordinal); } } return false; } private static bool IsExactCageControlFsm(PlayMakerFSM fsm, string activeScene) { //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)(object)fsm != (Object)null && (Object)(object)((Component)fsm).gameObject != (Object)null) { Scene scene = ((Component)fsm).gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, activeScene, StringComparison.Ordinal) && WardenflyScenes.Contains(activeScene) && string.Equals(Utils.GetHierarchyPath(((Component)fsm).transform), GetExpectedCagePath(activeScene), StringComparison.Ordinal) && string.Equals(fsm.FsmName, "Control", StringComparison.Ordinal) && fsm.Fsm != null && fsm.Fsm.RestartOnEnable) { return string.Equals(fsm.Fsm.StartState, "Init", StringComparison.Ordinal); } } return false; } private static bool IsExactGreymoorSceneControlFsm(PlayMakerFSM fsm) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)fsm != (Object)null && (Object)(object)((Component)fsm).gameObject != (Object)null) { Scene scene = ((Component)fsm).gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, "Greymoor_05", StringComparison.Ordinal) && string.Equals(Utils.GetHierarchyPath(((Component)fsm).transform), "Scene Control", StringComparison.Ordinal)) { return string.Equals(fsm.FsmName, "Scene Control", StringComparison.Ordinal); } } return false; } private static string GetExpectedCagePath(string sceneName) { if (!string.Equals(sceneName, "Greymoor_05", StringComparison.Ordinal)) { return "Scene Control/Slab Jailer Scene/Slab Fly Large Cage"; } return "Scene Control/Slab Jailer Enemy/Slab Fly Large Cage"; } } [HarmonyPatch(typeof(PlayMakerFSM), "Start")] internal static class FleaCaravanWarpPatches { private sealed class RecordGreymoorCaravanRideAction : FsmStateAction { public override void OnEnter() { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRoomBound) { instance.rodeFleaCaravanToGreymoor = true; } ((FsmStateAction)this).Finish(); } } private const string ArrivalSceneName = "Greymoor_08_caravan"; private const string ArrivalObjectName = "door_caravanTravelEnd"; private const string ArrivalFsmName = "Travel End"; private const string ArrivalStateName = "Send Event"; private static readonly string[] ExpectedActionTypes = new string[7] { "HutongGames.PlayMaker.Actions.CallMethodProper", "HutongGames.PlayMaker.Actions.CallMethodProper", "HutongGames.PlayMaker.Actions.SendMessage", "QueueSaveGame", "HutongGames.PlayMaker.Actions.RemoveHeroInputBlocker", "HutongGames.PlayMaker.Actions.SetPlayerDataBool", "HutongGames.PlayMaker.Actions.SendEventToRegister" }; [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(PlayMakerFSM __instance) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).gameObject == (Object)null) { return; } Scene scene = ((Component)__instance).gameObject.scene; if (!string.Equals(((Scene)(ref scene)).name, "Greymoor_08_caravan", StringComparison.Ordinal) || !string.Equals(((Object)((Component)__instance).gameObject).name, "door_caravanTravelEnd", StringComparison.Ordinal) || !string.Equals(__instance.FsmName, "Travel End", StringComparison.Ordinal)) { return; } Fsm fsm = __instance.Fsm; FsmState val = ((fsm != null) ? fsm.GetState("Send Event") : null); FsmStateAction[] array = ((val != null) ? val.Actions : null); if (array == null) { LogFailure("the shipped Send Event state was missing"); } else if (!array.Any((FsmStateAction action) => action is RecordGreymoorCaravanRideAction)) { if (!array.Select((FsmStateAction action) => ((object)action)?.GetType().FullName ?? string.Empty).ToArray().SequenceEqual(ExpectedActionTypes)) { LogFailure("the shipped Send Event action layout no longer matched"); return; } RecordGreymoorCaravanRideAction recordGreymoorCaravanRideAction = new RecordGreymoorCaravanRideAction(); ((FsmStateAction)recordGreymoorCaravanRideAction).Init(val); val.Actions = array.Take(3).Concat((IEnumerable)(object)new FsmStateAction[1] { recordGreymoorCaravanRideAction }).Concat(array.Skip(3)) .ToArray(); } } private static void LogFailure(string reason) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Flea Caravan F4 unlock failed closed: " + reason + ".")); } } } public class FleaPatches { [HarmonyPatch(typeof(PlayerData), "get_SavedFleasCount")] private static class PlayerData_SavedFleasCount_Patch { private static bool Prefix(ref int __result) { if (SaveState.Instance == null || !SaveState.Instance.IsRandomized(ItemType.Flea)) { return true; } __result = GetReceivedFleaCount(); return false; } } [HarmonyPatch(typeof(PlayerData), "get_IsFleaPinMapKeyVisible")] private static class PlayerData_IsFleaPinMapKeyVisible_Patch { private static bool Prefix(PlayerData __instance, ref bool __result) { if (SaveState.Instance == null || !SaveState.Instance.IsRandomized(ItemType.Flea)) { return true; } if (!__instance.HasAnyFleaPin) { __result = false; return false; } __result = GetReceivedFleaCount() < 30; return false; } } [HarmonyPatch(typeof(QuestTargetPlayerDataBools), "GetCounts")] private static class QuestTargetPlayerDataBools_GetCounts_Patch { private static bool Prefix(string ___pdFieldTemplate, ref int completed, ref int total) { if (SaveState.Instance == null || !SaveState.Instance.IsRandomized(ItemType.Flea) || !string.Equals(___pdFieldTemplate, "SavedFlea_", StringComparison.Ordinal)) { return true; } completed = GetReceivedFleaCount(); total = 30; return false; } } [HarmonyPatch(typeof(PlayerDataBoolTest), "OnEnter")] private static class NamedNpcFleaBoolSourceVisibilityPatch { [HarmonyPrefix] private static bool Prefix(PlayerDataBoolTest __instance) { string text = null; string locationName = null; object obj; if (__instance == null) { obj = null; } else { FsmString boolName = __instance.boolName; obj = ((boolName != null) ? boolName.Value : null); } string a = (string)obj; if (string.Equals(a, "CaravanLechSaved", StringComparison.Ordinal) && (IsExactAction((FsmStateAction)(object)__instance, "Greymoor_24", "Caravan Lech Strung Up", "Control", "Init") || IsExactAction((FsmStateAction)(object)__instance, "Greymoor_24", "Caravan Lech", "Custom Dialogue - Act3 Rescue", "Convo Check"))) { text = "Flea: Greymoor - Kratt"; locationName = "Flea: Greymoor - Kratt"; } else if (string.Equals(a, "tamedGiantFlea", StringComparison.Ordinal) && IsExactAction((FsmStateAction)(object)__instance, "Arborium_08", "Giant Flea Cage", "Flea Control", "Idle")) { text = "Flea: Memorium - Huge Flea"; locationName = "Flea: Memorium - Huge Flea"; } if (text == null || !ShouldExposeReceivedNpcFleaSource(text, locationName)) { return true; } ((FsmStateAction)__instance).Fsm.Event(__instance.isFalse); ((FsmStateAction)__instance).Finish(); return false; } } [HarmonyPatch(typeof(PlayerDataVariableTest), "OnEnter")] private static class VogSourceVisibilityPatch { [HarmonyPrefix] private static bool Prefix(PlayerDataVariableTest __instance) { object obj; if (__instance == null) { obj = null; } else { FsmVar expectedValue = __instance.ExpectedValue; obj = ((expectedValue != null) ? expectedValue.GetValue() : null); } object obj2 = obj; if (obj2 is bool && (bool)obj2) { FsmString variableName = __instance.VariableName; if (string.Equals((variableName != null) ? variableName.Value : null, "MetTroupeHunterWild", StringComparison.Ordinal) && (IsExactAction((FsmStateAction)(object)__instance, "Bellway_Aqueduct", "Caravan Troupe Hunter Wild", "Dialogue", "Met?") || IsExactAction((FsmStateAction)(object)__instance, "Bellway_Aqueduct", "Caravan Troupe Hunter Wild", "Dialogue", "Met? N")) && ShouldExposeReceivedNpcFleaSource("Flea: Putrified Ducts - Vog", "Flea: Putrified Ducts - Vog")) { ((FsmStateAction)__instance).Fsm.Event(__instance.IsNotExpectedEvent); ((FsmStateAction)__instance).Finish(); return false; } } return true; } } [HarmonyPatch(typeof(SetPlayerDataBool), "OnEnter")] private static class NamedNpcFleaBoolSourceCompletionPatch { [HarmonyPostfix] private static void Postfix(SetPlayerDataBool __instance) { if (__instance?.value != null && __instance.value.Value && __instance.boolName != null && PlayerData.instance != null) { string value = __instance.boolName.Value; if (string.Equals(value, "CaravanLechSaved", StringComparison.Ordinal) && PlayerData.instance.CaravanLechSaved && (IsExactAction((FsmStateAction)(object)__instance, "Greymoor_24", "Caravan Lech Strung Up", "Control", "Break") || IsExactAction((FsmStateAction)(object)__instance, "Greymoor_24", "Caravan Lech", "Custom Dialogue - Act3 Rescue", "Meet"))) { ReportNpcFleaSource("Flea: Greymoor - Kratt"); } else if (string.Equals(value, "tamedGiantFlea", StringComparison.Ordinal) && PlayerData.instance.tamedGiantFlea && IsExactAction((FsmStateAction)(object)__instance, "Arborium_08", "Giant Flea", "Control", "Stun")) { ReportNpcFleaSource("Flea: Memorium - Huge Flea"); } } } } [HarmonyPatch(typeof(SetPlayerDataVariable), "OnEnter")] private static class VogSourceCompletionPatch { [HarmonyPostfix] private static void Postfix(SetPlayerDataVariable __instance) { if (__instance != null) { FsmString variableName = __instance.VariableName; if (string.Equals((variableName != null) ? variableName.Value : null, "MetTroupeHunterWild", StringComparison.Ordinal) && (IsExactAction((FsmStateAction)(object)__instance, "Bellway_Aqueduct", "Caravan Troupe Hunter Wild", "Dialogue", "Meet") || IsExactAction((FsmStateAction)(object)__instance, "Bellway_Aqueduct", "Caravan Troupe Hunter Wild", "Dialogue", "Meet N")) && PlayerData.instance != null && PlayerData.instance.MetTroupeHunterWild) { ReportNpcFleaSource("Flea: Putrified Ducts - Vog"); } } } } [HarmonyPatch(typeof(SavedFleaActivator), "Start")] private static class SavedFleaActivator_Start_Patch { private static bool Prefix(SavedFleaActivator __instance) { if (SaveState.Instance == null || !SaveState.Instance.IsRandomized(ItemType.Flea) || SavedFleaActivatorTemplateField == null || SavedFleaActivatorParentsField == null || ActivateFleasMethod == null) { return true; } if (!string.Equals(SavedFleaActivatorTemplateField.GetValue(__instance) as string, "SavedFlea_", StringComparison.Ordinal)) { return true; } if (!(SavedFleaActivatorParentsField.GetValue(__instance) is Transform[] array)) { return true; } int num = GetReceivedOrdinaryFleaCount(); Transform[] array2 = array; foreach (Transform val in array2) { object[] array3 = new object[3] { val, num, num }; ActivateFleasMethod.Invoke(null, array3); num = (int)array3[2]; } return false; } } private const string SavedFleaFieldTemplate = "SavedFlea_"; private const int OrdinaryFleaCount = 27; private const int TotalFleaCount = 30; private const float GoalCheckIntervalSeconds = 0.25f; private static float nextGoalCheckTime; internal const string KrattItemName = "Flea: Greymoor - Kratt"; internal const string VogItemName = "Flea: Putrified Ducts - Vog"; internal const string HugeFleaItemName = "Flea: Memorium - Huge Flea"; internal const string KrattLocationName = "Flea: Greymoor - Kratt"; internal const string VogLocationName = "Flea: Putrified Ducts - Vog"; internal const string HugeFleaLocationName = "Flea: Memorium - Huge Flea"; private static readonly FieldInfo SavedFleaActivatorTemplateField = AccessTools.Field(typeof(SavedFleaActivator), "pdBoolTemplate"); private static readonly FieldInfo SavedFleaActivatorParentsField = AccessTools.Field(typeof(SavedFleaActivator), "fleaParents"); private static readonly MethodInfo ActivateFleasMethod = AccessTools.Method(typeof(SavedFleaActivator), "ActivateFleas", new Type[3] { typeof(Transform), typeof(int), typeof(int).MakeByRefType() }, (Type[])null); internal static int GetReceivedFleaCount() { return SaveState.Instance?.GetReceivedFleaCount() ?? 0; } internal static bool IsNamedNpcFleaLocation(string locationName) { if (!string.Equals(locationName, "Flea: Greymoor - Kratt", StringComparison.OrdinalIgnoreCase) && !string.Equals(locationName, "Flea: Putrified Ducts - Vog", StringComparison.OrdinalIgnoreCase)) { return string.Equals(locationName, "Flea: Memorium - Huge Flea", StringComparison.OrdinalIgnoreCase); } return true; } internal static void Update() { if (!(Time.unscaledTime < nextGoalCheckTime)) { nextGoalCheckTime = Time.unscaledTime + 0.25f; SaveState.Instance?.TryCompleteFleaHuntGoal(); } } internal static int GetReceivedOrdinaryFleaCount() { SaveState instance = SaveState.Instance; if (instance == null) { return 0; } int num = 0; if (HasReceivedItem(instance, "Flea: Greymoor - Kratt")) { num++; } if (HasReceivedItem(instance, "Flea: Putrified Ducts - Vog")) { num++; } if (HasReceivedItem(instance, "Flea: Memorium - Huge Flea")) { num++; } return Math.Max(0, Math.Min(27, GetReceivedFleaCount() - num)); } private static bool HasReceivedItem(SaveState state, string itemName) { if (state?.receivedItems != null) { return state.receivedItems.Contains(ItemSet.GetCanonicalItemName(itemName)); } return false; } private static bool IsExactAction(FsmStateAction action, string sceneName, string objectName, string fsmName, string stateName) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (action != null && (Object)(object)action.Owner != (Object)null) { Scene scene = action.Owner.scene; if (string.Equals(((Scene)(ref scene)).name, sceneName, StringComparison.Ordinal) && string.Equals(((Object)action.Owner).name, objectName, StringComparison.Ordinal) && action.Fsm != null && string.Equals(action.Fsm.Name, fsmName, StringComparison.Ordinal) && action.State != null) { return string.Equals(action.State.Name, stateName, StringComparison.Ordinal); } } return false; } private static bool CanUseNpcFleaLocation(string locationName) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsLocationEnabled(locationName)) { return instance.IsLocationInSeed(locationName); } return false; } private static bool ShouldExposeReceivedNpcFleaSource(string itemName, string locationName) { SaveState instance = SaveState.Instance; if (CanUseNpcFleaLocation(locationName) && !instance.IsLocationChecked(locationName)) { return HasReceivedItem(instance, itemName); } return false; } private static void ReportNpcFleaSource(string locationName) { SaveState instance = SaveState.Instance; if (CanUseNpcFleaLocation(locationName) && !instance.IsLocationChecked(locationName)) { instance.CheckLocation(locationName); ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogInfo((object)("[RANDOMIZER] Reported named NPC Flea source: " + locationName)); } } } } [HarmonyPatch(typeof(PlayMakerFSM), "Start")] internal static class LostLaceGoalPatch { private sealed class CompleteLostLaceGoal : FsmStateAction { public override void OnEnter() { SaveState instance = SaveState.Instance; if (instance != null && string.Equals(instance.goal, "act_3", StringComparison.Ordinal)) { instance.CheckLocation("Goal"); } ((FsmStateAction)this).Finish(); } } private const string SceneName = "Abyss_Cocoon"; private const string ObjectName = "Superjump Sequence"; private const string FsmName = "Control"; private const string DefeatState = "Dormant"; private const string DefeatEvent = "LACE DEFEATED"; private const string CompletionState = "Start Pause"; [HarmonyPostfix] private static void Postfix(PlayMakerFSM __instance) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null) { return; } Scene scene = ((Component)__instance).gameObject.scene; if (!string.Equals(((Scene)(ref scene)).name, "Abyss_Cocoon", StringComparison.Ordinal) || !string.Equals(((Object)__instance).name, "Superjump Sequence", StringComparison.Ordinal) || !string.Equals(__instance.FsmName, "Control", StringComparison.Ordinal)) { return; } FsmState val = FindState(__instance, "Dormant"); FsmState val2 = FindState(__instance, "Start Pause"); if (val == null || val2 == null || !HasTransition(val, "LACE DEFEATED", "Start Pause") || val2.Actions == null) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)"[RANDOMIZER] Lost Lace goal hook was not installed because its validated native completion path changed."); } return; } FsmStateAction[] actions = val2.Actions; for (int i = 0; i < actions.Length; i++) { if (actions[i] is CompleteLostLaceGoal) { return; } } CompleteLostLaceGoal completeLostLaceGoal = new CompleteLostLaceGoal(); ((FsmStateAction)completeLostLaceGoal).Init(val2); FsmStateAction[] array = (FsmStateAction[])(object)new FsmStateAction[val2.Actions.Length + 1]; array[0] = (FsmStateAction)(object)completeLostLaceGoal; Array.Copy(val2.Actions, 0, array, 1, val2.Actions.Length); val2.Actions = array; ManualLogSource log2 = RandomizerPlugin.Log; if (log2 != null) { log2.LogInfo((object)"[RANDOMIZER] Act 3 goal anchored to Lost Lace defeat."); } } private static FsmState FindState(PlayMakerFSM fsm, string stateName) { if (fsm.FsmStates == null) { return null; } FsmState[] fsmStates = fsm.FsmStates; foreach (FsmState val in fsmStates) { if (val != null && string.Equals(val.Name, stateName, StringComparison.Ordinal)) { return val; } } return null; } private static bool HasTransition(FsmState state, string eventName, string targetState) { if (state.Transitions == null) { return false; } FsmTransition[] transitions = state.Transitions; foreach (FsmTransition val in transitions) { if (val != null && string.Equals(val.EventName, eventName, StringComparison.Ordinal) && string.Equals(val.ToState, targetState, StringComparison.Ordinal)) { return true; } } return false; } } [HarmonyPatch(typeof(SetEndingCompleted), "OnEnter")] internal static class EndingGoalPatch { private const int ActTwoRegularEndingValue = 1; private const int ActTwoCursedEndingValue = 2; private const int ActTwoSoulSnareEndingValue = 4; private const int ActThreeEndingValue = 8; private static void Postfix() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected I4, but got Unknown SaveState instance = SaveState.Instance; if (instance != null && PlayerData.instance != null && MatchesConfiguredGoal(instance.goal, (int)PlayerData.instance.LastCompletedEnding)) { instance.CheckLocation("Goal"); } } internal static bool MatchesConfiguredGoal(string goal, int endingValue) { if (string.Equals(goal, "act_2", StringComparison.Ordinal)) { if (endingValue != 1 && endingValue != 2) { return endingValue == 4; } return true; } if (string.Equals(goal, "act_3", StringComparison.Ordinal)) { return endingValue == 8; } return false; } } [HarmonyPatch(typeof(PlayMakerFSM), "Start")] internal static class GreyrootCurseQuestPatches { [HarmonyPatch(typeof(PlayerDataVariableTest), "OnEnter")] private static class YarnabyCurseGateRuntimePatch { [HarmonyPrefix] private static bool Prefix(PlayerDataVariableTest __instance) { if (!IsExactYarnabyCurseGate(__instance)) { return true; } if (HasGenuineGreyrootCurse(PlayerData.instance)) { return true; } ((FsmStateAction)__instance).Fsm.Event(__instance.IsNotExpectedEvent); ((FsmStateAction)__instance).Finish(); ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogInfo((object)"[RANDOMIZER] Yarnaby ignored a temporary or non-Greyroot Cursed Crest."); } return false; } } private sealed class ApplyGenuineGreyrootCurse : FsmStateAction { private readonly ToolCrest cursedCrest; internal ApplyGenuineGreyrootCurse(ToolCrest cursedCrest) { this.cursedCrest = cursedCrest; } public override void OnEnter() { TrapManager.RelinquishCursedCrestForNativeCurse(); if (!ToolPatches.AutoEquipNativeRiteCursedCrest(cursedCrest)) { LogFailure("Greyroot's permanent Cursed Crest equip was rejected"); } ((FsmStateAction)this).Finish(); } } private const string YarnabyScene = "Belltown_Room_doctor"; private const string YarnabyObject = "Doctor Fly"; private const string YarnabyFsm = "Dialogue"; private const string FirstCurseGateState = "Cursed? 2"; private const string RepeatCurseGateState = "Cursed? 3"; private const string NativeCurseScene = "Shellwood_25b"; private const string NativeCurseObject = "door_curseSequenceEnd"; private const string NativeCurseFsm = "Curse Sequence"; private const string NativeSetCursedState = "Set Cursed"; [HarmonyPrefix] [HarmonyPriority(0)] private static void Prefix(PlayMakerFSM __instance) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__instance == (Object)null) && !((Object)(object)((Component)__instance).gameObject == (Object)null)) { Scene scene = ((Component)__instance).gameObject.scene; string name = ((Scene)(ref scene)).name; if (Matches(__instance, name, "Belltown_Room_doctor", "Doctor Fly", "Dialogue")) { ValidateYarnabyCurseGate(__instance); } else if (Matches(__instance, name, "Shellwood_25b", "door_curseSequenceEnd", "Curse Sequence")) { PatchNativeCurseOwnership(__instance); } } } private static bool Matches(PlayMakerFSM fsm, string actualScene, string expectedScene, string expectedObject, string expectedFsm) { if (string.Equals(actualScene, expectedScene, StringComparison.OrdinalIgnoreCase) && string.Equals(((Object)((Component)fsm).gameObject).name, expectedObject, StringComparison.Ordinal)) { return string.Equals(fsm.FsmName, expectedFsm, StringComparison.Ordinal); } return false; } private static void ValidateYarnabyCurseGate(PlayMakerFSM fsm) { Fsm fsm2 = fsm.Fsm; FsmState val = ((fsm2 != null) ? fsm2.GetState("Cursed? 2") : null); Fsm fsm3 = fsm.Fsm; FsmState val2 = ((fsm3 != null) ? fsm3.GetState("Cursed? 3") : null); bool num = ((val != null) ? val.Actions : null) != null && val.Actions.Length == 2 && val.Actions[0] is ActivateInteractible && IsNativeAnyCurseTest(val.Actions[1]) && HasTransition(val, "TRUE", "Quest Active?") && HasTransition(val, "FINISHED", "Convo Choice"); bool flag = ((val2 != null) ? val2.Actions : null) != null && val2.Actions.Length == 1 && IsNativeAnyCurseTest(val2.Actions[0]) && HasTransition(val2, "TRUE", "Quest Active?") && HasTransition(val2, "FINISHED", "Interacted"); if (!num || !flag) { LogFailure("Yarnaby's shipped IsAnyCursed gates no longer matched"); } } private static void PatchNativeCurseOwnership(PlayMakerFSM fsm) { Fsm fsm2 = fsm.Fsm; FsmState val = ((fsm2 != null) ? fsm2.GetState("Set Cursed") : null); FsmStateAction[] array = ((val != null) ? val.Actions : null); if (array == null || array.Length != 6 || !(array[4] is ApplyGenuineGreyrootCurse)) { AutoEquipCrest val2 = (AutoEquipCrest)((array != null && array.Length > 4) ? /*isinst with value type is only supported in some contexts*/: null); if (array == null || array.Length != 6 || !(array[0] is SetPlayerDataVariable) || !(array[1] is SetPlayerDataVariable) || !(array[2] is SetPlayerDataVariable) || !(array[3] is SendEventToRegister) || val2 == null || !IsNativeCursedCrest(val2) || !(array[5] is CallMethodProper) || !HasTransition(val, "FINISHED", "Wait")) { LogFailure("Greyroot's shipped Set Cursed state no longer matched"); return; } Object value = val2.Crest.Value; ApplyGenuineGreyrootCurse applyGenuineGreyrootCurse = new ApplyGenuineGreyrootCurse((ToolCrest)(object)((value is ToolCrest) ? value : null)); ((FsmStateAction)applyGenuineGreyrootCurse).Init(val); array[4] = (FsmStateAction)(object)applyGenuineGreyrootCurse; } } private static bool IsNativeAnyCurseTest(FsmStateAction action) { PlayerDataVariableTest val = (PlayerDataVariableTest)(object)((action is PlayerDataVariableTest) ? action : null); if (val != null) { FsmString variableName = val.VariableName; if (string.Equals((variableName != null) ? variableName.Value : null, "IsAnyCursed", StringComparison.Ordinal) && val.ExpectedValue != null && val.ExpectedValue.boolValue) { FsmEvent isExpectedEvent = val.IsExpectedEvent; return string.Equals((isExpectedEvent != null) ? isExpectedEvent.Name : null, "TRUE", StringComparison.Ordinal); } } return false; } private static bool IsNativeCursedCrest(AutoEquipCrest action) { ToolCrest cursedCrest = Gameplay.CursedCrest; if ((Object)(object)cursedCrest != (Object)null) { object obj; if (action == null) { obj = null; } else { FsmObject crest = action.Crest; obj = ((crest != null) ? crest.Value : null); } ToolCrest val = (ToolCrest)((obj is ToolCrest) ? obj : null); if (val != null) { return string.Equals(val.name, cursedCrest.name, StringComparison.Ordinal); } } return false; } private static bool IsExactYarnabyCurseGate(PlayerDataVariableTest action) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (action != null && !((Object)(object)((FsmStateAction)action).Owner == (Object)null) && ((FsmStateAction)action).Fsm != null && ((FsmStateAction)action).State != null) { Scene scene = ((FsmStateAction)action).Owner.scene; if (string.Equals(((Scene)(ref scene)).name, "Belltown_Room_doctor", StringComparison.OrdinalIgnoreCase) && string.Equals(((Object)((FsmStateAction)action).Owner).name, "Doctor Fly", StringComparison.Ordinal) && string.Equals(((FsmStateAction)action).Fsm.Name, "Dialogue", StringComparison.Ordinal) && (string.Equals(((FsmStateAction)action).State.Name, "Cursed? 2", StringComparison.Ordinal) || string.Equals(((FsmStateAction)action).State.Name, "Cursed? 3", StringComparison.Ordinal))) { return IsNativeAnyCurseTest((FsmStateAction)(object)action); } } return false; } private static bool HasTransition(FsmState state, string eventName, string targetState) { if (((state != null) ? state.Transitions : null) == null) { return false; } FsmTransition[] transitions = state.Transitions; foreach (FsmTransition val in transitions) { if (val != null && string.Equals(val.EventName, eventName, StringComparison.Ordinal) && string.Equals(val.ToState, targetState, StringComparison.Ordinal)) { return true; } } return false; } private static bool HasGenuineGreyrootCurse(PlayerData playerData) { if (playerData != null && !TrapManager.IsCursedCrestActive && !playerData.IsCurrentCrestTemp && playerData.IsAnyCursed && playerData.gainedCurse) { return !playerData.BelltownDoctorCuredCurse; } return false; } private static void LogFailure(string reason) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Rite of Rebirth safety patch failed closed: " + reason + ".")); } } } internal static class HeroControllerAbilityPatchUtil { public static T Field(object instance, string name) { return Traverse.Create(instance).Field(name).GetValue(); } public static T Property(object instance, string name) { if (instance == null) { throw new ArgumentNullException("instance"); } PropertyInfo? property = instance.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property == null) { throw new MissingMemberException(instance.GetType().FullName, name); } return (T)property.GetValue(instance, null); } public static T ConfigField(object heroController, string name) { return Property(Property(heroController, "Config"), name); } public static bool CState(object heroController, string name) { return Traverse.Create(Field(heroController, "cState")).Field(name).GetValue(); } public static T Call(object instance, string methodName, params object[] args) { return Traverse.Create(instance).Method(methodName, args).GetValue(); } public static bool ResolveBrollyOwnership(bool vanillaOwned) { SaveState instance = SaveState.Instance; bool owned = ((instance == null || !instance.IsRandomized(ItemType.Skill)) ? vanillaOwned : instance.canBrolly); if (NakedTrapManager.SuppressesCloakAbilities) { return false; } return LogicAuditCloakManager.ResolveBrollyOwnership(owned); } public static bool ResolveDoubleJumpOwnership(bool vanillaOwned) { SaveState instance = SaveState.Instance; bool owned = ((instance == null || !instance.IsRandomized(ItemType.Skill)) ? vanillaOwned : instance.canDoubleJump); if (NakedTrapManager.SuppressesCloakAbilities) { return false; } return LogicAuditCloakManager.ResolveDoubleJumpOwnership(owned); } public static bool HasBrolly(PlayerData playerData) { return ResolveBrollyOwnership(playerData?.hasBrolly ?? false); } public static bool HasDoubleJump(PlayerData playerData) { return ResolveDoubleJumpOwnership(playerData?.hasDoubleJump ?? false); } } [HarmonyPatch(typeof(HeroController), "HasNeedolin")] internal static class HeroController_HasNeedolin_Patch { private static bool Prefix(HeroController __instance, ref bool __result) { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Skill)) { return true; } __result = __instance.Config.CanPlayNeedolin && instance.canUseNeedolin; return false; } } [HarmonyPatch(typeof(HeroController), "RegainControl", new Type[] { typeof(bool) })] internal static class HeroController_RegainControl_BrollyPatch { private static IEnumerable Transpiler(IEnumerable instructions) { List list = new List(instructions); FieldInfo objB = AccessTools.Field(typeof(PlayerData), "hasBrolly"); FieldInfo objB2 = AccessTools.Field(typeof(PlayerData), "hasDoubleJump"); MethodInfo operand = AccessTools.Method(typeof(HeroControllerAbilityPatchUtil), "HasBrolly", (Type[])null, (Type[])null); MethodInfo operand2 = AccessTools.Method(typeof(HeroControllerAbilityPatchUtil), "HasDoubleJump", (Type[])null, (Type[])null); int num = 0; int num2 = 0; foreach (CodeInstruction item in list) { if (item.opcode == OpCodes.Ldfld && object.Equals(item.operand, objB)) { item.opcode = OpCodes.Call; item.operand = operand; num++; } else if (item.opcode == OpCodes.Ldfld && object.Equals(item.operand, objB2)) { item.opcode = OpCodes.Call; item.operand = operand2; num2++; } } if (num != 2) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Expected two Drifter's Cloak ownership reads in HeroController.RegainControl(bool), replaced " + num + ".")); } } if (num2 != 1) { ManualLogSource log2 = RandomizerPlugin.Log; if (log2 != null) { log2.LogWarning((object)("[RANDOMIZER] Expected one Faydown Cloak ownership read in HeroController.RegainControl(bool), replaced " + num2 + ".")); } } return list; } } [HarmonyPatch(typeof(HeroController), "TickFrostEffect", new Type[] { typeof(bool) })] internal static class HeroController_TickFrostEffect_FaydownPatch { private struct OwnershipSnapshot { internal PlayerData PlayerData; internal bool NativeDoubleJump; internal bool Changed; } [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(PlayerData ___playerData, out OwnershipSnapshot __state) { __state = default(OwnershipSnapshot); SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Skill) && ___playerData != null) { bool hasDoubleJump = ___playerData.hasDoubleJump; bool flag = HeroControllerAbilityPatchUtil.ResolveDoubleJumpOwnership(hasDoubleJump); if (hasDoubleJump != flag) { __state = new OwnershipSnapshot { PlayerData = ___playerData, NativeDoubleJump = hasDoubleJump, Changed = true }; ___playerData.hasDoubleJump = flag; } } } [HarmonyFinalizer] [HarmonyPriority(0)] private static Exception Finalizer(Exception __exception, OwnershipSnapshot __state) { if (__state.Changed && __state.PlayerData != null) { __state.PlayerData.hasDoubleJump = __state.NativeDoubleJump; } return __exception; } } [HarmonyPatch] public static class HeroControllerPatches { [HarmonyPatch(typeof(HeroController), "HasHarpoonDash")] internal static class HeroController_HasHarpoonDash_Patch { [HarmonyPrefix] private static bool Prefix(HeroController __instance, ref bool __result) { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Skill)) { return true; } __result = instance.canUseHarpoon && HeroControllerAbilityPatchUtil.ConfigField(__instance, "CanHarpoonDash"); return false; } } [HarmonyPatch(typeof(HeroController), "CanDash")] internal static class HeroController_CanDash_Patch { [HarmonyPrefix] private static bool Prefix(HeroController __instance, ref bool __result) { //IL_001a: 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) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Invalid comparison between Unknown and I4 //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Invalid comparison between Unknown and I4 //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Invalid comparison between Unknown and I4 SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Skill)) { return true; } ActorStates val = HeroControllerAbilityPatchUtil.Field(__instance, "hero_state"); float num = HeroControllerAbilityPatchUtil.Field(__instance, "dashCooldownTimer"); float num2 = HeroControllerAbilityPatchUtil.Field(__instance, "attack_time"); bool flag = HeroControllerAbilityPatchUtil.Field(__instance, "airDashed"); bool flag2 = WidowSequenceSafety.CanUseEmergencySwiftStep(instance); __result = (int)val != 7 && (int)val != 5 && (int)val != 6 && num <= 0f && !HeroControllerAbilityPatchUtil.CState(__instance, "dashing") && !HeroControllerAbilityPatchUtil.CState(__instance, "backDashing") && (!HeroControllerAbilityPatchUtil.CState(__instance, "attacking") || num2 >= HeroControllerAbilityPatchUtil.ConfigField(__instance, "AttackRecoveryTime")) && !HeroControllerAbilityPatchUtil.CState(__instance, "preventDash") && (HeroControllerAbilityPatchUtil.CState(__instance, "onGround") || !flag || HeroControllerAbilityPatchUtil.CState(__instance, "wallSliding")) && !HeroControllerAbilityPatchUtil.CState(__instance, "hazardDeath") && (instance.canDash || flag2); return false; } } [HarmonyPatch(typeof(HeroController), "CanSprint")] internal static class HeroController_CanSprint_Patch { [HarmonyPostfix] private static void Postfix(ref bool __result) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Skill)) { __result = __result && (instance.canSprint || WidowSequenceSafety.CanUseEmergencySwiftStep(instance)); } } } [HarmonyPatch(typeof(HeroController), "LookForQueueInput")] internal static class HeroController_LookForQueueInput_SprintPatch { private const string SprintStartSpeedVariable = "Sprint Start Speed"; private const string SprintRegularSpeedVariable = "Sprint Speed Regular"; private static PlayMakerFSM trackedSprintFsm; private static float nativeSprintStartSpeed; private static bool hasNativeSprintStartSpeed; private static bool loggedMissingSprintSpeed; [HarmonyPostfix] private static void Postfix(HeroController __instance) { SaveState instance = SaveState.Instance; bool flag = SynchronizeSprintStartSpeed(__instance, instance); if (instance == null || !instance.IsRandomized(ItemType.Skill) || !instance.canSprint || instance.canDash || !flag || (Object)(object)__instance == (Object)null || !HeroControllerAbilityPatchUtil.CState(__instance, "onGround")) { return; } try { InputHandler val = HeroControllerAbilityPatchUtil.Field(__instance, "inputHandler"); if (!((Object)(object)val == (Object)null) && ((OneAxisInputControl)val.inputActions.Dash).WasPressed && HeroControllerAbilityPatchUtil.Field(__instance, "acceptingInput") && HeroControllerAbilityPatchUtil.Field(__instance, "isGameplayScene") && !HeroControllerAbilityPatchUtil.Call(__instance, "IsInputBlocked", Array.Empty()) && !((Object)(object)InteractManager.BlockingInteractable != (Object)null) && !HeroControllerAbilityPatchUtil.Call(__instance, "IsPaused", Array.Empty()) && __instance.CanSprint() && !((Object)(object)__instance.sprintFSM == (Object)null)) { __instance.sprintFSM.SendEvent("TRY SPRINT"); } } catch (Exception ex) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Sprint input could not be applied: " + ex.Message)); } } } private static bool SynchronizeSprintStartSpeed(HeroController hero, SaveState state) { PlayMakerFSM val = (((Object)(object)hero == (Object)null) ? null : hero.sprintFSM); if (trackedSprintFsm != val) { RestoreTrackedSprintStartSpeed(); trackedSprintFsm = val; hasNativeSprintStartSpeed = false; loggedMissingSprintSpeed = false; } if ((Object)(object)val == (Object)null) { return false; } FsmFloat val2 = val.FsmVariables.FindFsmFloat("Sprint Start Speed"); FsmFloat val3 = val.FsmVariables.FindFsmFloat("Sprint Speed Regular"); if (val2 == null || val3 == null) { if (!loggedMissingSprintSpeed) { loggedMissingSprintSpeed = true; ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)"[RANDOMIZER] Sprint-only Swift Step could not resolve the shipped sprint speed variables; the input was withheld to avoid leaking Dash."); } } return false; } if (!hasNativeSprintStartSpeed) { nativeSprintStartSpeed = val2.Value; hasNativeSprintStartSpeed = true; } bool flag = state != null && state.IsRandomized(ItemType.Skill) && state.canSprint && !state.canDash; val2.Value = (flag ? val3.Value : nativeSprintStartSpeed); return flag; } private static void RestoreTrackedSprintStartSpeed() { if (!((Object)(object)trackedSprintFsm == (Object)null) && hasNativeSprintStartSpeed) { FsmFloat val = trackedSprintFsm.FsmVariables.FindFsmFloat("Sprint Start Speed"); if (val != null) { val.Value = nativeSprintStartSpeed; } } } } [HarmonyPatch(typeof(HeroController), "GetCanAirDashCancel")] internal static class HeroController_GetCanAirDashCancel_Patch { [HarmonyPostfix] private static void Postfix(HeroController __instance, ref bool __result) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Skill)) { __result = (instance.canDash || WidowSequenceSafety.CanUseEmergencySwiftStep(instance)) && !__instance.GetAirdashed(); } } } [HarmonyPatch(typeof(HeroWaterController), "CanDash")] internal static class HeroWaterController_CanDash_Patch { [HarmonyPrefix] private static bool Prefix(HeroWaterController __instance, ref bool __result) { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Skill)) { return true; } bool flag = HeroControllerAbilityPatchUtil.Field(__instance, "isEnterTumbling"); double num = HeroControllerAbilityPatchUtil.Field(__instance, "nextDashTime"); __result = !flag && instance.canDash && Time.timeAsDouble >= num; return false; } } [HarmonyPatch(typeof(HeroController), "CanDoubleJump", new Type[] { typeof(bool) })] internal static class HeroController_CanDoubleJump_Patch { [HarmonyPrefix] private static bool Prefix(HeroController __instance, ref bool __result, bool checkControlState = true) { //IL_001a: 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) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Invalid comparison between Unknown and I4 //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Invalid comparison between Unknown and I4 //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Invalid comparison between Unknown and I4 SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Skill)) { return true; } ActorStates val = HeroControllerAbilityPatchUtil.Field(__instance, "hero_state"); bool flag = HeroControllerAbilityPatchUtil.Field(__instance, "controlReqlinquished"); bool flag2 = HeroControllerAbilityPatchUtil.Field(__instance, "doubleJumped"); bool flag3 = !checkControlState || ((int)val != 7 && (int)val != 5 && (int)val != 6 && !flag); __result = flag3 && HeroControllerAbilityPatchUtil.ResolveDoubleJumpOwnership(instance.canDoubleJump) && !flag2 && !HeroControllerAbilityPatchUtil.Call(__instance, "IsDashLocked", Array.Empty()) && !HeroControllerAbilityPatchUtil.CState(__instance, "wallSliding") && !HeroControllerAbilityPatchUtil.CState(__instance, "backDashing") && !HeroControllerAbilityPatchUtil.Call(__instance, "IsAttackLocked", Array.Empty()) && !HeroControllerAbilityPatchUtil.CState(__instance, "bouncing") && !HeroControllerAbilityPatchUtil.CState(__instance, "shroomBouncing") && !HeroControllerAbilityPatchUtil.CState(__instance, "onGround") && !HeroControllerAbilityPatchUtil.CState(__instance, "doubleJumping") && HeroControllerAbilityPatchUtil.ConfigField(__instance, "CanDoubleJump") && !HeroControllerAbilityPatchUtil.Call(__instance, "TryQueueWallJumpInterrupt", Array.Empty()) && !HeroControllerAbilityPatchUtil.Call(__instance, "IsApproachingSolidGround", Array.Empty()); return false; } } [HarmonyPatch(typeof(HeroController), "CanFloat", new Type[] { typeof(bool) })] internal static class HeroController_CanFloat_Patch { [HarmonyPrefix] private static bool Prefix(HeroController __instance, ref bool __result, bool checkControlState = true) { //IL_001a: 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) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Invalid comparison between Unknown and I4 //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Invalid comparison between Unknown and I4 //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Invalid comparison between Unknown and I4 SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Skill)) { return true; } ActorStates val = HeroControllerAbilityPatchUtil.Field(__instance, "hero_state"); bool flag = HeroControllerAbilityPatchUtil.Field(__instance, "controlReqlinquished"); int num = HeroControllerAbilityPatchUtil.Field(__instance, "ledgeBufferSteps"); bool flag2 = !checkControlState || ((int)val != 7 && (int)val != 5 && (int)val != 6 && !flag); __result = flag2 && !HeroControllerAbilityPatchUtil.Call(__instance, "CanInfiniteAirJump", Array.Empty()) && HeroControllerAbilityPatchUtil.ResolveBrollyOwnership(instance.canBrolly) && !HeroControllerAbilityPatchUtil.CState(__instance, "onGround") && !HeroControllerAbilityPatchUtil.Call(__instance, "IsDashLocked", Array.Empty()) && !HeroControllerAbilityPatchUtil.CState(__instance, "swimming") && num <= 0 && !HeroControllerAbilityPatchUtil.CState(__instance, "wallSliding") && !HeroControllerAbilityPatchUtil.Call(__instance, "IsAttackLocked", Array.Empty()) && !HeroControllerAbilityPatchUtil.CState(__instance, "hazardDeath") && !HeroControllerAbilityPatchUtil.CState(__instance, "hazardRespawning") && (!HeroControllerAbilityPatchUtil.CState(__instance, "doubleJumping") || HeroControllerAbilityPatchUtil.CState(__instance, "inUpdraft")) && HeroControllerAbilityPatchUtil.Call(__instance, "CanDoFSMCancelMove", Array.Empty()) && HeroControllerAbilityPatchUtil.ConfigField(__instance, "CanBrolly") && !HeroControllerAbilityPatchUtil.Call(__instance, "TryQueueWallJumpInterrupt", Array.Empty()) && !HeroControllerAbilityPatchUtil.Call(__instance, "IsApproachingSolidGround", Array.Empty()); return false; } } [HarmonyPatch(typeof(HeroController), "CanNailCharge")] internal static class HeroController_CanNailCharge_Patch { [HarmonyPrefix] private static bool Prefix(HeroController __instance, ref bool __result) { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Skill)) { return true; } bool flag = HeroControllerAbilityPatchUtil.Field(__instance, "controlReqlinquished"); bool flag2 = HeroControllerAbilityPatchUtil.Field(__instance, "allowNailChargingWhileRelinquished"); __result = !HeroControllerAbilityPatchUtil.CState(__instance, "dead") && !HeroControllerAbilityPatchUtil.CState(__instance, "attacking") && (!flag || flag2) && !HeroControllerAbilityPatchUtil.CState(__instance, "recoiling") && !HeroControllerAbilityPatchUtil.CState(__instance, "recoilingLeft") && !HeroControllerAbilityPatchUtil.CState(__instance, "recoilingRight") && !HeroControllerAbilityPatchUtil.CState(__instance, "hazardDeath") && !HeroControllerAbilityPatchUtil.CState(__instance, "hazardRespawning") && instance.canChargeSlash && HeroControllerAbilityPatchUtil.ConfigField(__instance, "CanNailCharge") && !HeroControllerAbilityPatchUtil.Call(__instance, "IsInputBlocked", Array.Empty()) && (Object)(object)InteractManager.BlockingInteractable == (Object)null; return false; } } [HarmonyPatch(typeof(HeroController), "CanSuperJump")] internal static class HeroController_CanSuperJump_Patch { [HarmonyPrefix] private static bool Prefix(HeroController __instance, ref bool __result) { //IL_003a: 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_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Invalid comparison between Unknown and I4 //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Invalid comparison between Unknown and I4 SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Skill)) { return true; } bool value = Traverse.Create(HeroControllerAbilityPatchUtil.Field(__instance, "gm")).Field("isPaused").GetValue(); ActorStates val = HeroControllerAbilityPatchUtil.Field(__instance, "hero_state"); float num = HeroControllerAbilityPatchUtil.Field(__instance, "attack_time"); __result = !value && (int)val != 5 && (int)val != 6 && HeroControllerAbilityPatchUtil.CState(__instance, "onGround") && !HeroControllerAbilityPatchUtil.CState(__instance, "dashing") && !HeroControllerAbilityPatchUtil.CState(__instance, "hazardDeath") && !HeroControllerAbilityPatchUtil.CState(__instance, "hazardRespawning") && !HeroControllerAbilityPatchUtil.CState(__instance, "backDashing") && (!HeroControllerAbilityPatchUtil.CState(__instance, "attacking") || num >= HeroControllerAbilityPatchUtil.ConfigField(__instance, "AttackRecoveryTime")) && HeroControllerAbilityPatchUtil.Call(__instance, "CanDoFSMCancelMove", Array.Empty()) && !HeroControllerAbilityPatchUtil.CState(__instance, "recoilFrozen") && !HeroControllerAbilityPatchUtil.CState(__instance, "recoiling") && !HeroControllerAbilityPatchUtil.CState(__instance, "transitioning") && instance.canSilkSoar; return false; } } } internal static class LaceAlternativeCompletionPatches { [HarmonyPatch(typeof(SetPlayerDataBool), "OnEnter")] private static class BlastedBridgeEncounterPatch { [HarmonyPostfix] private static void Postfix(SetPlayerDataBool __instance) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) SaveState instance = SaveState.Instance; if (__instance == null || instance == null || !instance.IsRandomized(ItemType.Boss) || !instance.IsLocationEnabled("Boss: Lace (Deep Docks)") || !instance.IsLocationInSeed("Boss: Lace (Deep Docks)") || instance.IsLocationChecked("Boss: Lace (Deep Docks)") || (Object)(object)((FsmStateAction)__instance).Owner == (Object)null) { return; } Scene scene = ((FsmStateAction)__instance).Owner.scene; if (IsEncounterScene(((Scene)(ref scene)).name) && string.Equals(((Object)((FsmStateAction)__instance).Owner).name, "Lace NPC Blasted Bridge", StringComparison.Ordinal) && ((FsmStateAction)__instance).Fsm != null && string.Equals(((FsmStateAction)__instance).Fsm.Name, "Control", StringComparison.Ordinal) && ((FsmStateAction)__instance).State != null && string.Equals(((FsmStateAction)__instance).State.Name, "End", StringComparison.Ordinal) && __instance.boolName != null && string.Equals(__instance.boolName.Value, "encounteredLaceBlastedBridge", StringComparison.Ordinal) && __instance.value != null && __instance.value.Value && PlayerData.instance != null && PlayerData.instance.encounteredLaceBlastedBridge) { instance.CheckLocation("Boss: Lace (Deep Docks)"); ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogInfo((object)"[RANDOMIZER] The Blasted Bridge Lace encounter reported the shared Deep Docks Lace location."); } } } } internal const string LocationName = "Boss: Lace (Deep Docks)"; private const string EncounterFlag = "encounteredLaceBlastedBridge"; private const string EncounterObjectName = "Lace NPC Blasted Bridge"; private const string EncounterFsmName = "Control"; private const string EncounterEndStateName = "End"; private static bool IsEncounterScene(string sceneName) { if (!string.Equals(sceneName, "Dust_01", StringComparison.Ordinal)) { return string.Equals(sceneName, "Coral_19", StringComparison.Ordinal); } return true; } } internal static class MarrowLocketGauntletPatches { private sealed class WallJumpSnapshot { internal PlayerData PlayerData; internal bool NativeWallJump; } [HarmonyPatch(typeof(DeactivateIfPlayerdataFalse), "ForceEvaluate")] private static class NormalWorldActivationPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(DeactivateIfPlayerdataFalse __instance, out WallJumpSnapshot __state) { __state = null; SaveState instance = SaveState.Instance; GameManager instance2 = GameManager.instance; PlayerData val = (((Object)(object)instance2 == (Object)null) ? null : instance2.playerData); if (instance != null && val != null && instance.IsRandomized(ItemType.Skill) && IsExactNormalWorldGate(__instance)) { __state = new WallJumpSnapshot { PlayerData = val, NativeWallJump = val.hasWalljump }; val.hasWalljump = instance.canWallJump; } } [HarmonyFinalizer] [HarmonyPriority(0)] private static Exception Finalizer(Exception __exception, WallJumpSnapshot __state) { if (__state != null && __state.PlayerData != null) { __state.PlayerData.hasWalljump = __state.NativeWallJump; } return __exception; } } private const string SourceScene = "Bone_18"; private const string QuestSceneObject = "Quest Scene"; private const string FinalEncounterObject = "Final Encounter"; private const string BattleSceneObject = "Battle Scene"; private const string WallJumpBool = "hasWalljump"; private static bool IsExactNormalWorldGate(DeactivateIfPlayerdataFalse source) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)source == (Object)null) && !((Object)(object)((Component)source).gameObject == (Object)null)) { Scene scene = ((Component)source).gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, "Bone_18", StringComparison.Ordinal) && string.Equals(source.boolName, "hasWalljump", StringComparison.Ordinal) && string.Equals(((Object)((Component)source).gameObject).name, "Battle Scene", StringComparison.Ordinal)) { Transform parent = ((Component)source).transform.parent; Transform val = (((Object)(object)parent == (Object)null) ? null : parent.parent); if ((Object)(object)parent != (Object)null && (Object)(object)val != (Object)null && (Object)(object)val.parent == (Object)null && string.Equals(((Object)parent).name, "Final Encounter", StringComparison.Ordinal)) { return string.Equals(((Object)val).name, "Quest Scene", StringComparison.Ordinal); } return false; } } return false; } } internal static class MaskShardsPatches { private struct InventoryState { internal bool Active; internal int HeartPieces; internal int MaxHealthBase; } [HarmonyPatch(typeof(PlayMakerFSM), "Start")] internal static class LoosePhysicalMaskShardPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(PlayMakerFSM __instance) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) SaveState instance = SaveState.Instance; if ((Object)(object)__instance == (Object)null || instance == null || !instance.IsRandomized(ItemType.MaskShard)) { return; } Scene scene = ((Component)__instance).gameObject.scene; if (!TryGetLoosePhysicalLocation(((Scene)(ref scene)).name, ((Object)__instance).name, __instance.FsmName, out var locationName) || !instance.IsLocationEnabled(locationName) || !instance.IsLocationInSeed(locationName)) { return; } if ((Object)(object)((Component)__instance).GetComponent() == (Object)null) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Physical Mask Shard patch found " + locationName + " without its PersistentBoolItem.")); } return; } FsmState val = FindState(__instance, "Shift Up?"); if (val == null) { ManualLogSource log2 = RandomizerPlugin.Log; if (log2 != null) { log2.LogWarning((object)("[RANDOMIZER] Physical Mask Shard patch found " + locationName + " but not its shipped Shift Up? pickup state.")); } return; } FsmStateAction[] actions = val.Actions; if (actions == null || actions.Length != 1 || !(actions[0] is CompleteLooseMaskShardLocation)) { CompleteLooseMaskShardLocation completeLooseMaskShardLocation = new CompleteLooseMaskShardLocation(locationName); ((FsmStateAction)completeLooseMaskShardLocation).Init(val); val.Actions = (FsmStateAction[])(object)new FsmStateAction[1] { completeLooseMaskShardLocation }; } } } private sealed class CompleteLooseMaskShardLocation : FsmStateAction { private readonly string locationName; internal CompleteLooseMaskShardLocation(string locationName) { this.locationName = locationName; } public override void OnEnter() { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.MaskShard) || !instance.IsLocationEnabled(locationName) || !instance.IsLocationInSeed(locationName) || (Object)(object)((FsmStateAction)this).Owner == (Object)null) { return; } PersistentBoolItem component = ((FsmStateAction)this).Owner.GetComponent(); if ((Object)(object)component == (Object)null) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Physical Mask Shard lost its PersistentBoolItem before collection: " + locationName + ".")); } return; } FsmBool val = ((((FsmStateAction)this).Fsm == null) ? null : ((FsmStateAction)this).Fsm.Variables.FindFsmBool("Activated")); if (val != null) { val.Value = true; } component.SetValueOverride(true); ((PersistentItem)(object)component).SaveStateNoCondition(); instance.CheckLocation(locationName); ((FsmStateAction)this).Owner.SetActive(false); } } [HarmonyPatch(typeof(PlayerData), "get_CurrentMaxHealth", new Type[] { })] internal static class PlayerData_CurrentMaxHealth_Patch { private static void Postfix(PlayerData __instance, ref int __result) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.MaskShard)) { int receivedMaxHealth = GetReceivedMaxHealth(); __result = ((__result < __instance.maxHealth) ? Math.Min(__result, receivedMaxHealth) : receivedMaxHealth); } } } [HarmonyPatch(typeof(PlayerData), "TakeHealth", new Type[] { typeof(int), typeof(bool), typeof(bool) })] internal static class PlayerData_TakeHealth_Patch { private static void Prefix() { SynchronizeReceivedMaskShards(refillNewMask: false); } } [HarmonyPatch(typeof(PrefabCollectable), "Get", new Type[] { typeof(bool) })] internal static class PhysicalMaskShardPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(PrefabCollectable __instance) { if (!IsRandomizedHeartPiece(__instance)) { return true; } GameManager instance = GameManager.instance; string sceneName = (((Object)(object)instance == (Object)null) ? null : instance.GetSceneNameString()); if (MaskAndSpoolLocationManifest.TryGetPhysicalLocation(ItemType.MaskShard, sceneName, out var locationName)) { SaveState.Instance.CheckLocation(locationName); } return false; } } [HarmonyPatch(typeof(PrefabCollectable), "TryGetPrespawnedItem")] internal static class PhysicalMaskShardPreSpawnPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(PrefabCollectable __instance, ref PreSpawnedItem item, ref bool __result) { if (!IsRandomizedHeartPiece(__instance)) { return true; } item = null; __result = false; return false; } } [HarmonyPatch(typeof(PrefabCollectable), "GetTakesHeroControl")] internal static class PhysicalMaskShardHeroControlPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(PrefabCollectable __instance, ref bool __result) { if (IsRandomizedHeartPiece(__instance)) { __result = false; } } } [HarmonyPatch(typeof(InventoryItemHeartPieces), "UpdateState", new Type[] { })] internal static class InventoryMaskShardPatch { [HarmonyPrefix] private static void Prefix(out InventoryState __state) { __state = default(InventoryState); SaveState instance = SaveState.Instance; PlayerData instance2 = PlayerData.instance; if (instance != null && instance.IsRandomized(ItemType.MaskShard) && instance2 != null) { __state.Active = true; __state.HeartPieces = instance2.heartPieces; __state.MaxHealthBase = instance2.maxHealthBase; instance2.heartPieces = GetReceivedHeartPieces(); instance2.maxHealthBase = GetReceivedMaxHealth(); } } [HarmonyFinalizer] private static Exception Finalizer(Exception __exception, InventoryState __state) { PlayerData instance = PlayerData.instance; if (__state.Active && instance != null) { instance.heartPieces = __state.HeartPieces; instance.maxHealthBase = __state.MaxHealthBase; } return __exception; } } private const int BaseHealth = 5; private const int ShardsPerMask = 4; private const int TotalMaskShards = 20; private const string HeartPieceAssetName = "Heart Piece"; private const string HeartPieceControlFsmName = "Heart Container Control"; private const string LooseMaskPickupStateName = "Shift Up?"; internal static int CountMaskShards() { SaveState instance = SaveState.Instance; if (instance == null) { return 0; } int num = 0; for (int i = 1; i <= 20; i++) { if (instance.receivedItems.Contains("Mask Shard #" + i)) { num++; } } return num; } internal static int GetReceivedMaxHealth() { return 5 + CountMaskShards() / 4; } internal static int GetReceivedHeartPieces() { return CountMaskShards() % 4; } internal static bool SynchronizeReceivedMaskShards(bool refillNewMask) { SaveState instance = SaveState.Instance; PlayerData instance2 = PlayerData.instance; if (instance == null || !instance.IsRandomized(ItemType.MaskShard) || instance2 == null) { return false; } int num = CountMaskShards(); int num2 = num % 4; int num3 = 5 + num / 4; bool num4 = instance2.heartPieces != num2 || instance2.maxHealthBase != num3 || instance2.maxHealth != num3; bool flag = refillNewMask && num > 0 && num % 4 == 0; instance2.heartPieces = num2; instance2.maxHealthBase = num3; instance2.maxHealth = num3; if (flag) { instance2.prevHealth = instance2.health; instance2.health = num3; } else if (instance2.health > num3) { instance2.health = num3; } MemorySequenceSync.SynchronizeMaskHealth(instance2, num3, flag); if (flag) { EventRegister.SendEvent("MAX HP UP", (GameObject)null); } if (num4) { EventRegister.SendEvent(EventRegisterEvents.HealthUpdate, (GameObject)null); } return true; } private static bool IsRandomizedHeartPiece(PrefabCollectable item) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.MaskShard) && (Object)(object)item != (Object)null) { return ((Object)item).name.StartsWith("Heart Piece", StringComparison.Ordinal); } return false; } private static bool TryGetLoosePhysicalLocation(string sceneName, string gameObjectName, string fsmName, out string locationName) { locationName = null; if (!string.Equals(fsmName, "Heart Container Control", StringComparison.Ordinal)) { return false; } return MaskAndSpoolLocationManifest.TryGetPhysicalLocation(ItemType.MaskShard, sceneName, gameObjectName, out locationName); } private static FsmState FindState(PlayMakerFSM fsm, string stateName) { FsmState[] array = (((Object)(object)fsm == (Object)null) ? null : fsm.FsmStates); if (array == null) { return null; } FsmState[] array2 = array; foreach (FsmState val in array2) { if (val != null && string.Equals(val.Name, stateName, StringComparison.Ordinal)) { return val; } } return null; } } internal static class MelodyPatches { [HarmonyPatch(typeof(PlayMakerFSM), "Start")] private static class MelodySourceFsmPatch { [HarmonyPrefix] [HarmonyPriority(0)] private static void Prefix(PlayMakerFSM __instance) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).gameObject == (Object)null) { return; } Scene scene = ((Component)__instance).gameObject.scene; if (MelodyLocationManifest.TryGet(((Scene)(ref scene)).name, ((Object)((Component)__instance).gameObject).name, __instance.FsmName, out var entry) && IsActive(entry.LocationName) && (!string.Equals(entry.LocationName, "Vaultkeeper's Melody", StringComparison.Ordinal) || FindState(__instance, "Open Relic Board") != null) && entry.LocationName switch { "Architect's Melody" => PatchArchitect(__instance) ? 1 : 0, "Conductor's Melody" => (PatchConductorQuestGate(__instance) && PatchNestedMelodyReward(__instance, "Run Melody Play Prompted", 5, entry.LocationName)) ? 1 : 0, "Vaultkeeper's Melody" => PatchNestedMelodyReward(__instance, "Needolin", 4, entry.LocationName) ? 1 : 0, "Elegy of the Deep" => PatchElegy(__instance) ? 1 : 0, "Beastling Call" => PatchBeastlingCall(__instance) ? 1 : 0, _ => 0, } == 0) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Learned-song source patch failed closed for " + entry.LocationName + " at '" + entry.SceneName + "/" + entry.ObjectName + "/" + entry.FsmName + "'. The FSM has an unexpected layout.")); } } } } [HarmonyPatch(typeof(SavedItemCanGetMore), "get_IsTrue")] private static class MelodyCanGetMorePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(SavedItemCanGetMore __instance, ref bool __result) { object obj; if (__instance == null) { obj = null; } else { FsmObject item = __instance.Item; obj = ((item != null) ? item.Value : null); } SavedItem val = (SavedItem)((obj is SavedItem) ? obj : null); if ((Object)(object)val == (Object)null || !TryGetCanGetMoreLocation((FsmStateAction)(object)__instance, ((Object)val).name, out var locationName) || !IsActive(locationName)) { return true; } __result = !SaveState.Instance.IsLocationChecked(locationName); return false; } } [HarmonyPatch(typeof(PlayerData), "get_BellCentipedeWaiting")] private static class BellCentipedeWaitingPatch { [HarmonyPostfix] private static void Postfix(PlayerData __instance, ref bool __result) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Melody) && instance.IsLocationInSeed("Beastling Call")) { __result = __instance.blackThreadWorld && !IsBellEaterResolved(instance); } } } [HarmonyPatch(typeof(PlayerData), "get_BellCentipedeLocked")] private static class BellCentipedeLockedPatch { [HarmonyPostfix] private static void Postfix(PlayerData __instance, ref bool __result) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Melody) && instance.IsLocationInSeed("Beastling Call")) { __result = __instance.bellCentipedeAppeared && !IsBellEaterResolved(instance); } } } private sealed class ArchitectSourceGate : FsmStateAction { private readonly string locationName; internal ArchitectSourceGate(string locationName) { this.locationName = locationName; } public override void OnEnter() { SaveState instance = SaveState.Instance; if (instance != null && instance.IsLocationChecked(locationName)) { ((FsmStateAction)this).Fsm.Event("CANCEL"); ((FsmStateAction)this).Finish(); } } } private sealed class CompleteLocationAction : FsmStateAction { private readonly string locationName; private readonly string completionEvent; private readonly bool resolveBellEater; private readonly bool restoreHeroControl; internal CompleteLocationAction(string locationName, string completionEvent = null, bool resolveBellEater = false, bool restoreHeroControl = false) { this.locationName = locationName; this.completionEvent = completionEvent; this.resolveBellEater = resolveBellEater; this.restoreHeroControl = restoreHeroControl; } public override void OnEnter() { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Melody) && instance.IsLocationEnabled(locationName) && instance.IsLocationInSeed(locationName)) { instance.CheckLocation(locationName); if (resolveBellEater) { instance.bellEaterResolved = true; } } if (restoreHeroControl) { HeroController instance2 = HeroController.instance; if ((Object)(object)instance2 != (Object)null) { instance2.RelinquishControl(); } if (PlayerData.instance != null) { PlayerData.instance.disableInventory = false; } if ((Object)(object)instance2 != (Object)null) { instance2.RegainControl(); } } ((FsmStateAction)this).Finish(); if (!string.IsNullOrEmpty(completionEvent)) { ((FsmStateAction)this).Fsm.Event(completionEvent); } } } private sealed class PreserveElegyOwnershipAction : FsmStateAction { public override void OnEnter() { SaveState instance = SaveState.Instance; if (PlayerData.instance != null && instance != null) { PlayerData.instance.hasNeedolinMemoryPowerup = instance.receivedItems.Contains("Elegy of the Deep"); } ((FsmStateAction)this).Finish(); } } private sealed class PreserveBeastlingOwnershipAction : FsmStateAction { public override void OnEnter() { SaveState instance = SaveState.Instance; if (instance != null) { instance.bellEaterResolved = true; } if (PlayerData.instance != null && instance != null) { PlayerData.instance.UnlockedFastTravelTeleport = instance.canUseBeastlingCall; } ((FsmStateAction)this).Finish(); } } private const string MelodyVaultAsset = "melody_Vault"; private const string MelodyConductorAsset = "melody_Conductor"; private static bool IsActive(string locationName) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Melody) && instance.IsLocationEnabled(locationName)) { return instance.IsLocationInSeed(locationName); } return false; } private static bool PatchArchitect(PlayMakerFSM fsm) { FsmState val = FindState(fsm, "Wait For Notify"); FsmState val2 = FindState(fsm, "Show Prompt"); if (val == null || val2 == null) { return false; } if (val2.Actions != null && val2.Actions.Length == 1 && val2.Actions[0] is CompleteLocationAction) { return true; } PlayerDataVariableTest val3 = ((val.Actions == null) ? null : val.Actions.OfType().SingleOrDefault()); if (val3 == null || val3.VariableName == null || !string.Equals(val3.VariableName.Value, "HasMelodyArchitect", StringComparison.Ordinal) || !HasTransition(val, "CANCEL", "Has Melody") || !HasTransition(val2, "GET ITEM MSG COVERED", "Singing End")) { return false; } ArchitectSourceGate architectSourceGate = new ArchitectSourceGate("Architect's Melody"); ((FsmStateAction)architectSourceGate).Init(val); val.Actions = (FsmStateAction[])(object)new FsmStateAction[1] { architectSourceGate }; CompleteLocationAction completeLocationAction = new CompleteLocationAction("Architect's Melody", "GET ITEM MSG COVERED", resolveBellEater: false, restoreHeroControl: true); ((FsmStateAction)completeLocationAction).Init(val2); val2.Actions = (FsmStateAction[])(object)new FsmStateAction[1] { completeLocationAction }; return true; } private static bool PatchConductorQuestGate(PlayMakerFSM fsm) { //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Expected O, but got Unknown FsmState val = FindState(fsm, "Has Item?"); FsmState val2 = FindState(fsm, "Quest Active?"); object obj; if (fsm == null) { obj = null; } else { Fsm fsm2 = fsm.Fsm; if (fsm2 == null) { obj = null; } else { FsmVariables variables = fsm2.Variables; obj = ((variables != null) ? variables.FindFsmBool("Is Melody Quest Active") : null); } } FsmBool val3 = (FsmBool)obj; if (((val != null) ? val.Actions : null) == null || val.Actions.Length <= 3 || ((val2 != null) ? val2.Actions : null) == null || val2.Actions.Length != 1 || val3 == null || !string.Equals(((object)val.Actions[3])?.GetType().FullName, "QuestPlaymakerActions.GetQuestState", StringComparison.Ordinal) || !string.Equals(((object)val2.Actions[0])?.GetType().FullName, "QuestPlaymakerActions.CheckQuestStateV2", StringComparison.Ordinal)) { return false; } FsmTransition val4 = val2.Transitions?.SingleOrDefault((Func)((FsmTransition transition) => transition != null && string.Equals(transition.EventName, "TRUE", StringComparison.Ordinal) && string.Equals(transition.ToState, "Run Melody Play Prompted", StringComparison.Ordinal))); FieldInfo fieldInfo = AccessTools.Field(((object)val2.Actions[0]).GetType(), "NotTrackedEvent"); if (((val4 != null) ? val4.FsmEvent : null) == null || fieldInfo == null || !typeof(FsmEvent).IsAssignableFrom(fieldInfo.FieldType)) { return false; } SetBoolValue val5 = new SetBoolValue { boolVariable = val3, boolValue = FsmBool.op_Implicit(false) }; ((FsmStateAction)val5).Init(val); val.Actions[3] = (FsmStateAction)(object)val5; fieldInfo.SetValue(val2.Actions[0], val4.FsmEvent); return true; } private static bool PatchNestedMelodyReward(PlayMakerFSM owner, string stateName, int runFsmIndex, string locationName) { FsmState val = FindState(owner, stateName); if (val != null && val.Actions != null && val.Actions.Length > runFsmIndex) { FsmStateAction obj = val.Actions[runFsmIndex]; RunFSM val2 = (RunFSM)(object)((obj is RunFSM) ? obj : null); if (val2 != null) { FsmTemplateControl fsmTemplateControl = val2.fsmTemplateControl; Fsm obj2 = ((fsmTemplateControl != null) ? fsmTemplateControl.RunFsm : null); FsmState val3 = ((obj2 != null) ? obj2.GetState("Give Item") : null); FsmState val4 = ((obj2 != null) ? obj2.GetState("UI Msg") : null); FsmState val5 = ((obj2 != null) ? obj2.GetState("Stop Needolin") : null); if (val3 == null || val4 == null || val5 == null) { return false; } if (val3.Actions != null && val3.Actions.Any((FsmStateAction action) => action is CompleteLocationAction)) { return true; } if (val3.Actions == null || val3.Actions.Length == 0 || val4.Actions == null || val4.Actions.Length < 5 || !HasTransitionTo(val4, "Stop Needolin") || !HasTransitionTo(val5, "Give Item")) { return false; } List list = val3.Actions.Skip(1).ToList(); list.Add((FsmStateAction)(object)CreateWait(val3, 0.5f)); CompleteLocationAction completeLocationAction = new CompleteLocationAction(locationName); ((FsmStateAction)completeLocationAction).Init(val3); list.Add((FsmStateAction)(object)completeLocationAction); list.Add((FsmStateAction)(object)CreateWait(val3, 0.5f)); val3.Actions = list.ToArray(); HashSet removeUiIndices = new HashSet { 0, 1, 3, 4 }; val4.Actions = val4.Actions.Where((FsmStateAction _, int index) => !removeUiIndices.Contains(index)).ToArray(); if (!RetargetTransition(val4, "Stop Needolin", FsmEvent.Finished) || !RetargetTransition(val5, "Give Item", FsmEvent.Finished)) { return false; } return true; } } return false; } private static bool PatchElegy(PlayMakerFSM fsm) { FsmState val = FindState(fsm, "Get Item Msg"); FsmState val2 = FindState(fsm, "Update Quest"); if (val == null || val2 == null || !HasTransition(val, "GET ITEM MSG END", "Stop Needolin") || !ReplaceNamedPlayerDataWrite(val2, "hasNeedolinMemoryPowerup", (FsmStateAction)(object)new PreserveElegyOwnershipAction())) { return false; } if (val.Actions.Any((FsmStateAction action) => action is CompleteLocationAction)) { return true; } List list = val.Actions.Where((FsmStateAction action) => action != null && ((object)action).GetType().Name != "CreateUIMsgGetItem" && ((object)action).GetType().Name != "SetFsmString" && !(action is Wait)).ToList(); CompleteLocationAction completeLocationAction = new CompleteLocationAction("Elegy of the Deep", "GET ITEM MSG END"); ((FsmStateAction)completeLocationAction).Init(val); list.Add((FsmStateAction)(object)completeLocationAction); val.Actions = list.ToArray(); return true; } private static bool PatchBeastlingCall(PlayMakerFSM fsm) { FsmState val = FindState(fsm, "Get Item Msg"); FsmState val2 = FindState(fsm, "Time Passes"); if (val == null || val2 == null || !HasTransition(val, "GET ITEM MSG END", "Time Passes") || !ReplaceNamedPlayerDataWrite(val2, "UnlockedFastTravelTeleport", (FsmStateAction)(object)new PreserveBeastlingOwnershipAction())) { return false; } if (val.Actions.Any((FsmStateAction action) => action is CompleteLocationAction)) { return true; } List list = val.Actions.Where((FsmStateAction action) => action != null && ((object)action).GetType().Name != "CreateUIMsgGetItem" && ((object)action).GetType().Name != "SetFsmString").ToList(); CompleteLocationAction completeLocationAction = new CompleteLocationAction("Beastling Call", "GET ITEM MSG END", resolveBellEater: true); ((FsmStateAction)completeLocationAction).Init(val); list.Add((FsmStateAction)(object)completeLocationAction); val.Actions = list.ToArray(); return true; } private static bool TryGetCanGetMoreLocation(FsmStateAction action, string itemName, out string locationName) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) locationName = null; if (!((Object)(object)((action != null) ? action.Owner : null) == (Object)null)) { Scene scene = action.Owner.scene; if (((Scene)(ref scene)).name != null) { if (string.Equals(itemName, "melody_Conductor", StringComparison.Ordinal)) { scene = action.Owner.scene; if (string.Equals(((Scene)(ref scene)).name, "Hang_12", StringComparison.OrdinalIgnoreCase) && string.Equals(((Object)action.Owner).name, "Last Conductor NPC", StringComparison.Ordinal)) { locationName = "Conductor's Melody"; return true; } } if (string.Equals(itemName, "melody_Vault", StringComparison.Ordinal)) { scene = action.Owner.scene; if (string.Equals(((Scene)(ref scene)).name, "Library_08", StringComparison.OrdinalIgnoreCase) && string.Equals(((Object)action.Owner).name, "Librarian", StringComparison.Ordinal)) { locationName = "Vaultkeeper's Melody"; return true; } } return false; } } return false; } private static bool IsBellEaterResolved(SaveState state) { if (!state.bellEaterResolved) { return state.IsLocationChecked("Beastling Call"); } return true; } private static bool ReplaceNamedPlayerDataWrite(FsmState state, string variableName, FsmStateAction replacement) { if (((state != null) ? state.Actions : null) == null) { return false; } for (int i = 0; i < state.Actions.Length; i++) { if (state.Actions[i] is PreserveElegyOwnershipAction || state.Actions[i] is PreserveBeastlingOwnershipAction) { return true; } FsmStateAction obj = state.Actions[i]; SetPlayerDataVariable val = (SetPlayerDataVariable)(object)((obj is SetPlayerDataVariable) ? obj : null); if (val != null && val.VariableName != null && string.Equals(val.VariableName.Value, variableName, StringComparison.Ordinal)) { replacement.Init(state); state.Actions[i] = replacement; return true; } } return false; } private static Wait CreateWait(FsmState state, float seconds) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown Wait val = new Wait { time = FsmFloat.op_Implicit(seconds), realTime = false }; ((FsmStateAction)val).Init(state); return val; } private static FsmState FindState(PlayMakerFSM fsm, string stateName) { if (fsm == null) { return null; } Fsm fsm2 = fsm.Fsm; if (fsm2 == null) { return null; } return fsm2.GetState(stateName); } private static bool HasTransition(FsmState state, string eventName, string targetState) { if (((state != null) ? state.Transitions : null) != null) { return state.Transitions.Any((FsmTransition transition) => transition != null && string.Equals(transition.EventName, eventName, StringComparison.Ordinal) && string.Equals(transition.ToState, targetState, StringComparison.Ordinal)); } return false; } private static bool HasTransitionTo(FsmState state, string targetState) { if (((state != null) ? state.Transitions : null) != null) { return state.Transitions.Any((FsmTransition transition) => transition != null && string.Equals(transition.ToState, targetState, StringComparison.Ordinal)); } return false; } private static bool RetargetTransition(FsmState state, string targetStateName, FsmEvent newEvent) { FsmTransition val = ((state == null) ? null : state.Transitions?.FirstOrDefault((Func)((FsmTransition candidate) => candidate != null && string.Equals(candidate.ToState, targetStateName, StringComparison.Ordinal)))); object obj; if (state == null) { obj = null; } else { Fsm fsm = state.Fsm; obj = ((fsm != null) ? fsm.GetState(targetStateName) : null); } FsmState val2 = (FsmState)obj; if (val == null || val2 == null || newEvent == null) { return false; } val.FsmEvent = newEvent; val.ToState = val2.Name; val.ToFsmState = val2; return true; } } [HarmonyPatch(typeof(PlayMakerFSM), "Start")] internal static class MemoryBossCompletionPatches { private sealed class Entry { internal readonly string SceneName; internal readonly string ObjectName; internal readonly string FsmName; internal readonly string VictorySourceState; internal readonly string VictoryEvent; internal readonly string SecondaryVictorySourceState; internal readonly string ReturnSceneName; internal readonly string LocationName; internal readonly bool HasAudioSnapshotAction; internal Entry(string sceneName, string objectName, string fsmName, string victorySourceState, string victoryEvent, string secondaryVictorySourceState, string returnSceneName, string locationName, bool hasAudioSnapshotAction = false) { SceneName = sceneName; ObjectName = objectName; FsmName = fsmName; VictorySourceState = victorySourceState; VictoryEvent = victoryEvent; SecondaryVictorySourceState = secondaryVictorySourceState; ReturnSceneName = returnSceneName; LocationName = locationName; HasAudioSnapshotAction = hasAudioSnapshotAction; } } [HarmonyPatch(typeof(SetPlayerDataBool), "OnEnter")] private static class CoralKingVictoryPatch { [HarmonyPostfix] private static void Postfix(SetPlayerDataBool __instance) { if (IsExactCoralKingVictoryWrite(__instance)) { TryReportLocation("Boss: Crust King Khann"); } } } private sealed class ReportMemoryBossCompletion : FsmStateAction { internal string LocationName { get; } internal ReportMemoryBossCompletion(string locationName) { LocationName = locationName; } public override void OnEnter() { try { TryReportLocation(LocationName); } finally { ((FsmStateAction)this).Finish(); } } } private const string ExitStateName = "Exit Memory"; private const string WakeGateName = "door_wakeOnGround"; private const string CoralKingSceneName = "Memory_Coral_Tower"; private const string CoralKingParentName = "Boss Scene"; private const string CoralKingObjectName = "Coral King"; private const string CoralKingFsmName = "Control"; private const string CoralKingVictorySourceState = "Death Stagger"; private const string CoralKingVictoryState = "Heart Death Start"; private const string CoralKingDefeatedFlag = "defeatedCoralKing"; private const string CoralKingLocation = "Boss: Crust King Khann"; private static readonly Entry[] Entries = new Entry[3] { new Entry("Shellwood_11b_Memory", "Boss Scene", "Scene End", "State 1", "BOSS DEFEAT", null, "Shellwood_11b", "Boss: Nyleth", hasAudioSnapshotAction: true), new Entry("Memory_Ant_Queen", "Boss Scene", "End Memory", "Idle", "BOSS DEFEAT", null, "Ant_Queen", "Boss: Skarrsinger Karmelita"), new Entry("Clover_10", "Boss Scene", "Scene End", "State 1", "BOSS DEFEAT", null, "Clover_01", "Boss: Clover Dancers") }; [HarmonyPostfix] private static void Postfix(PlayMakerFSM __instance) { Entry entry = FindEntry(__instance); if (entry == null) { return; } FsmState val = FindState(__instance, "Exit Memory"); if (val != null && ContainsCompletionAction(val, entry.LocationName)) { return; } if (val == null || !HasExactVictoryIncomingEdges(__instance, entry) || !HasExactExitActions(val, entry)) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Memory-boss completion hook was not installed for " + entry.LocationName + " because the validated victory/exit graph changed.")); } return; } ReportMemoryBossCompletion reportMemoryBossCompletion = new ReportMemoryBossCompletion(entry.LocationName); ((FsmStateAction)reportMemoryBossCompletion).Init(val); FsmStateAction[] actions = val.Actions; FsmStateAction[] array = (FsmStateAction[])(object)new FsmStateAction[actions.Length + 1]; array[0] = (FsmStateAction)(object)reportMemoryBossCompletion; Array.Copy(actions, 0, array, 1, actions.Length); val.Actions = array; ManualLogSource log2 = RandomizerPlugin.Log; if (log2 != null) { log2.LogInfo((object)("[RANDOMIZER] Memory-boss completion anchored before memory exit for " + entry.LocationName + ".")); } } private static Entry FindEntry(PlayMakerFSM fsm) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)fsm == (Object)null || (Object)(object)((Component)fsm).gameObject == (Object)null) { return null; } Scene scene = ((Component)fsm).gameObject.scene; string name = ((Scene)(ref scene)).name; Entry[] entries = Entries; foreach (Entry entry in entries) { if (string.Equals(name, entry.SceneName, StringComparison.Ordinal) && string.Equals(((Object)((Component)fsm).gameObject).name, entry.ObjectName, StringComparison.Ordinal) && string.Equals(fsm.FsmName, entry.FsmName, StringComparison.Ordinal)) { return entry; } } return null; } private static bool IsExactCoralKingVictoryWrite(SetPlayerDataBool action) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) if (action != null && !((Object)(object)((FsmStateAction)action).Owner == (Object)null) && !((Object)(object)((FsmStateAction)action).Owner.transform == (Object)null) && !((Object)(object)((FsmStateAction)action).Owner.transform.parent == (Object)null) && ((FsmStateAction)action).Fsm != null && ((FsmStateAction)action).State != null && action.boolName != null && action.value != null && PlayerData.instance != null) { Scene scene = ((FsmStateAction)action).Owner.scene; if (string.Equals(((Scene)(ref scene)).name, "Memory_Coral_Tower", StringComparison.Ordinal) && string.Equals(((Object)((FsmStateAction)action).Owner).name, "Coral King", StringComparison.Ordinal) && string.Equals(((Object)((FsmStateAction)action).Owner.transform.parent).name, "Boss Scene", StringComparison.Ordinal) && string.Equals(((FsmStateAction)action).Fsm.Name, "Control", StringComparison.Ordinal) && string.Equals(((FsmStateAction)action).State.Name, "Heart Death Start", StringComparison.Ordinal) && string.Equals(action.boolName.Value, "defeatedCoralKing", StringComparison.Ordinal) && action.value.Value && PlayerData.instance.defeatedCoralKing) { FsmStateAction[] actions = ((FsmStateAction)action).State.Actions; if (actions != null && actions.Length == 2 && (object)actions[0] == action) { FsmStateAction obj = actions[1]; RunFSM val = (RunFSM)(object)((obj is RunFSM) ? obj : null); if (val != null && ((FsmStateAction)val).Enabled && !val.everyFrame && (((FsmStateAction)action).State.Transitions == null || ((FsmStateAction)action).State.Transitions.Length == 0)) { return HasExactCoralKingVictoryIncomingEdge(((FsmStateAction)action).Fsm); } } return false; } } return false; } private static bool HasExactCoralKingVictoryIncomingEdge(Fsm fsm) { if (((fsm != null) ? fsm.States : null) == null) { return false; } int num = 0; FsmState[] states = fsm.States; foreach (FsmState val in states) { if (val == null || val.Transitions == null) { continue; } FsmTransition[] transitions = val.Transitions; foreach (FsmTransition val2 in transitions) { if (val2 != null && string.Equals(val2.ToState, "Heart Death Start", StringComparison.Ordinal)) { num++; if (!string.Equals(val.Name, "Death Stagger", StringComparison.Ordinal) || !string.Equals(val2.EventName, FsmEvent.Finished.Name, StringComparison.Ordinal)) { return false; } } } } if (fsm.GlobalTransitions != null) { FsmTransition[] transitions = fsm.GlobalTransitions; foreach (FsmTransition val3 in transitions) { if (val3 != null && string.Equals(val3.ToState, "Heart Death Start", StringComparison.Ordinal)) { return false; } } } return num == 1; } private static FsmState FindState(PlayMakerFSM fsm, string stateName) { if (fsm.FsmStates == null) { return null; } FsmState[] fsmStates = fsm.FsmStates; foreach (FsmState val in fsmStates) { if (val != null && string.Equals(val.Name, stateName, StringComparison.Ordinal)) { return val; } } return null; } private static bool ContainsCompletionAction(FsmState state, string locationName) { if (state.Actions == null) { return false; } FsmStateAction[] actions = state.Actions; for (int i = 0; i < actions.Length; i++) { if (actions[i] is ReportMemoryBossCompletion reportMemoryBossCompletion && string.Equals(reportMemoryBossCompletion.LocationName, locationName, StringComparison.Ordinal)) { return true; } } return false; } private static bool HasExactVictoryIncomingEdges(PlayMakerFSM fsm, Entry entry) { if (fsm.FsmStates == null) { return false; } int num = 0; int num2 = 0; FsmState[] fsmStates = fsm.FsmStates; foreach (FsmState val in fsmStates) { if (val == null || val.Transitions == null) { continue; } FsmTransition[] transitions = val.Transitions; foreach (FsmTransition val2 in transitions) { if (val2 != null && string.Equals(val2.ToState, "Exit Memory", StringComparison.Ordinal)) { bool flag = string.Equals(val.Name, entry.VictorySourceState, StringComparison.Ordinal); bool flag2 = !string.IsNullOrEmpty(entry.SecondaryVictorySourceState) && string.Equals(val.Name, entry.SecondaryVictorySourceState, StringComparison.Ordinal); if ((!flag && !flag2) || !string.Equals(val2.EventName, entry.VictoryEvent, StringComparison.Ordinal)) { return false; } if (flag) { num++; } else { num2++; } } } } FsmTransition[] fsmGlobalTransitions = fsm.FsmGlobalTransitions; if (fsmGlobalTransitions != null) { FsmTransition[] transitions = fsmGlobalTransitions; foreach (FsmTransition val3 in transitions) { if (val3 != null && string.Equals(val3.ToState, "Exit Memory", StringComparison.Ordinal)) { return false; } } } bool flag3 = !string.IsNullOrEmpty(entry.SecondaryVictorySourceState); if (num == 1) { return num2 == (flag3 ? 1 : 0); } return false; } private static bool HasExactExitActions(FsmState exitState, Entry entry) { FsmStateAction[] actions = exitState.Actions; if (actions == null || (exitState.Transitions != null && exitState.Transitions.Length != 0)) { return false; } int num = (entry.HasAudioSnapshotAction ? 6 : 5); if (actions.Length == num) { FsmStateAction obj = actions[0]; StartPreloadingScene val = (StartPreloadingScene)(object)((obj is StartPreloadingScene) ? obj : null); if (val != null) { if (entry.HasAudioSnapshotAction) { if (!(actions[1] is TransitionToAudioSnapshot)) { return false; } } else if (!(actions[1] is ScreenFader)) { return false; } int num2 = (entry.HasAudioSnapshotAction ? 1 : 0); if (actions[1 + num2] is ScreenFader && actions[2 + num2] is Wait && actions[3 + num2] is SetMeshRenderer) { FsmStateAction obj2 = actions[4 + num2]; BeginSceneTransition val2 = (BeginSceneTransition)(object)((obj2 is BeginSceneTransition) ? obj2 : null); if (val2 != null) { if (val.SceneName != null && string.Equals(val.SceneName.Value, entry.ReturnSceneName, StringComparison.Ordinal) && val2.sceneName != null && val2.entryGateName != null && string.Equals(val2.sceneName.Value, entry.ReturnSceneName, StringComparison.Ordinal)) { return string.Equals(val2.entryGateName.Value, "door_wakeOnGround", StringComparison.Ordinal); } return false; } } return false; } } return false; } private static void TryReportLocation(string locationName) { try { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Boss) && instance.IsLocationEnabled(locationName) && instance.IsLocationInSeed(locationName) && !instance.IsLocationChecked(locationName)) { instance.CheckLocation(locationName); } } catch (Exception ex) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogError((object)("[RANDOMIZER] Failed to report memory-boss location " + locationName + ": " + ex)); } } } } internal static class MinorCachePatches { private sealed class PreventAntRegionCarry : MonoBehaviour, ICheck { public bool CanEnterAntRegion => false; public bool TryGetRenderer(out Renderer renderer) { renderer = null; return false; } } [HarmonyPatch(typeof(GeoRock), "Start")] private static class GeoRockStartPatch { private static bool Prefix(GeoRock __instance) { return !TryReplace((Component)(object)__instance, MinorCacheManifest.SourceKind.GeoRock); } } [HarmonyPatch(typeof(BreakableHolder), "Awake")] private static class BreakableHolderAwakePatch { private static bool Prefix(BreakableHolder __instance) { if (TryGetDirectBreakEntry(__instance, out var _, out var _)) { return true; } return !TryReplace((Component)(object)__instance, MinorCacheManifest.SourceKind.BreakableHolder); } } [HarmonyPatch(typeof(BreakableHolder), "FlingHolding")] private static class BreakableHolderFlingHoldingPatch { private static bool Prefix(BreakableHolder __instance) { SaveState state; MinorCacheManifest.Entry entry; return !TryGetDirectBreakEntry(__instance, out state, out entry); } } [HarmonyPatch(typeof(BreakableHolder), "SetBroken")] private static class BreakableHolderSetBrokenPatch { private static void Postfix(BreakableHolder __instance) { if (TryGetDirectBreakEntry(__instance, out var state, out var entry) && !state.IsLocationChecked(entry.LocationName)) { state.CheckLocation(entry.LocationName); } } } [HarmonyPatch(typeof(RosaryCache), "Awake")] private static class RosaryCacheAwakePatch { private static bool Prefix(RosaryCache __instance) { if (__instance is RosaryCacheString) { return true; } return !TryReplace((Component)(object)__instance, MinorCacheManifest.SourceKind.RosaryCache); } private static void Postfix(RosaryCache __instance) { RosaryCacheString val = (RosaryCacheString)(object)((__instance is RosaryCacheString) ? __instance : null); if (val != null) { TryCreateHangingGlow(val); } } } [HarmonyPatch(typeof(RosaryCacheString), "FlingRosaries")] private static class RosaryCacheStringFlingRosariesPatch { private static void Prefix(RosaryCacheString __instance, bool isLast, ref bool __state) { if (TryGetSeededEntry((Component)(object)__instance, MinorCacheManifest.SourceKind.RosaryCache, out var _, out var _)) { __state = IsReactivatingField.Invoke((RosaryCache)(object)__instance); IsReactivatingField.Invoke((RosaryCache)(object)__instance) = true; } } private static void Postfix(RosaryCacheString __instance, bool isLast, bool __state) { if (isLast) { RemoveHangingGlow(__instance); } if (TryGetSeededEntry((Component)(object)__instance, MinorCacheManifest.SourceKind.RosaryCache, out var state, out var entry)) { IsReactivatingField.Invoke((RosaryCache)(object)__instance) = __state; if (isLast && !__state && !state.IsLocationChecked(entry.LocationName)) { state.CheckLocation(entry.LocationName); } } } } private const float MatchTolerance = 0.75f; private const string HangingGlowName = "AP Check Shimmer"; private const string StationaryHunterNecklaceLocation = "Rosary Necklace: Hunter's March"; private const string StationaryFarFieldsNecklaceLocation = "Pale Rosary Necklace: Far Fields"; private static readonly HashSet DirectBreakCheckLocations = new HashSet(StringComparer.Ordinal) { "Rosary Cache: Far Fields #19", "Shell Shard Cache: Bilewater #1", "Shell Shard Cache: Bilewater #2", "Shell Shard Cache: Mount Fay #5", "Shell Shard Cache: Mount Fay #6", "Shell Shard Cache: Mount Fay #7", "Shell Shard Cache: Putrified Ducts #6", "Shell Shard Cache: Putrified Ducts #8", "Shell Shard Cache: Putrified Ducts #9", "Shell Shard Cache: Putrified Ducts #10", "Shell Shard Cache: Putrified Ducts #11", "Shell Shard Cache: Sinner's Road #6", "Shell Shard Cache: Sinner's Road #7", "Shell Shard Cache: The Slab #4", "Shell Shard Cache: The Slab #5", "Shell Shard Cache: Underworks #3" }; private static bool loggedReplacementFailure; private static bool loggedHangingGlowFailure; private static readonly FieldRef IsReactivatingField = AccessTools.FieldRefAccess("k__BackingField"); private static readonly FieldInfo RosaryGroupsField = AccessTools.Field(typeof(RosaryCacheString), "rosaryGroups"); internal static MinorCacheManifest.Entry FindExactSource(string sceneName, string objectName, Vector2 position, MinorCacheManifest.SourceKind kind) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(sceneName) || string.IsNullOrWhiteSpace(objectName)) { return null; } float num = 0.5625f; MinorCacheManifest.Entry entry = null; MinorCacheManifest.Entry[] entries = MinorCacheManifest.Entries; foreach (MinorCacheManifest.Entry entry2 in entries) { if (entry2.Kind != kind || !string.Equals(entry2.SceneName, sceneName, StringComparison.OrdinalIgnoreCase) || !string.Equals(entry2.ObjectName, objectName, StringComparison.Ordinal)) { continue; } float num2 = position.x - entry2.X; float num3 = position.y - entry2.Y; if (!(num2 * num2 + num3 * num3 > num)) { if (entry != null && !string.Equals(entry.LocationName, entry2.LocationName, StringComparison.Ordinal)) { return null; } entry = entry2; } } return entry; } private static bool TryGetSeededEntry(Component source, MinorCacheManifest.SourceKind kind, out SaveState state, out MinorCacheManifest.Entry entry) { //IL_002b: 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_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) state = SaveState.Instance; entry = null; if ((Object)(object)source == (Object)null || state == null || !state.IsRandomized(ItemType.Resource)) { return false; } Scene scene = source.gameObject.scene; entry = FindExactSource(((Scene)(ref scene)).name, ((Object)source.gameObject).name, Vector2.op_Implicit(source.transform.position), kind); if (entry == null || !state.IsLocationEnabled(entry.LocationName) || !state.IsLocationInSeed(entry.LocationName)) { entry = null; return false; } return true; } private static bool TryReplace(Component source, MinorCacheManifest.SourceKind kind) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) if (!TryGetSeededEntry(source, kind, out var state, out var entry)) { return false; } GameObject val = null; try { if (!state.IsLocationChecked(entry.LocationName)) { val = Object.Instantiate(((Component)Gameplay.CollectableItemPickupPrefab).gameObject); ((Object)val).name = "AP Minor Cache - " + entry.LocationName; val.transform.position = source.transform.position; if (RequiresStationaryAntVeto(entry.LocationName)) { val.AddComponent(); } CollectableItemPickup component = val.GetComponent(); if ((Object)(object)component == (Object)null) { Object.Destroy((Object)(object)val); return false; } component.SetItem(MinorPickupPatches.GetProxyItem(entry.LocationName), false); } source.gameObject.SetActive(false); Object.Destroy((Object)(object)source.gameObject); return true; } catch (Exception ex) { if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } if (!loggedReplacementFailure) { loggedReplacementFailure = true; ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Could not replace a known minor cache; its vanilla source was left intact: " + ex.Message)); } } return false; } } internal static bool RequiresStationaryAntVeto(string locationName) { if (!string.Equals(locationName, "Rosary Necklace: Hunter's March", StringComparison.Ordinal)) { return string.Equals(locationName, "Pale Rosary Necklace: Far Fields", StringComparison.Ordinal); } return true; } private static bool RequiresDirectBreakCheck(MinorCacheManifest.Entry entry) { if (entry == null) { return false; } return DirectBreakCheckLocations.Contains(entry.LocationName); } private static bool TryGetDirectBreakEntry(BreakableHolder source, out SaveState state, out MinorCacheManifest.Entry entry) { if (TryGetSeededEntry((Component)(object)source, MinorCacheManifest.SourceKind.BreakableHolder, out state, out entry)) { return RequiresDirectBreakCheck(entry); } return false; } private static bool TryGetRosaryVisualBounds(RosaryCacheString source, out Bounds bounds, out SpriteRenderer sortingReference) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) bounds = default(Bounds); sortingReference = null; if ((Object)(object)source == (Object)null || RosaryGroupsField == null) { return false; } if (!(RosaryGroupsField.GetValue(source) is Array array)) { return false; } bool flag = false; foreach (object item in array) { if (item == null || !(AccessTools.Field(item.GetType(), "RepresentingObjects")?.GetValue(item) is GameObject[] array2)) { continue; } GameObject[] array3 = array2; foreach (GameObject val in array3) { if ((Object)(object)val == (Object)null) { continue; } SpriteRenderer[] componentsInChildren = val.GetComponentsInChildren(true); foreach (SpriteRenderer val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null)) { if (!flag) { bounds = ((Renderer)val2).bounds; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(((Renderer)val2).bounds); } if ((Object)(object)sortingReference == (Object)null || ((Renderer)val2).sortingOrder > ((Renderer)sortingReference).sortingOrder) { sortingReference = val2; } } } } } return flag; } private static void TryCreateHangingGlow(RosaryCacheString source) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) if (!TryGetSeededEntry((Component)(object)source, MinorCacheManifest.SourceKind.RosaryCache, out var state, out var entry) || state.IsLocationChecked(entry.LocationName) || (Object)(object)((Component)source).transform.Find("AP Check Shimmer") != (Object)null) { return; } Sprite val = RandomizerPlugin.Instance?.MapCheckIcon ?? RandomizerPlugin.Instance?.ArchipelagoIcon; if ((Object)(object)val == (Object)null) { return; } GameObject val2 = null; try { if (TryGetRosaryVisualBounds(source, out var bounds, out var sortingReference)) { val2 = new GameObject("AP Check Shimmer"); val2.SetActive(false); val2.layer = ((Component)source).gameObject.layer; val2.transform.SetParent(((Component)source).transform, true); val2.transform.position = ((Bounds)(ref bounds)).center; SpriteRenderer val3 = val2.AddComponent(); val3.sprite = val; val3.color = new Color(1f, 1f, 1f, 0.66f); if ((Object)(object)sortingReference != (Object)null) { ((Renderer)val3).sortingLayerID = ((Renderer)sortingReference).sortingLayerID; ((Renderer)val3).sortingOrder = ((Renderer)sortingReference).sortingOrder + 2; } Bounds bounds2 = val.bounds; float x = ((Bounds)(ref bounds2)).size.x; bounds2 = val.bounds; float num = Mathf.Max(x, ((Bounds)(ref bounds2)).size.y); float num2 = Mathf.Max(Mathf.Abs(((Component)source).transform.lossyScale.x), Mathf.Abs(((Component)source).transform.lossyScale.y)); float num3 = ((num > 0f && num2 > 0f) ? (0.9f / num / num2) : 1f); val2.transform.localScale = new Vector3(num3, num3, 1f); SpriteFadePulse obj = val2.AddComponent(); obj.lowAlpha = 0.42f; obj.highAlpha = 0.9f; obj.fadeDuration = 0.8f; obj.startPaused = false; val2.SetActive(true); } } catch (Exception ex) { if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)val2); } if (!loggedHangingGlowFailure) { loggedHangingGlowFailure = true; ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Could not add the visual AP shimmer to a hanging rosary cache: " + ex.Message)); } } } } private static void RemoveHangingGlow(RosaryCacheString source) { Transform val = (((Object)(object)source == (Object)null) ? null : ((Component)source).transform.Find("AP Check Shimmer")); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)((Component)val).gameObject); } } } internal static class MinorPickupPatches { private sealed class ArchipelagoLocationItem : SavedItem { internal string LocationName; internal ItemType Type; public override void Get(bool showPopup = true) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(Type) && instance.IsLocationInSeed(LocationName) && !instance.IsLocationChecked(LocationName)) { instance.CheckLocation(LocationName); } } public override bool CanGetMore() { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(Type) && instance.IsLocationInSeed(LocationName)) { return !instance.IsLocationChecked(LocationName); } return false; } } [HarmonyPatch(typeof(CollectableItemPickup), "Awake")] private static class CollectableItemPickupAwakePatch { private static void Prefix(CollectableItemPickup __instance) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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_004a: Unknown result type (might be due to invalid IL or missing references) SaveState instance = SaveState.Instance; SavedItem val = (((Object)(object)__instance == (Object)null) ? null : __instance.Item); if (instance != null && !((Object)(object)val == (Object)null)) { Scene scene = ((Component)__instance).gameObject.scene; MinorPickupManifest.Entry entry = FindExactSource(((Scene)(ref scene)).name, ((Object)val).name, Vector2.op_Implicit(((Component)__instance).transform.position), Utils.GetHierarchyPath(((Component)__instance).transform)); if (entry != null && instance.IsRandomized(entry.Type) && instance.IsLocationEnabled(entry.LocationName) && instance.IsLocationInSeed(entry.LocationName)) { __instance.SetItem(GetProxyItem(entry.LocationName, entry.Type), true); } } } } [HarmonyPatch(typeof(CollectableItemPickup), "SetItem", new Type[] { typeof(SavedItem), typeof(bool) })] private static class CollectableItemPickupSetItemPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(CollectableItemPickup __instance, ref SavedItem newItem) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)newItem == (Object)null)) { TryReplaceSourceItem(__instance, ref newItem); } } } [HarmonyPatch(typeof(CollectableItemPickup), "CheckActivation")] private static class CollectableItemPickupCheckActivationPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(CollectableItemPickup __instance, ref SavedItem ___item) { TryReplaceSourceItem(__instance, ref ___item); } } [HarmonyPatch(typeof(CollectableItemPickup), "DoPickupAction", new Type[] { typeof(bool) })] private static class CollectableItemPickupDoPickupActionPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(CollectableItemPickup __instance, ref SavedItem ___item) { TryReplaceSourceItem(__instance, ref ___item); } } private const float MatchTolerance = 0.75f; private static readonly Dictionary ProxyByLocation = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static SavedItem GetProxyItem(string locationName, ItemType type = ItemType.Resource) { if (ProxyByLocation.TryGetValue(locationName, out var value) && (Object)(object)value != (Object)null) { return (SavedItem)(object)value; } value = ScriptableObject.CreateInstance(); ((Object)value).name = "Archipelago Location - " + locationName; value.LocationName = locationName; value.Type = type; ProxyByLocation[locationName] = value; return (SavedItem)(object)value; } internal static MinorPickupManifest.Entry FindExactSource(string sceneName, string assetName, Vector2 position, string hierarchyPath = "") { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(sceneName) || string.IsNullOrWhiteSpace(assetName)) { return null; } float num = 0.5625f; MinorPickupManifest.Entry entry = null; MinorPickupManifest.Entry[] entries = MinorPickupManifest.Entries; foreach (MinorPickupManifest.Entry entry2 in entries) { if (!string.Equals(sceneName, entry2.SceneName, StringComparison.OrdinalIgnoreCase) || !string.Equals(assetName, entry2.AssetName, StringComparison.Ordinal)) { continue; } bool num2 = !string.IsNullOrEmpty(entry2.HierarchyPath) && string.Equals(hierarchyPath, entry2.HierarchyPath, StringComparison.OrdinalIgnoreCase); float num3 = position.x - entry2.X; float num4 = position.y - entry2.Y; bool flag = num3 * num3 + num4 * num4 <= num; if (num2 || flag) { if (entry != null && !string.Equals(entry.LocationName, entry2.LocationName, StringComparison.Ordinal)) { return null; } entry = entry2; } } return entry; } private static void TryReplaceSourceItem(CollectableItemPickup pickup, ref SavedItem item) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_003a: 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) if (!((Object)(object)pickup == (Object)null) && !((Object)(object)item == (Object)null)) { SaveState instance = SaveState.Instance; Scene scene = ((Component)pickup).gameObject.scene; MinorPickupManifest.Entry entry = FindExactSource(((Scene)(ref scene)).name, ((Object)item).name, Vector2.op_Implicit(((Component)pickup).transform.position), Utils.GetHierarchyPath(((Component)pickup).transform)); if (entry != null && instance != null && instance.IsRandomized(entry.Type) && instance.IsLocationEnabled(entry.LocationName) && instance.IsLocationInSeed(entry.LocationName)) { item = GetProxyItem(entry.LocationName, entry.Type); } } } } internal class MistFix { [HarmonyPatch(typeof(MazeController), "LinkDoors", new Type[] { typeof(IReadOnlyList) })] internal static class MazeController_LinkDoors_Patch { private static void Prefix(out bool __state) { __state = PlayerData.instance.hasNeedolin; SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Skill)) { PlayerData.instance.hasNeedolin = instance.canUseNeedolin; } } private static Exception Finalizer(Exception __exception, bool __state) { PlayerData.instance.hasNeedolin = __state; return __exception; } } } [HarmonyPatch(typeof(HeroController), "SendHeroInPosition")] internal static class MossMotherVineMaskPatches { private const string SceneName = "Tut_03"; private const string EntryGateName = "top1"; private const string ControlFsmName = "Control"; private const string MaskVariableName = "Mask"; private const string MaskRendererName = "GameObject"; private const string MaskSpriteName = "msk_generic"; private const string MaskSortingLayerName = "Over"; private static readonly string[] MaskPaths = new string[2] { "Black Thread States/Normal World/Moss Vine Cluster/Mask", "Black Thread States/Normal World/Moss Vine Cluster (1)/Mask" }; [HarmonyPostfix] private static void Postfix(HeroController __instance) { if (IsExactBypassEntry(__instance)) { TryHideSkippedVineMasks(); } } private static bool IsExactBypassEntry(HeroController hero) { SaveState instance = SaveState.Instance; GameManager instance2 = GameManager.instance; if ((Object)(object)hero != (Object)null && (Object)(object)((Component)hero).gameObject != (Object)null && (Object)(object)hero.sceneEntryGate != (Object)null && instance != null && (Object)(object)instance2 != (Object)null && instance.mossMotherBypassedByBoneBottomWarp && !instance.IsLocationChecked("Boss: Moss Mother") && string.Equals(instance2.GetSceneNameString(), "Tut_03", StringComparison.Ordinal)) { return string.Equals(((Object)hero.sceneEntryGate).name, "top1", StringComparison.Ordinal); } return false; } private static void TryHideSkippedVineMasks() { try { NestedFadeGroup[] groups = Resources.FindObjectsOfTypeAll(); NestedFadeGroup[] array = (NestedFadeGroup[])(object)new NestedFadeGroup[MaskPaths.Length]; for (int i = 0; i < MaskPaths.Length; i++) { if (!TryResolveExactMask(groups, MaskPaths[i], out array[i])) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)"[RANDOMIZER] Moss Mother top1 mask repair found changed Tut_03 vine structure and made no scene changes."); } return; } } bool flag = false; NestedFadeGroup[] array2 = array; foreach (NestedFadeGroup val in array2) { flag |= !Mathf.Approximately(((NestedFadeGroupBase)val).AlphaSelf, 0f); ((NestedFadeGroupBase)val).AlphaSelf = 0f; } if (flag) { ManualLogSource log2 = RandomizerPlugin.Log; if (log2 != null) { log2.LogInfo((object)"[RANDOMIZER] Hid Tut_03's two skipped right-vine masks for the Bone Bottom top1 route."); } } } catch (Exception ex) { ManualLogSource log3 = RandomizerPlugin.Log; if (log3 != null) { log3.LogWarning((object)("[RANDOMIZER] Moss Mother top1 mask repair failed closed: " + ex.Message)); } } } private static bool TryResolveExactMask(NestedFadeGroup[] groups, string expectedPath, out NestedFadeGroup target) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) target = null; int num = 0; foreach (NestedFadeGroup val in groups) { if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject == (Object)null)) { Scene scene = ((Component)val).gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, "Tut_03", StringComparison.Ordinal) && string.Equals(Utils.GetHierarchyPath(((Component)val).transform), expectedPath, StringComparison.Ordinal)) { num++; target = val; } } } if (num == 1) { return IsExactSerializedMask(target); } return false; } private static bool IsExactSerializedMask(NestedFadeGroup group) { if ((Object)(object)group == (Object)null || (Object)(object)((Component)group).transform.parent == (Object)null || !string.Equals(((Object)((Component)group).gameObject).name, "Mask", StringComparison.Ordinal)) { return false; } PlayMakerFSM val = null; int num = 0; PlayMakerFSM[] components = ((Component)((Component)group).transform.parent).GetComponents(); foreach (PlayMakerFSM val2 in components) { if ((Object)(object)val2 != (Object)null && string.Equals(val2.FsmName, "Control", StringComparison.Ordinal)) { val = val2; num++; } } FsmGameObject val3 = ((num == 1 && (Object)(object)val != (Object)null && val.FsmVariables != null) ? val.FsmVariables.GetFsmGameObject("Mask") : null); Transform val4 = ((Component)group).transform.Find("GameObject"); SpriteRenderer val5 = (((Object)(object)val4 == (Object)null) ? null : ((Component)val4).GetComponent()); if (val3 != null && (Object)(object)val3.Value == (Object)(object)((Component)group).gameObject && (Object)(object)val5 != (Object)null && (Object)(object)val5.sprite != (Object)null && string.Equals(((Object)val5.sprite).name, "msk_generic", StringComparison.Ordinal)) { return string.Equals(((Renderer)val5).sortingLayerName, "Over", StringComparison.Ordinal); } return false; } } [HarmonyPatch(typeof(PlayMakerFSM), "Start")] internal static class MossMotherWarpSafety { private sealed class ConfirmRealDefeat : FsmStateAction { public override void OnEnter() { SaveState instance = SaveState.Instance; if (instance != null) { instance.mossMotherBypassedByBoneBottomWarp = false; } ((FsmStateAction)this).Finish(); } } internal const string LocationName = "Boss: Moss Mother"; [HarmonyPostfix] private static void Postfix(PlayMakerFSM __instance) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null) { return; } Scene scene = ((Component)__instance).gameObject.scene; if (!string.Equals(((Scene)(ref scene)).name, "Tut_03", StringComparison.Ordinal) || !string.Equals(((Object)__instance).name, "Mossbone Mother", StringComparison.Ordinal) || !string.Equals(__instance.FsmName, "Control", StringComparison.Ordinal)) { return; } FsmState val = FindState(__instance, "End"); if (val == null || val.Actions == null) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)"[RANDOMIZER] Moss Mother F4 safety could not find the shipped Control/End state."); } return; } for (int i = 0; i < val.Actions.Length; i++) { if (val.Actions[i] is ConfirmRealDefeat) { return; } } int num = -1; for (int j = 0; j < val.Actions.Length; j++) { if (val.Actions[j] is SetPlayerDataBool) { num = j; break; } } if (num < 0) { ManualLogSource log2 = RandomizerPlugin.Log; if (log2 != null) { log2.LogWarning((object)"[RANDOMIZER] Moss Mother F4 safety found Control/End but not its defeatedMossMother action."); } return; } ConfirmRealDefeat confirmRealDefeat = new ConfirmRealDefeat(); ((FsmStateAction)confirmRealDefeat).Init(val); FsmStateAction[] actions = val.Actions; FsmStateAction[] array = (FsmStateAction[])(object)new FsmStateAction[actions.Length + 1]; Array.Copy(actions, 0, array, 0, num); array[num] = (FsmStateAction)(object)confirmRealDefeat; Array.Copy(actions, num, array, num + 1, actions.Length - num); val.Actions = array; ManualLogSource log3 = RandomizerPlugin.Log; if (log3 != null) { log3.LogInfo((object)"[RANDOMIZER] Moss Mother F4 safety attached to the shipped boss completion state."); } } internal static bool PrepareForBoneBottomWarp() { SaveState instance = SaveState.Instance; PlayerData instance2 = PlayerData.instance; if (instance == null || instance2 == null || instance2.defeatedMossMother || instance.IsLocationChecked("Boss: Moss Mother")) { return false; } instance.mossMotherBypassedByBoneBottomWarp = true; return true; } internal static void CancelPreparedWarp() { SaveState instance = SaveState.Instance; if (instance != null) { instance.mossMotherBypassedByBoneBottomWarp = false; } } internal static void RecoverInterruptedBoneBottomWarp() { SaveState instance = SaveState.Instance; if (instance != null && !instance.IsLocationChecked("Boss: Moss Mother")) { instance.mossMotherBypassedByBoneBottomWarp = true; } } internal static void Update() { SaveState instance = SaveState.Instance; PlayerData instance2 = PlayerData.instance; if (instance != null && instance2 != null && instance.mossMotherBypassedByBoneBottomWarp) { if (instance.IsLocationChecked("Boss: Moss Mother")) { instance.mossMotherBypassedByBoneBottomWarp = false; } else { instance2.defeatedMossMother = false; } } } internal static bool IsLegitimateDefeat() { SaveState instance = SaveState.Instance; if (PlayerData.instance != null && PlayerData.instance.defeatedMossMother) { if (instance != null) { return !instance.mossMotherBypassedByBoneBottomWarp; } return true; } return false; } private static FsmState FindState(PlayMakerFSM fsm, string stateName) { FsmState[] fsmStates = fsm.FsmStates; if (fsmStates == null) { return null; } FsmState[] array = fsmStates; foreach (FsmState val in array) { if (val != null && string.Equals(val.Name, stateName, StringComparison.Ordinal)) { return val; } } return null; } } internal static class NeedleUpgradePatches { [HarmonyPatch(typeof(GetPlayerDataInt), "OnEnter")] private static class PlinneyServiceProgressPatch { private static bool Prefix(GetPlayerDataInt __instance) { if (!IsEnabled || !IsExactPlinneyAction((FsmStateAction)(object)__instance, "Upgrade State") || __instance.intName == null || !string.Equals(__instance.intName.Value, "nailUpgrades", StringComparison.Ordinal) || __instance.storeValue == null) { return true; } __instance.storeValue.Value = GetPurchasedPlinneyTier(); ((FsmStateAction)__instance).Finish(); return false; } } [HarmonyPatch(typeof(SavedItemGetV2), "OnEnter")] private static class PlinneyNeedleRewardPatch { private static bool Prefix(SavedItemGetV2 __instance) { if (!IsEnabled || !IsExactPlinneyAction((FsmStateAction)(object)__instance, "Upgrade")) { return true; } SavedItem val = (SavedItem)((__instance.Item == null) ? null : /*isinst with value type is only supported in some contexts*/); if ((Object)(object)val == (Object)null || !string.Equals(((Object)val).name, "Needle Upgrade", StringComparison.Ordinal)) { return true; } string currentPlinneyLocation = GetCurrentPlinneyLocation(); if (!string.IsNullOrEmpty(currentPlinneyLocation)) { SaveState.Instance.CheckLocation(currentPlinneyLocation); } ((FsmStateAction)__instance).Finish(); return false; } } [HarmonyPatch(typeof(SavedItem), "TryGet", new Type[] { typeof(bool), typeof(bool) })] private static class PaleOilSourceRewardPatch { private static bool Prefix(SavedItem __instance, ref bool __result) { string paleOilLocation = GetPaleOilLocation(); if (!IsEnabled || string.IsNullOrEmpty(paleOilLocation) || (Object)(object)__instance == (Object)null || !string.Equals(((Object)__instance).name, "Pale_Oil", StringComparison.Ordinal)) { return true; } SaveState.Instance.CheckLocation(paleOilLocation); __result = true; return false; } } private const string PlinneyScene = "Belltown_Room_pinsmith"; private const string PlinneyObject = "Plinney Inside"; private const string PlinneyFsm = "Dialogue"; private const string PlinneyUpgradeStateRead = "Upgrade State"; private const string PlinneyUpgradeState = "Upgrade"; private const string NeedleUpgradeAsset = "Needle Upgrade"; private const string PaleOilAsset = "Pale_Oil"; private const string WhisperingVaultsScene = "Library_03"; private const string GreatTasteScene = "Song_09b"; private const string EcstasyOfTheEndScene = "Aqueduct_05_Festival"; private static readonly string[] PlinneyLocations = new string[4] { "Pinmaster Plinney: Sharpened Needle", "Pinmaster Plinney: Shining Needle", "Pinmaster Plinney: Hivesteel Needle", "Pinmaster Plinney: Pale Steel Needle" }; private static bool IsEnabled { get { if (SaveState.Instance != null) { return SaveState.Instance.randomizeNeedleUpgrades; } return false; } } private static string CurrentSceneName { get { string text; if (!((Object)(object)GameManager.instance == (Object)null)) { text = GameManager.instance.sceneName; if (text == null) { return string.Empty; } } else { text = string.Empty; } return text; } } private static bool IsExactPlinneyAction(FsmStateAction action, string stateName) { if (action != null && (Object)(object)action.Owner != (Object)null && action.Fsm != null && action.State != null && string.Equals(CurrentSceneName, "Belltown_Room_pinsmith", StringComparison.OrdinalIgnoreCase) && string.Equals(((Object)action.Owner).name, "Plinney Inside", StringComparison.Ordinal) && string.Equals(action.Fsm.Name, "Dialogue", StringComparison.Ordinal)) { return string.Equals(action.State.Name, stateName, StringComparison.Ordinal); } return false; } internal static int GetPurchasedPlinneyTier() { SaveState instance = SaveState.Instance; if (instance == null) { return 0; } int i; for (i = 0; i < PlinneyLocations.Length && instance.IsLocationChecked(PlinneyLocations[i]); i++) { } return i; } private static string GetCurrentPlinneyLocation() { int purchasedPlinneyTier = GetPurchasedPlinneyTier(); if (purchasedPlinneyTier < 0 || purchasedPlinneyTier >= PlinneyLocations.Length) { return null; } return PlinneyLocations[purchasedPlinneyTier]; } private static string GetPaleOilLocation() { return CurrentSceneName switch { "Library_03" => "Pale Oil: Whispering Vaults", "Song_09b" => "Pale Oil: Great Taste of Pharloom", "Aqueduct_05_Festival" => "Pale Oil: Ecstasy of the End", _ => null, }; } } [HarmonyPatch(typeof(OpeningSequence), "Start")] internal static class NewRandomizerIntroSkipPatch { private static bool skipNextOpeningSequence; internal static void ArmForNewRandomizerSave() { skipNextOpeningSequence = true; } internal static bool TryConsumeSkipRequest() { if (!skipNextOpeningSequence) { return false; } skipNextOpeningSequence = false; return true; } private static void Postfix(OpeningSequence __instance, ChainSequence ___chainSequence, ref IEnumerator __result) { if (TryConsumeSkipRequest() && !((Object)(object)__instance == (Object)null) && !((Object)(object)___chainSequence == (Object)null) && __result != null) { __result = RunAndSkipOpening(__instance, ___chainSequence, __result); } } private static IEnumerator RunAndSkipOpening(OpeningSequence openingSequence, ChainSequence chainSequence, IEnumerator nativeRoutine) { while (nativeRoutine.MoveNext()) { if (((SkippableSequence)chainSequence).IsPlaying && chainSequence.CanSkipCurrent) { IEnumerator skipRoutine = openingSequence.Skip(); while (skipRoutine.MoveNext()) { yield return skipRoutine.Current; } } yield return nativeRoutine.Current; } } } internal static class PriceRandomizerPatches { [HarmonyPatch(typeof(ShopItem), "get_Cost")] private static class ShopItemCostPatch { [HarmonyPrefix] private static bool Prefix(ShopItem __instance, ref int __result) { if ((Object)(object)__instance == (Object)null || !__instance.IsAvailableNotInfinite || !TryGetPrice("shop:" + (((Object)__instance).name ?? string.Empty), out var price)) { return true; } __result = price; return false; } } [HarmonyPatch(typeof(PlayMakerFSM), "Start")] private static class BellwayTollCostPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(PlayMakerFSM __instance) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).gameObject == (Object)null || !string.Equals(__instance.FsmName, "Unlock Behaviour", StringComparison.Ordinal) || (!string.Equals(((Object)((Component)__instance).gameObject).name, "Bellway Toll Machine", StringComparison.Ordinal) && !string.Equals(((Object)((Component)__instance).gameObject).name, "Bellway Toll Machine (1)", StringComparison.Ordinal))) { return; } FsmVariables fsmVariables = __instance.FsmVariables; FsmString obj = ((fsmVariables != null) ? fsmVariables.GetFsmString("Pickup PlayerData Bool") : null); string text = ((obj != null) ? obj.Value : null) ?? string.Empty; Scene scene = ((Component)__instance).gameObject.scene; string baseSceneName = GameManager.GetBaseSceneName(((Scene)(ref scene)).name ?? string.Empty); string text2 = "bellway:" + baseSceneName + ":" + ((Object)((Component)__instance).gameObject).name + ":" + text; if (!TryGetPrice(text2, out var price)) { return; } FsmVariables fsmVariables2 = __instance.FsmVariables; FsmObject val = ((fsmVariables2 != null) ? fsmVariables2.GetFsmObject("Cost Reference") : null); if (val == null || IntReferenceValueField == null) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Could not apply Bellway price for " + text2 + ": the Cost Reference field was not found.")); } } else { CostReference val2 = ScriptableObject.CreateInstance(); ((Object)val2).name = "AP " + text2; IntReferenceValueField.SetValue(val2, price); val.Value = (Object)(object)val2; } } } [HarmonyPatch(typeof(FullQuestBase), "get_Targets")] private static class DonationTargetsPatch { [HarmonyPrefix] private static void Prefix(FullQuestBase __instance) { ApplyDonationPrice(__instance); } } [HarmonyPatch(typeof(FullQuestBase), "get_Counters")] private static class DonationCountersPatch { [HarmonyPrefix] private static void Prefix(FullQuestBase __instance) { ApplyDonationPrice(__instance); } } [HarmonyPatch(typeof(FullQuestBase), "get_CanComplete")] private static class DonationCanCompletePatch { [HarmonyPrefix] private static void Prefix(FullQuestBase __instance) { ApplyDonationPrice(__instance); } } [HarmonyPatch(typeof(FullQuestBase), "TryEndQuest")] private static class DonationTryEndQuestPatch { [HarmonyPrefix] private static void Prefix(FullQuestBase __instance) { ApplyDonationPrice(__instance); } } [HarmonyPatch(typeof(GetCostFromReference), "OnEnter")] private static class PlinneyUpgradeCostPatch { [HarmonyPrefix] private static bool Prefix(GetCostFromReference __instance) { if (!IsExactPlinneyCostAction((FsmStateAction)(object)__instance)) { return true; } SaveState instance = SaveState.Instance; int num = ((instance == null || !instance.randomizeNeedleUpgrades) ? ((PlayerData.instance == null) ? (-1) : PlayerData.instance.nailUpgrades) : NeedleUpgradePatches.GetPurchasedPlinneyTier()); int num2 = num + 1; if ((num2 != 3 && num2 != 4) || !TryGetPrice("upgrade:plinney:" + num2, out var price) || __instance.StoreValue == null) { return true; } __instance.StoreValue.Value = price; ((FsmStateAction)__instance).Finish(); return false; } } private const string ShopPrefix = "shop:"; private const string BellwayPrefix = "bellway:"; private const string DonationPrefix = "donation:"; private const string UpgradePrefix = "upgrade:plinney:"; private const string BellwayTollObject = "Bellway Toll Machine"; private const string BellwayTollFsm = "Unlock Behaviour"; private const string BellwayFlagVariable = "Pickup PlayerData Bool"; private const string BellwayCostVariable = "Cost Reference"; private const string PlinneyScene = "Belltown_Room_pinsmith"; private const string PlinneyObject = "Plinney Inside"; private const string PlinneyFsm = "Dialogue"; private const string PlinneyCostState = "Upgrade? Cost"; private static readonly FieldInfo IntReferenceValueField = AccessTools.Field(typeof(IntReference), "value"); private static readonly FieldInfo QuestTargetsField = AccessTools.Field(typeof(FullQuestBase), "targets"); private static readonly Dictionary VanillaDonationPrices = new Dictionary(StringComparer.Ordinal) { { "Belltown House Start", 250 }, { "Belltown House Mid", 400 }, { "Songclave Donation 1", 300 }, { "Songclave Donation 2", 500 }, { "Building Materials", 200 }, { "Building Materials (Bridge)", 300 }, { "Building Materials (Statue)", 440 } }; private static bool TryGetPrice(string key, out int price) { price = 0; return SaveState.Instance?.TryGetPurchasePrice(key, out price) ?? false; } private static void ApplyDonationPrice(FullQuestBase quest) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)quest == (Object)null || QuestTargetsField == null || !VanillaDonationPrices.TryGetValue(((Object)quest).name ?? string.Empty, out var value)) { return; } int count = value; if (TryGetPrice("donation:" + ((Object)quest).name, out var price)) { count = price; } if (!(QuestTargetsField.GetValue(quest) is QuestTarget[] array)) { return; } for (int i = 0; i < array.Length; i++) { QuestTarget val = array[i]; if (val.Counter is QuestTargetCurrency) { val.Count = count; array[i] = val; } } } private static bool IsExactPlinneyCostAction(FsmStateAction action) { if (action != null && (Object)(object)action.Owner != (Object)null && action.Fsm != null && action.State != null && string.Equals(GameManager.instance?.sceneName ?? string.Empty, "Belltown_Room_pinsmith", StringComparison.OrdinalIgnoreCase) && string.Equals(((Object)action.Owner).name, "Plinney Inside", StringComparison.Ordinal) && string.Equals(action.Fsm.Name, "Dialogue", StringComparison.Ordinal)) { return string.Equals(action.State.Name, "Upgrade? Cost", StringComparison.Ordinal); } return false; } } internal static class QuestFallbackPatches { [HarmonyPatch(typeof(CollectableItemPickup), "DoPickupAction", new Type[] { typeof(bool) })] private static class RockRollersFallbackPickupPatch { private static void Postfix(CollectableItemPickup __instance, bool __result) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) SaveState instance = SaveState.Instance; if (__result && !((Object)(object)__instance == (Object)null) && instance != null && instance.IsRandomized(ItemType.Quest) && instance.IsLocationEnabled("Quest Completion: Rock Rollers") && instance.IsLocationInSeed("Quest Completion: Rock Rollers") && !instance.IsLocationChecked("Quest Completion: Rock Rollers") && PlayerData.instance != null && PlayerData.instance.blackThreadWorld) { Scene scene = ((Component)__instance).gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, "Bone_10", StringComparison.OrdinalIgnoreCase) && string.Equals(Utils.GetHierarchyPath(((Component)__instance).transform), "Black Thread States Thread Only Variant/Black Thread World/Rock Rollers Quest Not Completed/Collectable Item Pickup Locket", StringComparison.OrdinalIgnoreCase)) { instance.CheckLocation("Quest Completion: Rock Rollers"); } } } } private const string RockRollersLocation = "Quest Completion: Rock Rollers"; private const string RockRollersFallbackPath = "Black Thread States Thread Only Variant/Black Thread World/Rock Rollers Quest Not Completed/Collectable Item Pickup Locket"; } internal static class QuestRequirementPatches { [HarmonyPatch(typeof(FullQuestBase), "get_CanComplete")] private static class SoulSnareCanCompletePatch { [HarmonyPostfix] private static void Postfix(FullQuestBase __instance, ref bool __result) { __result = ApplySoulSnareRequirement(__instance, __result, SaveState.Instance); } } [HarmonyPatch(typeof(FullQuestBase), "get_IsAvailable")] private static class ShakraFinalQuestAvailablePatch { [HarmonyPostfix] private static void Postfix(FullQuestBase __instance, ref bool __result) { __result = ApplyShakraFinalQuestRequirement(__instance, __result, SaveState.Instance, PlayerData.instance); } } private const string SoulSnareQuest = "Soul Snare"; private const string ShakraFinalQuest = "Shakra Final Quest"; private const string SnareSetterItem = "Tool: Snare Setter"; internal static bool ApplySoulSnareRequirement(FullQuestBase quest, bool nativeCanComplete, SaveState state) { if (!nativeCanComplete || (Object)(object)quest == (Object)null || state == null || !state.IsRandomized(ItemType.Tool) || !string.Equals(((Object)quest).name, "Soul Snare", StringComparison.Ordinal)) { return nativeCanComplete; } if (state.receivedItems != null) { return state.receivedItems.Contains(ItemSet.GetCanonicalItemName("Tool: Snare Setter")); } return false; } internal static bool ApplyShakraFinalQuestRequirement(FullQuestBase quest, bool nativeIsAvailable, SaveState state, PlayerData playerData) { if ((Object)(object)quest == (Object)null || state == null || !state.IsRandomized(ItemType.Skill) || !string.Equals(((Object)quest).name, "Shakra Final Quest", StringComparison.Ordinal)) { return nativeIsAvailable; } if (playerData != null && playerData.ShakraFinalQuestAppear) { return state.canDoubleJump; } return false; } } internal static class QuillPatches { [HarmonyPatch(typeof(GameMap), "SetupMap", new Type[] { typeof(bool) })] private static class GameMap_SetupMap_Patch { private static IEnumerable Transpiler(IEnumerable instructions) { return ReplacePlayerDataHasQuillReads(instructions); } } [HarmonyPatch(typeof(PlayerData), "get_CanUpdateMap")] private static class PlayerData_CanUpdateMap_Patch { private static bool Prefix(PlayerData __instance, ref bool __result) { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Skill)) { return true; } __result = CanUseQuill() && __instance.HasAnyMap; return false; } } [HarmonyPatch(typeof(AddScenesMapped), "OnEnter")] private static class AddScenesMapped_OnEnter_Patch { private static IEnumerable Transpiler(IEnumerable instructions) { return ReplacePlayerDataHasQuillReads(instructions); } } [HarmonyPatch(typeof(SetMappedOnStart), "Start")] private static class SetMappedOnStart_Start_Patch { private static IEnumerable Transpiler(IEnumerable instructions) { return ReplacePlayerDataHasQuillReads(instructions); } } private static bool CanUseQuill() { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Skill)) { if (PlayerData.instance != null) { return PlayerData.instance.hasQuill; } return false; } return instance.canUseQuill; } private static bool CanUseQuill(PlayerData playerData) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Skill)) { return instance.canUseQuill; } return playerData?.hasQuill ?? false; } private static IEnumerable ReplacePlayerDataHasQuillReads(IEnumerable instructions) { FieldInfo hasQuillField = AccessTools.Field(typeof(PlayerData), "hasQuill"); MethodInfo hasQuillGetter = typeof(PlayerData).GetProperty("hasQuill", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetGetMethod(nonPublic: true); MethodInfo replacement = AccessTools.Method(typeof(QuillPatches), "CanUseQuill", new Type[1] { typeof(PlayerData) }, (Type[])null); foreach (CodeInstruction instruction in instructions) { if (hasQuillField != null && CodeInstructionExtensions.LoadsField(instruction, hasQuillField, false)) { instruction.opcode = OpCodes.Call; instruction.operand = replacement; } else if (hasQuillGetter != null && CodeInstructionExtensions.Calls(instruction, hasQuillGetter)) { instruction.opcode = OpCodes.Call; instruction.operand = replacement; } yield return instruction; } } } internal static class RuinedToolPatches { private sealed class ArchipelagoRuinedToolLocation : SavedItem { public override void Get(bool showPopup = true) { SaveState instance = SaveState.Instance; if (IsActiveUncheckedLocation(instance)) { instance.CheckLocation("Ruined Tool"); } } public override bool CanGetMore() { return IsActiveUncheckedLocation(SaveState.Instance); } } [HarmonyPatch(typeof(CollectableItemPickup), "Awake")] private static class CollectableItemPickupAwakePatch { [HarmonyPrefix] private static void Prefix(CollectableItemPickup __instance) { SaveState instance = SaveState.Instance; SavedItem nativeItem = (((Object)(object)__instance == (Object)null) ? null : __instance.Item); if (instance != null && instance.IsRandomized(ItemType.Tool) && MatchesNativeSource(__instance, nativeItem) && instance.IsLocationEnabled("Ruined Tool") && instance.IsLocationInSeed("Ruined Tool")) { __instance.SetItem(GetProxyItem(), true); } } } internal const string LocationName = "Ruined Tool"; internal const string NativeAssetName = "Broken SilkShot"; internal const string NativeCraftmetalAssetName = "Tool Metal"; internal const string NativeSceneName = "Shadow_Weavehome"; internal const float NativePositionX = 76.642f; internal const float NativePositionY = 54.577f; private const float SourceMatchTolerance = 0.75f; private static readonly Dictionary RepairLocationByNativeTool = new Dictionary(StringComparer.Ordinal) { { "WebShot Forge", "Tool Unlock: WebShot Forge" }, { "WebShot Architect", "Tool Unlock: WebShot Architect" }, { "WebShot Weaver", "Tool Unlock: WebShot Weaver" } }; private static ArchipelagoRuinedToolLocation proxyItem; internal static bool TryHandleWebShotRepair(SaveState state, string nativeToolName) { if (!RepairLocationByNativeTool.TryGetValue(nativeToolName ?? string.Empty, out var value)) { return false; } if (state != null && state.IsLocationEnabled(value) && state.IsLocationInSeed(value)) { state.CheckLocation(value); } RestoreSharedToolWhileRepairRemains(state); return true; } private static void RestoreSharedToolWhileRepairRemains(SaveState state) { if (state != null && HasRemainingActiveRepair(state)) { CollectableItem itemByName = CollectableItemManager.GetItemByName("Broken SilkShot"); if ((Object)(object)itemByName == (Object)null) { throw new InvalidOperationException("Ruined Tool collectable asset is not ready."); } if (itemByName.CollectedAmount <= 0) { itemByName.Collect(1, false); } CollectableItem itemByName2 = CollectableItemManager.GetItemByName("Tool Metal"); if ((Object)(object)itemByName2 == (Object)null) { throw new InvalidOperationException("Craftmetal collectable asset is not ready."); } itemByName2.Collect(1, false); } } private static bool HasRemainingActiveRepair(SaveState state) { foreach (string value in RepairLocationByNativeTool.Values) { if (state.IsLocationEnabled(value) && state.IsLocationInSeed(value) && !state.IsLocationChecked(value)) { return true; } } return false; } private static bool IsActiveUncheckedLocation(SaveState state) { if (state != null && state.IsRandomized(ItemType.Tool) && state.IsLocationEnabled("Ruined Tool") && state.IsLocationInSeed("Ruined Tool")) { return !state.IsLocationChecked("Ruined Tool"); } return false; } private static SavedItem GetProxyItem() { if ((Object)(object)proxyItem != (Object)null) { return (SavedItem)(object)proxyItem; } proxyItem = ScriptableObject.CreateInstance(); ((Object)proxyItem).name = "Archipelago Location - Ruined Tool"; return (SavedItem)(object)proxyItem; } private static bool MatchesNativeSource(CollectableItemPickup pickup, SavedItem nativeItem) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)pickup == (Object)null) && !((Object)(object)nativeItem == (Object)null)) { Scene scene = ((Component)pickup).gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, "Shadow_Weavehome", StringComparison.OrdinalIgnoreCase) && string.Equals(((Object)nativeItem).name, "Broken SilkShot", StringComparison.Ordinal)) { Vector2 val = Vector2.op_Implicit(((Component)pickup).transform.position) - new Vector2(76.642f, 54.577f); return ((Vector2)(ref val)).sqrMagnitude <= 0.5625f; } } return false; } } internal static class SavePatches { public const string VanillaExtension = ".dat"; public const string SaveExtension = ".randomizersave"; private const string DataExtension = ".randomizerdata"; private const string DataBackupExtension = ".bak"; private const string DataTemporaryExtension = ".tmp"; private static readonly object SaveLock = new object(); internal static void NewGame() { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) SaveState saveState = (SaveState.Instance = new SaveState()); NewRandomizerIntroSkipPatch.ArmForNewRandomizerSave(); RandomizerPlugin.Instance.ClearPendingGameplayQueues(); if (Archipelago.Instance != null && Archipelago.Instance.Connected) { Archipelago.Instance.ResetReceivedItemQueueCursor(); saveState.BindToRoom(Archipelago.Instance); Archipelago.Instance.Resynchronize(); } BellhomePhaseManager.EnsureBellhomeUnlocked(); if (saveState.IsRandomized(ItemType.Crest)) { ToolCrest[] array = Resources.FindObjectsOfTypeAll(); foreach (ToolCrest obj in array) { Data saveData = obj.SaveData; saveData.IsUnlocked = false; obj.SaveData = saveData; } } ((MonoBehaviour)RandomizerPlugin.Instance).StartCoroutine(StartingCrestFix.EnsureUsableStartingCrest()); StartingLocationManager.ScheduleIfNeeded(); } internal static bool Load(int slot, out string error) { error = string.Empty; if (!TryLoadState(slot, out var state, out error)) { return false; } if (state.schemaVersion > 22) { error = "This save uses newer randomizer metadata (schema " + state.schemaVersion + ") than this plugin supports (" + 22 + ")."; return false; } state.InitializeAfterLoad(); if (!state.IsRoomBound) { error = "This save has no room binding and cannot be matched safely to an Archipelago seed. Start a new randomizer save."; return false; } if (!state.HasWorldVersionBinding) { error = GetWorldVersionBindingError(state); return false; } if (!state.HasGoalBinding) { error = "This save has no goal binding and cannot determine its completion condition safely. Start a new randomizer save."; return false; } if (!state.HasStartingCrestBinding) { error = "This save has no starting-crest binding and cannot determine its initial crest safely. Start a new randomizer save."; return false; } if (!state.HasStartingLocationBinding) { error = "This save has no starting-location binding and cannot determine its initial area safely. Start a new randomizer save."; return false; } if (Archipelago.Instance != null && Archipelago.Instance.Connected) { if (!Archipelago.Instance.ValidateLoadedSave(state, out error)) { return false; } SaveState.Instance = state; RandomizerPlugin.Instance.ClearPendingGameplayQueues(); Archipelago.Instance.ResetReceivedItemQueueCursor(); Archipelago.Instance.Resynchronize(); } else { SaveState.Instance = state; RandomizerPlugin.Instance.ClearPendingGameplayQueues(); } SpoolFragmentPatches.RefreshReceivedSpoolHud(); ((MonoBehaviour)RandomizerPlugin.Instance).StartCoroutine(StartingCrestFix.EnsureUsableStartingCrest()); ((MonoBehaviour)RandomizerPlugin.Instance).StartCoroutine(ConsumableToolPatches.SynchronizeReceivedConsumables(state)); StartingLocationManager.ScheduleIfNeeded(); BellhomePhaseManager.EnsureBellhomeUnlocked(); Debug.Log((object)("[Randomizer Save] SaveState loaded: " + GetDataPath(slot))); return true; } internal static bool CanLoad(int slot, out string error) { if (!TryLoadState(slot, out var state, out error)) { return false; } if (state.schemaVersion > 22) { error = "This save uses newer randomizer metadata (schema " + state.schemaVersion + ") than this plugin supports (" + 22 + ")."; return false; } state.InitializeAfterLoad(); if (!state.IsRoomBound) { error = "This save has no room binding and cannot be matched safely to an Archipelago seed. Start a new randomizer save."; return false; } if (!state.HasWorldVersionBinding) { error = GetWorldVersionBindingError(state); return false; } if (!state.HasGoalBinding) { error = "This save has no goal binding and cannot determine its completion condition safely. Start a new randomizer save."; return false; } if (!state.HasStartingCrestBinding) { error = "This save has no starting-crest binding and cannot determine its initial crest safely. Start a new randomizer save."; return false; } if (!state.HasStartingLocationBinding) { error = "This save has no starting-location binding and cannot determine its initial area safely. Start a new randomizer save."; return false; } if (Archipelago.Instance != null && Archipelago.Instance.Connected && !Archipelago.Instance.ValidateLoadedSave(state, out error)) { return false; } return true; } private static string GetWorldVersionBindingError(SaveState loadedState) { string text = ((loadedState == null || string.IsNullOrWhiteSpace(loadedState.worldVersion)) ? "missing" : loadedState.worldVersion); return "This save has APWorld version '" + text + "', but this plugin requires the current APWorld version '0.4.2'. Generate a new seed with the current APWorld and start a new randomizer save."; } internal static bool Save(int slot) { string dataPath = GetDataPath(slot); string text = dataPath + ".tmp"; string text2 = dataPath + ".bak"; lock (SaveLock) { try { if (SaveState.Instance == null) { throw new InvalidOperationException("No active randomizer state exists."); } XmlSerializer xmlSerializer = new XmlSerializer(typeof(SaveState)); Directory.CreateDirectory(Path.GetDirectoryName(dataPath)); using (FileStream fileStream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None)) { using StreamWriter streamWriter = new StreamWriter(fileStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); xmlSerializer.Serialize(streamWriter, SaveState.Instance); streamWriter.Flush(); fileStream.Flush(flushToDisk: true); } if (File.Exists(dataPath)) { File.Replace(text, dataPath, text2); } else { File.Move(text, dataPath); File.Copy(dataPath, text2, overwrite: true); } Debug.Log((object)("[Randomizer Save] SaveState saved: " + dataPath)); return true; } catch (Exception ex) { Debug.LogWarning((object)("[Randomizer Save] Failed to save SaveState. " + ex)); return false; } finally { TryDelete(text); } } } internal static void Delete(int slot) { TryDelete(GetDataPath(slot)); TryDelete(GetDataPath(slot) + ".bak"); TryDelete(GetDataPath(slot) + ".tmp"); } private static bool TryLoadState(int slot, out SaveState state, out string error) { string dataPath = GetDataPath(slot); string text = dataPath + ".bak"; state = null; error = string.Empty; if (!File.Exists(dataPath) && !File.Exists(text)) { error = "Randomizer metadata is missing for slot " + slot + ". The save was not loaded so no blank state could overwrite it."; return false; } Exception error2 = null; if (File.Exists(dataPath) && TryDeserialize(dataPath, out state, out error2)) { return true; } Exception error3 = null; if (File.Exists(text) && TryDeserialize(text, out state, out error3)) { Debug.LogWarning((object)("[Randomizer Save] Recovered metadata from backup: " + text)); return true; } error = "Randomizer metadata for slot " + slot + " is unreadable. The save was blocked; primary error: " + ((error2 == null) ? "missing" : error2.Message) + "; backup error: " + ((error3 == null) ? "missing" : error3.Message); return false; } private static bool TryDeserialize(string path, out SaveState state, out Exception error) { state = null; error = null; try { XmlSerializer xmlSerializer = new XmlSerializer(typeof(SaveState)); using (FileStream stream = File.OpenRead(path)) { state = xmlSerializer.Deserialize(stream) as SaveState; } if (state == null) { throw new InvalidDataException("The file deserialized to null."); } return true; } catch (Exception ex) { error = ex; return false; } } private static void TryDelete(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch (Exception ex) { Debug.LogWarning((object)("[Randomizer Save] Failed to delete " + path + ": " + ex.Message)); } } internal static string GetDataPath(int slot) { return Path.Combine(Application.persistentDataPath, "user" + slot + ".randomizerdata"); } internal static MethodBase GetSaveFileNameMethod() { Type type = AccessTools.Inner(typeof(Platform), "SaveSlotFileNameUsage"); if (type != null) { return AccessTools.Method(typeof(Platform), "GetSaveSlotFileName", new Type[2] { typeof(int), type }, (Type[])null); } return AccessTools.Method(typeof(Platform), "GetSaveSlotFileName", new Type[1] { typeof(int) }, (Type[])null); } internal static MethodBase GetWriteSaveSlotMethod() { return AccessTools.Method(typeof(DesktopPlatform), "WriteSaveSlot", new Type[3] { typeof(int), typeof(byte[]), typeof(Action) }, (Type[])null); } } [HarmonyPatch(typeof(GameManager), "CreateSaveGameData", new Type[] { typeof(int) })] internal static class RestoreTemporaryTrapBeforeSavePatch { private static void Prefix() { SlabCaptureWarpSafety.PrepareForSave(); LogicAuditCloakManager.PrepareForSave(); TrapManager.PrepareForSave(); BellhomePhaseManager.EnsureBellhomeUnlocked(); } private static void Postfix() { TrapManager.ResumeAfterSave(); LogicAuditCloakManager.ResumeAfterSave(); } } [HarmonyPatch(typeof(GameManager), "StartNewGame", new Type[] { typeof(bool), typeof(bool) })] internal static class StartNewGamePatch { private static bool Prefix(out bool __state) { __state = Archipelago.Instance != null && Archipelago.Instance.Connected; if (__state) { return true; } RandomizerPlugin.Instance.ReportBlockingError("Connect to Archipelago before starting a new randomizer save. Offline play is available after that save has been bound once."); return false; } private static void Postfix(bool __state) { if (__state) { SavePatches.NewGame(); } } } [HarmonyPatch(typeof(GameManager), "LoadGame", new Type[] { typeof(int), typeof(Action) })] internal static class ValidateLoadGamePatch { private static bool Prefix(int saveSlot, Action callback) { if (SavePatches.CanLoad(saveSlot, out var error)) { return true; } RandomizerPlugin.Instance.ReportBlockingError(error); callback?.Invoke(obj: false); return false; } } [HarmonyPatch(typeof(GameManager), "SetLoadedGameData", new Type[] { typeof(SaveGameData), typeof(int) })] internal static class LoadGamePatch { private static bool Prefix(int saveSlot, out bool __state) { __state = false; if (SavePatches.CanLoad(saveSlot, out var error)) { __state = true; return true; } RandomizerPlugin.Instance.ReportBlockingError(error); return false; } private static void Postfix(SaveGameData saveGameData, int saveSlot, bool __state) { if (__state && !SavePatches.Load(saveSlot, out var error)) { RandomizerPlugin.Instance.ReportBlockingError(error); } } } [HarmonyPatch] internal static class WriteSaveSlotPatch { private static MethodBase TargetMethod() { return SavePatches.GetWriteSaveSlotMethod(); } private static void Prefix(int slotIndex, ref Action callback) { Action originalCallback = callback; callback = delegate(bool successful) { bool flag = successful && SavePatches.Save(slotIndex); originalCallback?.Invoke(successful && flag); }; } } [HarmonyPatch] internal static class SaveFileNamePatch { private static MethodBase TargetMethod() { return SavePatches.GetSaveFileNameMethod(); } private static void Postfix(int __0, ref string __result) { if (!string.IsNullOrEmpty(__result)) { __result = __result.Replace(".dat", ".randomizersave"); } } } [HarmonyPatch(typeof(DesktopPlatform), "ClearSaveSlot", new Type[] { typeof(int), typeof(Action) })] internal static class ClearRandomizerSavePatch { private static void Prefix(int slotIndex, ref Action callback) { Action originalCallback = callback; callback = delegate(bool successful) { if (successful) { SavePatches.Delete(slotIndex); } originalCallback?.Invoke(successful); }; } } [HarmonyPatch] internal static class RandomizerRestoreDirectoryPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(SaveRestoreHandler), "GetDirectoryName", new Type[1] { typeof(int) }, (Type[])null); } private static void Postfix(ref string __result) { if (!string.IsNullOrEmpty(__result) && !__result.StartsWith("Randomizer_", StringComparison.Ordinal)) { __result = "Randomizer_" + __result; } } } [HarmonyPatch] internal static class RandomizerVersionBackupPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(SaveRestoreHandler), "GetVersionedBackupName", new Type[1] { typeof(int) }, (Type[])null); } private static void Postfix(ref string __result) { if (!string.IsNullOrEmpty(__result) && !__result.StartsWith("randomizer_", StringComparison.Ordinal)) { __result = "randomizer_" + __result; } } } internal static class ScroungeRelicTurnInPatches { [HarmonyPatch(/*Could not decode attribute arguments.*/)] private static class ScroungeRewardAmountPatch { private static bool Prefix(CollectableItemRelicType __instance, ref int __result) { if (!IsIndividualTurnInRelicType(__instance)) { return true; } __result = 0; return false; } } [HarmonyPatch(typeof(RelicBoardOwnerYesNo), "DoOpen")] private static class ScroungePromptScopePatch { private static void Prefix(RelicBoardOwnerYesNo __instance, out bool __state) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) int num; if (IsEnabled() && __instance != null && (Object)(object)((FsmStateAction)__instance).Owner != (Object)null) { Scene scene = ((FsmStateAction)__instance).Owner.scene; num = (IsTurnInOwner(((Scene)(ref scene)).name, ((Object)((FsmStateAction)__instance).Owner).name) ? 1 : 0); } else { num = 0; } __state = (byte)num != 0; if (__state) { relicTurnInPromptScopeDepth++; } } private static Exception Finalizer(Exception __exception, bool __state) { if (__state && relicTurnInPromptScopeDepth > 0) { relicTurnInPromptScopeDepth--; } return __exception; } } [HarmonyPatch(typeof(DialogueYesNoBox), "Open", new Type[] { typeof(Action), typeof(Action), typeof(bool), typeof(string), typeof(IReadOnlyList), typeof(IReadOnlyList), typeof(bool), typeof(bool), typeof(SavedItem) })] private static class ScroungeConfirmationPromptPatch { private static void Prefix([HarmonyArgument(3)] ref string text, [HarmonyArgument(4)] IReadOnlyList items, [HarmonyArgument(5)] IReadOnlyList amounts) { if (relicTurnInPromptScopeDepth <= 0 || items == null || items.Count == 0) { return; } for (int i = 0; i < items.Count; i++) { if (!(items[i] is CollectableItemRelicType)) { return; } } int num = 0; if (amounts != null) { for (int j = 0; j < amounts.Count; j++) { num += Math.Max(0, amounts[j]); } } text = ((num == 1) ? "Turn in this relic as an Archipelago check?" : "Turn in these relics as Archipelago checks?"); } } [ThreadStatic] private static int relicTurnInPromptScopeDepth; private static bool IsEnabled() { return SaveState.Instance?.individualRelicTurnIns ?? false; } private static bool IsTurnInOwner(string sceneName, string objectName) { if (!string.Equals(sceneName, "Belltown_Room_Relic", StringComparison.Ordinal) || !string.Equals(objectName, "Relic Dealer NPC", StringComparison.Ordinal)) { if (string.Equals(sceneName, "Library_08", StringComparison.Ordinal)) { return string.Equals(objectName, "Librarian", StringComparison.Ordinal); } return false; } return true; } private static bool IsIndividualTurnInRelicType(CollectableItemRelicType relicType) { if (!IsEnabled() || (Object)(object)relicType == (Object)null || relicType.Relics == null) { return false; } foreach (CollectableRelic relic in relicType.Relics) { if ((Object)(object)relic != (Object)null && (ScroungeRelicTurnInManifest.TryGetLocationName(((Object)relic).name, out var locationName) || CardiniusCylinderTurnInManifest.TryGetLocationName(((Object)relic).name, out locationName))) { return true; } } return false; } } internal class ShopPatches { private sealed class PendingShopHintRefresh { internal ShopMenuStock ShopMenu; internal SaveState State; internal Archipelago Client; internal string RoomSeed; internal int Team; internal int Slot; internal HashSet LocationNames; internal bool IsSilentPreview; internal DateTime StartedUtc; internal Task> Request; } private sealed class CompletedShopHintRefresh { internal PendingShopHintRefresh Registration; internal Dictionary Hints; } [HarmonyPatch(typeof(ShopMenuStock), "SetStock", new Type[] { typeof(ShopItem[]) })] internal static class ShopMenuStock_SetStock_PreviewPrefetch_Patch { [HarmonyPostfix] private static void Postfix(ShopMenuStock __instance) { PrefetchAvailableStock(__instance); } } [HarmonyPatch(typeof(ShopMenuStock), "DisplayCurrencyCounters")] internal static class ShopMenuStock_DisplayCurrencyCounters_HintReveal_Patch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(ShopMenuStock __instance) { RevealAvailableStock(__instance); } } [HarmonyPatch(typeof(ShopItem), "SetPurchased", new Type[] { typeof(Action), typeof(int) })] internal static class ShopItem_SetPurchased_Quill_Patch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(ShopItem __instance, ref Action onComplete) { SaveState state = SaveState.Instance; if (state == null || (Object)(object)__instance == (Object)null || !string.Equals(((Object)__instance).name, "Mapper Quill", StringComparison.Ordinal) || !state.IsRandomized(ItemType.Skill) || !state.IsLocationEnabled("Item: Quill") || !state.IsLocationInSeed("Item: Quill") || state.IsLocationChecked("Item: Quill")) { return; } Action originalOnComplete = onComplete; onComplete = delegate { if (!state.IsLocationChecked("Item: Quill")) { state.CheckLocation("Item: Quill"); } originalOnComplete?.Invoke(); }; } } [HarmonyPatch(typeof(ShopItem), "get_IsAvailable")] internal static class ShopItem_IsAvailable_Patch { private static bool Prefix(ShopItem __instance, ref bool __result) { if (!IsRandomizedTarget(__instance)) { return true; } if (__instance.IsToolItem()) { __result = !__instance.IsPurchased; return false; } if (((Object)__instance).name == "Mapper Quill") { __result = !__instance.IsPurchased; return false; } return true; } } [HarmonyPatch(typeof(ShopItem), "get_IsPurchased")] internal static class ShopItem_IsPurchased_Patch { private static bool Prefix(ShopItem __instance, ref bool __result) { if (!IsRandomizedTarget(__instance)) { return true; } if (__instance.IsToolItem()) { object value = Traverse.Create((object)__instance).Field("savedItem").GetValue(); SavedItem val = (SavedItem)((value is SavedItem) ? value : null); if ((Object)(object)val == (Object)null) { return true; } string locationName = LocationSet.GetCanonicalLocationName("Tool Unlock: " + ((Object)val).name); if (SaveState.Instance.locations.Locations.FirstOrDefault((Location f) => f.Name == locationName) != null) { __result = SaveState.Instance.IsLocationChecked(locationName); return false; } } if (((Object)__instance).name == "Mapper Quill") { __result = SaveState.Instance.IsLocationChecked("Item: Quill"); return false; } return true; } } [HarmonyPatch(typeof(ShopItem), "get_DisplayName")] internal static class ShopItem_DisplayName_Patch { private static bool Prefix(ShopItem __instance, ref string __result) { if (!TryResolveShopPreviewLocation(__instance, out var locationName)) { return true; } if (TryGetPresentationHint(locationName, out var user, out var item, out var _)) { __result = user + "'s " + item; } else { __result = "AP Item"; } return false; } } [HarmonyPatch(typeof(ShopItem), "get_Description")] internal static class ShopItem_Description_Patch { private static bool Prefix(ShopItem __instance, ref string __result) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected I4, but got Unknown if (!TryResolveShopPreviewLocation(__instance, out var locationName)) { return true; } if (TryGetPresentationHint(locationName, out var user, out var item, out var flags)) { __result = user + "'s " + item + ".\r\n"; switch ((int)flags) { case 0: __result += "Seems not important."; break; case 1: __result += "It is very important!"; break; case 2: __result += "Seems useful."; break; case 4: __result += "Seems fun..."; break; } } else { __result = "Something for someone else, maybe..."; } return false; } } [HarmonyPatch(typeof(ShopItem), "get_ItemSprite")] internal static class ShopItem_ItemSprite_Patch { private static bool Prefix(ShopItem __instance, ref Sprite __result) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (!TryResolveShopPreviewLocation(__instance, out var locationName)) { return true; } RandomizerPlugin instance = RandomizerPlugin.Instance; if ((Object)(object)instance == (Object)null) { return true; } if (TryGetPresentationHint(locationName, out var _, out var _, out var flags)) { __result = instance.GetItemClassificationIcon(flags); } else { __result = instance.MapCheckIcon ?? instance.ArchipelagoIcon; } return false; } } private static readonly HashSet InteractionRevealedLocations = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary SilentPreviewHints = new Dictionary(StringComparer.OrdinalIgnoreCase); private static SaveState shopSessionState; private static Archipelago shopSessionClient; private static string shopSessionSeed; private static int shopSessionTeam = -1; private static int shopSessionSlot = -1; private const int StaleShopRequestSeconds = 15; [ThreadStatic] private static int presentationRefreshDepth; private static readonly Dictionary PendingShopHintRefreshes = new Dictionary(); private static readonly Dictionary PendingShopPreviewPrefetches = new Dictionary(); private static readonly object CompletedShopHintRefreshesLock = new object(); private static readonly Queue CompletedShopHintRefreshes = new Queue(); private static readonly HashSet DeferredShopDetailRefreshes = new HashSet(); private static void SynchronizeShopSession() { SaveState instance = SaveState.Instance; Archipelago instance2 = Archipelago.Instance; if (shopSessionState != instance || shopSessionClient != instance2 || instance2 == null || !instance2.Connected || !string.Equals(shopSessionSeed, instance2.RoomSeed, StringComparison.Ordinal) || shopSessionTeam != instance2.Team || shopSessionSlot != instance2.Slot) { shopSessionState = instance; shopSessionClient = instance2; shopSessionSeed = ((instance2 != null && instance2.Connected) ? instance2.RoomSeed : null); shopSessionTeam = ((instance2 != null && instance2.Connected) ? instance2.Team : (-1)); shopSessionSlot = ((instance2 != null && instance2.Connected) ? instance2.Slot : (-1)); InteractionRevealedLocations.Clear(); SilentPreviewHints.Clear(); PendingShopHintRefreshes.Clear(); PendingShopPreviewPrefetches.Clear(); DeferredShopDetailRefreshes.Clear(); } } internal static bool CanRequestHint(ShopItem shopItem) { SynchronizeShopSession(); if (shopSessionState != null && shopSessionClient != null && shopSessionClient.Connected && (Object)(object)shopItem != (Object)null && presentationRefreshDepth == 0 && TryResolveShopPreviewLocation(shopItem, out var locationName)) { return InteractionRevealedLocations.Contains(LocationSet.GetCanonicalLocationName(locationName)); } return false; } private static void RevealAvailableStock(ShopMenuStock shopMenu) { SynchronizeShopSession(); if (shopSessionState == null || shopSessionClient == null || !shopSessionClient.Connected || (Object)(object)shopMenu == (Object)null) { return; } ShopMenuStock obj = shopMenu.MasterList ?? shopMenu; HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (ShopItem item in obj.EnumerateStock()) { if ((Object)(object)item != (Object)null && item.IsAvailable && TryResolveShopPreviewLocation(item, out var locationName)) { string canonicalLocationName = LocationSet.GetCanonicalLocationName(locationName); InteractionRevealedLocations.Add(canonicalLocationName); hashSet.Add(canonicalLocationName); } } RequestShopHintRefresh(shopMenu, hashSet); } private static void PrefetchAvailableStock(ShopMenuStock shopMenu) { SynchronizeShopSession(); if (presentationRefreshDepth != 0 || shopSessionState == null || shopSessionClient == null || !shopSessionClient.Connected || (Object)(object)shopMenu == (Object)null) { return; } ShopMenuStock obj = shopMenu.MasterList ?? shopMenu; HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (ShopItem item in obj.EnumerateStock()) { if ((Object)(object)item != (Object)null && item.IsAvailable && TryResolveShopPreviewLocation(item, out var locationName)) { hashSet.Add(LocationSet.GetCanonicalLocationName(locationName)); } } RequestShopPreviewPrefetch(shopMenu, hashSet); } private static void RequestShopHintRefresh(ShopMenuStock shopMenu, IEnumerable locationNames) { BeginShopHintRequest(shopMenu, locationNames, isSilentPreview: false); } private static void RequestShopPreviewPrefetch(ShopMenuStock shopMenu, IEnumerable locationNames) { BeginShopHintRequest(shopMenu, locationNames, isSilentPreview: true); } private static void BeginShopHintRequest(ShopMenuStock shopMenu, IEnumerable locationNames, bool isSilentPreview) { SaveState saveState = shopSessionState; Archipelago archipelago = shopSessionClient; Dictionary dictionary = (isSilentPreview ? PendingShopPreviewPrefetches : PendingShopHintRefreshes); if ((Object)(object)shopMenu == (Object)null || saveState == null || archipelago == null || !archipelago.Connected) { if (shopMenu != null) { dictionary.Remove(shopMenu); } return; } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (string locationName in locationNames) { string canonicalLocationName = LocationSet.GetCanonicalLocationName(locationName); if (!string.IsNullOrWhiteSpace(canonicalLocationName) && !saveState.GetHint(canonicalLocationName, out var _, out var _, out var _, allowNetworkRequest: false) && (!isSilentPreview || !SilentPreviewHints.ContainsKey(canonicalLocationName))) { hashSet.Add(canonicalLocationName); } } PendingShopHintRefresh value; if (hashSet.Count == 0) { dictionary.Remove(shopMenu); } else if (!dictionary.TryGetValue(shopMenu, out value) || value.State != saveState || value.Client != archipelago || !string.Equals(value.RoomSeed, archipelago.RoomSeed, StringComparison.Ordinal) || value.Team != archipelago.Team || value.Slot != archipelago.Slot || !value.LocationNames.SetEquals(hashSet) || !(DateTime.UtcNow - value.StartedUtc < TimeSpan.FromSeconds(15.0))) { Task> request = archipelago.RequestHintsAsync(hashSet, (HintCreationPolicy)((!isSilentPreview) ? 2 : 0), isSilentPreview ? "shop preview" : "opened shop hints"); PendingShopHintRefresh registration = (dictionary[shopMenu] = new PendingShopHintRefresh { ShopMenu = shopMenu, State = saveState, Client = archipelago, RoomSeed = archipelago.RoomSeed, Team = archipelago.Team, Slot = archipelago.Slot, LocationNames = hashSet, IsSilentPreview = isSilentPreview, StartedUtc = DateTime.UtcNow, Request = request }); AwaitShopHintsAsync(registration); } } private static async Task AwaitShopHintsAsync(PendingShopHintRefresh registration) { Dictionary hints; try { hints = await registration.Request.ConfigureAwait(continueOnCapturedContext: false); } catch (Exception ex) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Shop hint request failed: " + ex.Message)); } hints = new Dictionary(StringComparer.OrdinalIgnoreCase); } lock (CompletedShopHintRefreshesLock) { CompletedShopHintRefreshes.Enqueue(new CompletedShopHintRefresh { Registration = registration, Hints = hints }); } } internal static void ProcessQueuedHintRefreshes() { while (true) { CompletedShopHintRefresh completion; lock (CompletedShopHintRefreshesLock) { if (CompletedShopHintRefreshes.Count == 0) { break; } completion = CompletedShopHintRefreshes.Dequeue(); goto IL_0035; } IL_0035: ProcessCompletedHintRefresh(completion); } ProcessDeferredShopDetailRefreshes(); } private static void ProcessCompletedHintRefresh(CompletedShopHintRefresh completion) { PendingShopHintRefresh registration = completion.Registration; bool flag = SaveState.Instance == registration.State; bool flag2 = Archipelago.Instance == registration.Client && registration.Client.Connected && string.Equals(registration.Client.RoomSeed, registration.RoomSeed, StringComparison.Ordinal) && registration.Client.Team == registration.Team && registration.Client.Slot == registration.Slot; bool flag3 = false; if (flag && flag2 && completion.Hints != null) { foreach (KeyValuePair hint in completion.Hints) { if (hint.Value != null) { string canonicalLocationName = LocationSet.GetCanonicalLocationName(hint.Key); if (registration.IsSilentPreview) { SilentPreviewHints[canonicalLocationName] = hint.Value; } else { registration.State.CacheHint(hint.Value); } flag3 = true; } } } Dictionary dictionary = (registration.IsSilentPreview ? PendingShopPreviewPrefetches : PendingShopHintRefreshes); int num; if (registration.ShopMenu != null && dictionary.TryGetValue(registration.ShopMenu, out var value)) { num = ((value == registration) ? 1 : 0); if (num != 0) { dictionary.Remove(registration.ShopMenu); } } else { num = 0; } if (((uint)num & (flag ? 1u : 0u) & (flag2 ? 1u : 0u) & (flag3 ? 1u : 0u)) != 0) { RefreshShopPresentation(registration.ShopMenu); } } private static void RefreshShopPresentation(ShopMenuStock shopMenu) { if ((Object)(object)shopMenu == (Object)null || (Object)(object)((Component)shopMenu).gameObject == (Object)null || !((Component)shopMenu).gameObject.activeInHierarchy) { return; } presentationRefreshDepth++; try { ShopMenuStock val = shopMenu.MasterList ?? shopMenu; if (val != shopMenu) { val.SpawnStock(); } shopMenu.BuildItemList(); PlayMakerFSM val2 = FindItemListControl(shopMenu); if (((val2 != null) ? val2.Fsm : null) != null && string.Equals(val2.Fsm.ActiveStateName, "Idle", StringComparison.Ordinal)) { val2.Fsm.SetState("Get Details"); } else if (((val2 != null) ? val2.Fsm : null) != null) { DeferredShopDetailRefreshes.Add(shopMenu); } } catch (Exception ex) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Failed to refresh completed shop hints: " + ex)); } } finally { presentationRefreshDepth--; } } private static PlayMakerFSM FindItemListControl(ShopMenuStock shopMenu) { if (!((Object)(object)shopMenu == (Object)null)) { return ((IEnumerable)((Component)shopMenu).GetComponents()).FirstOrDefault((Func)((PlayMakerFSM fsm) => string.Equals(fsm.FsmName, "Item List Control", StringComparison.Ordinal))); } return null; } private static void ProcessDeferredShopDetailRefreshes() { ShopMenuStock[] array = DeferredShopDetailRefreshes.ToArray(); foreach (ShopMenuStock val in array) { if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null || !((Component)val).gameObject.activeInHierarchy) { DeferredShopDetailRefreshes.Remove(val); continue; } PlayMakerFSM val2 = FindItemListControl(val); if (((val2 != null) ? val2.Fsm : null) == null) { DeferredShopDetailRefreshes.Remove(val); } else { if (!string.Equals(val2.Fsm.ActiveStateName, "Idle", StringComparison.Ordinal)) { continue; } presentationRefreshDepth++; try { val2.Fsm.SetState("Get Details"); DeferredShopDetailRefreshes.Remove(val); } catch (Exception ex) { DeferredShopDetailRefreshes.Remove(val); ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Failed to redraw completed shop details: " + ex.Message)); } } finally { presentationRefreshDepth--; } } } } internal static bool TryGetPresentationHint(string locationName, out string user, out string item, out ItemFlags flags) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected I4, but got Unknown user = null; item = null; flags = (ItemFlags)0; SynchronizeShopSession(); string canonicalLocationName = LocationSet.GetCanonicalLocationName(locationName); if (shopSessionState == null || string.IsNullOrWhiteSpace(canonicalLocationName)) { return false; } if (shopSessionState.GetHint(canonicalLocationName, out user, out item, out flags, allowNetworkRequest: false)) { return true; } if (!SilentPreviewHints.TryGetValue(canonicalLocationName, out var value) || value == null) { return false; } user = value.user; item = value.item; flags = (ItemFlags)(int)value.flags; return true; } private static bool IsRandomizedTarget(ShopItem shopItem) { SaveState instance = SaveState.Instance; if (instance == null || (Object)(object)shopItem == (Object)null) { return false; } if (!shopItem.IsToolItem() || !instance.IsRandomized(GetToolItemType(shopItem))) { if (((Object)shopItem).name == "Mapper Quill") { return instance.IsRandomized(ItemType.Skill); } return false; } return true; } private static bool TryResolveShopPreviewLocation(ShopItem shopItem, out string locationName) { bool flag = TryResolveToolAndQuillPreviewLocation(shopItem, out locationName); if (!flag) { flag = CoreLocationPatches.TryResolveShopHintLocation(shopItem, out locationName); } SaveState instance = SaveState.Instance; if (!flag || instance == null || string.IsNullOrWhiteSpace(locationName)) { locationName = null; return false; } locationName = LocationSet.GetCanonicalLocationName(locationName); if (!instance.IsLocationEnabled(locationName) || !instance.IsLocationInSeed(locationName)) { locationName = null; return false; } return true; } private static bool TryResolveToolAndQuillPreviewLocation(ShopItem shopItem, out string locationName) { locationName = null; if (!IsRandomizedTarget(shopItem)) { return false; } if (shopItem.IsToolItem()) { object value = Traverse.Create((object)shopItem).Field("savedItem").GetValue(); SavedItem val = (SavedItem)((value is SavedItem) ? value : null); if ((Object)(object)val == (Object)null) { return false; } locationName = LocationSet.GetCanonicalLocationName("Tool Unlock: " + ((Object)val).name); return true; } if (string.Equals(((Object)shopItem).name, "Mapper Quill", StringComparison.Ordinal)) { locationName = "Item: Quill"; return true; } return false; } private static ItemType GetToolItemType(ShopItem shopItem) { object value = Traverse.Create((object)shopItem).Field("savedItem").GetValue(); SavedItem val = (SavedItem)((value is SavedItem) ? value : null); switch (((Object)(object)val == (Object)null) ? string.Empty : ((Object)val).name) { case "Silk Spear": case "Parry": case "Silk Boss Needle": case "Silk Charge": case "Silk Bomb": case "Thread Sphere": return ItemType.Spell; default: return ItemType.Tool; } } } internal class SilkHeartPatches { [HarmonyPatch(typeof(PlayMakerFSM), "Start")] private static class SilkHeartSequencePatch { [HarmonyPrefix] [HarmonyPriority(0)] private static void Prefix(PlayMakerFSM __instance) { //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).gameObject == (Object)null) { return; } if (IsBellBeastSource(__instance)) { if (IsActive("Silk Heart: Bell Beast") && PatchBellBeastSource(__instance)) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogInfo((object)"[RANDOMIZER] Bell Beast Silk Heart memory was shortened through the native Bone_05 return and redirected to the AP check."); } } return; } if (IsBellBeastReturnHeart(__instance)) { if (IsActive("Silk Heart: Bell Beast") && PatchBellBeastReturnRecovery(__instance)) { ManualLogSource log2 = RandomizerPlugin.Log; if (log2 != null) { log2.LogInfo((object)"[RANDOMIZER] Bell Beast Silk Heart return now uses the native no-regeneration rise path."); } } return; } if (TryGetLaterBossReturnLocation(__instance, out var locationName)) { if (IsActive(locationName) && PatchBellBeastReturnRecovery(__instance)) { ManualLogSource log3 = RandomizerPlugin.Log; if (log3 != null) { log3.LogInfo((object)("[RANDOMIZER] Silk Heart return now uses the native no-regeneration rise path: " + locationName + ".")); } } return; } Dictionary heartLocationsByScene = HeartLocationsByScene; Scene scene = ((Component)__instance).gameObject.scene; if (!heartLocationsByScene.TryGetValue(((Scene)(ref scene)).name, out var value) || !string.Equals(__instance.FsmName, "Heart Container Control", StringComparison.Ordinal) || !IsActive(value)) { return; } if (!IsSupportedHeartObject(((Object)((Component)__instance).gameObject).name)) { ManualLogSource log4 = RandomizerPlugin.Log; if (log4 != null) { string[] obj = new string[7] { "[RANDOMIZER] Silk Heart source patch failed closed at ", null, null, null, null, null, null }; scene = ((Component)__instance).gameObject.scene; obj[1] = ((Scene)(ref scene)).name; obj[2] = "/"; obj[3] = ((Object)((Component)__instance).gameObject).name; obj[4] = "/"; obj[5] = __instance.FsmName; obj[6] = ": the shipped Heart Piece Instant object identity did not match."; log4.LogWarning((object)string.Concat(obj)); } } else if (PatchSequence(__instance, value)) { ManualLogSource log5 = RandomizerPlugin.Log; if (log5 != null) { log5.LogInfo((object)("[RANDOMIZER] Silk Heart presentation skipped and redirected to the AP check: " + value + ".")); } } } } private sealed class SkipHeartPresentationAction : FsmStateAction { public override void OnEnter() { ((FsmStateAction)this).Finish(); } } private sealed class CompleteHeartLocationAction : FsmStateAction { internal readonly string LocationName; internal readonly bool RestoreHero; internal CompleteHeartLocationAction(string locationName, bool restoreHero) { LocationName = locationName; RestoreHero = restoreHero; } public override void OnEnter() { try { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.SilkHeart) && instance.IsLocationEnabled(LocationName) && instance.IsLocationInSeed(LocationName)) { instance.CheckLocation(LocationName); } } catch (Exception ex) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogError((object)("[RANDOMIZER] Failed to complete Silk Heart AP check " + LocationName + ": " + ex)); } } finally { if (RestoreHero) { RestoreHeroAfterSkippedPresentation(); } ((FsmStateAction)this).Finish(); } } } private sealed class CompleteBellBeastSourceAction : FsmStateAction { public override void OnEnter() { try { PlayerData obj = PlayerData.instance ?? throw new InvalidOperationException("PlayerData was unavailable."); obj.defeatedBellBeast = true; obj.bonebottomQuestBoardFixed = true; SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.SilkHeart) && instance.IsLocationEnabled("Silk Heart: Bell Beast") && instance.IsLocationInSeed("Silk Heart: Bell Beast")) { instance.CheckLocation("Silk Heart: Bell Beast"); } } catch (Exception ex) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogError((object)("[RANDOMIZER] Failed to complete the Bell Beast Silk Heart return: " + ex)); } } finally { ((FsmStateAction)this).Finish(); } } } [HarmonyPatch(typeof(PlayerData), "get_CurrentSilkRegenMax", new Type[] { })] internal static class PlayerData_CurrentSilkRegenMax_Patch { private static void Prefix(out int __state) { __state = PlayerData.instance.silkRegenMax; if (SaveState.Instance != null && SaveState.Instance.IsRandomized(ItemType.SilkHeart)) { PlayerData.instance.silkRegenMax = Math.Max(0, Math.Min(3, SaveState.Instance.silkHeartLevel)); } } private static Exception Finalizer(Exception __exception, int __state) { PlayerData.instance.silkRegenMax = __state; return __exception; } } private const string HeartFsmName = "Heart Container Control"; private const string HeartObjectName = "Heart Piece Instant"; private const string BellBeastSourceScene = "Bone_05_boss"; private const string BellBeastSourceObject = "Silk Heart"; private const string BellBeastSourceFsmName = "Control"; private const string BellBeastMemoryScene = "Memory_Silk_Heart_BellBeast"; private const string BellBeastReturnScene = "Bone_05"; private const string BellBeastReturnGate = "door_cinematicEnd"; private const string BellBeastLocation = "Silk Heart: Bell Beast"; private const string UnravelledReturnScene = "Ward_02"; private const string UnravelledLocation = "Silk Heart: The Unravelled"; private const string LaceTowerReturnScene = "Song_Tower_01"; private const string LaceTowerLocation = "Silk Heart: Lace (Cradle)"; private static readonly Dictionary LaterBossReturnLocationsByScene = new Dictionary(StringComparer.Ordinal) { { "Ward_02", "Silk Heart: The Unravelled" }, { "Song_Tower_01", "Silk Heart: Lace (Cradle)" } }; private static readonly Dictionary HeartLocationsByScene = new Dictionary(StringComparer.Ordinal) { { "Memory_Silk_Heart_BellBeast", "Silk Heart: Bell Beast" }, { "Memory_Silk_Heart_WardBoss", "Silk Heart: The Unravelled" }, { "Memory_Silk_Heart_LaceTower", "Silk Heart: Lace (Cradle)" } }; private static bool IsSupportedHeartObject(string objectName) { if (!string.Equals(objectName, "Heart Piece Instant", StringComparison.Ordinal)) { return string.Equals(objectName, "Heart Piece Instant(Clone)", StringComparison.Ordinal); } return true; } private static bool IsBellBeastSource(PlayMakerFSM fsm) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)fsm != (Object)null && (Object)(object)((Component)fsm).gameObject != (Object)null) { Scene scene = ((Component)fsm).gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, "Bone_05_boss", StringComparison.Ordinal) && string.Equals(((Object)((Component)fsm).gameObject).name, "Silk Heart", StringComparison.Ordinal)) { return string.Equals(fsm.FsmName, "Control", StringComparison.Ordinal); } } return false; } private static bool IsBellBeastReturnHeart(PlayMakerFSM fsm) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)fsm != (Object)null && (Object)(object)((Component)fsm).gameObject != (Object)null) { Scene scene = ((Component)fsm).gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, "Bone_05", StringComparison.Ordinal) && string.Equals(((Object)((Component)fsm).gameObject).name, "Silk Heart", StringComparison.Ordinal)) { return string.Equals(fsm.FsmName, "Control", StringComparison.Ordinal); } } return false; } private static bool TryGetLaterBossReturnLocation(PlayMakerFSM fsm, out string locationName) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) locationName = null; if ((Object)(object)fsm != (Object)null && (Object)(object)((Component)fsm).gameObject != (Object)null && string.Equals(((Object)((Component)fsm).gameObject).name, "Silk Heart", StringComparison.Ordinal) && string.Equals(fsm.FsmName, "Control", StringComparison.Ordinal)) { Dictionary laterBossReturnLocationsByScene = LaterBossReturnLocationsByScene; Scene scene = ((Component)fsm).gameObject.scene; return laterBossReturnLocationsByScene.TryGetValue(((Scene)(ref scene)).name, out locationName); } return false; } private static bool PatchBellBeastReturnRecovery(PlayMakerFSM fsm) { //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) Fsm fsm2 = fsm.Fsm; FsmState upPause = ((fsm2 != null) ? fsm2.GetState("Up Pause") : null); Fsm fsm3 = fsm.Fsm; FsmState val = ((fsm3 != null) ? fsm3.GetState("Regen Last Silk") : null); Fsm fsm4 = fsm.Fsm; FsmState heroUp = ((fsm4 != null) ? fsm4.GetState("Hero Up") : null); Fsm fsm5 = fsm.Fsm; FsmState heroUpMemoryFirst = ((fsm5 != null) ? fsm5.GetState("Hero Up Memory 1st") : null); Fsm fsm6 = fsm.Fsm; FsmState heroUpMemorySecond = ((fsm6 != null) ? fsm6.GetState("Hero Up Memory 2") : null); Fsm fsm7 = fsm.Fsm; FsmState getUpSoundTriggers = ((fsm7 != null) ? fsm7.GetState("Get Up Sound Triggers") : null); Fsm fsm8 = fsm.Fsm; FsmState playAudio = ((fsm8 != null) ? fsm8.GetState("Play Audio") : null); Fsm fsm9 = fsm.Fsm; FsmState end = ((fsm9 != null) ? fsm9.GetState("End") : null); Fsm fsm10 = fsm.Fsm; FsmState collected = ((fsm10 != null) ? fsm10.GetState("Collected") : null); IntCompare val2 = (IntCompare)((((val != null) ? val.Actions : null) != null && val.Actions.Length == 5) ? /*isinst with value type is only supported in some contexts*/: null); if (MatchesBellBeastReturnRecovery(upPause, val, heroUp, heroUpMemoryFirst, heroUpMemorySecond, getUpSoundTriggers, playAudio, end, collected, val2, patched: true)) { return true; } if (!MatchesBellBeastReturnRecovery(upPause, val, heroUp, heroUpMemoryFirst, heroUpMemorySecond, getUpSoundTriggers, playAudio, end, collected, val2, patched: false)) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { string[] obj = new string[7] { "[RANDOMIZER] Bell Beast Silk Heart return patch failed closed at ", null, null, null, null, null, null }; Scene scene = ((Component)fsm).gameObject.scene; obj[1] = ((Scene)(ref scene)).name; obj[2] = "/"; obj[3] = ((Object)((Component)fsm).gameObject).name; obj[4] = "/"; obj[5] = fsm.FsmName; obj[6] = ": the shipped prone-recovery sequence no longer matched."; log.LogWarning((object)string.Concat(obj)); } return false; } val2.equal = val2.greaterThan; val2.lessThan = val2.greaterThan; return true; } private static bool MatchesBellBeastReturnRecovery(FsmState upPause, FsmState regenLastSilk, FsmState heroUp, FsmState heroUpMemoryFirst, FsmState heroUpMemorySecond, FsmState getUpSoundTriggers, FsmState playAudio, FsmState end, FsmState collected, IntCompare comparison, bool patched) { if (((upPause != null) ? upPause.Actions : null) == null || ((regenLastSilk != null) ? regenLastSilk.Actions : null) == null || heroUp == null || heroUpMemoryFirst == null || ((heroUpMemorySecond != null) ? heroUpMemorySecond.Actions : null) == null || ((getUpSoundTriggers != null) ? getUpSoundTriggers.Actions : null) == null || ((playAudio != null) ? playAudio.Actions : null) == null || ((end != null) ? end.Actions : null) == null || ((collected != null) ? collected.Actions : null) == null || comparison == null) { return false; } if (upPause.Actions.Length != 3 || !(upPause.Actions[0] is Wait) || !IsBoolTest(upPause.Actions[1], "Continued From Memory", "MEMORY", string.Empty) || !IsBoolTest(upPause.Actions[2], "Stay Kneeling", "NEXT", string.Empty) || regenLastSilk.Actions.Length != 5 || !IsSilkHeartRegenUnblock(regenLastSilk.Actions[0]) || !IsNamedEventSend(regenLastSilk.Actions[1], "WORLD FADER APPEAR") || !IsEventRegister(regenLastSilk.Actions[2], "REGENERATED SILK CHUNK") || !IsSilkRegenMaxRead(regenLastSilk.Actions[3]) || !IsSilkRegenComparison(comparison, patched) || heroUpMemorySecond.Actions.Length != 3 || !(heroUpMemorySecond.Actions[0] is Tk2dPlayAnimationWithEventsV3) || !(heroUpMemorySecond.Actions[1] is PlayRandomAudioClipTable) || !(heroUpMemorySecond.Actions[2] is Tk2dPlayAnimationWait) || getUpSoundTriggers.Actions.Length != 1 || !(getUpSoundTriggers.Actions[0] is Tk2dWatchAnimationEventsV3) || playAudio.Actions.Length != 1 || !(playAudio.Actions[0] is PlayRandomAudioClipTable) || end.Actions.Length != 2 || !IsHeroMessage(end.Actions[0], "RegainControl") || !IsHeroMessage(end.Actions[1], "StartAnimationControl") || collected.Actions.Length != 3 || !IsSilkRegenBlockAction(collected.Actions[0], blocked: false) || !IsHeartCollectedEvent(collected.Actions[1]) || !IsDisableInventoryFalse(collected.Actions[2])) { return false; } if (HasOnlyTransitions(upPause, Tuple.Create("MEMORY", regenLastSilk), Tuple.Create(FsmEvent.Finished.Name, heroUp), Tuple.Create("NEXT", collected)) && HasOnlyTransitions(regenLastSilk, Tuple.Create("CANCEL", heroUpMemorySecond), Tuple.Create("REGENERATED SILK CHUNK", heroUpMemoryFirst)) && HasOnlyTransition(heroUpMemorySecond, FsmEvent.Finished.Name, getUpSoundTriggers) && HasOnlyTransitions(getUpSoundTriggers, Tuple.Create("PLAY AUDIO", playAudio), Tuple.Create(FsmEvent.Finished.Name, end)) && HasOnlyTransition(playAudio, FsmEvent.Finished.Name, getUpSoundTriggers) && HasOnlyTransition(end, FsmEvent.Finished.Name, collected)) { if (collected.Transitions != null) { return collected.Transitions.Length == 0; } return true; } return false; } private static bool IsBoolTest(FsmStateAction action, string variableName, string trueEvent, string falseEvent) { BoolTest val = (BoolTest)(object)((action is BoolTest) ? action : null); if (val != null && val.boolVariable != null && string.Equals(((NamedVariable)val.boolVariable).Name, variableName, StringComparison.Ordinal) && IsEventNamed(val.isTrue, trueEvent) && IsEventNamed(val.isFalse, falseEvent)) { return !val.everyFrame; } return false; } private static bool IsSilkHeartRegenUnblock(FsmStateAction action) { SendMessage val = (SendMessage)(object)((action is SendMessage) ? action : null); if (val != null && val.functionCall != null && string.Equals(val.functionCall.FunctionName, "SetSilkRegenBlockedSilkHeart", StringComparison.Ordinal) && val.functionCall.BoolParameter != null) { return !val.functionCall.BoolParameter.Value; } return false; } private static bool IsNamedEventSend(FsmStateAction action, string eventName) { SendEventByNameV2 val = (SendEventByNameV2)(object)((action is SendEventByNameV2) ? action : null); if (val != null && val.sendEvent != null) { return string.Equals(val.sendEvent.Value, eventName, StringComparison.Ordinal); } return false; } private static bool IsEventRegister(FsmStateAction action, string eventName) { AddEventRegister val = (AddEventRegister)(object)((action is AddEventRegister) ? action : null); if (val != null && val.eventName != null) { return string.Equals(val.eventName.Value, eventName, StringComparison.Ordinal); } return false; } private static bool IsSilkRegenMaxRead(FsmStateAction action) { GetPlayerDataVariable val = (GetPlayerDataVariable)(object)((action is GetPlayerDataVariable) ? action : null); if (val != null && val.VariableName != null && string.Equals(val.VariableName.Value, "CurrentSilkRegenMax", StringComparison.Ordinal) && val.StoreValue != null) { return string.Equals(val.StoreValue.variableName, "Regen Max", StringComparison.Ordinal); } return false; } private static bool IsSilkRegenComparison(IntCompare comparison, bool patched) { if (comparison.integer1 != null && string.Equals(((NamedVariable)comparison.integer1).Name, "Regen Max", StringComparison.Ordinal) && comparison.integer2 != null && comparison.integer2.Value == 1 && IsEventNamed(comparison.greaterThan, "CANCEL") && IsEventNamed(comparison.equal, patched ? "CANCEL" : string.Empty) && IsEventNamed(comparison.lessThan, patched ? "CANCEL" : string.Empty)) { return !comparison.everyFrame; } return false; } private static bool IsEventNamed(FsmEvent fsmEvent, string name) { return string.Equals(((fsmEvent != null) ? fsmEvent.Name : null) ?? string.Empty, name ?? string.Empty, StringComparison.Ordinal); } private static bool PatchBellBeastSource(PlayMakerFSM fsm) { //IL_031b: Unknown result type (might be due to invalid IL or missing references) //IL_0320: Unknown result type (might be due to invalid IL or missing references) //IL_032b: Unknown result type (might be due to invalid IL or missing references) //IL_0337: Unknown result type (might be due to invalid IL or missing references) //IL_033f: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0255: Unknown result type (might be due to invalid IL or missing references) FsmVariables fsmVariables = fsm.FsmVariables; FsmString val = ((fsmVariables != null) ? fsmVariables.FindFsmString("Memory Scene") : null); Fsm fsm2 = fsm.Fsm; FsmState val2 = ((fsm2 != null) ? fsm2.GetState("Memory?") : null); Fsm fsm3 = fsm.Fsm; FsmState fadeAudio = ((fsm3 != null) ? fsm3.GetState("Fade Audio Down") : null); Fsm fsm4 = fsm.Fsm; FsmState val3 = ((fsm4 != null) ? fsm4.GetState("Memory Scene") : null); Fsm fsm5 = fsm.Fsm; FsmState val4 = ((fsm5 != null) ? fsm5.GetState("Set Data") : null); Fsm fsm6 = fsm.Fsm; FsmState val5 = ((fsm6 != null) ? fsm6.GetState("Drop To Place") : null); Fsm fsm7 = fsm.Fsm; FsmState fadeReturn = ((fsm7 != null) ? fsm7.GetState("Fade Return") : null); Fsm fsm8 = fsm.Fsm; FsmState upPause = ((fsm8 != null) ? fsm8.GetState("Up Pause") : null); Fsm fsm9 = fsm.Fsm; FsmState heroUp = ((fsm9 != null) ? fsm9.GetState("Hero Up") : null); Fsm fsm10 = fsm.Fsm; FsmState end = ((fsm10 != null) ? fsm10.GetState("End") : null); Fsm fsm11 = fsm.Fsm; FsmState collected = ((fsm11 != null) ? fsm11.GetState("Collected") : null); if (val != null && string.Equals(val.Value, "Bone_05", StringComparison.Ordinal) && ((val2 != null) ? val2.Actions : null) != null && val2.Actions.Length == 2 && IsSilkRegenBlockAction(val2.Actions[0], blocked: true) && IsMemorySceneCompare(val2.Actions[1]) && ((val4 != null) ? val4.Actions : null) != null && val4.Actions.Length == 2 && IsActivatedTrueAction(val4.Actions[0]) && val4.Actions[1] is CompleteBellBeastSourceAction && HasOnlyTransitions(val2, Tuple.Create("MEMORY", val4), Tuple.Create(FsmEvent.Finished.Name, val4)) && HasOnlyTransition(val4, FsmEvent.Finished.Name, val5) && HasOnlyTransition(val5, FsmEvent.Finished.Name, val3) && MatchesPatchedBellBeastReturn(val3)) { return true; } if (!MatchesBellBeastSourceSequence(fsm, val, val2, fadeAudio, val3, val4, val5, fadeReturn, upPause, heroUp, end, collected)) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { string[] obj = new string[7] { "[RANDOMIZER] Bell Beast Silk Heart source patch failed closed at ", null, null, null, null, null, null }; Scene scene = ((Component)fsm).gameObject.scene; obj[1] = ((Scene)(ref scene)).name; obj[2] = "/"; obj[3] = ((Object)((Component)fsm).gameObject).name; obj[4] = "/"; obj[5] = fsm.FsmName; obj[6] = ": the shipped memory and cleanup sequence no longer matched."; log.LogWarning((object)string.Concat(obj)); } return false; } val.Value = "Bone_05"; FsmTransition obj2 = val2.Transitions.First((FsmTransition transition) => transition != null && transition.FsmEvent != null && string.Equals(transition.FsmEvent.Name, "MEMORY", StringComparison.Ordinal)); obj2.ToState = val4.Name; obj2.ToFsmState = val4; CompleteBellBeastSourceAction completeBellBeastSourceAction = new CompleteBellBeastSourceAction(); ((FsmStateAction)completeBellBeastSourceAction).Init(val4); val4.Actions = (FsmStateAction[])(object)new FsmStateAction[2] { val4.Actions[1], completeBellBeastSourceAction }; val5.Transitions = (FsmTransition[])(object)new FsmTransition[1] { new FsmTransition { FsmEvent = FsmEvent.Finished, ToState = val3.Name, ToFsmState = val3 } }; FsmStateAction obj3 = val3.Actions[6]; ((BeginSceneTransition)((obj3 is BeginSceneTransition) ? obj3 : null)).entryGateName.Value = "door_cinematicEnd"; val3.Actions = val3.Actions.Skip(1).ToArray(); return true; } private static bool MatchesBellBeastSourceSequence(PlayMakerFSM fsm, FsmString memoryScene, FsmState memoryCheck, FsmState fadeAudio, FsmState memoryTransition, FsmState setData, FsmState dropToPlace, FsmState fadeReturn, FsmState upPause, FsmState heroUp, FsmState end, FsmState collected) { if (((fsm != null) ? fsm.Fsm : null) == null || memoryScene == null || !string.Equals(memoryScene.Value, "Memory_Silk_Heart_BellBeast", StringComparison.Ordinal) || ((memoryCheck != null) ? memoryCheck.Actions : null) == null || fadeAudio == null || memoryTransition == null || ((setData != null) ? setData.Actions : null) == null || dropToPlace == null || fadeReturn == null || upPause == null || heroUp == null || ((end != null) ? end.Actions : null) == null || ((collected != null) ? collected.Actions : null) == null) { return false; } if (memoryCheck.Actions.Length != 2 || !IsSilkRegenBlockAction(memoryCheck.Actions[0], blocked: true) || !IsMemorySceneCompare(memoryCheck.Actions[1]) || setData.Actions.Length != 2 || !IsVanillaSilkHeartGrant(setData.Actions[0]) || !IsActivatedTrueAction(setData.Actions[1]) || !MatchesShippedBellBeastMemoryTransition(memoryTransition) || end.Actions.Length != 2 || !IsHeroMessage(end.Actions[0], "RegainControl") || !IsHeroMessage(end.Actions[1], "StartAnimationControl") || collected.Actions.Length != 3 || !IsSilkRegenBlockAction(collected.Actions[0], blocked: false) || !IsHeartCollectedEvent(collected.Actions[1]) || !IsDisableInventoryFalse(collected.Actions[2])) { return false; } if (HasOnlyTransitions(memoryCheck, Tuple.Create("MEMORY", fadeAudio), Tuple.Create(FsmEvent.Finished.Name, setData)) && HasOnlyTransition(setData, FsmEvent.Finished.Name, dropToPlace) && HasOnlyTransition(dropToPlace, FsmEvent.Finished.Name, fadeReturn) && HasOnlyTransition(fadeReturn, FsmEvent.Finished.Name, upPause) && HasOnlyTransitions(upPause, Tuple.Create("MEMORY", fsm.Fsm.GetState("Regen Last Silk")), Tuple.Create(FsmEvent.Finished.Name, heroUp), Tuple.Create("NEXT", collected)) && HasOnlyTransition(heroUp, FsmEvent.Finished.Name, end)) { return HasOnlyTransition(end, FsmEvent.Finished.Name, collected); } return false; } private static bool MatchesShippedBellBeastMemoryTransition(FsmState state) { if (((state != null) ? state.Actions : null) != null && state.Actions.Length == 7) { FsmStateAction obj = state.Actions[0]; StartPreloadingScene val = (StartPreloadingScene)(object)((obj is StartPreloadingScene) ? obj : null); if (val != null && state.Actions[1] is ScreenFader && state.Actions[2] is ClearHeroEffects && state.Actions[3] is Wait && state.Actions[4] is ClearHeroEffects && state.Actions[5] is HeroLockState) { FsmStateAction obj2 = state.Actions[6]; BeginSceneTransition val2 = (BeginSceneTransition)(object)((obj2 is BeginSceneTransition) ? obj2 : null); if (val2 != null && IsMemorySceneVariable(val.SceneName) && IsMemorySceneVariable(val2.sceneName) && val2.entryGateName != null && string.Equals(val2.entryGateName.Value, "door_wakeOnGround", StringComparison.Ordinal)) { if (state.Transitions != null) { return state.Transitions.Length == 0; } return true; } } } return false; } private static bool MatchesPatchedBellBeastReturn(FsmState state) { if (((state != null) ? state.Actions : null) != null && state.Actions.Length == 6 && state.Actions[0] is ScreenFader && state.Actions[1] is ClearHeroEffects && state.Actions[2] is Wait && state.Actions[3] is ClearHeroEffects && state.Actions[4] is HeroLockState) { FsmStateAction obj = state.Actions[5]; BeginSceneTransition val = (BeginSceneTransition)(object)((obj is BeginSceneTransition) ? obj : null); if (val != null && IsMemorySceneVariable(val.sceneName) && val.entryGateName != null && string.Equals(val.entryGateName.Value, "door_cinematicEnd", StringComparison.Ordinal)) { if (state.Transitions != null) { return state.Transitions.Length == 0; } return true; } } return false; } private static bool IsMemorySceneVariable(FsmString variable) { if (variable != null) { return string.Equals(((NamedVariable)variable).Name, "Memory Scene", StringComparison.Ordinal); } return false; } private static bool IsSilkRegenBlockAction(FsmStateAction action, bool blocked) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 HeroControllerMethods val = (HeroControllerMethods)(object)((action is HeroControllerMethods) ? action : null); if (val != null && (int)val.method == 20 && val.parameters != null && val.parameters.Length == 1 && val.parameters[0] != null) { return val.parameters[0].boolValue == blocked; } return false; } private static bool IsMemorySceneCompare(FsmStateAction action) { StringCompare val = (StringCompare)(object)((action is StringCompare) ? action : null); if (val != null && val.stringVariable != null && string.Equals(((NamedVariable)val.stringVariable).Name, "Memory Scene", StringComparison.Ordinal) && val.compareTo != null && string.IsNullOrEmpty(val.compareTo.Value) && val.equalEvent != null && string.Equals(val.equalEvent.Name, FsmEvent.Finished.Name, StringComparison.Ordinal) && val.notEqualEvent != null) { return string.Equals(val.notEqualEvent.Name, "MEMORY", StringComparison.Ordinal); } return false; } private static bool IsVanillaSilkHeartGrant(FsmStateAction action) { CallMethodProper val = (CallMethodProper)(object)((action is CallMethodProper) ? action : null); if (val != null && val.behaviour != null && string.Equals(val.behaviour.Value, "HeroController", StringComparison.Ordinal) && val.methodName != null && string.Equals(val.methodName.Value, "AddToMaxSilkRegen", StringComparison.Ordinal) && val.parameters != null && val.parameters.Length == 1 && val.parameters[0] != null) { return val.parameters[0].intValue == 1; } return false; } private static bool IsActivatedTrueAction(FsmStateAction action) { SetBoolValue val = (SetBoolValue)(object)((action is SetBoolValue) ? action : null); if (val != null && val.boolVariable != null && string.Equals(((NamedVariable)val.boolVariable).Name, "Activated", StringComparison.Ordinal) && val.boolValue != null) { return val.boolValue.Value; } return false; } private static bool IsHeroMessage(FsmStateAction action, string functionName) { SendMessage val = (SendMessage)(object)((action is SendMessage) ? action : null); if (val != null && val.functionCall != null) { return string.Equals(val.functionCall.FunctionName, functionName, StringComparison.Ordinal); } return false; } private static bool IsHeartCollectedEvent(FsmStateAction action) { SendEventToRegister val = (SendEventToRegister)(object)((action is SendEventToRegister) ? action : null); if (val != null && val.eventName != null) { return string.Equals(val.eventName.Value, "HEART COLLECTED", StringComparison.Ordinal); } return false; } private static bool IsDisableInventoryFalse(FsmStateAction action) { SetPlayerDataVariable val = (SetPlayerDataVariable)(object)((action is SetPlayerDataVariable) ? action : null); if (val != null && val.VariableName != null && string.Equals(val.VariableName.Value, "disableInventory", StringComparison.Ordinal) && val.SetValue != null) { return !val.SetValue.boolValue; } return false; } private static bool HasOnlyTransitions(FsmState state, params Tuple[] expected) { FsmState obj = state; if (((obj != null) ? obj.Transitions : null) == null || expected == null || state.Transitions.Length != expected.Length) { return false; } return expected.All((Tuple pair) => pair != null && state.Transitions.Any((FsmTransition transition) => transition != null && transition.FsmEvent != null && string.Equals(transition.FsmEvent.Name, pair.Item1, StringComparison.Ordinal) && pair.Item2 != null && string.Equals(transition.ToState, pair.Item2.Name, StringComparison.Ordinal))); } private static bool IsActive(string locationName) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.SilkHeart) && instance.IsLocationEnabled(locationName)) { return instance.IsLocationInSeed(locationName); } return false; } private static bool PatchSequence(PlayMakerFSM fsm, string locationName) { //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Expected O, but got Unknown //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Expected O, but got Unknown //IL_014d: 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) Fsm fsm2 = fsm.Fsm; FsmState init = ((fsm2 != null) ? fsm2.GetState("Init") : null); Fsm fsm3 = fsm.Fsm; FsmState takeControl = ((fsm3 != null) ? fsm3.GetState("Take Control?") : null); Fsm fsm4 = fsm.Fsm; FsmState val = ((fsm4 != null) ? fsm4.GetState("Get") : null); Fsm fsm5 = fsm.Fsm; FsmState onGround = ((fsm5 != null) ? fsm5.GetState("On Ground?") : null); Fsm fsm6 = fsm.Fsm; FsmState wait = ((fsm6 != null) ? fsm6.GetState("Wait") : null); Fsm fsm7 = fsm.Fsm; FsmState ui = ((fsm7 != null) ? fsm7.GetState("UI") : null); Fsm fsm8 = fsm.Fsm; FsmState val2 = ((fsm8 != null) ? fsm8.GetState("Save") : null); if (val != null && val2 != null && val.Actions != null && val2.Actions != null && val.Actions.Any((FsmStateAction action) => action is SkipHeartPresentationAction) && val2.Actions.Any((FsmStateAction action) => action is CompleteHeartLocationAction completeHeartLocationAction2 && string.Equals(completeHeartLocationAction2.LocationName, locationName, StringComparison.Ordinal))) { return true; } if (!MatchesShippedSequence(fsm, init, takeControl, val, onGround, wait, ui, val2)) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { string[] obj = new string[5] { "[RANDOMIZER] Silk Heart source patch failed closed at ", null, null, null, null }; Scene scene = ((Component)fsm).gameObject.scene; obj[1] = ((Scene)(ref scene)).name; obj[2] = "/"; obj[3] = ((Object)fsm).name; obj[4] = ": the Heart Piece Instant FSM no longer matched the shipped Get/UI/Save layout."; log.LogWarning((object)string.Concat(obj)); } return false; } SkipHeartPresentationAction skipHeartPresentationAction = new SkipHeartPresentationAction(); ((FsmStateAction)skipHeartPresentationAction).Init(val); SendEventToRegister val3 = new SendEventToRegister { eventName = FsmString.op_Implicit("HEART PIECE COLLECTED") }; ((FsmStateAction)val3).Init(val2); CompleteHeartLocationAction completeHeartLocationAction = new CompleteHeartLocationAction(locationName, restoreHero: true); ((FsmStateAction)completeHeartLocationAction).Init(val2); FsmStateAction[] array = (FsmStateAction[])(object)new FsmStateAction[val2.Actions.Length + 2]; Array.Copy(val2.Actions, array, val2.Actions.Length); array[^2] = (FsmStateAction)(object)val3; array[^1] = (FsmStateAction)(object)completeHeartLocationAction; val2.Actions = array; val.Actions = (FsmStateAction[])(object)new FsmStateAction[1] { skipHeartPresentationAction }; val.Transitions = (FsmTransition[])(object)new FsmTransition[1] { new FsmTransition { FsmEvent = FsmEvent.Finished, ToState = val2.Name, ToFsmState = val2 } }; return true; } private static bool MatchesShippedSequence(PlayMakerFSM fsm, FsmState init, FsmState takeControl, FsmState get, FsmState onGround, FsmState wait, FsmState ui, FsmState save) { if (((fsm != null) ? fsm.Fsm : null) == null || ((init != null) ? init.Actions : null) == null || ((takeControl != null) ? takeControl.Actions : null) == null || ((get != null) ? get.Actions : null) == null || onGround == null || wait == null || ((ui != null) ? ui.Actions : null) == null || ((save != null) ? save.Actions : null) == null || fsm.Fsm.GetState("End") != null) { return false; } if (init.Actions.Length != 2 || !(init.Actions[0] is AddHeroInputBlocker) || takeControl.Actions.Length != 4 || !(takeControl.Actions[3] is CallMethodProper) || get.Actions.Length != 14 || ui.Actions.Length != 2 || !(ui.Actions[0] is CreateObject) || !(ui.Actions[1] is CreateObjectV2) || save.Actions.Length != 2 || !(save.Actions[0] is RemoveHeroInputBlocker) || !(save.Actions[1] is SetPlayerDataVariable)) { return false; } if (HasOnlyTransition(get, FsmEvent.Finished.Name, onGround) && HasOnlyTransition(ui, "HEART PIECE SAVE", save)) { if (save.Transitions != null) { return save.Transitions.Length == 0; } return true; } return false; } private static bool HasOnlyTransition(FsmState state, string eventName, FsmState destination) { if (((state != null) ? state.Transitions : null) == null || state.Transitions.Length != 1 || destination == null) { return false; } FsmTransition val = state.Transitions[0]; if (val != null && val.FsmEvent != null && string.Equals(val.FsmEvent.Name, eventName, StringComparison.Ordinal)) { return string.Equals(val.ToState, destination.Name, StringComparison.Ordinal); } return false; } private static void RestoreHeroAfterSkippedPresentation() { PlayerData instance = PlayerData.instance; if (instance != null) { instance.isInvincible = false; instance.disablePause = false; } HeroController instance2 = HeroController.instance; if ((Object)(object)instance2 == (Object)null) { return; } try { instance2.ResetVelocity(); instance2.AffectedByGravity(true); instance2.ResetGravity(); instance2.RegainControl(); instance2.StartAnimationControl(); } catch (Exception ex) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogError((object)("[RANDOMIZER] Silk Heart skip could not fully restore Hornet's gravity/control state: " + ex)); } } } } internal static class SkillInventoryPatches { private sealed class AbilitySnapshot { internal PlayerData PlayerData; internal bool NativeWallJump; internal bool NativeSuperJump; internal bool NativeHarpoonDash; } private sealed class DressesSnapshot { internal PlayerData PlayerData; internal bool NativeBrolly; internal bool NativeDoubleJump; } [HarmonyPatch(typeof(InventoryItemConditional), "Evaluate")] private static class RandomizedAbilityDisplayPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(InventoryItemConditional __instance, out AbilitySnapshot __state) { __state = null; SaveState instance = SaveState.Instance; PlayerData instance2 = PlayerData.instance; if (instance != null && instance2 != null && instance.IsRandomized(ItemType.Skill) && !((Object)(object)__instance == (Object)null) && TestsRandomizedInventoryAbility(__instance.Test)) { __state = new AbilitySnapshot { PlayerData = instance2, NativeWallJump = instance2.hasWalljump, NativeSuperJump = instance2.hasSuperJump, NativeHarpoonDash = instance2.hasHarpoonDash }; instance2.hasWalljump = instance.canWallJump; instance2.hasSuperJump = instance.canSilkSoar; instance2.hasHarpoonDash = instance.canUseHarpoon; } } [HarmonyFinalizer] [HarmonyPriority(0)] private static Exception Finalizer(Exception __exception, AbilitySnapshot __state) { if (__state != null && __state.PlayerData != null) { __state.PlayerData.hasWalljump = __state.NativeWallJump; __state.PlayerData.hasSuperJump = __state.NativeSuperJump; __state.PlayerData.hasHarpoonDash = __state.NativeHarpoonDash; } return __exception; } } [HarmonyPatch(typeof(CollectableItemStates), "GetCurrentStateIndex")] private static class RandomizedDressesStatePatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(CollectableItemStates __instance, ref int __result) { if (TryResolveDressesStateIndex(__instance, out var stateIndex)) { __result = stateIndex; } } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] private static class RandomizedDressesAmountPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(CollectableItemStates __instance, out DressesSnapshot __state) { PrefixDressesEvaluation(__instance, out __state); } [HarmonyFinalizer] [HarmonyPriority(0)] private static Exception Finalizer(Exception __exception, DressesSnapshot __state) { return FinalizeDressesEvaluation(__exception, __state); } } private static bool TestsRandomizedInventoryAbility(PlayerDataTest test) { //IL_001a: 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) //IL_0020: 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) //IL_0037: 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_003e: 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_0047: Unknown result type (might be due to invalid IL or missing references) if (test == null || test.TestGroups == null) { return false; } TestGroup[] testGroups = test.TestGroups; foreach (TestGroup val in testGroups) { if (val.Tests == null) { continue; } Test[] tests = val.Tests; foreach (Test val2 in tests) { if ((int)val2.Type == 0) { string fieldName = val2.FieldName; if (string.Equals(fieldName, "hasWalljump", StringComparison.Ordinal) || string.Equals(fieldName, "hasSuperJump", StringComparison.Ordinal) || string.Equals(fieldName, "hasHarpoonDash", StringComparison.Ordinal)) { return true; } } } } return false; } private static void PrefixDressesEvaluation(CollectableItemStates instance, out DressesSnapshot snapshot) { snapshot = null; SaveState instance2 = SaveState.Instance; PlayerData instance3 = PlayerData.instance; if (instance2 != null && instance3 != null && instance2.IsRandomized(ItemType.Skill) && !((Object)(object)instance == (Object)null) && string.Equals(((Object)instance).name, "Dresses", StringComparison.Ordinal)) { snapshot = new DressesSnapshot { PlayerData = instance3, NativeBrolly = instance3.hasBrolly, NativeDoubleJump = instance3.hasDoubleJump }; instance3.hasBrolly = instance2.canBrolly; instance3.hasDoubleJump = instance2.canDoubleJump; } } private static Exception FinalizeDressesEvaluation(Exception exception, DressesSnapshot snapshot) { if (snapshot != null && snapshot.PlayerData != null) { snapshot.PlayerData.hasBrolly = snapshot.NativeBrolly; snapshot.PlayerData.hasDoubleJump = snapshot.NativeDoubleJump; } return exception; } internal static int ResolveDressesStateIndex(bool hasDriftersCloak, bool hasFaydownCloak) { if (hasFaydownCloak) { if (!hasDriftersCloak) { return 2; } return 3; } return hasDriftersCloak ? 1 : 0; } private static bool TryResolveDressesStateIndex(CollectableItemStates instance, out int stateIndex) { stateIndex = 0; SaveState instance2 = SaveState.Instance; if (instance2 == null || !instance2.IsRandomized(ItemType.Skill) || (Object)(object)instance == (Object)null || !string.Equals(((Object)instance).name, "Dresses", StringComparison.Ordinal)) { return false; } stateIndex = ResolveDressesStateIndex(instance2.canBrolly, instance2.canDoubleJump); return true; } } internal static class SlabCaptureWarpSafety { private const string CloaklessCrestName = "Cloakless"; private const string SlabCaptureRespawnScene = "Slab_03"; private const string SlabScenePrefix = "Slab_"; private const int NativeRestoredCloakOdour = 100; private static bool benchRestoreFailureReported; internal static void Update() { PlayerData instance = PlayerData.instance; if (instance == null || !instance.atBench) { benchRestoreFailureReported = false; return; } NakedTrapManager.Reset(); if (TryRestoreCapturedState(acceptAnySeatedBench: true, out var error)) { benchRestoreFailureReported = false; } else if (!benchRestoreFailureReported) { benchRestoreFailureReported = true; ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] " + error)); } } } internal static void PrepareForSave() { PlayerData instance = PlayerData.instance; if (instance != null && instance.atBench) { NakedTrapManager.Reset(); if (!TryRestoreCapturedState(acceptAnySeatedBench: true, out var error) && !ForceRestoreCapturedStateForSave(acceptAnySeatedBench: true, out var error2)) { throw new InvalidOperationException(error + " " + error2); } } } internal static bool TryRestoreBeforeRecoveryWarp(out string error) { error = string.Empty; if (IsActiveSlabCaptureCrest(PlayerData.instance)) { NakedTrapManager.Reset(); } return TryRestoreCapturedState(acceptAnySeatedBench: false, out error); } private static bool TryRestoreCapturedState(bool acceptAnySeatedBench, out string error) { error = string.Empty; PlayerData instance = PlayerData.instance; if (!IsRestorableSlabCaptureCrest(instance, acceptAnySeatedBench)) { return true; } string previousCrestID = instance.PreviousCrestID; ToolCrest crestByName = ToolItemManager.GetCrestByName(previousCrestID); if ((Object)(object)crestByName == (Object)null) { error = "The Slab recovery could not resolve the crest it confiscated. Finish the native recovery fight."; return false; } if (!ToolPatches.SetRandomizerCrest(crestByName, markTemporary: false) || !string.Equals(instance.CurrentCrestID, previousCrestID, StringComparison.Ordinal)) { error = "The Slab recovery could not safely restore the crest taken by The Slab."; return false; } instance.PreviousCrestID = string.Empty; instance.IsCurrentCrestTemp = false; instance.cloakOdour_slabFly = 100; CurrencyManager.RestoreTempStoredCurrency(); return true; } private static bool ForceRestoreCapturedStateForSave(bool acceptAnySeatedBench, out string error) { error = string.Empty; PlayerData instance = PlayerData.instance; if (!IsRestorableSlabCaptureCrest(instance, acceptAnySeatedBench)) { return true; } string previousCrestID = instance.PreviousCrestID; if (string.IsNullOrEmpty(previousCrestID) || string.Equals(previousCrestID, "Cloakless", StringComparison.Ordinal)) { error = "The Slab bench save had no valid captured crest ID."; return false; } try { ToolPatches.PrepareHeroForCrestChange(); instance.CurrentCrestID = previousCrestID; instance.PreviousCrestID = string.Empty; instance.IsCurrentCrestTemp = false; instance.cloakOdour_slabFly = 100; CurrencyManager.RestoreTempStoredCurrency(); ToolItemManager.RefreshEquippedState(); ToolItemManager.SendEquippedChangedEvent(true); ToolPatches.ResetHeroInputAfterCrestChange(); return true; } catch (Exception ex) { error = "The Slab bench-save fallback failed: " + ex.Message; return false; } } internal static bool IsActiveSlabCaptureCrest(PlayerData playerData) { if (HasSlabCaptureCrestSignature(playerData)) { return IsSlabCaptureContext(playerData); } return false; } private static bool IsRestorableSlabCaptureCrest(PlayerData playerData, bool acceptAnySeatedBench) { if (HasSlabCaptureCrestSignature(playerData)) { if (!IsSlabCaptureContext(playerData)) { if (acceptAnySeatedBench) { return playerData.atBench; } return false; } return true; } return false; } private static bool HasSlabCaptureCrestSignature(PlayerData playerData) { if (playerData != null && string.Equals(playerData.CurrentCrestID, "Cloakless", StringComparison.Ordinal) && !playerData.IsCurrentCrestTemp && !string.IsNullOrEmpty(playerData.PreviousCrestID) && !string.Equals(playerData.PreviousCrestID, "Cloakless", StringComparison.Ordinal) && !TrapManager.IsCursedCrestActive && !NakedTrapManager.IsActive) { return !LogicAuditCloakManager.IsOverrideActive; } return false; } private static bool IsSlabCaptureContext(PlayerData playerData) { if (!string.Equals(playerData.respawnScene, "Slab_03", StringComparison.Ordinal)) { return IsCurrentSceneInSlab(); } return true; } private static bool IsCurrentSceneInSlab() { GameManager silentInstance = GameManager.SilentInstance; string text = ((silentInstance != null) ? silentInstance.GetSceneNameString() : null); if (!string.IsNullOrEmpty(text)) { return text.StartsWith("Slab_", StringComparison.OrdinalIgnoreCase); } return false; } } [HarmonyPatch(typeof(HeroSlabCapture), "ApplyCaptured")] internal static class SlabCaptureTrapOwnershipPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix() { TrapManager.PrepareForNativeSlabCapture(); LogicAuditCloakManager.Reset(); } } [HarmonyPatch(typeof(GameManager), "FixUpSaveState")] internal static class SlabCaptureBeforeNativeSaveFixupPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix() { SlabCaptureWarpSafety.PrepareForSave(); } } internal static class SpoolFragmentPatches { [HarmonyPatch(typeof(SilkSpool), "DrawSpool", new Type[] { typeof(int) })] internal static class SilkSpool_DrawSpool_Patch { [HarmonyPrefix] private static void Prefix() { SynchronizeReceivedSpoolProgress(PlayerData.instance); } } [HarmonyPatch(typeof(PlayMakerFSM), "Start")] internal static class LoosePhysicalSpoolFragmentPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(PlayMakerFSM __instance) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) SaveState instance = SaveState.Instance; if ((Object)(object)__instance == (Object)null || instance == null || !instance.IsRandomized(ItemType.SpoolFragment)) { return; } Scene scene = ((Component)__instance).gameObject.scene; if (!TryGetLoosePhysicalLocation(((Scene)(ref scene)).name, ((Object)__instance).name, __instance.FsmName, out var locationName) || !instance.IsLocationEnabled(locationName) || !instance.IsLocationInSeed(locationName)) { return; } if ((Object)(object)((Component)__instance).GetComponent() == (Object)null) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Physical Spool Fragment patch found " + locationName + " without its PersistentBoolItem.")); } return; } int num = 0; string[] looseSpoolPickupStateNames = LooseSpoolPickupStateNames; foreach (string stateName in looseSpoolPickupStateNames) { FsmState val = FindState(__instance, stateName); if (val != null) { FsmStateAction[] actions = val.Actions; if (actions != null && actions.Length == 1 && actions[0] is CompleteLooseSpoolLocation) { num++; continue; } CompleteLooseSpoolLocation completeLooseSpoolLocation = new CompleteLooseSpoolLocation(locationName); ((FsmStateAction)completeLooseSpoolLocation).Init(val); val.Actions = (FsmStateAction[])(object)new FsmStateAction[1] { completeLooseSpoolLocation }; num++; } } if (num != LooseSpoolPickupStateNames.Length) { ManualLogSource log2 = RandomizerPlugin.Log; if (log2 != null) { log2.LogWarning((object)("[RANDOMIZER] Physical Spool Fragment patch found " + locationName + " but not both shipped pickup states.")); } } } } private sealed class CompleteLooseSpoolLocation : FsmStateAction { private readonly string locationName; internal CompleteLooseSpoolLocation(string locationName) { this.locationName = locationName; } public override void OnEnter() { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.SpoolFragment) || !instance.IsLocationEnabled(locationName) || !instance.IsLocationInSeed(locationName) || (Object)(object)((FsmStateAction)this).Owner == (Object)null) { return; } PersistentBoolItem component = ((FsmStateAction)this).Owner.GetComponent(); if ((Object)(object)component == (Object)null) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Physical Spool Fragment lost its PersistentBoolItem before collection: " + locationName + ".")); } return; } FsmBool val = ((((FsmStateAction)this).Fsm == null) ? null : ((FsmStateAction)this).Fsm.Variables.FindFsmBool("Activated")); if (val != null) { val.Value = true; } component.SetValueOverride(true); ((PersistentItem)(object)component).SaveStateNoCondition(); instance.CheckLocation(locationName); ((FsmStateAction)this).Owner.SetActive(false); } } [HarmonyPatch(typeof(PrefabCollectable), "Get", new Type[] { typeof(bool) })] internal static class PhysicalSpoolFragmentPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(PrefabCollectable __instance) { if (!IsRandomizedSilkSpool(__instance)) { return true; } GameManager instance = GameManager.instance; string sceneName = (((Object)(object)instance == (Object)null) ? null : instance.GetSceneNameString()); if (MaskAndSpoolLocationManifest.TryGetPhysicalLocation(ItemType.SpoolFragment, sceneName, out var locationName)) { SaveState.Instance.CheckLocation(locationName); } return false; } } [HarmonyPatch(typeof(PrefabCollectable), "TryGetPrespawnedItem")] internal static class PhysicalSpoolFragmentPreSpawnPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(PrefabCollectable __instance, ref PreSpawnedItem item, ref bool __result) { if (!IsRandomizedSilkSpool(__instance)) { return true; } item = null; __result = false; return false; } } [HarmonyPatch(typeof(InventoryItemSpoolPieces), "UpdateState", new Type[] { })] internal static class InventoryItemSpoolPieces_UpdateState_Patch { [HarmonyPrefix] private static void Prefix() { SynchronizeReceivedSpoolProgress(PlayerData.instance); } } [HarmonyPatch(typeof(PlayerData), "get_CurrentSilkMaxBasic", new Type[] { })] internal static class PlayerData_CurrentSilkMaxBasic_Patch { private static void Postfix(PlayerData __instance, ref int __result) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.SpoolFragment) && __result == __instance.silkMax) { __result = 9 + CountSpoolFragments() / 2; } } } private const int BaseSilkMax = 9; private const int FragmentsPerSpool = 2; private const int TotalSpoolFragments = 18; private const string SilkSpoolAssetName = "Silk Spool"; private const string LooseSpoolFsmName = "Control"; private static readonly string[] LooseSpoolPickupStateNames = new string[2] { "Get", "Get And Shift Up" }; internal static int CountSpoolFragments() { SaveState instance = SaveState.Instance; if (instance == null) { return 0; } int num = 0; for (int i = 1; i <= 18; i++) { if (instance.receivedItems.Contains("Spool Fragment #" + i)) { num++; } } return num; } internal static bool SynchronizeReceivedSpoolProgress(PlayerData playerData) { SaveState instance = SaveState.Instance; if (playerData == null || instance == null || !instance.IsRandomized(ItemType.SpoolFragment)) { return false; } int num = CountSpoolFragments(); playerData.silkMax = 9 + num / 2; playerData.silkSpoolParts = num % 2; return true; } internal static void RefreshReceivedSpoolHud() { try { if (SynchronizeReceivedSpoolProgress(PlayerData.HasInstance ? PlayerData.instance : null)) { SilkSpool instance = SilkSpool.Instance; if ((Object)(object)instance != (Object)null) { instance.DrawSpool(); } } } catch (Exception ex) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Spool Fragment was applied, but native spool progress/HUD reconciliation failed: " + ex)); } } } private static bool IsRandomizedSilkSpool(PrefabCollectable item) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.SpoolFragment) && (Object)(object)item != (Object)null) { return string.Equals(((Object)item).name, "Silk Spool", StringComparison.Ordinal); } return false; } private static bool TryGetLoosePhysicalLocation(string sceneName, string gameObjectName, string fsmName, out string locationName) { locationName = null; if (!string.Equals(gameObjectName, "Silk Spool", StringComparison.Ordinal) || !string.Equals(fsmName, "Control", StringComparison.Ordinal)) { return false; } return MaskAndSpoolLocationManifest.TryGetPhysicalLocation(ItemType.SpoolFragment, sceneName, out locationName); } private static FsmState FindState(PlayMakerFSM fsm, string stateName) { FsmState[] array = (((Object)(object)fsm == (Object)null) ? null : fsm.FsmStates); if (array == null) { return null; } FsmState[] array2 = array; foreach (FsmState val in array2) { if (val != null && string.Equals(val.Name, stateName, StringComparison.Ordinal)) { return val; } } return null; } } internal static class StartingCrestFix { internal static bool NeedsRepair(string currentCrestId) { return string.Equals(currentCrestId, "Cloakless", StringComparison.OrdinalIgnoreCase); } internal static bool NeedsOwnershipRepair(string currentCrestId) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Crest) && CrestNames.IsHunterInternalName(currentCrestId) && !string.Equals(currentCrestId, "Hunter", StringComparison.OrdinalIgnoreCase)) { return !instance.receivedItems.Contains("Crest: Hunter"); } return false; } private static bool NeedsRandomizerRepair(string currentCrestId) { if (!NeedsRepair(currentCrestId)) { return NeedsOwnershipRepair(currentCrestId); } return true; } internal static string GetRepairCrest(string startingCrest) { string internalCrestName = CrestNames.GetInternalCrestName(startingCrest); if (!string.IsNullOrWhiteSpace(internalCrestName)) { return internalCrestName; } return "Hunter"; } internal static bool IsCrestRuntimeReady() { if ((Object)(object)HeroController.instance != (Object)null && PlayerData.instance != null && (Object)(object)GameManager.instance != (Object)null) { return (Object)(object)GameManager.instance.gameMap != (Object)null; } return false; } public static IEnumerator EnsureUsableStartingCrest() { while (!IsCrestRuntimeReady() || SaveState.Instance == null || string.IsNullOrWhiteSpace(PlayerData.instance.CurrentCrestID)) { yield return null; } ToolPatches.EnsureReceivedBaseCrestsUnlocked(); ToolPatches.RemoveUnreceivedSilkspearFromCrests(); ToolPatches.RemoveAutomaticCompassFromCrests(); TravelPatches.ApplyBellwayAccessOption(); while (!SimpleKeyDoorManager.TrySynchronizeReceivedKeys()) { yield return null; } while (SaveState.Instance.IsRandomized(ItemType.Tool) && (!ItemGrants.TrySynchronizeProgressiveDruidsEyeEquips() || !ItemGrants.TrySynchronizeProgressiveToolEquips())) { yield return null; } MaskShardsPatches.SynchronizeReceivedMaskShards(refillNewMask: false); while (SaveState.Instance.IsRandomized(ItemType.Relic) && !CoreLocationPatches.TrySynchronizeReceivedRelics()) { yield return null; } if (SlabCaptureWarpSafety.IsActiveSlabCaptureCrest(PlayerData.instance) || !NeedsRandomizerRepair(PlayerData.instance.CurrentCrestID)) { yield break; } string crestName = (SaveState.Instance.IsRandomized(ItemType.Crest) ? GetRepairCrest(SaveState.Instance.startingCrest) : "Hunter"); while (IsCrestRuntimeReady() && NeedsRandomizerRepair(PlayerData.instance.CurrentCrestID)) { try { if (Utils.ForceCrest(crestName) && !NeedsRandomizerRepair(PlayerData.instance.CurrentCrestID)) { Debug.Log((object)("[RANDOMIZER] Repaired unusable or unowned crest with: " + crestName)); break; } } catch (Exception ex) { Debug.LogWarning((object)("[RANDOMIZER] Starting crest is not ready yet; retrying: " + ex.Message)); } yield return (object)new WaitForSecondsRealtime(0.25f); } } } internal static class StaticMapPatches { private enum Resolution { PassThrough, Intercept, Block } [HarmonyPatch(typeof(PlayerDataCollectable), "Get", new Type[] { typeof(bool) })] internal static class PlayerDataCollectableGetPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(PlayerDataCollectable __instance) { SaveState state; StaticMapManifest.Entry entry; switch (Resolve(__instance, out state, out entry)) { case Resolution.PassThrough: return true; case Resolution.Intercept: if (!state.IsLocationChecked(entry.LocationName)) { state.CheckLocation(entry.LocationName); } break; } return false; } } [HarmonyPatch(typeof(PlayerDataCollectable), "CanGetMore")] internal static class PlayerDataCollectableCanGetMorePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(PlayerDataCollectable __instance, ref bool __result) { SaveState state; StaticMapManifest.Entry entry; Resolution resolution = Resolve(__instance, out state, out entry); if (resolution == Resolution.PassThrough) { return true; } __result = resolution == Resolution.Intercept && !state.IsLocationChecked(entry.LocationName); return false; } } [HarmonyPatch(typeof(DeactivateIfPlayerdataTrue), "ForceEvaluate")] internal static class CradleFallbackVisibilityPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(DeactivateIfPlayerdataTrue __instance) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance != (Object)null && StaticMapPatches.cradleEvaluationInstance == __instance) { return false; } SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Map) && instance.IsLocationEnabled("Map Purchase: The Cradle") && instance.IsLocationInSeed("Map Purchase: The Cradle") && !((Object)(object)__instance == (Object)null) && !((Object)(object)((Component)__instance).gameObject == (Object)null)) { Scene scene = ((Component)__instance).gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, "Tube_Hub", StringComparison.OrdinalIgnoreCase)) { if (!string.Equals(Utils.GetHierarchyPath(((Component)__instance).transform), "Black Thread States/Black Thread World/Collectable Item Pickup", StringComparison.Ordinal) || !HasAncestorNamed(((Component)__instance).transform, "Black Thread World")) { return true; } StaticMapManifest.TryGetByLocationName("Map Purchase: The Cradle", out var entry); if (!TryGetActThreeFallbackSource(entry, out var fallbackSource) || !string.Equals(fallbackSource.SceneName, "Tube_Hub", StringComparison.OrdinalIgnoreCase) || !string.Equals(fallbackSource.HierarchyPath, "Black Thread States/Black Thread World/Collectable Item Pickup", StringComparison.Ordinal)) { ReportBlockingError(entry, "The Cradle Act 3 fallback manifest no longer matches its Tube_Hub source."); return false; } if (DeactivateBoolNameField == null || ObjectToDeactivateField == null) { ReportBlockingError(entry, "The Cradle fallback's visibility fields could not be resolved."); return false; } try { string a = DeactivateBoolNameField.GetValue(__instance) as string; object? value = ObjectToDeactivateField.GetValue(__instance); GameObject val = (GameObject)((value is GameObject) ? value : null); GameObject val2 = (((Object)(object)val != (Object)null) ? val : ((Component)__instance).gameObject); if (!string.Equals(a, entry.PlayerDataBool, StringComparison.Ordinal)) { ReportBlockingError(entry, "The Cradle fallback no longer uses HasCradleMap."); return false; } if ((Object)(object)val2 != (Object)(object)((Component)__instance).gameObject) { goto IL_01c9; } scene = val2.scene; if (!string.Equals(((Scene)(ref scene)).name, fallbackSource.SceneName, StringComparison.OrdinalIgnoreCase) || !string.Equals(Utils.GetHierarchyPath(val2.transform), fallbackSource.HierarchyPath, StringComparison.Ordinal) || !HasAncestorNamed(val2.transform, "Black Thread World")) { goto IL_01c9; } bool flag = !instance.IsLocationChecked(entry.LocationName); if (val2.activeSelf != flag) { DeactivateIfPlayerdataTrue cradleEvaluationInstance = StaticMapPatches.cradleEvaluationInstance; StaticMapPatches.cradleEvaluationInstance = __instance; try { val2.SetActive(flag); } finally { StaticMapPatches.cradleEvaluationInstance = cradleEvaluationInstance; } } goto end_IL_0117; IL_01c9: ReportBlockingError(entry, "The Cradle Act 3 fallback no longer matches its HasCradleMap target."); return false; end_IL_0117:; } catch (Exception ex) { ReportBlockingError(entry, "The Cradle Act 3 fallback could not be evaluated: " + ex.Message); } return false; } } return true; } } private const string CradleFallbackSceneName = "Tube_Hub"; private const string CradleFallbackPath = "Black Thread States/Black Thread World/Collectable Item Pickup"; private const string BlackThreadWorldName = "Black Thread World"; private static readonly FieldInfo LinkedPlayerDataBoolField = AccessTools.Field(typeof(PlayerDataCollectable), "linkedPDBool"); private static readonly FieldInfo IsMapField = AccessTools.Field(typeof(PlayerDataCollectable), "isMap"); private static readonly FieldInfo DeactivateBoolNameField = AccessTools.Field(typeof(DeactivateIfPlayerdataTrue), "boolName"); private static readonly FieldInfo ObjectToDeactivateField = AccessTools.Field(typeof(DeactivateIfPlayerdataTrue), "objectToDeactivate"); private static readonly HashSet ReportedBlockingErrors = new HashSet(StringComparer.Ordinal); [ThreadStatic] private static DeactivateIfPlayerdataTrue cradleEvaluationInstance; private static Resolution Resolve(PlayerDataCollectable collectable, out SaveState state, out StaticMapManifest.Entry entry) { //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) state = SaveState.Instance; entry = null; if (state == null || !state.IsRandomized(ItemType.Map) || (Object)(object)collectable == (Object)null || !StaticMapManifest.TryGetByAssetName(((Object)collectable).name, out entry)) { return Resolution.PassThrough; } if (!state.IsLocationEnabled(entry.LocationName) || !state.IsLocationInSeed(entry.LocationName)) { return Resolution.PassThrough; } if (LinkedPlayerDataBoolField == null || IsMapField == null) { ReportBlockingError(entry, "PlayerDataCollectable's required private fields could not be resolved."); return Resolution.Block; } try { string text = LinkedPlayerDataBoolField.GetValue(collectable) as string; bool flag = Convert.ToBoolean(IsMapField.GetValue(collectable)); if (!flag || !string.Equals(text, entry.PlayerDataBool, StringComparison.Ordinal)) { ReportBlockingError(entry, "Asset metadata changed: expected isMap=true and linkedPDBool='" + entry.PlayerDataBool + "', found isMap=" + flag + " and linkedPDBool='" + (text ?? "") + "'."); return Resolution.Block; } } catch (Exception ex) { ReportBlockingError(entry, "Asset metadata could not be read: " + ex.Message); return Resolution.Block; } Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; if (!entry.HasSourceScene(name)) { ReportBlockingError(entry, "The asset was invoked from unexpected scene '" + (name ?? "") + "'."); return Resolution.Block; } return Resolution.Intercept; } private static void ReportBlockingError(StaticMapManifest.Entry entry, string detail) { string text = "Could not safely randomize '" + (entry?.LocationName ?? "unknown static map") + "'. No vanilla map was granted. " + detail; lock (ReportedBlockingErrors) { if (!ReportedBlockingErrors.Add(text)) { return; } } if ((Object)(object)RandomizerPlugin.Instance != (Object)null) { RandomizerPlugin.Instance.ReportBlockingError(text); } else { Debug.LogError((object)("[RANDOMIZER] " + text)); } } private static bool TryGetActThreeFallbackSource(StaticMapManifest.Entry entry, out StaticMapManifest.Source fallbackSource) { fallbackSource = null; if (entry == null) { return false; } StaticMapManifest.Source[] sources = entry.Sources; foreach (StaticMapManifest.Source source in sources) { if (source != null && source.IsActThreeFallback) { if (fallbackSource != null) { return false; } fallbackSource = source; } } return fallbackSource != null; } private static bool HasAncestorNamed(Transform transform, string expectedName) { Transform val = transform; while ((Object)(object)val != (Object)null) { if (string.Equals(((Object)val).name, expectedName, StringComparison.Ordinal)) { return true; } val = val.parent; } return false; } } public class ToolPatches { [HarmonyPatch(typeof(HeroController), "Update")] internal static class HeroController_Update_CrestInputReset_Patch { [HarmonyPostfix] private static void Postfix(HeroController __instance) { TryApplyPendingHeroInputReset(__instance); } } [HarmonyPatch(typeof(ToolItemManager), "AutoEquip", new Type[] { typeof(ToolCrest), typeof(bool), typeof(bool) })] internal static class ToolItemManager_AutoEquipCrest_Patch { [HarmonyPrefix] private static bool Prefix(ToolCrest crest) { if ((Object)(object)crest == (Object)null) { return false; } SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Crest)) { return true; } ToolCrest cursedCrest = Gameplay.CursedCrest; if (nativeRiteCursedCrestEquipDepth > 0 && (Object)(object)cursedCrest != (Object)null && string.Equals(crest.name, cursedCrest.name, StringComparison.Ordinal)) { return true; } if (crest.name == "Cloakless") { return true; } if (canUnlockedCrestBeEquipped) { return true; } if (!instance.receivedItems.Contains(CrestNames.GetItemNameFromInternal(crest.name))) { return false; } if (CrestNames.IsHunterInternalName(crest.name) && !crest.IsBaseVersion) { if (PlayerData.instance != null) { return CrestNames.IsHunterInternalName(PlayerData.instance.CurrentCrestID); } return false; } return true; } [HarmonyPostfix] private static void Postfix() { RemoveUnreceivedSilkspearFromCrests(); } } [HarmonyPatch(typeof(ToolItemManager), "AutoEquip", new Type[] { typeof(ToolItem) })] internal static class ToolItemManager_AutoEquipTool_Patch { [HarmonyPrefix] private static bool Prefix(ToolItem tool) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Invalid comparison between Unknown and I4 if ((Object)(object)tool == (Object)null) { return true; } SaveState instance = SaveState.Instance; if (IsAutomaticCompassTool(instance, tool)) { return false; } ItemType type = (((int)tool.Type == 3) ? ItemType.Spell : ItemType.Tool); if (instance == null || !instance.IsRandomized(type)) { return true; } Debug.Log((object)("TRIED TO EQUIP " + tool.name)); Debug.Log((object)("WAS ALREADY UNLOCKED: " + tool.IsUnlocked)); if ((int)tool.Type == 3) { SaveState.Instance.CheckLocation("Spell Unlock: " + tool.name); if (!tool.IsUnlocked && !HasReceivedSilkSkill(tool)) { ((MonoBehaviour)RandomizerPlugin.Instance).StartCoroutine(DelayRelock(tool)); } return false; } return true; } } [HarmonyPatch(typeof(ToolItem), "Unlock", new Type[] { typeof(Action), typeof(PopupFlags) })] internal static class ToolItem_Unlock_Patch { [HarmonyPrefix] private static bool Prefix(ToolItem __instance, Action afterTutorialMsg, ref PopupFlags popupFlags) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Invalid comparison between Unknown and I4 string text = ""; if ((Object)(object)__instance != (Object)null && !string.IsNullOrEmpty(__instance.name)) { text = __instance.name; } Debug.Log((object)("[RANDOMIZER] Tried to Unlock: " + text)); SaveState instance = SaveState.Instance; ItemType type = (((Object)(object)__instance != (Object)null && (int)__instance.Type == 3) ? ItemType.Spell : ItemType.Tool); bool flag = IsAutomaticCompassTool(instance, __instance); if (instance == null || (!instance.IsRandomized(type) && !flag)) { return true; } if (ShouldUseNativeShellSatchel(instance, __instance)) { return true; } if (!RuinedToolPatches.TryHandleWebShotRepair(instance, text)) { switch (text) { case "Silk Spear": case "Parry": case "Silk Boss Needle": case "Silk Charge": case "Silk Bomb": case "Thread Sphere": SaveState.Instance.CheckLocation("Spell Unlock: " + text); break; default: SaveState.Instance.CheckLocation("Tool Unlock: " + text); break; } } popupFlags = (PopupFlags)((uint)popupFlags & 0xFFFFFFFCu); return true; } [HarmonyPostfix] private static void Postfix(ToolItem __instance) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Tool) && ItemGrants.TryGetProgressiveToolState(__instance, out var _, out var _, out var _) && !ItemGrants.TrySynchronizeProgressiveToolEquips()) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)"[RANDOMIZER] Progressive tool assets were not ready after native ToolItem.Unlock."); } } } } [HarmonyPatch(typeof(ToolItem), "Lock", new Type[] { })] internal static class ToolItem_Lock_ProgressiveTool_Patch { [HarmonyPrefix] private static bool Prefix(ToolItem __instance) { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Tool) || !ItemGrants.TryGetProgressiveToolState(__instance, out var level, out var requiredLevel, out var isBaseTier)) { return true; } return !(isBaseTier ? (level == 1) : (level >= requiredLevel)); } } [HarmonyPatch(typeof(ToolItem), "GetSavedAmount", new Type[] { })] internal static class ToolItem_GetSavedAmount_ProgressiveTool_Patch { [HarmonyPrefix] private static bool Prefix(ToolItem __instance, ref int __result) { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Tool) || !ItemGrants.TryGetProgressiveToolState(__instance, out var level, out var requiredLevel, out var _)) { return true; } __result = ((level >= requiredLevel) ? 1 : 0); return false; } } [HarmonyPatch(typeof(ToolItem), "get_IsUnlockedNotHidden", new Type[] { })] internal static class ToolItem_IsUnlockedNotHidden_Patch { [HarmonyPrefix] private static bool Prefix(ToolItem __instance, ref bool __result) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Invalid comparison between Unknown and I4 SaveState instance = SaveState.Instance; if (instance == null) { return true; } if (ShouldUseNativeShellSatchel(instance, __instance)) { return true; } if (IsAutomaticCompassTool(instance, __instance)) { __result = false; return false; } ItemType itemType = (((int)__instance.Type == 3) ? ItemType.Spell : ItemType.Tool); if (!instance.IsRandomized(itemType)) { return true; } if (itemType == ItemType.Tool && ItemGrants.TryGetProgressiveToolState(__instance, out var level, out var requiredLevel, out var isBaseTier)) { __result = (isBaseTier ? (level == 1) : (level >= requiredLevel)); return false; } if (itemType == ItemType.Tool && instance.druidsEyeLevel > 0) { if (__instance.name == "Mosscreep Tool 1") { __result = instance.druidsEyeLevel == 1; return false; } if (__instance.name == "Mosscreep Tool 2") { __result = instance.druidsEyeLevel >= 2; return false; } } if ((int)__instance.Type == 3) { __result = instance.receivedItems.Contains(ItemSet.GetCanonicalItemName("Spell: " + __instance.name)); return false; } __result = instance.receivedItems.Contains(ItemSet.GetCanonicalItemName("Tool: " + __instance.name)); return false; } } [HarmonyPatch(typeof(InventoryToolCrest), "get_IsUnlocked", new Type[] { })] internal static class InventoryToolCrest_IsUnlocked_Patch { [HarmonyPrefix] private static bool Prefix(InventoryToolCrest __instance, ref bool __result) { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Crest) || (Object)(object)__instance.CrestData == (Object)null) { return true; } if (!instance.receivedItems.Contains(CrestNames.GetItemNameFromInternal(__instance.CrestData.name))) { __result = false; return false; } return true; } } [HarmonyPatch(typeof(InventoryPane), "get_IsAvailable", new Type[] { })] internal static class InventoryPane_IsAvailable_Patch { [HarmonyPrefix] private static bool Prefix(ref bool __result) { SaveState instance = SaveState.Instance; if (instance == null || (!instance.IsRandomized(ItemType.Tool) && !instance.IsRandomized(ItemType.Spell) && !instance.IsRandomized(ItemType.Crest) && !instance.IsRandomized(ItemType.CrestSlot))) { return true; } __result = true; return false; } } public static bool canUnlockedCrestBeEquipped = false; public static bool canCrestBeUnlockedByRandomizer = false; [ThreadStatic] private static int nativeRiteCursedCrestEquipDepth; private static bool pendingHeroInputReset; private static int pendingHeroInputResetRequestFrame = -1; private static readonly MethodInfo CancelDashMethod = AccessTools.Method(typeof(HeroController), "CancelDash", new Type[1] { typeof(bool) }, (Type[])null); private static bool IsAutomaticCompassTool(SaveState state, ToolItem tool) { if (state != null && state.automaticCompass && (Object)(object)tool != (Object)null) { return tool == Gameplay.CompassTool; } return false; } private static bool ShouldUseNativeShellSatchel(SaveState state, ToolItem tool) { if (state != null && (Object)(object)tool != (Object)null && string.Equals(tool.name, "Shell Satchel", StringComparison.OrdinalIgnoreCase)) { return !state.IsLocationInSeed("Shell Satchel"); } return false; } internal static void EnsureReceivedBaseCrestsUnlocked() { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Crest) || PlayerData.instance == null) { return; } bool flag = canCrestBeUnlockedByRandomizer; canCrestBeUnlockedByRandomizer = true; try { foreach (ToolCrest allCrest in ToolItemManager.GetAllCrests()) { if (!((Object)(object)allCrest == (Object)null) && allCrest.IsBaseVersion && !allCrest.IsUnlocked && instance.receivedItems.Contains(CrestNames.GetItemNameFromInternal(allCrest.name))) { allCrest.Unlock(); } } } finally { canCrestBeUnlockedByRandomizer = flag; } } internal static void RemoveAutomaticCompassFromCrests() { SaveState instance = SaveState.Instance; if (instance != null && instance.automaticCompass && PlayerData.instance != null) { ToolItem compassTool = Gameplay.CompassTool; if ((Object)(object)compassTool != (Object)null) { ToolItemManager.RemoveToolFromAllCrests(compassTool); } } } internal static void RemoveUnreceivedSilkspearFromCrests() { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Spell) || PlayerData.instance == null) { return; } string canonicalItemName = ItemSet.GetCanonicalItemName("Spell: Silk Spear"); if (!instance.receivedItems.Contains(canonicalItemName)) { ToolItem toolByName = ToolItemManager.GetToolByName("Silk Spear"); if ((Object)(object)toolByName != (Object)null) { ToolItemManager.RemoveToolFromAllCrests(toolByName); } } } internal static bool SetRandomizerCrest(ToolCrest crest, bool markTemporary, bool deferRuntimeRefresh = false) { PlayerData instance = PlayerData.instance; if ((Object)(object)crest == (Object)null || instance == null) { return false; } string name = crest.name; if (string.IsNullOrEmpty(name)) { return false; } if (NakedTrapManager.TryDeferRandomizerCrestChange(crest, markTemporary)) { RemoveUnreceivedSilkspearFromCrests(); ResetHeroInputAfterCrestChange(); return true; } if (!string.Equals(instance.CurrentCrestID, name, StringComparison.Ordinal)) { PrepareHeroForCrestChange(); instance.PreviousCrestID = instance.CurrentCrestID; } ToolItemManager.SetEquippedCrest(name); instance.IsCurrentCrestTemp = markTemporary; if (!deferRuntimeRefresh) { ToolItemManager.RefreshEquippedState(); ToolItemManager.SendEquippedChangedEvent(true); } RemoveUnreceivedSilkspearFromCrests(); ResetHeroInputAfterCrestChange(); return string.Equals(instance.CurrentCrestID, name, StringComparison.Ordinal); } internal static bool AutoEquipNativeRiteCursedCrest(ToolCrest crest) { ToolCrest cursedCrest = Gameplay.CursedCrest; if ((Object)(object)crest == (Object)null || (Object)(object)cursedCrest == (Object)null || !string.Equals(crest.name, cursedCrest.name, StringComparison.Ordinal)) { return false; } nativeRiteCursedCrestEquipDepth++; try { ToolItemManager.AutoEquip(crest, false, true); } finally { nativeRiteCursedCrestEquipDepth--; } PlayerData instance = PlayerData.instance; if (instance != null && string.Equals(instance.CurrentCrestID, crest.name, StringComparison.Ordinal)) { return !instance.IsCurrentCrestTemp; } return false; } internal static void PrepareHeroForCrestChange() { HeroController instance = HeroController.instance; if ((Object)(object)instance == (Object)null || instance.cState == null) { return; } bool flag = DoesSprintOwnHeroControl(instance); if (!CanSafelyReleaseCrestMotion(instance, flag)) { return; } if (instance.cState.dashing) { try { if (CancelDashMethod == null) { throw new MissingMethodException(typeof(HeroController).FullName, "CancelDash(bool)"); } CancelDashMethod.Invoke(instance, new object[1] { true }); } catch (Exception ex) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Active dash could not be released before a crest change: " + ex.Message)); } } } if (!flag) { return; } try { instance.sprintFSM.SendEvent("SPRINT CANCEL"); instance.RegainControl(); instance.StartAnimationControlToIdle(); } catch (Exception ex2) { ManualLogSource log2 = RandomizerPlugin.Log; if (log2 != null) { log2.LogWarning((object)("[RANDOMIZER] Active sprint could not be released before a crest change: " + ex2.Message)); } } } private static bool DoesSprintOwnHeroControl(HeroController hero) { if ((Object)(object)hero == (Object)null || hero.cState == null || (Object)(object)hero.sprintFSM == (Object)null || !hero.controlReqlinquished) { return false; } FsmVariables fsmVariables = hero.sprintFSM.FsmVariables; FsmBool val = ((fsmVariables != null) ? fsmVariables.FindFsmBool("Is Sprinting") : null); if (!hero.cState.isSprinting) { if (val != null) { return val.Value; } return false; } return true; } private static bool CanSafelyReleaseCrestMotion(HeroController hero, bool sprintOwnsControl) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Invalid comparison between Unknown and I4 //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Invalid comparison between Unknown and I4 GameManager silentInstance = GameManager.SilentInstance; PlayerData instance = PlayerData.instance; bool flag = !hero.controlReqlinquished && (int)hero.hero_state != 7 && hero.CanInput(); if ((Object)(object)silentInstance != (Object)null && instance != null && (int)silentInstance.GameState == 4 && silentInstance.IsGameplayScene() && !silentInstance.isPaused && !silentInstance.IsLoadingSceneTransition && !silentInstance.IsInSceneTransition && !TransitionPoint.IsTransitionBlocked && !BossSceneController.IsTransitioning && !instance.HasStoredMemoryState && !hero.cState.transitioning && !hero.cState.dead && !hero.cState.hazardDeath && !hero.cState.hazardRespawning) { return flag || sprintOwnsControl; } return false; } internal static void ResetHeroInputAfterCrestChange() { pendingHeroInputReset = true; pendingHeroInputResetRequestFrame = Time.frameCount; } private static void TryApplyPendingHeroInputReset(HeroController hero) { if (pendingHeroInputReset && Time.frameCount > pendingHeroInputResetRequestFrame && CanSafelyNeutralizeHeroInput(hero)) { hero.move_input = 0f; hero.vertical_input = 0f; hero.ClearActionsInputState(); hero.ClearJumpInputState(); pendingHeroInputReset = false; pendingHeroInputResetRequestFrame = -1; } } private static bool CanSafelyNeutralizeHeroInput(HeroController hero) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Invalid comparison between Unknown and I4 GameManager silentInstance = GameManager.SilentInstance; if ((Object)(object)hero != (Object)null && (Object)(object)silentInstance != (Object)null && (int)silentInstance.GameState == 4 && silentInstance.IsGameplayScene() && !hero.controlReqlinquished && (int)hero.hero_state != 7) { return hero.CanInput(); } return false; } private static IEnumerator DelayRelock(ToolItem tool) { while (!tool.IsUnlocked && !HasReceivedSilkSkill(tool)) { yield return null; } if (HasReceivedSilkSkill(tool)) { yield break; } yield return (object)new WaitForSeconds(1f); if (!HasReceivedSilkSkill(tool)) { Debug.Log((object)("Relocked " + tool.name)); tool.Lock(); yield return null; while (!HasReceivedSilkSkill(tool)) { yield return null; } Debug.Log((object)("Reunlocked " + tool.name)); Data savedData = tool.SavedData; savedData.IsHidden = false; tool.SavedData = savedData; tool.Unlock((Action)null, (PopupFlags)3); } } private static bool HasReceivedSilkSkill(ToolItem tool) { SaveState instance = SaveState.Instance; if (instance != null && instance.receivedItems != null && (Object)(object)tool != (Object)null) { return instance.receivedItems.Contains(ItemSet.GetCanonicalItemName("Spell: " + tool.name)); } return false; } } internal static class ToolPouchPatches { [HarmonyPatch(typeof(PlayMakerFSM), "Start")] private static class ToolPouchFsmPatch { [HarmonyPrefix] [HarmonyPriority(0)] private static void Prefix(PlayMakerFSM __instance) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)((Component)__instance).gameObject == (Object)null)) { PatchLoddieReward(__instance); PatchLoddieActThreeFallback(__instance); PatchFleatopiaReward(__instance); } } } private sealed class CompleteToolPouchLocation : FsmStateAction { internal readonly string LocationName; internal CompleteToolPouchLocation(string locationName) { LocationName = locationName; } public override void OnEnter() { SaveState instance = SaveState.Instance; if (IsActive(LocationName) && !instance.IsLocationChecked(LocationName)) { instance.CheckLocation(LocationName); } ((FsmStateAction)this).Finish(); } } private static bool IsActive(string locationName) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.ToolPouch) && instance.IsLocationEnabled(locationName)) { return instance.IsLocationInSeed(locationName); } return false; } private static bool MatchesOwner(PlayMakerFSM fsm, string sceneName, string objectName, string fsmName) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)fsm != (Object)null && (Object)(object)((Component)fsm).gameObject != (Object)null) { Scene scene = ((Component)fsm).gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, sceneName, StringComparison.OrdinalIgnoreCase) && string.Equals(((Object)((Component)fsm).gameObject).name, objectName, StringComparison.Ordinal)) { return string.Equals(fsm.FsmName, fsmName, StringComparison.Ordinal); } } return false; } private static void ReportFailedClosed(string locationName, string detail) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Tool Pouch source '" + locationName + "' was left vanilla because " + detail)); } } private static void PatchLoddieReward(PlayMakerFSM fsm) { string locationName = "Tool Pouch: Loddie"; if (!IsActive(locationName) || !MatchesOwner(fsm, "Bone_12", "Lady Bug Large", "Convo")) { return; } Fsm fsm2 = fsm.Fsm; FsmState val = ((fsm2 != null) ? fsm2.GetState("Reward 1") : null); if (((val != null) ? val.Actions : null) != null && val.Actions.Length == 2 && val.Actions[1] is CompleteToolPouchLocation) { return; } if (((val != null) ? val.Actions : null) != null && val.Actions.Length == 2) { FsmStateAction obj = val.Actions[0]; SetPlayerDataInt val2 = (SetPlayerDataInt)(object)((obj is SetPlayerDataInt) ? obj : null); if (val2 != null && val.Actions[1] is RunFSM) { if (val2.intName == null || val2.value == null || !string.Equals(val2.intName.Value, "pinGalleriesCompleted", StringComparison.Ordinal) || val2.value.Value != 1) { ReportFailedClosed(locationName, "Loddie's completion flag no longer matches the first challenge."); return; } CompleteToolPouchLocation completeToolPouchLocation = new CompleteToolPouchLocation(locationName); ((FsmStateAction)completeToolPouchLocation).Init(val); val.Actions[1] = (FsmStateAction)(object)completeToolPouchLocation; return; } } ReportFailedClosed(locationName, "Loddie's Reward 1 action layout changed."); } private static void PatchFleatopiaReward(PlayMakerFSM fsm) { string locationName = "Tool Pouch: Fleatopia"; if (!IsActive(locationName) || !MatchesOwner(fsm, "Aqueduct_05_caravan", "Caravan Troupe Leader Fleatopia NPC", "Dialogue")) { return; } Fsm fsm2 = fsm.Fsm; FsmState val = ((fsm2 != null) ? fsm2.GetState("Award Tool Pouch") : null); if (((val != null) ? val.Actions : null) == null || val.Actions.Length != 4 || !(val.Actions[2] is CompleteToolPouchLocation)) { if (((val != null) ? val.Actions : null) == null || val.Actions.Length != 4 || !(val.Actions[0] is SetBoolValue) || !(val.Actions[1] is SetBoolValue) || !(val.Actions[2] is RunFSM) || !(val.Actions[3] is Wait)) { ReportFailedClosed(locationName, "Mooshka's Award Tool Pouch action layout changed."); return; } CompleteToolPouchLocation completeToolPouchLocation = new CompleteToolPouchLocation(locationName); ((FsmStateAction)completeToolPouchLocation).Init(val); val.Actions[2] = (FsmStateAction)(object)completeToolPouchLocation; } } private static void PatchLoddieActThreeFallback(PlayMakerFSM fsm) { string locationName = "Tool Pouch: Loddie"; if (!IsActive(locationName) || !MatchesOwner(fsm, "Bone_12", "Ladybug Craft Pickup", "FSM")) { return; } FsmVariables fsmVariables = fsm.FsmVariables; FsmObject val = ((fsmVariables != null) ? fsmVariables.GetFsmObject("Item") : null); SavedItem proxyItem = CollectibleSourcePatches.GetProxyItem(locationName, ItemType.ToolPouch, showGenericPresentation: true); if ((object)((val != null) ? val.Value : null) == proxyItem) { return; } Object obj = ((val != null) ? val.Value : null); SavedItem val2 = (SavedItem)(object)((obj is SavedItem) ? obj : null); if (val2 == null || !string.Equals(((Object)val2).name, "Tool Pouch Pickup", StringComparison.Ordinal)) { ReportFailedClosed(locationName, "the Act 3 fallback no longer contains its exact Tool Pouch item variable."); return; } val.Value = (Object)(object)proxyItem; FsmBool fsmBool = fsm.FsmVariables.GetFsmBool("Awards Tool Pouch"); if (fsmBool != null) { fsmBool.Value = false; } } } [HarmonyPatch(typeof(DarknessRegion), "SetDarknessLevel", new Type[] { typeof(int) })] internal static class DarknessTrapNativeLevelPatch { [HarmonyPrefix] private static void Prefix(ref int __0) { TrapManager.ObserveNativeDarknessRequest(ref __0); } } [HarmonyPatch(typeof(HeroController), "SetIsMaggoted", new Type[] { typeof(bool) })] internal static class MuckmaggotTrapNativeStatusPatch { [HarmonyPrefix] private static void Prefix(bool __0) { TrapManager.ObserveNativeMuckmaggotRequest(__0); } } internal static class TravelPatches { [HarmonyPatch(typeof(FastTravelCutscene), "Start")] private static class StagTravelVideoSkipPatch { [HarmonyPostfix] private static void Postfix(FastTravelCutscene __instance, ref IEnumerator __result) { if (__result != null && IsRandomizerStagTravel(__instance)) { __result = SkipStagTravelVideo(__result); } } } [HarmonyPatch(typeof(InteractableBase), "QueueInteraction")] private static class TollInteractionPriorityRestorePatch { [HarmonyPrefix] private static void Prefix(InteractableBase __instance) { RestoreTollInteractionPriority(__instance); } } [HarmonyPatch] private static class FastTravelMapButtonBase_IsUnlocked_Patch { private static IEnumerable TargetMethods() { Type genericBaseType = typeof(FastTravelMapButtonBase<>); return from type in (from type in AccessTools.AllTypes().Select(FindFastTravelMapButtonBase) where type != null && type.IsGenericType && type.GetGenericTypeDefinition() == genericBaseType && !type.ContainsGenericParameters select type).Distinct() select AccessTools.Method(type, "IsUnlocked", (Type[])null, (Type[])null) into method where method != null select method; } private static Type FindFastTravelMapButtonBase(Type type) { while (type != null && type != typeof(object)) { if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(FastTravelMapButtonBase<>)) { return type; } type = type.BaseType; } return null; } private static bool Prefix(string ___playerDataBool, ref bool __result) { if (string.IsNullOrEmpty(___playerDataBool) || !TryGetRandomizedUnlock(___playerDataBool, out var type, out var isUnlocked)) { return true; } PlayerData instance = PlayerData.instance; bool physicalStationPurchased = type == ItemType.Bellway && BellwayFields.Contains(___playerDataBool) && instance != null && instance.GetBool(___playerDataBool); __result = ShouldUnlockRandomizedDestination(type, isUnlocked, physicalStationPurchased); return false; } } [HarmonyPatch(typeof(FastTravelMapButtonBase), "IsUnlocked")] private static class MarrowFastTravelUnlockPatch { [HarmonyPostfix] private static void Postfix(FastTravelLocations ___targetLocation, ref bool __result) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 SaveState instance = SaveState.Instance; PlayerData instance2 = PlayerData.instance; if (__result && instance != null && instance2 != null && instance.AllowsBellwaysBeforeBellBeast && (int)___targetLocation == 13 && !instance2.defeatedBellBeast) { __result = false; } } } [HarmonyPatch(typeof(PlayerDataBoolTest), "OnEnter")] private static class BellBeastPlayerDataBoolTestPatch { [HarmonyPrefix] private static bool Prefix(PlayerDataBoolTest __instance) { FsmString boolName = __instance.boolName; string playerDataField = ((boolName != null) ? boolName.Value : null) ?? string.Empty; if (!TryGetBellBeastApUnlock((FsmStateAction)(object)__instance, playerDataField, out var isUnlocked)) { return true; } ((FsmStateAction)__instance).Fsm.Event(isUnlocked ? __instance.isTrue : __instance.isFalse); ((FsmStateAction)__instance).Finish(); return false; } } [HarmonyPatch(typeof(SetPlayerDataBool), "OnEnter")] private static class BellBeastArrivalTollInteractionPatch { [HarmonyPostfix] private static void Postfix(SetPlayerDataBool __instance) { if (IsBellBeastArrivalEndAction((FsmStateAction)(object)__instance)) { RearmUnpaidBellwayTollInteraction(); } } } private const string BellBeastObjectName = "Bone Beast NPC"; private const string BellBeastFsmName = "Interaction"; private const string BellBeastArrivalEndState = "Travel Arrive End"; private const string TollMachineObjectName = "Bellway Toll Machine"; private const string TollMachineFsmName = "Unlock Behaviour"; private const string TollMachineUnpaidState = "Inert"; private const string TollMachinePlayerDataVariable = "Pickup PlayerData Bool"; private const string SkipCutsceneVariable = "SkipCutscene"; private static readonly HashSet BellwayFields = new HashSet(StringComparer.Ordinal) { "UnlockedDocksStation", "UnlockedBoneforestEastStation", "UnlockedGreymoorStation", "UnlockedBelltownStation", "UnlockedCoralTowerStation", "UnlockedCityStation", "UnlockedPeakStation", "UnlockedShellwoodStation", "UnlockedShadowStation", "UnlockedAqueductStation" }; private static readonly Dictionary BellwayLocationsByField = new Dictionary(StringComparer.Ordinal) { { "UnlockedDocksStation", "Bellway: Deep Docks" }, { "UnlockedBoneforestEastStation", "Bellway: Far Fields" }, { "UnlockedGreymoorStation", "Bellway: Greymoor" }, { "UnlockedBelltownStation", "Bellway: Bellhart" }, { "UnlockedCoralTowerStation", "Bellway: Blasted Steps" }, { "UnlockedCityStation", "Bellway: Grand Bellway" }, { "UnlockedPeakStation", "Bellway: The Slab" }, { "UnlockedShellwoodStation", "Bellway: Shellwood" }, { "UnlockedShadowStation", "Bellway: Bilewater" }, { "UnlockedAqueductStation", "Bellway: Putrified Ducts" } }; private static readonly MethodInfo HideInteractionMethod = AccessTools.Method(typeof(InteractableBase), "HideInteraction", (Type[])null, (Type[])null); private static readonly FieldInfo IsShowingInteractionField = AccessTools.Field(typeof(InteractableBase), "isShowingInteraction"); private static readonly FieldInfo InteractionPriorityField = AccessTools.Field(typeof(InteractableBase), "priority"); private static readonly Dictionary BoostedTollPriorities = new Dictionary(); private static readonly HashSet VentricaFields = new HashSet(StringComparer.Ordinal) { "UnlockedSongTube", "UnlockedUnderTube", "UnlockedCityBellwayTube", "UnlockedHangTube", "UnlockedEnclaveTube", "UnlockedArboriumTube" }; private static bool TryGetRandomizedUnlock(string playerDataField, out ItemType type, out bool isUnlocked) { isUnlocked = false; if (BellwayFields.Contains(playerDataField)) { type = ItemType.Bellway; } else { if (!VentricaFields.Contains(playerDataField)) { type = ItemType.Unknown; return false; } type = ItemType.Ventrica; } SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(type)) { return false; } FieldInfo field = instance.GetType().GetField(playerDataField, BindingFlags.Instance | BindingFlags.Public); if (field == null || field.FieldType != typeof(bool)) { return false; } isUnlocked = (bool)field.GetValue(instance); return true; } internal static bool ApplyBellwayAccessOption() { SaveState instance = SaveState.Instance; PlayerData instance2 = PlayerData.instance; if (instance == null || instance2 == null || !instance.AllowsBellwaysBeforeBellBeast) { return false; } instance2.UnlockedFastTravel = true; return true; } private static bool IsBellBeastArrivalEndAction(FsmStateAction action) { if (action != null && (Object)(object)action.Owner != (Object)null && string.Equals(((Object)action.Owner).name, "Bone Beast NPC", StringComparison.Ordinal) && action.Fsm != null && string.Equals(action.Fsm.Name, "Interaction", StringComparison.Ordinal) && action.State != null) { return string.Equals(action.State.Name, "Travel Arrive End", StringComparison.Ordinal); } return false; } internal static bool RearmUnpaidBellwayTollInteraction() { //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) SaveState instance = SaveState.Instance; GameManager instance2 = GameManager.instance; if (instance == null || (Object)(object)instance2 == (Object)null || !instance.IsRandomized(ItemType.Bellway)) { return false; } string baseSceneName = GameManager.GetBaseSceneName(instance2.sceneName ?? string.Empty); if (string.IsNullOrEmpty(baseSceneName)) { return false; } bool result = false; PlayMakerFSM[] array = Resources.FindObjectsOfTypeAll(); foreach (PlayMakerFSM val in array) { if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null || !((Component)val).gameObject.activeInHierarchy || !((Object)((Component)val).gameObject).name.StartsWith("Bellway Toll Machine", StringComparison.Ordinal) || !string.Equals(val.FsmName, "Unlock Behaviour", StringComparison.Ordinal) || val.Fsm == null || !string.Equals(val.Fsm.ActiveStateName, "Inert", StringComparison.Ordinal)) { continue; } Scene scene = ((Component)val).gameObject.scene; if (!string.Equals(GameManager.GetBaseSceneName(((Scene)(ref scene)).name), baseSceneName, StringComparison.OrdinalIgnoreCase)) { continue; } FsmVariables fsmVariables = val.FsmVariables; FsmString obj = ((fsmVariables != null) ? fsmVariables.GetFsmString("Pickup PlayerData Bool") : null); string key = ((obj != null) ? obj.Value : null) ?? string.Empty; if (BellwayLocationsByField.TryGetValue(key, out var value) && instance.IsLocationInSeed(value) && !instance.IsLocationChecked(value)) { InteractableBase component = ((Component)val).GetComponent(); string error = null; if ((Object)(object)component == (Object)null || !TryPrioritizeUnpaidTollInteraction(component, out error)) { Debug.LogWarning((object)("[RANDOMIZER] Could not re-arm unpaid " + value + " toll interaction: " + (error ?? "InteractableBase was not found."))); continue; } result = true; Debug.Log((object)("[RANDOMIZER] Prioritized unpaid " + value + " toll interaction after Bell Beast arrival.")); } } return result; } private static bool TryPrioritizeUnpaidTollInteraction(InteractableBase interactable, out string error) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) error = null; if (HideInteractionMethod == null || IsShowingInteractionField == null || InteractionPriorityField == null) { error = "InteractableBase interaction members were not found."; return false; } try { HideInteractionMethod.Invoke(interactable, null); IsShowingInteractionField.SetValue(interactable, false); int instanceID = ((Object)interactable).GetInstanceID(); if (!BoostedTollPriorities.ContainsKey(instanceID)) { BoostedTollPriorities[instanceID] = (InteractPriority)InteractionPriorityField.GetValue(interactable); } InteractionPriorityField.SetValue(interactable, (object)(InteractPriority)2); interactable.Activate(); return true; } catch (TargetInvocationException ex) { RestoreTollInteractionPriority(interactable); Exception ex2 = ex.InnerException ?? ex; error = ex2.GetType().Name + ": " + ex2.Message; return false; } catch (Exception ex3) { RestoreTollInteractionPriority(interactable); error = ex3.GetType().Name + ": " + ex3.Message; return false; } } private static void RestoreTollInteractionPriority(InteractableBase interactable) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)interactable == (Object)null) && !(InteractionPriorityField == null)) { int instanceID = ((Object)interactable).GetInstanceID(); if (BoostedTollPriorities.TryGetValue(instanceID, out var value)) { BoostedTollPriorities.Remove(instanceID); InteractionPriorityField.SetValue(interactable, value); } } } private static bool TryGetBellBeastApUnlock(FsmStateAction action, string playerDataField, out bool isUnlocked) { isUnlocked = false; if (action == null || (Object)(object)action.Owner == (Object)null || !string.Equals(((Object)action.Owner).name, "Bone Beast NPC", StringComparison.Ordinal) || action.Fsm == null || !string.Equals(action.Fsm.Name, "Interaction", StringComparison.Ordinal) || action.State == null || (!string.Equals(action.State.Name, "Can Appear", StringComparison.Ordinal) && !string.Equals(action.State.Name, "Can Appear 2", StringComparison.Ordinal)) || !TryGetRandomizedUnlock(playerDataField, out var type, out isUnlocked) || type != ItemType.Bellway) { isUnlocked = false; return false; } PlayerData instance = PlayerData.instance; isUnlocked = ShouldAllowBellBeastCall(isUnlocked, instance != null && instance.GetBool(playerDataField)); return true; } internal static bool ShouldAllowBellBeastCall(bool apStationUnlocked, bool physicalStationPurchased) { return apStationUnlocked || physicalStationPurchased; } internal static bool ShouldUnlockRandomizedDestination(ItemType type, bool apStationUnlocked, bool physicalStationPurchased) { if (type != ItemType.Bellway) { return apStationUnlocked; } return ShouldAllowBellBeastCall(apStationUnlocked, physicalStationPurchased); } private static IEnumerator SkipStagTravelVideo(IEnumerator nativeRoutine) { bool armedSkip = false; try { while (true) { GameManager instance = GameManager.instance; if (!armedSkip && (Object)(object)instance != (Object)null && !instance.IsInSceneTransition) { StaticVariableList.SetValue("SkipCutscene", (object)true, 0); armedSkip = true; } if (nativeRoutine.MoveNext()) { yield return nativeRoutine.Current; continue; } break; } } finally { if (armedSkip && StaticVariableList.GetValue("SkipCutscene", false)) { StaticVariableList.SetValue("SkipCutscene", (object)false, 0); } (nativeRoutine as IDisposable)?.Dispose(); } } private static bool IsRandomizerStagTravel(FastTravelCutscene cutscene) { SaveState instance = SaveState.Instance; if (cutscene is StagTravel && instance != null) { return instance.IsRoomBound; } return false; } } internal static class VanillaPickupPopupPatches { private readonly struct PopupSource { internal readonly ItemType ItemType; internal readonly string LocationName; internal readonly string SceneName; internal readonly string OwnerName; internal readonly string FsmName; internal readonly string StateName; internal PopupSource(ItemType itemType, string locationName, string sceneName, string ownerName, string fsmName, string stateName) { ItemType = itemType; LocationName = locationName; SceneName = sceneName; OwnerName = ownerName; FsmName = fsmName; StateName = stateName; } internal bool Matches(FsmStateAction action) { //IL_001a: 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 (action != null && (Object)(object)action.Owner != (Object)null) { Scene scene = action.Owner.scene; if (string.Equals(((Scene)(ref scene)).name, SceneName, StringComparison.Ordinal) && string.Equals(((Object)action.Owner).name, OwnerName, StringComparison.Ordinal) && action.Fsm != null && string.Equals(action.Fsm.Name, FsmName, StringComparison.Ordinal) && action.State != null) { return string.Equals(action.State.Name, StateName, StringComparison.Ordinal); } } return false; } } [HarmonyPatch(typeof(PlayerDataCollectable), "Get", new Type[] { typeof(bool) })] internal static class RandomizedMapGetPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(PlayerDataCollectable __instance, bool ___isMap, string ___linkedPDBool, ref bool showPopup, out bool __state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; __state = IsExactActiveMapCollectable(__instance, ___isMap, ___linkedPDBool, name); if (__state || IsExactActiveAbilityCollectable(___isMap, ___linkedPDBool, name)) { if (__state) { randomizedMapGetDepth++; } showPopup = false; } } [HarmonyFinalizer] private static Exception Finalizer(Exception __exception, bool __state) { if (__state) { randomizedMapGetDepth = Math.Max(0, randomizedMapGetDepth - 1); } return __exception; } } [HarmonyPatch(typeof(GameManager), "UpdateGameMapWithPopup", new Type[] { typeof(float) })] internal static class RandomizedMapUpdatePopupPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(GameManager __instance, ref bool __result) { if (randomizedMapGetDepth <= 0) { return true; } __result = __instance.UpdateGameMap(); return false; } } [HarmonyPatch(typeof(SpawnPowerUpGetMsg), "OnEnter")] internal static class RandomizedPowerUpGetMessagePatch { [HarmonyPrefix] private static bool Prefix(SpawnPowerUpGetMsg __instance) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) if (__instance == null || __instance.PowerUp == null || !(__instance.PowerUp.Value is PowerUps powerUp) || !TryGetPowerUpSource(powerUp, out var source) || !ShouldSuppress((FsmStateAction)(object)__instance, source)) { return true; } ((FsmStateAction)__instance).Finish(); return false; } } [HarmonyPatch(typeof(CreateUIMsgGetItem), "OnEnter")] internal static class RandomizedItemGetMessagePatch { private const string GetItemMessageEndedEvent = "GET ITEM MSG END"; [HarmonyPrefix] private static bool Prefix(CreateUIMsgGetItem __instance) { if (!TryGetItemMessageSource(__instance, out var source) || !IsActiveApLocation(source)) { return true; } if (DriftersCloakSource.Matches((FsmStateAction)(object)__instance)) { LocationSet.RestoreDriftersCloakSourceFlag(); SaveState.Instance.CheckLocation(source.LocationName); } ((FsmStateAction)__instance).Finish(); ((FsmStateAction)__instance).Fsm.Event("GET ITEM MSG END"); return false; } } [HarmonyPatch(typeof(SpawnSkillGetMsg), "OnEnter")] internal static class RandomizedSilkSkillGetMessagePatch { private const string SkillGetMessageFadedOutEvent = "SKILL GET MSG FADED OUT"; private const string SkillGetMessageEndedEvent = "SKILL GET MSG ENDED"; [HarmonyPrefix] private static bool Prefix(SpawnSkillGetMsg __instance) { if (!TryGetSilkSkillSource((ToolItemSkill)((__instance == null || __instance.Skill == null) ? null : /*isinst with value type is only supported in some contexts*/), out var source) || !ShouldSuppress((FsmStateAction)(object)__instance, source)) { return true; } if (ParrySource.Matches((FsmStateAction)(object)__instance)) { RandomizerPlugin instance = RandomizerPlugin.Instance; if ((Object)(object)instance == (Object)null) { return true; } ((FsmStateAction)__instance).Finish(); ((MonoBehaviour)instance).StartCoroutine(CompleteCrossStitchSequenceAfterRegisters(((FsmStateAction)__instance).Fsm)); return false; } ((FsmStateAction)__instance).Finish(); return false; } private static IEnumerator CompleteCrossStitchSequenceAfterRegisters(Fsm phantomFsm) { yield return null; if (phantomFsm != null && string.Equals(phantomFsm.ActiveStateName, "UI Msg", StringComparison.Ordinal)) { EventRegister.SendEvent("SKILL GET MSG FADED OUT", (GameObject)null); yield return null; if (phantomFsm != null && string.Equals(phantomFsm.ActiveStateName, "Get Control", StringComparison.Ordinal)) { EventRegister.SendEvent("SKILL GET MSG ENDED", (GameObject)null); } } } } private static readonly PopupSource SprintSource = new PopupSource(ItemType.Skill, "Swift Step", "Bone_East_05", "Shrine Weaver Ability", "Inspection", "Powerup Msg"); private static readonly PopupSource WallJumpSource = new PopupSource(ItemType.Skill, "Cling Grip", "Shellwood_10", "Shrine Weaver Ability", "Inspection", "Powerup Msg"); private static readonly PopupSource HarpoonDashSource = new PopupSource(ItemType.Skill, "Clawline", "Under_18", "Shrine Weaver Ability", "Inspection", "Powerup Msg"); private static readonly PopupSource NeedolinSource = new PopupSource(ItemType.Skill, "Needolin", "Belltown_Shrine", "Spinner Boss", "Control", "Get Needolin"); private static readonly PopupSource SuperJumpSource = new PopupSource(ItemType.Skill, "Silk Soar", "Abyss_08", "Shrine Weaver Ability", "Inspection", "Powerup Msg"); private static readonly PopupSource SilkSpearSource = new PopupSource(ItemType.Spell, "Silkspear", "Mosstown_02", "Shrine Weaver Ability", "Inspection", "Skill Msg"); private static readonly PopupSource ParrySource = new PopupSource(ItemType.Spell, "Cross Stitch", "Organ_01", "Phantom", "Control", "UI Msg"); private static readonly PopupSource SilkBossNeedleSource = new PopupSource(ItemType.Spell, "Pale Nails", "Cradle_03_Destroyed", "Silk Needle Spell Get", "Control", "Msg"); private static readonly PopupSource SilkChargeSource = new PopupSource(ItemType.Spell, "Sharpdart", "Crawl_05", "Shrine Weaver Ability", "Inspection", "Skill Msg"); private static readonly PopupSource SilkBombSource = new PopupSource(ItemType.Spell, "Rune Rage", "Slab_10b", "Shrine Weaver Ability", "Inspection", "Skill Msg"); private static readonly PopupSource ThreadSphereSource = new PopupSource(ItemType.Spell, "Thread Storm", "Greymoor_22", "Shrine Weaver Ability", "Inspection", "Skill Msg"); private static readonly PopupSource FaydownSource = new PopupSource(ItemType.Skill, "Faydown Cloak", "Peak_08b", "DJ Get Sequence", "DJ Get Sequence", "Msg"); private static readonly PopupSource DriftersCloakSource = new PopupSource(ItemType.Skill, "Drifter's Cloak", "Bone_East_Umbrella", "Seamstress", "Dialogue", "Msg"); private static readonly PopupSource NeedleStrikeSource = new PopupSource(ItemType.Skill, "Needle Strike", "Room_Pinstress", "Pinstress Interior Ground Sit", "Behaviour", "Msg"); [ThreadStatic] private static int randomizedMapGetDepth; private static bool IsActiveApLocation(PopupSource source) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(source.ItemType) && instance.IsLocationEnabled(source.LocationName)) { return instance.IsLocationInSeed(source.LocationName); } return false; } private static bool ShouldSuppress(FsmStateAction action, PopupSource source) { if (source.Matches(action)) { return IsActiveApLocation(source); } return false; } private static bool TryGetPowerUpSource(PowerUps powerUp, out PopupSource source) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected I4, but got Unknown switch ((int)powerUp) { case 0: source = SprintSource; return true; case 1: source = WallJumpSource; return true; case 2: source = HarpoonDashSource; return true; case 3: source = NeedolinSource; return true; case 4: source = SuperJumpSource; return true; default: source = default(PopupSource); return false; } } private static bool TryGetSilkSkillSource(ToolItemSkill skill, out PopupSource source) { if ((Object)(object)skill == (Object)null) { source = default(PopupSource); return false; } switch (((ToolItem)skill).name) { case "Silk Spear": source = SilkSpearSource; return true; case "Parry": source = ParrySource; return true; case "Silk Boss Needle": source = SilkBossNeedleSource; return true; case "Silk Charge": source = SilkChargeSource; return true; case "Silk Bomb": source = SilkBombSource; return true; case "Thread Sphere": source = ThreadSphereSource; return true; default: source = default(PopupSource); return false; } } private static bool TryGetItemMessageSource(CreateUIMsgGetItem action, out PopupSource source) { if (FaydownSource.Matches((FsmStateAction)(object)action)) { source = FaydownSource; return true; } if (DriftersCloakSource.Matches((FsmStateAction)(object)action)) { source = DriftersCloakSource; return true; } if (NeedleStrikeSource.Matches((FsmStateAction)(object)action)) { source = NeedleStrikeSource; return true; } source = default(PopupSource); return false; } private static bool TryGetAbilityCollectableSource(string playerDataField, out PopupSource source) { switch (playerDataField) { case "hasDoubleJump": source = FaydownSource; return true; case "hasChargeSlash": source = NeedleStrikeSource; return true; case "hasSuperJump": source = SuperJumpSource; return true; case "hasWalljump": source = WallJumpSource; return true; case "hasBrolly": source = DriftersCloakSource; return true; case "hasDash": source = SprintSource; return true; case "hasHarpoonDash": source = HarpoonDashSource; return true; case "hasNeedolin": source = NeedolinSource; return true; default: source = default(PopupSource); return false; } } private static bool IsExactActiveAbilityCollectable(bool isMap, string playerDataField, string sceneName) { if (!isMap && TryGetAbilityCollectableSource(playerDataField, out var source) && string.Equals(sceneName, source.SceneName, StringComparison.Ordinal)) { return IsActiveApLocation(source); } return false; } private static bool IsExactManifestScene(StaticMapManifest.Entry entry, string sceneName) { if (entry == null || entry.Sources == null) { return false; } StaticMapManifest.Source[] sources = entry.Sources; foreach (StaticMapManifest.Source source in sources) { if (source != null && string.Equals(source.SceneName, sceneName, StringComparison.Ordinal)) { return true; } } return false; } private static bool IsExactActiveMapCollectable(PlayerDataCollectable collectable, bool isMap, string playerDataField, string sceneName) { SaveState instance = SaveState.Instance; if (instance == null || !instance.IsRandomized(ItemType.Map) || (Object)(object)collectable == (Object)null || !isMap || !StaticMapManifest.TryGetByAssetName(((Object)collectable).name, out var entry) || !string.Equals(playerDataField, entry.PlayerDataBool, StringComparison.Ordinal) || !IsExactManifestScene(entry, sceneName)) { return false; } if (instance.IsLocationEnabled(entry.LocationName)) { return instance.IsLocationInSeed(entry.LocationName); } return false; } } internal static class WandererChapelPatches { private sealed class ChapelSource { internal readonly string LocationName; internal readonly string RewardSceneName; internal readonly string DoorSceneName; internal readonly string ClosedFlag; internal ChapelSource(string locationName, string rewardSceneName, string doorSceneName = null, string closedFlag = null) { LocationName = locationName; RewardSceneName = rewardSceneName; DoorSceneName = doorSceneName; ClosedFlag = closedFlag; } } [HarmonyPatch(typeof(PlayerData), "SetBool", new Type[] { typeof(string), typeof(bool) })] private static class PlayerData_SetBool_Patch { [HarmonyPrefix] private static void Prefix(string __0, ref bool __1) { if (__1 && ShouldKeepChapelOpen(__0)) { __1 = false; } } } [HarmonyPatch(typeof(GetIsCrestUnlocked), "get_IsTrue", new Type[] { })] private static class GetIsCrestUnlocked_IsTrue_Patch { [HarmonyPostfix] private static void Postfix(GetIsCrestUnlocked __instance, ref bool __result) { if (TryGetSourceCompletion(__instance, out var sourceCompleted)) { __result = sourceCompleted; } } } private const string WandererClosedFlag = "chapelClosed_wanderer"; private const string ReaperClosedFlag = "chapelClosed_reaper"; private const string BeastClosedFlag = "chapelClosed_beast"; private static readonly ChapelSource[] StandardChapelSources = new ChapelSource[6] { new ChapelSource("Crest: Beast", "Ant_19", "Ant_20", "chapelClosed_beast"), new ChapelSource("Crest: Reaper", "Greymoor_20c", "Greymoor_20b", "chapelClosed_reaper"), new ChapelSource("Crest: Wanderer", "Chapel_Wanderer", "Bonegrave", "chapelClosed_wanderer"), new ChapelSource("Crest: Architect", "Under_20", "Under_17"), new ChapelSource("Crest: Shaman", "Tut_04"), new ChapelSource("Crest: Shaman", "Tut_05") }; private static readonly Dictionary ProtectedLocationsByClosedFlag = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "chapelClosed_wanderer", new string[5] { "Crest: Wanderer", "Rosary Cache: Bonegrave #1", "Rosary Cache: Bonegrave #2", "Rosary Cache: Bonegrave #3", "Rosary Cache: Bonegrave #4" } }, { "chapelClosed_reaper", new string[1] { "Crest: Reaper" } }, { "chapelClosed_beast", new string[1] { "Crest: Beast" } } }; private static bool IsManagedLocation(string locationName) { SaveState instance = SaveState.Instance; if (instance != null && instance.IsLocationEnabled(locationName)) { return instance.IsLocationInSeed(locationName); } return false; } private static bool IsPendingLocation(string locationName) { SaveState instance = SaveState.Instance; if (IsManagedLocation(locationName)) { return !instance.IsLocationChecked(locationName); } return false; } private static bool TryGetLocationCompletion(string locationName, out bool isChecked) { isChecked = false; if (!IsManagedLocation(locationName)) { return false; } isChecked = SaveState.Instance.IsLocationChecked(locationName); return true; } private static bool ShouldKeepChapelOpen(string closedFlag) { if (string.IsNullOrWhiteSpace(closedFlag) || !ProtectedLocationsByClosedFlag.TryGetValue(closedFlag, out var value)) { return false; } string[] array = value; for (int i = 0; i < array.Length; i++) { if (IsPendingLocation(array[i])) { return true; } } return false; } private static string GetSceneName(GetIsCrestUnlocked action) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (action == null || (Object)(object)((FsmStateAction)action).Owner == (Object)null) { return null; } Scene scene = ((FsmStateAction)action).Owner.scene; return GameManager.GetBaseSceneName(((Scene)(ref scene)).name); } private static bool MatchesContext(GetIsCrestUnlocked action, string ownerName, string fsmName, params string[] stateNames) { if (action == null || (Object)(object)((FsmStateAction)action).Owner == (Object)null || ((FsmStateAction)action).Fsm == null || ((FsmStateAction)action).State == null || !string.Equals(((Object)((FsmStateAction)action).Owner).name, ownerName, StringComparison.Ordinal) || !string.Equals(((FsmStateAction)action).Fsm.Name, fsmName, StringComparison.Ordinal)) { return false; } foreach (string b in stateNames) { if (string.Equals(((FsmStateAction)action).State.Name, b, StringComparison.Ordinal)) { return true; } } return false; } private static ChapelSource FindSourceByRewardScene(string sceneName) { ChapelSource[] standardChapelSources = StandardChapelSources; foreach (ChapelSource chapelSource in standardChapelSources) { if (string.Equals(chapelSource.RewardSceneName, sceneName, StringComparison.OrdinalIgnoreCase)) { return chapelSource; } } return null; } private static ChapelSource FindSourceByDoorScene(string sceneName) { ChapelSource[] standardChapelSources = StandardChapelSources; foreach (ChapelSource chapelSource in standardChapelSources) { if (!string.IsNullOrWhiteSpace(chapelSource.DoorSceneName) && string.Equals(chapelSource.DoorSceneName, sceneName, StringComparison.OrdinalIgnoreCase)) { return chapelSource; } } return null; } private static bool TryGetSourceCompletion(GetIsCrestUnlocked action, out bool sourceCompleted) { sourceCompleted = false; string sceneName = GetSceneName(action); if (string.IsNullOrWhiteSpace(sceneName)) { return false; } if (MatchesContext(action, "Crest Get Shrine", "Control", "Check Unlocked")) { ChapelSource chapelSource = FindSourceByRewardScene(sceneName); if (chapelSource != null) { return TryGetLocationCompletion(chapelSource.LocationName, out sourceCompleted); } return false; } if (MatchesContext(action, "Chapel Door Control", "chapel_door_control", "State Check")) { ChapelSource chapelSource2 = FindSourceByDoorScene(sceneName); if (chapelSource2 == null) { return false; } if (ShouldKeepChapelOpen(chapelSource2.ClosedFlag)) { sourceCompleted = false; return true; } return TryGetLocationCompletion(chapelSource2.LocationName, out sourceCompleted); } if (string.Equals(sceneName, "Under_17", StringComparison.OrdinalIgnoreCase) && MatchesContext(action, "Architect Shrine Door", "FSM", "Got Crest?", "Got Crest? 2")) { return TryGetLocationCompletion("Crest: Architect", out sourceCompleted); } if (string.Equals(sceneName, "Under_17", StringComparison.OrdinalIgnoreCase) && MatchesContext(action, "Architect NPC", "Behaviour", "Do Toolmaster Convo?", "Will Leave?")) { return TryGetLocationCompletion("Crest: Architect", out sourceCompleted); } if (string.Equals(sceneName, "Tut_04", StringComparison.OrdinalIgnoreCase) && MatchesContext(action, "Snail Shamans Set", "Dialogue", "State", "Talk?", "Can Complete?")) { return TryGetLocationCompletion("Crest: Shaman", out sourceCompleted); } return false; } internal static void Update() { PlayerData instance = PlayerData.instance; if (instance != null) { if (instance.chapelClosed_wanderer && ShouldKeepChapelOpen("chapelClosed_wanderer")) { instance.chapelClosed_wanderer = false; } if (instance.chapelClosed_reaper && ShouldKeepChapelOpen("chapelClosed_reaper")) { instance.chapelClosed_reaper = false; } if (instance.chapelClosed_beast && ShouldKeepChapelOpen("chapelClosed_beast")) { instance.chapelClosed_beast = false; } } } } [HarmonyPatch(typeof(PlayMakerFSM), "Start")] internal static class WeaknessScenePatches { private sealed class DelayedFsmEventFallback : FsmStateAction { private readonly string eventName; private readonly float delay; private float elapsed; public DelayedFsmEventFallback(string eventName, float delay) { this.eventName = eventName; this.delay = delay; } public override void OnEnter() { elapsed = 0f; } public override void OnUpdate() { elapsed += Time.unscaledDeltaTime; if (!(elapsed < delay)) { ((FsmStateAction)this).Finish(); ((FsmStateAction)this).Fsm.Event(eventName); } } } private const string ControlFsm = "Control"; private const string FinishedEvent = "FINISHED"; private const string GetItemMessageEndEvent = "GET ITEM MSG END"; private const string ChurchRespawnMarker = "Death Respawn Marker Church"; [HarmonyPostfix] private static void Postfix(PlayMakerFSM __instance) { //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)(object)__instance == (Object)null || !string.Equals(__instance.FsmName, "Control", StringComparison.Ordinal)) { return; } Scene scene = ((Component)__instance).gameObject.scene; string name = ((Scene)(ref scene)).name; string name2 = ((Object)__instance).name; try { if (IsOneOf(name, "Tut_01", "Tut_03", "Bonetown") && string.Equals(name2, "Weakness Scene", StringComparison.Ordinal)) { TryRedirectFinished(__instance, "Check PD Bool", "Activated", "early weakness"); } else if (string.Equals(name, "Bonetown", StringComparison.Ordinal) && string.Equals(name2, "Churchkeeper Intro Scene", StringComparison.Ordinal)) { TryPatchChurchkeeperIntro(__instance); } else if (string.Equals(name, "Cog_09_Destroyed", StringComparison.Ordinal) && string.Equals(name2, "Weakness Cog Drop Scene", StringComparison.Ordinal)) { TryRedirectFinished(__instance, "Init", "Activated", "Act 3 cog weakness"); } else if (string.Equals(name, "Song_25", StringComparison.Ordinal) && string.Equals(name2, "Weakness Scene Act3 Final", StringComparison.Ordinal)) { TryPatchAct3Final(__instance); } else if (string.Equals(name, "Song_25", StringComparison.Ordinal) && string.Equals(name2, "Hatch", StringComparison.Ordinal)) { TryPatchAct3Hatch(__instance); } } catch (Exception ex) { LogFailure(__instance, "unexpected exception; leaving any uncommitted edit alone: " + ex); } } private static void TryRedirectFinished(PlayMakerFSM fsm, string fromStateName, string toStateName, string description) { if (!TryPrepareTransition(fsm, fromStateName, "FINISHED", toStateName, out var transition, out var target, out var reason)) { LogFailure(fsm, description + ": " + reason); return; } CommitTransition(transition, target); LogSuccess(fsm, description); } private static void TryPatchChurchkeeperIntro(PlayMakerFSM fsm) { if (!TryPrepareTransition(fsm, "Pause", "FINISHED", "Set End", out var transition, out var target, out var reason)) { LogFailure(fsm, "Churchkeeper intro: " + reason); return; } FsmState val = FindState(fsm, "End"); if (val == null) { LogFailure(fsm, "Churchkeeper intro: required state 'End' is missing"); return; } if (!HasTransition(target, "GET ITEM MSG END", "End")) { LogFailure(fsm, "Churchkeeper intro: Set End -> End event is missing"); return; } if (!HasTruePlayerDataBoolAction(target, "churchKeeperIntro") || !HasActionType(target, "HutongGames.PlayMaker.Actions.EndDialogue") || !HasActionType(target, "QuestPlaymakerActions.BeginQuestV2") || !HasActionType(target, "QueueSaveGameV2") || !HasActionType(target, "HutongGames.PlayMaker.Actions.ActivateGameObject")) { LogFailure(fsm, "Churchkeeper intro: Set End no longer contains the expected story/quest/dialogue/save/message actions"); return; } SetDeathRespawnV2 val2 = FindOnlyAction(val); if (val2 == null || val2.RespawnMarkerName == null || !string.Equals(val2.RespawnMarkerName.Value, "Death Respawn Marker Church", StringComparison.Ordinal)) { LogFailure(fsm, "Churchkeeper intro: End no longer contains the expected Church death-respawn action"); return; } FsmStateAction[] actions = target.Actions; FsmStateAction[] actions2 = val.Actions; if (actions == null || actions2 == null) { LogFailure(fsm, "Churchkeeper intro: completion actions could not be loaded"); return; } bool flag = false; FsmStateAction[] array = actions; for (int i = 0; i < array.Length; i++) { if (array[i] is DelayedFsmEventFallback) { flag = true; break; } } FsmStateAction[] actions3 = PrepareActionRemoval(actions2, (FsmStateAction)(object)val2); if (!flag) { AppendAction(target, (FsmStateAction)(object)new DelayedFsmEventFallback("GET ITEM MSG END", 8f)); } val.Actions = actions3; CommitTransition(transition, target); if (PlayerData.instance != null) { PlayerData.instance.churchKeeperIntro = true; } LogSuccess(fsm, "Churchkeeper weakness bypass with native quest completion"); } private static void TryPatchAct3Final(PlayMakerFSM fsm) { //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Expected O, but got Unknown //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Expected O, but got Unknown FsmState val = FindState(fsm, "Dormant"); FsmState val2 = FindState(fsm, "Walk R"); FsmState val3 = FindState(fsm, "Weak Fall"); FsmState val4 = FindState(fsm, "First Land"); FsmState val5 = FindState(fsm, "Weak Fall Land"); FsmState val6 = FindState(fsm, "Fade Out"); FsmState val7 = FindState(fsm, "To Enclave Scene"); FsmState val8 = FindState(fsm, "Release Lock"); if (val == null || val2 == null || val3 == null || val4 == null || val5 == null || val6 == null || val7 == null || val8 == null) { LogFailure(fsm, "Act 3 final: one or more required states are missing"); return; } Trigger2dEvent val9 = FindFirstAction(val); Trigger2dEvent val10 = FindLastAction(val2); AddHeroInputBlocker val11 = FindFirstAction(val4); Wait val12 = FindFirstAction(val5); ScreenFader val13 = FindFirstAction(val6); Wait val14 = FindFirstAction(val6); RemoveHeroInputBlocker val15 = FindFirstAction(val8); if (val9 == null || val10 == null || val10.gameObject == null || val10.sendEvent == null || val11 == null || val11.Blocker == null || val12 == null || val12.time == null || val13 == null || val13.duration == null || val14 == null || val14.time == null || val15 == null || val15.Blocker == null) { LogFailure(fsm, "Act 3 final: one or more required actions/values are missing"); return; } if (!string.Equals(val10.sendEvent.Name, "HATCH", StringComparison.Ordinal)) { LogFailure(fsm, "Act 3 final: Walk R trigger no longer sends HATCH"); return; } if (!TryPrepareTransition(fsm, "Get Up To Kneel", "FINISHED", "Fade Out", out var transition, out var target, out var reason)) { LogFailure(fsm, "Act 3 final: " + reason); return; } FsmStateAction[] actions = val3.Actions; FsmStateAction[] actions2 = val7.Actions; if (actions == null || actions2 == null || actions2.Length < 4) { LogFailure(fsm, "Act 3 final: action insertion points are unavailable"); return; } bool num = FindFirstAction(val3) != null; bool flag = FindFirstAction(val7) != null; FsmStateAction[] actions3 = actions; FsmStateAction[] actions4 = actions2; if (!num) { actions3 = PrepareActionInsertion(val3, 0, (FsmStateAction)new AddHeroInputBlocker { Blocker = val11.Blocker }); } if (!flag) { actions4 = PrepareActionInsertion(val7, 4, (FsmStateAction)new RemoveHeroInputBlocker { Blocker = val15.Blocker }); } val9.gameObject = val10.gameObject; val9.trigger = (Trigger2DType)1; val9.sendEvent = val10.sendEvent; val9.storeCollider = val10.storeCollider; if (!num) { val3.Actions = actions3; } val12.time.Value = 0.5f; val13.duration.Value = 0.6f; val14.time.Value = 0.6f; CommitTransition(transition, target); if (!flag) { val7.Actions = actions4; } LogSuccess(fsm, "Act 3 final weakness bypass with input/fade cleanup"); } private static void TryPatchAct3Hatch(PlayMakerFSM fsm) { FsmState val = FindState(fsm, "Wait for Call"); if (val == null) { LogFailure(fsm, "Act 3 hatch: required state 'Wait for Call' is missing"); return; } SetFloatValue val2 = FindOnlyAction(val); if (val2 == null || val2.floatValue == null) { LogFailure(fsm, "Act 3 hatch: expected single SetFloatValue action is missing"); return; } val2.floatValue.Value = 0.1f; LogSuccess(fsm, "Act 3 hatch wake-up delay"); } private static bool TryPrepareTransition(PlayMakerFSM fsm, string fromStateName, string eventName, string toStateName, out FsmTransition transition, out FsmState target, out string reason) { transition = null; target = FindState(fsm, toStateName); if (target == null) { reason = "required target state '" + toStateName + "' is missing"; return false; } FsmState val = FindState(fsm, fromStateName); if (val == null) { reason = "required source state '" + fromStateName + "' is missing"; return false; } transition = FindTransition(val, eventName); if (transition == null) { reason = "state '" + fromStateName + "' has no '" + eventName + "' transition"; return false; } reason = string.Empty; return true; } private static FsmState FindState(PlayMakerFSM fsm, string stateName) { FsmState[] fsmStates = fsm.FsmStates; if (fsmStates == null) { return null; } FsmState[] array = fsmStates; foreach (FsmState val in array) { if (val != null && string.Equals(val.Name, stateName, StringComparison.Ordinal)) { return val; } } return null; } private static FsmTransition FindTransition(FsmState state, string eventName) { FsmTransition[] transitions = state.Transitions; if (transitions == null) { return null; } FsmTransition[] array = transitions; foreach (FsmTransition val in array) { if (val != null && string.Equals(val.EventName, eventName, StringComparison.Ordinal)) { return val; } } return null; } private static bool HasTransition(FsmState state, string eventName, string toStateName) { FsmTransition val = FindTransition(state, eventName); if (val != null) { return string.Equals(val.ToState, toStateName, StringComparison.Ordinal); } return false; } private static void CommitTransition(FsmTransition transition, FsmState target) { transition.ToState = target.Name; transition.ToFsmState = target; } private static bool HasTruePlayerDataBoolAction(FsmState state, string boolName) { FsmStateAction[] actions = state.Actions; if (actions == null) { return false; } FsmStateAction[] array = actions; foreach (FsmStateAction obj in array) { SetPlayerDataBool val = (SetPlayerDataBool)(object)((obj is SetPlayerDataBool) ? obj : null); if (val != null && val.boolName != null && val.value != null && string.Equals(val.boolName.Value, boolName, StringComparison.Ordinal) && val.value.Value) { return true; } } return false; } private static bool HasActionType(FsmState state, string fullTypeName) { FsmStateAction[] actions = state.Actions; if (actions == null) { return false; } FsmStateAction[] array = actions; foreach (FsmStateAction val in array) { if (val != null && string.Equals(((object)val).GetType().FullName, fullTypeName, StringComparison.Ordinal)) { return true; } } return false; } private static T FindFirstAction(FsmState state) where T : FsmStateAction { FsmStateAction[] actions = state.Actions; if (actions == null) { return default(T); } FsmStateAction[] array = actions; foreach (FsmStateAction obj in array) { T val = (T)(object)((obj is T) ? obj : null); if (val != null) { return val; } } return default(T); } private static T FindLastAction(FsmState state) where T : FsmStateAction { FsmStateAction[] actions = state.Actions; if (actions == null) { return default(T); } for (int num = actions.Length - 1; num >= 0; num--) { FsmStateAction obj = actions[num]; T val = (T)(object)((obj is T) ? obj : null); if (val != null) { return val; } } return default(T); } private static T FindOnlyAction(FsmState state) where T : FsmStateAction { FsmStateAction[] actions = state.Actions; if (actions == null) { return default(T); } T val = default(T); FsmStateAction[] array = actions; foreach (FsmStateAction obj in array) { T val2 = (T)(object)((obj is T) ? obj : null); if (val2 != null) { if (val != null) { return default(T); } val = val2; } } return val; } private static void AppendAction(FsmState state, FsmStateAction action) { InsertAction(state, state.Actions.Length, action); } private static void InsertAction(FsmState state, int index, FsmStateAction action) { state.Actions = PrepareActionInsertion(state, index, action); } private static FsmStateAction[] PrepareActionInsertion(FsmState state, int index, FsmStateAction action) { FsmStateAction[] actions = state.Actions; int num = Math.Max(0, Math.Min(index, actions.Length)); FsmStateAction[] array = (FsmStateAction[])(object)new FsmStateAction[actions.Length + 1]; Array.Copy(actions, 0, array, 0, num); action.Init(state); array[num] = action; Array.Copy(actions, num, array, num + 1, actions.Length - num); return array; } private static FsmStateAction[] PrepareActionRemoval(FsmStateAction[] oldActions, FsmStateAction action) { int num = Array.IndexOf(oldActions, action); if (num < 0) { throw new InvalidOperationException("The validated FSM action was not present in its state."); } FsmStateAction[] array = (FsmStateAction[])(object)new FsmStateAction[oldActions.Length - 1]; Array.Copy(oldActions, 0, array, 0, num); Array.Copy(oldActions, num + 1, array, num, oldActions.Length - num - 1); return array; } private static bool IsOneOf(string value, params string[] candidates) { foreach (string b in candidates) { if (string.Equals(value, b, StringComparison.Ordinal)) { return true; } } return false; } private static void LogSuccess(PlayMakerFSM fsm, string description) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogInfo((object)("[RANDOMIZER] Disabled forced weakness (" + description + ") in " + Describe(fsm) + ".")); } } private static void LogFailure(PlayMakerFSM fsm, string reason) { ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)("[RANDOMIZER] Forced weakness patch skipped for " + Describe(fsm) + ": " + reason + ".")); } } private static string Describe(PlayMakerFSM fsm) { //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) string[] obj = new string[7] { "'", null, null, null, null, null, null }; Scene scene = ((Component)fsm).gameObject.scene; obj[1] = ((Scene)(ref scene)).name; obj[2] = "/"; obj[3] = ((Object)fsm).name; obj[4] = "/"; obj[5] = fsm.FsmName; obj[6] = "'"; return string.Concat(obj); } } internal static class WidowSequencePatches { [HarmonyPatch(typeof(SetPlayerDataBool), "OnEnter")] private static class RandomizedNeedolinCheckPatch { [HarmonyPrefix] private static bool Prefix(SetPlayerDataBool __instance) { if (!IsRandomizedWidowAction((FsmStateAction)(object)__instance, "Final Bind Burst") || __instance.boolName == null || !string.Equals(__instance.boolName.Value, "hasNeedolin", StringComparison.Ordinal)) { return true; } SaveState.Instance.CheckLocation("Skill Unlock: Needolin"); ((FsmStateAction)__instance).Finish(); ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogInfo((object)"[RANDOMIZER] Widow reported the randomized Needolin location without granting vanilla Needolin ownership."); } return false; } } [HarmonyPatch(typeof(BeginSceneTransition), "OnEnter")] private static class MissingNeedolinMemoryBypassPatch { [HarmonyPrefix] private static void Prefix(BeginSceneTransition __instance) { SaveState instance = SaveState.Instance; if (IsRandomizedWidowAction((FsmStateAction)(object)__instance, "To Memory Scene") && instance != null && !instance.canUseNeedolin && __instance.sceneName != null && string.Equals(__instance.sceneName.Value, "Memory_Needolin", StringComparison.Ordinal)) { __instance.sceneName.Value = "Belltown_Shrine"; __instance.entryGateName.Value = "door_wakeOnGround"; ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogWarning((object)"[RANDOMIZER] Skipped Widow's Needolin memory because the AP Needolin has not been received; returning through the native shrine wake sequence."); } } } } private const string BossObjectName = "Spinner Boss"; private const string BossFsmName = "Control"; private const string NeedolinLocationName = "Skill Unlock: Needolin"; private static bool IsRandomizedWidowAction(FsmStateAction action, string stateName) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) SaveState instance = SaveState.Instance; if (instance != null && instance.IsRandomized(ItemType.Skill) && action != null && (Object)(object)action.Owner != (Object)null) { Scene scene = action.Owner.scene; if (string.Equals(((Scene)(ref scene)).name, "Belltown_Shrine", StringComparison.Ordinal) && string.Equals(((Object)action.Owner).name, "Spinner Boss", StringComparison.Ordinal) && action.Fsm != null && string.Equals(action.Fsm.Name, "Control", StringComparison.Ordinal) && action.State != null) { return string.Equals(action.State.Name, stateName, StringComparison.Ordinal); } } return false; } } [HarmonyPatch(typeof(ActivateIfPlayerdataFalse), "Start")] internal static class WispThicketFaydownPatches { private const string SourceScene = "Greymoor_06"; private const string SourcePath = "wisp temporary block"; private const string SourceBoolName = "hasDoubleJump"; private const string TargetPath = "wisp temporary block/terrain collider temp blocker"; private static readonly FieldInfo BoolNameField = AccessTools.Field(typeof(ActivateIfPlayerdataFalse), "boolName"); private static readonly FieldInfo ObjectToActivateField = AccessTools.Field(typeof(ActivateIfPlayerdataFalse), "objectToActivate"); [HarmonyPostfix] private static void Postfix(ActivateIfPlayerdataFalse __instance) { TryRemoveRandomizedFaydownBlocker(__instance); } internal static bool SynchronizeActiveScene() { SaveState instance = SaveState.Instance; if (instance == null || !instance.canDoubleJump) { return false; } bool flag = false; ActivateIfPlayerdataFalse[] array = Resources.FindObjectsOfTypeAll(); foreach (ActivateIfPlayerdataFalse source in array) { flag |= TryRemoveRandomizedFaydownBlocker(source); } return flag; } private static bool TryRemoveRandomizedFaydownBlocker(ActivateIfPlayerdataFalse source) { SaveState instance = SaveState.Instance; if (instance == null || !instance.canDoubleJump || !IsExactSource(source) || BoolNameField == null || ObjectToActivateField == null) { return false; } try { string a = BoolNameField.GetValue(source) as string; object? value = ObjectToActivateField.GetValue(source); GameObject val = (GameObject)((value is GameObject) ? value : null); if (!string.Equals(a, "hasDoubleJump", StringComparison.Ordinal) || !IsExactTarget(((Component)source).gameObject, val)) { return false; } if (val.activeSelf) { val.SetActive(false); ManualLogSource log = RandomizerPlugin.Log; if (log != null) { log.LogInfo((object)"[RANDOMIZER] Removed the Greymoor Wisp Thicket Faydown blocker for randomized cloak ownership."); } } return true; } catch (Exception ex) { ManualLogSource log2 = RandomizerPlugin.Log; if (log2 != null) { log2.LogWarning((object)("[RANDOMIZER] Greymoor Faydown route synchronization failed closed: " + ex.Message)); } return false; } } private static bool IsExactSource(ActivateIfPlayerdataFalse source) { //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) if ((Object)(object)source != (Object)null && (Object)(object)((Component)source).gameObject != (Object)null && ((Component)source).gameObject.activeInHierarchy) { Scene scene = ((Component)source).gameObject.scene; if (string.Equals(((Scene)(ref scene)).name, "Greymoor_06", StringComparison.Ordinal)) { return string.Equals(Utils.GetHierarchyPath(((Component)source).transform), "wisp temporary block", StringComparison.Ordinal); } } return false; } private static bool IsExactTarget(GameObject source, GameObject target) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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_0041: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)source == (Object)null) && !((Object)(object)target == (Object)null)) { Scene scene = target.scene; if (string.Equals(((Scene)(ref scene)).name, "Greymoor_06", StringComparison.Ordinal)) { scene = source.scene; string name = ((Scene)(ref scene)).name; scene = target.scene; if (string.Equals(name, ((Scene)(ref scene)).name, StringComparison.Ordinal) && string.Equals(Utils.GetHierarchyPath(target.transform), "wisp temporary block/terrain collider temp blocker", StringComparison.Ordinal)) { return true; } } } return false; } } }