diff --git a/src/TrackerCouncil.Smz3.Abstractions/TrackerBase.cs b/src/TrackerCouncil.Smz3.Abstractions/TrackerBase.cs index c66910f5d..dfaf28686 100644 --- a/src/TrackerCouncil.Smz3.Abstractions/TrackerBase.cs +++ b/src/TrackerCouncil.Smz3.Abstractions/TrackerBase.cs @@ -75,7 +75,7 @@ public abstract class TrackerBase : IDisposable /// /// Gets the configured responses. /// - public ResponseConfig Responses { get; protected init; } = null!; + public ResponseConfig Responses { get; protected set; } = null!; /// /// Gets a collection of basic requests and responses. @@ -370,5 +370,11 @@ protected virtual void OnVoiceRecognitionEnabledChanged() VoiceRecognitionEnabledChanged?.Invoke(this, EventArgs.Empty); } + /// + /// Updates the responses to reflect the selected tracker image pack + /// + /// The name of the pack + public abstract void SetImagePack(string? packName); + public abstract void Dispose(); } diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/TrackerProfileConfig.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/TrackerProfileConfig.cs new file mode 100644 index 000000000..ca0293b05 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/TrackerProfileConfig.cs @@ -0,0 +1,7 @@ +namespace TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; + +public class TrackerProfileConfig +{ + public bool UseAltVoice { get; set; } + public ResponseConfig? ResponseConfig { get; set; } +} diff --git a/src/TrackerCouncil.Smz3.Data/Options/GeneralOptions.cs b/src/TrackerCouncil.Smz3.Data/Options/GeneralOptions.cs index 7d4e27c09..c58a8d12a 100644 --- a/src/TrackerCouncil.Smz3.Data/Options/GeneralOptions.cs +++ b/src/TrackerCouncil.Smz3.Data/Options/GeneralOptions.cs @@ -265,6 +265,7 @@ public bool Validate() AutoMapUpdateBehavior = AutoMapUpdateBehavior ?? Options.AutoMapUpdateBehavior.Disabled, VoiceFrequency = TrackerVoiceFrequency, TrackerProfiles = SelectedProfiles, + TrackerImagePackName = TrackerSpeechImagePack, UndoExpirationTime = UndoExpirationTime, TrackDisplayFormat = TrackDisplayFormat, MsuTrackOutputPath = MsuTrackOutputPath, diff --git a/src/TrackerCouncil.Smz3.Data/Options/TrackerOptions.cs b/src/TrackerCouncil.Smz3.Data/Options/TrackerOptions.cs index 1a91ea75a..228bdf890 100644 --- a/src/TrackerCouncil.Smz3.Data/Options/TrackerOptions.cs +++ b/src/TrackerCouncil.Smz3.Data/Options/TrackerOptions.cs @@ -100,6 +100,11 @@ public record TrackerOptions /// public ICollection TrackerProfiles { get; set; } = new List() { "Sassy" }; + /// + /// Gets the name of the tracker images that the user selected + /// + public string? TrackerImagePackName { get; set; } + /// /// The output style for the current song (Deprecated) /// diff --git a/src/TrackerCouncil.Smz3.Data/Options/TrackerSpeechReactionImages.cs b/src/TrackerCouncil.Smz3.Data/Options/TrackerSpeechReactionImages.cs index fe270dbe3..509d5d775 100644 --- a/src/TrackerCouncil.Smz3.Data/Options/TrackerSpeechReactionImages.cs +++ b/src/TrackerCouncil.Smz3.Data/Options/TrackerSpeechReactionImages.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; namespace TrackerCouncil.Smz3.Data.Options; @@ -38,6 +39,8 @@ public class TrackerSpeechImagePack /// public required Dictionary Reactions { get; set; } + public required TrackerProfileConfig? ProfileConfig { get; set; } + /// /// Gets the reaction images for a given reaction type. Will return the default reaction type if not specified /// or the requested reaction type is not present in this pack. diff --git a/src/TrackerCouncil.Smz3.Data/Services/TrackerSpriteService.cs b/src/TrackerCouncil.Smz3.Data/Services/TrackerSpriteService.cs index 965c150eb..5a9dae786 100644 --- a/src/TrackerCouncil.Smz3.Data/Services/TrackerSpriteService.cs +++ b/src/TrackerCouncil.Smz3.Data/Services/TrackerSpriteService.cs @@ -1,8 +1,12 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Extensions.Logging; +using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; using TrackerCouncil.Smz3.Data.Options; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; namespace TrackerCouncil.Smz3.Data.Services; @@ -10,7 +14,7 @@ namespace TrackerCouncil.Smz3.Data.Services; /// Service for loading tracker speech sprites /// /// -public class TrackerSpriteService(OptionsFactory optionsFactory) +public class TrackerSpriteService(OptionsFactory optionsFactory, ILogger logger) { private List _packs = []; @@ -82,6 +86,9 @@ private void LoadPack(string folder, bool isUserPack) packName += " (Custom)"; } + var config = GetConfig(Path.Combine(folder, "config.yml")) + ?? GetConfig(Path.Combine(folder, "config.yaml")); + _packs.Add(new TrackerSpeechImagePack { Name = packName, @@ -90,7 +97,31 @@ private void LoadPack(string folder, bool isUserPack) IdleImage = Path.Combine(folder, "default_idle.png"), TalkingImage = Path.Combine(folder, "default_talk.png"), }, - Reactions = reactions + Reactions = reactions, + ProfileConfig = config, }); } + + private TrackerProfileConfig? GetConfig(string path) + { + if (!File.Exists(path)) + { + return null; + } + + var yml = File.ReadAllText(path); + var deserializer = new DeserializerBuilder() + .WithNamingConvention(PascalCaseNamingConvention.Instance) + .IgnoreUnmatchedProperties() + .Build(); + try + { + return deserializer.Deserialize(yml); + } + catch (Exception e) + { + logger.LogError(e, "Unable to parse tracker profile config {Path}", path); + return null; + } + } } diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/ICommunicator.cs b/src/TrackerCouncil.Smz3.Tracking/Services/ICommunicator.cs index 7b773343d..54dea9c92 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/ICommunicator.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/ICommunicator.cs @@ -42,7 +42,7 @@ public void Abort() { } /// When overridden in an implementing class, uses an alternate voice /// when communicating with the player. /// - public void UseAlternateVoice() { } + public void UseAlternateVoice(bool useAlt = true) { } /// /// When overridden in an implementing class, increases the diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs b/src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs index 1c85d211c..6591b4920 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs @@ -72,14 +72,14 @@ public TextToSpeechCommunicator(TrackerOptionsAccessor trackerOptionsAccessor, I /// /// Selects a different text-to-speech voice. /// - public void UseAlternateVoice() + public void UseAlternateVoice(bool useAlt = true) { if (!OperatingSystem.IsWindows()) { return; } - _tts.SelectVoiceByHints(VoiceGender.Male); + _tts.SelectVoiceByHints(useAlt ? VoiceGender.Male : VoiceGender.Female); } /// diff --git a/src/TrackerCouncil.Smz3.Tracking/Tracker.cs b/src/TrackerCouncil.Smz3.Tracking/Tracker.cs index a60222bf2..1ccfb4100 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Tracker.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Tracker.cs @@ -47,10 +47,13 @@ public sealed class Tracker : TrackerBase, IDisposable private readonly ITrackerStateService _stateService; private readonly ITrackerTimerService _timerService; private readonly ISpeechRecognitionService _recognizer; + private readonly TrackerSpriteService _trackerSpriteService; + private readonly HashSet _saidLines = new(); + private bool _disposed; private string? _lastSpokenText; - private readonly bool _alternateTracker; - private readonly HashSet _saidLines = new(); + private string? _previousImagePackName; + private ResponseConfig _defaultResponseConfig; /// /// Initializes a new instance of the class. @@ -71,6 +74,7 @@ public sealed class Tracker : TrackerBase, IDisposable /// /// /// + /// public Tracker(IWorldAccessor worldAccessor, TrackerModuleFactory moduleFactory, IChatClient chatClient, @@ -82,7 +86,8 @@ public Tracker(IWorldAccessor worldAccessor, Configs configs, ITrackerStateService stateService, ITrackerTimerService timerService, - IServiceProvider serviceProvider) + IServiceProvider serviceProvider, + TrackerSpriteService trackerSpriteService) { if (trackerOptions.Options == null) throw new InvalidOperationException("Tracker options have not yet been activated."); @@ -98,10 +103,12 @@ public Tracker(IWorldAccessor worldAccessor, _communicator = communicator; _stateService = stateService; _timerService = timerService; + _trackerSpriteService = trackerSpriteService; // Initialize the tracker configuration Configs = configs; - Responses = configs.Responses; + _defaultResponseConfig = Responses = configs.Responses; + SetImagePack(trackerOptions.Options.TrackerImagePackName); Requests = configs.Requests; PlayerProgressionService.ResetProgression(); @@ -119,14 +126,6 @@ public Tracker(IWorldAccessor worldAccessor, _idleTimers = new(); } - - // Initialize the text-to-speech - if (s_random.NextDouble() <= 0.01) - { - _alternateTracker = true; - _communicator.UseAlternateVoice(); - } - // Initialize the speech recognition engine if (_trackerOptions.Options.SpeechRecognitionMode == SpeechRecognitionMode.Disabled || !OperatingSystem.IsWindows()) @@ -217,6 +216,30 @@ public override bool Load(GeneratedRom rom, string romPath) return false; } + public override void SetImagePack(string? packName) + { + if (packName == _previousImagePackName) + { + return; + } + + _previousImagePackName = packName; + var profileConfig = _trackerSpriteService.GetPack(packName).ProfileConfig; + _communicator.UseAlternateVoice(profileConfig?.UseAltVoice ?? false); + + if (profileConfig?.ResponseConfig == null) + { + Responses = _defaultResponseConfig; + return; + } + + var newResponseConfig = ResponseConfig.Default(); + IMergeable mergeableConfig = newResponseConfig; + mergeableConfig.MergeFrom(_defaultResponseConfig); + mergeableConfig.MergeFrom(profileConfig.ResponseConfig); + Responses = newResponseConfig; + } + private void LoadServices(IServiceProvider serviceProvider) { var interfaceNamespace = typeof(TrackerBase).Namespace; @@ -319,7 +342,7 @@ public override bool TryStartTracking() loadError = true; } - Say(response: _alternateTracker ? Responses.StartingTrackingAlternate : Responses.StartedTracking); + Say(response: Responses.StartedTracking); RestartIdleTimers(); return !loadError; } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoModeModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoModeModule.cs index 83dd69b57..06b2839fb 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoModeModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoModeModule.cs @@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; using TrackerCouncil.Smz3.Abstractions; -using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; using TrackerCouncil.Smz3.Tracking.Services; namespace TrackerCouncil.Smz3.Tracking.VoiceCommands; @@ -12,8 +11,6 @@ namespace TrackerCouncil.Smz3.Tracking.VoiceCommands; /// public class GoModeModule : TrackerModule { - private ResponseConfig _responseConfig; - /// /// Initializes a new instance of the class. /// @@ -21,11 +18,9 @@ public class GoModeModule : TrackerModule /// Service to get item information /// Service to get world information /// Used to log information. - /// - public GoModeModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger, ResponseConfig responseConfig) + public GoModeModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger) : base(tracker, playerProgressionService, worldQueryService, logger) { - _responseConfig = responseConfig; } private GrammarBuilder GetGoModeRule(List prompts) @@ -38,12 +33,12 @@ private GrammarBuilder GetGoModeRule(List prompts) [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public override void AddCommands() { - if (_responseConfig.GoModePrompts == null) + if (TrackerBase.Responses.GoModePrompts == null) { return; } - AddCommand("Toggle Go Mode", GetGoModeRule(_responseConfig.GoModePrompts), (result) => + AddCommand("Toggle Go Mode", GetGoModeRule(TrackerBase.Responses.GoModePrompts), (result) => { TrackerBase.ModeTracker.ToggleGoMode(result.Confidence); }); diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoalModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoalModule.cs index 343254eed..4f13e93c6 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoalModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoalModule.cs @@ -14,7 +14,6 @@ namespace TrackerCouncil.Smz3.Tracking.VoiceCommands; /// public class GoalModule : TrackerModule { - private ResponseConfig _responseConfig; private const string ItemCountKey = "ItemCount"; /// @@ -24,11 +23,9 @@ public class GoalModule : TrackerModule /// Service to get item information /// Service to get world information /// Used to log information. - /// - public GoalModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger, ResponseConfig responseConfig) + public GoalModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger) : base(tracker, playerProgressionService, worldQueryService, logger) { - _responseConfig = responseConfig; } [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]