From 0036b5d772129b8ff3080c4fc572d33dae8d8ab0 Mon Sep 17 00:00:00 2001 From: MattEqualsCoder Date: Wed, 25 Oct 2023 23:36:13 -0400 Subject: [PATCH] Initial game modes commit --- src/Randomizer.Abstractions/GameModeBase.cs | 61 +++ .../IGameModeService.cs | 30 ++ src/Randomizer.Abstractions/RomPatch.cs | 73 +++ .../ConsoleTrackerDisplayService.cs | 6 +- src/Randomizer.CrossPlatform/Program.cs | 9 +- .../Configuration/ConfigTypes/ItemData.cs | 6 +- .../Generation/GameModeTypeAttribute.cs | 26 + .../Generation}/GeneratedPatch.cs | 0 .../Generation}/GetPatchesRequest.cs | 0 src/Randomizer.Data/Options/Config.cs | 505 +++++++++--------- .../Options/DefaultGameConfig.cs | 5 + src/Randomizer.Data/Options/GameModeConfig.cs | 6 + .../Options/GameModeConfigs.cs | 39 ++ .../Options/KeysanityConfig.cs | 23 + src/Randomizer.Data/Options/PlandoConfig.cs | 8 +- .../Options/RandomizerOptions.cs | 8 +- src/Randomizer.Data/Options/SeedOptions.cs | 4 +- src/Randomizer.Data/WorldData/Location.cs | 2 +- .../WorldData/Regions/IDungeon.cs | 2 +- .../WorldData/Regions/Region.cs | 4 +- .../SuperMetroid/Crateria/CentralCrateria.cs | 4 +- .../SuperMetroid/Crateria/EastCrateria.cs | 4 +- .../Norfair/UpperNorfairCrocomire.cs | 6 +- .../SuperMetroid/Norfair/UpperNorfairEast.cs | 2 +- .../SuperMetroid/Norfair/UpperNorfairWest.cs | 4 +- .../Regions/SuperMetroid/WreckedShip.cs | 2 +- .../WorldData/Regions/Zelda/GanonsTower.cs | 2 +- .../WorldData/Regions/Zelda/HyruleCastle.cs | 4 +- .../Regions/Zelda/PalaceOfDarkness.cs | 18 +- .../WorldData/Regions/Zelda/SwampPalace.cs | 2 +- .../WorldData/Regions/Zelda/TurtleRock.cs | 2 +- .../WorldData/WorldItemPools.cs | 54 +- .../AutoTracking/AutoTracker.cs | 12 +- .../ZeldaStateChecks/EnteredDungeon.cs | 4 +- .../ZeldaStateChecks/ViewedMap.cs | 8 +- .../Services/ItemService.cs | 8 +- .../Services/WorldService.cs | 2 +- src/Randomizer.SMZ3.Tracking/Tracker.cs | 40 +- .../VoiceCommands/MultiplayerModule.cs | 2 +- .../FileData/Patches/DiggingGamePatch.cs | 1 + .../FileData/Patches/DungeonMusicPatch.cs | 3 +- .../FileData/Patches/FairyPondTradePatch.cs | 1 + .../FileData/Patches/FlashRemovalPatch.cs | 8 +- .../FileData/Patches/GoalsPatch.cs | 1 + .../FileData/Patches/HeartColorPatch.cs | 1 + .../Patches/InfiniteSpaceJumpPatch.cs | 1 + .../FileData/Patches/LocationsPatch.cs | 1 + .../FileData/Patches/LowHealthPatch.cs | 1 + .../FileData/Patches/MedallionPatch.cs | 1 + .../FileData/Patches/MenuSpeedPatch.cs | 1 + .../FileData/Patches/MetadataPatch.cs | 6 +- .../FileData/Patches/MetroidAutoSavePatch.cs | 1 + .../FileData/Patches/MetroidControlsPatch.cs | 1 + .../FileData/Patches/MetroidKeysanityPatch.cs | 8 +- .../FileData/Patches/NoBozoSoftlock.cs | 1 + .../FileData/Patches/RomPatch.cs | 74 --- .../FileData/Patches/RomPatchFactory.cs | 2 + .../Patches/SaveAndQuitFromBossRoomPatch.cs | 1 + .../Patches/StartingEquipmentPatch.cs | 1 + .../FileData/Patches/UncleEquipmentPatch.cs | 1 + .../FileData/Patches/ZeldaKeysanityPatch.cs | 7 +- .../FileData/Patches/ZeldaPrizesPatch.cs | 1 + .../FileData/Patches/ZeldaRewardsPatch.cs | 1 + .../FileData/Patches/ZeldaTextsPatch.cs | 1 + .../GameModes/GameModeBaseDefault.cs | 12 + .../GameModes/GameModeBaseKeysanity.cs | 69 +++ .../GameModes/GameModeService.cs | 91 ++++ .../Generation/GameHintService.cs | 4 +- .../Generation/MultiplayerFiller.cs | 2 +- .../Generation/PatcherService.cs | 27 +- .../Generation/RomGenerationService.cs | 2 +- .../Generation/RomTextService.cs | 4 +- .../Generation/Smz3GeneratedRomLoader.cs | 4 +- .../Generation/Smz3Randomizer.cs | 17 +- .../Generation/StandardFiller.cs | 4 +- src/Randomizer.SMZ3/Playthrough.cs | 4 +- src/Randomizer.SMZ3/Randomizer.SMZ3.csproj | 1 + .../RandomizerServiceCollectionExtensions.cs | 18 + src/Randomizer.Shared/Enums/GameModeType.cs | 7 + src/Randomizer.Shared/ManualAttribute.cs | 10 + .../LogicTests/LogicConfigTests.cs | 2 +- .../LogicTests/RandomizerTests.cs | 6 +- 82 files changed, 949 insertions(+), 458 deletions(-) create mode 100644 src/Randomizer.Abstractions/GameModeBase.cs create mode 100644 src/Randomizer.Abstractions/IGameModeService.cs create mode 100644 src/Randomizer.Abstractions/RomPatch.cs create mode 100644 src/Randomizer.Data/Generation/GameModeTypeAttribute.cs rename src/{Randomizer.SMZ3/FileData/Patches => Randomizer.Data/Generation}/GeneratedPatch.cs (100%) rename src/{Randomizer.SMZ3/FileData => Randomizer.Data/Generation}/GetPatchesRequest.cs (100%) create mode 100644 src/Randomizer.Data/Options/DefaultGameConfig.cs create mode 100644 src/Randomizer.Data/Options/GameModeConfig.cs create mode 100644 src/Randomizer.Data/Options/GameModeConfigs.cs create mode 100644 src/Randomizer.Data/Options/KeysanityConfig.cs delete mode 100644 src/Randomizer.SMZ3/FileData/Patches/RomPatch.cs create mode 100644 src/Randomizer.SMZ3/GameModes/GameModeBaseDefault.cs create mode 100644 src/Randomizer.SMZ3/GameModes/GameModeBaseKeysanity.cs create mode 100644 src/Randomizer.SMZ3/GameModes/GameModeService.cs create mode 100644 src/Randomizer.Shared/Enums/GameModeType.cs create mode 100644 src/Randomizer.Shared/ManualAttribute.cs diff --git a/src/Randomizer.Abstractions/GameModeBase.cs b/src/Randomizer.Abstractions/GameModeBase.cs new file mode 100644 index 000000000..816d404ac --- /dev/null +++ b/src/Randomizer.Abstractions/GameModeBase.cs @@ -0,0 +1,61 @@ +using Randomizer.Data.Options; +using Randomizer.Data.Tracking; +using Randomizer.Data.WorldData; +using Randomizer.Data.WorldData.Regions; +using Randomizer.Shared.Enums; + +namespace Randomizer.Abstractions; + +public abstract class GameModeBase +{ + public abstract GameModeType GameModeType { get; } + + public abstract string Name { get; } + + public abstract string Description { get; } + + public virtual void ModifyWorldConfig(Config config) + { + Console.WriteLine($"Modify world config for{Name}"); + } + + public virtual void ModifyWorldItemPools(WorldItemPools itemPool) + { + Console.WriteLine($"Modify item pool for {Name}"); + } + + public virtual void PreWorldGeneration() + { + + } + + public virtual ICollection GetPatches(World world) + { + return new List(); + } + + public virtual void ItemTracked(Item item, Location? location, TrackerBase? tracker) + { + + } + + public virtual void DungeonCleared(IDungeon dungeon, TrackerBase? tracker) + { + + } + + public virtual void BossDefeated(Boss boss, TrackerBase? tracker) + { + + } + + public virtual void ZeldaStateChanged(AutoTrackerZeldaState currentState, AutoTrackerZeldaState? previousState, TrackerBase? tracker) + { + + } + + public virtual void MetroidStateChanged(AutoTrackerMetroidState currentState, AutoTrackerMetroidState? previousState, TrackerBase? tracker) + { + + } +} diff --git a/src/Randomizer.Abstractions/IGameModeService.cs b/src/Randomizer.Abstractions/IGameModeService.cs new file mode 100644 index 000000000..36c6f7cd5 --- /dev/null +++ b/src/Randomizer.Abstractions/IGameModeService.cs @@ -0,0 +1,30 @@ +using Microsoft.Extensions.DependencyInjection; +using Randomizer.Data.Options; +using Randomizer.Data.Tracking; +using Randomizer.Data.WorldData; +using Randomizer.Data.WorldData.Regions; + +namespace Randomizer.Abstractions; + +public interface IGameModeService +{ + public Dictionary GameModeTypes { get; } + + public void SetTracker(TrackerBase tracker, GameModeConfigs gameModeConfigs); + + public void ModifyConfig(Config config); + + public void ModifyWorldItemPools(World world); + + public IEnumerable GetPatches(World world); + + public void ItemTracked(Item item, Location? location); + + public void DungeonCleared(IDungeon dungeon); + + public void BossDefeated(Boss boss); + + public void ZeldaStateChanged(AutoTrackerZeldaState currentState, AutoTrackerZeldaState? previousState); + + public void MetroidStateChanged(AutoTrackerMetroidState currentState, AutoTrackerMetroidState? previousState); +} diff --git a/src/Randomizer.Abstractions/RomPatch.cs b/src/Randomizer.Abstractions/RomPatch.cs new file mode 100644 index 000000000..e53226af5 --- /dev/null +++ b/src/Randomizer.Abstractions/RomPatch.cs @@ -0,0 +1,73 @@ +using System.Text; +using Randomizer.SMZ3.FileData; +using Randomizer.SMZ3.FileData.Patches; + +namespace Randomizer.Abstractions; + +/// +/// Represents one or more changes that can be applied to a generated SMZ3 +/// ROM. +/// +public abstract class RomPatch +{ + /// + /// Returns the PC offset for the specified SNES address. + /// + /// The SNES address to convert. + /// + /// The PC offset equivalent to the SNES . + /// + protected static int Snes(int addr) + { + addr = addr switch + { + /* Redirect hi bank $30 access into ExHiRom lo bank $40 */ + _ when (addr & 0xFF8000) == 0x308000 => 0x400000 | (addr & 0x7FFF), + /* General case, add ExHi offset for banks < $80, and collapse mirroring */ + _ => (addr < 0x800000 ? 0x400000 : 0) | (addr & 0x3FFFFF), + }; + if (addr > 0x600000) + throw new InvalidOperationException($"Unmapped pc address target ${addr:X}"); + return addr; + } + + /// + /// Returns a byte array representing the specified 32-bit unsigned + /// integer. + /// + /// The 32-bit unsigned integer. + /// + /// A new byte array containing the 32-bit unsigned integer. + /// + protected static byte[] UintBytes(int value) => BitConverter.GetBytes((uint)value); + + /// + /// Returns a byte array representing the specified 16-bit unsigned + /// integer. + /// + /// The 16-bit unsigned integer. + /// + /// A new byte array containing the 16-bit unsigned integer. + /// + protected static byte[] UshortBytes(int value) => BitConverter.GetBytes((ushort)value); + + /// + /// Returns a byte array representing the specified ASCII-encoded text. + /// + /// The text. + /// + /// A new byte array containing the ASCII representation of the + /// . + /// + protected static byte[] AsAscii(string text) => Encoding.ASCII.GetBytes(text); + + /// + /// Returns the changes to be applied to an SMZ3 ROM file. + /// + /// Patcher Data with the world and config information + /// + /// A collection of changes, represented by the data to overwrite at the + /// specified ROM offset. + /// + public abstract IEnumerable GetChanges(GetPatchesRequest data); +} diff --git a/src/Randomizer.CrossPlatform/ConsoleTrackerDisplayService.cs b/src/Randomizer.CrossPlatform/ConsoleTrackerDisplayService.cs index 8722a4d13..59d46207c 100644 --- a/src/Randomizer.CrossPlatform/ConsoleTrackerDisplayService.cs +++ b/src/Randomizer.CrossPlatform/ConsoleTrackerDisplayService.cs @@ -8,6 +8,7 @@ using Randomizer.Shared; using Randomizer.Shared.Enums; using Randomizer.Shared.Models; +using Randomizer.SMZ3.GameModes; using Randomizer.SMZ3.Generation; using Randomizer.SMZ3.Tracking; using Randomizer.SMZ3.Tracking.AutoTracking; @@ -22,15 +23,17 @@ public class ConsoleTrackerDisplayService private readonly TrackerOptionsAccessor _trackerOptionsAccessor; private readonly RandomizerOptions _options; private readonly IServiceProvider _serviceProvider; + private readonly IGameModeService _gameModeService; private TrackerBase _trackerBase = null!; private World _world = null!; private IWorldService _worldService = null!; private Region _lastRegion = null!; - public ConsoleTrackerDisplayService(IServiceProvider serviceProvider, Smz3GeneratedRomLoader romLoaderService, TrackerOptionsAccessor trackerOptionsAccessor, OptionsFactory optionsFactory) + public ConsoleTrackerDisplayService(IServiceProvider serviceProvider, Smz3GeneratedRomLoader romLoaderService, TrackerOptionsAccessor trackerOptionsAccessor, OptionsFactory optionsFactory, IGameModeService gameModeService) { _romLoaderService = romLoaderService; _trackerOptionsAccessor = trackerOptionsAccessor; + _gameModeService = gameModeService; _options = optionsFactory.Create(); _serviceProvider = serviceProvider; _timer = new System.Timers.Timer(TimeSpan.FromMilliseconds(250)); @@ -43,6 +46,7 @@ public async Task StartTracking(GeneratedRom rom, string romPath) _world = _romLoaderService.LoadGeneratedRom(rom).First(x => x.IsLocalWorld); _worldService = _serviceProvider.GetRequiredService(); _trackerBase = _serviceProvider.GetRequiredService(); + _gameModeService.SetTracker(_trackerBase, _world.Config.GameModeConfigs); _trackerBase.Load(rom, romPath); _trackerBase.TryStartTracking(); _trackerBase.AutoTracker?.SetConnector(_options.AutoTrackerDefaultConnector, _options.AutoTrackerQUsb2SnesIp); diff --git a/src/Randomizer.CrossPlatform/Program.cs b/src/Randomizer.CrossPlatform/Program.cs index a79aa02db..3671593ab 100644 --- a/src/Randomizer.CrossPlatform/Program.cs +++ b/src/Randomizer.CrossPlatform/Program.cs @@ -66,11 +66,15 @@ public static void Main(string[] args) .Where(x => x.ValidTracks.Count > 10).ToList(); result = DisplayMenu("Which msu do you want to use?", - msus.Select(x => $"{x.Name} ({x.MsuTypeName})").ToList()); - if (result != null) + msus.Select(x => $"{x.Name} ({x.MsuTypeName})").Append("None").ToList()); + if (result != null && result.Value.Item1 < msus.Count) { randomizerOptions.PatchOptions.MsuPaths = new List() { msus[result.Value.Item1].Path }; } + else + { + randomizerOptions.PatchOptions.MsuPaths = new List(); + } } // Generate the rom @@ -81,6 +85,7 @@ public static void Main(string[] args) Console.WriteLine($"Rom generated successfully: {romPath}"); launcher.LaunchRom(romPath, randomizerOptions.GeneralOptions.LaunchApplication, randomizerOptions.GeneralOptions.LaunchArguments); + randomizerOptions.Save(); _ = s_services.GetRequiredService().StartTracking(results.Rom, romPath); } else diff --git a/src/Randomizer.Data/Configuration/ConfigTypes/ItemData.cs b/src/Randomizer.Data/Configuration/ConfigTypes/ItemData.cs index 78e307306..2d1b78afe 100644 --- a/src/Randomizer.Data/Configuration/ConfigTypes/ItemData.cs +++ b/src/Randomizer.Data/Configuration/ConfigTypes/ItemData.cs @@ -272,10 +272,10 @@ public bool IsJunk(Config? config) if (InternalItemType == ItemType.Nothing || InternalItemType.IsInAnyCategory(new[] { ItemCategory.Junk, ItemCategory.Scam, ItemCategory.NonRandomized, ItemCategory.Compass })) return true; - if (config?.ZeldaKeysanity == false && InternalItemType.IsInAnyCategory(new[] { ItemCategory.SmallKey, ItemCategory.BigKey, ItemCategory.Map })) + if (config?.GameModeConfigs.KeysanityConfig.ZeldaKeysanity == false && InternalItemType.IsInAnyCategory(new[] { ItemCategory.SmallKey, ItemCategory.BigKey, ItemCategory.Map })) return true; - if (config?.MetroidKeysanity == false && InternalItemType.IsInCategory(ItemCategory.Keycard)) + if (config?.GameModeConfigs.KeysanityConfig.MetroidKeysanity == false && InternalItemType.IsInCategory(ItemCategory.Keycard)) return true; return false; @@ -297,7 +297,7 @@ public bool IsJunk(Config? config) public bool IsProgression(Config? config) { // Todo: We can add special logic like checking if it's one of the first two swords - return InternalItemType.IsPossibleProgression(config?.ZeldaKeysanity == true, config?.MetroidKeysanity == true); + return InternalItemType.IsPossibleProgression(config?.GameModeConfigs.KeysanityConfig.ZeldaKeysanity == true, config?.GameModeConfigs.KeysanityConfig.MetroidKeysanity == true); } /// diff --git a/src/Randomizer.Data/Generation/GameModeTypeAttribute.cs b/src/Randomizer.Data/Generation/GameModeTypeAttribute.cs new file mode 100644 index 000000000..2970abae5 --- /dev/null +++ b/src/Randomizer.Data/Generation/GameModeTypeAttribute.cs @@ -0,0 +1,26 @@ +using System; +using Randomizer.Shared.Enums; + +namespace Randomizer.Shared; + +[AttributeUsage(AttributeTargets.Property, + Inherited = false, AllowMultiple = false)] +public class GameModeTypeAttribute : Attribute +{ + /// + /// Initializes a new instance of the class with the specified game mode type. + /// + /// + /// The game mode type applicable to the property + /// + public GameModeTypeAttribute(GameModeType gameModeType) + { + GameModeType = gameModeType; + } + + /// + /// The game mode of property + /// + public GameModeType GameModeType { get; } +} diff --git a/src/Randomizer.SMZ3/FileData/Patches/GeneratedPatch.cs b/src/Randomizer.Data/Generation/GeneratedPatch.cs similarity index 100% rename from src/Randomizer.SMZ3/FileData/Patches/GeneratedPatch.cs rename to src/Randomizer.Data/Generation/GeneratedPatch.cs diff --git a/src/Randomizer.SMZ3/FileData/GetPatchesRequest.cs b/src/Randomizer.Data/Generation/GetPatchesRequest.cs similarity index 100% rename from src/Randomizer.SMZ3/FileData/GetPatchesRequest.cs rename to src/Randomizer.Data/Generation/GetPatchesRequest.cs diff --git a/src/Randomizer.Data/Options/Config.cs b/src/Randomizer.Data/Options/Config.cs index 39cc51fdc..08de60f2d 100644 --- a/src/Randomizer.Data/Options/Config.cs +++ b/src/Randomizer.Data/Options/Config.cs @@ -15,308 +15,303 @@ using Randomizer.Shared.Multiplayer; using JsonSerializer = System.Text.Json.JsonSerializer; -namespace Randomizer.Data.Options +namespace Randomizer.Data.Options; + +[DefaultValue(Normal)] +public enum GameMode { - [DefaultValue(Normal)] - public enum GameMode - { - [Description("Single player")] - Normal, + [Description("Single player")] + Normal, - [Description("Multiworld")] - Multiworld, - } + [Description("Multiworld")] + Multiworld, +} - [DefaultValue(Normal)] - public enum Z3Logic - { - [Description("Normal")] - Normal, +[DefaultValue(Normal)] +public enum Z3Logic +{ + [Description("Normal")] + Normal, - [Description("No major glitches")] - Nmg, + [Description("No major glitches")] + Nmg, - [Description("Overworld glitches")] - Owg, - } + [Description("Overworld glitches")] + Owg, +} - [DefaultValue(Normal)] - public enum SMLogic - { - [Description("Normal")] - Normal, +[DefaultValue(Normal)] +public enum SMLogic +{ + [Description("Normal")] + Normal, - [Description("Hard")] - Hard, - } + [Description("Hard")] + Hard, +} - [DefaultValue(Randomized)] - public enum ItemPlacement - { - [Description("Randomized")] - Randomized, +[DefaultValue(Randomized)] +public enum ItemPlacement +{ + [Description("Randomized")] + Randomized, - [Description("Early")] - Early, + [Description("Early")] + Early, - [Description("Original location")] - Original, - } + [Description("Original location")] + Original, +} - [DefaultValue(Any)] - public enum ItemPool - { - [Description("Any")] - Any = 0, +[DefaultValue(Any)] +public enum ItemPool +{ + [Description("Any")] + Any = 0, - [Description("Progression items")] - Progression, + [Description("Progression items")] + Progression, - /*[Description("Non-junk items")] - NonJunk,*/ + /*[Description("Non-junk items")] + NonJunk,*/ - [Description("Junk items")] - Junk, - } + [Description("Junk items")] + Junk, +} - [DefaultValue(DefeatBoth)] - public enum Goal - { - [Description("Defeat Ganon and Mother Brain")] - DefeatBoth, - } +[DefaultValue(DefeatBoth)] +public enum Goal +{ + [Description("Defeat Ganon and Mother Brain")] + DefeatBoth, +} - public enum GanonInvincible - { - [Description("Never")] - Never, +public enum GanonInvincible +{ + [Description("Never")] + Never, - [Description("Before Crystals")] - BeforeCrystals, + [Description("Before Crystals")] + BeforeCrystals, - [Description("Before All Dungeons")] - BeforeAllDungeons, + [Description("Before All Dungeons")] + BeforeAllDungeons, - [Description("Always")] - Always, - } + [Description("Always")] + Always, +} +/// +/// Specifies how dungeon music in A Link to the Past is selected. +/// +public enum MusicShuffleMode +{ /// - /// Specifies how dungeon music in A Link to the Past is selected. + /// Specifies music should not be shuffled. /// - public enum MusicShuffleMode - { - /// - /// Specifies music should not be shuffled. - /// - /// - /// Dungeons play the light/dark world dungeon theme depending on the - /// type of crystal. In extended mode, dungeons have their own theme. - /// - [Description("Dungeons play the normal dungeon themes")] - Default, - - /// - /// Specifies music should be shuffled between dungeons. - /// - /// - /// Dungeons play the light/dark world theme randomly. In extended mode, - /// all dungeon themes are shuffled. - /// - [Description("Dungeons can play any dungeon theme")] - ShuffleDungeons, - - /// - /// Specifies dungeon music can be replaced by any track in the game. - /// - /// - /// Dungeons can play any track from the game soundtrack. In extended - /// mode, all extended soundtracks are included. - /// - [Description("Dungeons can play any track")] - ShuffleAll - } + /// + /// Dungeons play the light/dark world dungeon theme depending on the + /// type of crystal. In extended mode, dungeons have their own theme. + /// + [Description("Dungeons play the normal dungeon themes")] + Default, - public enum HeartColor - { - Red = 0, - Yellow, - Green, - Blue - } + /// + /// Specifies music should be shuffled between dungeons. + /// + /// + /// Dungeons play the light/dark world theme randomly. In extended mode, + /// all dungeon themes are shuffled. + /// + [Description("Dungeons can play any dungeon theme")] + ShuffleDungeons, - public enum LowHealthBeepSpeed - { - Normal = 0, - Double, - Half, - Quarter, - Off, - } + /// + /// Specifies dungeon music can be replaced by any track in the game. + /// + /// + /// Dungeons can play any track from the game soundtrack. In extended + /// mode, all extended soundtracks are included. + /// + [Description("Dungeons can play any track")] + ShuffleAll +} - public enum MenuSpeed +public enum HeartColor +{ + Red = 0, + Yellow, + Green, + Blue +} + +public enum LowHealthBeepSpeed +{ + Normal = 0, + Double, + Half, + Quarter, + Off, +} + +public enum MenuSpeed +{ + Default = 0, + Fast, + Instant, + Slow +} + +public class Config +{ + private static readonly JsonSerializerOptions s_options = new() + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + + public GameMode GameMode { get; set; } = GameMode.Normal; + public bool Race { get; set; } = false; + public bool DisableSpoilerLog { get; set; } = false; + public bool DisableTrackerSpoilers { get; set; } = false; + public bool DisableTrackerHints { get; set; } = false; + public bool DisableCheats { get; set; } = false; + public GanonInvincible GanonInvincible { get; set; } = GanonInvincible.BeforeCrystals; + public bool ExtendedMsuSupport { get; set; } = false; + public MusicShuffleMode ShuffleDungeonMusic { get; set; } = MusicShuffleMode.Default; + public HeartColor HeartColor { get; set; } = HeartColor.Red; + public LowHealthBeepSpeed LowHealthBeepSpeed { get; set; } = LowHealthBeepSpeed.Normal; + public bool DisableLowEnergyBeep { get; set; } = false; + public MenuSpeed MenuSpeed { get; set; } = MenuSpeed.Default; + public bool CasualSMPatches { get; set; } = false; + public ZeldaDrops ZeldaDrops { get; set; } = ZeldaDrops.Randomized; + public bool GenerateSeedOnly { get; private set; } = false; + + public string LinkName { get; set; } = "Link"; + + public string SamusName { get; set; } = "Samus"; + + public bool SingleWorld => GameMode == GameMode.Normal; + public bool MultiWorld => GameMode == GameMode.Multiworld; + public int Id { get; set; } + public string PlayerName { get; set; } = ""; + public string PhoneticName { get; set; } = ""; + public string PlayerGuid { get; set; } = ""; + public string Seed { get; set; } = ""; + public string SettingsString { get; set; } = ""; + public bool CopySeedAndRaceSettings { get; set; } + public IDictionary LocationItems { get; set; } = new Dictionary(); + public LogicConfig LogicConfig { get; set; } = new LogicConfig(); + public CasPatches CasPatches { get; set; } = new(); + public MetroidControlOptions MetroidControls { get; set; } = new(); + public PlandoConfig? PlandoConfig { get; set; } + public GameModeConfigs GameModeConfigs { get; set; } = new(); + public MultiplayerPlayerGenerationData? MultiplayerPlayerGenerationData { get; set; } + public ItemPlacementRule ItemPlacementRule { get; set; } + public int UniqueHintCount { get; set; } = 8; + public int GanonsTowerCrystalCount { get; set; } = 7; + public int GanonCrystalCount { get; set; } = 7; + public bool OpenPyramid { get; set; } + public int TourianBossCount { get; set; } = 4; + public IDictionary ItemOptions { get; set; } = new Dictionary(); + + [System.Text.Json.Serialization.JsonIgnore, JsonIgnore] + public bool IsLocalConfig { get; set; } = true; + + public Config SeedOnly() { - Default = 0, - Fast, - Instant, - Slow + var clone = (Config)MemberwiseClone(); + clone.GenerateSeedOnly = true; + return clone; } - public class Config + /// + /// Converts the config into a compressed string of the json + /// + /// The config to convert + /// If the config should be compressed + /// The string representation + public static string ToConfigString(Config config, bool compress) { - private static readonly JsonSerializerOptions s_options = new() + var json = JsonSerializer.Serialize(config, s_options); + if (!compress) return json; + var buffer = Encoding.UTF8.GetBytes(json); + var memoryStream = new MemoryStream(); + using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress, true)) { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping - }; - - public GameMode GameMode { get; set; } = GameMode.Normal; - public KeysanityMode KeysanityMode { get; set; } = KeysanityMode.None; - public bool Race { get; set; } = false; - public bool DisableSpoilerLog { get; set; } = false; - public bool DisableTrackerSpoilers { get; set; } = false; - public bool DisableTrackerHints { get; set; } = false; - public bool DisableCheats { get; set; } = false; - public GanonInvincible GanonInvincible { get; set; } = GanonInvincible.BeforeCrystals; - public bool ExtendedMsuSupport { get; set; } = false; - public MusicShuffleMode ShuffleDungeonMusic { get; set; } = MusicShuffleMode.Default; - public HeartColor HeartColor { get; set; } = HeartColor.Red; - public LowHealthBeepSpeed LowHealthBeepSpeed { get; set; } = LowHealthBeepSpeed.Normal; - public bool DisableLowEnergyBeep { get; set; } = false; - public MenuSpeed MenuSpeed { get; set; } = MenuSpeed.Default; - public bool CasualSMPatches { get; set; } = false; - public ZeldaDrops ZeldaDrops { get; set; } = ZeldaDrops.Randomized; - public bool GenerateSeedOnly { get; private set; } = false; - - public string LinkName { get; set; } = "Link"; - - public string SamusName { get; set; } = "Samus"; - - public bool SingleWorld => GameMode == GameMode.Normal; - public bool MultiWorld => GameMode == GameMode.Multiworld; - public bool Keysanity => KeysanityMode != KeysanityMode.None; - public int Id { get; set; } - public string PlayerName { get; set; } = ""; - public string PhoneticName { get; set; } = ""; - public string PlayerGuid { get; set; } = ""; - public string Seed { get; set; } = ""; - public string SettingsString { get; set; } = ""; - public bool CopySeedAndRaceSettings { get; set; } - public IDictionary LocationItems { get; set; } = new Dictionary(); - public LogicConfig LogicConfig { get; set; } = new LogicConfig(); - public CasPatches CasPatches { get; set; } = new(); - public MetroidControlOptions MetroidControls { get; set; } = new(); - public PlandoConfig? PlandoConfig { get; set; } - public MultiplayerPlayerGenerationData? MultiplayerPlayerGenerationData { get; set; } - public ItemPlacementRule ItemPlacementRule { get; set; } - public int UniqueHintCount { get; set; } = 8; - public bool ZeldaKeysanity => KeysanityMode == KeysanityMode.Both || KeysanityMode == KeysanityMode.Zelda; - public bool MetroidKeysanity => KeysanityMode == KeysanityMode.Both || KeysanityMode == KeysanityMode.SuperMetroid; - public bool KeysanityForRegion(Region region) => KeysanityMode == KeysanityMode.Both || (region is Z3Region && ZeldaKeysanity) || (region is SMRegion && MetroidKeysanity); - public int GanonsTowerCrystalCount { get; set; } = 7; - public int GanonCrystalCount { get; set; } = 7; - public bool OpenPyramid { get; set; } - public int TourianBossCount { get; set; } = 4; - public IDictionary ItemOptions { get; set; } = new Dictionary(); - - [System.Text.Json.Serialization.JsonIgnore, JsonIgnore] - public bool IsLocalConfig { get; set; } = true; - - public Config SeedOnly() - { - var clone = (Config)MemberwiseClone(); - clone.GenerateSeedOnly = true; - return clone; + gZipStream.Write(buffer, 0, buffer.Length); } - /// - /// Converts the config into a compressed string of the json - /// - /// The config to convert - /// If the config should be compressed - /// The string representation - public static string ToConfigString(Config config, bool compress) - { - var json = JsonSerializer.Serialize(config, s_options); - if (!compress) return json; - var buffer = Encoding.UTF8.GetBytes(json); - var memoryStream = new MemoryStream(); - using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress, true)) - { - gZipStream.Write(buffer, 0, buffer.Length); - } + memoryStream.Position = 0; - memoryStream.Position = 0; + var compressedData = new byte[memoryStream.Length]; + memoryStream.Read(compressedData, 0, compressedData.Length); - var compressedData = new byte[memoryStream.Length]; - memoryStream.Read(compressedData, 0, compressedData.Length); + var gZipBuffer = new byte[compressedData.Length + 4]; + Buffer.BlockCopy(compressedData, 0, gZipBuffer, 4, compressedData.Length); + Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gZipBuffer, 0, 4); + return Convert.ToBase64String(gZipBuffer); + } + + /// + /// Takes in a compressed json config string and converts it into a config + /// + /// + /// The converted json data + public static IEnumerable FromConfigString(string configString) + { + if (configString.Contains("{")) + return new List() { JsonSerializer.Deserialize(configString, s_options) ?? new Config() }; - var gZipBuffer = new byte[compressedData.Length + 4]; - Buffer.BlockCopy(compressedData, 0, gZipBuffer, 4, compressedData.Length); - Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gZipBuffer, 0, 4); - return Convert.ToBase64String(gZipBuffer); + if (configString.StartsWith("[")) + { + var configs = new List(); + var configStrings = JsonSerializer.Deserialize>(configString, s_options) ?? new List(); + return configStrings.SelectMany(x => FromConfigString(x)); } - /// - /// Takes in a compressed json config string and converts it into a config - /// - /// - /// The converted json data - public static IEnumerable FromConfigString(string configString) + var gZipBuffer = Convert.FromBase64String(configString); + using (var memoryStream = new MemoryStream()) { - if (configString.Contains("{")) - return new List() { JsonSerializer.Deserialize(configString, s_options) ?? new Config() }; + var dataLength = BitConverter.ToInt32(gZipBuffer, 0); + memoryStream.Write(gZipBuffer, 4, gZipBuffer.Length - 4); - if (configString.StartsWith("[")) - { - var configs = new List(); - var configStrings = JsonSerializer.Deserialize>(configString, s_options) ?? new List(); - return configStrings.SelectMany(x => FromConfigString(x)); - } + var buffer = new byte[dataLength]; - var gZipBuffer = Convert.FromBase64String(configString); - using (var memoryStream = new MemoryStream()) + memoryStream.Position = 0; + using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Decompress)) { - var dataLength = BitConverter.ToInt32(gZipBuffer, 0); - memoryStream.Write(gZipBuffer, 4, gZipBuffer.Length - 4); - - var buffer = new byte[dataLength]; - - memoryStream.Position = 0; - using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Decompress)) - { - gZipStream.Read(buffer, 0, buffer.Length); - } - - var json = Encoding.UTF8.GetString(buffer); - return new List() { JsonSerializer.Deserialize(json, s_options) ?? new Config() }; + gZipStream.Read(buffer, 0, buffer.Length); } - } - /// - /// Takes a series of config files and generates them into a combined json array of - /// their compressed config strings - /// - /// - /// - public static string ToConfigString(IEnumerable configs) - { - var configStrings = new List(); - foreach (var config in configs) - { - configStrings.Add(ToConfigString(config, true)); - } - return JsonSerializer.Serialize(configStrings, s_options); + var json = Encoding.UTF8.GetString(buffer); + return new List() { JsonSerializer.Deserialize(json, s_options) ?? new Config() }; } + } - /// - /// Takes a config file and generates it into a compressed config string - /// - /// - /// - public static string ToConfigString(Config config) + /// + /// Takes a series of config files and generates them into a combined json array of + /// their compressed config strings + /// + /// + /// + public static string ToConfigString(IEnumerable configs) + { + var configStrings = new List(); + foreach (var config in configs) { - return ToConfigString(new[] { config } ); + configStrings.Add(ToConfigString(config, true)); } + return JsonSerializer.Serialize(configStrings, s_options); + } + + /// + /// Takes a config file and generates it into a compressed config string + /// + /// + /// + public static string ToConfigString(Config config) + { + return ToConfigString(new[] { config } ); } } diff --git a/src/Randomizer.Data/Options/DefaultGameConfig.cs b/src/Randomizer.Data/Options/DefaultGameConfig.cs new file mode 100644 index 000000000..39a4ffabf --- /dev/null +++ b/src/Randomizer.Data/Options/DefaultGameConfig.cs @@ -0,0 +1,5 @@ +namespace Randomizer.Data.Options; + +public class DefaultGameConfig : GameModeConfig +{ +} diff --git a/src/Randomizer.Data/Options/GameModeConfig.cs b/src/Randomizer.Data/Options/GameModeConfig.cs new file mode 100644 index 000000000..8dd6d8a17 --- /dev/null +++ b/src/Randomizer.Data/Options/GameModeConfig.cs @@ -0,0 +1,6 @@ +namespace Randomizer.Data.Options; + +public class GameModeConfig +{ + public bool Enabled { get; set; } +} diff --git a/src/Randomizer.Data/Options/GameModeConfigs.cs b/src/Randomizer.Data/Options/GameModeConfigs.cs new file mode 100644 index 000000000..f1a7995bc --- /dev/null +++ b/src/Randomizer.Data/Options/GameModeConfigs.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Randomizer.Shared; +using Randomizer.Shared.Enums; + +namespace Randomizer.Data.Options; + +public class GameModeConfigs +{ + [GameModeType(GameModeType.Default)] + public DefaultGameConfig DefaultGameConfig { get; set; } = new() + { + Enabled = true; + } + + [GameModeType(GameModeType.Keysanity)] + public KeysanityConfig KeysanityConfig { get; set; } = new(); + + public ICollection GetEnabledGameModes() + { + var toReturn = new List(); + + var properties = GetType().GetProperties().Where(x => + x.PropertyType.IsSubclassOf(typeof(GameModeConfig)) && + x.GetCustomAttribute() != null); + + foreach (var property in properties) + { + var config = property.GetValue(this) as GameModeConfig; + if (config?.Enabled == true) + { + toReturn.Add(property.GetCustomAttribute()!.GameModeType); + } + } + + return toReturn; + } +} diff --git a/src/Randomizer.Data/Options/KeysanityConfig.cs b/src/Randomizer.Data/Options/KeysanityConfig.cs new file mode 100644 index 000000000..f8873fb20 --- /dev/null +++ b/src/Randomizer.Data/Options/KeysanityConfig.cs @@ -0,0 +1,23 @@ +using Randomizer.Data.WorldData.Regions; +using Randomizer.Shared.Enums; +using YamlDotNet.Serialization; + +namespace Randomizer.Data.Options; + +public class KeysanityConfig : GameModeConfig +{ + public KeysanityMode KeysanityMode { get; set; } + + [YamlIgnore] + public bool KeysanityEnabled => Enabled && KeysanityMode != KeysanityMode.None; + + [YamlIgnore] + public bool ZeldaKeysanity => Enabled && KeysanityMode is KeysanityMode.Zelda or KeysanityMode.Both; + + [YamlIgnore] + public bool MetroidKeysanity => Enabled && KeysanityMode is KeysanityMode.SuperMetroid or KeysanityMode.Both; + + public bool KeysanityForRegion(Region region) => KeysanityMode == KeysanityMode.Both || + (region is Z3Region && ZeldaKeysanity) || + (region is SMRegion && MetroidKeysanity); +} diff --git a/src/Randomizer.Data/Options/PlandoConfig.cs b/src/Randomizer.Data/Options/PlandoConfig.cs index b23a23978..e0b787414 100644 --- a/src/Randomizer.Data/Options/PlandoConfig.cs +++ b/src/Randomizer.Data/Options/PlandoConfig.cs @@ -7,7 +7,6 @@ using YamlDotNet.Serialization; using Randomizer.Data.Logic; -using Randomizer.Shared.Enums; namespace Randomizer.Data.Options { @@ -31,7 +30,7 @@ public PlandoConfig() /// The world instance to export to a config. public PlandoConfig(World world) { - KeysanityMode = world.Config.KeysanityMode; + GameModeConfigs = world.Config.GameModeConfigs; GanonsTowerCrystalCount = world.Config.GanonCrystalCount; GanonCrystalCount = world.Config.GanonCrystalCount; OpenPyramid = world.Config.OpenPyramid; @@ -61,9 +60,10 @@ public PlandoConfig(World world) public string FileName { get; set; } = ""; /// - /// Gets or sets a value indicating whether Keysanity should be enabled. + /// Gets or sets the properties for the game modes /// - public KeysanityMode KeysanityMode { get; set; } + + public GameModeConfigs GameModeConfigs { get; set; } = new(); /// /// Gets or sets the number of crystals for entering GT diff --git a/src/Randomizer.Data/Options/RandomizerOptions.cs b/src/Randomizer.Data/Options/RandomizerOptions.cs index 018b199e0..d6ea6c01c 100644 --- a/src/Randomizer.Data/Options/RandomizerOptions.cs +++ b/src/Randomizer.Data/Options/RandomizerOptions.cs @@ -151,7 +151,6 @@ public Config ToConfig() var config = new Config() { GameMode = GameMode.Normal, - KeysanityMode = SeedOptions.KeysanityMode, Race = SeedOptions.Race, ItemPlacementRule = SeedOptions.ItemPlacementRule, DisableSpoilerLog = SeedOptions.DisableSpoilerLog, @@ -177,7 +176,8 @@ public Config ToConfig() TourianBossCount = SeedOptions.TourianBossCount, MetroidControls = PatchOptions.MetroidControls.Clone(), ItemOptions = SeedOptions.ItemOptions, - ZeldaDrops = PatchOptions.ZeldaDrops + ZeldaDrops = PatchOptions.ZeldaDrops, + GameModeConfigs = SeedOptions.GameModeConfigs, }; return config; } @@ -209,7 +209,6 @@ public Config ToConfig() return new Config() { GameMode = GameMode.Normal, - KeysanityMode = oldConfig.KeysanityMode, ItemPlacementRule = oldConfig.ItemPlacementRule, Race = race, DisableSpoilerLog = disableSpoilerLog, @@ -236,7 +235,8 @@ public Config ToConfig() TourianBossCount = oldConfig.TourianBossCount, MetroidControls = PatchOptions.MetroidControls.Clone(), ItemOptions = oldConfig.ItemOptions, - ZeldaDrops = zeldaDrops + ZeldaDrops = zeldaDrops, + GameModeConfigs = oldConfig.GameModeConfigs }; } } diff --git a/src/Randomizer.Data/Options/SeedOptions.cs b/src/Randomizer.Data/Options/SeedOptions.cs index 8159427f3..9fef7af89 100644 --- a/src/Randomizer.Data/Options/SeedOptions.cs +++ b/src/Randomizer.Data/Options/SeedOptions.cs @@ -23,8 +23,6 @@ public class SeedOptions [JsonIgnore] public string ConfigString { get; set; } = ""; - public KeysanityMode KeysanityMode { get; set; } = KeysanityMode.None; - public ItemPlacementRule ItemPlacementRule { get; set; } = ItemPlacementRule.Anywhere; public bool Race { get; set; } @@ -44,6 +42,8 @@ public class SeedOptions public bool OpenPyramid { get; set; } = false; public int TourianBossCount { get; set; } = 4; + public GameModeConfigs GameModeConfigs { get; set; } = new(); + [JsonIgnore, YamlIgnore] public int MaxHints => 15; diff --git a/src/Randomizer.Data/WorldData/Location.cs b/src/Randomizer.Data/WorldData/Location.cs index d2cd9109a..f5709c707 100644 --- a/src/Randomizer.Data/WorldData/Location.cs +++ b/src/Randomizer.Data/WorldData/Location.cs @@ -265,7 +265,7 @@ public bool CanFill(Item item, Progression items) { var oldItem = Item; Item = item; - var isCustomPlacementAndSphereOne = (item.Progression || item.IsDungeonItem && item.World.Config.ZeldaKeysanity || item.IsKeycard && item.World.Config.MetroidKeysanity) + var isCustomPlacementAndSphereOne = (item.Progression || item.IsDungeonItem && item.World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity || item.IsKeycard && item.World.Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity) && Region.Config.ItemPlacementRule != ItemPlacementRule.Anywhere && _weight <= -10; var fillable = _alwaysAllow(item, items) diff --git a/src/Randomizer.Data/WorldData/Regions/IDungeon.cs b/src/Randomizer.Data/WorldData/Regions/IDungeon.cs index 26ce3bc00..dfcbb6271 100644 --- a/src/Randomizer.Data/WorldData/Regions/IDungeon.cs +++ b/src/Randomizer.Data/WorldData/Regions/IDungeon.cs @@ -29,7 +29,7 @@ public interface IDungeon public int GetTreasureCount() { var region = (Region)this; - return region.Locations.Count(x => x.Item.Type != ItemType.Nothing && (!x.Item.IsDungeonItem || region.World.Config.ZeldaKeysanity) && x.Type != LocationType.NotInDungeon); + return region.Locations.Count(x => x.Item.Type != ItemType.Nothing && (!x.Item.IsDungeonItem || region.World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity) && x.Type != LocationType.NotInDungeon); } /// diff --git a/src/Randomizer.Data/WorldData/Regions/Region.cs b/src/Randomizer.Data/WorldData/Regions/Region.cs index 0bf203532..2b318cb3a 100644 --- a/src/Randomizer.Data/WorldData/Regions/Region.cs +++ b/src/Randomizer.Data/WorldData/Regions/Region.cs @@ -112,7 +112,7 @@ public bool IsRegionItem(Item item) /// public virtual bool CanFill(Item item, Progression items) { - return (item.World.Config.ZeldaKeysanity || !item.IsDungeonItem || IsRegionItem(item)) && MatchesItemPlacementRule(item); + return (item.World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity || !item.IsDungeonItem || IsRegionItem(item)) && MatchesItemPlacementRule(item); } private bool MatchesItemPlacementRule(Item item) @@ -121,7 +121,7 @@ private bool MatchesItemPlacementRule(Item item) var rule = Config.ItemPlacementRule; if (rule == ItemPlacementRule.Anywhere || (!item.Progression && !item.IsKey && !item.IsKeycard && !item.IsBigKey) - || (!item.World.Config.ZeldaKeysanity && (item.IsKey || item.IsBigKey))) return true; + || (!item.World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity && (item.IsKey || item.IsBigKey))) return true; else if (rule == ItemPlacementRule.DungeonsAndMetroid) { return this is Z3Region { IsOverworld: false } || this is SMRegion; diff --git a/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Crateria/CentralCrateria.cs b/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Crateria/CentralCrateria.cs index f45b6eed2..c2ea908b9 100644 --- a/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Crateria/CentralCrateria.cs +++ b/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Crateria/CentralCrateria.cs @@ -47,7 +47,7 @@ public PowerBombRoom(CentralCrateria region, IMetadataService? metadata, Tracker new Location(this, LocationId.CrateriaPowerBomb, 0x8F81CC, LocationType.Visible, name: "Power Bomb (Crateria surface)", vanillaItem: ItemType.PowerBomb, - access: items => (Config.MetroidKeysanity ? items.CardCrateriaL1 : Logic.CanUsePowerBombs(items)) && (items.SpeedBooster || Logic.CanFly(items)), + access: items => (Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity ? items.CardCrateriaL1 : Logic.CanUsePowerBombs(items)) && (items.SpeedBooster || Logic.CanFly(items)), memoryAddress: 0x0, memoryFlag: 0x1, metadata: metadata, @@ -123,7 +123,7 @@ public BombTorizoRoom(CentralCrateria region, IMetadataService? metadata, Tracke new Location(this, LocationId.CrateriaBombTorizo, 0x8F8404, LocationType.Chozo, name: "Bombs", vanillaItem: ItemType.Bombs, - access: items => (Config.MetroidKeysanity ? items.CardCrateriaBoss : Logic.CanOpenRedDoors(items)) + access: items => (Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity ? items.CardCrateriaBoss : Logic.CanOpenRedDoors(items)) && (Logic.CanPassBombPassages(items) || Logic.CanWallJump(WallJumpDifficulty.Hard)), memoryAddress: 0x0, memoryFlag: 0x80, diff --git a/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Crateria/EastCrateria.cs b/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Crateria/EastCrateria.cs index a4337522b..4f490c85d 100644 --- a/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Crateria/EastCrateria.cs +++ b/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Crateria/EastCrateria.cs @@ -29,9 +29,9 @@ public override bool CanEnter(Progression items, bool requireRewards) { return /* Ship -> Moat */ - ((Config.MetroidKeysanity ? items.CardCrateriaL2 : Logic.CanUsePowerBombs(items)) && items.Super) || + ((Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity ? items.CardCrateriaL2 : Logic.CanUsePowerBombs(items)) && items.Super) || /* UN Portal -> Red Tower -> Moat */ - ((Config.MetroidKeysanity ? items.CardCrateriaL2 : Logic.CanUsePowerBombs(items)) && Logic.CanAccessNorfairUpperPortal(items) && + ((Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity ? items.CardCrateriaL2 : Logic.CanUsePowerBombs(items)) && Logic.CanAccessNorfairUpperPortal(items) && (items.Ice || items.HiJump || items.SpaceJump)) || /*Through Maridia From Portal*/ (Logic.CanAccessMaridiaPortal(items, requireRewards) && items.Gravity && items.Super && ( diff --git a/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Norfair/UpperNorfairCrocomire.cs b/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Norfair/UpperNorfairCrocomire.cs index e004b1b60..5bb855bc8 100644 --- a/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Norfair/UpperNorfairCrocomire.cs +++ b/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Norfair/UpperNorfairCrocomire.cs @@ -44,11 +44,11 @@ public override bool CanEnter(Progression items, bool requireRewards) ) && items.Varia && ( /* Ice Beam -> Croc Speedway */ - ((Config.MetroidKeysanity ? items.CardNorfairL1 : items.Super) && Logic.CanUsePowerBombs(items) && items.SpeedBooster) || + ((Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity ? items.CardNorfairL1 : items.Super) && Logic.CanUsePowerBombs(items) && items.SpeedBooster) || /* Frog Speedway */ (items.SpeedBooster && items.Wave) || /* Cathedral -> through the floor or Vulcano */ - (Logic.CanOpenRedDoors(items) && (Config.MetroidKeysanity ? items.CardNorfairL2 : items.Super) && + (Logic.CanOpenRedDoors(items) && (Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity ? items.CardNorfairL2 : items.Super) && (Logic.CanFly(items) || items.HiJump || items.SpeedBooster) && (Logic.CanPassBombPassages(items) || (items.Gravity && items.Morph)) && items.Wave) || @@ -60,7 +60,7 @@ public override bool CanEnter(Progression items, bool requireRewards) private bool CanAccessCrocomire(Progression items) { - return (Config.MetroidKeysanity ? items.CardNorfairBoss : items.Super) && (items.HiJump || items.SpeedBooster || Logic.CanFly(items) || Logic.CanWallJump(WallJumpDifficulty.Easy)); + return (Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity ? items.CardNorfairBoss : items.Super) && (items.HiJump || items.SpeedBooster || Logic.CanFly(items) || Logic.CanWallJump(WallJumpDifficulty.Easy)); } public class CrocomiresRoom : Room diff --git a/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Norfair/UpperNorfairEast.cs b/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Norfair/UpperNorfairEast.cs index 1e10ca9cf..430f8ece2 100644 --- a/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Norfair/UpperNorfairEast.cs +++ b/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Norfair/UpperNorfairEast.cs @@ -49,7 +49,7 @@ public override bool CanEnter(Progression items, bool requireRewards) Logic.CanAccessNorfairUpperPortal(items) ) && items.Varia && items.Super && ( /* Cathedral */ - (Logic.CanOpenRedDoors(items) && (Config.MetroidKeysanity ? items.CardNorfairL2 : items.Super) && + (Logic.CanOpenRedDoors(items) && (Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity ? items.CardNorfairL2 : items.Super) && (Logic.CanFly(items) || items.HiJump || items.SpeedBooster)) || /* Frog Speedway */ (items.SpeedBooster && (items.CardNorfairL2 || items.Wave) && Logic.CanUsePowerBombs(items)) diff --git a/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Norfair/UpperNorfairWest.cs b/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Norfair/UpperNorfairWest.cs index 751b4bb35..c15c4529d 100644 --- a/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Norfair/UpperNorfairWest.cs +++ b/src/Randomizer.Data/WorldData/Regions/SuperMetroid/Norfair/UpperNorfairWest.cs @@ -71,7 +71,7 @@ public IceBeamRoom(UpperNorfairWest region, IMetadataService? metadata, TrackerS new Location(this, LocationId.UpperNorfairIceBeam, 0x8F8B24, LocationType.Chozo, name: "Ice Beam", vanillaItem: ItemType.Ice, - access: items => (Config.MetroidKeysanity ? items.CardNorfairL1 : items.Super) + access: items => (Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity ? items.CardNorfairL1 : items.Super) && Logic.CanPassBombPassages(items) && items.Varia && Logic.CanMoveAtHighSpeeds(items), memoryAddress: 0x6, memoryFlag: 0x4, @@ -91,7 +91,7 @@ public CrumbleShaftRoom(UpperNorfairWest region, IMetadataService? metadata, Tra new Location(this, LocationId.UpperNorfairCrumbleShaft, 0x8F8B46, LocationType.Hidden, name: "Missile (below Ice Beam)", vanillaItem: ItemType.Missile, - access: items => (Config.MetroidKeysanity ? items.CardNorfairL1 : items.Super) + access: items => (Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity ? items.CardNorfairL1 : items.Super) && Logic.CanUsePowerBombs(items) && items.Varia && Logic.CanMoveAtHighSpeeds(items) && (Logic.CanWallJump(WallJumpDifficulty.Easy) || Logic.CanFly(items)), memoryAddress: 0x6, diff --git a/src/Randomizer.Data/WorldData/Regions/SuperMetroid/WreckedShip.cs b/src/Randomizer.Data/WorldData/Regions/SuperMetroid/WreckedShip.cs index 870f07535..b937c0f5b 100644 --- a/src/Randomizer.Data/WorldData/Regions/SuperMetroid/WreckedShip.cs +++ b/src/Randomizer.Data/WorldData/Regions/SuperMetroid/WreckedShip.cs @@ -47,7 +47,7 @@ public override bool CanEnter(Progression items, bool requireRewards) { return items.Super && ( /* Over the Moat */ - ((Config.MetroidKeysanity ? items.CardCrateriaL2 : Logic.CanUsePowerBombs(items)) && ( + ((Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity ? items.CardCrateriaL2 : Logic.CanUsePowerBombs(items)) && ( Logic.CanMoatSpeedBoost(items) || items.Grapple || items.SpaceJump || (items.Gravity && (Logic.CanIbj(items) || (items.HiJump && Logic.CanWallJump(WallJumpDifficulty.Easy)))) || Logic.CanWallJump(WallJumpDifficulty.Insane) diff --git a/src/Randomizer.Data/WorldData/Regions/Zelda/GanonsTower.cs b/src/Randomizer.Data/WorldData/Regions/Zelda/GanonsTower.cs index 143538d5c..08c589896 100644 --- a/src/Randomizer.Data/WorldData/Regions/Zelda/GanonsTower.cs +++ b/src/Randomizer.Data/WorldData/Regions/Zelda/GanonsTower.cs @@ -177,7 +177,7 @@ public override bool CanFill(Item item, Progression items) return false; } - if (Config.ZeldaKeysanity + if (Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity && !((item.Type == ItemType.BigKeyGT || item.Type == ItemType.KeyGT) && item.World == World) && (item.IsKey || item.IsBigKey || item.IsKeycard)) { diff --git a/src/Randomizer.Data/WorldData/Regions/Zelda/HyruleCastle.cs b/src/Randomizer.Data/WorldData/Regions/Zelda/HyruleCastle.cs index dc610778a..cd5e280f6 100644 --- a/src/Randomizer.Data/WorldData/Regions/Zelda/HyruleCastle.cs +++ b/src/Randomizer.Data/WorldData/Regions/Zelda/HyruleCastle.cs @@ -62,7 +62,7 @@ public HyruleCastle(World world, Config config, IMetadataService? metadata, Trac memoryType: LocationMemoryType.ZeldaMisc, metadata: metadata, trackerState: trackerState) - .Allow((item, items) => Config.ZeldaKeysanity || !item.IsDungeonItem) + .Allow((item, items) => Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity || !item.IsDungeonItem) .Weighted(SphereOne); SecretPassage = new Location(this, LocationId.SecretPassage, 0x1E971, LocationType.NotInDungeon, @@ -72,7 +72,7 @@ public HyruleCastle(World world, Config config, IMetadataService? metadata, Trac memoryFlag: 0x4, metadata: metadata, trackerState: trackerState) - .Allow((item, items) => Config.ZeldaKeysanity || !item.IsDungeonItem) + .Allow((item, items) => Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity || !item.IsDungeonItem) .Weighted(SphereOne); BackOfEscape = new BackOfEscapeRoom(this, metadata, trackerState); diff --git a/src/Randomizer.Data/WorldData/Regions/Zelda/PalaceOfDarkness.cs b/src/Randomizer.Data/WorldData/Regions/Zelda/PalaceOfDarkness.cs index a7dce0cd9..4138f3629 100644 --- a/src/Randomizer.Data/WorldData/Regions/Zelda/PalaceOfDarkness.cs +++ b/src/Randomizer.Data/WorldData/Regions/Zelda/PalaceOfDarkness.cs @@ -30,7 +30,7 @@ public PalaceOfDarkness(World world, Config config, IMetadataService? metadata, name: "Big Key Chest", vanillaItem: ItemType.BigKeyPD, access: items => BigKeyChest != null && items.KeyPD >= (BigKeyChest.ItemIs(ItemType.KeyPD, World) ? 1 : - (items.Hammer && items.Bow && Logic.CanPassSwordOnlyDarkRooms(items)) || config.ZeldaKeysanity ? 6 : 5), + (items.Hammer && items.Bow && Logic.CanPassSwordOnlyDarkRooms(items)) || config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity ? 6 : 5), memoryAddress: 0x3A, memoryFlag: 0x4, metadata: metadata, @@ -76,7 +76,7 @@ public PalaceOfDarkness(World world, Config config, IMetadataService? metadata, CompassChest = new Location(this, LocationId.PalaceOfDarknessCompassChest, 0x1EA43, LocationType.Regular, name: "Compass Chest", vanillaItem: ItemType.CompassPD, - access: items => items.KeyPD >= ((items.Hammer && items.Bow && Logic.CanPassSwordOnlyDarkRooms(items)) || config.ZeldaKeysanity ? 4 : 3), + access: items => items.KeyPD >= ((items.Hammer && items.Bow && Logic.CanPassSwordOnlyDarkRooms(items)) || config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity ? 4 : 3), memoryAddress: 0x1A, memoryFlag: 0x5, metadata: metadata, @@ -86,8 +86,8 @@ public PalaceOfDarkness(World world, Config config, IMetadataService? metadata, name: "Harmless Hellway", vanillaItem: ItemType.FiveRupees, access: items => HarmlessHellway != null && items.KeyPD >= (HarmlessHellway.ItemIs(ItemType.KeyPD, World) ? - (items.Hammer && items.Bow && Logic.CanPassSwordOnlyDarkRooms(items)) || config.ZeldaKeysanity ? 4 : 3 : - (items.Hammer && items.Bow && Logic.CanPassSwordOnlyDarkRooms(items)) || config.ZeldaKeysanity ? 6 : 5), + (items.Hammer && items.Bow && Logic.CanPassSwordOnlyDarkRooms(items)) || config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity ? 4 : 3 : + (items.Hammer && items.Bow && Logic.CanPassSwordOnlyDarkRooms(items)) || config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity ? 6 : 5), memoryAddress: 0x1A, memoryFlag: 0x6, metadata: metadata, @@ -97,7 +97,7 @@ public PalaceOfDarkness(World world, Config config, IMetadataService? metadata, BigChest = new Location(this, LocationId.PalaceOfDarknessBigChest, 0x1EA40, LocationType.Regular, name: "Big Chest", vanillaItem: ItemType.Hammer, - access: items => items.BigKeyPD && Logic.CanPassSwordOnlyDarkRooms(items) && items.KeyPD >= ((items.Hammer && items.Bow) || config.ZeldaKeysanity ? 6 : 5), + access: items => items.BigKeyPD && Logic.CanPassSwordOnlyDarkRooms(items) && items.KeyPD >= ((items.Hammer && items.Bow) || config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity ? 6 : 5), memoryAddress: 0x1A, memoryFlag: 0x4, metadata: metadata, @@ -184,7 +184,7 @@ public DarkMazeRoom(Region region, IMetadataService? metadata, TrackerState? tra new Location(this, LocationId.PalaceOfDarknessDarkMazeTop, 0x1EA55, LocationType.Regular, name: "Top", vanillaItem: ItemType.ThreeBombs, - access: items => Logic.CanPassSwordOnlyDarkRooms(items) && items.KeyPD >= ((items.Hammer && items.Bow) || Config.ZeldaKeysanity ? 6 : 5), + access: items => Logic.CanPassSwordOnlyDarkRooms(items) && items.KeyPD >= ((items.Hammer && items.Bow) || Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity ? 6 : 5), memoryAddress: 0x19, memoryFlag: 0x4, metadata: metadata, @@ -192,7 +192,7 @@ public DarkMazeRoom(Region region, IMetadataService? metadata, TrackerState? tra new Location(this, LocationId.PalaceOfDarknessDarkMazeBottom, 0x1EA58, LocationType.Regular, name: "Bottom", vanillaItem: ItemType.KeyPD, - access: items => Logic.CanPassSwordOnlyDarkRooms(items) && items.KeyPD >= ((items.Hammer && items.Bow) || Config.ZeldaKeysanity ? 6 : 5), + access: items => Logic.CanPassSwordOnlyDarkRooms(items) && items.KeyPD >= ((items.Hammer && items.Bow) || Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity ? 6 : 5), memoryAddress: 0x19, memoryFlag: 0x5, metadata: metadata, @@ -211,7 +211,7 @@ public DarkBasementRoom(Region region, IMetadataService? metadata, TrackerState? new Location(this, LocationId.PalaceOfDarknessDarkBasementLeft, 0x1EA4C, LocationType.Regular, name: "Left", vanillaItem: ItemType.TenArrows, - access: items => Logic.CanPassFireRodDarkRooms(items) && items.KeyPD >= ((items.Hammer && items.Bow) || Config.ZeldaKeysanity ? 4 : 3), + access: items => Logic.CanPassFireRodDarkRooms(items) && items.KeyPD >= ((items.Hammer && items.Bow) || Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity ? 4 : 3), memoryAddress: 0x6A, memoryFlag: 0x4, metadata: metadata, @@ -219,7 +219,7 @@ public DarkBasementRoom(Region region, IMetadataService? metadata, TrackerState? new Location(this, LocationId.PalaceOfDarknessDarkBasementRight, 0x1EA4F, LocationType.Regular, name: "Right", vanillaItem: ItemType.KeyPD, - access: items => Logic.CanPassFireRodDarkRooms(items) && items.KeyPD >= ((items.Hammer && items.Bow) || Config.ZeldaKeysanity ? 4 : 3), + access: items => Logic.CanPassFireRodDarkRooms(items) && items.KeyPD >= ((items.Hammer && items.Bow) || Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity ? 4 : 3), memoryAddress: 0x6A, memoryFlag: 0x5, metadata: metadata, diff --git a/src/Randomizer.Data/WorldData/Regions/Zelda/SwampPalace.cs b/src/Randomizer.Data/WorldData/Regions/Zelda/SwampPalace.cs index 67e6ded96..af52e14d8 100644 --- a/src/Randomizer.Data/WorldData/Regions/Zelda/SwampPalace.cs +++ b/src/Randomizer.Data/WorldData/Regions/Zelda/SwampPalace.cs @@ -25,7 +25,7 @@ public SwampPalace(World world, Config config, IMetadataService? metadata, Track memoryFlag: 0x4, metadata: metadata, trackerState: trackerState) - .Allow((item, items) => Config.ZeldaKeysanity || item.Is(ItemType.KeySP, World)); + .Allow((item, items) => Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity || item.Is(ItemType.KeySP, World)); MapChest = new Location(this, LocationId.SwampPalaceMapChest, 0x1E986, LocationType.Regular, name: "Map Chest", diff --git a/src/Randomizer.Data/WorldData/Regions/Zelda/TurtleRock.cs b/src/Randomizer.Data/WorldData/Regions/Zelda/TurtleRock.cs index 20720eb88..6e98a80d0 100644 --- a/src/Randomizer.Data/WorldData/Regions/Zelda/TurtleRock.cs +++ b/src/Randomizer.Data/WorldData/Regions/Zelda/TurtleRock.cs @@ -43,7 +43,7 @@ public TurtleRock(World world, Config config, IMetadataService? metadata, Tracke name: "Big Key Chest", vanillaItem: ItemType.BigKeyTR, access: items => BigKeyChest != null && items.KeyTR >= - (!Config.ZeldaKeysanity || BigKeyChest.ItemIs(ItemType.BigKeyTR, World) ? 2 : + (!Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity || BigKeyChest.ItemIs(ItemType.BigKeyTR, World) ? 2 : BigKeyChest.ItemIs(ItemType.KeyTR, World) ? 3 : 4), memoryAddress: 0x14, memoryFlag: 0x4, diff --git a/src/Randomizer.Data/WorldData/WorldItemPools.cs b/src/Randomizer.Data/WorldData/WorldItemPools.cs index 2fe0ec048..9dba300b7 100644 --- a/src/Randomizer.Data/WorldData/WorldItemPools.cs +++ b/src/Randomizer.Data/WorldData/WorldItemPools.cs @@ -13,6 +13,8 @@ public class WorldItemPools { public WorldItemPools(World world) { + World = world; + // Create the item pools var progression = CreateProgressionPool(world); var nice = CreateNicePool(world); @@ -35,16 +37,6 @@ public WorldItemPools(World world) keycards.Remove(keycards.First(x => x.Type == itemType)); } - // If we're missing any items, fill up the spots with twenty rupees - var itemCount = progression.Count + nice.Count + dungeon.Count + junk.Count; - if (world.Config.MetroidKeysanity) - itemCount += keycards.Count; - var locationCount = world.Locations.Count(); - if (itemCount < locationCount) - { - junk.AddRange(Copies(locationCount - itemCount, () => new Item(ItemType.TwentyRupees, world))); - } - Progression = progression; Nice = nice; Junk = junk; @@ -52,13 +44,27 @@ public WorldItemPools(World world) Keycards = keycards; } - public IReadOnlyCollection Progression { get; set; } - public IReadOnlyCollection Nice { get; } - public IReadOnlyCollection Junk { get; } - public IReadOnlyCollection Dungeon { get; set; } - public IReadOnlyCollection Keycards { get; set; } + public World World { get; init; } + public List Progression { get; set; } + public List Nice { get; } + public List Junk { get; } + public List Dungeon { get; set; } + public List Keycards { get; set; } public IEnumerable AllItems => Progression.Concat(Nice).Concat(Junk).Concat(Dungeon).Concat(Keycards); + public void FillGaps() + { + // If we're missing any items, fill up the spots with twenty rupees + var itemCount = Progression.Count + Nice.Count + Dungeon.Count + Junk.Count; + if (World.Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity) + itemCount += Keycards.Count; + var locationCount = World.Locations.Count(); + if (itemCount < locationCount) + { + Junk.AddRange(Copies(locationCount - itemCount, () => new Item(ItemType.TwentyRupees, World))); + } + } + /// /// Returns a list of items that are nice to have but are not required /// to finish the game. @@ -172,8 +178,22 @@ public static List CreateDungeonPool(World world) new Item(ItemType.MapIP, world), new Item(ItemType.MapMM, world), new Item(ItemType.MapTR, world), + + new Item(ItemType.MapHC, world), + new Item(ItemType.MapGT, world), + new Item(ItemType.CompassEP, world), + new Item(ItemType.CompassDP, world), + new Item(ItemType.CompassTH, world), + new Item(ItemType.CompassPD, world), + new Item(ItemType.CompassSP, world), + new Item(ItemType.CompassSW, world), + new Item(ItemType.CompassTT, world), + new Item(ItemType.CompassIP, world), + new Item(ItemType.CompassMM, world), + new Item(ItemType.CompassTR, world), + new Item(ItemType.CompassGT, world), }); - if (!world.Config.MetroidKeysanity) + /*if (!world.Config.MetroidKeysanity) { itemPool.AddRange(new[] { new Item(ItemType.MapHC, world), @@ -190,7 +210,7 @@ public static List CreateDungeonPool(World world) new Item(ItemType.CompassTR, world), new Item(ItemType.CompassGT, world), }); - } + }*/ return itemPool; } diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTracker.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTracker.cs index f04864bfb..abffd9128 100644 --- a/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTracker.cs +++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTracker.cs @@ -37,6 +37,7 @@ public class AutoTracker : AutoTrackerBase private readonly TrackerModuleFactory _trackerModuleFactory; private readonly IRandomizerConfigService _config; private readonly IWorldService _worldService; + private readonly IGameModeService _gameModeService; private int _currentIndex; private Game _previousGame; private bool _hasStarted; @@ -62,6 +63,7 @@ public class AutoTracker : AutoTrackerBase /// /// /// + /// public AutoTracker(ILogger logger, ILoggerFactory loggerFactory, IItemService itemService, @@ -70,7 +72,8 @@ public AutoTracker(ILogger logger, TrackerModuleFactory trackerModuleFactory, IRandomizerConfigService randomizerConfigService, IWorldService worldService, - TrackerBase trackerBase) + TrackerBase trackerBase, + IGameModeService gameModeService) { _logger = logger; _loggerFactory = loggerFactory; @@ -78,6 +81,7 @@ public AutoTracker(ILogger logger, _trackerModuleFactory = trackerModuleFactory; _config = randomizerConfigService; _worldService = worldService; + _gameModeService = gameModeService; TrackerBase = trackerBase; // Check if the game has started or not @@ -747,6 +751,8 @@ private void CheckZeldaState(EmulatorAction action) _logger.LogInformation("{StateName} detected", check.GetType().Name); } } + + _gameModeService.ZeldaStateChanged(ZeldaState, prevState); } /// @@ -847,6 +853,8 @@ private void CheckMetroidState(EmulatorAction action) _logger.LogInformation("{StateName} detected", check.GetType().Name); } } + + _gameModeService.MetroidStateChanged(MetroidState, prevState); } /// @@ -865,7 +873,7 @@ private void CheckShip(EmulatorAction action) private void IncrementGTItems(Location location) { - if (_foundGTKey || _config.Config.ZeldaKeysanity) return; + if (_foundGTKey || _config.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity) return; var chatIntegrationModule = _trackerModuleFactory.GetModule(); _numGTItems++; diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/EnteredDungeon.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/EnteredDungeon.cs index 193bdf546..6ea64c828 100644 --- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/EnteredDungeon.cs +++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/EnteredDungeon.cs @@ -47,11 +47,11 @@ public bool ExecuteCheck(TrackerBase tracker, AutoTrackerZeldaState currentState var dungeon = region as IDungeon; if (dungeon == null) return false; - if (!_worldAccessor.World.Config.ZeldaKeysanity && !_enteredDungeons.Contains(region) && dungeon.IsPendantDungeon) + if (!_worldAccessor.World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity && !_enteredDungeons.Contains(region) && dungeon.IsPendantDungeon) { tracker.Say(tracker.Responses.AutoTracker.EnterPendantDungeon, dungeon.DungeonMetadata.Name, dungeon.DungeonReward?.Metadata.Name); } - else if (!_worldAccessor.World.Config.ZeldaKeysanity && region is CastleTower) + else if (!_worldAccessor.World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity && region is CastleTower) { tracker.Say(x => x.AutoTracker.EnterHyruleCastleTower); } diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMap.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMap.cs index 8cd071f6f..0374a5ec6 100644 --- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMap.cs +++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMap.cs @@ -89,7 +89,7 @@ private void UpdateLightWorldRewards() foreach (var (region, map) in dungeons) { - if (World.Config.ZeldaKeysanity && !Items.IsTracked(map)) + if (World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity && !Items.IsTracked(map)) continue; var dungeon = (IDungeon)region; @@ -101,7 +101,7 @@ private void UpdateLightWorldRewards() } } - if (!World.Config.ZeldaKeysanity) + if (!World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity) { if (rewards.Count(x => x == RewardType.CrystalRed || x == RewardType.CrystalBlue) == 3) { @@ -142,7 +142,7 @@ protected void UpdateDarkWorldRewards() foreach (var (region, map) in dungeons) { - if (World.Config.ZeldaKeysanity && !Items.IsTracked(map)) + if (World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity && !Items.IsTracked(map)) continue; var dungeon = (IDungeon)region; @@ -157,7 +157,7 @@ protected void UpdateDarkWorldRewards() var isMiseryMirePendant = (World.MiseryMire as IDungeon).IsPendantDungeon; var isTurtleRockPendant = (World.TurtleRock as IDungeon).IsPendantDungeon; - if (!World.Config.ZeldaKeysanity) + if (!World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity) { if (isMiseryMirePendant && isTurtleRockPendant) { diff --git a/src/Randomizer.SMZ3.Tracking/Services/ItemService.cs b/src/Randomizer.SMZ3.Tracking/Services/ItemService.cs index 0f66c90c8..f6d80b45e 100644 --- a/src/Randomizer.SMZ3.Tracking/Services/ItemService.cs +++ b/src/Randomizer.SMZ3.Tracking/Services/ItemService.cs @@ -226,7 +226,7 @@ public Progression GetProgression(bool assumeKeys) var progression = new Progression(); - if (!_world.World.Config.MetroidKeysanity || assumeKeys) + if (!_world.World.Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity || assumeKeys) { progression.AddRange(_world.World.ItemPools.Keycards); if (assumeKeys) @@ -266,12 +266,12 @@ public Progression GetProgression(IHasLocations area) { case Z3Region: case Room { Region: Z3Region }: - return GetProgression(assumeKeys: !_world.World.Config.ZeldaKeysanity); + return GetProgression(assumeKeys: !_world.World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity); case SMRegion: case Room { Region: SMRegion }: - return GetProgression(assumeKeys: !_world.World.Config.MetroidKeysanity); + return GetProgression(assumeKeys: !_world.World.Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity); default: - return GetProgression(assumeKeys: _world.World.Config.KeysanityMode == KeysanityMode.None); + return GetProgression(assumeKeys: _world.World.Config.GameModeConfigs.KeysanityConfig.KeysanityMode == KeysanityMode.None); } } diff --git a/src/Randomizer.SMZ3.Tracking/Services/WorldService.cs b/src/Randomizer.SMZ3.Tracking/Services/WorldService.cs index a4888c52c..fe03ddd50 100644 --- a/src/Randomizer.SMZ3.Tracking/Services/WorldService.cs +++ b/src/Randomizer.SMZ3.Tracking/Services/WorldService.cs @@ -200,7 +200,7 @@ public IEnumerable Regions }; private bool IsKeysanityForLocation(Location location) - => World.Config.KeysanityForRegion(location.Region); + => World.Config.GameModeConfigs.KeysanityConfig.KeysanityForRegion(location.Region); private Progression Progression(bool assumeKeys) => _itemService.GetProgression(assumeKeys); diff --git a/src/Randomizer.SMZ3.Tracking/Tracker.cs b/src/Randomizer.SMZ3.Tracking/Tracker.cs index 915368e58..00fe7046f 100644 --- a/src/Randomizer.SMZ3.Tracking/Tracker.cs +++ b/src/Randomizer.SMZ3.Tracking/Tracker.cs @@ -53,6 +53,7 @@ public sealed class Tracker : TrackerBase, IDisposable private readonly IWorldService _worldService; private readonly ITrackerTimerService _timerService; private readonly SpeechRecognitionServiceBase _recognizer; + private readonly IGameModeService _gameModeService; private bool _disposed; private string? _lastSpokenText; private readonly bool _alternateTracker; @@ -79,6 +80,7 @@ public sealed class Tracker : TrackerBase, IDisposable /// /// /// + /// public Tracker(IWorldAccessor worldAccessor, TrackerModuleFactory moduleFactory, IChatClient chatClient, @@ -91,7 +93,8 @@ public Tracker(IWorldAccessor worldAccessor, ITrackerStateService stateService, IWorldService worldService, ITrackerTimerService timerService, - SpeechRecognitionServiceBase speechRecognitionService) + SpeechRecognitionServiceBase speechRecognitionService, + IGameModeService gameModeService) { if (trackerOptions.Options == null) throw new InvalidOperationException("Tracker options have not yet been activated."); @@ -106,6 +109,8 @@ public Tracker(IWorldAccessor worldAccessor, _stateService = stateService; _worldService = worldService; _timerService = timerService; + _recognizer = speechRecognitionService; + _gameModeService = gameModeService; // Initialize the tracker configuration Responses = configs.Responses; @@ -127,7 +132,6 @@ public Tracker(IWorldAccessor worldAccessor, } // Initialize the speech recognition engine - _recognizer = speechRecognitionService; if (OperatingSystem.IsWindows()) { _recognizer.SpeechRecognized += Recognizer_SpeechRecognized; @@ -876,13 +880,13 @@ public override bool TrackItem(Item item, string? trackedAs = null, float? confi var originalTrackingState = item.State.TrackingState; ItemService.ResetProgression(); - var isGTPreBigKey = !World.Config.ZeldaKeysanity + var isGTPreBigKey = !World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity && autoTracked && location?.Region.GetType() == typeof(GanonsTower) && !ItemService.GetProgression(false).BigKeyGT; var stateResponse = !isGTPreBigKey && !silent && (!autoTracked || !item.Metadata.IsDungeonItem() - || World.Config.ZeldaKeysanity); + || World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity); // Actually track the item if it's for the local player's world if (item.World == World) @@ -1034,7 +1038,7 @@ public override bool TrackItem(Item item, string? trackedAs = null, float? confi } } - var isKeysanityForLocation = (location.Region is Z3Region && World.Config.ZeldaKeysanity) || (location.Region is SMRegion && World.Config.MetroidKeysanity); + var isKeysanityForLocation = (location.Region is Z3Region && World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity) || (location.Region is SMRegion && World.Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity); var items = ItemService.GetProgression(!isKeysanityForLocation); if (stateResponse && !location.IsAvailable(items) && (confidence >= Options.MinimumSassConfidence || autoTracked)) { @@ -1107,6 +1111,11 @@ public override bool TrackItem(Item item, string? trackedAs = null, float? confi }); } + if (autoTracked || giftedItem) + { + _gameModeService.ItemTracked(item, location!); + } + GiveLocationHint(accessibleBefore); RestartIdleTimers(); @@ -1132,6 +1141,7 @@ public override void TrackItems(List items, bool autoTracked, bool giftedI foreach (var item in items) { item.Track(); + _gameModeService.ItemTracked(item, null); } if (items.Count == 2) @@ -1144,11 +1154,11 @@ public override void TrackItems(List items, bool autoTracked, bool giftedI } else { - var itemsToSay = items.Where(x => x.Type.IsPossibleProgression(World.Config.ZeldaKeysanity, World.Config.MetroidKeysanity)).Take(2).ToList(); + var itemsToSay = items.Where(x => x.Type.IsPossibleProgression(World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity, World.Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity)).Take(2).ToList(); if (itemsToSay.Count() < 2) { var numToTake = 2 - itemsToSay.Count(); - itemsToSay.AddRange(items.Where(x => !x.Type.IsPossibleProgression(World.Config.ZeldaKeysanity, World.Config.MetroidKeysanity)).Take(numToTake)); + itemsToSay.AddRange(items.Where(x => !x.Type.IsPossibleProgression(World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity, World.Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity)).Take(numToTake)); } Say(x => x.TrackedManyItems, itemsToSay[0].Metadata.Name, itemsToSay[1].Metadata.Name, items.Count - 2); @@ -1417,7 +1427,7 @@ public override void ClearArea(IHasLocations area, bool trackItems, bool include itemsCleared++; if (!trackItems) { - if (IsTreasure(location.Item) || World.Config.ZeldaKeysanity) + if (IsTreasure(location.Item) || World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity) treasureTracked++; location.State.Cleared = true; World.LastClearedLocation = location; @@ -1430,7 +1440,7 @@ public override void ClearArea(IHasLocations area, bool trackItems, bool include _logger.LogWarning("Failed to track {ItemType} in {Area}.", item.Name, area.Name); // Probably the compass or something, who cares else itemsTracked.Add(item); - if (IsTreasure(location.Item) || World.Config.ZeldaKeysanity) + if (IsTreasure(location.Item) || World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity) treasureTracked++; location.State.Cleared = true; @@ -1529,7 +1539,7 @@ public override void ClearDungeon(IDungeon dungeon, float? confidence = null) dungeon.DungeonState.Cleared = true; var region = (Region)dungeon; - var progress = ItemService.GetProgression(assumeKeys: !World.Config.ZeldaKeysanity); + var progress = ItemService.GetProgression(assumeKeys: !World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity); var locations = region.Locations.Where(x => x.State.Cleared == false).ToList(); var inaccessibleLocations = locations.Where(x => !x.IsAvailable(progress)).ToList(); if (locations.Count > 0) @@ -1703,6 +1713,10 @@ public override void MarkDungeonAsCleared(IDungeon dungeon, float? confidence = } }); } + else + { + _gameModeService.DungeonCleared(dungeon); + } } /// @@ -1753,6 +1767,10 @@ public override void MarkBossAsDefeated(Boss boss, bool admittedGuilt = true, fl addedEvent.IsUndone = true; }); } + else + { + _gameModeService.BossDefeated(boss); + } } /// @@ -2215,7 +2233,7 @@ private static bool IsTreasure(Item? item) } var dungeon = GetDungeonFromLocation(location); - if (dungeon != null && (IsTreasure(location.Item) || World.Config.ZeldaKeysanity)) + if (dungeon != null && (IsTreasure(location.Item) || World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity)) { if (TrackDungeonTreasure(dungeon, confidence, 1, autoTracked, stateResponse)) return _undoHistory.Pop().Action; diff --git a/src/Randomizer.SMZ3.Tracking/VoiceCommands/MultiplayerModule.cs b/src/Randomizer.SMZ3.Tracking/VoiceCommands/MultiplayerModule.cs index a5d48ee9a..08701a470 100644 --- a/src/Randomizer.SMZ3.Tracking/VoiceCommands/MultiplayerModule.cs +++ b/src/Randomizer.SMZ3.Tracking/VoiceCommands/MultiplayerModule.cs @@ -157,7 +157,7 @@ private void PlayerTrackedLocation(PlayerTrackedLocationEventHandlerArgs args) if (item == null) throw new InvalidOperationException($"Player retrieved invalid item {args.ItemToGive}"); TrackerBase.GameService!.TryGiveItem(item, args.PlayerId); - if (item.Type.IsPossibleProgression(item.World.Config.ZeldaKeysanity, item.World.Config.MetroidKeysanity)) + if (item.Type.IsPossibleProgression(item.World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity, item.World.Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity)) { TrackerBase.Say(x => x.Multiplayer.ReceivedUsefulItemFromOtherPlayer, args.PhoneticName, item.Metadata.Name, item.Metadata.NameWithArticle); diff --git a/src/Randomizer.SMZ3/FileData/Patches/DiggingGamePatch.cs b/src/Randomizer.SMZ3/FileData/Patches/DiggingGamePatch.cs index bcee9eab6..75ae2ee08 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/DiggingGamePatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/DiggingGamePatch.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Randomizer.Abstractions; using Randomizer.Shared; namespace Randomizer.SMZ3.FileData.Patches; diff --git a/src/Randomizer.SMZ3/FileData/Patches/DungeonMusicPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/DungeonMusicPatch.cs index 13010aaeb..02568cbf7 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/DungeonMusicPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/DungeonMusicPatch.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Randomizer.Abstractions; using Randomizer.Data.WorldData.Regions; using Randomizer.Data.WorldData.Regions.Zelda; using Randomizer.Shared; @@ -11,7 +12,7 @@ public class DungeonMusicPatch : RomPatch { public override IEnumerable GetChanges(GetPatchesRequest data) { - if (data.World.Config.ZeldaKeysanity) + if (data.World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity) { return new List(); }; diff --git a/src/Randomizer.SMZ3/FileData/Patches/FairyPondTradePatch.cs b/src/Randomizer.SMZ3/FileData/Patches/FairyPondTradePatch.cs index e5a05b3e9..6d3795f96 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/FairyPondTradePatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/FairyPondTradePatch.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Randomizer.Abstractions; using Randomizer.Shared; namespace Randomizer.SMZ3.FileData.Patches; diff --git a/src/Randomizer.SMZ3/FileData/Patches/FlashRemovalPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/FlashRemovalPatch.cs index 426000ec2..605f4aa03 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/FlashRemovalPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/FlashRemovalPatch.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Randomizer.Data.Options; +using System.Collections.Generic; +using Randomizer.Abstractions; namespace Randomizer.SMZ3.FileData.Patches { diff --git a/src/Randomizer.SMZ3/FileData/Patches/GoalsPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/GoalsPatch.cs index 6073e48cb..e39aca99b 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/GoalsPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/GoalsPatch.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Randomizer.Abstractions; using Randomizer.Data.Options; namespace Randomizer.SMZ3.FileData.Patches; diff --git a/src/Randomizer.SMZ3/FileData/Patches/HeartColorPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/HeartColorPatch.cs index d19263229..9e9c05600 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/HeartColorPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/HeartColorPatch.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Randomizer.Abstractions; using Randomizer.Data.Options; namespace Randomizer.SMZ3.FileData.Patches diff --git a/src/Randomizer.SMZ3/FileData/Patches/InfiniteSpaceJumpPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/InfiniteSpaceJumpPatch.cs index 24be88b2b..517b2d678 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/InfiniteSpaceJumpPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/InfiniteSpaceJumpPatch.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Randomizer.Abstractions; namespace Randomizer.SMZ3.FileData.Patches { diff --git a/src/Randomizer.SMZ3/FileData/Patches/LocationsPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/LocationsPatch.cs index 91167edd5..a63739315 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/LocationsPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/LocationsPatch.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Randomizer.Abstractions; using Randomizer.Data.WorldData; using Randomizer.Data.WorldData.Regions; using Randomizer.Shared; diff --git a/src/Randomizer.SMZ3/FileData/Patches/LowHealthPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/LowHealthPatch.cs index 4e417350c..50ba5a4be 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/LowHealthPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/LowHealthPatch.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Randomizer.Abstractions; using Randomizer.Data.Options; namespace Randomizer.SMZ3.FileData.Patches diff --git a/src/Randomizer.SMZ3/FileData/Patches/MedallionPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/MedallionPatch.cs index 7c0cf258b..91f92bad1 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/MedallionPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/MedallionPatch.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Randomizer.Abstractions; using Randomizer.Shared; namespace Randomizer.SMZ3.FileData.Patches; diff --git a/src/Randomizer.SMZ3/FileData/Patches/MenuSpeedPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/MenuSpeedPatch.cs index bdf6a1e94..f04384011 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/MenuSpeedPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/MenuSpeedPatch.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Randomizer.Abstractions; using Randomizer.Data.Options; namespace Randomizer.SMZ3.FileData.Patches diff --git a/src/Randomizer.SMZ3/FileData/Patches/MetadataPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/MetadataPatch.cs index 66ca48b0b..7866a5fe0 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/MetadataPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/MetadataPatch.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Randomizer.Abstractions; using Randomizer.Shared; using Randomizer.SMZ3.Generation; @@ -56,7 +57,7 @@ private IEnumerable WriteSeedData() { var configField = ((_data.World.Config.Race ? 1 : 0) << 15) | - ((_data.World.Config.Keysanity ? 1 : 0) << 13) | + ((_data.World.Config.GameModeConfigs.KeysanityConfig.KeysanityEnabled ? 1 : 0) << 13) | ((GetPatchesRequest.EnableMultiworld ? 1 : 0) << 12) | (Smz3Randomizer.Version.Major << 4) | (Smz3Randomizer.Version.Minor << 0); @@ -82,8 +83,5 @@ private IEnumerable WriteCommonFlags() /* Common Combo Configuration flags at [asm]/config.asm */ // Enable multiworld (for cheats) yield return new GeneratedPatch(Snes(0xF47000), UshortBytes(0x0001)); - // Enable keysanity if applicable - if (_data.World.Config.Keysanity) - yield return new GeneratedPatch(Snes(0xF47006), UshortBytes(0x0001)); } } diff --git a/src/Randomizer.SMZ3/FileData/Patches/MetroidAutoSavePatch.cs b/src/Randomizer.SMZ3/FileData/Patches/MetroidAutoSavePatch.cs index 2471bf26c..21baf565b 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/MetroidAutoSavePatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/MetroidAutoSavePatch.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Randomizer.Abstractions; namespace Randomizer.SMZ3.FileData.Patches { diff --git a/src/Randomizer.SMZ3/FileData/Patches/MetroidControlsPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/MetroidControlsPatch.cs index bf4b7f193..364456f3c 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/MetroidControlsPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/MetroidControlsPatch.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Randomizer.Abstractions; using Randomizer.Data.Options; using Randomizer.SMZ3.Generation; diff --git a/src/Randomizer.SMZ3/FileData/Patches/MetroidKeysanityPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/MetroidKeysanityPatch.cs index 92baf9f09..84ffa769e 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/MetroidKeysanityPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/MetroidKeysanityPatch.cs @@ -1,8 +1,11 @@ using System.Collections.Generic; using System.Linq; +using Randomizer.Abstractions; +using Randomizer.Shared; namespace Randomizer.SMZ3.FileData.Patches; +[Manual] public class MetroidKeysanityPatch : RomPatch { @@ -64,7 +67,7 @@ public class MetroidKeysanityPatch : RomPatch public override IEnumerable GetChanges(GetPatchesRequest data) { - if (!data.World.Config.MetroidKeysanity) + if (!data.World.Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity) yield break; ushort plaquePLm = 0xd410; @@ -105,5 +108,8 @@ public override IEnumerable GetChanges(GetPatchesRequest data) } yield return new GeneratedPatch(Snes(0x8f0000 + plmTablePos), new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + + // Show doors on map + yield return new GeneratedPatch(Snes(0xF47006), UshortBytes(0x0001)); } } diff --git a/src/Randomizer.SMZ3/FileData/Patches/NoBozoSoftlock.cs b/src/Randomizer.SMZ3/FileData/Patches/NoBozoSoftlock.cs index f300af944..c38d87aa5 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/NoBozoSoftlock.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/NoBozoSoftlock.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Randomizer.Abstractions; namespace Randomizer.SMZ3.FileData.Patches { diff --git a/src/Randomizer.SMZ3/FileData/Patches/RomPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/RomPatch.cs deleted file mode 100644 index 15849f376..000000000 --- a/src/Randomizer.SMZ3/FileData/Patches/RomPatch.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Randomizer.SMZ3.FileData.Patches -{ - /// - /// Represents one or more changes that can be applied to a generated SMZ3 - /// ROM. - /// - public abstract class RomPatch - { - /// - /// Returns the PC offset for the specified SNES address. - /// - /// The SNES address to convert. - /// - /// The PC offset equivalent to the SNES . - /// - protected static int Snes(int addr) - { - addr = addr switch - { - /* Redirect hi bank $30 access into ExHiRom lo bank $40 */ - _ when (addr & 0xFF8000) == 0x308000 => 0x400000 | (addr & 0x7FFF), - /* General case, add ExHi offset for banks < $80, and collapse mirroring */ - _ => (addr < 0x800000 ? 0x400000 : 0) | (addr & 0x3FFFFF), - }; - if (addr > 0x600000) - throw new InvalidOperationException($"Unmapped pc address target ${addr:X}"); - return addr; - } - - /// - /// Returns a byte array representing the specified 32-bit unsigned - /// integer. - /// - /// The 32-bit unsigned integer. - /// - /// A new byte array containing the 32-bit unsigned integer. - /// - protected static byte[] UintBytes(int value) => BitConverter.GetBytes((uint)value); - - /// - /// Returns a byte array representing the specified 16-bit unsigned - /// integer. - /// - /// The 16-bit unsigned integer. - /// - /// A new byte array containing the 16-bit unsigned integer. - /// - protected static byte[] UshortBytes(int value) => BitConverter.GetBytes((ushort)value); - - /// - /// Returns a byte array representing the specified ASCII-encoded text. - /// - /// The text. - /// - /// A new byte array containing the ASCII representation of the - /// . - /// - protected static byte[] AsAscii(string text) => Encoding.ASCII.GetBytes(text); - - /// - /// Returns the changes to be applied to an SMZ3 ROM file. - /// - /// Patcher Data with the world and config information - /// - /// A collection of changes, represented by the data to overwrite at the - /// specified ROM offset. - /// - public abstract IEnumerable GetChanges(GetPatchesRequest data); - } -} diff --git a/src/Randomizer.SMZ3/FileData/Patches/RomPatchFactory.cs b/src/Randomizer.SMZ3/FileData/Patches/RomPatchFactory.cs index e7fd13426..459a46e8a 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/RomPatchFactory.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/RomPatchFactory.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Reflection; using Microsoft.Extensions.DependencyInjection; +using Randomizer.Abstractions; using Randomizer.Shared; namespace Randomizer.SMZ3.FileData.Patches @@ -19,6 +20,7 @@ public RomPatchFactory(IServiceProvider services) public IEnumerable GetPatches() { return _services.GetServices() + .Where(x => x.GetType().GetCustomAttribute() == null) .Select(x => (patch: x, order: x.GetType().GetCustomAttribute()?.Order ?? 0)) .OrderBy(x => x.order) .Select(x => x.patch); diff --git a/src/Randomizer.SMZ3/FileData/Patches/SaveAndQuitFromBossRoomPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/SaveAndQuitFromBossRoomPatch.cs index e8cf945a1..dafd8c627 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/SaveAndQuitFromBossRoomPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/SaveAndQuitFromBossRoomPatch.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Randomizer.Abstractions; namespace Randomizer.SMZ3.FileData.Patches; diff --git a/src/Randomizer.SMZ3/FileData/Patches/StartingEquipmentPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/StartingEquipmentPatch.cs index 8833e1147..cf04a4019 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/StartingEquipmentPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/StartingEquipmentPatch.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Randomizer.Abstractions; using Randomizer.Data.Options; namespace Randomizer.SMZ3.FileData.Patches; diff --git a/src/Randomizer.SMZ3/FileData/Patches/UncleEquipmentPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/UncleEquipmentPatch.cs index df6cfd98c..c2701920b 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/UncleEquipmentPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/UncleEquipmentPatch.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Randomizer.Abstractions; using Randomizer.Shared; namespace Randomizer.SMZ3.FileData.Patches; diff --git a/src/Randomizer.SMZ3/FileData/Patches/ZeldaKeysanityPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/ZeldaKeysanityPatch.cs index 4eb866c88..e794dab9f 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/ZeldaKeysanityPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/ZeldaKeysanityPatch.cs @@ -1,17 +1,20 @@ using System.Collections.Generic; +using Randomizer.Abstractions; +using Randomizer.Shared; namespace Randomizer.SMZ3.FileData.Patches; +[Manual] public class ZeldaKeysanityPatch : RomPatch { public override IEnumerable GetChanges(GetPatchesRequest data) { - if (data.World.Config.ZeldaKeysanity) + if (data.World.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity) { yield return new GeneratedPatch(Snes(0x40003B), new byte[] { 1 }); // MapMode #$00 = Always On (default) - #$01 = Require Map Item yield return new GeneratedPatch(Snes(0x400045), new byte[] { 0x0f }); // display ----dcba a: Small Keys, b: Big Key, c: Map, d: Compass } - if (data.World.Config.Keysanity) + if (data.World.Config.GameModeConfigs.KeysanityConfig.KeysanityEnabled) { yield return new GeneratedPatch(Snes(0x40016A), new byte[] { 1 }); // FreeItemText: db #$01 ; #00 = Off (default) - #$01 = On } diff --git a/src/Randomizer.SMZ3/FileData/Patches/ZeldaPrizesPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/ZeldaPrizesPatch.cs index 7fbe7c5c8..d694f1ebb 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/ZeldaPrizesPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/ZeldaPrizesPatch.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Randomizer.Abstractions; using Randomizer.Data.Options; using Randomizer.Shared; diff --git a/src/Randomizer.SMZ3/FileData/Patches/ZeldaRewardsPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/ZeldaRewardsPatch.cs index 714ef7d09..4460e3df9 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/ZeldaRewardsPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/ZeldaRewardsPatch.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Randomizer.Abstractions; using Randomizer.Data.WorldData.Regions; using Randomizer.Data.WorldData.Regions.Zelda; using Randomizer.Shared; diff --git a/src/Randomizer.SMZ3/FileData/Patches/ZeldaTextsPatch.cs b/src/Randomizer.SMZ3/FileData/Patches/ZeldaTextsPatch.cs index c7b1e5775..a7f436794 100644 --- a/src/Randomizer.SMZ3/FileData/Patches/ZeldaTextsPatch.cs +++ b/src/Randomizer.SMZ3/FileData/Patches/ZeldaTextsPatch.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Randomizer.Abstractions; using Randomizer.Data.Configuration; using Randomizer.Data.Configuration.ConfigFiles; using Randomizer.Data.Configuration.ConfigTypes; diff --git a/src/Randomizer.SMZ3/GameModes/GameModeBaseDefault.cs b/src/Randomizer.SMZ3/GameModes/GameModeBaseDefault.cs new file mode 100644 index 000000000..d0eec2973 --- /dev/null +++ b/src/Randomizer.SMZ3/GameModes/GameModeBaseDefault.cs @@ -0,0 +1,12 @@ +using Randomizer.Abstractions; +using Randomizer.Data.Options; +using Randomizer.Shared.Enums; + +namespace Randomizer.SMZ3.GameModes; + +public class GameModeBaseDefault : GameModeBase +{ + public override GameModeType GameModeType => GameModeType.Default; + public override string Name => "Default"; + public override string Description => "Default"; +} diff --git a/src/Randomizer.SMZ3/GameModes/GameModeBaseKeysanity.cs b/src/Randomizer.SMZ3/GameModes/GameModeBaseKeysanity.cs new file mode 100644 index 000000000..9e3fe0c3f --- /dev/null +++ b/src/Randomizer.SMZ3/GameModes/GameModeBaseKeysanity.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; +using Randomizer.Abstractions; +using Randomizer.Data.WorldData; +using Randomizer.Data.WorldData.Regions; +using Randomizer.Shared; +using Randomizer.Shared.Enums; +using Randomizer.SMZ3.FileData.Patches; + +namespace Randomizer.SMZ3.GameModes; + +public class GameModeBaseKeysanity : GameModeBase +{ + public override GameModeType GameModeType => GameModeType.Keysanity; + public override string Name => "Keysanity"; + public override string Description => "Keysanity"; + + public override void ModifyWorldItemPools(WorldItemPools itemPool) + { + itemPool.Dungeon.Remove(itemPool.Dungeon.First(x => x.Type == ItemType.MapHC)); + itemPool.Dungeon.Remove(itemPool.Dungeon.First(x => x.Type == ItemType.MapGT)); + itemPool.Dungeon.Remove(itemPool.Dungeon.First(x => x.Type == ItemType.CompassEP)); + itemPool.Dungeon.Remove(itemPool.Dungeon.First(x => x.Type == ItemType.CompassDP)); + itemPool.Dungeon.Remove(itemPool.Dungeon.First(x => x.Type == ItemType.CompassTH)); + itemPool.Dungeon.Remove(itemPool.Dungeon.First(x => x.Type == ItemType.CompassPD)); + itemPool.Dungeon.Remove(itemPool.Dungeon.First(x => x.Type == ItemType.CompassSP)); + itemPool.Dungeon.Remove(itemPool.Dungeon.First(x => x.Type == ItemType.CompassSW)); + itemPool.Dungeon.Remove(itemPool.Dungeon.First(x => x.Type == ItemType.CompassTT)); + itemPool.Dungeon.Remove(itemPool.Dungeon.First(x => x.Type == ItemType.CompassIP)); + itemPool.Dungeon.Remove(itemPool.Dungeon.First(x => x.Type == ItemType.CompassMM)); + itemPool.Dungeon.Remove(itemPool.Dungeon.First(x => x.Type == ItemType.CompassTR)); + itemPool.Dungeon.Remove(itemPool.Dungeon.First(x => x.Type == ItemType.CompassGT)); + } + + public override ICollection GetPatches(World world) + { + return world.Config.GameModeConfigs.KeysanityConfig.KeysanityMode == KeysanityMode.None + ? base.GetPatches(world) + : new List { new MetroidKeysanityPatch(), new ZeldaKeysanityPatch() }; + } + + public override void ItemTracked(Item item, Location? location, TrackerBase? _tracker) + { + if (!item.Type.IsInCategory(ItemCategory.Map)) + return; + + var world = item.World; + + IDungeon? dungeon = item.Type switch + { + ItemType.MapEP => world.EasternPalace, + ItemType.MapTH => world.TowerOfHera, + ItemType.MapDP => world.DesertPalace, + ItemType.MapPD => world.PalaceOfDarkness, + ItemType.MapSP => world.SwampPalace, + ItemType.MapTT => world.ThievesTown, + ItemType.MapSW => world.SkullWoods, + ItemType.MapIP => world.IcePalace, + ItemType.MapMM => world.MiseryMire, + ItemType.MapTR => world.TurtleRock, + _ => null + }; + + if (dungeon == null || dungeon.MarkedReward == dungeon.DungeonRewardType) + return; + + _tracker?.SetDungeonReward(dungeon, dungeon.DungeonRewardType, null, true); + } +} diff --git a/src/Randomizer.SMZ3/GameModes/GameModeService.cs b/src/Randomizer.SMZ3/GameModes/GameModeService.cs new file mode 100644 index 000000000..27d2a3628 --- /dev/null +++ b/src/Randomizer.SMZ3/GameModes/GameModeService.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Randomizer.Abstractions; +using Randomizer.Data.Options; +using Randomizer.Data.Tracking; +using Randomizer.Data.WorldData; +using Randomizer.Data.WorldData.Regions; + +namespace Randomizer.SMZ3.GameModes; + +public class GameModeService : IGameModeService +{ + private readonly List _allGameModes; + private List _trackerGameModes = new(); + private Dictionary _gameModeTypes; + private TrackerBase? _tracker; + + public GameModeService(IServiceProvider serviceProvider) + { + _allGameModes = serviceProvider.GetServices().ToList(); + _gameModeTypes = _allGameModes.ToDictionary(x => x.Name, x => x); + } + + public Dictionary GameModeTypes => _gameModeTypes; + + public void SetTracker(TrackerBase tracker, GameModeConfigs gameModeConfigs) + { + _trackerGameModes = GetConfigGameModes(gameModeConfigs).ToList(); + _tracker = tracker; + } + + public void ModifyConfig(Config config) + { + foreach (var gameMode in GetConfigGameModes(config)) + { + gameMode.ModifyWorldConfig(config); + } + } + + public void ModifyWorldItemPools(World world) + { + foreach (var gameMode in GetConfigGameModes(world.Config)) + { + gameMode.ModifyWorldItemPools(world.ItemPools); + } + } + + public IEnumerable GetPatches(World world) + { + return GetConfigGameModes(world.Config).SelectMany(x => x.GetPatches(world)); + } + + private IEnumerable GetConfigGameModes(Config config) + { + return GetConfigGameModes(config.GameModeConfigs); + } + + private IEnumerable GetConfigGameModes(GameModeConfigs gameModeConfigs) + { + var enabledGameModes = gameModeConfigs.GetEnabledGameModes(); + return _allGameModes.Where(x => enabledGameModes.Contains(x.GameModeType)); + } + + public void ItemTracked(Item item, Location? location) + { + _trackerGameModes.ForEach(x => x.ItemTracked(item, location, _tracker)); + } + + public void DungeonCleared(IDungeon dungeon) + { + _trackerGameModes.ForEach(x => x.DungeonCleared(dungeon, _tracker)); + } + + public void BossDefeated(Boss boss) + { + _trackerGameModes.ForEach(x => x.BossDefeated(boss, _tracker)); + } + + public void ZeldaStateChanged(AutoTrackerZeldaState currentState, AutoTrackerZeldaState? previousState) + { + _trackerGameModes.ForEach(x => x.ZeldaStateChanged(currentState, previousState, _tracker)); + } + + public void MetroidStateChanged(AutoTrackerMetroidState currentState, AutoTrackerMetroidState? previousState) + { + _trackerGameModes.ForEach(x => x.MetroidStateChanged(currentState, previousState, _tracker)); + } + +} diff --git a/src/Randomizer.SMZ3/Generation/GameHintService.cs b/src/Randomizer.SMZ3/Generation/GameHintService.cs index 4d27c6edc..754c2dbf9 100644 --- a/src/Randomizer.SMZ3/Generation/GameHintService.cs +++ b/src/Randomizer.SMZ3/Generation/GameHintService.cs @@ -162,7 +162,7 @@ private IEnumerable GetDungeonHints(World hintPlayerWorld, ICollection hintPlayerWorld.Config.MultiWorld || hintPlayerWorld.Config.ZeldaKeysanity || x.IsPendantDungeon || x is HyruleCastle or GanonsTower); + .Where(x => hintPlayerWorld.Config.MultiWorld || hintPlayerWorld.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity || x.IsPendantDungeon || x is HyruleCastle or GanonsTower); var hints = new List(); foreach (var dungeon in dungeons) @@ -432,7 +432,7 @@ private bool CheckSphereLocationCount(IEnumerable sphereLocations, IEn /// private bool CheckSphereCrateriaBossKeys(IEnumerable sphereLocations) { - var numKeysanity = sphereLocations.Select(x => x.World).Distinct().Count(x => x.Config.MetroidKeysanity); + var numKeysanity = sphereLocations.Select(x => x.World).Distinct().Count(x => x.Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity); var numCratieriaBossKeys = sphereLocations.Select(x => x.Item.Type).Count(x => x == ItemType.CardMaridiaBoss); return numKeysanity == numCratieriaBossKeys; } diff --git a/src/Randomizer.SMZ3/Generation/MultiplayerFiller.cs b/src/Randomizer.SMZ3/Generation/MultiplayerFiller.cs index 6ac70f3be..9be75b75e 100644 --- a/src/Randomizer.SMZ3/Generation/MultiplayerFiller.cs +++ b/src/Randomizer.SMZ3/Generation/MultiplayerFiller.cs @@ -119,7 +119,7 @@ private void FillItems(World world, List worlds) var generatedData = generatedLocationData.Single(x => x.Id == location.Id); var itemType = generatedData.Item; var itemWorld = worlds.Single(x => x.Id == generatedData.ItemWorldId); - location.Item = new Item(itemType, itemWorld, isProgression: itemType.IsPossibleProgression(itemWorld.Config.ZeldaKeysanity, itemWorld.Config.MetroidKeysanity)); + location.Item = new Item(itemType, itemWorld, isProgression: itemType.IsPossibleProgression(itemWorld.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity, itemWorld.Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity)); _logger.LogDebug("Fast-filled {Item} at {Location}", generatedData.Item, location.Name); } EnsureLocationsHaveItems(world); diff --git a/src/Randomizer.SMZ3/Generation/PatcherService.cs b/src/Randomizer.SMZ3/Generation/PatcherService.cs index 6dcaa2f1e..6ce093976 100644 --- a/src/Randomizer.SMZ3/Generation/PatcherService.cs +++ b/src/Randomizer.SMZ3/Generation/PatcherService.cs @@ -1,12 +1,10 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; -using Randomizer.Data.Configuration; -using Randomizer.Data.Options; -using Randomizer.Data.WorldData; +using Randomizer.Abstractions; using Randomizer.SMZ3.FileData; using Randomizer.SMZ3.FileData.Patches; +using Randomizer.SMZ3.GameModes; namespace Randomizer.SMZ3.Generation; @@ -17,25 +15,30 @@ public class PatcherService : IPatcherService { private readonly ILogger _logger; private readonly RomPatchFactory _romPatchFactory; + private readonly IGameModeService _gameModeService; - public PatcherService(RomPatchFactory romPatchFactory, ILogger logger) + public PatcherService(RomPatchFactory romPatchFactory, ILogger logger, IGameModeService gameModeService) { _logger = logger; + _gameModeService = gameModeService; _romPatchFactory = romPatchFactory; } public Dictionary GetPatches(GetPatchesRequest data) { - var patches = new List(); + var patches = new Dictionary(); - foreach (var patch in _romPatchFactory.GetPatches()) + foreach (var patch in _romPatchFactory.GetPatches().SelectMany(x => x.GetChanges(data))) { - var updates = patch.GetChanges(data).ToList(); - _logger.LogInformation("Retrieving {Number} updates from {Name}", updates.Count, patch.GetType().Name); - patches.AddRange(updates); + patches[patch.Offset] = patch.Data; } - return patches.ToDictionary(x => x.Offset, x => x.Data); + foreach (var patch in _gameModeService.GetPatches(data.World).SelectMany(x => x.GetChanges(data))) + { + patches[patch.Offset] = patch.Data; + } + + return patches; } } diff --git a/src/Randomizer.SMZ3/Generation/RomGenerationService.cs b/src/Randomizer.SMZ3/Generation/RomGenerationService.cs index 12f5336be..b3adedfe9 100644 --- a/src/Randomizer.SMZ3/Generation/RomGenerationService.cs +++ b/src/Randomizer.SMZ3/Generation/RomGenerationService.cs @@ -68,7 +68,7 @@ public SeedData GeneratePlandoSeed(RandomizerOptions options, PlandoConfig pland { var config = options.ToConfig(); config.PlandoConfig = plandoConfig; - config.KeysanityMode = plandoConfig.KeysanityMode; + config.GameModeConfigs = plandoConfig.GameModeConfigs; config.GanonsTowerCrystalCount = plandoConfig.GanonsTowerCrystalCount; config.GanonCrystalCount = plandoConfig.GanonCrystalCount; config.OpenPyramid = plandoConfig.OpenPyramid; diff --git a/src/Randomizer.SMZ3/Generation/RomTextService.cs b/src/Randomizer.SMZ3/Generation/RomTextService.cs index a13150eec..771129e11 100644 --- a/src/Randomizer.SMZ3/Generation/RomTextService.cs +++ b/src/Randomizer.SMZ3/Generation/RomTextService.cs @@ -175,9 +175,9 @@ private string GetSpoilerLog(RandomizerOptions options, SeedData seed, bool conf var logicOptions = string.Join(',', type.GetProperties().Select(x => $"{x.Name}: {x.GetValue(world.Config.LogicConfig)}")); log.AppendLine($"Logic Options: {logicOptions}"); - if (world.Config.Keysanity) + if (world.Config.GameModeConfigs.KeysanityConfig.KeysanityEnabled) { - log.AppendLine("Keysanity: " + world.Config.KeysanityMode.ToString()); + log.AppendLine("Keysanity: " + world.Config.GameModeConfigs.KeysanityConfig.KeysanityMode); } var gtCrystals = world.Config.GanonsTowerCrystalCount; diff --git a/src/Randomizer.SMZ3/Generation/Smz3GeneratedRomLoader.cs b/src/Randomizer.SMZ3/Generation/Smz3GeneratedRomLoader.cs index 101ecc59a..d0db143c5 100644 --- a/src/Randomizer.SMZ3/Generation/Smz3GeneratedRomLoader.cs +++ b/src/Randomizer.SMZ3/Generation/Smz3GeneratedRomLoader.cs @@ -63,8 +63,8 @@ public List LoadGeneratedRom(GeneratedRom rom) var itemWorld = worlds.First(w => w.Id == locationState.ItemWorldId); location.Item = new Item(locationState.Item, itemWorld, itemState.ItemName, itemMetadata, itemState, - locationState.Item.IsPossibleProgression(itemWorld.Config.ZeldaKeysanity, - itemWorld.Config.MetroidKeysanity)); + locationState.Item.IsPossibleProgression(itemWorld.Config.GameModeConfigs.KeysanityConfig.ZeldaKeysanity, + itemWorld.Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity)); } // Create items for saved state items not in the world diff --git a/src/Randomizer.SMZ3/Generation/Smz3Randomizer.cs b/src/Randomizer.SMZ3/Generation/Smz3Randomizer.cs index d388fb864..2cd6b772c 100644 --- a/src/Randomizer.SMZ3/Generation/Smz3Randomizer.cs +++ b/src/Randomizer.SMZ3/Generation/Smz3Randomizer.cs @@ -5,12 +5,14 @@ using System.Linq; using System.Threading; using Microsoft.Extensions.Logging; +using Randomizer.Abstractions; using Randomizer.Data.Options; using Randomizer.Data.WorldData; using Randomizer.Shared; using Randomizer.Shared.Enums; using Randomizer.SMZ3.Contracts; using Randomizer.SMZ3.FileData; +using Randomizer.SMZ3.GameModes; namespace Randomizer.SMZ3.Generation { @@ -20,14 +22,16 @@ public class Smz3Randomizer : ISeededRandomizer private readonly ILogger _logger; private readonly IGameHintService _hintService; private readonly IPatcherService _patcherService; + private readonly IGameModeService _gameModeService; public Smz3Randomizer(IFiller filler, IWorldAccessor worldAccessor, IGameHintService gameHintGenerator, - ILogger logger, IPatcherService patcherService) + ILogger logger, IPatcherService patcherService, IGameModeService gameModeService) { Filler = filler; _worldAccessor = worldAccessor; _logger = logger; _patcherService = patcherService; + _gameModeService = gameModeService; _hintService = gameHintGenerator; } @@ -82,15 +86,24 @@ public SeedData GenerateSeed(List configs, string? seed, CancellationTok var worlds = new List(); if (primaryConfig.SingleWorld) { + _gameModeService.ModifyConfig(primaryConfig); worlds.Add(new World(primaryConfig, "Player", 0, Guid.NewGuid().ToString("N"))); + _gameModeService.ModifyWorldItemPools(worlds[0]); + worlds[0].ItemPools.FillGaps(); _logger.LogDebug( "Seed: {SeedNumber} | Race: {PrimaryConfigRace} | Keysanity: {PrimaryConfigKeysanityMode} | Item placement: {PrimaryConfigItemPlacementRule}", - seedNumber, primaryConfig.Race, primaryConfig.KeysanityMode, primaryConfig.ItemPlacementRule); + seedNumber, primaryConfig.Race, primaryConfig.GameModeConfigs.KeysanityConfig.KeysanityMode, primaryConfig.ItemPlacementRule); } else { + configs.ForEach(x => _gameModeService.ModifyConfig(x)); worlds.AddRange(configs.OrderBy(x => x.Id).Select(config => new World(config, config.PlayerName, config.Id, config.PlayerGuid, config.IsLocalConfig))); + worlds.ForEach(x => + { + _gameModeService.ModifyWorldItemPools(x); + x.ItemPools.FillGaps(); + }); _logger.LogDebug( "Seed: {SeedNumber} | Race: {PrimaryConfigRace} | World Count: {Count}", seedNumber, primaryConfig.Race, configs.Count); diff --git a/src/Randomizer.SMZ3/Generation/StandardFiller.cs b/src/Randomizer.SMZ3/Generation/StandardFiller.cs index 099f5ab7a..a0dfd65df 100644 --- a/src/Randomizer.SMZ3/Generation/StandardFiller.cs +++ b/src/Randomizer.SMZ3/Generation/StandardFiller.cs @@ -79,7 +79,7 @@ public void Fill(List worlds, Config config, CancellationToken cancellati InitialFillInOwnWorld(dungeon, progression, world, config, startingInventory); - if (worldConfig.ZeldaKeysanity == false) + if (worldConfig.GameModeConfigs.KeysanityConfig.ZeldaKeysanity == false) { _logger.LogDebug("Distributing dungeon items according to logic"); var worldLocations = world.Locations.Empty().Shuffle(Random); @@ -87,7 +87,7 @@ public void Fill(List worlds, Config config, CancellationToken cancellati AssumedFill(dungeon, progression.Concat(keyCards).Concat(assumedInventory).Concat(preferenceItems).ToList(), worldLocations, new[] { world }, cancellationToken); } - if (worldConfig.MetroidKeysanity) + if (worldConfig.GameModeConfigs.KeysanityConfig.MetroidKeysanity) { progressionItems.AddRange(world.ItemPools.Keycards); } diff --git a/src/Randomizer.SMZ3/Playthrough.cs b/src/Randomizer.SMZ3/Playthrough.cs index 2870f63b7..5e6967836 100644 --- a/src/Randomizer.SMZ3/Playthrough.cs +++ b/src/Randomizer.SMZ3/Playthrough.cs @@ -95,7 +95,7 @@ public static IEnumerable GenerateSpheres(IEnumerable allLocat foreach (var world in worlds) { - if (!world.Config.MetroidKeysanity) + if (!world.Config.GameModeConfigs.KeysanityConfig.MetroidKeysanity) { var keycards = world.ItemPools.Keycards; items.AddRange(keycards); @@ -169,7 +169,7 @@ public List> GetPlaythroughText() text.Add($"Inaccessible Item: {location}", $"{location.Item.Name}"); } - foreach (var location in x.Locations.Where(l => IsImportant(l, Config.KeysanityMode))) + foreach (var location in x.Locations.Where(l => IsImportant(l, Config.GameModeConfigs.KeysanityConfig.KeysanityMode))) { if (Config.MultiWorld) text.Add($"{location} ({location.Region.World.Player})", $"{location.Item.Name} ({location.Item.World.Player})"); diff --git a/src/Randomizer.SMZ3/Randomizer.SMZ3.csproj b/src/Randomizer.SMZ3/Randomizer.SMZ3.csproj index a524b0b80..603a9658f 100644 --- a/src/Randomizer.SMZ3/Randomizer.SMZ3.csproj +++ b/src/Randomizer.SMZ3/Randomizer.SMZ3.csproj @@ -42,6 +42,7 @@ + diff --git a/src/Randomizer.SMZ3/RandomizerServiceCollectionExtensions.cs b/src/Randomizer.SMZ3/RandomizerServiceCollectionExtensions.cs index 48235368c..de39fb96b 100644 --- a/src/Randomizer.SMZ3/RandomizerServiceCollectionExtensions.cs +++ b/src/Randomizer.SMZ3/RandomizerServiceCollectionExtensions.cs @@ -4,11 +4,14 @@ using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection.Extensions; +using Randomizer.Abstractions; using Randomizer.Shared.Models; using Randomizer.SMZ3.Contracts; using Randomizer.SMZ3.FileData.Patches; +using Randomizer.SMZ3.GameModes; using Randomizer.SMZ3.Generation; using Randomizer.SMZ3.Infrastructure; +using GameMode = Randomizer.Data.Options.GameMode; namespace Microsoft.Extensions.DependencyInjection { @@ -26,6 +29,8 @@ public static IServiceCollection AddRandomizerServices(this IServiceCollection s services.AddSingleton(); services.AddSingleton(); services.AddTransient(); + services.AddGameModes(); + services.AddSingleton(); return services; } @@ -73,5 +78,18 @@ private static IServiceCollection AddRomPatches(this IServiceCollecti return services; } + + private static IServiceCollection AddGameModes(this IServiceCollection services) + { + var moduleTypes = typeof(TAssembly).Assembly.GetTypes() + .Where(x => x.IsSubclassOf(typeof(GameModeBase))); + + foreach (var moduleType in moduleTypes) + { + services.TryAddEnumerable(ServiceDescriptor.Scoped(typeof(GameModeBase), moduleType)); + } + + return services; + } } } diff --git a/src/Randomizer.Shared/Enums/GameModeType.cs b/src/Randomizer.Shared/Enums/GameModeType.cs new file mode 100644 index 000000000..8639e71e7 --- /dev/null +++ b/src/Randomizer.Shared/Enums/GameModeType.cs @@ -0,0 +1,7 @@ +namespace Randomizer.Shared.Enums; + +public enum GameModeType +{ + Default, + Keysanity +} diff --git a/src/Randomizer.Shared/ManualAttribute.cs b/src/Randomizer.Shared/ManualAttribute.cs new file mode 100644 index 000000000..35e76e048 --- /dev/null +++ b/src/Randomizer.Shared/ManualAttribute.cs @@ -0,0 +1,10 @@ +using System; +using Randomizer.Shared.Enums; + +namespace Randomizer.Shared; + +[AttributeUsage(AttributeTargets.Class, + Inherited = false, AllowMultiple = false)] +public class ManualAttribute : Attribute +{ +} diff --git a/tests/Randomizer.SMZ3.Tests/LogicTests/LogicConfigTests.cs b/tests/Randomizer.SMZ3.Tests/LogicTests/LogicConfigTests.cs index 579a63c65..47fa35082 100644 --- a/tests/Randomizer.SMZ3.Tests/LogicTests/LogicConfigTests.cs +++ b/tests/Randomizer.SMZ3.Tests/LogicTests/LogicConfigTests.cs @@ -414,7 +414,7 @@ public void TestKholdstareNeedsCaneOfSomaria() [Fact] public void TestEasyBlueBrinstarTop() { - var config = new Config { LogicConfig = { EasyBlueBrinstarTop = false, }, KeysanityMode = KeysanityMode.None }; + var config = new Config { LogicConfig = { EasyBlueBrinstarTop = false, }, GameModeConfigs = new() }; var tempWorld = new World(config, "", 0, ""); var progression = new Progression(new[] { ItemType.Morph, ItemType.PowerBomb, ItemType.CardBrinstarL1 }, new List(), new List()); diff --git a/tests/Randomizer.SMZ3.Tests/LogicTests/RandomizerTests.cs b/tests/Randomizer.SMZ3.Tests/LogicTests/RandomizerTests.cs index 2a7732daf..bfeaa7797 100644 --- a/tests/Randomizer.SMZ3.Tests/LogicTests/RandomizerTests.cs +++ b/tests/Randomizer.SMZ3.Tests/LogicTests/RandomizerTests.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Randomizer.Abstractions; using Randomizer.Data.Configuration; using Randomizer.Data.Logic; using Randomizer.Data.Options; @@ -14,6 +15,7 @@ using Randomizer.Shared; using Randomizer.SMZ3.Contracts; using Randomizer.SMZ3.FileData.Patches; +using Randomizer.SMZ3.GameModes; using Randomizer.SMZ3.Generation; using Randomizer.SMZ3.Infrastructure; @@ -176,6 +178,7 @@ private static Smz3Randomizer GetRandomizer() .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddConfigs() .BuildServiceProvider(); @@ -183,7 +186,8 @@ private static Smz3Randomizer GetRandomizer() return new Smz3Randomizer(filler, new WorldAccessor(), serviceProvider.GetRequiredService(), GetLogger(), - serviceProvider.GetRequiredService()); + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService()); } } }