diff --git a/CrossPlatformUI/Lang/Resources.resx b/CrossPlatformUI/Lang/Resources.resx
index a336781c..1a2dbc39 100644
--- a/CrossPlatformUI/Lang/Resources.resx
+++ b/CrossPlatformUI/Lang/Resources.resx
@@ -353,6 +353,9 @@ There can only be as many duplicates as there are minor items to replace.
What is duplicated, in order (and if they are in the pool) is:
Glove, Downstab, Fairy, Thunder, Reflect, Magic key, Hammer, Raft, Flute, Jump, Upstab,
Boots
+
+
+ When checked, a spell item can never be required to obtain another town item.
Shuffles the experience required to gain each level of the selected attribute. The new EXP
diff --git a/CrossPlatformUI/Presets/BeginnerPreset.cs b/CrossPlatformUI/Presets/BeginnerPreset.cs
index e8e33b7c..86538ca6 100644
--- a/CrossPlatformUI/Presets/BeginnerPreset.cs
+++ b/CrossPlatformUI/Presets/BeginnerPreset.cs
@@ -115,6 +115,7 @@ public static class BeginnerPreset
PalacesContainExtraKeys = true,
RandomizeNewKasutoJarRequirements = true,
AllowImportantItemDuplicates = false,
+ PreventSpellItemChains = false,
//Drops
ShuffleItemDropFrequency = true,
diff --git a/CrossPlatformUI/Presets/FullShufflePreset.cs b/CrossPlatformUI/Presets/FullShufflePreset.cs
index cccc929e..afcfda75 100644
--- a/CrossPlatformUI/Presets/FullShufflePreset.cs
+++ b/CrossPlatformUI/Presets/FullShufflePreset.cs
@@ -119,6 +119,7 @@ public static class FullShufflePreset
PalacesContainExtraKeys = false,
RandomizeNewKasutoJarRequirements = true,
AllowImportantItemDuplicates = false,
+ PreventSpellItemChains = false,
//Drops
ShuffleItemDropFrequency = true,
diff --git a/CrossPlatformUI/Presets/MaxRando2025Preset.cs b/CrossPlatformUI/Presets/MaxRando2025Preset.cs
index 66f70139..1af04b3b 100644
--- a/CrossPlatformUI/Presets/MaxRando2025Preset.cs
+++ b/CrossPlatformUI/Presets/MaxRando2025Preset.cs
@@ -119,6 +119,7 @@ public static class MaxRando2025Preset
PalacesContainExtraKeys = false,
RandomizeNewKasutoJarRequirements = true,
AllowImportantItemDuplicates = false,
+ PreventSpellItemChains = false,
//Drops
ShuffleItemDropFrequency = true,
diff --git a/CrossPlatformUI/Presets/MaxRandoPreset.cs b/CrossPlatformUI/Presets/MaxRandoPreset.cs
index f0856f39..e37d0ee7 100644
--- a/CrossPlatformUI/Presets/MaxRandoPreset.cs
+++ b/CrossPlatformUI/Presets/MaxRandoPreset.cs
@@ -120,6 +120,7 @@ public static class MaxRandoPreset
PalacesContainExtraKeys = false,
RandomizeNewKasutoJarRequirements = true,
AllowImportantItemDuplicates = false,
+ PreventSpellItemChains = false,
//Drops
ShuffleItemDropFrequency = true,
diff --git a/CrossPlatformUI/Presets/NormalPreset.cs b/CrossPlatformUI/Presets/NormalPreset.cs
index 3beb3d1b..c98d4694 100644
--- a/CrossPlatformUI/Presets/NormalPreset.cs
+++ b/CrossPlatformUI/Presets/NormalPreset.cs
@@ -120,6 +120,7 @@ public static class NormalPreset
PalacesContainExtraKeys = false,
RandomizeNewKasutoJarRequirements = true,
AllowImportantItemDuplicates = false,
+ PreventSpellItemChains = false,
//Drops
ShuffleItemDropFrequency = true,
diff --git a/CrossPlatformUI/Presets/RandomPercentPreset.cs b/CrossPlatformUI/Presets/RandomPercentPreset.cs
index ddd0f7ac..ba0a4dc7 100644
--- a/CrossPlatformUI/Presets/RandomPercentPreset.cs
+++ b/CrossPlatformUI/Presets/RandomPercentPreset.cs
@@ -118,6 +118,7 @@ public static class RandomPercentPreset
PalacesContainExtraKeys = null,
RandomizeNewKasutoJarRequirements = true,
AllowImportantItemDuplicates = false,
+ PreventSpellItemChains = false,
//Drops
ShuffleItemDropFrequency = true,
diff --git a/CrossPlatformUI/Presets/Sgl2025Preset.cs b/CrossPlatformUI/Presets/Sgl2025Preset.cs
index 9861d4ec..414a38a9 100644
--- a/CrossPlatformUI/Presets/Sgl2025Preset.cs
+++ b/CrossPlatformUI/Presets/Sgl2025Preset.cs
@@ -122,6 +122,7 @@ public static class Sgl2025Preset
PalacesContainExtraKeys = false,
RandomizeNewKasutoJarRequirements = false,
AllowImportantItemDuplicates = false,
+ PreventSpellItemChains = false,
//Drops
ShuffleItemDropFrequency = false,
diff --git a/CrossPlatformUI/Presets/StandardPreset.cs b/CrossPlatformUI/Presets/StandardPreset.cs
index 15e80ceb..2b07a9bb 100644
--- a/CrossPlatformUI/Presets/StandardPreset.cs
+++ b/CrossPlatformUI/Presets/StandardPreset.cs
@@ -119,6 +119,7 @@ public static class StandardPreset
PalacesContainExtraKeys = false,
RandomizeNewKasutoJarRequirements = true,
AllowImportantItemDuplicates = false,
+ PreventSpellItemChains = false,
//Drops
ShuffleItemDropFrequency = true,
diff --git a/CrossPlatformUI/Presets/StandardSwissPreset.cs b/CrossPlatformUI/Presets/StandardSwissPreset.cs
index eaabb44d..5a071a37 100644
--- a/CrossPlatformUI/Presets/StandardSwissPreset.cs
+++ b/CrossPlatformUI/Presets/StandardSwissPreset.cs
@@ -119,6 +119,7 @@ public static class StandardSwissPreset
PalacesContainExtraKeys = false,
RandomizeNewKasutoJarRequirements = true,
AllowImportantItemDuplicates = false,
+ PreventSpellItemChains = false,
//Drops
ShuffleItemDropFrequency = true,
diff --git a/CrossPlatformUI/Presets/UpstartsTournamentPreset.cs b/CrossPlatformUI/Presets/UpstartsTournamentPreset.cs
index bdce7068..5086ee7b 100644
--- a/CrossPlatformUI/Presets/UpstartsTournamentPreset.cs
+++ b/CrossPlatformUI/Presets/UpstartsTournamentPreset.cs
@@ -113,6 +113,7 @@ public static class UpstartsTournamentPreset
PalacesContainExtraKeys = false,
RandomizeNewKasutoJarRequirements = true,
AllowImportantItemDuplicates = false,
+ PreventSpellItemChains = false,
//Drops
ShuffleItemDropFrequency = true,
diff --git a/CrossPlatformUI/Views/Tabs/ItemsView.axaml b/CrossPlatformUI/Views/Tabs/ItemsView.axaml
index 918d1879..1e468e16 100644
--- a/CrossPlatformUI/Views/Tabs/ItemsView.axaml
+++ b/CrossPlatformUI/Views/Tabs/ItemsView.axaml
@@ -75,6 +75,12 @@
>
+
+
+
true,
+ Collectable.MIRROR => true,
+ Collectable.MEDICINE => true,
+ Collectable.WATER => true,
+ Collectable.CHILD => true,
+ _ => false
+ };
+ }
+
public static bool IsSwordTech(this Collectable collectable)
{
return collectable switch
diff --git a/RandomizerCore/Hyrule.cs b/RandomizerCore/Hyrule.cs
index cf4a04a8..bf16a2c9 100644
--- a/RandomizerCore/Hyrule.cs
+++ b/RandomizerCore/Hyrule.cs
@@ -1212,6 +1212,51 @@ private void ShuffleItems()
}
}
}
+
+ if (props.PreventSpellItemChains)
+ {
+ PreventSpellItemChains();
+ }
+ }
+
+ // Disallow old men from holding other spell items, preventing long
+ // item chains
+ private void PreventSpellItemChains()
+ {
+ List chainTowns = [Town.RUTO, Town.SARIA_NORTH, Town.MIDO_WEST,
+ Town.NABOORU, Town.DARUNIA_WEST];
+
+ bool IsChainLocation(Location l) =>
+ l.ActualTown != null && chainTowns.Contains((Town)l.ActualTown);
+
+ List chainLocations = itemLocs.Where(IsChainLocation).ToList();
+ foreach (Location chainLocation in chainLocations)
+ {
+ for (int i = 0; i < chainLocation.Collectables.Count; i++)
+ {
+ if (!chainLocation.Collectables[i].IsSpellItem())
+ {
+ continue;
+ }
+
+ // Town wizard locations are always single-collectable overworld
+ // locations, so restrict swap targets to other non-chain overworld
+ // locations to avoid desyncing palace item room bookkeeping.
+ Location? target = itemLocs
+ .Where(l => l.PalaceNumber == null && !IsChainLocation(l))
+ .Where(l => l.Collectables.Any(c => !c.IsSpellItem()))
+ .ToList()
+ .Sample(r);
+ if (target == null)
+ {
+ continue;
+ }
+
+ int targetIndex = target.Collectables.FindIndex(c => !c.IsSpellItem());
+ (chainLocation.Collectables[i], target.Collectables[targetIndex]) =
+ (target.Collectables[targetIndex], chainLocation.Collectables[i]);
+ }
+ }
}
private void DoShuffle(List itemsToShuffle, List itemShuffleLocations)
diff --git a/RandomizerCore/RandomizerConfiguration.cs b/RandomizerCore/RandomizerConfiguration.cs
index 9aa1ca92..bff83eb6 100644
--- a/RandomizerCore/RandomizerConfiguration.cs
+++ b/RandomizerCore/RandomizerConfiguration.cs
@@ -604,6 +604,9 @@ private bool palaceStylesAnyMetastyleSelected()
[Reactive]
private bool? includeQuestItemsInShuffle;
+ [Reactive]
+ private bool preventSpellItemChains;
+
//Drops
[Reactive]
private bool shuffleItemDropFrequency;
@@ -1367,6 +1370,7 @@ public RandomizerProperties Export(Random r, bool includeDifficulty = true)
properties.StartWithSpellItems = removeSpellItems ?? GetIndeterminateFlagValue(r);
properties.ShufflePbagXp = shufflePBagAmounts ?? GetIndeterminateFlagValue(r);
properties.IncludeQuestItemsInShuffle = includeQuestItemsInShuffle ?? GetIndeterminateFlagValue(r);
+ properties.PreventSpellItemChains = preventSpellItemChains;
properties.IncludeSpellsInShuffle = includeSpellsInShuffle ?? GetIndeterminateFlagValue(r);
properties.IncludeSwordTechsInShuffle = includeSwordTechsInShuffle ?? GetIndeterminateFlagValue(r);
diff --git a/RandomizerCore/RandomizerProperties.cs b/RandomizerCore/RandomizerProperties.cs
index 271d0d85..d947cf24 100644
--- a/RandomizerCore/RandomizerProperties.cs
+++ b/RandomizerCore/RandomizerProperties.cs
@@ -176,6 +176,8 @@ public class RandomizerProperties
public bool IncludeSwordTechsInShuffle { get; set; }
//Bagu's note / fountain water / saria mirror
public bool IncludeQuestItemsInShuffle { get; set; }
+ //Spell items may not be required to obtain another spell item
+ public bool PreventSpellItemChains { get; set; }
public bool RandomizeSmallItems { get; set; }
public bool ExtraKeys { get; set; }
public bool AllowImportantItemDuplicates { get; set; }