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; }