From d3e8fd4df8c5b743fd4a694a2ee20c21f33fc2e5 Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Thu, 12 Mar 2026 20:04:00 -0400 Subject: [PATCH 1/3] Track traversed entrances to use with UT deferred locations --- Archipelago/ArchipelagoClient.cs | 7 +++++ Archipelago/ItemsAndLocationsHandler.cs | 2 ++ GameOverrideManagers/RandoLevelManager.cs | 34 ++++++++++++++++++++++ GameOverrideManagers/RandoPortalManager.cs | 1 + Utils/Constants/LevelConstants.cs | 13 +++++++++ Utils/RandomizerStateManager.cs | 7 +++-- 6 files changed, 62 insertions(+), 2 deletions(-) diff --git a/Archipelago/ArchipelagoClient.cs b/Archipelago/ArchipelagoClient.cs index 1a521b3..f5981f8 100644 --- a/Archipelago/ArchipelagoClient.cs +++ b/Archipelago/ArchipelagoClient.cs @@ -370,6 +370,13 @@ public static void SyncEvents() } } + public static void SyncVisitedEntrances() + { + Console.WriteLine("Checking datastorage visited entrances"); + RandoLevelManager.VisitedEntrances = + Session.DataStorage[Scope.Slot, "VisitedEntrances"].To>() ?? new List(); + } + private static void OnItemReceived(ReceivedItemsHelper helper) { var itemToUnlock = helper.DequeueItem(); diff --git a/Archipelago/ItemsAndLocationsHandler.cs b/Archipelago/ItemsAndLocationsHandler.cs index c5ee5b2..d71d4d0 100644 --- a/Archipelago/ItemsAndLocationsHandler.cs +++ b/Archipelago/ItemsAndLocationsHandler.cs @@ -546,6 +546,8 @@ public static void ReSync() { Synced = true; ArchipelagoClient.SyncEvents(); + if (RandoLevelManager.RandoLevelMapping != null) + ArchipelagoClient.SyncVisitedEntrances(); var receivedItems = new Dictionary(); for (int i = 0; i < ArchipelagoClient.ServerData.Index; i++) diff --git a/GameOverrideManagers/RandoLevelManager.cs b/GameOverrideManagers/RandoLevelManager.cs index e2d34cf..d6c15cf 100644 --- a/GameOverrideManagers/RandoLevelManager.cs +++ b/GameOverrideManagers/RandoLevelManager.cs @@ -19,6 +19,7 @@ public static class RandoLevelManager // ReSharper disable once UnassignedField.Global public static Dictionary RandoLevelMapping; + public static List VisitedEntrances = new List(); public static void LoadLevel(On.LevelManager.orig_LoadLevel orig, LevelManager self, LevelLoadingInfo levelInfo) { @@ -119,6 +120,32 @@ public static LevelConstants.RandoLevel FindEntrance() } } Console.WriteLine(entrance); + string sourceExit; + if (LevelConstants.SpecialConnectionSourceExits.TryGetValue(entrance, out var specialSource)) + { + sourceExit = specialSource + " exit"; + } + else if (entrance.Equals("Corrupted Future")) + { + sourceExit = "HQ - Artificer's Portal"; + } + else if (entrance.Equals("Tower of Time - Left")) + { + sourceExit = "HQ - Artificer's Challenge"; + } + else if (entrance.Equals("Glacial Peak - Left")) + { + sourceExit = "Elemental Skylands - Right exit"; + } + else if (!LevelConstants.TransitionToEntranceName.TryGetValue(new LevelConstants.Transition(currentLevel, lastLevel), out sourceExit)) + { + sourceExit = entrance; + } + else + { + sourceExit = sourceExit + " exit"; + } + AddVisitedEntrance(sourceExit); return RandoLevelMapping[entrance]; } catch (Exception e){ Console.WriteLine(e);} return new LevelConstants.RandoLevel(ELevel.NONE, new Vector3()); @@ -291,4 +318,11 @@ public static void CleanupAfterTeleport() Manager.Instance.CloseAllScreensOfType(false); Manager.Instance.CloseAllScreensOfType(false); } + + public static void AddVisitedEntrance(string entrance) + { + if (!ArchipelagoClient.Authenticated || VisitedEntrances.Contains(entrance)) return; + VisitedEntrances.Add(entrance); + ArchipelagoClient.Session.DataStorage[Scope.Slot, "VisitedEntrances"] = VisitedEntrances; + } } \ No newline at end of file diff --git a/GameOverrideManagers/RandoPortalManager.cs b/GameOverrideManagers/RandoPortalManager.cs index eab3927..3247225 100644 --- a/GameOverrideManagers/RandoPortalManager.cs +++ b/GameOverrideManagers/RandoPortalManager.cs @@ -416,6 +416,7 @@ public static void Teleport() return; } + RandoLevelManager.AddVisitedEntrance("HQ - " + portal.Replace("- ", "")); RandoLevelManager.TeleportInArea(newLevel.LevelName, newLevel.PlayerPos, newLevel.Dimension); } catch (Exception e) diff --git a/Utils/Constants/LevelConstants.cs b/Utils/Constants/LevelConstants.cs index e73be20..ebee3a2 100644 --- a/Utils/Constants/LevelConstants.cs +++ b/Utils/Constants/LevelConstants.cs @@ -464,6 +464,19 @@ public static Transition TransitionFromName(string name) "Quillshroom Marsh - Right", "Searing Crags - Left" }; + public static readonly Dictionary SpecialConnectionSourceExits = + new Dictionary + { + { "Howling Grotto - Right", "Quillshroom Marsh - Top Left" }, + { "Howling Grotto - Top", "Quillshroom Marsh - Bottom Left" }, + { "Quillshroom Marsh - Bottom Left", "Howling Grotto - Top" }, + { "Quillshroom Marsh - Top Left", "Howling Grotto - Right" }, + { "Quillshroom Marsh - Top Right", "Searing Crags - Left" }, + { "Quillshroom Marsh - Bottom Right", "Searing Crags - Bottom" }, + { "Searing Crags - Left", "Quillshroom Marsh - Top Right" }, + { "Searing Crags - Bottom", "Quillshroom Marsh - Bottom Right" }, + }; + public static readonly List TransitionNames = new List { "Ninja Village - Right", diff --git a/Utils/RandomizerStateManager.cs b/Utils/RandomizerStateManager.cs index 0edd61d..bfe5a27 100644 --- a/Utils/RandomizerStateManager.cs +++ b/Utils/RandomizerStateManager.cs @@ -119,6 +119,9 @@ public static void InitializeSeed() if (!slotData.TryGetValue("transitions", out var transitions)) return; RandoLevelManager.RandoLevelMapping = new Dictionary(); + RandoLevelManager.VisitedEntrances = new List(); + if (ArchipelagoClient.Session is not null) + ArchipelagoClient.Session.DataStorage[Scope.Slot, "VisitedEntrances"].Initialize(new List()); var transitionPairs = ((JArray)transitions).ToObject>>(); if (transitionPairs.Count == 0) RandoLevelManager.RandoLevelMapping = null; else @@ -302,9 +305,9 @@ public static void InitializeNewSecondQuest(SaveGameSelectionScreen saveScreen, // "HowlingGrottoBossOutroCutscene", // "HowlingGrottoToQuillshroomFirstQuestCutScene", // "QuillshroomMarshBossIntroCutscene", - // "QuillshroomMarshBossOutroCutscene", + // "QuillshroomMarshBossOutroCutScene", // "SearingCragsBossIntroCutscene", - // "SearingCragsBossOutroCutscene", + // "SearingCragsBossOutroCutScene", // "SearagToGlacialPeakEntranceCutscene", // "GlacialPeakTowerOfTimeCutscene", // "GlacialPeakTowerOutCutscene", From fb0fb16341633b0ea149e82e9c18d0e0c6ec4b93 Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sun, 10 May 2026 13:23:58 -0400 Subject: [PATCH 2/3] self review --- Utils/RandomizerStateManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Utils/RandomizerStateManager.cs b/Utils/RandomizerStateManager.cs index bfe5a27..87b26b4 100644 --- a/Utils/RandomizerStateManager.cs +++ b/Utils/RandomizerStateManager.cs @@ -305,9 +305,9 @@ public static void InitializeNewSecondQuest(SaveGameSelectionScreen saveScreen, // "HowlingGrottoBossOutroCutscene", // "HowlingGrottoToQuillshroomFirstQuestCutScene", // "QuillshroomMarshBossIntroCutscene", - // "QuillshroomMarshBossOutroCutScene", + // "QuillshroomMarshBossOutroCutscene", // "SearingCragsBossIntroCutscene", - // "SearingCragsBossOutroCutScene", + // "SearingCragsBossOutroCutscene", // "SearagToGlacialPeakEntranceCutscene", // "GlacialPeakTowerOfTimeCutscene", // "GlacialPeakTowerOutCutscene", From f8da76383d9f8319eed89dab3c958fe47ee7a0ee Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sun, 7 Jun 2026 15:16:31 -0400 Subject: [PATCH 3/3] rework all the visited entrances tracking --- APRandomizerMain.cs | 13 ++++++ Archipelago/ArchipelagoClient.cs | 31 ++++++++++--- Archipelago/ItemsAndLocationsHandler.cs | 6 +-- GameOverrideManagers/RandoLevelManager.cs | 9 ++-- GameOverrideManagers/RandoPortalManager.cs | 35 ++++++++++++++- Utils/RandomizerStateManager.cs | 52 ++++++++++++---------- 6 files changed, 107 insertions(+), 39 deletions(-) diff --git a/APRandomizerMain.cs b/APRandomizerMain.cs index 45d9339..4c5922e 100644 --- a/APRandomizerMain.cs +++ b/APRandomizerMain.cs @@ -604,14 +604,27 @@ void Cutscene_Play(On.Cutscene.orig_Play orig, Cutscene self) } } } + if (ArchipelagoClient.EventsICareAbout.Contains(eventName) && ArchipelagoClient.Authenticated) { ArchipelagoClient.Session.DataStorage[Scope.Slot, "Events"] += new List { eventName }; } + + if (eventName.EndsWith("PortalOpeningCutscene")) + { + self.onDone += OnAnyPortalOpeningCutsceneDone; + } + orig(self); } + private static void OnAnyPortalOpeningCutsceneDone(Cutscene cutscene) + { + cutscene.onDone -= OnAnyPortalOpeningCutsceneDone; + ArchipelagoClient.ReconciliateUnlockedPortals(); + } + void PhantomIntro_OnEnterRoom(On.PhantomIntroCutscene.orig_OnEnterRoom orig, PhantomIntroCutscene self, bool teleportedInRoom) { diff --git a/Archipelago/ArchipelagoClient.cs b/Archipelago/ArchipelagoClient.cs index f5981f8..e38d515 100644 --- a/Archipelago/ArchipelagoClient.cs +++ b/Archipelago/ArchipelagoClient.cs @@ -370,11 +370,32 @@ public static void SyncEvents() } } - public static void SyncVisitedEntrances() + public static List AddVisitedEntrance(string entrance) { - Console.WriteLine("Checking datastorage visited entrances"); - RandoLevelManager.VisitedEntrances = - Session.DataStorage[Scope.Slot, "VisitedEntrances"].To>() ?? new List(); + if (!Authenticated) return []; + + var visitedEntrances = Session.DataStorage[Scope.Slot, "VisitedEntrances"].To>(); + if (visitedEntrances.Contains(entrance)) return visitedEntrances; + + Console.WriteLine("Adding visited entrance: " + entrance); + visitedEntrances.Add(entrance); + visitedEntrances.Sort(); + Session.DataStorage[Scope.Slot, "VisitedEntrances"] = visitedEntrances; + + return visitedEntrances; + } + + public static void ReconciliateUnlockedPortals() + { + if (!Authenticated) return; + + var unlockedPortals = RandoPortalManager.UnlockedPortals; + unlockedPortals.UnionWith(Session.DataStorage[Scope.Slot, "UnlockedPortals"].To>()); + + var unlockedPortalsList = unlockedPortals.ToList(); + unlockedPortalsList.Sort(); + Console.WriteLine($"Updating unlocked portals with Data Storage. Unlocked portals are {string.Join(", ", [.. unlockedPortalsList])}"); + Session.DataStorage[Scope.Slot, "UnlockedPortals"] = unlockedPortalsList; } private static void OnItemReceived(ReceivedItemsHelper helper) @@ -565,4 +586,4 @@ public static void ClearMessages() DialogQueue = new Queue(); } } -} \ No newline at end of file +} diff --git a/Archipelago/ItemsAndLocationsHandler.cs b/Archipelago/ItemsAndLocationsHandler.cs index d71d4d0..5e472fa 100644 --- a/Archipelago/ItemsAndLocationsHandler.cs +++ b/Archipelago/ItemsAndLocationsHandler.cs @@ -546,8 +546,8 @@ public static void ReSync() { Synced = true; ArchipelagoClient.SyncEvents(); - if (RandoLevelManager.RandoLevelMapping != null) - ArchipelagoClient.SyncVisitedEntrances(); + ArchipelagoClient.ReconciliateUnlockedPortals(); + var receivedItems = new Dictionary(); for (int i = 0; i < ArchipelagoClient.ServerData.Index; i++) @@ -567,4 +567,4 @@ public static void ReSync() } } } -} \ No newline at end of file +} diff --git a/GameOverrideManagers/RandoLevelManager.cs b/GameOverrideManagers/RandoLevelManager.cs index d6c15cf..d066c8d 100644 --- a/GameOverrideManagers/RandoLevelManager.cs +++ b/GameOverrideManagers/RandoLevelManager.cs @@ -19,7 +19,7 @@ public static class RandoLevelManager // ReSharper disable once UnassignedField.Global public static Dictionary RandoLevelMapping; - public static List VisitedEntrances = new List(); + public static List VisitedEntrances = []; public static void LoadLevel(On.LevelManager.orig_LoadLevel orig, LevelManager self, LevelLoadingInfo levelInfo) { @@ -321,8 +321,7 @@ public static void CleanupAfterTeleport() public static void AddVisitedEntrance(string entrance) { - if (!ArchipelagoClient.Authenticated || VisitedEntrances.Contains(entrance)) return; - VisitedEntrances.Add(entrance); - ArchipelagoClient.Session.DataStorage[Scope.Slot, "VisitedEntrances"] = VisitedEntrances; + if (VisitedEntrances.Contains(entrance)) return; + VisitedEntrances = ArchipelagoClient.AddVisitedEntrance(entrance); } -} \ No newline at end of file +} diff --git a/GameOverrideManagers/RandoPortalManager.cs b/GameOverrideManagers/RandoPortalManager.cs index 3247225..5ba5712 100644 --- a/GameOverrideManagers/RandoPortalManager.cs +++ b/GameOverrideManagers/RandoPortalManager.cs @@ -329,6 +329,37 @@ public Portal(int portalWarp) }; private static readonly List AccessedStartingPortals = new List(); + + public static HashSet UnlockedPortals + { + get + { + var unlockedPortals = new HashSet(); + + var progressManager = Manager.Instance; + if (progressManager.cutscenesPlayed.Contains("PortalOpeningCutscene")) + { + unlockedPortals.Add("Autumn Hills Portal"); + unlockedPortals.Add("Howling Grotto Portal"); + unlockedPortals.Add("Glacial Peak Portal"); + } + if (progressManager.cutscenesPlayed.Contains("SunkenShrinePortalOpeningCutscene")) + { + unlockedPortals.Add("Sunken Shrine Portal"); + } + if (progressManager.cutscenesPlayed.Contains("SearingCragsPortalOpeningCutscene")) + { + unlockedPortals.Add("Searing Crags Portal"); + } + if (progressManager.cutscenesPlayed.Contains("RiviereTurquoisePortalOpeningCutscene")) + { + unlockedPortals.Add("Riviere Turquoise Portal"); + } + + return unlockedPortals; + } + } + public static bool ShouldPortalBeOpen(string portal) { return AccessedStartingPortals.Contains(portal); @@ -365,7 +396,7 @@ public static void OpenPortalEvent(On.PortalOpeningCutscene.orig_OnOpenPortalEve break; } } - + private static LevelConstants.RandoLevel GetPortalExit(string enteredPortal) { Console.WriteLine($"getting portal. entered {enteredPortal}"); @@ -437,4 +468,4 @@ public static void LeaveHQ(On.TotHQ.orig_LeaveToLevel orig, TotHQ self, bool pla orig(self, playLevelMusic, loadingNewLevel); } } -} \ No newline at end of file +} diff --git a/Utils/RandomizerStateManager.cs b/Utils/RandomizerStateManager.cs index 87b26b4..b90b944 100644 --- a/Utils/RandomizerStateManager.cs +++ b/Utils/RandomizerStateManager.cs @@ -99,55 +99,59 @@ public static void InitializeSeed() Instance.SkipMusicBox = !Convert.ToBoolean(slotData["music_box"]); RandoShopManager.ShopPrices = ((JObject)slotData["shop"]).ToObject>(); RandoShopManager.FigurePrices = ((JObject)slotData["figures"]).ToObject>(); + if (slotData.TryGetValue("starting_portals", out var portals)) { var startingPortals = ((JArray)portals).ToObject>(); RandoPortalManager.StartingPortals = []; foreach (var portal in startingPortals) { - Console.WriteLine(portal); + Console.WriteLine($"Starting portal: {portal}"); RandoPortalManager.StartingPortals.Add(portal); } + } + else + { + RandoPortalManager.StartingPortals = + [ + "AutumnHillsPortal", + "RiviereTurquoisePortal", + "HowlingGrottoPortal", + "SunkenShrinePortal", + "SearingCragsPortal", + "GlacialPeakPortal" + ]; + } - var portalExits = ((JArray)slotData["portal_exits"]).ToObject>(); + if (slotData.TryGetValue("portal_exits", out var portalExitsJson)) + { + var portalExits = ((JArray)portalExitsJson).ToObject>(); RandoPortalManager.PortalMapping = []; foreach (var portalExit in portalExits) { RandoPortalManager.PortalMapping.Add(new RandoPortalManager.Portal(portalExit)); } + } - if (!slotData.TryGetValue("transitions", out var transitions)) return; - RandoLevelManager.RandoLevelMapping = - new Dictionary(); - RandoLevelManager.VisitedEntrances = new List(); - if (ArchipelagoClient.Session is not null) - ArchipelagoClient.Session.DataStorage[Scope.Slot, "VisitedEntrances"].Initialize(new List()); + if (slotData.TryGetValue("transitions", out var transitions)) + { var transitionPairs = ((JArray)transitions).ToObject>>(); - if (transitionPairs.Count == 0) RandoLevelManager.RandoLevelMapping = null; + if (transitionPairs.Count == 0) + { + RandoLevelManager.RandoLevelMapping = null; + } else { + RandoLevelManager.RandoLevelMapping = []; foreach (var pairing in transitionPairs) { var orig = LevelConstants.TransitionNames[pairing[0]]; - var replacement = - LevelConstants.EntranceNameToRandoLevel[LevelConstants.TransitionNames[pairing[1]]]; + var replacement = LevelConstants.EntranceNameToRandoLevel[LevelConstants.TransitionNames[pairing[1]]]; RandoLevelManager.RandoLevelMapping[orig] = replacement; - Console.WriteLine($"{orig}: {LevelConstants.TransitionNames[pairing[1]]}"); + Console.WriteLine($"Setting transition from {orig} to {LevelConstants.TransitionNames[pairing[1]]}"); } } } - else - { - RandoPortalManager.StartingPortals = - [ - "AutumnHillsPortal", - "RiviereTurquoisePortal", - "HowlingGrottoPortal", - "SunkenShrinePortal", - "SearingCragsPortal", - "GlacialPeakPortal" - ]; - } if (ArchipelagoClient.Session.Locations.AllLocations.Any(location => ItemsAndLocationsHandler.IDtoLocationsLookup.TryGetValue(location, out var loc)