├── .gitattributes ├── .github └── workflows │ └── github-pages.yml ├── .gitignore ├── LICENSE ├── LOGIC_README.md ├── README.md ├── RandomizerMod.sln ├── RandomizerMod ├── Extensions │ ├── ICExtensions.cs │ └── StringExtensions.cs ├── IC │ ├── CostConversion.cs │ ├── DupeUIDef.cs │ ├── Export.cs │ ├── GSComparison.cs │ ├── Grubfather.cs │ ├── HelperLogModule.cs │ ├── NailUpgradeWarningModule.cs │ ├── PlatformList.cs │ ├── RandoItemTag.cs │ ├── RandoPlacementTag.cs │ ├── RandomizerModule.cs │ ├── Seer.cs │ ├── Shops.cs │ ├── TrackerLogModule.cs │ └── TrackerUpdate.cs ├── Localization.cs ├── LogHelper.cs ├── Logging │ ├── ItemSpoilerLog.cs │ ├── LogArguments.cs │ ├── LogManager.cs │ ├── NotchCostSpoilerLog.cs │ ├── RandoLogger.cs │ ├── SettingsLog.cs │ └── TransitionSpoilerLog.cs ├── Menu │ ├── Hash.cs │ ├── ModMenu.cs │ ├── RandomizerMenu.cs │ └── RandomizerMenuAPI.cs ├── PriorityEvent.cs ├── Properties │ └── AssemblyInfo.cs ├── RC │ ├── CharmNotchCosts.cs │ ├── CustomGeoItem.cs │ ├── ItemPlacement.cs │ ├── LogicConstUtil.cs │ ├── LogicGeoCost.cs │ ├── LogicInts │ │ ├── NotchCostInt.cs │ │ ├── SafeNotchCostInt.cs │ │ └── StartLocationDelta.cs │ ├── PlaceholderItem.cs │ ├── ProgressionInitializer.cs │ ├── RCData.cs │ ├── RandoController.cs │ ├── RandoModContext.cs │ ├── RandoModItem.cs │ ├── RandoModLocation.cs │ ├── RandoModTransition.cs │ ├── RandoVariableResolver.cs │ ├── Requests │ │ ├── Bucket.cs │ │ ├── BuiltinRequests.cs │ │ ├── CDFWeightedArray.cs │ │ ├── GroupBuilder.cs │ │ ├── ICFactory.cs │ │ ├── ItemGroupBuilder.cs │ │ ├── ItemRequestInfo.cs │ │ ├── LocationRequestInfo.cs │ │ ├── RBConsts.cs │ │ ├── RandoFactory.cs │ │ ├── RequestBuilder.cs │ │ ├── SelfDualTransitionGroupBuilder.cs │ │ ├── StageBuilder.cs │ │ ├── SymmetricTransitionGroupBuilder.cs │ │ ├── TransitionGroupBuilder.cs │ │ └── TransitionRequestInfo.cs │ ├── SplitCloakItem.cs │ ├── StateVariables │ │ ├── BenchResetVariable.cs │ │ ├── CastSpellVariable.cs │ │ ├── EquipCharmVariable.cs │ │ ├── FlowerProviderVariable.cs │ │ ├── FragileCharmVariable.cs │ │ ├── HPStateManager.cs │ │ ├── HotSpringResetVariable.cs │ │ ├── LifebloodCountVariable.cs │ │ ├── RegainSoulVariable.cs │ │ ├── SaveQuitResetVariable.cs │ │ ├── ShadeStateVariable.cs │ │ ├── ShriekPogoVariable.cs │ │ ├── SlopeballVariable.cs │ │ ├── SoulStateManager.cs │ │ ├── SpendSoulVariable.cs │ │ ├── StagStateVariable.cs │ │ ├── StartRespawnResetVariable.cs │ │ ├── StateModifierWrapper.cs │ │ ├── TakeDamageVariable.cs │ │ ├── WarpToBenchResetVariable.cs │ │ ├── WarpToStartResetVariable.cs │ │ └── WhiteFragmentEquipVariable.cs │ └── TransitionPlacement.cs ├── RandomizerData │ ├── CostDef.cs │ ├── Data.cs │ ├── ItemDef.cs │ ├── JsonUtil.cs │ ├── LocationDef.cs │ ├── PoolDef.cs │ ├── PoolNames.cs │ ├── RoomDef.cs │ ├── StartDef.cs │ ├── TransitionDef.cs │ └── VanillaDef.cs ├── RandomizerMod.cs ├── RandomizerMod.csproj ├── Resources │ ├── Data │ │ ├── costs.json │ │ ├── items.json │ │ ├── locations.json │ │ ├── logic_settings.json │ │ ├── pools.json │ │ ├── rooms.json │ │ ├── starts.json │ │ └── transitions.json │ ├── Logic │ │ ├── items.json │ │ ├── locations.json │ │ ├── macros.json │ │ ├── state.json │ │ ├── terms.json │ │ ├── transitions.json │ │ └── waypoints.json │ ├── entries.txt │ └── logo.png └── Settings │ ├── BinaryFormatting.cs │ ├── CostSettings.cs │ ├── CursedSettings.cs │ ├── DuplicateItemSettings.cs │ ├── GenerationSettings.cs │ ├── GlobalSettings.cs │ ├── LongLocationSettings.cs │ ├── MiscSettings.cs │ ├── NoveltySettings.cs │ ├── PoolSettings.cs │ ├── Presets │ ├── Captions.cs │ ├── CostPresetData.cs │ ├── CursePresetData.cs │ ├── DuplicateItemPresetData.cs │ ├── LongLocationPresetData.cs │ ├── MiscPresetData.cs │ ├── NoveltyPresetData.cs │ ├── PoolPresetData.cs │ ├── ProgressionDepthPresetData.cs │ ├── SkipPresetData.cs │ ├── SplitGroupPresetData.cs │ ├── StartItemPresetData.cs │ ├── StartLocationPresetData.cs │ └── TransitionPresetData.cs │ ├── ProgressionDepthSettings.cs │ ├── RandomizerSettings.cs │ ├── SettingsModule.cs │ ├── SettingsPM.cs │ ├── SkipSettings.cs │ ├── SplitGroupSettings.cs │ ├── StartItemSettings.cs │ ├── StartLocationSettings.cs │ ├── TrackerData.cs │ ├── TransitionSettings.cs │ └── Util.cs └── RandomizerModTests ├── Extensions.cs ├── LogicFixture.cs ├── MiscTests.cs ├── RandomizerModTests.csproj ├── StateVariables ├── CastSpellVariableTests.cs ├── EquipCharmVariableTests.cs ├── HPStateManagerTests.cs ├── LifebloodCountVariableTests.cs ├── ShadeSkipVariableTests.cs ├── ShriekPogoVariableTests.cs ├── SlopeballVariableTests.cs ├── SoulStateManagerTests.cs └── TakeDamageVariableTests.cs └── Usings.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/github-pages.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: [ master, docs-source ] 6 | 7 | jobs: 8 | generate-docs: 9 | runs-on: windows-2022 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | with: 15 | ref: master 16 | - name: Checkout doc source 17 | uses: actions/checkout@v3 18 | with: 19 | ref: docs-source 20 | path: docs 21 | 22 | # setup .NET 23 | - name: Setup .NET 24 | uses: actions/setup-dotnet@v3 25 | 26 | # Install DocFX 27 | - name: Setup DocFX 28 | uses: crazy-max/ghaction-chocolatey@v1 29 | with: 30 | args: install docfx 31 | 32 | # Build and publish docs 33 | - name: DocFX build 34 | working-directory: docs 35 | run: docfx docfx.json 36 | continue-on-error: false 37 | 38 | - name: Publish 39 | uses: peaceiris/actions-gh-pages@v3 40 | with: 41 | github_token: ${{ secrets.GITHUB_TOKEN }} 42 | publish_dir: docs/_site 43 | force_orphan: true 44 | -------------------------------------------------------------------------------- /RandomizerMod.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33530.505 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RandomizerMod", "RandomizerMod\RandomizerMod.csproj", "{8B1AB441-2E8A-49EB-87BD-8E1C9729AD00}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RandomizerModTests", "RandomizerModTests\RandomizerModTests.csproj", "{5167F8F2-9E73-473B-B039-F03CC1499702}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {8B1AB441-2E8A-49EB-87BD-8E1C9729AD00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {8B1AB441-2E8A-49EB-87BD-8E1C9729AD00}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {8B1AB441-2E8A-49EB-87BD-8E1C9729AD00}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {8B1AB441-2E8A-49EB-87BD-8E1C9729AD00}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {5167F8F2-9E73-473B-B039-F03CC1499702}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {5167F8F2-9E73-473B-B039-F03CC1499702}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {5167F8F2-9E73-473B-B039-F03CC1499702}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {5167F8F2-9E73-473B-B039-F03CC1499702}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {37A3DAA4-C744-471C-BD9D-9DF4229E8EFC} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /RandomizerMod/Extensions/ICExtensions.cs: -------------------------------------------------------------------------------- 1 | using ItemChanger; 2 | using RandomizerMod.IC; 3 | using RandomizerMod.RC; 4 | 5 | namespace RandomizerMod.Extensions 6 | { 7 | /// 8 | /// Extensions for accessing randomizer data from IC classes. 9 | /// 10 | public static class ICExtensions 11 | { 12 | /// 13 | /// Enumerates the ItemPlacements indicated by the placement's RandoPlacementTag. 14 | ///
Returns an empty enumerable if the placement does not have a tag. 15 | ///
16 | public static IEnumerable RandoPlacements(this AbstractPlacement placement) 17 | { 18 | if (placement.GetTag(out RandoPlacementTag tag)) 19 | { 20 | return tag.ids.Select(id => RandomizerMod.RS.Context.itemPlacements[id]); 21 | } 22 | return Enumerable.Empty(); 23 | } 24 | 25 | /// 26 | /// Gets a RandoModLocation corresponding to the placement's RandoPlacementTag. Returns null if the placement does not have a tag. 27 | ///
Warning: different RandoModLocations corresponding to the same placement may have different behavior due to costs. 28 | ///
29 | public static RandoModLocation? RandoLocation(this AbstractPlacement placement) 30 | { 31 | if (placement.GetTag(out RandoPlacementTag tag)) 32 | { 33 | return RandomizerMod.RS.Context.itemPlacements[tag.ids.First()].Location; 34 | } 35 | return null; 36 | } 37 | 38 | /// 39 | /// Gets the ItemPlacement indicated by the item's RandoItemTag. Returns default if the item does not have a tag. 40 | /// 41 | public static ItemPlacement RandoPlacement(this AbstractItem item) 42 | { 43 | if (item.GetTag(out RandoItemTag tag)) 44 | { 45 | return RandomizerMod.RS.Context.itemPlacements[tag.id]; 46 | } 47 | return default; 48 | } 49 | 50 | /// 51 | /// Gets the RandoModItem corresponding to the item's RandoItemTag. Returns null if the item does not have a tag. 52 | /// 53 | public static RandoModItem? RandoItem(this AbstractItem item) 54 | { 55 | return item.RandoPlacement().Item; 56 | } 57 | 58 | /// 59 | /// Gets the RandoModLocation corresponding to the item's RandoItemTag. Returns null if the item does not have a tag. 60 | /// 61 | public static RandoModLocation? RandoLocation(this AbstractItem item) 62 | { 63 | return item.RandoPlacement().Location; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /RandomizerMod/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Extensions 2 | { 3 | public static class StringExtensions 4 | { 5 | public static int GetStableHashCode(this string str) 6 | { 7 | unchecked 8 | { 9 | int hash1 = 5381; 10 | int hash2 = hash1; 11 | 12 | for (int i = 0; i < str.Length && str[i] != '\0'; i += 2) 13 | { 14 | hash1 = ((hash1 << 5) + hash1) ^ str[i]; 15 | if (i == str.Length - 1 || str[i + 1] == '\0') 16 | break; 17 | hash2 = ((hash2 << 5) + hash2) ^ str[i + 1]; 18 | } 19 | 20 | return hash1 + (hash2 * 1566083941); 21 | } 22 | } 23 | 24 | public static bool TryToEnum(this string self, out T val) where T : Enum 25 | { 26 | try 27 | { 28 | val = (T)Enum.Parse(typeof(T), self); 29 | return true; 30 | } 31 | catch 32 | { 33 | val = default; 34 | return false; 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /RandomizerMod/IC/DupeUIDef.cs: -------------------------------------------------------------------------------- 1 | using ItemChanger; 2 | using ItemChanger.UIDefs; 3 | using UnityEngine; 4 | 5 | namespace RandomizerMod.IC 6 | { 7 | /// 8 | /// Class for converting an arbitrary UIDef on a redundant item to a MsgUIDef which displays the amount of geo replacing the item. 9 | /// 10 | public class DupeUIDef : MsgUIDef 11 | { 12 | public static MsgUIDef Convert(int geoAmount, UIDef orig) 13 | { 14 | if (orig is MsgUIDef msgDef) 15 | { 16 | return new SplitUIDef 17 | { 18 | preview = new BoxedString(msgDef.GetPreviewName()), 19 | name = new BoxedString($"{geoAmount} ({msgDef.GetPostviewName()})"), 20 | shopDesc = msgDef.shopDesc?.Clone(), 21 | sprite = msgDef.sprite?.Clone(), 22 | }; 23 | } 24 | else return new DupeUIDef(geoAmount, orig); 25 | } 26 | 27 | private DupeUIDef(int geoAmount, UIDef inner) 28 | { 29 | GeoAmount = geoAmount; 30 | Inner = inner; 31 | 32 | if (inner is null) 33 | { 34 | base.name = new BoxedString($"{GeoAmount} (Dupe)"); 35 | base.shopDesc = new BoxedString(""); 36 | base.sprite = new ItemChangerSprite("ShopIcons.Geo"); 37 | } 38 | else 39 | { 40 | // these fields will not be accessed normally, but should still have values for compatibility 41 | base.name = new BoxedString($"{GeoAmount} Geo ({inner?.GetPostviewName()})"); 42 | base.shopDesc = new BoxedString(inner?.GetShopDesc()); 43 | base.sprite = new EmptySprite(); 44 | } 45 | } 46 | 47 | public int GeoAmount; 48 | public UIDef Inner; 49 | public override Sprite GetSprite() => Inner is not null 50 | ? Inner.GetSprite() 51 | : base.GetSprite(); 52 | public override string GetPreviewName() => Inner is not null 53 | ? Inner.GetPreviewName() 54 | : base.GetPreviewName(); 55 | public override string GetPostviewName() => Inner is not null 56 | ? $"{GeoAmount} Geo ({Inner?.GetPostviewName()})" 57 | : base.GetPostviewName(); 58 | public override string GetShopDesc() => Inner is not null 59 | ? Inner.GetShopDesc() 60 | : base.GetShopDesc(); 61 | public override UIDef Clone() 62 | { 63 | return new DupeUIDef(GeoAmount, Inner); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /RandomizerMod/IC/GSComparison.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using ItemChanger; 7 | using RandomizerMod.Settings; 8 | 9 | namespace RandomizerMod.IC 10 | { 11 | /// 12 | /// Compare a settings field to a given value. 13 | /// 14 | public class GSComparison : IBool where T : IComparable 15 | { 16 | /// 17 | /// Dot separators: e.g. SkipSettings.MildSkips 18 | /// 19 | public string FieldPath { get; init; } 20 | 21 | public T Target { get; init; } 22 | 23 | public ComparisonOperator Op { get; init; } = ComparisonOperator.Eq; 24 | 25 | public bool Value => ItemChanger.Extensions.Extensions.Compare((T)RandomizerMod.RS.GenerationSettings.Get(FieldPath), Op, Target); 26 | 27 | public IBool Clone() => (IBool)MemberwiseClone(); 28 | 29 | public GSComparison() { } 30 | 31 | public GSComparison(string fieldName) 32 | { 33 | FieldPath = Util.GetPath(fieldName); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /RandomizerMod/IC/Grubfather.cs: -------------------------------------------------------------------------------- 1 | using ItemChanger; 2 | using RandomizerMod.Settings; 3 | using RandomizerMod.RC; 4 | using ItemChanger.Locations; 5 | 6 | namespace RandomizerMod.IC 7 | { 8 | public static class Grubfather 9 | { 10 | public static AbstractPlacement GetGrubfatherPlacement(ICFactory factory) 11 | { 12 | AbstractPlacement p = factory.MakeLocation(LocationNames.Grubfather).Wrap(); 13 | p.AddTag().destroyRewards = GetRandomizedGrubRewards(factory.gs); 14 | return p; 15 | } 16 | 17 | public static GrubfatherRewards GetRandomizedGrubRewards(GenerationSettings gs) 18 | { 19 | GrubfatherRewards gr = GrubfatherRewards.None; 20 | if (gs.PoolSettings.Charms) 21 | { 22 | gr |= GrubfatherRewards.Grubsong | GrubfatherRewards.GrubberflysElegy; 23 | } 24 | if (gs.PoolSettings.MaskShards) 25 | { 26 | gr |= GrubfatherRewards.MaskShard; 27 | } 28 | if (gs.PoolSettings.PaleOre) 29 | { 30 | gr |= GrubfatherRewards.PaleOre; 31 | } 32 | if (gs.PoolSettings.Relics) 33 | { 34 | gr |= GrubfatherRewards.HallownestSeal | GrubfatherRewards.KingsIdol; 35 | } 36 | if (gs.PoolSettings.RancidEggs) 37 | { 38 | gr |= GrubfatherRewards.RancidEgg; 39 | } 40 | 41 | return gr; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /RandomizerMod/IC/NailUpgradeWarningModule.cs: -------------------------------------------------------------------------------- 1 | using ItemChanger; 2 | using ItemChanger.Modules; 3 | 4 | namespace RandomizerMod.IC 5 | { 6 | /* From LOGIC_README.md: 7 | - With skips allowed, the player is advised to take care to not get locked out of certain required pogos. Obtain: 8 | - No more than 1 nail upgrade before claw or wings 9 | - No more than 3 nail upgrades before claw 10 | - In Split Claw mode, instead obtain no more than 2 nail upgrades with only a single claw piece, and any number of nail upgrades with a single claw piece as well as wings 11 | */ 12 | 13 | public class NailUpgradeWarningModule : Module 14 | { 15 | public override void Initialize() 16 | { 17 | ToggleLanguageHook(true); 18 | } 19 | 20 | public override void Unload() 21 | { 22 | ToggleLanguageHook(false); 23 | } 24 | 25 | private void ToggleLanguageHook(bool toggle) 26 | { 27 | if (toggle) 28 | { 29 | Events.AddLanguageEdit(new("Nailsmith", "NAILSMITH_OFFER_ORE"), NailUpgradeWarning); 30 | } 31 | else 32 | { 33 | Events.RemoveLanguageEdit(new("Nailsmith", "NAILSMITH_OFFER_ORE"), NailUpgradeWarning); 34 | } 35 | } 36 | 37 | private static void NailUpgradeWarning(ref string s) 38 | { 39 | bool claw = PlayerData.instance.GetBool(nameof(PlayerData.hasWalljump)); 40 | bool wings = PlayerData.instance.GetBool(nameof(PlayerData.hasDoubleJump)); 41 | SplitClaw? scm = ItemChangerMod.Modules.Get(); 42 | int level = PlayerData.instance.GetInt(nameof(PlayerData.nailSmithUpgrades)); 43 | const int repetitions = 10; 44 | 45 | switch (level + 1) 46 | { 47 | case >= 2 when !(claw || wings || scm is not null && scm.hasWalljumpAny): 48 | CreateMessage( 49 | Localize("WARNING -- obtaining more than one nail upgrade before collecting one of Mantis Claw or Monarch Wings may lock out required enemy pogos!"), 50 | repetitions, ref s); 51 | break; 52 | case >= 3 when scm is not null && !(scm.hasWalljumpAny && wings || scm.hasWalljumpBoth): 53 | CreateMessage( 54 | Localize("WARNING -- obtaining more than two nail upgrades before collecting two out of three of Left Mantis Claw, Right Mantis Claw, and Monarch Wings may lock out required enemy pogos!"), 55 | repetitions, ref s); 56 | break; 57 | case >= 4 when !(claw && wings): 58 | CreateMessage( 59 | Localize("WARNING -- obtaining more than three nail upgrades before collecting both Mantis Claw and Monarch Wings may lock out required enemy pogos!"), 60 | repetitions, ref s); 61 | break; 62 | } 63 | } 64 | 65 | private static void CreateMessage(string warning, int times, ref string result) 66 | { 67 | result = string.Empty; 68 | for (int i = 0; i < times - 1; i++) 69 | { 70 | result += warning + $" ({i + 1}/{times})" + ""; 71 | } 72 | result += warning + $" ({times}/{times})"; 73 | } 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /RandomizerMod/IC/RandoItemTag.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using ItemChanger; 7 | 8 | namespace RandomizerMod.IC 9 | { 10 | public class RandoItemTag : Tag 11 | { 12 | public static event Action AfterRandoItemGive; 13 | public static event Action OnLoad; 14 | 15 | public int id; 16 | public bool obtained = false; 17 | 18 | public override void Load(object parent) 19 | { 20 | ((AbstractItem)parent).AfterGive += Broadcast; 21 | try 22 | { 23 | OnLoad?.Invoke((AbstractItem)parent, this); 24 | } 25 | catch (Exception e) 26 | { 27 | LogError($"Error invoking RandoItemTag.OnLoad:\n{e}"); 28 | } 29 | } 30 | 31 | public override void Unload(object parent) 32 | { 33 | ((AbstractItem)parent).AfterGive -= Broadcast; 34 | } 35 | 36 | private void Broadcast(ReadOnlyGiveEventArgs args) 37 | { 38 | if (!obtained) AfterRandoItemGive?.Invoke(id, args); 39 | obtained = true; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /RandomizerMod/IC/RandoPlacementTag.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using ItemChanger; 7 | 8 | namespace RandomizerMod.IC 9 | { 10 | public class RandoPlacementTag : Tag 11 | { 12 | public static event Action OnRandoPlacementVisitStateChanged; 13 | public static event Action OnLoad; 14 | 15 | /// 16 | /// The ids of the item placements managed by this placement. 17 | /// 18 | public List ids = new(); 19 | 20 | public override void Load(object parent) 21 | { 22 | ((AbstractPlacement)parent).OnVisitStateChanged += OnVisitStateChanged; 23 | try 24 | { 25 | OnLoad?.Invoke((AbstractPlacement)parent, this); 26 | } 27 | catch (Exception e) 28 | { 29 | LogError($"Error invoking RandoPlacementTag.OnLoad:\n{e}"); 30 | } 31 | } 32 | 33 | public override void Unload(object parent) 34 | { 35 | ((AbstractPlacement)parent).OnVisitStateChanged -= OnVisitStateChanged; 36 | } 37 | 38 | private void OnVisitStateChanged(VisitStateChangedEventArgs args) 39 | { 40 | OnRandoPlacementVisitStateChanged?.Invoke(args); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /RandomizerMod/IC/Seer.cs: -------------------------------------------------------------------------------- 1 | using ItemChanger; 2 | using RandomizerMod.Settings; 3 | using RandomizerMod.RC; 4 | using ItemChanger.Locations; 5 | 6 | namespace RandomizerMod.IC 7 | { 8 | public static class Seer 9 | { 10 | public static AbstractPlacement GetSeerPlacement(ICFactory factory) 11 | { 12 | AbstractPlacement p = factory.MakeLocation(LocationNames.Seer).Wrap(); 13 | p.AddTag().destroyRewards = GetRandomizedSeerRewards(factory.gs); 14 | return p; 15 | } 16 | public static SeerRewards GetRandomizedSeerRewards(GenerationSettings gs) 17 | { 18 | SeerRewards sr = SeerRewards.None; 19 | if (gs.PoolSettings.Relics) 20 | { 21 | sr |= SeerRewards.HallownestSeal | SeerRewards.ArcaneEgg; 22 | } 23 | if (gs.PoolSettings.PaleOre) 24 | { 25 | sr |= SeerRewards.PaleOre; 26 | } 27 | if (gs.PoolSettings.Charms) 28 | { 29 | sr |= SeerRewards.DreamWielder; 30 | } 31 | if (gs.PoolSettings.VesselFragments) 32 | { 33 | sr |= SeerRewards.VesselFragment; 34 | } 35 | if (gs.PoolSettings.Skills) 36 | { 37 | sr |= SeerRewards.DreamGate | SeerRewards.AwokenDreamNail; 38 | } 39 | if (gs.PoolSettings.MaskShards) 40 | { 41 | sr |= SeerRewards.MaskShard; 42 | } 43 | 44 | return sr; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /RandomizerMod/IC/Shops.cs: -------------------------------------------------------------------------------- 1 | using ItemChanger; 2 | using RandomizerMod.Settings; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace RandomizerMod.IC 10 | { 11 | public static class Shops 12 | { 13 | public static DefaultShopItems GetDefaultShopItems(GenerationSettings gs) 14 | { 15 | DefaultShopItems items = DefaultShopItems.None; 16 | 17 | items |= DefaultShopItems.IseldaMapPins; 18 | items |= DefaultShopItems.IseldaMapMarkers; 19 | items |= DefaultShopItems.LegEaterRepair; 20 | 21 | if (!gs.PoolSettings.Keys) 22 | { 23 | items |= DefaultShopItems.SlyLantern; 24 | items |= DefaultShopItems.SlySimpleKey; 25 | items |= DefaultShopItems.SlyKeyElegantKey; 26 | } 27 | 28 | if (!gs.PoolSettings.Charms) 29 | { 30 | items |= DefaultShopItems.SlyCharms; 31 | items |= DefaultShopItems.SlyKeyCharms; 32 | items |= DefaultShopItems.IseldaCharms; 33 | items |= DefaultShopItems.SalubraCharms; 34 | items |= DefaultShopItems.LegEaterCharms; 35 | } 36 | 37 | if (!gs.PoolSettings.Maps) 38 | { 39 | items |= DefaultShopItems.IseldaQuill; 40 | items |= DefaultShopItems.IseldaMaps; 41 | } 42 | 43 | if (!gs.PoolSettings.MaskShards) 44 | { 45 | items |= DefaultShopItems.SlyMaskShards; 46 | } 47 | 48 | if (!gs.PoolSettings.VesselFragments) 49 | { 50 | items |= DefaultShopItems.SlyVesselFragments; 51 | } 52 | 53 | if (!gs.PoolSettings.RancidEggs) 54 | { 55 | items |= DefaultShopItems.SlyRancidEgg; 56 | } 57 | 58 | if (!gs.PoolSettings.CharmNotches && gs.MiscSettings.SalubraNotches == MiscSettings.SalubraNotchesSetting.GroupedWithCharmNotchesPool 59 | || gs.MiscSettings.SalubraNotches == MiscSettings.SalubraNotchesSetting.Vanilla 60 | || gs.MiscSettings.SalubraNotches == MiscSettings.SalubraNotchesSetting.AutoGivenAtCharmThreshold) 61 | { 62 | items |= DefaultShopItems.SalubraNotches; 63 | items |= DefaultShopItems.SalubraBlessing; 64 | } 65 | 66 | return items; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /RandomizerMod/LogHelper.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable file UnusedMember.Global 2 | 3 | 4 | using System; 5 | using System.Diagnostics; 6 | 7 | namespace RandomizerMod 8 | { 9 | public static class LogHelper 10 | { 11 | public static event Action OnLog; 12 | 13 | public static void Log(string message = "") 14 | { 15 | OnLog?.Invoke(message); 16 | } 17 | 18 | public static void Log(object message) 19 | { 20 | Log(message.ToString()); 21 | } 22 | 23 | [Conditional("DEBUG")] 24 | public static void LogDebug(string message) 25 | { 26 | OnLog?.Invoke(message); 27 | } 28 | 29 | public static void LogError(string message) 30 | { 31 | OnLog?.Invoke(message); 32 | } 33 | 34 | public static void LogError(object message) 35 | { 36 | OnLog?.Invoke(message.ToString()); 37 | } 38 | 39 | public static void LogWarn(string message) 40 | { 41 | OnLog?.Invoke(message); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /RandomizerMod/Logging/ItemSpoilerLog.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using RandomizerCore; 3 | using RandomizerMod.RC; 4 | 5 | namespace RandomizerMod.Logging 6 | { 7 | class ItemSpoilerLog : RandoLogger 8 | { 9 | public readonly struct SpoilerEntry 10 | { 11 | public readonly string item; 12 | public readonly string location; 13 | public readonly string[] costs; 14 | 15 | public SpoilerEntry(ItemPlacement p) 16 | { 17 | item = p.Item.Name; 18 | location = p.Location.Name; 19 | costs = p.Location.costs?.Select(c => c.ToString())?.ToArray(); 20 | } 21 | } 22 | 23 | public override void Log(LogArguments args) 24 | { 25 | JsonSerializer js = new() 26 | { 27 | DefaultValueHandling = DefaultValueHandling.Ignore, 28 | Formatting = Formatting.Indented, 29 | }; 30 | using StringWriter sw = new(); 31 | js.Serialize(sw, args.ctx.itemPlacements.Select(p => new SpoilerEntry(p)).ToList()); 32 | LogManager.Write(sw.ToString(), "ItemSpoilerLog.json"); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /RandomizerMod/Logging/LogArguments.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore; 2 | using RandomizerMod.RC; 3 | using RandomizerMod.Settings; 4 | 5 | namespace RandomizerMod.Logging 6 | { 7 | public class LogArguments 8 | { 9 | public object randomizer { get; init; } 10 | public GenerationSettings gs { get; init; } 11 | public RandoModContext ctx { get; init; } 12 | public Dictionary properties { get; init; } = new(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /RandomizerMod/Logging/NotchCostSpoilerLog.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace RandomizerMod.Logging 4 | { 5 | class NotchCostSpoilerLog : RandoLogger 6 | { 7 | static string[] _charmNames = new string[] 8 | { 9 | "Gathering_Swarm", 10 | "Wayward_Compass", 11 | "Grubsong", 12 | "Stalwart_Shell", 13 | "Baldur_Shell", 14 | "Fury_of_the_Fallen", 15 | "Quick_Focus", 16 | "Lifeblood_Heart", 17 | "Lifeblood_Core", 18 | "Defender's_Crest", 19 | "Flukenest", 20 | "Thorns_of_Agony", 21 | "Mark_of_Pride", 22 | "Steady_Body", 23 | "Heavy_Blow", 24 | "Sharp_Shadow", 25 | "Spore_Shroom", 26 | "Longnail", 27 | "Shaman_Stone", 28 | "Soul_Catcher", 29 | "Soul_Eater", 30 | "Glowing_Womb", 31 | "Fragile_Heart", 32 | "Fragile_Greed", 33 | "Fragile_Strength", 34 | "Nailmaster's_Glory", 35 | "Joni's_Blessing", 36 | "Shape_of_Unn", 37 | "Hiveblood", 38 | "Dream_Wielder", 39 | "Dashmaster", 40 | "Quick_Slash", 41 | "Spell_Twister", 42 | "Deep_Focus", 43 | "Grubberfly's_Elegy", 44 | "Kingsoul", 45 | "Sprintmaster", 46 | "Dreamshield", 47 | "Weaversong", 48 | "Grimmchild", 49 | }; 50 | 51 | public override void Log(LogArguments args) 52 | { 53 | List notchCosts = args.ctx.notchCosts; 54 | if (notchCosts is null || notchCosts.Count < 40) return; 55 | 56 | Dictionary costLookup = new(40); 57 | for (int i = 0; i < 40; i++) 58 | { 59 | costLookup[_charmNames[i]] = notchCosts[i]; 60 | } 61 | 62 | JsonSerializer js = new() 63 | { 64 | DefaultValueHandling = DefaultValueHandling.Ignore, 65 | Formatting = Formatting.Indented, 66 | }; 67 | LogManager.Write((tw) => js.Serialize(tw, costLookup), "NotchCostSpoilerLog.json"); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /RandomizerMod/Logging/RandoLogger.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Logging 2 | { 3 | public abstract class RandoLogger 4 | { 5 | public abstract void Log(LogArguments args); 6 | internal void DoLog(LogArguments args) 7 | { 8 | try 9 | { 10 | Log(args); 11 | } 12 | catch (Exception e) 13 | { 14 | LogError($"Error in RandoLogger {GetType().Name}:\n{e}"); 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /RandomizerMod/Logging/SettingsLog.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using RandomizerMod.RandomizerData; 3 | 4 | namespace RandomizerMod.Logging 5 | { 6 | public class SettingsLog : RandoLogger 7 | { 8 | public static event Action AfterLogSettings; 9 | 10 | public override void Log(LogArguments args) 11 | { 12 | LogManager.Write(tw => 13 | { 14 | tw.WriteLine("Logging RandomizerMod GenerationSettings:"); 15 | using JsonTextWriter jtw = new(tw); 16 | JsonUtil._js.Serialize(jtw, args.gs); 17 | tw.WriteLine(); 18 | tw.WriteLine("Logging menu GenerationSettings code:"); 19 | tw.WriteLine(RandomizerMod.GS.DefaultMenuSettings.Serialize()); 20 | tw.WriteLine("Logging final GenerationSettings code:"); 21 | tw.WriteLine(args.gs.Serialize()); 22 | try 23 | { 24 | AfterLogSettings?.Invoke(args, tw); 25 | } 26 | catch (Exception e) 27 | { 28 | LogError($"Error invoking AfterLogSettings:\n{e}"); 29 | } 30 | }, "settings.txt"); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /RandomizerMod/Logging/TransitionSpoilerLog.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore; 2 | using RandomizerMod.RC; 3 | 4 | namespace RandomizerMod.Logging 5 | { 6 | class TransitionSpoilerLog : RandoLogger 7 | { 8 | public readonly struct SpoilerEntry 9 | { 10 | public readonly string source; 11 | public readonly string target; 12 | 13 | public SpoilerEntry(TransitionPlacement p) 14 | { 15 | source = p.Source.Name; 16 | target = p.Target.Name; 17 | } 18 | } 19 | 20 | public override void Log(LogArguments args) 21 | { 22 | string contents = RandomizerData.JsonUtil.Serialize(args.ctx.transitionPlacements.Select(p => new SpoilerEntry(p)).ToList()); 23 | LogManager.Write(contents, "TransitionSpoilerLog.json"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /RandomizerMod/Menu/Hash.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Menu 2 | { 3 | public static class Hash 4 | { 5 | public const int Length = 4; 6 | public static string[] Entries; 7 | 8 | public static string[] GetHash(int seed, int length = Length) 9 | { 10 | Random rng = new(seed + length); 11 | string[] arr = new string[length]; 12 | for (int i = 0; i < length; i++) 13 | { 14 | arr[i] = Entries[rng.Next(Entries.Length)]; 15 | } 16 | 17 | return arr; 18 | } 19 | 20 | static Hash() 21 | { 22 | using Stream stream = typeof(Hash).Assembly.GetManifestResourceStream("RandomizerMod.Resources.entries.txt"); 23 | using StreamReader sr = new(stream); 24 | List strs = new(); 25 | while (sr.ReadLine() is string s) strs.Add(s); 26 | Entries = strs.ToArray(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /RandomizerMod/Menu/ModMenu.cs: -------------------------------------------------------------------------------- 1 | using ItemChanger.Internal.Menu; 2 | using DirectoryOptions = RandomizerMod.Menu.RandomizerMenu.DirectoryOptions; 3 | 4 | namespace RandomizerMod.Menu 5 | { 6 | public static class ModMenu 7 | { 8 | public static MenuScreen GetRandomizerMenuScreen(MenuScreen modListMenu) 9 | { 10 | ModMenuScreenBuilder builder = new(Localize("Randomizer 4"), modListMenu); 11 | builder.AddButton(Localize("Open Log Folder"), null, () => RandomizerMenu.OpenFile(null, string.Empty, DirectoryOptions.RecentLogFolder)); 12 | builder.AddButton(Localize("Open Helper Log"), null, () => RandomizerMenu.OpenFile(null, "HelperLog.txt", DirectoryOptions.RecentLogFolder)); 13 | builder.AddButton(Localize("Open Tracker Log"), null, () => RandomizerMenu.OpenFile(null, "TrackerLog.txt", DirectoryOptions.RecentLogFolder)); 14 | #if DEBUG 15 | builder.AddButton(Localize("Reset Profiling Data"), null, () => RandomizerCore.Profiling.Reset()); 16 | #endif 17 | return builder.CreateMenuScreen(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /RandomizerMod/PriorityEvent.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Collections; 2 | 3 | namespace RandomizerMod 4 | { 5 | public class PriorityEvent 6 | { 7 | public PriorityEvent(out IPriorityEventOwner p) 8 | { 9 | p = new PriorityEventOwner(this); 10 | } 11 | 12 | private readonly SortedArrayList _entries = new(Comparer.Default, EqualityComparer.Default); 13 | private PriorityEntry[] _cachedSubscriberArray; 14 | private bool _cacheInvalidated = true; 15 | 16 | public void Subscribe(float key, T value) 17 | { 18 | _entries.Add(new(key, value)); 19 | _cacheInvalidated = true; 20 | } 21 | 22 | public void Unsubscribe(float key, T value) 23 | { 24 | if (_entries.Remove(new(key, value))) 25 | { 26 | _cacheInvalidated = true; 27 | } 28 | } 29 | 30 | public interface IPriorityEventOwner 31 | { 32 | /// 33 | /// Returns an ordered enumerable of the event's subscribers at the moment of the request. 34 | /// 35 | IEnumerable GetSubscribers(); 36 | /// 37 | /// Returns an ordered enumerable of the event's subscribers in the specified priority range, with inclusive bounds. 38 | /// 39 | IEnumerable GetSubscriberRange(float min, float max); 40 | 41 | } 42 | 43 | private class PriorityEventOwner : IPriorityEventOwner 44 | { 45 | public PriorityEventOwner(PriorityEvent _parent) => this._parent = _parent; 46 | private readonly PriorityEvent _parent; 47 | public IEnumerable GetSubscribers() 48 | { 49 | if (_parent._cacheInvalidated) 50 | { 51 | _parent._cachedSubscriberArray = _parent._entries.ToArray(); 52 | _parent._cacheInvalidated = false; 53 | } 54 | 55 | return _parent._cachedSubscriberArray.Select(kvp => kvp.Value); 56 | } 57 | 58 | public IEnumerable GetSubscriberRange(float min, float max) 59 | { 60 | int lb = _parent._entries.FindInclusiveLowerBound(new (min, default)); 61 | int ub = _parent._entries.FindExclusiveUpperBound(new (max, default)); 62 | 63 | T[] result = new T[ub - lb]; 64 | for (int i = lb; i < ub; i++) result[i - lb] = _parent._entries[i].Value; 65 | return result; 66 | } 67 | } 68 | 69 | public readonly record struct PriorityEntry(float Key, T Value) : IComparable 70 | { 71 | public int CompareTo(PriorityEvent.PriorityEntry other) 72 | { 73 | return Key.CompareTo(other.Key); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /RandomizerMod/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Setting ComVisible to false makes the types in this assembly not visible 6 | // to COM components. If you need to access a type in this assembly from 7 | // COM, set the ComVisible attribute to true on that type. 8 | [assembly: ComVisible(false)] 9 | 10 | // The following GUID is for the ID of the typelib if this project is exposed to COM 11 | [assembly: Guid("8b1ab441-2e8a-49eb-87bd-8e1c9729ad00")] 12 | -------------------------------------------------------------------------------- /RandomizerMod/RC/CharmNotchCosts.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.RC 2 | { 3 | public static class CharmNotchCosts 4 | { 5 | public static int[] _vanillaCosts; 6 | 7 | public static int GetVanillaCost(int id) => _vanillaCosts[id - 1]; 8 | 9 | public static int[] GetUniformlyRandomCosts(Random rng, int min, int max) 10 | { 11 | if (max > 240) throw new ArgumentOutOfRangeException(nameof(max)); 12 | if (min < 0 || min > max) throw new ArgumentOutOfRangeException(nameof(min)); 13 | 14 | int count = rng.Next(min, max + 1); 15 | int[] costs = new int[40]; 16 | 17 | for (int i = 0; i < count; i++) 18 | { 19 | int index; 20 | do 21 | { 22 | index = rng.Next(40); 23 | } 24 | while (costs[index] >= 6); 25 | 26 | costs[index]++; 27 | } 28 | 29 | return costs; 30 | } 31 | 32 | 33 | static CharmNotchCosts() 34 | { 35 | _vanillaCosts = new int[] 36 | { 37 | 1, 38 | 1, 39 | 1, 40 | 2, 41 | 2, 42 | 2, 43 | 3, 44 | 2, 45 | 3, 46 | 1, 47 | 3, 48 | 1, 49 | 3, 50 | 1, 51 | 2, 52 | 2, 53 | 1, 54 | 2, 55 | 3, 56 | 2, 57 | 4, 58 | 2, 59 | 2, 60 | 2, 61 | 3, 62 | 1, 63 | 4, 64 | 2, 65 | 4, 66 | 1, 67 | 2, 68 | 3, 69 | 2, 70 | 4, 71 | 3, 72 | 5, 73 | 1, 74 | 3, 75 | 2, 76 | 2 77 | }; 78 | 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /RandomizerMod/RC/CustomGeoItem.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerCore.LogicItems; 3 | 4 | namespace RandomizerMod.RC 5 | { 6 | public class CustomGeoItem : RandoModItem 7 | { 8 | public int geo; 9 | 10 | [Newtonsoft.Json.JsonConstructor] 11 | private CustomGeoItem() { } 12 | 13 | public CustomGeoItem(LogicManager lm, int geo) 14 | { 15 | this.geo = geo; 16 | item = new SingleItem($"{geo}_Geo", new(lm.GetTermStrict("GEO"), geo)); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /RandomizerMod/RC/ItemPlacement.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace RandomizerMod.RC 9 | { 10 | public readonly record struct ItemPlacement(RandoModItem Item, RandoModLocation Location) 11 | { 12 | /// 13 | /// The index of the item placement in the RandoModContext item placements. Initialized to -1 if the placement is not part of the ctx. 14 | /// 15 | public int Index { get; init; } = -1; 16 | 17 | public void Deconstruct(out RandoModItem item, out RandoModLocation location) 18 | { 19 | item = Item; 20 | location = Location; 21 | } 22 | 23 | public static implicit operator GeneralizedPlacement(ItemPlacement p) => new(p.Item, p.Location); 24 | public static explicit operator ItemPlacement(GeneralizedPlacement p) => new((RandoModItem)p.Item, (RandoModLocation)p.Location); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /RandomizerMod/RC/LogicConstUtil.cs: -------------------------------------------------------------------------------- 1 | using RandomizerMod.RandomizerData; 2 | 3 | namespace RandomizerMod.RC 4 | { 5 | public static class LogicConstUtil 6 | { 7 | private static readonly string[] _charmTerms; 8 | private static readonly Dictionary _charmIDs; 9 | 10 | /// 11 | /// Returns the term name corresponding to the (1-based) charm ID. 12 | /// 13 | public static string GetCharmTerm(int charmID) 14 | { 15 | return _charmTerms[charmID - 1]; 16 | } 17 | 18 | /// 19 | /// Returns the (1-based) charm ID corresponding to the charm term name. 20 | /// 21 | public static int GetCharmID(string charmTermName) 22 | { 23 | return _charmIDs[charmTermName]; 24 | } 25 | 26 | static LogicConstUtil() 27 | { 28 | // TODO: const string fields? 29 | _charmTerms = JsonUtil.Deserialize>>("RandomizerMod.Resources.Logic.terms.json")["SignedByte"].SkipWhile(t => t != "Gathering_Swarm").Take(40).ToArray(); 30 | _charmIDs = _charmTerms.Select((s, i) => (s, i)).ToDictionary(p => p.s, p => p.i + 1); 31 | _charmIDs["White_Fragment"] = _charmIDs["Queen_Fragment"] = _charmIDs["King_Fragment"] = _charmIDs["Kingsoul"] = _charmIDs["Void_Heart"] = 36; // aliases of WHITEFRAGMENT 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /RandomizerMod/RC/LogicGeoCost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RandomizerCore.Logic; 7 | 8 | namespace RandomizerMod.RC 9 | { 10 | public class LogicGeoCost : LogicCost 11 | { 12 | public Term CanReplenishGeoWaypoint { get; init; } 13 | public int GeoAmount { get; set; } 14 | 15 | public LogicGeoCost() { } 16 | 17 | public LogicGeoCost(LogicManager lm, int amount) 18 | { 19 | CanReplenishGeoWaypoint = lm.GetTermStrict("Can_Replenish_Geo"); 20 | GeoAmount = amount; 21 | } 22 | 23 | public override bool CanGet(ProgressionManager pm) 24 | { 25 | return pm.Has(CanReplenishGeoWaypoint.Id); 26 | } 27 | 28 | public override IEnumerable GetTerms() 29 | { 30 | yield return CanReplenishGeoWaypoint; 31 | } 32 | 33 | public override string ToString() 34 | { 35 | return $"LogicGeoCost {{{GeoAmount} Geo}}"; 36 | } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /RandomizerMod/RC/LogicInts/NotchCostInt.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | 3 | namespace RandomizerMod.RC.LogicInts 4 | { 5 | /// 6 | /// LogicInt which returns 1 less than the number of notches to equip the charm with or without overcharming. 7 | /// 8 | [Obsolete("Use EquipCharmVariable")] 9 | public class NotchCostInt : LogicInt 10 | { 11 | // the ids should correspond to the 1-40 charm nums (i.e. 1-indexed) 12 | public readonly int[] charmIDs; 13 | public override string Name { get; } 14 | public const string Prefix = "$NotchCost"; 15 | 16 | public NotchCostInt(params int[] charmIDs) 17 | { 18 | this.charmIDs = charmIDs; 19 | Array.Sort(charmIDs); 20 | Name = $"$NotchCost[{string.Join(",", charmIDs)}]"; 21 | } 22 | 23 | public static bool TryMatch(LogicManager lm, string term, out LogicVariable variable) 24 | { 25 | if (VariableResolver.TryMatchPrefix(term, Prefix, out string[] parameters)) 26 | { 27 | int[] charmIDs = parameters.Select(int.Parse).ToArray(); 28 | variable = new NotchCostInt(charmIDs); 29 | return true; 30 | } 31 | variable = null; 32 | return false; 33 | } 34 | 35 | public override int GetValue(object? sender, ProgressionManager pm) 36 | { 37 | List notchCosts = (pm.ctx as RandoModContext)?.notchCosts; 38 | if (notchCosts != null && notchCosts.Count >= charmIDs[charmIDs.Length - 1]) 39 | { 40 | return charmIDs.Sum(i => notchCosts[i - 1]) - charmIDs.Max(i => notchCosts[i - 1]); 41 | } 42 | else 43 | { 44 | return charmIDs.Sum(i => CharmNotchCosts.GetVanillaCost(i)) - charmIDs.Max(i => CharmNotchCosts.GetVanillaCost(i)); 45 | } 46 | } 47 | 48 | public override IEnumerable GetTerms() => Enumerable.Empty(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RandomizerMod/RC/LogicInts/SafeNotchCostInt.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | 3 | namespace RandomizerMod.RC.LogicInts 4 | { 5 | /// 6 | /// LogicInt which returns 1 less than the number of notches needed to equip the charm without overcharming. 7 | /// 8 | [Obsolete("Use EquipCharmVariable")] 9 | public class SafeNotchCostInt : LogicInt 10 | { 11 | // the ids should correspond to the 1-40 charm nums (i.e. 1-indexed) 12 | public readonly int[] charmIDs; 13 | public override string Name { get; } 14 | public const string Prefix = "$SafeNotchCost"; 15 | 16 | public SafeNotchCostInt(params int[] charmIDs) 17 | { 18 | this.charmIDs = charmIDs; 19 | Array.Sort(charmIDs); 20 | Name = $"$SafeNotchCost[{string.Join(",", charmIDs)}]"; 21 | } 22 | 23 | public static bool TryMatch(LogicManager lm, string term, out LogicVariable variable) 24 | { 25 | if (VariableResolver.TryMatchPrefix(term, Prefix, out string[] parameters)) 26 | { 27 | int[] charmIDs = parameters.Select(int.Parse).ToArray(); 28 | variable = new SafeNotchCostInt(charmIDs); 29 | return true; 30 | } 31 | variable = null; 32 | return false; 33 | } 34 | 35 | public override int GetValue(object? sender, ProgressionManager pm) 36 | { 37 | List notchCosts = (pm.ctx as RandoModContext)?.notchCosts; 38 | if (notchCosts != null && notchCosts.Count >= charmIDs[charmIDs.Length - 1]) 39 | { 40 | return charmIDs.Sum(i => notchCosts[i - 1]) - 1; 41 | } 42 | else 43 | { 44 | return charmIDs.Sum(i => CharmNotchCosts.GetVanillaCost(i)) - 1; 45 | } 46 | } 47 | 48 | public override IEnumerable GetTerms() => Enumerable.Empty(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RandomizerMod/RC/LogicInts/StartLocationDelta.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerCore.Logic.StateLogic; 3 | 4 | namespace RandomizerMod.RC.LogicInts 5 | { 6 | /// 7 | /// LogicInt which is true exactly when the GenerationSettings StartLocation equals its argument. 8 | /// 9 | public class StartLocationDelta : StateProvider 10 | { 11 | protected readonly Term? StartStateTerm; // null in files generated before state logic 12 | public override string Name { get; } 13 | public string Location { get; } 14 | public const string Prefix = "$StartLocation"; 15 | 16 | public StartLocationDelta(string name, LogicManager lm, string location) 17 | { 18 | Location = location; 19 | Name = name; 20 | StartStateTerm = lm.GetTerm("Start_State"); 21 | } 22 | 23 | public static bool TryMatch(LogicManager lm, string term, out LogicVariable variable) 24 | { 25 | if (VariableResolver.TryMatchPrefix(term, Prefix, out string[] parameters)) 26 | { 27 | string location = parameters[0]; 28 | variable = new StartLocationDelta(term, lm, location); 29 | return true; 30 | } 31 | variable = null; 32 | return false; 33 | } 34 | 35 | public override StateUnion? GetInputState(object? sender, ProgressionManager pm) 36 | { 37 | return ((RandoModContext)pm.ctx).GenerationSettings.StartLocationSettings.StartLocation == Location 38 | ? StartStateTerm is not null 39 | ? pm.GetState(StartStateTerm) 40 | : StateUnion.Empty 41 | : null; 42 | } 43 | 44 | public override IEnumerable GetTerms() 45 | { 46 | if (StartStateTerm is not null) yield return StartStateTerm; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /RandomizerMod/RC/PlaceholderItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RandomizerCore; 7 | using RandomizerCore.LogicItems; 8 | 9 | namespace RandomizerMod.RC 10 | { 11 | /// 12 | /// A wrapper for a RandoModItem to allow it to be ignored by logic. 13 | ///
Used mainly with duplicate progression, to avoid skewing. 14 | ///
15 | public class PlaceholderItem : RandoModItem 16 | { 17 | public const string Prefix = "Placeholder-"; 18 | 19 | public PlaceholderItem(RandoModItem innerItem, bool wrapped = true) 20 | { 21 | this.innerItem = innerItem; 22 | this.info = innerItem.info?.Clone() ?? new(); 23 | this.info.realItemCreator ??= ((factory, next) => factory.MakeItemWithEvents(innerItem.Name, next)); 24 | if (wrapped) Wrap(); 25 | else Unwrap(); 26 | } 27 | 28 | /// 29 | /// Hides the innerItem of the PlaceholderItem from logic. 30 | /// 31 | public void Wrap() 32 | { 33 | wrapped = true; 34 | item = new EmptyItem($"Placeholder-{innerItem.Name}"); 35 | } 36 | 37 | /// 38 | /// Makes the innerItem of the PlaceholderItem visible to logic. 39 | /// 40 | public void Unwrap() 41 | { 42 | wrapped = false; 43 | item = innerItem.item; 44 | } 45 | 46 | public readonly RandoModItem innerItem; 47 | public bool wrapped; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /RandomizerMod/RC/ProgressionInitializer.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore; 2 | using RandomizerCore.Logic; 3 | using RandomizerMod.RandomizerData; 4 | using RandomizerMod.Settings; 5 | 6 | namespace RandomizerMod.RC 7 | { 8 | public class ProgressionInitializer : ILogicItem 9 | { 10 | /// 11 | /// Event invoked after base randomizer term modifiers are added to the initializer. 12 | /// 13 | public static event Action OnCreateProgressionInitializer; 14 | 15 | public ProgressionInitializer() { } 16 | public ProgressionInitializer(LogicManager lm, GenerationSettings gs, StartDef startDef) 17 | { 18 | foreach (string setting in Data.GetApplicableLogicSettings(gs)) 19 | { 20 | Setters.Add(new(lm.GetTermStrict(setting), 1)); 21 | } 22 | 23 | Setters.Add(new(lm.GetTermStrict(gs.TransitionSettings.Mode switch 24 | { 25 | TransitionSettings.TransitionMode.None => "ITEMRANDO", 26 | TransitionSettings.TransitionMode.MapAreaRandomizer => "MAPAREARANDO", 27 | TransitionSettings.TransitionMode.FullAreaRandomizer => "FULLAREARANDO", 28 | _ => "ROOMRANDO", 29 | }), 1)); 30 | 31 | foreach (TermValue tv in startDef.GetStartLocationProgression(lm)) 32 | { 33 | if (tv.Term.Type == TermType.State) StartStateLinkedTerms.Add(tv.Term); 34 | else Setters.Add(tv); 35 | } 36 | 37 | Setters.Add(new(lm.GetTermStrict("GRUBS"), -gs.CostSettings.GrubTolerance)); 38 | Setters.Add(new(lm.GetTermStrict("ESSENCE"), -gs.CostSettings.EssenceTolerance)); 39 | Setters.Add(new(lm.GetTermStrict("RANCIDEGGS"), -gs.CostSettings.EggTolerance)); 40 | Setters.Add(new(lm.GetTermStrict("CHARMS"), -gs.CostSettings.CharmTolerance)); 41 | 42 | Setters.Add(new(lm.GetTermStrict("MASKSHARDS"), 20 - 4 * gs.CursedSettings.CursedMasks)); 43 | Setters.Add(new(lm.GetTermStrict("NOTCHES"), 3 - gs.CursedSettings.CursedNotches)); 44 | 45 | StartStateTerm = lm.GetTerm("Start_State"); 46 | 47 | try 48 | { 49 | OnCreateProgressionInitializer?.Invoke(lm, gs, this); 50 | } 51 | catch (Exception e) 52 | { 53 | throw new InvalidOperationException("Error invoking OnCreateProgressionInitializer", e); 54 | } 55 | } 56 | 57 | public List Setters = new(); 58 | public List Increments = new(); 59 | public List StartStateLinkedTerms = new(); 60 | public Term? StartStateTerm; 61 | 62 | public string Name => "Progression Initializer"; 63 | 64 | public void AddTo(ProgressionManager pm) 65 | { 66 | foreach (TermValue tv in Setters) pm.Set(tv); 67 | foreach (TermValue tv in Increments) pm.Incr(tv); 68 | if (StartStateTerm is not null && !pm.mu.HasCustomLongTermRevertPoint) 69 | { 70 | foreach (Term t in StartStateLinkedTerms) pm.mu.LinkState(StartStateTerm, t); 71 | } 72 | } 73 | 74 | public IEnumerable GetAffectedTerms() 75 | { 76 | foreach (TermValue tv in Setters) yield return tv.Term; 77 | foreach (TermValue tv in Increments) yield return tv.Term; 78 | if (StartStateTerm is not null) yield return StartStateTerm; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /RandomizerMod/RC/RCData.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Json; 2 | using RandomizerCore.Logic; 3 | using RandomizerMod.Settings; 4 | 5 | namespace RandomizerMod.RC 6 | { 7 | public static class RCData 8 | { 9 | private static readonly (LogicFileType type, string fileName)[] files = new[] 10 | { 11 | (LogicFileType.Terms, "terms"), 12 | (LogicFileType.Macros, "macros"), 13 | (LogicFileType.Waypoints, "waypoints"), 14 | (LogicFileType.Transitions, "transitions"), 15 | (LogicFileType.Locations, "locations"), 16 | (LogicFileType.ItemStrings, "items"), 17 | (LogicFileType.StateData, "state"), 18 | }; 19 | 20 | /// 21 | /// Creates a new LogicManager, allowing edits from local files and runtime hooks. 22 | /// 23 | public static LogicManager GetNewLogicManager(GenerationSettings gs) 24 | { 25 | LogicManagerBuilder lmb = new() { VariableResolver = new RandoVariableResolver() }; 26 | ILogicFormat fmt = new JsonLogicFormat(); 27 | 28 | foreach ((LogicFileType type, string fileName) in files) 29 | { 30 | lmb.DeserializeFile(type, fmt, RandomizerMod.Assembly.GetManifestResourceStream($"RandomizerMod.Resources.Logic.{fileName}.json")); 31 | } 32 | 33 | foreach (var a in _runtimeLogicOverrideOwner.GetSubscribers()) 34 | { 35 | try 36 | { 37 | a?.Invoke(gs, lmb); 38 | } 39 | catch (Exception e) 40 | { 41 | throw new InvalidOperationException($"Error invoking logic override event from {a?.Method?.DeclaringType?.AssemblyQualifiedName ?? "unknown source"}", e); 42 | } 43 | } 44 | 45 | return new(lmb); 46 | } 47 | 48 | /// 49 | /// Event invoked when building the LogicManager for randomization. 50 | ///
A subscriber with a nonpositive key is invoked before local logic edits. 51 | ///
A subscriber with a positive key is invoked after local logic edits. 52 | ///
53 | public static readonly PriorityEvent> RuntimeLogicOverride = new(out _runtimeLogicOverrideOwner); 54 | private static readonly PriorityEvent>.IPriorityEventOwner _runtimeLogicOverrideOwner; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /RandomizerMod/RC/RandoModContext.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using RandomizerCore; 3 | using RandomizerCore.Logic; 4 | using RandomizerMod.RandomizerData; 5 | using RandomizerMod.Settings; 6 | 7 | namespace RandomizerMod.RC 8 | { 9 | public class RandoModContext : RandoContext 10 | { 11 | public RandoModContext(LogicManager LM) : base(LM) { } 12 | 13 | public RandoModContext(GenerationSettings gs, StartDef startDef) : base(RCData.GetNewLogicManager(gs)) 14 | { 15 | base.InitialProgression = new ProgressionInitializer(LM, gs, startDef); 16 | this.GenerationSettings = gs; 17 | this.StartDef = startDef; 18 | } 19 | 20 | public RandoModContext(RandoModContext ctx) : base(ctx.LM) 21 | { 22 | notchCosts = ctx.notchCosts.ToList(); 23 | itemPlacements = ctx.itemPlacements.ToList(); 24 | transitionPlacements = ctx.transitionPlacements.ToList(); 25 | StartDef = ctx.StartDef; 26 | InitialProgression = ctx.InitialProgression; 27 | Vanilla = ctx.Vanilla.ToList(); 28 | GenerationSettings = ctx.GenerationSettings; 29 | } 30 | 31 | public GenerationSettings GenerationSettings { get; init; } 32 | public StartDef StartDef { get; init; } 33 | 34 | public List Vanilla = new(); 35 | public List itemPlacements = new(); 36 | [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] 37 | public List transitionPlacements = new(); 38 | public List notchCosts = new(); 39 | /// 40 | /// Additional data needed for logic; e.g. parameters for LogicVariables like notch costs, etc. 41 | /// 42 | public Dictionary Properties { get; } = []; 43 | 44 | public override IEnumerable EnumerateExistingPlacements() 45 | { 46 | foreach (GeneralizedPlacement p in Vanilla) yield return p; 47 | foreach (ItemPlacement p in itemPlacements) yield return p; 48 | foreach (TransitionPlacement p in transitionPlacements) yield return p; 49 | } 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /RandomizerMod/RC/RandoModItem.cs: -------------------------------------------------------------------------------- 1 | using ItemChanger; 2 | using RandomizerCore; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Newtonsoft.Json; 9 | using RandomizerMod.RandomizerData; 10 | 11 | namespace RandomizerMod.RC 12 | { 13 | public class RandoModItem : RandoItem 14 | { 15 | /// 16 | /// The ItemRequestInfo associated with the item. May be null if the item does not require modification. 17 | ///
This field is not serialized and will be null upon reloading the game. 18 | ///
19 | [JsonIgnore] public ItemRequestInfo? info; 20 | /// 21 | /// The ItemDef associated with the location. Preferred over Data.GetItemDef, since this preserves modified item data. 22 | ///
This field is serialized, and is safe to use after reloading the game. May rarely be null for external items which choose not to supply a value. 23 | ///
24 | public ItemDef ItemDef; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /RandomizerMod/RC/RandoModLocation.cs: -------------------------------------------------------------------------------- 1 | using ItemChanger; 2 | using Newtonsoft.Json; 3 | using RandomizerCore; 4 | using RandomizerMod.RandomizerData; 5 | 6 | namespace RandomizerMod.RC 7 | { 8 | public class RandoModLocation : RandoLocation 9 | { 10 | /// 11 | /// The LocationRequestInfo associated with the location. May be null if the location does not require modification. 12 | ///
This field is not serialized and will be null upon reloading the game. 13 | ///
14 | [JsonIgnore] public LocationRequestInfo? info; 15 | /// 16 | /// The LocationDef associated with the location. Preferred over Data.GetLocationDef, since this preserves modified location data. 17 | ///
This field is serialized, and is safe to use after reloading the game. May rarely be null for external locations which choose not to supply a value. 18 | ///
19 | public LocationDef LocationDef; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /RandomizerMod/RC/RandoModTransition.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using RandomizerCore; 3 | using RandomizerCore.Logic; 4 | using RandomizerMod.RandomizerData; 5 | 6 | namespace RandomizerMod.RC 7 | { 8 | public class RandoModTransition : RandoTransition 9 | { 10 | /// 11 | /// The TransitionRequestInfo associated with the transition. May be null if the transition does not require modification. 12 | ///
This field is not serialized and will be null upon reloading the game. 13 | ///
14 | [JsonIgnore] public TransitionRequestInfo? info; 15 | /// 16 | /// The TransitionDef associated with the location. Preferred over Data.GetTransitionDef, since this preserves modified transition data. 17 | ///
This field is serialized, and is safe to use after reloading the game. 18 | ///
19 | public TransitionDef TransitionDef; 20 | public RandoModTransition(LogicTransition lt) : base(lt) { } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /RandomizerMod/RC/RandoVariableResolver.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerMod.RC.LogicInts; 3 | using RandomizerMod.RC.StateVariables; 4 | 5 | namespace RandomizerMod.RC 6 | { 7 | public class RandoVariableResolver : VariableResolver 8 | { 9 | public override bool TryMatch(LogicManager lm, string term, out LogicVariable variable) 10 | { 11 | if (base.TryMatch(lm, term, out variable)) return true; 12 | 13 | if (StartLocationDelta.TryMatch(lm, term, out variable)) return true; 14 | if (BenchResetVariable.TryMatch(lm, term, out variable)) return true; 15 | if (CastSpellVariable.TryMatch(lm, term, out variable)) return true; 16 | if (SlopeballVariable.TryMatch(lm, term, out variable)) return true; 17 | if (ShriekPogoVariable.TryMatch(lm, term, out variable)) return true; 18 | if (SoulStateManager.TryMatch(lm, term, out variable)) return true; 19 | if (SpendSoulVariable.TryMatch(lm, term, out variable)) return true; 20 | if (RegainSoulVariable.TryMatch(lm, term, out variable)) return true; 21 | if (EquipCharmVariable.TryMatch(lm, term, out variable)) return true; 22 | if (HotSpringResetVariable.TryMatch(lm, term, out variable)) return true; 23 | if (ShadeStateVariable.TryMatch(lm, term, out variable)) return true; 24 | if (TakeDamageVariable.TryMatch(lm, term, out variable)) return true; 25 | if (HPStateManager.TryMatch(lm, term, out variable)) return true; 26 | if (LifebloodCountVariable.TryMatch(lm, term, out variable)) return true; 27 | if (StagStateVariable.TryMatch(lm, term, out variable)) return true; 28 | if (FlowerProviderVariable.TryMatch(lm, term, out variable)) return true; 29 | if (SaveQuitResetVariable.TryMatch(lm, term, out variable)) return true; 30 | if (StartRespawnResetVariable.TryMatch(lm, term, out variable)) return true; 31 | if (WarpToStartResetVariable.TryMatch(lm, term, out variable)) return true; 32 | if (WarpToBenchResetVariable.TryMatch(lm, term, out variable)) return true; 33 | 34 | #pragma warning disable CS0618 // Type or member is obsolete 35 | if (NotchCostInt.TryMatch(lm, term, out variable)) return true; 36 | if (SafeNotchCostInt.TryMatch(lm, term, out variable)) return true; 37 | #pragma warning restore CS0618 // Type or member is obsolete 38 | 39 | return false; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /RandomizerMod/RC/Requests/Bucket.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.RC 2 | { 3 | public class Bucket 4 | { 5 | public Bucket() 6 | { 7 | _buckets = new(); 8 | } 9 | 10 | public Bucket(IEqualityComparer equalityComparer) 11 | { 12 | _buckets = new(equalityComparer); 13 | } 14 | 15 | private readonly Dictionary _buckets; 16 | 17 | public void Increment(T t, int value) 18 | { 19 | _buckets.TryGetValue(t, out int current); 20 | _buckets[t] = current + value; 21 | } 22 | 23 | public int GetCount(T t) 24 | { 25 | _buckets.TryGetValue(t, out int value); 26 | return value; 27 | } 28 | 29 | public void AddRange(IEnumerable ts) 30 | { 31 | foreach (T t in ts) Increment(t, 1); 32 | } 33 | 34 | public void Add(T t) 35 | { 36 | Increment(t, 1); 37 | } 38 | 39 | public void RemoveAll(T t) 40 | { 41 | _buckets.Remove(t); 42 | } 43 | 44 | public void Remove(T t, int count) 45 | { 46 | if (_buckets.TryGetValue(t, out int value)) 47 | { 48 | if (value > count) _buckets[t] = value - count; 49 | else _buckets.Remove(t); 50 | } 51 | } 52 | 53 | public void Replace(T old, T replaceWith) 54 | { 55 | if (_buckets.TryGetValue(old, out int value)) 56 | { 57 | _buckets.Remove(old); 58 | Increment(replaceWith, value); 59 | } 60 | } 61 | 62 | public void Replace(T old, Func> replacer) 63 | { 64 | if (_buckets.TryGetValue(old, out int value)) 65 | { 66 | _buckets.Remove(old); 67 | AddRange(replacer(value)); 68 | } 69 | } 70 | 71 | public void Set(T t, int value) 72 | { 73 | _buckets[t] = value; 74 | } 75 | 76 | public IEnumerable EnumerateDistinct() 77 | { 78 | foreach (KeyValuePair kvp in _buckets) 79 | { 80 | if (kvp.Value > 0) yield return kvp.Key; 81 | } 82 | } 83 | 84 | public IEnumerable EnumerateWithMultiplicity() 85 | { 86 | foreach (KeyValuePair kvp in _buckets) 87 | { 88 | for (int i = 0; i < kvp.Value; i++) yield return kvp.Key; 89 | } 90 | } 91 | 92 | public CDFWeightedArray ToWeightedArray() 93 | { 94 | T[] values = _buckets.Where(kvp => kvp.Value > 0).Select(kvp => kvp.Key).ToArray(); 95 | double total = GetTotal(); 96 | double[] cumulativeDensities = new double[values.Length]; 97 | double previous = 0.0; 98 | for (int i = 0; i < values.Length; i++) 99 | { 100 | previous = cumulativeDensities[i] = previous + _buckets[values[i]] / total; 101 | } 102 | return new(values, cumulativeDensities); 103 | } 104 | 105 | public int GetTotal() => _buckets.Values.Sum(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /RandomizerMod/RC/Requests/CDFWeightedArray.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.RC 2 | { 3 | public struct CDFWeightedArray 4 | { 5 | public readonly T[] values; 6 | public readonly double[] cumulativeDensities; 7 | 8 | /// 9 | /// Creates a new CDFWeightedArray with the given values. Densities should have the same positive length as values, and its entries should be increasing positive numbers, with last entry 1. 10 | /// 11 | public CDFWeightedArray(T[] values, double[] cumulativeDensities) 12 | { 13 | this.values = values; 14 | this.cumulativeDensities = cumulativeDensities; 15 | } 16 | 17 | /// 18 | /// Creates a new CDFWeightedArray with the given values. 19 | ///
If cumulative, densities should have the same positive length as values, and its entries should be increasing positive numbers, with last entry 1. 20 | ///
If noncumulative, densities should have the same positive length as values, and its entries should be nonnegative numbers. 21 | ///
22 | public CDFWeightedArray(T[] values, double[] densities, bool cumulative) 23 | { 24 | this.values = values; 25 | if (cumulative) 26 | { 27 | this.cumulativeDensities = densities; 28 | } 29 | else 30 | { 31 | this.cumulativeDensities = new double[densities.Length]; 32 | double total = cumulativeDensities.Sum(); 33 | double cdf = 0.0; 34 | for (int i = 0; i < densities.Length; i++) 35 | { 36 | this.cumulativeDensities[i] = cdf += densities[i] / total; 37 | } 38 | } 39 | } 40 | 41 | /// 42 | /// Randomly selects a value from the array using the CDF weights. 43 | ///
Chooses a random number between 0 and 1, and then returns the value with the least weight greater than the 44 | ///
45 | public T Next(Random rng) 46 | { 47 | if (values.Length == 1) return values[0]; // don't burn rng samples unnecessarily 48 | 49 | double d = rng.NextDouble(); 50 | for (int i = 0; i < values.Length; i++) 51 | { 52 | if (cumulativeDensities[i] > d) return values[i]; 53 | } 54 | return values[values.Length - 1]; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /RandomizerMod/RC/Requests/GroupBuilder.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Randomization; 2 | 3 | namespace RandomizerMod.RC 4 | { 5 | public abstract class GroupBuilder 6 | { 7 | public string label; 8 | public string stageLabel; 9 | public Action? onPermute; 10 | public GroupPlacementStrategy? strategy; 11 | /// 12 | /// An action invoked on each group created by the GroupBuilder. Note that some GroupBuilders may create multiple groups. 13 | /// 14 | public Action? OnCreateGroup; 15 | 16 | public abstract void Apply(List groups, RandoFactory factory); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /RandomizerMod/RC/Requests/ItemGroupBuilder.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore; 2 | using RandomizerCore.Randomization; 3 | 4 | namespace RandomizerMod.RC 5 | { 6 | public class ItemGroupBuilder : GroupBuilder 7 | { 8 | public ItemGroupBuilder() { } 9 | 10 | public delegate IEnumerable ItemPaddingHandler(RandoFactory factory, int count); 11 | public delegate IEnumerable LocationPaddingHandler(RandoFactory factory, int count); 12 | public ItemPaddingHandler? ItemPadder; 13 | public LocationPaddingHandler? LocationPadder; 14 | 15 | public readonly Bucket Items = new(); 16 | public readonly Bucket Locations = new(); 17 | 18 | 19 | public override void Apply(List groups, RandoFactory factory) 20 | { 21 | List items = new(); 22 | foreach (string i in Items.EnumerateWithMultiplicity()) 23 | { 24 | items.Add(factory.MakeItem(i)); 25 | } 26 | List locations = new(); 27 | foreach (string i in Locations.EnumerateWithMultiplicity()) 28 | { 29 | locations.Add(factory.MakeLocation(i)); 30 | } 31 | 32 | int diff = items.Count - locations.Count; 33 | if (diff > 0 && LocationPadder != null) 34 | { 35 | locations.AddRange(LocationPadder(factory, diff)); 36 | } 37 | else if (diff < 0 && ItemPadder != null) 38 | { 39 | items.AddRange(ItemPadder(factory, -diff)); 40 | } 41 | 42 | if (items.Count != locations.Count) throw new InvalidOperationException($"Failed to build group {label} due to unbalanced counts."); 43 | 44 | RandomizationGroup group = new() 45 | { 46 | Items = items.ToArray(), 47 | Locations = locations.ToArray(), 48 | Label = label, 49 | Strategy = strategy ?? factory.gs.ProgressionDepthSettings.GetItemPlacementStrategy(), 50 | }; 51 | group.OnPermute += onPermute; 52 | groups.Add(group); 53 | OnCreateGroup?.Invoke(group); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /RandomizerMod/RC/Requests/ItemRequestInfo.cs: -------------------------------------------------------------------------------- 1 | using ItemChanger; 2 | using Newtonsoft.Json; 3 | using RandomizerCore; 4 | using RandomizerMod.RandomizerData; 5 | 6 | namespace RandomizerMod.RC 7 | { 8 | public class ItemRequestInfo 9 | { 10 | public Func? randoItemCreator; 11 | public Action? onRandoItemCreation; 12 | public Action? onRandomizerFinish; 13 | public Func? realItemCreator; 14 | public Func? getItemDef; 15 | 16 | public void AddGetItemDefModifier(string name, Func modifier) 17 | { 18 | Func get = getItemDef ?? (() => Data.GetItemDef(name)); 19 | getItemDef = () => modifier(get()); 20 | } 21 | 22 | public ItemRequestInfo Clone() 23 | { 24 | return new ItemRequestInfo 25 | { 26 | randoItemCreator = (Func)randoItemCreator?.Clone(), 27 | onRandoItemCreation = (Action)onRandoItemCreation?.Clone(), 28 | onRandomizerFinish = (Action)onRandomizerFinish?.Clone(), 29 | realItemCreator = (Func)realItemCreator?.Clone(), 30 | getItemDef = (Func)getItemDef?.Clone() 31 | }; 32 | } 33 | 34 | public void AppendTo(ItemRequestInfo info) 35 | { 36 | if (randoItemCreator != null) info.randoItemCreator = randoItemCreator; 37 | info.onRandoItemCreation += onRandoItemCreation; 38 | info.onRandomizerFinish += onRandomizerFinish; 39 | if (realItemCreator != null) info.realItemCreator = realItemCreator; 40 | if (getItemDef != null) info.getItemDef = getItemDef; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /RandomizerMod/RC/Requests/LocationRequestInfo.cs: -------------------------------------------------------------------------------- 1 | using ItemChanger; 2 | using Newtonsoft.Json; 3 | using RandomizerCore; 4 | using RandomizerMod.RandomizerData; 5 | using RandomizerMod.Settings; 6 | 7 | namespace RandomizerMod.RC 8 | { 9 | public class LocationRequestInfo 10 | { 11 | public Func? randoLocationCreator; 12 | public Action? onRandoLocationCreation; 13 | public Action? onRandomizerFinish; 14 | public Func? customPlacementFetch; 15 | public Action? onPlacementFetch; 16 | public Action? customAddToPlacement; 17 | public Func? getLocationDef; 18 | 19 | public void AddGetLocationDefModifier(string name, Func modifier) 20 | { 21 | Func get = getLocationDef ?? (() => Data.GetLocationDef(name)); 22 | getLocationDef = () => modifier(get()); 23 | } 24 | 25 | public LocationRequestInfo Clone() 26 | { 27 | return new LocationRequestInfo 28 | { 29 | randoLocationCreator = (Func)randoLocationCreator?.Clone(), 30 | onRandoLocationCreation = (Action)onRandoLocationCreation?.Clone(), 31 | onRandomizerFinish = (Action)onRandomizerFinish?.Clone(), 32 | customPlacementFetch = (Func)customPlacementFetch?.Clone(), 33 | onPlacementFetch = (Action)onPlacementFetch?.Clone(), 34 | customAddToPlacement = (Action)customAddToPlacement?.Clone(), 35 | getLocationDef = (Func)getLocationDef?.Clone(), 36 | }; 37 | } 38 | 39 | public void AppendTo(LocationRequestInfo info) 40 | { 41 | if (randoLocationCreator != null) info.randoLocationCreator = randoLocationCreator; 42 | info.onRandoLocationCreation += onRandoLocationCreation; 43 | info.onRandomizerFinish += onRandomizerFinish; 44 | if (customPlacementFetch != null) info.customPlacementFetch = customPlacementFetch; 45 | info.onPlacementFetch += onPlacementFetch; 46 | info.customAddToPlacement += customAddToPlacement; 47 | if (getLocationDef != null) info.getLocationDef = getLocationDef; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RandomizerMod/RC/Requests/RBConsts.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.RC 2 | { 3 | /// 4 | /// Strings used by the RequestBuilder as labels. 5 | /// 6 | public static class RBConsts 7 | { 8 | /// 9 | /// Label for the transition stage, in transition rando only. 10 | /// 11 | public const string MainTransitionStage = "Main Transition Stage"; 12 | /// 13 | /// Label for the default stage, in any rando. 14 | /// 15 | public const string MainItemStage = "Main Item Stage"; 16 | 17 | /// 18 | /// Label for the default group of the default stage, in any rando. 19 | /// 20 | public const string MainItemGroup = "Main Item Group"; 21 | 22 | /// 23 | /// Label for the stage of grubs and mimics, when mimics are randomized but grubs are not randomized. 24 | /// 25 | public const string GrubMimicStage = "Grub Mimic Stage"; 26 | /// 27 | /// Label for the group of grubs and mimics, when mimics are randomized but grubs are not randomized. 28 | /// 29 | public const string GrubMimicGroup = "Grub Mimic Group"; 30 | 31 | /// 32 | /// Prefix used for split groups. The label is formed by appending an integer between 1 and 99 according to the setting. 33 | /// 34 | public const string SplitGroupPrefix = "Split Group "; 35 | 36 | /// 37 | /// Label for the corresponding matched transition group. 38 | /// 39 | public const string InLeftOutRightGroup = "Left -> Right"; 40 | /// 41 | /// Label for the corresponding matched transition group. 42 | /// 43 | public const string InRightOutLeftGroup = "Right -> Left"; 44 | /// 45 | /// Label for the corresponding matched transition group. 46 | /// 47 | public const string InBotOutTopGroup = "Bot -> Top"; 48 | /// 49 | /// Label for the corresponding matched transition group. 50 | /// 51 | public const string InTopOutBotGroup = "Top -> Bot"; 52 | /// 53 | /// Label for the group of one-way transitions in any transition rando. 54 | /// 55 | public const string OneWayGroup = "One Way Transitions"; 56 | /// 57 | /// Label for the group of two-way transitions, in non-matched transition rando. 58 | /// 59 | public const string TwoWayGroup = "Two Way Transitions"; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /RandomizerMod/RC/Requests/SelfDualTransitionGroupBuilder.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore; 2 | using RandomizerCore.Randomization; 3 | 4 | namespace RandomizerMod.RC 5 | { 6 | public class SelfDualTransitionGroupBuilder : GroupBuilder 7 | { 8 | public bool coupled; 9 | public readonly Bucket Transitions = new(); 10 | 11 | public override void Apply(List groups, RandoFactory factory) 12 | { 13 | List ts = new(); 14 | foreach (string s in Transitions.EnumerateWithMultiplicity()) 15 | { 16 | ts.Add(factory.MakeTransition(s)); 17 | } 18 | 19 | if (coupled) 20 | { 21 | CoupledRandomizationGroup g = new() 22 | { 23 | Items = ts.ToArray(), 24 | Locations = ts.ToArray(), 25 | Label = label, 26 | Strategy = strategy ?? factory.gs.ProgressionDepthSettings.GetTransitionPlacementStrategy(), 27 | Validator = new WeakTransitionValidator(), 28 | }; 29 | g.Dual = g; 30 | groups.Add(g); 31 | OnCreateGroup?.Invoke(g); 32 | } 33 | else 34 | { 35 | RandomizationGroup g = new() 36 | { 37 | Items = ts.ToArray(), 38 | Locations = ts.ToArray(), 39 | Label = label, 40 | Strategy = strategy ?? factory.gs.ProgressionDepthSettings.GetTransitionPlacementStrategy(), 41 | }; 42 | groups.Add(g); 43 | OnCreateGroup?.Invoke(g); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /RandomizerMod/RC/Requests/StageBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using RandomizerCore.Randomization; 3 | 4 | namespace RandomizerMod.RC 5 | { 6 | public class StageBuilder 7 | { 8 | public readonly string label; 9 | public StagePlacementStrategy strategy; 10 | private readonly Dictionary groupLookup; 11 | private readonly List groups; 12 | public readonly ReadOnlyCollection Groups; 13 | 14 | public StageBuilder(string label) 15 | { 16 | this.label = label; 17 | this.groupLookup = new(); 18 | this.groups = new(); 19 | this.Groups = new(groups); 20 | } 21 | 22 | public void Add(GroupBuilder gb) 23 | { 24 | if (gb == null) throw new ArgumentNullException(nameof(gb)); 25 | if (gb.label == null || groupLookup.ContainsKey(gb.label)) throw new ArgumentException(nameof(gb)); 26 | gb.stageLabel = label; 27 | groups.Add(gb); 28 | groupLookup.Add(gb.label, gb); 29 | } 30 | 31 | public void Insert(int index, GroupBuilder gb) 32 | { 33 | if (gb == null) throw new ArgumentNullException(nameof(gb)); 34 | if (gb.label == null || groupLookup.ContainsKey(gb.label)) throw new ArgumentException(nameof(gb)); 35 | gb.stageLabel = label; 36 | groups.Insert(index, gb); 37 | groupLookup.Add(gb.label, gb); 38 | } 39 | 40 | public ItemGroupBuilder AddItemGroup(string group) 41 | { 42 | return InsertItemGroup(group, groups.Count); 43 | } 44 | 45 | public ItemGroupBuilder InsertItemGroup(string group, int index) 46 | { 47 | if (groupLookup.ContainsKey(group)) throw new ArgumentException(nameof(group)); 48 | 49 | ItemGroupBuilder gb = new() 50 | { 51 | label = group, 52 | stageLabel = label, 53 | }; 54 | 55 | groups.Insert(index, gb); 56 | groupLookup.Add(group, gb); 57 | return gb; 58 | } 59 | 60 | public bool TryGetGroup(string group, out GroupBuilder gb) 61 | { 62 | return groupLookup.TryGetValue(group, out gb); 63 | } 64 | 65 | public GroupBuilder Get(string group) 66 | { 67 | return groupLookup[group]; 68 | } 69 | 70 | public RandomizationStage ToRandomizationStage(RandoFactory factory) 71 | { 72 | List rgs = new(); 73 | foreach (GroupBuilder gb in groupLookup.Values) 74 | { 75 | gb.Apply(rgs, factory); 76 | } 77 | 78 | return new RandomizationStage 79 | { 80 | label = label, 81 | groups = rgs.ToArray(), 82 | strategy = strategy ?? new StagePlacementStrategy(), 83 | }; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /RandomizerMod/RC/Requests/SymmetricTransitionGroupBuilder.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore; 2 | using RandomizerCore.Randomization; 3 | 4 | namespace RandomizerMod.RC 5 | { 6 | public class SymmetricTransitionGroupBuilder : GroupBuilder 7 | { 8 | public readonly Bucket Group1 = new(); 9 | public readonly Bucket Group2 = new(); 10 | public bool coupled; 11 | public string reverseLabel; 12 | 13 | public override void Apply(List groups, RandoFactory factory) 14 | { 15 | if (Group1.GetTotal() != Group2.GetTotal()) 16 | { 17 | throw new InvalidOperationException($"Failed to build group {label} due to unbalanced counts."); 18 | } 19 | 20 | List t1s = new(); 21 | foreach (string s in Group1.EnumerateWithMultiplicity()) 22 | { 23 | t1s.Add(factory.MakeTransition(s)); 24 | } 25 | 26 | List t2s = new(); 27 | foreach (string s in Group2.EnumerateWithMultiplicity()) 28 | { 29 | t2s.Add(factory.MakeTransition(s)); 30 | } 31 | 32 | if (coupled) 33 | { 34 | CoupledRandomizationGroup g1 = new() 35 | { 36 | Items = t1s.ToArray(), 37 | Locations = t2s.ToArray(), 38 | Label = label, 39 | Strategy = strategy ?? factory.gs.ProgressionDepthSettings.GetTransitionPlacementStrategy(), 40 | Validator = new WeakTransitionValidator(), 41 | }; 42 | CoupledRandomizationGroup g2 = new() 43 | { 44 | Items = t2s.ToArray(), 45 | Locations = t1s.ToArray(), 46 | Label = reverseLabel, 47 | Strategy = strategy?.Clone() ?? factory.gs.ProgressionDepthSettings.GetTransitionPlacementStrategy(), 48 | Validator = new WeakTransitionValidator(), 49 | }; 50 | g1.Dual = g2; 51 | g2.Dual = g1; 52 | 53 | groups.Add(g1); 54 | groups.Add(g2); 55 | OnCreateGroup?.Invoke(g1); 56 | OnCreateGroup?.Invoke(g2); 57 | } 58 | else 59 | { 60 | RandomizationGroup g1 = new() 61 | { 62 | Items = t1s.ToArray(), 63 | Locations = t2s.ToArray(), 64 | Label = label, 65 | Strategy = strategy ?? factory.gs.ProgressionDepthSettings.GetTransitionPlacementStrategy(), 66 | }; 67 | RandomizationGroup g2 = new() 68 | { 69 | Items = t2s.ToArray(), 70 | Locations = t1s.ToArray(), 71 | Label = reverseLabel, 72 | Strategy = strategy?.Clone() ?? factory.gs.ProgressionDepthSettings.GetTransitionPlacementStrategy(), 73 | }; 74 | 75 | groups.Add(g1); 76 | groups.Add(g2); 77 | OnCreateGroup?.Invoke(g1); 78 | OnCreateGroup?.Invoke(g2); 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /RandomizerMod/RC/Requests/TransitionGroupBuilder.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore; 2 | using RandomizerCore.Randomization; 3 | 4 | namespace RandomizerMod.RC 5 | { 6 | public class TransitionGroupBuilder : GroupBuilder 7 | { 8 | public Bucket Sources { get; } = new(); 9 | public Bucket Targets { get; } = new(); 10 | 11 | public override void Apply(List groups, RandoFactory factory) 12 | { 13 | if (Sources.GetTotal() != Targets.GetTotal()) 14 | { 15 | throw new InvalidOperationException($"Failed to build group {label} due to unbalanced counts."); 16 | } 17 | 18 | List locations = new(); 19 | foreach (string s in Sources.EnumerateWithMultiplicity()) 20 | { 21 | locations.Add(factory.MakeTransition(s)); 22 | } 23 | 24 | List items = new(); 25 | foreach (string s in Targets.EnumerateWithMultiplicity()) 26 | { 27 | items.Add(factory.MakeTransition(s)); 28 | } 29 | 30 | RandomizationGroup g = new() 31 | { 32 | Label = label, 33 | Items = items.ToArray(), 34 | Locations = locations.ToArray(), 35 | Strategy = strategy ?? factory.gs.ProgressionDepthSettings.GetTransitionPlacementStrategy(), 36 | }; 37 | groups.Add(g); 38 | OnCreateGroup?.Invoke(g); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /RandomizerMod/RC/Requests/TransitionRequestInfo.cs: -------------------------------------------------------------------------------- 1 | using ItemChanger; 2 | using RandomizerCore; 3 | using RandomizerMod.RandomizerData; 4 | 5 | namespace RandomizerMod.RC 6 | { 7 | public class TransitionRequestInfo 8 | { 9 | public Func? randoTransitionCreator; 10 | public Action? onRandoTransitionCreation; 11 | public Action? onRandomizerFinish; 12 | public Func? realTargetCreator; 13 | public Func realSourceCreator; 14 | public Func? getTransitionDef; 15 | 16 | public void AddGetTransitionDefModifier(string name, Func modifier) 17 | { 18 | Func get = getTransitionDef ?? (() => Data.GetTransitionDef(name)); 19 | getTransitionDef = () => modifier(get()); 20 | } 21 | 22 | public TransitionRequestInfo Clone() 23 | { 24 | return new TransitionRequestInfo 25 | { 26 | randoTransitionCreator = (Func)randoTransitionCreator?.Clone(), 27 | onRandoTransitionCreation = (Action)onRandoTransitionCreation?.Clone(), 28 | onRandomizerFinish = (Action)onRandomizerFinish?.Clone(), 29 | realTargetCreator = (Func)realTargetCreator?.Clone(), 30 | realSourceCreator = (Func)realSourceCreator?.Clone(), 31 | getTransitionDef = (Func)getTransitionDef?.Clone() 32 | }; 33 | } 34 | 35 | public void AppendTo(TransitionRequestInfo info) 36 | { 37 | if (randoTransitionCreator != null) info.randoTransitionCreator = randoTransitionCreator; 38 | info.onRandoTransitionCreation += onRandoTransitionCreation; 39 | info.onRandomizerFinish += onRandomizerFinish; 40 | if (realTargetCreator != null) info.realTargetCreator = realTargetCreator; 41 | if (realSourceCreator != null) info.realSourceCreator = realSourceCreator; 42 | if (getTransitionDef != null) info.getTransitionDef = getTransitionDef; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /RandomizerMod/RC/SplitCloakItem.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore; 2 | using RandomizerCore.Logic; 3 | 4 | namespace RandomizerMod.RC 5 | { 6 | [Obsolete("Use StringItem for split cloak items")] 7 | public record SplitCloakItem(string Name, bool LeftBiased, Term LeftDashTerm, Term RightDashTerm) : LogicItem(Name) 8 | { 9 | public override void AddTo(ProgressionManager pm) 10 | { 11 | /* 12 | // behavior when left and right must be obtained before shade cloak 13 | bool noLeftDash = pm.Get(LeftDashTerm.Id) < 1; 14 | bool noRightDash = pm.Get(RightDashTerm.Id) < 1; 15 | // Left Dash behavior 16 | if (noLeftDash && (LeftBiased || !noRightDash)) pm.Incr(LeftDashTerm.Id, 1); 17 | // Right Dash behavior 18 | else if (noRightDash && (!LeftBiased || !noLeftDash)) pm.Incr(RightDashTerm.Id, 1); 19 | // Shade Cloak behavior (increments both flags) 20 | else 21 | { 22 | pm.Incr(LeftDashTerm.Id, 1); 23 | pm.Incr(RightDashTerm.Id, 1); 24 | } 25 | */ 26 | 27 | // behavior when split shade cloak of one direction can be obtained, but not the other 28 | bool hasLeftDash = pm.Has(LeftDashTerm.Id); 29 | bool hasRightDash = pm.Has(RightDashTerm.Id); 30 | bool hasAnyShadowDash = pm.Has(LeftDashTerm.Id, 2) || pm.Has(RightDashTerm.Id, 2); 31 | 32 | if (hasLeftDash && hasRightDash && hasAnyShadowDash) 33 | { 34 | return; // dupe 35 | } 36 | else if (hasLeftDash && hasRightDash) // full shade cloak behavior 37 | { 38 | pm.Incr(LeftDashTerm, 1); 39 | pm.Incr(RightDashTerm, 1); 40 | return; 41 | } 42 | else if (LeftBiased) 43 | { 44 | if (!hasLeftDash && hasAnyShadowDash) // left shade cloak behavior 45 | { 46 | pm.Incr(LeftDashTerm, 2); 47 | return; 48 | } 49 | else // left cloak behavior 50 | { 51 | pm.Incr(LeftDashTerm, 1); 52 | return; 53 | } 54 | } 55 | else 56 | { 57 | if (!hasRightDash && hasAnyShadowDash) // right shade cloak behavior 58 | { 59 | pm.Incr(RightDashTerm, 2); 60 | return; 61 | } 62 | else // right cloak behavior 63 | { 64 | pm.Incr(RightDashTerm, 1); 65 | return; 66 | } 67 | } 68 | } 69 | 70 | public override IEnumerable GetAffectedTerms() 71 | { 72 | yield return LeftDashTerm; 73 | yield return RightDashTerm; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /RandomizerMod/RC/StateVariables/FlowerProviderVariable.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerCore.Logic.StateLogic; 3 | 4 | namespace RandomizerMod.RC.StateVariables 5 | { 6 | /* 7 | * Prefix: $FLOWERGET 8 | * Required Parameters: none 9 | * Optiional Parameters: none 10 | */ 11 | public class FlowerProviderVariable : StateModifier 12 | { 13 | public override string Name { get; } 14 | protected readonly StateBool NoFlower; 15 | public const string Prefix = "$FLOWERGET"; 16 | 17 | public static bool TryMatch(LogicManager lm, string term, out LogicVariable variable) 18 | { 19 | if (term == Prefix) 20 | { 21 | variable = new FlowerProviderVariable(term, lm); 22 | return true; 23 | } 24 | variable = default; 25 | return false; 26 | } 27 | 28 | public FlowerProviderVariable(string name, LogicManager lm) 29 | { 30 | Name = name; 31 | try 32 | { 33 | NoFlower = lm.StateManager.GetBoolStrict("NOFLOWER"); 34 | } 35 | catch (Exception e) 36 | { 37 | throw new InvalidOperationException("Error constructing FlowerProviderVariable", e); 38 | } 39 | } 40 | 41 | protected FlowerProviderVariable(string name) 42 | { 43 | Name = name; 44 | } 45 | 46 | public override IEnumerable GetTerms() 47 | { 48 | return Enumerable.Empty(); 49 | } 50 | 51 | public override IEnumerable? ProvideState(object? sender, ProgressionManager pm) 52 | { 53 | return Enumerable.Empty(); 54 | } 55 | 56 | public override IEnumerable ModifyState(object? sender, ProgressionManager pm, LazyStateBuilder state) 57 | { 58 | state.SetBool(NoFlower, false); 59 | yield return state; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /RandomizerMod/RC/StateVariables/FragileCharmVariable.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerCore.Logic.StateLogic; 3 | 4 | namespace RandomizerMod.RC.StateVariables 5 | { 6 | /* 7 | * Equip logic for Fragile Heart, Greed, and Strength. See documentation for EquipCharmVariable for variable pattern. 8 | */ 9 | public class FragileCharmVariable : EquipCharmVariable 10 | { 11 | protected readonly Term RepairTerm; 12 | protected readonly StateBool BreakBool; 13 | 14 | public FragileCharmVariable(string name, string charmName, int charmID, LogicManager lm) : this(name, charmName, charmID, lm, "Can_Repair_Fragile_Charms", charmID switch 15 | { 16 | 23 => "BROKEHEART", 17 | 24 => "BROKEGREED", 18 | 25 => "BROKESTRENGTH", 19 | _ => throw new ArgumentException($"Error constructing FCV from {name}: Unknown fragile charm id {charmID}.") 20 | }) { } 21 | 22 | public FragileCharmVariable(string name, string charmName, int charmID, LogicManager lm, string repairTermName, string breakBoolName) : base(name, charmName, charmID, lm) 23 | { 24 | RepairTerm = lm.GetTermStrict(repairTermName) ?? throw new ArgumentException($"Error constructing ECV from {name}: {repairTermName} term does not exist?"); 25 | BreakBool = lm.StateManager.GetBoolStrict(breakBoolName) ?? throw new ArgumentException($"Error constructing ECV from {name}: could not find {breakBoolName} state bool."); 26 | } 27 | 28 | public override IEnumerable GetTerms() 29 | { 30 | return base.GetTerms().Append(RepairTerm); 31 | } 32 | 33 | public override bool HasStateRequirements(ProgressionManager pm, T state) 34 | { 35 | return base.HasStateRequirements(pm, state) && (pm.Has(CharmTerm, 2) || !state.GetBool(BreakBool) && pm.Has(RepairTerm)); 36 | } 37 | 38 | public void BreakCharm(ProgressionManager pm, ref LazyStateBuilder state) 39 | { 40 | if (pm.Has(CharmTerm, 2)) return; 41 | if (state.GetBool(CharmBool)) 42 | { 43 | state.SetBool(CharmBool, false); 44 | state.Increment(UsedNotchesInt, -((RandoModContext)pm.ctx).notchCosts[CharmID - 1]); 45 | if (state.GetBool(Overcharmed)) state.SetBool(Overcharmed, false); 46 | } 47 | state.SetBool(AnticharmBool, true); 48 | state.SetBool(BreakBool, true); 49 | } 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /RandomizerMod/RC/StateVariables/HotSpringResetVariable.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerCore.Logic.StateLogic; 3 | 4 | namespace RandomizerMod.RC.StateVariables 5 | { 6 | /* 7 | * Prefix: $HOTSPRINGRESET 8 | * Required Parameters: none 9 | * Optiional Parameters: none 10 | */ 11 | public class HotSpringResetVariable : StateModifier 12 | { 13 | public override string Name { get; } 14 | protected readonly ISoulStateManager SSM; 15 | protected readonly IHPStateManager HPSM; 16 | public const string Prefix = "$HOTSPRINGRESET"; 17 | 18 | public HotSpringResetVariable(string name, LogicManager lm) 19 | { 20 | Name = name; 21 | try 22 | { 23 | SSM = (ISoulStateManager)lm.GetVariableStrict(SoulStateManager.Prefix); 24 | HPSM = (IHPStateManager)lm.GetVariableStrict(HPStateManager.Prefix); 25 | } 26 | catch (Exception e) 27 | { 28 | throw new InvalidOperationException("Error constructing HotSpringResetVariable", e); 29 | } 30 | } 31 | 32 | public override IEnumerable GetTerms() 33 | { 34 | foreach (Term t in SSM.GetTerms(ISoulStateManager.SSMOperation.RestoreSoul)) yield return t; 35 | foreach (Term t in HPSM.GetTerms(IHPStateManager.HPSMOperation.RestoreWhiteHealth)) yield return t; 36 | } 37 | 38 | public static bool TryMatch(LogicManager lm, string term, out LogicVariable variable) 39 | { 40 | if (term == Prefix) 41 | { 42 | variable = new HotSpringResetVariable(term, lm); 43 | return true; 44 | } 45 | variable = default; 46 | return false; 47 | } 48 | 49 | public override IEnumerable? ProvideState(object? sender, ProgressionManager pm) 50 | { 51 | return []; 52 | } 53 | 54 | public override IEnumerable ModifyState(object? sender, ProgressionManager pm, LazyStateBuilder state) 55 | { 56 | SSM.TryRestoreAllSoul(pm, ref state, restoreReserves: true); 57 | return HPSM.RestoreWhiteHealth(pm, state); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /RandomizerMod/RC/StateVariables/LifebloodCountVariable.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerCore.Logic.StateLogic; 3 | 4 | namespace RandomizerMod.RC.StateVariables 5 | { 6 | /* 7 | * Prefix: $LIFEBLOOD 8 | * Required Parameters: 9 | - If any parameters are provided, the first parameter must parse to int to give the required number of blue masks (including Joni masks). 10 | If absent, defaults to 1. 11 | * Optional Parameters: none 12 | * Filters to states with determined hp which have at least a certain number of blue masks, including Joni masks. 13 | */ 14 | public class LifebloodCountVariable : StateModifier 15 | { 16 | public override string Name { get; } 17 | public const string Prefix = "$LIFEBLOOD"; 18 | 19 | protected readonly int RequiredBlueMasks; 20 | protected readonly IHPStateManager HPSM; 21 | protected readonly EquipCharmVariable JonisBlessing; 22 | 23 | public LifebloodCountVariable(string name, LogicManager lm, int requiredBlueMasks) 24 | { 25 | this.Name = name; 26 | RequiredBlueMasks = requiredBlueMasks; 27 | try 28 | { 29 | HPSM = (IHPStateManager)lm.GetVariableStrict(HPStateManager.Prefix); 30 | JonisBlessing = (EquipCharmVariable)lm.GetVariableStrict(EquipCharmVariable.GetName("Joni's_Blessing")); 31 | } 32 | catch (Exception e) 33 | { 34 | throw new InvalidOperationException("Error constructing LifebloodCountVariable", e); 35 | } 36 | } 37 | 38 | public override IEnumerable GetTerms() 39 | { 40 | return HPSM.GetTerms(IHPStateManager.HPSMOperation.GetHPInfo); 41 | } 42 | 43 | public static bool TryMatch(LogicManager lm, string term, out LogicVariable variable) 44 | { 45 | if (VariableResolver.TryMatchPrefix(term, Prefix, out string[] parameters)) 46 | { 47 | int amount = parameters.Length == 0 ? 1 : int.Parse(parameters[0]); 48 | variable = new LifebloodCountVariable(term, lm, amount); 49 | return true; 50 | } 51 | variable = default; 52 | return false; 53 | } 54 | 55 | public override IEnumerable ModifyState(object? sender, ProgressionManager pm, LazyStateBuilder state) 56 | { 57 | return HPSM.DetermineHP(pm, state).Where(s => HPSM.GetHPInfo(pm, s) is IHPStateManager.StrictHPInfo hp 58 | && hp.CurrentBlueHP + (JonisBlessing.IsEquipped(s) ? hp.CurrentWhiteHP : 0) >= RequiredBlueMasks); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /RandomizerMod/RC/StateVariables/RegainSoulVariable.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerCore.Logic.StateLogic; 3 | 4 | namespace RandomizerMod.RC.StateVariables 5 | { 6 | /* 7 | * Prefix: $REGAINSOUL 8 | * Required Parameters: 9 | 10 | * Optional Parameters: 11 | - The first parameter, if given, must parse to int to give the regain amount. Otherwise, fully restores soul. 12 | */ 13 | public class RegainSoulVariable : StateModifier 14 | { 15 | public override string Name { get; } 16 | public const string Prefix = "$REGAINSOUL"; 17 | 18 | protected readonly int? Amount; 19 | protected readonly ISoulStateManager SSM; 20 | 21 | public RegainSoulVariable(string name, LogicManager lm, int? amount) 22 | { 23 | Name = name; 24 | this.Amount = amount; 25 | try 26 | { 27 | SSM = (ISoulStateManager)lm.GetVariableStrict(SoulStateManager.Prefix); 28 | } 29 | catch (Exception e) 30 | { 31 | throw new InvalidOperationException($"Error constructing RegainSoulVariable", e); 32 | } 33 | } 34 | 35 | public static bool TryMatch(LogicManager lm, string term, out LogicVariable variable) 36 | { 37 | if (VariableResolver.TryMatchPrefix(term, Prefix, out string[] parameters)) 38 | { 39 | int amount; 40 | if (parameters.Length == 0) 41 | { 42 | amount = -1; 43 | } 44 | else if (parameters.Length == 1 && int.TryParse(parameters[0], out amount)) { } 45 | else 46 | { 47 | throw new ArgumentException($"{term} is missing amount argument for RegainSoulVariable."); 48 | } 49 | 50 | variable = new RegainSoulVariable(term, lm, amount >= 0 ? amount : null); 51 | return true; 52 | } 53 | variable = default; 54 | return false; 55 | } 56 | 57 | public override IEnumerable GetTerms() 58 | { 59 | return SSM.GetTerms(ISoulStateManager.SSMOperation.RestoreSoul); 60 | } 61 | 62 | public override IEnumerable? ProvideState(object? sender, ProgressionManager pm) 63 | { 64 | return []; 65 | } 66 | 67 | public override IEnumerable ModifyState(object? sender, ProgressionManager pm, LazyStateBuilder state) 68 | { 69 | return Amount.HasValue ? SSM.RestoreSoul(pm, state, Amount.Value) : SSM.RestoreAllSoul(pm, state, restoreReserves: true); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /RandomizerMod/RC/StateVariables/SaveQuitResetVariable.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerCore.Logic.StateLogic; 3 | 4 | namespace RandomizerMod.RC.StateVariables 5 | { 6 | /* 7 | * Prefix: $SAVEQUITRESET 8 | * Required Parameters: none 9 | * Optional Parameters: none 10 | * Provides the effect of warping via Benchwarp or savequit, regardless of destination type. 11 | */ 12 | public class SaveQuitResetVariable : StateModifier 13 | { 14 | public override string Name { get; } 15 | public const string Prefix = "$SAVEQUITRESET"; 16 | 17 | protected readonly StateBool NoFlower; 18 | protected readonly StateBool UsedShade; 19 | protected readonly StateInt RequiredMaxSoul; 20 | protected readonly ISoulStateManager SSM; 21 | protected readonly IHPStateManager HPSM; 22 | 23 | public SaveQuitResetVariable(string term, LogicManager lm) 24 | { 25 | Name = term; 26 | try 27 | { 28 | NoFlower = lm.StateManager.GetBoolStrict("NOFLOWER"); 29 | UsedShade = lm.StateManager.GetBoolStrict("USEDSHADE"); 30 | RequiredMaxSoul = lm.StateManager.GetIntStrict("REQUIREDMAXSOUL"); 31 | 32 | SSM = (ISoulStateManager)lm.GetVariableStrict(SoulStateManager.Prefix); 33 | HPSM = (IHPStateManager)lm.GetVariableStrict(HPStateManager.Prefix); 34 | } 35 | catch (Exception e) 36 | { 37 | throw new InvalidOperationException("Error constructing SaveQuitResetVariable", e); 38 | } 39 | } 40 | 41 | public static bool TryMatch(LogicManager lm, string term, out LogicVariable variable) 42 | { 43 | if (VariableResolver.TryMatchPrefix(term, Prefix, out _)) 44 | { 45 | variable = new SaveQuitResetVariable(term, lm); 46 | return true; 47 | } 48 | variable = default; 49 | return false; 50 | } 51 | 52 | public override IEnumerable GetTerms() 53 | { 54 | foreach (Term t in SSM.GetTerms()) yield return t; 55 | foreach (Term t in HPSM.GetTerms(IHPStateManager.HPSMOperation.RestoreWhiteHealth)) yield return t; 56 | } 57 | 58 | public override IEnumerable? ProvideState(object? sender, ProgressionManager pm) 59 | { 60 | return []; 61 | } 62 | 63 | public override IEnumerable ModifyState(object? sender, ProgressionManager pm, LazyStateBuilder state) 64 | { 65 | state.SetBool(NoFlower, true); // not game accurate, but we do this to prevent warps from being required for flower quest. 66 | SSM.TrySpendAllSoul(pm, ref state); // zero out soul. A subsequent modifier will handle bench / start respawn soul effects. 67 | state.SetBool(UsedShade, false); // not necessary to reset shade variables for typical use, but in the case of warping to a non-start hard respawn, it would be correct to reset them here. 68 | state.SetInt(RequiredMaxSoul, 0); 69 | return HPSM.RestoreWhiteHealth(pm, state); // bad to chain this into a cheaper restore on the bench... 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /RandomizerMod/RC/StateVariables/SlopeballVariable.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerCore.Logic.StateLogic; 3 | 4 | namespace RandomizerMod.RC.StateVariables 5 | { 6 | public class SlopeballVariable : StateModifierWrapper 7 | { 8 | public override string Name { get; } 9 | 10 | protected readonly Term slopeballSkips; 11 | protected readonly Term fireball; 12 | public const string Prefix = "$SLOPEBALL"; 13 | protected override string InnerPrefix => CastSpellVariable.Prefix; 14 | 15 | public static bool TryMatch(LogicManager lm, string term, out LogicVariable variable) 16 | { 17 | if (term.StartsWith(Prefix)) 18 | { 19 | variable = new SlopeballVariable(term, lm); 20 | return true; 21 | } 22 | variable = default; 23 | return false; 24 | } 25 | 26 | public SlopeballVariable(string name, LogicManager lm) : base(name, lm) 27 | { 28 | Name = name; 29 | try 30 | { 31 | slopeballSkips = lm.GetTermStrict("SLOPEBALLSKIPS"); 32 | fireball = lm.GetTermStrict("FIREBALL"); 33 | } 34 | catch (Exception e) 35 | { 36 | throw new InvalidOperationException("Error constructing SlopeballVariable", e); 37 | } 38 | } 39 | 40 | public override IEnumerable GetTerms() 41 | { 42 | yield return slopeballSkips; 43 | yield return fireball; 44 | foreach (Term t in InnerVariable.GetTerms()) yield return t; 45 | } 46 | 47 | public override IEnumerable ModifyState(object? sender, ProgressionManager pm, LazyStateBuilder state) 48 | { 49 | if (!pm.Has(slopeballSkips) || !pm.Has(fireball)) return Enumerable.Empty(); 50 | return InnerVariable.ModifyState(sender, pm, state); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /RandomizerMod/RC/StateVariables/SpendSoulVariable.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic.StateLogic; 2 | using RandomizerCore.Logic; 3 | 4 | namespace RandomizerMod.RC.StateVariables 5 | { 6 | /* 7 | * Prefix: $SPENDSOUL 8 | * Required Parameters: 9 | - The first parameter must parse to int to give the spend amount. 10 | * Optional Parameters: none 11 | */ 12 | public class SpendSoulVariable : StateModifier 13 | { 14 | public override string Name { get; } 15 | public const string Prefix = "$SPENDSOUL"; 16 | 17 | protected readonly int Amount; 18 | protected readonly ISoulStateManager SSM; 19 | 20 | public SpendSoulVariable(string name, LogicManager lm, int amount) 21 | { 22 | Name = name; 23 | this.Amount = amount; 24 | try 25 | { 26 | SSM = (ISoulStateManager)lm.GetVariableStrict(SoulStateManager.Prefix); 27 | } 28 | catch (Exception e) 29 | { 30 | throw new InvalidOperationException($"Error constructing SpendSoulVariable", e); 31 | } 32 | } 33 | 34 | public static bool TryMatch(LogicManager lm, string term, out LogicVariable variable) 35 | { 36 | if (VariableResolver.TryMatchPrefix(term, Prefix, out string[] parameters)) 37 | { 38 | if (parameters.Length < 1 || !int.TryParse(parameters[0], out int amount)) 39 | { 40 | throw new ArgumentException($"{term} is missing amount argument for SpendSoulVariable."); 41 | } 42 | 43 | variable = new SpendSoulVariable(term, lm, amount); 44 | return true; 45 | } 46 | variable = default; 47 | return false; 48 | } 49 | 50 | public override IEnumerable GetTerms() 51 | { 52 | return SSM.GetTerms(ISoulStateManager.SSMOperation.SpendSoul); 53 | } 54 | 55 | public override IEnumerable ModifyState(object? sender, ProgressionManager pm, LazyStateBuilder state) 56 | { 57 | return SSM.SpendSoul(pm, state, Amount); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /RandomizerMod/RC/StateVariables/StagStateVariable.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerCore.Logic.StateLogic; 3 | 4 | namespace RandomizerMod.RC.StateVariables 5 | { 6 | /* 7 | * Prefix: $STAGSTATEMODIFIER 8 | * Required Parameters: none 9 | * Optiional Parameters: none 10 | */ 11 | public class StagStateVariable : StateModifier 12 | { 13 | public override string Name { get; } 14 | protected readonly StateBool NoFlower; 15 | public const string Prefix = "$STAGSTATEMODIFIER"; 16 | 17 | public static bool TryMatch(LogicManager lm, string term, out LogicVariable variable) 18 | { 19 | if (term == Prefix) 20 | { 21 | variable = new StagStateVariable(term, lm); 22 | return true; 23 | } 24 | variable = default; 25 | return false; 26 | } 27 | 28 | protected StagStateVariable(string name) 29 | { 30 | Name = name; 31 | } 32 | 33 | public StagStateVariable(string name, LogicManager lm) 34 | { 35 | Name = name; 36 | try 37 | { 38 | NoFlower = lm.StateManager.GetBoolStrict("NOFLOWER"); 39 | } 40 | catch (Exception e) 41 | { 42 | throw new InvalidOperationException("Error constructing StagStateVariable", e); 43 | } 44 | } 45 | 46 | public override IEnumerable GetTerms() 47 | { 48 | return Enumerable.Empty(); 49 | } 50 | 51 | public override IEnumerable? ProvideState(object? sender, ProgressionManager pm) 52 | { 53 | return Enumerable.Empty(); 54 | } 55 | 56 | public override IEnumerable ModifyState(object? sender, ProgressionManager pm, LazyStateBuilder state) 57 | { 58 | state.SetBool(NoFlower, true); 59 | yield return state; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /RandomizerMod/RC/StateVariables/StartRespawnResetVariable.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerCore.Logic.StateLogic; 3 | 4 | namespace RandomizerMod.RC.StateVariables 5 | { 6 | /* 7 | * Prefix: $STARTRESPAWN 8 | * Required Parameters: none 9 | * Optional Parameters: none 10 | * Provides the effect of the start respawn. Typically applied in sequence after $SAVEQUITRESET as part of $WARPTOSTART. 11 | */ 12 | public class StartRespawnResetVariable : StateModifier 13 | { 14 | public override string Name { get; } 15 | public const string Prefix = "$STARTRESPAWN"; 16 | 17 | protected readonly ISoulStateManager SSM; 18 | 19 | public StartRespawnResetVariable(string term, LogicManager lm) 20 | { 21 | Name = term; 22 | try 23 | { 24 | SSM = (ISoulStateManager)lm.GetVariableStrict(SoulStateManager.Prefix); 25 | } 26 | catch (Exception e) 27 | { 28 | throw new InvalidOperationException("Error constructing StartRespawnResetVariable", e); 29 | } 30 | } 31 | 32 | public override IEnumerable GetTerms() 33 | { 34 | return SSM.GetTerms(ISoulStateManager.SSMOperation.RestoreSoul); 35 | } 36 | 37 | public static bool TryMatch(LogicManager lm, string term, out LogicVariable variable) 38 | { 39 | if (VariableResolver.TryMatchPrefix(term, Prefix, out _)) 40 | { 41 | variable = new StartRespawnResetVariable(term, lm); 42 | return true; 43 | } 44 | variable = default; 45 | return false; 46 | } 47 | 48 | public override IEnumerable? ProvideState(object? sender, ProgressionManager pm) 49 | { 50 | return []; 51 | } 52 | 53 | public override IEnumerable ModifyState(object? sender, ProgressionManager pm, LazyStateBuilder state) 54 | { 55 | return SSM.RestoreAllSoul(pm, state, true); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /RandomizerMod/RC/StateVariables/StateModifierWrapper.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerCore.Logic.StateLogic; 3 | using System.Security.Cryptography; 4 | 5 | namespace RandomizerMod.RC.StateVariables 6 | { 7 | /// 8 | /// Base class which handles passing the parameters in a given state modifier name to an inner state modifier. 9 | /// 10 | public abstract class StateModifierWrapper : StateModifier where T : StateModifier 11 | { 12 | public readonly T InnerVariable; 13 | public override string Name { get; } 14 | protected abstract string InnerPrefix { get; } 15 | /// 16 | /// The parameters which were not consumed, and thus were passed to the inner variable. 17 | /// 18 | protected readonly string[] InnerParameters; 19 | 20 | protected StateModifierWrapper(string name, LogicManager lm) 21 | { 22 | Name = name; 23 | try 24 | { 25 | int i = name.IndexOf('['); 26 | string innerName; 27 | if (i >= 0 && VariableResolver.TryMatchPrefix(name, name.Substring(0, i), out string[] parameters)) 28 | { 29 | InnerParameters = parameters.Where(p => !Consume(p)).ToArray(); 30 | innerName = InnerParameters.Length > 0 ? $"{InnerPrefix}[{string.Join(",", InnerParameters)}]" : InnerPrefix; 31 | } 32 | else 33 | { 34 | InnerParameters = Array.Empty(); 35 | innerName = InnerPrefix; 36 | } 37 | InnerVariable = (T)lm.GetVariableStrict(innerName); 38 | } 39 | catch (Exception e) 40 | { 41 | throw new InvalidOperationException("Error constructing " + GetType().Name, e); 42 | } 43 | } 44 | 45 | /// 46 | /// Indicates that the parameter should not be passed to the inner variable. 47 | /// 48 | protected virtual bool Consume(string parameter) => false; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RandomizerMod/RC/StateVariables/TakeDamageVariable.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerCore.Logic.StateLogic; 3 | 4 | namespace RandomizerMod.RC.StateVariables 5 | { 6 | /* 7 | * Prefix: $TAKEDAMAGE 8 | * Required Parameters: 9 | - If any parameters are provided, the first parameter must parse to int to give the damage amount. If absent, defaults to 1. 10 | * Optional Parameters: none 11 | * Implements taking damage from a single hit. Assumes enough time to focus/hiveblood before and after the hit. 12 | */ 13 | public class TakeDamageVariable : StateModifier 14 | { 15 | public override string Name { get; } 16 | public const string Prefix = "$TAKEDAMAGE"; 17 | protected readonly int Amount; 18 | protected readonly IHPStateManager HPSM; 19 | 20 | public TakeDamageVariable(string name, LogicManager lm, int amount) 21 | { 22 | Name = name; 23 | Amount = amount; 24 | try 25 | { 26 | HPSM = (IHPStateManager)lm.GetVariableStrict(HPStateManager.Prefix); 27 | } 28 | catch (Exception e) 29 | { 30 | throw new InvalidOperationException("Error constructing TakeDamageVariable", e); 31 | } 32 | } 33 | 34 | public static bool TryMatch(LogicManager lm, string term, out LogicVariable variable) 35 | { 36 | if (VariableResolver.TryMatchPrefix(term, Prefix, out string[] parameters)) 37 | { 38 | int amount = parameters.Length == 0 ? 1 : int.Parse(parameters[0]); 39 | variable = new TakeDamageVariable(term, lm, amount); 40 | return true; 41 | } 42 | variable = default; 43 | return false; 44 | } 45 | 46 | public override IEnumerable GetTerms() 47 | { 48 | return HPSM.GetTerms(IHPStateManager.HPSMOperation.TakeDamage); 49 | } 50 | 51 | public override IEnumerable ModifyState(object? sender, ProgressionManager pm, LazyStateBuilder state) 52 | { 53 | return HPSM.TakeDamage(pm, state, Amount); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /RandomizerMod/RC/StateVariables/WarpToBenchResetVariable.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerCore.Logic.StateLogic; 3 | 4 | namespace RandomizerMod.RC.StateVariables 5 | { 6 | /* 7 | * Prefix: $WARPTOBENCH 8 | * Required Parameters: none 9 | * Optional Parameters: none 10 | * Provides the effect of warping to a bench via Benchwarp or savequit. Does not verify whether the player can warp to a bench. 11 | */ 12 | public class WarpToBenchResetVariable : StateModifier 13 | { 14 | public override string Name { get; } 15 | public const string Prefix = "$WARPTOBENCH"; 16 | 17 | protected readonly SaveQuitResetVariable SaveQuitReset; 18 | protected readonly BenchResetVariable BenchReset; 19 | 20 | public WarpToBenchResetVariable(string name, LogicManager lm) 21 | { 22 | Name = name; 23 | try 24 | { 25 | SaveQuitReset = (SaveQuitResetVariable)lm.GetVariableStrict(SaveQuitResetVariable.Prefix); 26 | BenchReset = (BenchResetVariable)lm.GetVariableStrict(BenchResetVariable.Prefix); 27 | } 28 | catch (Exception e) 29 | { 30 | throw new InvalidOperationException("Error constructing WarpToBenchResetVariable", e); 31 | } 32 | } 33 | 34 | public static bool TryMatch(LogicManager lm, string term, out LogicVariable variable) 35 | { 36 | if (VariableResolver.TryMatchPrefix(term, Prefix, out _)) 37 | { 38 | variable = new WarpToBenchResetVariable(term, lm); 39 | return true; 40 | } 41 | variable = default; 42 | return false; 43 | } 44 | 45 | public override IEnumerable? ProvideState(object? sender, ProgressionManager pm) 46 | { 47 | return []; 48 | } 49 | 50 | public override IEnumerable ModifyState(object? sender, ProgressionManager pm, LazyStateBuilder state) 51 | { 52 | return SaveQuitReset.ModifyState(sender, pm, state).SelectMany(s => BenchReset.ModifyState(sender, pm, s)); 53 | } 54 | 55 | public override IEnumerable GetTerms() 56 | { 57 | return SaveQuitReset.GetTerms().Concat(BenchReset.GetTerms()); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /RandomizerMod/RC/StateVariables/WarpToStartResetVariable.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerCore.Logic.StateLogic; 3 | 4 | namespace RandomizerMod.RC.StateVariables 5 | { 6 | /* 7 | * Prefix: $WARPTOSTART 8 | * Required Parameters: none 9 | * Optional Parameters: none 10 | * Provides the effect of warping to start via Benchwarp or savequit. 11 | */ 12 | public class WarpToStartResetVariable : StateModifier 13 | { 14 | public override string Name { get; } 15 | public const string Prefix = "$WARPTOSTART"; 16 | 17 | protected readonly SaveQuitResetVariable SaveQuitReset; 18 | protected readonly StartRespawnResetVariable StartRespawnReset; 19 | 20 | public WarpToStartResetVariable(string name, LogicManager lm) 21 | { 22 | Name = name; 23 | try 24 | { 25 | SaveQuitReset = (SaveQuitResetVariable)lm.GetVariableStrict(SaveQuitResetVariable.Prefix); 26 | StartRespawnReset = (StartRespawnResetVariable)lm.GetVariableStrict(StartRespawnResetVariable.Prefix); 27 | } 28 | catch (Exception e) 29 | { 30 | throw new InvalidOperationException("Error constructing WarpToStartResetVariable", e); 31 | } 32 | } 33 | 34 | public static bool TryMatch(LogicManager lm, string term, out LogicVariable variable) 35 | { 36 | if (VariableResolver.TryMatchPrefix(term, Prefix, out _)) 37 | { 38 | variable = new WarpToStartResetVariable(term, lm); 39 | return true; 40 | } 41 | variable = default; 42 | return false; 43 | } 44 | 45 | public override IEnumerable? ProvideState(object? sender, ProgressionManager pm) 46 | { 47 | return Enumerable.Empty(); 48 | } 49 | 50 | public override IEnumerable ModifyState(object? sender, ProgressionManager pm, LazyStateBuilder state) 51 | { 52 | return SaveQuitReset.ModifyState(sender, pm, state).SelectMany(s => StartRespawnReset.ModifyState(sender, pm, s)); 53 | } 54 | 55 | public override IEnumerable GetTerms() 56 | { 57 | return SaveQuitReset.GetTerms().Concat(StartRespawnReset.GetTerms()); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /RandomizerMod/RC/StateVariables/WhiteFragmentEquipVariable.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | 3 | namespace RandomizerMod.RC.StateVariables 4 | { 5 | /* 6 | * Equip logic for Kingsoul/Void Heart. See documentation for EquipCharmVariable for variable pattern. 7 | */ 8 | public class WhiteFragmentEquipVariable : EquipCharmVariable 9 | { 10 | protected readonly int Threshold; 11 | 12 | public WhiteFragmentEquipVariable(string name, string charmName, LogicManager lm) : base(name, "WHITEFRAGMENT", 36, lm) 13 | { 14 | Threshold = charmName switch 15 | { 16 | "Void_Heart" => 3, 17 | "Kingsoul" or _ => 2, 18 | }; 19 | } 20 | 21 | public override bool HasCharmProgression(ProgressionManager pm) 22 | { 23 | return pm.Has(CharmTerm, Threshold); 24 | } 25 | 26 | public override int GetNotchCost(ProgressionManager pm, T state) 27 | { 28 | return pm.Get(CharmTerm) switch 29 | { 30 | <= 2 => base.GetNotchCost(pm, state), 31 | > 2 => 0, 32 | }; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /RandomizerMod/RC/TransitionPlacement.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore; 2 | 3 | namespace RandomizerMod.RC 4 | { 5 | public record struct TransitionPlacement(RandoModTransition Target, RandoModTransition Source) 6 | { 7 | public void Deconstruct(out RandoModTransition target, out RandoModTransition source) 8 | { 9 | target = this.Target; 10 | source = this.Source; 11 | } 12 | 13 | public static implicit operator GeneralizedPlacement(TransitionPlacement p) => new(p.Target, p.Source); 14 | public static explicit operator TransitionPlacement(GeneralizedPlacement p) => new((RandoModTransition)p.Item, (RandoModTransition)p.Location); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /RandomizerMod/RandomizerData/CostDef.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerMod.RC; 3 | 4 | namespace RandomizerMod.RandomizerData 5 | { 6 | public record CostDef(string Term, int Amount) 7 | { 8 | public virtual LogicCost ToLogicCost(LogicManager lm) 9 | { 10 | return Term switch 11 | { 12 | "GEO" => new LogicGeoCost(lm, Amount), 13 | _ => new SimpleCost(lm.GetTermStrict(Term), Amount), 14 | }; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /RandomizerMod/RandomizerData/ItemDef.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.RandomizerData 2 | { 3 | public record ItemDef 4 | { 5 | public string Name { get; init; } 6 | public string Pool { get; init; } // for items in multiple pools, give the most common pool. 7 | public int PriceCap { get; init; } 8 | public bool MajorItem { get; init; } // reserved for the most useful items in the randomizer, used by CursedSettings to penalize progression 9 | } 10 | 11 | /* 12 | [Flags] 13 | public enum ItemPoolFlags : long 14 | { 15 | None = 0L, 16 | IsSkill = 1L << 0, 17 | IsKey = 1L << 1, 18 | IsCharm = 1L << 2, 19 | IsMask = 1L << 3, 20 | IsVessel = 1L << 4, 21 | IsNotch = 1L << 5, 22 | IsOre = 1L << 6, 23 | IsGeoChest = 1L << 7, 24 | IsEgg = 1L << 8, 25 | IsRelic = 1L << 9, 26 | IsRoot = 1L << 10, 27 | IsDreamWarriorEssence = 1L << 11, 28 | IsDreamBossEssence = 1L << 12, 29 | IsGrub = 1L << 13, 30 | IsMimic = 1L << 14, 31 | IsMap = 1L << 15, 32 | IsStag = 1L << 16, 33 | IsCocoon = 1L << 17, 34 | IsFlame = 1L << 18, 35 | IsHunterJournalEntry = 1L << 19, 36 | IsRock = 1L << 20, 37 | IsSoul = 1L << 21, 38 | IsLore = 1L << 22, 39 | IsCustomAbility = 1L << 23, 40 | IsDreamer = 1L << 24, // oops 41 | } 42 | 43 | [Flags] 44 | public enum ItemPropertyFlags : long 45 | { 46 | None = 0L, 47 | IsUnique = 1L << 0, 48 | IsBigItem = 1L << 1, 49 | IsSpell = 1L << 2, 50 | IsGeo = 1L << 3, 51 | // ??? 52 | } 53 | */ 54 | } 55 | -------------------------------------------------------------------------------- /RandomizerMod/RandomizerData/JsonUtil.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Serialization; 3 | using Newtonsoft.Json.Converters; 4 | using System.IO; 5 | using System.Reflection; 6 | 7 | namespace RandomizerMod.RandomizerData 8 | { 9 | public static class JsonUtil 10 | { 11 | public static readonly JsonSerializer _js; 12 | 13 | public static T Deserialize(string embeddedResourcePath) 14 | { 15 | using (StreamReader sr = new StreamReader(typeof(JsonUtil).Assembly.GetManifestResourceStream(embeddedResourcePath))) 16 | using (var jtr = new JsonTextReader(sr)) 17 | { 18 | return _js.Deserialize(jtr); 19 | } 20 | } 21 | 22 | public static T DeserializeString(string json) 23 | { 24 | using (StringReader sr = new StringReader(json)) 25 | using (var jtr = new JsonTextReader(sr)) 26 | { 27 | return _js.Deserialize(jtr); 28 | } 29 | } 30 | 31 | public static void Serialize(object o, string fileName) 32 | { 33 | File.WriteAllText(Path.Combine(Path.GetDirectoryName(typeof(JsonUtil).Assembly.Location), fileName), Serialize(o)); 34 | } 35 | 36 | public static string Serialize(object o) 37 | { 38 | using (StringWriter sw = new StringWriter()) 39 | { 40 | _js.Serialize(sw, o); 41 | sw.Flush(); 42 | return sw.ToString(); 43 | } 44 | } 45 | 46 | public static void Serialize(TextWriter tw, object o) 47 | { 48 | _js.Serialize(tw, o); 49 | } 50 | 51 | static JsonUtil() 52 | { 53 | _js = new JsonSerializer 54 | { 55 | DefaultValueHandling = DefaultValueHandling.Include, 56 | Formatting = Formatting.Indented, 57 | TypeNameHandling = TypeNameHandling.Auto, 58 | }; 59 | 60 | _js.Converters.Add(new StringEnumConverter()); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /RandomizerMod/RandomizerData/LocationDef.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace RandomizerMod.RandomizerData 4 | { 5 | public record LocationDef 6 | { 7 | public string Name { get; init; } 8 | public string SceneName { get; init; } 9 | [JsonIgnore] public virtual string TitledArea { get => Data.GetRoomDef(SceneName)?.TitledArea; } 10 | [JsonIgnore] public virtual string MapArea { get => Data.GetRoomDef(SceneName)?.MapArea; } 11 | /// 12 | /// If true, copies of this location after the first may be shuffled among other flexible count locations by the RequestBuilder. 13 | /// 14 | public bool FlexibleCount { get; init; } 15 | /// 16 | /// If true, copies of this location after the first will receive severe penalties to prevent multiple progression items. 17 | /// 18 | public bool AdditionalProgressionPenalty { get; init; } 19 | } 20 | 21 | // Incomplete ideas for potential enhancements below: 22 | /* 23 | [Flags] 24 | public enum LocationPoolFlags : long 25 | { 26 | None = 0L, 27 | IsSkill = 1L << 0, 28 | IsKey = 1L << 1, 29 | IsCharm = 1L << 2, 30 | IsMask = 1L << 3, 31 | IsVessel = 1L << 4, 32 | IsNotch = 1L << 5, 33 | IsOre = 1L << 6, 34 | IsGeoChest = 1L << 7, 35 | IsEgg = 1L << 8, 36 | IsRelic = 1L << 9, 37 | IsRoot = 1L << 10, 38 | IsDreamWarriorEssence = 1L << 11, 39 | IsDreamBossEssence = 1L << 12, 40 | IsGrub = 1L << 13, 41 | IsMimic = 1L << 14, 42 | IsMap = 1L << 15, 43 | IsStag = 1L << 16, 44 | IsCocoon = 1L << 17, 45 | IsFlame = 1L << 18, 46 | IsHunterJournalEntry = 1L << 19, 47 | IsRock = 1L << 20, 48 | IsSoul = 1L << 21, 49 | IsLore = 1L << 22, 50 | IsCustomAbility = 1L << 23, 51 | IsDreamer = 1L << 24, // oops 52 | } 53 | 54 | [Flags] 55 | public enum LocationPropertyFlags : long 56 | { 57 | None, 58 | 59 | IsChest, 60 | IsShop, 61 | } 62 | */ 63 | } 64 | -------------------------------------------------------------------------------- /RandomizerMod/RandomizerData/PoolDef.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.RandomizerData 2 | { 3 | /// 4 | /// Data structure representing a collection of items and locations that can be optionally randomized. 5 | /// 6 | public class PoolDef 7 | { 8 | /// 9 | /// The name of the pool. 10 | /// 11 | public string Name { get; init; } 12 | /// 13 | /// A slightly broader classification which merges smaller pools into larger ones (e.g. Focus into Skill, etc). Used by SplitGroupSettings. 14 | /// 15 | public string Group { get; init; } 16 | public string Path { get; init; } 17 | public string[] IncludeItems { get; init; } 18 | public string[] IncludeLocations { get; init; } 19 | public VanillaDef[] Vanilla { get; init; } 20 | 21 | public bool IsIncluded(Settings.GenerationSettings gs) 22 | { 23 | if (!string.IsNullOrEmpty(Path) 24 | && Settings.Util.Get(gs, Path) is bool value 25 | && value) return true; 26 | else return false; 27 | } 28 | 29 | public bool IsVanilla(Settings.GenerationSettings gs) 30 | { 31 | if (!string.IsNullOrEmpty(Path) 32 | && Settings.Util.Get(gs, Path) is bool value 33 | && !value) return true; 34 | else return false; 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /RandomizerMod/RandomizerData/PoolNames.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.RandomizerData 2 | { 3 | public static class PoolNames 4 | { 5 | public const string Dreamer = "Dreamer"; 6 | public const string Skill = "Skill"; 7 | public const string Charm = "Charm"; 8 | public const string Key = "Key"; 9 | public const string Focus = "Focus"; 10 | public const string Swim = "Swim"; 11 | public const string Mask = "Mask"; 12 | public const string CursedMask = "CursedMask"; 13 | public const string Vessel = "Vessel"; 14 | public const string Notch = "Notch"; 15 | public const string SalubraNotch = "SalubraNotch"; 16 | public const string CursedNotch = "CursedNotch"; 17 | public const string Ore = "Ore"; 18 | public const string Geo = "Geo"; 19 | public const string JunkPitChest = "JunkPitChest"; 20 | public const string Egg = "Egg"; 21 | public const string Relic = "Relic"; 22 | public const string Root = "Root"; 23 | public const string DreamWarrior = "DreamWarrior"; 24 | public const string DreamBoss = "DreamBoss"; 25 | public const string Grub = "Grub"; 26 | public const string Mimic = "Mimic"; 27 | public const string Map = "Map"; 28 | public const string Stag = "Stag"; 29 | public const string Cocoon = "Cocoon"; 30 | public const string Flame = "Flame"; 31 | public const string Journal = "Journal"; 32 | public const string ElevatorPass = "ElevatorPass"; 33 | public const string SplitCloak = "SplitCloak"; 34 | public const string SplitClaw = "SplitClaw"; 35 | public const string CursedNail = "CursedNail"; 36 | public const string SplitSuperdash = "SplitSuperdash"; 37 | public const string EggShop = "EggShop"; 38 | public const string Rock = "Rock"; 39 | public const string Boss_Geo = "Boss_Geo"; 40 | public const string Soul = "Soul"; 41 | public const string Lore = "Lore"; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /RandomizerMod/RandomizerData/RoomDef.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.RandomizerData 2 | { 3 | public record RoomDef 4 | { 5 | public string SceneName { get; init; } 6 | public string MapArea { get; init; } 7 | public string TitledArea { get; init; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RandomizerMod/RandomizerData/StartDef.cs: -------------------------------------------------------------------------------- 1 | using GlobalEnums; 2 | using RandomizerCore; 3 | using RandomizerCore.Logic; 4 | using RandomizerMod.Settings; 5 | 6 | namespace RandomizerMod.RandomizerData 7 | { 8 | public record StartDef 9 | { 10 | /// 11 | /// The name of the start. Names should be unique. 12 | /// 13 | public string Name { get; init; } 14 | /// 15 | /// The scene of the start location in-game. 16 | /// 17 | public string SceneName { get; init; } 18 | /// 19 | /// The x-coordinate of the start location in-game. 20 | /// 21 | public float X { get; init; } 22 | /// 23 | /// The y-coordinate of the start location in-game. 24 | /// 25 | public float Y { get; init; } 26 | /// 27 | /// The map zone of the start location in-game. 28 | /// 29 | public MapZone Zone { get; init; } 30 | 31 | /// 32 | /// The transition which is used as the initial logical progression for this start location. 33 | /// 34 | public string Transition { get; init; } 35 | 36 | /// 37 | /// Logic evaluated by the SettingsPM to determine whether the start can be selected in the menu. Must not be null. 38 | /// 39 | public string Logic { get; init; } 40 | /// 41 | /// Logic evaluated by the SettingsPM to determine whether the start can be randomly selected. If null, Logic is used instead. 42 | /// 43 | public string RandoLogic { get; init; } 44 | /// 45 | /// Flag which determines whether the start is given a button in the Start Locations menu. Hidden starts can still be randomly selected. 46 | /// 47 | public bool ExcludeFromMenu { get; init; } 48 | 49 | public virtual bool CanBeSelected(SettingsPM pm) 50 | { 51 | return pm.Evaluate(Logic); 52 | } 53 | 54 | public virtual bool CanBeRandomized(SettingsPM pm) 55 | { 56 | return pm.Evaluate(RandoLogic ?? Logic); 57 | } 58 | 59 | public virtual bool DisplayInMenu(SettingsPM pm) 60 | { 61 | return !ExcludeFromMenu; 62 | } 63 | 64 | /// 65 | /// Returns a sequence of term values which will be treated as setters by the ProgressionInitializer. 66 | ///
State-valued terms in the sequence will be linked to Start_State, regardless of the int parameter. 67 | ///
68 | public virtual IEnumerable GetStartLocationProgression(LogicManager lm) 69 | { 70 | yield return new(lm.GetTermStrict(Transition), 1); 71 | } 72 | 73 | public virtual ItemChanger.StartDef ToItemChangerStartDef() 74 | { 75 | return new ItemChanger.StartDef 76 | { 77 | SceneName = SceneName, 78 | X = X, 79 | Y = Y, 80 | MapZone = (int)Zone, 81 | RespawnFacingRight = true, 82 | SpecialEffects = ItemChanger.SpecialStartEffects.Default | ItemChanger.SpecialStartEffects.SlowSoulRefill, 83 | }; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /RandomizerMod/RandomizerData/TransitionDef.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace RandomizerMod.RandomizerData 4 | { 5 | public record TransitionDef 6 | { 7 | [JsonIgnore] public string Name => $"{SceneName}[{DoorName}]"; 8 | public string SceneName { get; init; } 9 | public string DoorName { get; init; } 10 | [JsonIgnore] public virtual string TitledArea { get => Data.GetRoomDef(SceneName)?.TitledArea; } 11 | [JsonIgnore] public virtual string MapArea { get => Data.GetRoomDef(SceneName)?.MapArea; } 12 | public string VanillaTarget { get; init; } 13 | public TransitionDirection Direction { get; init; } 14 | public bool IsTitledAreaTransition { get; init; } 15 | public bool IsMapAreaTransition { get; init; } 16 | public TransitionSides Sides { get; init; } 17 | } 18 | 19 | public enum TransitionSides 20 | { 21 | Both = 0, 22 | /// 23 | /// A one way transition exiting a scene. 24 | /// 25 | OneWayIn = 1, 26 | /// 27 | /// A one way transition entering a scene. 28 | /// 29 | OneWayOut = 2, 30 | } 31 | 32 | public enum TransitionDirection 33 | { 34 | Door, 35 | Left, 36 | Right, 37 | Top, 38 | Bot, 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /RandomizerMod/RandomizerData/VanillaDef.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.RandomizerData 2 | { 3 | public record VanillaDef(string Item, string Location, CostDef[]? Costs = null) 4 | { 5 | public virtual bool Equals(VanillaDef other) 6 | { 7 | return other != null && Item == other.Item && Location == other.Location && 8 | (Costs == other.Costs || (Costs != null && other.Costs != null && Costs.SequenceEqual(other.Costs))); 9 | } 10 | 11 | public override int GetHashCode() 12 | { 13 | return Item.GetHashCode() ^ Location.GetHashCode() + (Costs != null ? Costs.Length : -1); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /RandomizerMod/Resources/Data/logic_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "PRECISEMOVEMENT": "SkipSettings.PreciseMovement", 3 | "PROFICIENTCOMBAT": "SkipSettings.ProficientCombat", 4 | "BACKGROUNDPOGOS": "SkipSettings.BackgroundObjectPogos", 5 | "ENEMYPOGOS": "SkipSettings.EnemyPogos", 6 | "OBSCURESKIPS": "SkipSettings.ObscureSkips", 7 | "SHADESKIPS": "SkipSettings.ShadeSkips", 8 | "INFECTIONSKIPS": "SkipSettings.InfectionSkips", 9 | "FIREBALLSKIPS": "SkipSettings.FireballSkips", 10 | "SPIKETUNNELS": "SkipSettings.SpikeTunnels", 11 | "ACIDSKIPS": "SkipSettings.AcidSkips", 12 | "DAMAGEBOOSTS": "SkipSettings.DamageBoosts", 13 | "DANGEROUSSKIPS": "SkipSettings.DangerousSkips", 14 | "DARKROOMS": "SkipSettings.DarkRooms", 15 | "SLOPEBALLSKIPS": "SkipSettings.Slopeballs", 16 | "SHRIEKPOGOSKIPS": "SkipSettings.ShriekPogos", 17 | "COMPLEXSKIPS": "SkipSettings.ComplexSkips", 18 | "DIFFICULTSKIPS": "SkipSettings.DifficultSkips", 19 | "RANDOMNAIL": "NoveltySettings.RandomizeNail", 20 | "CURSED": "CursedSettings.RemoveSpellUpgrades", 21 | "RANDOMFOCUS": "NoveltySettings.RandomizeFocus", 22 | "RANDOMELEVATORS": "NoveltySettings.RandomizeElevatorPass" 23 | } -------------------------------------------------------------------------------- /RandomizerMod/Resources/Logic/state.json: -------------------------------------------------------------------------------- 1 | { 2 | "Fields": { 3 | "Bool": [ 4 | "USEDSHADE", 5 | "OVERCHARMED", 6 | "CANNOTOVERCHARM", 7 | "CANNOTREGAINSOUL", 8 | "CANNOTSHADESKIP", 9 | "BROKEHEART", 10 | "BROKEGREED", 11 | "BROKESTRENGTH", 12 | "NOFLOWER", 13 | "NOPASSEDCHARMEQUIP", 14 | "CHARM1", 15 | "CHARM2", 16 | "CHARM3", 17 | "CHARM4", 18 | "CHARM5", 19 | "CHARM6", 20 | "CHARM7", 21 | "CHARM8", 22 | "CHARM9", 23 | "CHARM10", 24 | "CHARM11", 25 | "CHARM12", 26 | "CHARM13", 27 | "CHARM14", 28 | "CHARM15", 29 | "CHARM16", 30 | "CHARM17", 31 | "CHARM18", 32 | "CHARM19", 33 | "CHARM20", 34 | "CHARM21", 35 | "CHARM22", 36 | "CHARM23", 37 | "CHARM24", 38 | "CHARM25", 39 | "CHARM26", 40 | "CHARM27", 41 | "CHARM28", 42 | "CHARM29", 43 | "CHARM30", 44 | "CHARM31", 45 | "CHARM32", 46 | "CHARM33", 47 | "CHARM34", 48 | "CHARM35", 49 | "CHARM36", 50 | "CHARM37", 51 | "CHARM38", 52 | "CHARM39", 53 | "CHARM40", 54 | 55 | "noCHARM1", 56 | "noCHARM2", 57 | "noCHARM3", 58 | "noCHARM4", 59 | "noCHARM5", 60 | "noCHARM6", 61 | "noCHARM7", 62 | "noCHARM8", 63 | "noCHARM9", 64 | "noCHARM10", 65 | "noCHARM11", 66 | "noCHARM12", 67 | "noCHARM13", 68 | "noCHARM14", 69 | "noCHARM15", 70 | "noCHARM16", 71 | "noCHARM17", 72 | "noCHARM18", 73 | "noCHARM19", 74 | "noCHARM20", 75 | "noCHARM21", 76 | "noCHARM22", 77 | "noCHARM23", 78 | "noCHARM24", 79 | "noCHARM25", 80 | "noCHARM26", 81 | "noCHARM27", 82 | "noCHARM28", 83 | "noCHARM29", 84 | "noCHARM30", 85 | "noCHARM31", 86 | "noCHARM32", 87 | "noCHARM33", 88 | "noCHARM34", 89 | "noCHARM35", 90 | "noCHARM36", 91 | "noCHARM37", 92 | "noCHARM38", 93 | "noCHARM39", 94 | "noCHARM40" 95 | 96 | ], 97 | "Int": [ 98 | "SPENTSOUL", 99 | "SPENTRESERVESOUL", 100 | "SOULLIMITER", 101 | "REQUIREDMAXSOUL", 102 | "SPENTHP", 103 | "SPENTBLUEHP", 104 | "LAZYSPENTHP", 105 | "USEDNOTCHES", 106 | "MAXNOTCHCOST" 107 | ] 108 | }, 109 | "Properties": { 110 | "NOFLOWER": { 111 | "DefaultValue": true 112 | }, 113 | "NOPASSEDCHARMEQUIP": { 114 | "DefaultValue": true 115 | } 116 | }, 117 | "NamedStates": {}, 118 | "NamedStateUnions": {} 119 | } 120 | -------------------------------------------------------------------------------- /RandomizerMod/Resources/entries.txt: -------------------------------------------------------------------------------- 1 | Great Hopper 2 | Mantis Petra 3 | Gluttonous Husk 4 | Pilflip 5 | Garpede 6 | Weathered Mask 7 | Uumuu 8 | Violent Husk 9 | Tiktik 10 | Sharp Baldur 11 | Death Loodle 12 | Winged Sentry 13 | Wandering Husk 14 | Husk Miner 15 | Obble 16 | Mossy Vagabond 17 | Crawlid 18 | Maskfly 19 | Hornet 20 | Gruzzer 21 | Lance Sentry 22 | Husk Bully 23 | Grimmkin Novice 24 | Goam 25 | Mistake 26 | Nightmare King 27 | Leaping Husk 28 | Nosk 29 | Aspid Mother 30 | Boofly 31 | Great Nailsage Sly 32 | Sibling 33 | Gorb 34 | Loodle 35 | Deepling 36 | Armoured Squit 37 | Fungified Husk 38 | Primal Aspid 39 | Baldur 40 | Grey Prince Zote 41 | Little Weaver 42 | Corpse Creeper 43 | Squit 44 | Charged Lumafly 45 | Hollow Knight 46 | Mosskin 47 | Broken Vessel 48 | Fungling 49 | Ooma 50 | Husk Warrior 51 | Mantis Warrior 52 | Mossfly 53 | Hiveling 54 | Mosscreep 55 | Hopping Zoteling 56 | Kingsmould 57 | Volatile Mosskin 58 | Stalking Devout 59 | Lesser Mawlek 60 | Wingmould 61 | Shrumeling 62 | Aluba 63 | Mantis Lords 64 | Aspid Hunter 65 | Volt Twister 66 | Aspid Hatchling 67 | Crystal Hunter 68 | Void Idol 69 | Shrumal Ogre 70 | No Eyes 71 | Radiance 72 | Glimback 73 | Seal of Binding 74 | Watcher Knight 75 | Xero 76 | Vengefly 77 | Lifeseed 78 | Pure Vessel 79 | Shielded Fool 80 | Traitor Lord 81 | Hive Guardian 82 | Spiny Husk 83 | Cowardly Husk 84 | Hopper 85 | Gorgeous Husk 86 | Dirtcarver 87 | Menderbug 88 | Marmu 89 | Soul Twister 90 | God Tamer 91 | Nailmasters Oro & Mato 92 | Bluggsac 93 | Sporg 94 | Flukemarm 95 | Pale Lurker 96 | Husk Hornhead 97 | Gruz Mother 98 | Mantis Youth 99 | Vengefly King 100 | Massive Moss Charger 101 | Dung Defender 102 | Grimmkin Master 103 | Shrumal Warrior 104 | Hive Knight 105 | Winged Zoteling 106 | Duranda 107 | Moss Charger 108 | Elder Hu 109 | Heavy Sentry 110 | Flukefey 111 | Husk Hive 112 | Paintmaster Sheo 113 | Entombed Husk 114 | Durandoo 115 | Volatile Zoteling 116 | Soul Master 117 | Mantis Traitor 118 | Markoth 119 | Grimmkin Nightmare 120 | Sturdy Fool 121 | Heavy Fool 122 | White Defender 123 | Shadow Creeper 124 | Elder Baldur 125 | Royal Retainer 126 | Hive Soldier 127 | Void Tendrils 128 | Shade 129 | Ambloom 130 | Flukemunga 131 | Folly 132 | Slobbering Husk 133 | Crystallised Husk 134 | Crystal Crawler 135 | Volatile Gruzzer 136 | Grub Mimic 137 | Great Husk Sentry 138 | Crystal Guardian 139 | The Collector 140 | Zote 141 | Flukemon 142 | Lightseed 143 | Galien 144 | Husk Guard 145 | Grimm 146 | Maggot 147 | Husk Dandy 148 | Gulka 149 | Hunter's Mark 150 | Carver Hatcher 151 | Winged Fool 152 | Shardmite 153 | Husk Sentry 154 | Mawlurk 155 | Deephunter 156 | False Knight 157 | Belfly 158 | Fool Eater 159 | Brooding Mawlek 160 | Moss Knight 161 | Fungoon 162 | Soul Warrior 163 | Oblobble 164 | Infected Balloon 165 | Battle Obble 166 | Uoma 167 | Furious Vengefly 168 | Hwurmp -------------------------------------------------------------------------------- /RandomizerMod/Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/homothetyhk/RandomizerMod/06b601c05ce08c90df2452c858855314e8037e45/RandomizerMod/Resources/logo.png -------------------------------------------------------------------------------- /RandomizerMod/Settings/CostSettings.cs: -------------------------------------------------------------------------------- 1 | using MenuChanger.Attributes; 2 | 3 | namespace RandomizerMod.Settings 4 | { 5 | public class CostSettings : SettingsModule 6 | { 7 | [DynamicBound(nameof(MaximumGrubCost), true)] 8 | [MenuRange(0, 46)] 9 | public int MinimumGrubCost; 10 | 11 | [TriggerValidation(nameof(GrubTolerance))] 12 | [DynamicBound(nameof(MinimumGrubCost), false)] 13 | [MenuRange(0, 46)] 14 | public int MaximumGrubCost; 15 | 16 | [DynamicBound(nameof(GrubToleranceUB), true)] 17 | [MenuRange(0, 46)] 18 | public int GrubTolerance; 19 | private int GrubToleranceUB => 46 - MaximumGrubCost; 20 | 21 | 22 | [DynamicBound(nameof(MaximumEssenceCost), true)] 23 | [MenuRange(0, 2800)] 24 | public int MinimumEssenceCost; 25 | 26 | [DynamicBound(nameof(MinimumEssenceCost), false)] 27 | [MenuRange(0, 2800)] 28 | public int MaximumEssenceCost; 29 | 30 | [MenuRange(0, 250)] 31 | public int EssenceTolerance; 32 | 33 | 34 | [DynamicBound(nameof(MaximumEggCost), true)] 35 | [MenuRange(0, 21)] 36 | public int MinimumEggCost; 37 | 38 | [TriggerValidation(nameof(EggTolerance))] 39 | [DynamicBound(nameof(MinimumEggCost), false)] 40 | [MenuRange(0, 21)] 41 | public int MaximumEggCost; 42 | 43 | [DynamicBound(nameof(EggToleranceUB), true)] 44 | [MenuRange(0, 21)] 45 | public int EggTolerance; 46 | private int EggToleranceUB => 21 - MaximumEggCost; 47 | 48 | 49 | [DynamicBound(nameof(MaximumCharmCost), true)] 50 | [MenuRange(0, 40)] 51 | public int MinimumCharmCost; 52 | 53 | [TriggerValidation(nameof(CharmTolerance))] 54 | [DynamicBound(nameof(MinimumCharmCost), false)] 55 | [MenuRange(0, 40)] 56 | public int MaximumCharmCost; 57 | 58 | [DynamicBound(nameof(CharmToleranceUB), true)] 59 | [MenuRange(0, 40)] 60 | public int CharmTolerance; 61 | private int CharmToleranceUB => 40 - MaximumCharmCost; 62 | 63 | 64 | public override void Clamp(GenerationSettings gs) 65 | { 66 | if (MaximumGrubCost < MinimumGrubCost) MaximumGrubCost = MinimumGrubCost; 67 | if (GrubTolerance + MaximumGrubCost > 46) GrubTolerance = 46 - MaximumGrubCost; 68 | 69 | if (MaximumEssenceCost < MinimumEssenceCost) MaximumEssenceCost = MinimumEssenceCost; 70 | 71 | if (MaximumEggCost < MinimumEggCost) MaximumEggCost = MinimumEggCost; 72 | if (EggTolerance + MaximumEggCost > 21) EggTolerance = 21 - MaximumEggCost; 73 | 74 | if (MaximumCharmCost < MinimumCharmCost) MaximumCharmCost = MinimumCharmCost; 75 | if (CharmTolerance + MaximumCharmCost > 40) CharmTolerance = 40 - MaximumCharmCost; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/CursedSettings.cs: -------------------------------------------------------------------------------- 1 | using MenuChanger.Attributes; 2 | using RandomizerCore.Extensions; 3 | using System.Text; 4 | 5 | namespace RandomizerMod.Settings 6 | { 7 | [Serializable] 8 | public class CursedSettings : SettingsModule 9 | { 10 | public bool LongerProgressionChains; 11 | public bool ReplaceJunkWithOneGeo; 12 | public bool RemoveSpellUpgrades; 13 | public bool Deranged; 14 | [MenuRange(0, 4)] 15 | public int CursedMasks; 16 | [MenuRange(0, 2)] 17 | public int CursedNotches; 18 | public bool RandomizeMimics; 19 | [MinValue(0)] 20 | public int MaximumGrubsReplacedByMimics; 21 | 22 | public string ToMultiline() 23 | { 24 | StringBuilder sb = new("Curses"); 25 | foreach (var field in Util.GetFieldNames(typeof(CursedSettings))) 26 | { 27 | sb.AppendLine($"{field.FromCamelCase()}: {Util.Get(this, field)}"); 28 | } 29 | 30 | return sb.ToString(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/DuplicateItemSettings.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Settings 2 | { 3 | public class DuplicateItemSettings : SettingsModule 4 | { 5 | public bool MothwingCloak; 6 | public bool MantisClaw; 7 | public bool CrystalHeart; 8 | public bool MonarchWings; 9 | public bool ShadeCloak; 10 | public bool DreamNail; 11 | public bool VoidHeart; 12 | public bool Dreamer; 13 | public bool SwimmingItems; 14 | public bool LevelOneSpells; 15 | 16 | public bool LevelTwoSpells; 17 | public bool Grimmchild; 18 | public bool NailArts; 19 | public bool CursedNailItems; 20 | public bool DuplicateUniqueKeys; 21 | public SimpleKeySetting SimpleKeyHandling; 22 | public SplitItemSetting SplitClawHandling; 23 | public SplitItemSetting SplitCloakHandling; 24 | public SplitItemSetting SplitSuperdashHandling; 25 | public enum SimpleKeySetting 26 | { 27 | NoDupe, 28 | TwoExtraKeysInLogic, 29 | TwoDupeKeys, 30 | } 31 | 32 | public enum SplitItemSetting 33 | { 34 | NoDupe, 35 | DupeLeft, 36 | DupeRight, 37 | DupeRandom, 38 | DupeBoth, 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/GlobalSettings.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Settings 2 | { 3 | public class GlobalSettings 4 | { 5 | public GenerationSettings DefaultMenuSettings = new(); 6 | public List Profiles = new(){ null }; 7 | 8 | public static bool IsInvalid(GlobalSettings value) 9 | { 10 | return value is null || value.Profiles is null || value.DefaultMenuSettings is null; 11 | } 12 | } 13 | 14 | public class MenuProfile 15 | { 16 | public string name; 17 | public GenerationSettings settings; 18 | public override string ToString() 19 | { 20 | return name; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/LongLocationSettings.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Settings 2 | { 3 | public class LongLocationSettings : SettingsModule 4 | { 5 | public enum WPSetting 6 | { 7 | Allowed, 8 | ExcludePathOfPain, 9 | ExcludeWhitePalace 10 | } 11 | 12 | public enum BossEssenceSetting 13 | { 14 | All, 15 | ExcludeZoteAndWhiteDefender, 16 | ExcludeAllDreamBosses, 17 | ExcludeAllDreamWarriors 18 | } 19 | 20 | public enum CostItemHintSettings 21 | { 22 | CostAndName, 23 | CostOnly, 24 | NameOnly, 25 | None 26 | } 27 | 28 | public WPSetting WhitePalaceRando; 29 | public BossEssenceSetting BossEssenceRando; 30 | 31 | public bool ColosseumPreview; 32 | public bool KingFragmentPreview; 33 | 34 | public bool FlowerQuestPreview; 35 | public bool GreyPrinceZotePreview; 36 | 37 | public bool WhisperingRootPreview; 38 | public bool DreamerPreview; 39 | 40 | public bool AbyssShriekPreview; 41 | public bool VoidHeartPreview; 42 | 43 | public bool GodtunerPreview; 44 | public bool LoreTabletPreview; 45 | 46 | public bool BasinFountainPreview; 47 | public bool NailmasterPreview; 48 | 49 | public bool StagPreview; 50 | public bool MapPreview; 51 | 52 | public bool DivinePreview; 53 | 54 | public CostItemHintSettings GeoShopPreview; 55 | public CostItemHintSettings GrubfatherPreview; 56 | public CostItemHintSettings SeerPreview; 57 | public CostItemHintSettings EggShopPreview; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/MiscSettings.cs: -------------------------------------------------------------------------------- 1 | using MenuChanger.Attributes; 2 | 3 | namespace RandomizerMod.Settings 4 | { 5 | public class MiscSettings : SettingsModule 6 | { 7 | public bool RandomizeNotchCosts; 8 | [MenuRange(0, 240)][DynamicBound(nameof(MaxRandomNotchTotal), true)] public int MinRandomNotchTotal = 70; 9 | [MenuRange(0, 240)][DynamicBound(nameof(MinRandomNotchTotal), false)] public int MaxRandomNotchTotal = 110; 10 | public bool ExtraPlatforms; 11 | public SalubraNotchesSetting SalubraNotches; 12 | public MaskShardType MaskShards; 13 | public VesselFragmentType VesselFragments; 14 | public bool SteelSoul; 15 | public ToggleableFireballSetting FireballUpgrade; 16 | 17 | public enum MaskShardType 18 | { 19 | FourShardsPerMask, 20 | TwoShardsPerMask, 21 | OneShardPerMask 22 | } 23 | 24 | public enum VesselFragmentType 25 | { 26 | ThreeFragmentsPerVessel, 27 | TwoFragmentsPerVessel, 28 | OneFragmentPerVessel 29 | } 30 | 31 | public enum SalubraNotchesSetting 32 | { 33 | GroupedWithCharmNotchesPool, 34 | Vanilla, 35 | Randomized, 36 | AutoGivenAtCharmThreshold 37 | } 38 | 39 | public enum ToggleableFireballSetting 40 | { 41 | Normal, 42 | Deferred, 43 | Toggleable 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/NoveltySettings.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Settings 2 | { 3 | public class NoveltySettings : SettingsModule 4 | { 5 | public bool RandomizeSwim; 6 | public bool RandomizeElevatorPass; 7 | public bool RandomizeNail; 8 | public bool RandomizeFocus; 9 | public bool SplitClaw; 10 | public bool SplitCloak; 11 | public bool SplitSuperdash; 12 | public bool EggShop; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/PoolSettings.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Settings 2 | { 3 | public class PoolSettings : SettingsModule 4 | { 5 | public bool Dreamers; 6 | public bool Skills; 7 | public bool Charms; 8 | public bool Keys; 9 | public bool MaskShards; 10 | public bool VesselFragments; 11 | public bool PaleOre; 12 | public bool CharmNotches; 13 | public bool GeoChests; 14 | public bool Relics; 15 | public bool RancidEggs; 16 | public bool Stags; 17 | public bool Maps; 18 | public bool WhisperingRoots; 19 | public bool Grubs; 20 | public bool LifebloodCocoons; 21 | public bool SoulTotems; 22 | public bool GrimmkinFlames; 23 | public bool GeoRocks; 24 | public bool BossEssence; 25 | public bool BossGeo; 26 | public bool LoreTablets; 27 | 28 | public bool JournalEntries; 29 | public bool JunkPitChests; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/Presets/CostPresetData.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Settings.Presets 2 | { 3 | public static class CostPresetData 4 | { 5 | public static CostSettings Standard; 6 | public static CostSettings More; 7 | public static CostSettings Less; 8 | public static CostSettings Expert; 9 | public static Dictionary CostPresets; 10 | 11 | static CostPresetData() 12 | { 13 | Standard = new CostSettings 14 | { 15 | GrubTolerance = 2, 16 | MinimumGrubCost = 1, 17 | MaximumGrubCost = 23, 18 | EssenceTolerance = 150, 19 | MinimumEssenceCost = 1, 20 | MaximumEssenceCost = 900, 21 | MinimumEggCost = 1, 22 | MaximumEggCost = 15, 23 | EggTolerance = 2, 24 | MinimumCharmCost = 1, 25 | MaximumCharmCost = 20, 26 | CharmTolerance = 2, 27 | }; 28 | Less = new CostSettings 29 | { 30 | GrubTolerance = 2, 31 | MinimumGrubCost = 1, 32 | MaximumGrubCost = 15, 33 | EssenceTolerance = 150, 34 | MinimumEssenceCost = 1, 35 | MaximumEssenceCost = 600, 36 | MinimumEggCost = 1, 37 | MaximumEggCost = 10, 38 | EggTolerance = 2, 39 | MinimumCharmCost = 1, 40 | MaximumCharmCost = 10, 41 | CharmTolerance = 2, 42 | }; 43 | More = new CostSettings 44 | { 45 | GrubTolerance = 4, 46 | MinimumGrubCost = 1, 47 | MaximumGrubCost = 42, 48 | EssenceTolerance = 200, 49 | MinimumEssenceCost = 1, 50 | MaximumEssenceCost = 1800, 51 | MinimumEggCost = 1, 52 | MaximumEggCost = 19, 53 | EggTolerance = 2, 54 | MinimumCharmCost = 1, 55 | MaximumCharmCost = 38, 56 | CharmTolerance = 2, 57 | }; 58 | Expert = new CostSettings 59 | { 60 | GrubTolerance = 0, 61 | MinimumGrubCost = 5, 62 | MaximumGrubCost = 42, 63 | EssenceTolerance = 20, 64 | MinimumEssenceCost = 1, 65 | MaximumEssenceCost = 1800, 66 | MinimumEggCost = 5, 67 | MaximumEggCost = 15, 68 | EggTolerance = 0, 69 | MinimumCharmCost = 5, 70 | MaximumCharmCost = 40, 71 | CharmTolerance = 0, 72 | }; 73 | 74 | CostPresets = new Dictionary 75 | { 76 | { "Standard", Standard }, 77 | { "More", More }, 78 | { "Less", Less }, 79 | { "Expert", Expert }, 80 | }; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/Presets/CursePresetData.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Settings.Presets 2 | { 3 | public static class CursePresetData 4 | { 5 | public static CursedSettings None; 6 | public static CursedSettings Classic; 7 | public static CursedSettings Modern; 8 | public static CursedSettings UltraCursed; 9 | 10 | public static Dictionary CursedPresets; 11 | 12 | static CursePresetData() 13 | { 14 | None = new CursedSettings 15 | { 16 | ReplaceJunkWithOneGeo = false, 17 | RemoveSpellUpgrades = false, 18 | LongerProgressionChains = false, 19 | Deranged = false, 20 | CursedMasks = 0, 21 | CursedNotches = 0, 22 | RandomizeMimics = false, 23 | MaximumGrubsReplacedByMimics = 0, 24 | }; 25 | Classic = new CursedSettings 26 | { 27 | ReplaceJunkWithOneGeo = true, 28 | RemoveSpellUpgrades = true, 29 | LongerProgressionChains = true, 30 | Deranged = false, 31 | CursedMasks = 0, 32 | CursedNotches = 0, 33 | RandomizeMimics = false, 34 | MaximumGrubsReplacedByMimics = 0, 35 | }; 36 | Modern = new CursedSettings 37 | { 38 | ReplaceJunkWithOneGeo = false, 39 | RemoveSpellUpgrades = false, 40 | LongerProgressionChains = false, 41 | Deranged = true, 42 | CursedMasks = 4, 43 | CursedNotches = 2, 44 | RandomizeMimics = true, 45 | MaximumGrubsReplacedByMimics = 10, 46 | }; 47 | UltraCursed = new CursedSettings 48 | { 49 | ReplaceJunkWithOneGeo = true, 50 | RemoveSpellUpgrades = true, 51 | LongerProgressionChains = true, 52 | Deranged = true, 53 | CursedMasks = 4, 54 | CursedNotches = 2, 55 | RandomizeMimics = true, 56 | MaximumGrubsReplacedByMimics = 10, 57 | }; 58 | 59 | CursedPresets = new Dictionary 60 | { 61 | { "None", None }, 62 | { "Classic", Classic }, 63 | { "Modern", Modern }, 64 | { "Ultra Cursed", UltraCursed }, 65 | }; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/Presets/DuplicateItemPresetData.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Settings.Presets 2 | { 3 | public static class DuplicateItemPresetData 4 | { 5 | public static DuplicateItemSettings DuplicateMajorItems; 6 | public static DuplicateItemSettings None; 7 | public static Dictionary Presets; 8 | 9 | 10 | static DuplicateItemPresetData() 11 | { 12 | DuplicateMajorItems = new() 13 | { 14 | MothwingCloak = true, 15 | MantisClaw = true, 16 | CrystalHeart = true, 17 | MonarchWings = true, 18 | ShadeCloak = true, 19 | DreamNail = true, 20 | VoidHeart = true, 21 | Dreamer = true, 22 | SwimmingItems = true, 23 | LevelOneSpells = true, 24 | LevelTwoSpells = false, 25 | Grimmchild = false, 26 | NailArts = false, 27 | CursedNailItems = false, 28 | DuplicateUniqueKeys = false, 29 | SimpleKeyHandling = DuplicateItemSettings.SimpleKeySetting.TwoExtraKeysInLogic, 30 | SplitClawHandling = DuplicateItemSettings.SplitItemSetting.NoDupe, 31 | SplitCloakHandling = DuplicateItemSettings.SplitItemSetting.DupeBoth, 32 | }; 33 | None = new() 34 | { 35 | MothwingCloak = false, 36 | MantisClaw = false, 37 | CrystalHeart = false, 38 | MonarchWings = false, 39 | ShadeCloak = false, 40 | DreamNail = false, 41 | VoidHeart = false, 42 | Dreamer = false, 43 | SwimmingItems = false, 44 | LevelOneSpells = false, 45 | LevelTwoSpells = false, 46 | Grimmchild = false, 47 | NailArts = false, 48 | CursedNailItems = false, 49 | DuplicateUniqueKeys = false, 50 | SimpleKeyHandling = DuplicateItemSettings.SimpleKeySetting.NoDupe, 51 | SplitClawHandling = DuplicateItemSettings.SplitItemSetting.NoDupe, 52 | SplitCloakHandling = DuplicateItemSettings.SplitItemSetting.NoDupe, 53 | }; 54 | 55 | Presets = new() 56 | { 57 | { "Duplicate Major Items", DuplicateMajorItems }, 58 | { "None", None }, 59 | }; 60 | } 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/Presets/MiscPresetData.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Settings.Presets 2 | { 3 | public static class MiscPresetData 4 | { 5 | public static MiscSettings Standard; 6 | public static MiscSettings Classic; 7 | public static MiscSettings ConsolidatedItems; 8 | public static Dictionary MiscPresets; 9 | 10 | static MiscPresetData() 11 | { 12 | Standard = new MiscSettings 13 | { 14 | MaskShards = MiscSettings.MaskShardType.FourShardsPerMask, 15 | VesselFragments = MiscSettings.VesselFragmentType.ThreeFragmentsPerVessel, 16 | RandomizeNotchCosts = true, 17 | MinRandomNotchTotal = 70, 18 | MaxRandomNotchTotal = 110, 19 | ExtraPlatforms = true, 20 | SalubraNotches = MiscSettings.SalubraNotchesSetting.GroupedWithCharmNotchesPool, 21 | SteelSoul = false, 22 | FireballUpgrade = MiscSettings.ToggleableFireballSetting.Normal, 23 | }; 24 | 25 | Classic = new MiscSettings 26 | { 27 | MaskShards = MiscSettings.MaskShardType.FourShardsPerMask, 28 | VesselFragments = MiscSettings.VesselFragmentType.ThreeFragmentsPerVessel, 29 | RandomizeNotchCosts = false, 30 | MinRandomNotchTotal = 70, 31 | MaxRandomNotchTotal = 110, 32 | ExtraPlatforms = true, 33 | SalubraNotches = MiscSettings.SalubraNotchesSetting.AutoGivenAtCharmThreshold, 34 | SteelSoul = false, 35 | FireballUpgrade = MiscSettings.ToggleableFireballSetting.Normal, 36 | }; 37 | 38 | ConsolidatedItems = new MiscSettings 39 | { 40 | MaskShards = MiscSettings.MaskShardType.OneShardPerMask, 41 | VesselFragments = MiscSettings.VesselFragmentType.OneFragmentPerVessel, 42 | RandomizeNotchCosts = true, 43 | MinRandomNotchTotal = 70, 44 | MaxRandomNotchTotal = 110, 45 | ExtraPlatforms = true, 46 | SalubraNotches = MiscSettings.SalubraNotchesSetting.GroupedWithCharmNotchesPool, 47 | SteelSoul = false, 48 | FireballUpgrade = MiscSettings.ToggleableFireballSetting.Normal, 49 | }; 50 | 51 | MiscPresets = new Dictionary 52 | { 53 | { "Standard", Standard }, 54 | { "Classic", Classic }, 55 | { "Consolidated Items", ConsolidatedItems }, 56 | }; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/Presets/NoveltyPresetData.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Settings.Presets 2 | { 3 | public static class NoveltyPresetData 4 | { 5 | public static Dictionary NoveltyPresets; 6 | public static NoveltySettings None; 7 | public static NoveltySettings Basic; 8 | public static NoveltySettings Clawful; 9 | public static NoveltySettings SplitStuff; 10 | public static NoveltySettings Everything; 11 | 12 | 13 | static NoveltyPresetData() 14 | { 15 | None = new() 16 | { 17 | RandomizeSwim = false, 18 | RandomizeElevatorPass = false, 19 | RandomizeNail = false, 20 | RandomizeFocus = false, 21 | SplitClaw = false, 22 | SplitCloak = false, 23 | SplitSuperdash = false, 24 | EggShop = false, 25 | }; 26 | 27 | Basic = new() 28 | { 29 | RandomizeSwim = true, 30 | RandomizeElevatorPass = true, 31 | RandomizeNail = false, 32 | RandomizeFocus = false, 33 | SplitClaw = false, 34 | SplitCloak = false, 35 | SplitSuperdash = false, 36 | EggShop = true, 37 | }; 38 | 39 | Clawful = new() 40 | { 41 | RandomizeSwim = true, 42 | RandomizeElevatorPass = true, 43 | RandomizeNail = false, 44 | RandomizeFocus = false, 45 | SplitClaw = true, 46 | SplitCloak = false, 47 | EggShop = true, 48 | }; 49 | 50 | SplitStuff = new() 51 | { 52 | RandomizeSwim = true, 53 | RandomizeElevatorPass = true, 54 | RandomizeNail = false, 55 | RandomizeFocus = false, 56 | SplitClaw = true, 57 | SplitCloak = true, 58 | SplitSuperdash = true, 59 | EggShop = true, 60 | }; 61 | 62 | Everything = new() 63 | { 64 | RandomizeSwim = true, 65 | RandomizeElevatorPass = true, 66 | RandomizeNail = true, 67 | RandomizeFocus = true, 68 | SplitClaw = true, 69 | SplitCloak = true, 70 | SplitSuperdash = true, 71 | EggShop = true, 72 | }; 73 | 74 | NoveltyPresets = new() 75 | { 76 | { "Basic", Basic }, 77 | { "Clawful", Clawful }, 78 | { "Split Stuff", SplitStuff }, 79 | { "Everything", Everything }, 80 | { "None", None }, 81 | }; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/Presets/ProgressionDepthPresetData.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Settings.Presets 2 | { 3 | public static class ProgressionDepthPresetData 4 | { 5 | public static ProgressionDepthSettings Default; 6 | public static ProgressionDepthSettings Relaxed; 7 | public static ProgressionDepthSettings Unweighted; 8 | public static ProgressionDepthSettings DelayedWeight; 9 | public static Dictionary Presets; 10 | 11 | static ProgressionDepthPresetData() 12 | { 13 | Default = new(); 14 | Relaxed = new() 15 | { 16 | LocationPriorityTransformCoefficient = 2.5f, 17 | LocationPriorityTransformType = RandomizerCore.Randomization.PriorityTransformUtil.TransformType.SquareRoot 18 | }; 19 | Unweighted = new() 20 | { 21 | ItemLocationPriorityInteraction = RandomizerCore.Randomization.PriorityTransformUtil.ItemPriorityDepthEffect.Ignore, 22 | LocationPriorityTransformCoefficient = 0f, 23 | LocationPriorityTransformType = RandomizerCore.Randomization.PriorityTransformUtil.TransformType.Linear, 24 | DuplicateItemPenalty = false, 25 | MultiLocationPenalty = false, 26 | TransitionPriorityTransformCoefficient = 0f, 27 | TransitionTransitionPriorityInteraction = RandomizerCore.Randomization.PriorityTransformUtil.ItemPriorityDepthEffect.Ignore, 28 | TransitionPriorityTransformType = RandomizerCore.Randomization.PriorityTransformUtil.TransformType.Linear, 29 | }; 30 | DelayedWeight = new() 31 | { 32 | ItemLocationPriorityInteraction = RandomizerCore.Randomization.PriorityTransformUtil.ItemPriorityDepthEffect.Fade, 33 | LocationPriorityTransformCoefficient = 0.1f, 34 | LocationPriorityTransformType = RandomizerCore.Randomization.PriorityTransformUtil.TransformType.Quadratic, 35 | }; 36 | 37 | Presets = new() 38 | { 39 | { "Default", Default }, 40 | { "Relaxed", Relaxed }, 41 | { "Unweighted", Unweighted }, 42 | { "Delayed Weight", DelayedWeight}, 43 | }; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/Presets/StartItemPresetData.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Settings.Presets 2 | { 3 | public static class StartItemPresetData 4 | { 5 | public static StartItemSettings EarlyGeo; 6 | public static StartItemSettings GeoAndStartItems; 7 | public static StartItemSettings None; 8 | 9 | public static Dictionary StartItemPresets; 10 | 11 | 12 | 13 | 14 | static StartItemPresetData() 15 | { 16 | EarlyGeo = new StartItemSettings 17 | { 18 | MinimumStartGeo = 300, 19 | MaximumStartGeo = 600, 20 | }; 21 | 22 | GeoAndStartItems = new StartItemSettings 23 | { 24 | MinimumStartGeo = 300, 25 | MaximumStartGeo = 600, 26 | Stags = StartItemSettings.StartStagType.ZeroOrMoreRandomStags, 27 | Charms = StartItemSettings.StartCharmType.ZeroOrMore, 28 | HorizontalMovement = StartItemSettings.StartHorizontalType.ZeroOrMore, 29 | VerticalMovement = StartItemSettings.StartVerticalType.ZeroOrMore, 30 | MiscItems = StartItemSettings.StartMiscItems.ZeroOrMore, 31 | }; 32 | 33 | None = new StartItemSettings 34 | { 35 | MinimumStartGeo = 0, 36 | MaximumStartGeo = 0, 37 | }; 38 | 39 | StartItemPresets = new Dictionary 40 | { 41 | { "Early Geo", EarlyGeo }, 42 | { "Geo and Start Items", GeoAndStartItems }, 43 | { "None", None }, 44 | }; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/Presets/StartLocationPresetData.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Settings.Presets 2 | { 3 | public static class StartLocationPresetData 4 | { 5 | public static StartLocationSettings KingsPass; 6 | public static StartLocationSettings RandomNoKP; 7 | public static StartLocationSettings RandomWithKP; 8 | 9 | public static Dictionary StartLocationPresets; 10 | 11 | static StartLocationPresetData() 12 | { 13 | KingsPass = new StartLocationSettings 14 | { 15 | StartLocationType = StartLocationSettings.RandomizeStartLocationType.Fixed, 16 | StartLocation = "King's Pass", 17 | }; 18 | 19 | RandomNoKP = new StartLocationSettings 20 | { 21 | StartLocationType = StartLocationSettings.RandomizeStartLocationType.RandomExcludingKP, 22 | StartLocation = null, 23 | }; 24 | 25 | RandomWithKP = new StartLocationSettings 26 | { 27 | StartLocationType = StartLocationSettings.RandomizeStartLocationType.Random, 28 | StartLocation = null, 29 | }; 30 | 31 | StartLocationPresets = new Dictionary 32 | { 33 | { "King's Pass", KingsPass }, 34 | { "Random (no King's Pass)", RandomNoKP }, 35 | { "Random (allow King's Pass)", RandomWithKP }, 36 | }; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/Presets/TransitionPresetData.cs: -------------------------------------------------------------------------------- 1 | using static RandomizerMod.Settings.TransitionSettings; 2 | 3 | namespace RandomizerMod.Settings.Presets 4 | { 5 | public static class TransitionPresetData 6 | { 7 | public static TransitionSettings None; 8 | public static TransitionSettings MapArea; 9 | public static TransitionSettings Area; 10 | public static TransitionSettings Room; 11 | public static TransitionSettings ConnectedAreaRoom; 12 | public static TransitionSettings Chaos; 13 | public static Dictionary TransitionPresets; 14 | 15 | static TransitionPresetData() 16 | { 17 | None = new TransitionSettings 18 | { 19 | Mode = TransitionMode.None, 20 | AreaConstraint = AreaConstraintSetting.None, 21 | TransitionMatching = TransitionMatchingSetting.MatchingDirections, 22 | Coupled = true, 23 | }; 24 | 25 | MapArea = new TransitionSettings 26 | { 27 | Mode = TransitionMode.MapAreaRandomizer, 28 | AreaConstraint = AreaConstraintSetting.None, 29 | TransitionMatching = TransitionMatchingSetting.MatchingDirections, 30 | Coupled = true, 31 | }; 32 | 33 | Area = new TransitionSettings 34 | { 35 | Mode = TransitionMode.FullAreaRandomizer, 36 | AreaConstraint = AreaConstraintSetting.None, 37 | TransitionMatching = TransitionMatchingSetting.MatchingDirections, 38 | Coupled = true, 39 | }; 40 | 41 | ConnectedAreaRoom = new TransitionSettings 42 | { 43 | Mode = TransitionMode.RoomRandomizer, 44 | AreaConstraint = AreaConstraintSetting.MoreConnectedMapAreas, 45 | TransitionMatching = TransitionMatchingSetting.MatchingDirections, 46 | Coupled = true, 47 | }; 48 | 49 | Room = new TransitionSettings 50 | { 51 | Mode = TransitionMode.RoomRandomizer, 52 | AreaConstraint = AreaConstraintSetting.None, 53 | TransitionMatching = TransitionMatchingSetting.MatchingDirections, 54 | Coupled = true, 55 | }; 56 | 57 | Chaos = new TransitionSettings 58 | { 59 | Mode = TransitionMode.RoomRandomizer, 60 | AreaConstraint = AreaConstraintSetting.None, 61 | TransitionMatching = TransitionMatchingSetting.NonmatchingDirections, 62 | Coupled = false, 63 | }; 64 | 65 | TransitionPresets = new Dictionary 66 | { 67 | { "None", None }, 68 | { "Map Area Rando", MapArea }, 69 | { "Full Area Rando", Area }, 70 | { "Connected-Area Room Rando", ConnectedAreaRoom }, 71 | { "Room Rando", Room }, 72 | { "Chaos Room Rando", Chaos }, 73 | }; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/ProgressionDepthSettings.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Randomization; 2 | using static RandomizerCore.Randomization.PriorityTransformUtil; 3 | 4 | namespace RandomizerMod.Settings 5 | { 6 | public class ProgressionDepthSettings : SettingsModule 7 | { 8 | public bool MultiLocationPenalty = true; 9 | public bool DuplicateItemPenalty = true; 10 | 11 | public TransformType LocationPriorityTransformType = TransformType.Linear; 12 | public ItemPriorityDepthEffect ItemLocationPriorityInteraction = ItemPriorityDepthEffect.Cliff; 13 | public float LocationPriorityTransformCoefficient = 3f; 14 | 15 | public TransformType TransitionPriorityTransformType = TransformType.SquareRoot; 16 | public ItemPriorityDepthEffect TransitionTransitionPriorityInteraction = ItemPriorityDepthEffect.Cliff; 17 | public float TransitionPriorityTransformCoefficient = 1f; 18 | 19 | public DefaultGroupPlacementStrategy GetItemPlacementStrategy() 20 | { 21 | return new(CreateTransform(LocationPriorityTransformCoefficient, LocationPriorityTransformType, ItemLocationPriorityInteraction)); 22 | } 23 | 24 | public DefaultGroupPlacementStrategy GetTransitionPlacementStrategy() 25 | { 26 | return new(CreateTransform(TransitionPriorityTransformCoefficient, TransitionPriorityTransformType, TransitionTransitionPriorityInteraction)); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/RandomizerSettings.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using RandomizerCore.Json; 3 | using RandomizerMod.RC; 4 | 5 | namespace RandomizerMod.Settings 6 | { 7 | public class RandomizerSettings 8 | { 9 | public GenerationSettings GenerationSettings; 10 | public int ProfileID = -1; // LocalSettings load before GameManager.instance.profileId or PlayerData.instance.profileId are loaded. 11 | public TrackerData TrackerData; 12 | public TrackerData TrackerDataWithoutSequenceBreaks; 13 | [JsonIgnore] 14 | public RandoModContext Context; 15 | 16 | public void Setup() 17 | { 18 | if (GenerationSettings != null && Context == null && ProfileID >= 0) 19 | { 20 | string rawSpoilerPath = Path.Combine(Logging.LogManager.UserDirectory, "RawSpoiler.json"); 21 | if (!File.Exists(rawSpoilerPath)) 22 | { 23 | LogError($"No file found at {rawSpoilerPath}!"); 24 | return; 25 | } 26 | 27 | using FileStream fs = File.OpenRead(rawSpoilerPath); 28 | using StreamReader sr = new(fs); 29 | using JsonTextReader jtr = new(sr); 30 | try 31 | { 32 | Context = JsonUtil.DeserializeFromReader(jtr); 33 | } 34 | catch (Exception e) 35 | { 36 | LogError($"Error deserializing raw spoiler from {rawSpoilerPath}\n:{e}"); 37 | } 38 | try 39 | { 40 | TrackerData?.Setup(GenerationSettings, Context); 41 | TrackerDataWithoutSequenceBreaks?.Setup(GenerationSettings, Context); 42 | } 43 | catch (Exception e) 44 | { 45 | LogError($"Error setting up tracker data:\n{e}"); 46 | } 47 | 48 | Logging.LogManager.UpdateRecent(ProfileID); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/SettingsModule.cs: -------------------------------------------------------------------------------- 1 | using MenuChanger.Attributes; 2 | using System.Reflection; 3 | 4 | namespace RandomizerMod.Settings 5 | { 6 | public abstract class SettingsModule : ICloneable 7 | { 8 | /// 9 | /// Randomize the fields of the module. 10 | /// 11 | public virtual void Randomize(Random rng) 12 | { 13 | foreach (FieldInfo f in Util.GetOrderedFields(GetType())) 14 | { 15 | Type T = f.FieldType; 16 | 17 | if (T == typeof(bool)) 18 | { 19 | f.SetValue(this, rng.Next(2) == 0); 20 | } 21 | else if (T == typeof(int) || T.IsEnum && Enum.GetUnderlyingType(T) == typeof(int)) 22 | { 23 | int maxValue = int.MaxValue - 1; 24 | int minValue = int.MinValue; 25 | 26 | if (f.GetCustomAttribute() is MenuRangeAttribute range) 27 | { 28 | maxValue = (int)range.max; 29 | minValue = (int)range.min; 30 | } 31 | else 32 | { 33 | if (f.GetCustomAttribute() is MaxValueAttribute max) maxValue = max.Value; 34 | else if (T.IsEnum) 35 | { 36 | maxValue = Enum.GetValues(T).Cast().Max(); 37 | } 38 | 39 | if (f.GetCustomAttribute() is MinValueAttribute min) minValue = min.Value; 40 | else if (T.IsEnum) 41 | { 42 | minValue = Enum.GetValues(T).Cast().Min(); 43 | } 44 | } 45 | 46 | f.SetValue(this, rng.Next(minValue, maxValue + 1)); 47 | } 48 | } 49 | } 50 | 51 | /// 52 | /// Fix all compatibility or range issues with current settings. 53 | /// 54 | public virtual void Clamp(GenerationSettings gs) 55 | { 56 | 57 | } 58 | 59 | public virtual void CopyTo(SettingsModule target) 60 | { 61 | Type T = GetType(); 62 | if (target.GetType() != T) throw new ArgumentException(nameof(target)); 63 | foreach (FieldInfo f in Util.GetOrderedFields(T)) 64 | { 65 | f.SetValue(target, f.GetValue(this)); 66 | } 67 | } 68 | 69 | public virtual object Clone() 70 | { 71 | return MemberwiseClone(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/SkipSettings.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Settings 2 | { 3 | public class SkipSettings : SettingsModule 4 | { 5 | public bool PreciseMovement; 6 | public bool ProficientCombat; 7 | public bool BackgroundObjectPogos; 8 | public bool EnemyPogos; 9 | public bool ObscureSkips; 10 | public bool ShadeSkips; 11 | public bool InfectionSkips; 12 | public bool FireballSkips; 13 | public bool SpikeTunnels; 14 | public bool AcidSkips; 15 | public bool DamageBoosts; 16 | public bool DangerousSkips; 17 | public bool DarkRooms; 18 | public bool Slopeballs; 19 | public bool ShriekPogos; 20 | public bool ComplexSkips; 21 | public bool DifficultSkips; 22 | public override void Clamp(GenerationSettings gs) 23 | { 24 | base.Clamp(gs); 25 | if (gs.MiscSettings.FireballUpgrade != MiscSettings.ToggleableFireballSetting.Toggleable) Slopeballs = false; 26 | if (gs.MiscSettings.SteelSoul) ShadeSkips = false; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/SplitGroupSettings.cs: -------------------------------------------------------------------------------- 1 | using MenuChanger.Attributes; 2 | using RandomizerMod.RandomizerData; 3 | using System.Reflection; 4 | 5 | namespace RandomizerMod.Settings 6 | { 7 | public class SplitGroupSettings : SettingsModule 8 | { 9 | public bool RandomizeOnStart; 10 | 11 | [MenuRange(-1, 99)] 12 | public int Dreamers; 13 | [MenuRange(-1, 99)] 14 | public int Skills; 15 | [MenuRange(-1, 99)] 16 | public int Charms; 17 | [MenuRange(-1, 99)] 18 | public int Keys; 19 | [MenuRange(-1, 99)] 20 | public int MaskShards; 21 | [MenuRange(-1, 99)] 22 | public int VesselFragments; 23 | [MenuRange(-1, 99)] 24 | public int CharmNotches; 25 | [MenuRange(-1, 99)] 26 | public int PaleOre; 27 | [MenuRange(-1, 99)] 28 | public int GeoChests; 29 | [MenuRange(-1, 99)] 30 | public int RancidEggs; 31 | [MenuRange(-1, 99)] 32 | public int Relics; 33 | [MenuRange(-1, 99)] 34 | public int WhisperingRoots; 35 | [MenuRange(-1, 99)] 36 | public int BossEssence; 37 | [MenuRange(-1, 99)] 38 | public int Grubs; 39 | [MenuRange(-1, 99)] 40 | public int Mimics; 41 | [MenuRange(-1, 99)] 42 | public int Maps; 43 | [MenuRange(-1, 99)] 44 | public int Stags; 45 | [MenuRange(-1, 99)] 46 | public int LifebloodCocoons; 47 | [MenuRange(-1, 99)] 48 | public int GrimmkinFlames; 49 | [MenuRange(-1, 99)] 50 | public int JournalEntries; 51 | [MenuRange(-1, 99)] 52 | public int GeoRocks; 53 | [MenuRange(-1, 99)] 54 | public int BossGeo; 55 | [MenuRange(-1, 99)] 56 | public int SoulTotems; 57 | [MenuRange(-1, 99)] 58 | public int LoreTablets; 59 | 60 | public override void Randomize(Random rng) 61 | { 62 | foreach (FieldInfo fi in IntFields.Values) 63 | { 64 | int e = (int)fi.GetValue(this); 65 | if (e < 0 || e > 2) continue; 66 | fi.SetValue(this, rng.Next(3)); 67 | } 68 | } 69 | 70 | public static readonly Dictionary IntFields = typeof(SplitGroupSettings) 71 | .GetFields(BindingFlags.Instance | BindingFlags.Public) 72 | .Where(fi => fi.FieldType == typeof(int)) 73 | .ToDictionary(fi => fi.Name); 74 | 75 | public bool TryGetValue(PoolDef def, out int value) 76 | { 77 | if (def.Group != null && IntFields.TryGetValue(def.Group, out FieldInfo fi)) 78 | { 79 | int i = (int)fi.GetValue(this); 80 | if (i >= 0) 81 | { 82 | value = i; 83 | return true; 84 | } 85 | } 86 | value = -1; 87 | return false; 88 | } 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/StartItemSettings.cs: -------------------------------------------------------------------------------- 1 | using MenuChanger.Attributes; 2 | 3 | namespace RandomizerMod.Settings 4 | { 5 | [Serializable] 6 | public class StartItemSettings : SettingsModule 7 | { 8 | [DynamicBound(nameof(MaximumStartGeo), true)] 9 | [MenuRange(0, 50000)] 10 | public int MinimumStartGeo; 11 | 12 | [DynamicBound(nameof(MinimumStartGeo), false)] 13 | [MenuRange(0, 50000)] 14 | public int MaximumStartGeo; 15 | 16 | public enum StartVerticalType 17 | { 18 | None, 19 | ZeroOrMore, 20 | OneRandomItem, 21 | MantisClaw, 22 | MonarchWings, 23 | All, 24 | } 25 | public StartVerticalType VerticalMovement; 26 | 27 | public enum StartHorizontalType 28 | { 29 | None, 30 | ZeroOrMore, 31 | OneRandomItem, 32 | MothwingCloak, 33 | CrystalHeart, 34 | All 35 | } 36 | public StartHorizontalType HorizontalMovement; 37 | 38 | public enum StartCharmType 39 | { 40 | None, 41 | ZeroOrMore, 42 | OneRandomItem, 43 | } 44 | public StartCharmType Charms; 45 | 46 | public enum StartStagType 47 | { 48 | None, 49 | DirtmouthStag, 50 | ZeroOrMoreRandomStags, 51 | OneRandomStag, 52 | ManyRandomStags, 53 | AllStags 54 | } 55 | public StartStagType Stags; 56 | 57 | public enum StartMiscItems 58 | { 59 | None, 60 | ZeroOrMore, 61 | Many, 62 | DreamNail, 63 | DreamNailAndMore, 64 | } 65 | public StartMiscItems MiscItems; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/StartLocationSettings.cs: -------------------------------------------------------------------------------- 1 | using RandomizerMod.RandomizerData; 2 | 3 | namespace RandomizerMod.Settings 4 | { 5 | [Serializable] 6 | public class StartLocationSettings : SettingsModule 7 | { 8 | public enum RandomizeStartLocationType 9 | { 10 | Fixed, 11 | RandomExcludingKP, 12 | Random, 13 | } 14 | 15 | public RandomizeStartLocationType StartLocationType; 16 | 17 | public string StartLocation; 18 | 19 | public override void Clamp(GenerationSettings gs) 20 | { 21 | base.Clamp(gs); 22 | if (StartLocationType == RandomizeStartLocationType.Fixed && StartLocation == null) 23 | { 24 | LogWarn("Found null fixed start location during Clamp."); 25 | StartLocation = Data.GetStartNames().First(); 26 | } 27 | } 28 | 29 | public void SetStartLocation(string start) => StartLocation = start; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /RandomizerMod/Settings/TransitionSettings.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerMod.Settings 2 | { 3 | [Serializable] 4 | public class TransitionSettings : SettingsModule 5 | { 6 | public enum TransitionMode 7 | { 8 | None, 9 | MapAreaRandomizer, 10 | FullAreaRandomizer, 11 | RoomRandomizer, 12 | } 13 | public TransitionMode Mode; 14 | 15 | public enum AreaConstraintSetting 16 | { 17 | None, 18 | MoreConnectedMapAreas, 19 | MoreConnectedTitledAreas, 20 | } 21 | 22 | public AreaConstraintSetting AreaConstraint; 23 | 24 | /* 25 | // This will likely be difficult to implement -- not many rooms which don't have items or npcs or events 26 | // and then even fewer combinations which give matching transition counts 27 | public enum RemoveRoomsSetting 28 | { 29 | None, 30 | RemoveEmptyHallways, 31 | AggressivelyRemoveRooms, 32 | } 33 | public RemoveRoomsSetting Remove; 34 | */ 35 | 36 | public enum TransitionMatchingSetting 37 | { 38 | MatchingDirections, 39 | MatchingDirectionsAndNoDoorToDoor, 40 | NonmatchingDirections 41 | } 42 | public TransitionMatchingSetting TransitionMatching; 43 | public bool Coupled = true; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /RandomizerModTests/Extensions.cs: -------------------------------------------------------------------------------- 1 | namespace RandomizerModTests 2 | { 3 | public static class Extensions 4 | { 5 | public static IEnumerable Product(this IEnumerable> eeos) 6 | { 7 | IEnumerator[] eos = eeos 8 | .Select(x => x.GetEnumerator()) 9 | .Where(x => x.MoveNext()) 10 | .ToArray(); 11 | 12 | while (true) 13 | { 14 | object[] os = new object[eos.Length]; 15 | for (int i = 0; i < eos.Length; i++) os[i] = eos[i].Current; 16 | yield return os; 17 | 18 | for (int i = 0; i < eos.Length; i++) 19 | { 20 | IEnumerator eo = eos[i]; 21 | if (!eo.MoveNext()) 22 | { 23 | if (i + 1 == eos.Length) { yield break; } 24 | eo.Reset(); 25 | eo.MoveNext(); 26 | break; 27 | } 28 | } 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /RandomizerModTests/LogicFixture.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerCore.Logic.StateLogic; 3 | using RandomizerMod.RandomizerData; 4 | using RandomizerMod.RC; 5 | 6 | namespace RandomizerModTests 7 | { 8 | public class LogicFixture 9 | { 10 | public LogicManager LM { get; } 11 | 12 | private RandoModContext Default_CTX { get; } 13 | 14 | public LogicFixture() 15 | { 16 | Data.Load(); 17 | Default_CTX = new(new(), Data.GetStartDef("King's Pass")); 18 | Default_CTX.notchCosts.AddRange(CharmNotchCosts._vanillaCosts); 19 | LM = Default_CTX.LM; 20 | } 21 | 22 | public RandoModContext GetContext() 23 | { 24 | return new(Default_CTX); 25 | } 26 | 27 | public ProgressionManager GetProgressionManager() 28 | { 29 | return new(LM, new RandoModContext(Default_CTX)); 30 | } 31 | 32 | public ProgressionManager GetProgressionManager(Dictionary pmFieldValues) 33 | { 34 | ProgressionManager pm = GetProgressionManager(); 35 | foreach (var kvp in pmFieldValues) pm.Set(kvp.Key, kvp.Value); 36 | return pm; 37 | } 38 | 39 | public LazyStateBuilder GetState(Dictionary stateFieldValues) 40 | { 41 | LazyStateBuilder lsb = new(LM.StateManager.DefaultState); 42 | foreach (var kvp in stateFieldValues) 43 | { 44 | StateField sf = LM.StateManager.FieldLookup[kvp.Key]; 45 | if (sf is StateBool) lsb.SetBool(sf, kvp.Value == 1); 46 | else lsb.SetInt(sf, kvp.Value); 47 | } 48 | return lsb; 49 | } 50 | 51 | public LazyStateBuilder GetStateP(params (string, int)[] vals) => GetState(vals.ToDictionary(p => p.Item1, p => p.Item2)); 52 | 53 | } 54 | 55 | [CollectionDefinition("Logic Collection")] 56 | public class LMCollection : ICollectionFixture 57 | { 58 | 59 | } 60 | } -------------------------------------------------------------------------------- /RandomizerModTests/MiscTests.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic; 2 | using RandomizerMod.RandomizerData; 3 | using RandomizerMod.RC; 4 | using RandomizerMod.Settings; 5 | 6 | namespace RandomizerModTests 7 | { 8 | public class MiscLogicTests 9 | { 10 | 11 | [Fact] 12 | public void NoLocationsUnlockedWithoutState() 13 | { 14 | Data.Load(); 15 | GenerationSettings gs = new(); 16 | gs.StartLocationSettings.StartLocation = ""; 17 | LogicManager lm = RCData.GetNewLogicManager(gs); 18 | RandoModContext ctx = new(lm) 19 | { 20 | GenerationSettings = gs, 21 | }; 22 | ctx.notchCosts.AddRange(CharmNotchCosts._vanillaCosts); 23 | ProgressionManager pm = new(lm, ctx); 24 | 25 | // set all item terms to max value, and zero out all waypoints and transitions. 26 | foreach (Term t in lm.Terms) 27 | { 28 | if (t.Type == TermType.State) pm.SetState(t, null); 29 | else pm.Set(t, int.MaxValue); 30 | } 31 | foreach (LogicWaypoint lw in lm.Waypoints) 32 | { 33 | if (lw.term.Type != TermType.State) pm.Set(lw.term, 0); 34 | } 35 | 36 | HashSet allowList = new() 37 | { 38 | "Start", 39 | "Start_State", 40 | "Nightmare_Lantern_Lit", // Grimmchild 41 | "Opened_Shaman_Pillar" // infection 42 | }; 43 | 44 | Assert.DoesNotContain(lm.LogicLookup.Values, ld => ld.CanGet(pm) && !allowList.Contains(ld.Name)); 45 | } 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /RandomizerModTests/RandomizerModTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472 5 | enable 6 | annotations 7 | 8 | false 9 | true 10 | latest 11 | ..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Hollow Knight\hollow_knight_Data\Managed 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | runtime; build; native; contentfiles; analyzers; buildtransitive 26 | all 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | $(HollowKnightRefs)\Assembly-CSharp.dll 36 | 37 | 38 | $(HollowKnightRefs)\Mods\ItemChanger\ItemChanger.dll 39 | 40 | 41 | $(HollowKnightRefs)\Mods\MenuChanger\MenuChanger.dll 42 | 43 | 44 | $(HollowKnightRefs)\MMHOOK_Assembly-CSharp.dll 45 | 46 | 47 | $(HollowKnightRefs)\MMHOOK_PlayMaker.dll 48 | 49 | 50 | $(HollowKnightRefs)\Newtonsoft.Json.dll 51 | 52 | 53 | $(HollowKnightRefs)\PlayMaker.dll 54 | 55 | 56 | $(HollowKnightRefs)\Mods\RandomizerCore\RandomizerCore.dll 57 | 58 | 59 | $(HollowKnightRefs)\Mods\RandomizerCore.Json\RandomizerCore.Json.dll 60 | 61 | 62 | $(HollowKnightRefs)\UnityEngine.dll 63 | 64 | 65 | $(HollowKnightRefs)\UnityEngine.CoreModule.dll 66 | 67 | 68 | $(HollowKnightRefs)\UnityEngine.ImageConversionModule.dll 69 | 70 | 71 | $(HollowKnightRefs)\UnityEngine.IMGUIModule.dll 72 | 73 | 74 | $(HollowKnightRefs)\UnityEngine.TextRenderingModule.dll 75 | 76 | 77 | $(HollowKnightRefs)\UnityEngine.UI.dll 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /RandomizerModTests/StateVariables/CastSpellVariableTests.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic.StateLogic; 2 | using RandomizerCore.Logic; 3 | 4 | namespace RandomizerModTests.StateVariables 5 | { 6 | [Collection("Logic Collection")] 7 | public class CastSpellVariableTests 8 | { 9 | LogicFixture Fix { get; } 10 | 11 | public CastSpellVariableTests(LogicFixture fix) 12 | { 13 | Fix = fix; 14 | } 15 | 16 | public static Dictionary CharmStateBase => new() { ["NOPASSEDCHARMEQUIP"] = 0 }; 17 | 18 | [Fact] 19 | public void CastSpellSucceedsWith3Casts() 20 | { 21 | StateModifier sm = (StateModifier)Fix.LM.GetVariableStrict("$CASTSPELL[3]"); 22 | ProgressionManager pm = Fix.GetProgressionManager(new()); 23 | LazyStateBuilder lsb = Fix.GetState(new()); 24 | 25 | IEnumerable result = sm.ModifyState(null, pm, lsb); 26 | Assert.NotEmpty(result); 27 | } 28 | 29 | [Fact] 30 | public void CastSpellFailsWith3CastsBeforeOrAfterAShadeSkip() 31 | { 32 | StateModifier sm = (StateModifier)Fix.LM.GetVariableStrict("$SHADESKIP"); 33 | StateModifier sm2 = (StateModifier)Fix.LM.GetVariableStrict("$CASTSPELL[3]"); 34 | ProgressionManager pm = Fix.GetProgressionManager(new()); 35 | LazyStateBuilder lsb = Fix.GetState(new()); 36 | 37 | IEnumerable result = sm.ModifyState(null, pm, new(lsb)).SelectMany(s => sm2.ModifyState(null, pm, s)); 38 | Assert.Empty(result); 39 | result = sm2.ModifyState(null, pm, new(lsb)).SelectMany(s => sm.ModifyState(null, pm, s)); 40 | Assert.Empty(result); 41 | } 42 | 43 | [Fact] 44 | public void CastSpellFailsWith4CastsAndASoulVessel() 45 | { 46 | StateModifier sm = (StateModifier)Fix.LM.GetVariableStrict("$CASTSPELL[4]"); 47 | ProgressionManager pm = Fix.GetProgressionManager(new()); 48 | LazyStateBuilder lsb = Fix.GetState(new()); 49 | 50 | for (int i = 0; i < 3; i++) pm.Add(Fix.LM.GetItemStrict("Vessel_Fragment")); 51 | 52 | IEnumerable result = sm.ModifyState(null, pm, lsb); 53 | Assert.Empty(result); 54 | } 55 | 56 | [Fact] 57 | public void CastSpellSucceedsWith4CastsAndSpellTwister() 58 | { 59 | StateModifier sm = (StateModifier)Fix.LM.GetVariableStrict("$CASTSPELL[4]"); 60 | ProgressionManager pm = Fix.GetProgressionManager(new()); 61 | LazyStateBuilder lsb = Fix.GetState(CharmStateBase); 62 | 63 | pm.Add(Fix.LM.GetItemStrict("Spell_Twister")); 64 | pm.Set("NOTCHES", 6); 65 | 66 | IEnumerable result = sm.ModifyState(null, pm, lsb); 67 | Assert.NotEmpty(result); 68 | } 69 | 70 | [Fact] 71 | public void CastSpellSucceedsWith3to1CastsAndASoulVessel() 72 | { 73 | StateModifier sm = (StateModifier)Fix.LM.GetVariableStrict("$CASTSPELL[3,1]"); 74 | ProgressionManager pm = Fix.GetProgressionManager(new()); 75 | LazyStateBuilder lsb = Fix.GetState(new()); 76 | 77 | for (int i = 0; i < 3; i++) pm.Add(Fix.LM.GetItemStrict("Vessel_Fragment")); 78 | 79 | IEnumerable result = sm.ModifyState(null, pm, lsb); 80 | Assert.NotEmpty(result); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /RandomizerModTests/StateVariables/SlopeballVariableTests.cs: -------------------------------------------------------------------------------- 1 | using RandomizerCore.Logic.StateLogic; 2 | using RandomizerCore.Logic; 3 | 4 | namespace RandomizerModTests.StateVariables 5 | { 6 | [Collection("Logic Collection")] 7 | public class SlopeballVariableTests 8 | { 9 | public LogicFixture Fix { get; } 10 | 11 | public SlopeballVariableTests(LogicFixture fix) 12 | { 13 | Fix = fix; 14 | } 15 | 16 | public static Dictionary SlopeballPMBase => new() { ["FIREBALL"] = 1, ["SLOPEBALLSKIPS"] = 1 }; 17 | 18 | public static object[][] InsufficientSlopeballPMBase => new Dictionary[] { new(), new() { ["FIREBALL"] = 1 }, new(){ ["SLOPEBALLSKIPS"] = 1 } }.Select(d => new object[] {d}).ToArray(); 19 | 20 | public static Dictionary InsufficientSoulState => new() { ["SPENTSOUL"] = 99 }; 21 | 22 | [Theory] 23 | [MemberData(nameof(InsufficientSlopeballPMBase))] 24 | public void CannotCastWithoutProgressionRequirements(Dictionary pmFields) 25 | { 26 | StateModifier sm = (StateModifier)Fix.LM.GetVariableStrict("$SLOPEBALL"); 27 | ProgressionManager pm = Fix.GetProgressionManager(pmFields); 28 | LazyStateBuilder lsb = Fix.GetState(new()); 29 | 30 | IEnumerable result = sm.ModifyState(null, pm, lsb); 31 | Assert.Empty(result); 32 | } 33 | 34 | [Fact] 35 | public void CannotCastWithoutSoul() 36 | { 37 | StateModifier sm = (StateModifier)Fix.LM.GetVariableStrict("$SLOPEBALL"); 38 | ProgressionManager pm = Fix.GetProgressionManager(SlopeballPMBase); 39 | LazyStateBuilder lsb = Fix.GetState(InsufficientSoulState); 40 | 41 | IEnumerable result = sm.ModifyState(null, pm, lsb); 42 | Assert.Empty(result); 43 | } 44 | 45 | [Fact] 46 | public void CanCastOnceWithRequirements() 47 | { 48 | StateModifier sm = (StateModifier)Fix.LM.GetVariableStrict("$SLOPEBALL"); 49 | ProgressionManager pm = Fix.GetProgressionManager(SlopeballPMBase); 50 | LazyStateBuilder lsb = Fix.GetState(new()); 51 | 52 | IEnumerable result = sm.ModifyState(null, pm, lsb); 53 | Assert.NotEmpty(result); 54 | } 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /RandomizerModTests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; --------------------------------------------------------------------------------