diff --git a/src/Randomizer.App/Controls/MultiRomListPanel.xaml.cs b/src/Randomizer.App/Controls/MultiRomListPanel.xaml.cs index defcbba6e..febc79738 100644 --- a/src/Randomizer.App/Controls/MultiRomListPanel.xaml.cs +++ b/src/Randomizer.App/Controls/MultiRomListPanel.xaml.cs @@ -11,6 +11,7 @@ using Randomizer.Data.Services; using Randomizer.Shared.Models; using Randomizer.SMZ3.Generation; +using Randomizer.SMZ3.Infrastructure; namespace Randomizer.App.Controls { @@ -23,7 +24,8 @@ public MultiRomListPanel(IServiceProvider serviceProvider, OptionsFactory optionsFactory, ILogger logger, RomGenerationService romGenerationService, - IGameDbService gameDbService) : base(serviceProvider, optionsFactory, logger, romGenerationService, gameDbService) + IGameDbService gameDbService, + RomLauncherService romLauncherService) : base(serviceProvider, optionsFactory, logger, romGenerationService, gameDbService, romLauncherService) { Model = new MultiplayerGamesViewModel(); DataContext = Model; diff --git a/src/Randomizer.App/Controls/RomListPanel.cs b/src/Randomizer.App/Controls/RomListPanel.cs index 38ce02831..01d767b15 100644 --- a/src/Randomizer.App/Controls/RomListPanel.cs +++ b/src/Randomizer.App/Controls/RomListPanel.cs @@ -12,6 +12,7 @@ using Randomizer.Data.Services; using Randomizer.Shared.Models; using Randomizer.SMZ3.Generation; +using Randomizer.SMZ3.Infrastructure; using Randomizer.SMZ3.Tracking; namespace Randomizer.App.Controls @@ -19,19 +20,22 @@ namespace Randomizer.App.Controls public abstract class RomListPanel : UserControl { private TrackerWindow? _trackerWindow; + private RomLauncherService _romLauncherService; public RomListPanel(IServiceProvider serviceProvider, OptionsFactory optionsFactory, ILogger logger, RomGenerationService romGenerationService, - IGameDbService gameDbService) + IGameDbService gameDbService, + RomLauncherService romLauncherService) { ServiceProvider = serviceProvider; Logger = logger; RomGenerationService = romGenerationService; GameDbService = gameDbService; Options = optionsFactory.Create(); + _romLauncherService = romLauncherService; CheckSpeechRecognition(); } @@ -170,11 +174,8 @@ public void LaunchRom(GeneratedRom rom) { try { - Process.Start(new ProcessStartInfo - { - FileName = path, - UseShellExecute = true - }); + _romLauncherService.LaunchRom(path, Options.GeneralOptions.LaunchApplication, + Options.GeneralOptions.LaunchArguments); } catch (Win32Exception e) { diff --git a/src/Randomizer.App/Controls/SoloRomListPanel.xaml.cs b/src/Randomizer.App/Controls/SoloRomListPanel.xaml.cs index b237cc3f7..1596b8e32 100644 --- a/src/Randomizer.App/Controls/SoloRomListPanel.xaml.cs +++ b/src/Randomizer.App/Controls/SoloRomListPanel.xaml.cs @@ -12,6 +12,7 @@ using Randomizer.Shared.Models; using Randomizer.SMZ3.Contracts; using Randomizer.SMZ3.Generation; +using Randomizer.SMZ3.Infrastructure; using YamlDotNet.Core; namespace Randomizer.App.Controls @@ -26,7 +27,8 @@ public SoloRomListPanel(IServiceProvider serviceProvider, OptionsFactory optionsFactory, ILogger logger, RomGenerationService romGenerationService, - IGameDbService gameDbService) : base(serviceProvider, optionsFactory, logger, romGenerationService, gameDbService) + IGameDbService gameDbService, + RomLauncherService romLauncherService) : base(serviceProvider, optionsFactory, logger, romGenerationService, gameDbService, romLauncherService) { Model = new GeneratedRomsViewModel(); DataContext = Model; diff --git a/src/Randomizer.App/Windows/OptionsWindow.xaml b/src/Randomizer.App/Windows/OptionsWindow.xaml index 703dbf029..6af2e3ae2 100644 --- a/src/Randomizer.App/Windows/OptionsWindow.xaml +++ b/src/Randomizer.App/Windows/OptionsWindow.xaml @@ -185,6 +185,28 @@ + + + + + + + + + + + + + + + + + + + + @@ -246,16 +268,23 @@ + + + + - - + + + - + diff --git a/src/Randomizer.CrossPlatform/Program.cs b/src/Randomizer.CrossPlatform/Program.cs index 31b5ca080..a79aa02db 100644 --- a/src/Randomizer.CrossPlatform/Program.cs +++ b/src/Randomizer.CrossPlatform/Program.cs @@ -6,6 +6,7 @@ using Randomizer.Data.Options; using Randomizer.Data.Services; using Randomizer.SMZ3.Generation; +using Randomizer.SMZ3.Infrastructure; using Serilog; namespace Randomizer.CrossPlatform; @@ -34,6 +35,7 @@ public static void Main(string[] args) var optionsFile = new FileInfo("randomizer-options.yml"); var randomizerOptions = s_services.GetRequiredService().LoadFromFile(optionsFile.FullName, optionsFile.FullName, true); randomizerOptions.Save(); + var launcher = s_services.GetRequiredService(); if (!ValidateRandomizerOptions(randomizerOptions)) { @@ -77,7 +79,8 @@ public static void Main(string[] args) { var romPath = Path.Combine(randomizerOptions.RomOutputPath, results.Rom!.RomPath); Console.WriteLine($"Rom generated successfully: {romPath}"); - Launch(romPath, randomizerOptions); + launcher.LaunchRom(romPath, randomizerOptions.GeneralOptions.LaunchApplication, + randomizerOptions.GeneralOptions.LaunchArguments); _ = s_services.GetRequiredService().StartTracking(results.Rom, romPath); } else @@ -99,7 +102,8 @@ public static void Main(string[] args) { var selectedRom = roms[result.Value.Item1]; var romPath = Path.Combine(randomizerOptions.RomOutputPath, selectedRom.RomPath); - Launch(romPath, randomizerOptions); + launcher.LaunchRom(romPath, randomizerOptions.GeneralOptions.LaunchApplication, + randomizerOptions.GeneralOptions.LaunchArguments); _ = s_services.GetRequiredService().StartTracking(selectedRom, romPath); } @@ -182,53 +186,6 @@ private static (int, string)? DisplayMenu(string prompt, List options) } - private static void Launch(string romPath, RandomizerOptions options) - { - if (!File.Exists(romPath)) - { - Log.Error("No test rom found at {Path}", romPath); - return; - } - - var launchApplication = options.GeneralOptions.LaunchApplication; - var launchArguments = ""; - if (string.IsNullOrEmpty(launchApplication)) - { - launchApplication = romPath; - } - else - { - if (string.IsNullOrEmpty(options.GeneralOptions.LaunchArguments)) - { - launchArguments = $"\"{romPath}\""; - } - else if (options.GeneralOptions.LaunchArguments.Contains("%rom%")) - { - launchArguments = options.GeneralOptions.LaunchArguments.Replace("%rom%", $"{romPath}"); - } - else - { - launchArguments = $"{options.GeneralOptions.LaunchArguments} \"{romPath}\""; - } - } - - try - { - Console.WriteLine($"Executing {launchApplication} {launchArguments}"); - Log.Information("Executing {FileName} {Arguments}", launchApplication, launchArguments); - Process.Start(new ProcessStartInfo - { - FileName = launchApplication, - Arguments = launchArguments, - UseShellExecute = true, - }); - } - catch (Exception e) - { - Log.Error(e, "Unable to launch rom"); - } - } - private static void InitializeMsuRandomizer(IMsuRandomizerInitializationService msuRandomizerInitializationService) { var settingsStream = Assembly.GetExecutingAssembly() diff --git a/src/Randomizer.Data/Options/GeneralOptions.cs b/src/Randomizer.Data/Options/GeneralOptions.cs index fa99bfb34..7c79eba0e 100644 --- a/src/Randomizer.Data/Options/GeneralOptions.cs +++ b/src/Randomizer.Data/Options/GeneralOptions.cs @@ -177,7 +177,7 @@ public string? TwitchId /// /// Automatically tracks the map and other "hey tracker, look at this" events when viewing /// - public bool AutoSaveLookAtEvents { get; set; } + public bool AutoSaveLookAtEvents { get; set; } = true; public event PropertyChangedEventHandler? PropertyChanged; diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMap.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMap.cs index 2fa5d26d1..3d9044b95 100644 --- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMap.cs +++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMap.cs @@ -21,6 +21,8 @@ public class ViewedMap : IZeldaStateCheck { private Tracker? _tracker; private readonly IWorldAccessor _worldAccessor; + private bool _lightWorldUpdated; + private bool _darkWorldUpdated; public ViewedMap(IWorldAccessor worldAccessor, IItemService itemService) { @@ -41,7 +43,7 @@ public ViewedMap(IWorldAccessor worldAccessor, IItemService itemService) /// True if the check was identified, false otherwise public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState) { - if (tracker.AutoTracker == null) return false; + if (tracker.AutoTracker == null || (_lightWorldUpdated && _darkWorldUpdated)) return false; if (currentState.State == 14 && currentState.Substate == 7 && currentState.ReadUInt8(0xE0) == 0x80) { @@ -49,7 +51,7 @@ public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, Au var currentRegion = tracker.World.Regions .OfType() .FirstOrDefault(x => x.StartingRooms != null && x.StartingRooms.Contains(currentState.OverworldScreen) && x.IsOverworld); - if (currentRegion is LightWorldNorthWest or LightWorldNorthEast or LightWorldSouth or LightWorldDeathMountainEast or LightWorldDeathMountainWest) + if (currentRegion is LightWorldNorthWest or LightWorldNorthEast or LightWorldSouth or LightWorldDeathMountainEast or LightWorldDeathMountainWest && !_lightWorldUpdated) { tracker.AutoTracker.LatestViewAction = new AutoTrackerViewedAction(UpdateLightWorldRewards); if (tracker.Options.AutoSaveLookAtEvents) @@ -57,7 +59,7 @@ public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, Au tracker.AutoTracker.LatestViewAction.Invoke(); } } - else if (currentRegion is DarkWorldNorthWest or DarkWorldNorthEast or DarkWorldSouth or DarkWorldMire or DarkWorldDeathMountainEast or DarkWorldDeathMountainWest) + else if (currentRegion is DarkWorldNorthWest or DarkWorldNorthEast or DarkWorldSouth or DarkWorldMire or DarkWorldDeathMountainEast or DarkWorldDeathMountainWest && !_darkWorldUpdated) { tracker.AutoTracker.LatestViewAction = new AutoTrackerViewedAction(UpdateDarkWorldRewards); if (tracker.Options.AutoSaveLookAtEvents) @@ -76,7 +78,7 @@ public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, Au /// private void UpdateLightWorldRewards() { - if (_tracker == null) return; + if (_tracker == null || _lightWorldUpdated) return; var rewards = new List(); var dungeons = new (Region Region, ItemType Map)[] { @@ -107,6 +109,14 @@ private void UpdateLightWorldRewards() { _tracker.Say(x => x.AutoTracker.LookedAtNothing); } + + // If all dungeons are marked, save the light world as updated + if (dungeons.Select(x => x.Region as IDungeon).Count(x => x?.DungeonState.MarkedReward != null) >= + dungeons.Length) + { + _lightWorldUpdated = true; + } + } /// @@ -114,7 +124,7 @@ private void UpdateLightWorldRewards() /// protected void UpdateDarkWorldRewards() { - if (_tracker == null) return; + if (_tracker == null || _darkWorldUpdated) return; var rewards = new List(); var dungeons = new (Region Region, ItemType Map)[] { @@ -153,6 +163,13 @@ protected void UpdateDarkWorldRewards() _tracker.Say(x => x.AutoTracker.LookedAtNothing); } + // If all dungeons are marked, save the light world as updated + if (dungeons.Select(x => x.Region as IDungeon).Count(x => x?.DungeonState.MarkedReward != null) >= + dungeons.Length) + { + _darkWorldUpdated = true; + } + } } } diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMedallion.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMedallion.cs index 132d9e5ca..139cfd58a 100644 --- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMedallion.cs +++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMedallion.cs @@ -11,6 +11,8 @@ public class ViewedMedallion : IZeldaStateCheck { private Tracker? _tracker; private readonly IWorldAccessor _worldAccessor; + private bool _mireUpdated; + private bool _turtleRockUpdated; public ViewedMedallion(IWorldAccessor worldAccessor, IItemService itemService) { @@ -31,14 +33,14 @@ public ViewedMedallion(IWorldAccessor worldAccessor, IItemService itemService) /// True if the check was identified, false otherwise public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState) { - if (tracker.AutoTracker == null || tracker.AutoTracker.LatestViewAction?.IsValid == true) return false; + if (tracker.AutoTracker == null || tracker.AutoTracker.LatestViewAction?.IsValid == true || (_mireUpdated && _turtleRockUpdated)) return false; _tracker = tracker; var x = currentState.LinkX; var y = currentState.LinkY; - if (currentState.OverworldScreen == 112 && x is >= 172 and <= 438 && y is >= 3200 and <= 3432) + if (!_mireUpdated && currentState.OverworldScreen == 112 && x is >= 172 and <= 438 && y is >= 3200 and <= 3432) { tracker.AutoTracker.LatestViewAction = new AutoTrackerViewedAction(MarkMiseryMireMedallion); if (tracker.Options.AutoSaveLookAtEvents) @@ -47,7 +49,7 @@ public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, Au } return true; } - else if (currentState.OverworldScreen == 71 && x is >= 3708 and <= 4016 && y is >= 128 and <= 368) + else if (!_turtleRockUpdated && currentState.OverworldScreen == 71 && x is >= 3708 and <= 4016 && y is >= 128 and <= 368) { tracker.AutoTracker.LatestViewAction = new AutoTrackerViewedAction(MarkTurtleRockMedallion); if (tracker.Options.AutoSaveLookAtEvents) @@ -62,16 +64,18 @@ public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, Au private void MarkMiseryMireMedallion() { - if (_tracker == null) return; + if (_tracker == null || _mireUpdated) return; var dungeon = _tracker.World.MiseryMire; _tracker.SetDungeonRequirement(dungeon, dungeon.DungeonState.RequiredMedallion); + _mireUpdated = true; } private void MarkTurtleRockMedallion() { - if (_tracker == null) return; + if (_tracker == null || _turtleRockUpdated) return; var dungeon = _tracker.World.TurtleRock; _tracker.SetDungeonRequirement(dungeon, dungeon.DungeonState.RequiredMedallion); + _turtleRockUpdated = true; } } diff --git a/src/Randomizer.SMZ3/Infrastructure/RomLauncherService.cs b/src/Randomizer.SMZ3/Infrastructure/RomLauncherService.cs new file mode 100644 index 000000000..291d96d64 --- /dev/null +++ b/src/Randomizer.SMZ3/Infrastructure/RomLauncherService.cs @@ -0,0 +1,58 @@ +using System; +using System.Diagnostics; +using System.IO; +using Randomizer.Data.Options; +using Randomizer.Shared.Models; + +namespace Randomizer.SMZ3.Infrastructure; + +public class RomLauncherService +{ + private readonly RandomizerOptions _options; + + public RomLauncherService(OptionsFactory optionsFactory) + { + _options = optionsFactory.Create(); + } + + public Process? LaunchRom(GeneratedRom rom) + { + var romPath = Path.Combine(_options.RomOutputPath, rom.RomPath); + return LaunchRom(romPath, _options.GeneralOptions.LaunchApplication, _options.GeneralOptions.LaunchArguments); + } + + public Process? LaunchRom(string romPath, string? launchApplication, string? launchArguments) + { + if (!File.Exists(romPath)) + { + throw new FileNotFoundException($"{romPath} not found"); + } + + if (string.IsNullOrEmpty(launchApplication)) + { + launchApplication = romPath; + } + else + { + if (string.IsNullOrEmpty(_options.GeneralOptions.LaunchArguments)) + { + launchArguments = $"\"{romPath}\""; + } + else if (_options.GeneralOptions.LaunchArguments.Contains("%rom%")) + { + launchArguments = _options.GeneralOptions.LaunchArguments.Replace("%rom%", $"{romPath}"); + } + else + { + launchArguments = $"{_options.GeneralOptions.LaunchArguments} \"{romPath}\""; + } + } + + return Process.Start(new ProcessStartInfo + { + FileName = launchApplication, + Arguments = launchArguments, + UseShellExecute = true, + }); + } +} diff --git a/src/Randomizer.SMZ3/RandomizerServiceCollectionExtensions.cs b/src/Randomizer.SMZ3/RandomizerServiceCollectionExtensions.cs index 93141ddd4..48235368c 100644 --- a/src/Randomizer.SMZ3/RandomizerServiceCollectionExtensions.cs +++ b/src/Randomizer.SMZ3/RandomizerServiceCollectionExtensions.cs @@ -25,6 +25,7 @@ public static IServiceCollection AddRandomizerServices(this IServiceCollection s services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddTransient(); return services; }