diff --git a/src/TrackerCouncil.Smz3.Abstractions/TrackerBase.cs b/src/TrackerCouncil.Smz3.Abstractions/TrackerBase.cs index dfaf28686..11c32cb2d 100644 --- a/src/TrackerCouncil.Smz3.Abstractions/TrackerBase.cs +++ b/src/TrackerCouncil.Smz3.Abstractions/TrackerBase.cs @@ -82,11 +82,6 @@ public abstract class TrackerBase : IDisposable /// public IReadOnlyCollection Requests { get; protected init; } = null!; - /// - /// Metadata configs - /// - public Configs Configs { get; protected init; } = null!; - /// /// Gets a dictionary containing the rules and the various speech /// recognition syntaxes. diff --git a/src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs b/src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs index 852c74345..72fc9113d 100644 --- a/src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs +++ b/src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs @@ -23,7 +23,6 @@ public static class Program private static readonly List<(Type, string)> s_generationTypes = new() { (typeof(BossConfig), "bosses.json"), - (typeof(DungeonConfig), "dungeons.json"), (typeof(GameLinesConfig), "game.json"), (typeof(HintTileConfig), "hint_tiles.json"), (typeof(ItemConfig), "items.json"), @@ -139,13 +138,6 @@ private static void CreateTemplates(string outputPath) var exampleBossConfig = BossConfig.Example(); WriteTemplate(templatePath, "bosses", templateBossConfig, exampleBossConfig); - // Dungeon Template - var dungeonConfig = configProvider.GetDungeonConfig(new List(), null); - var templateDungeonConfig = new DungeonConfig(); - templateDungeonConfig.AddRange(dungeonConfig.Select(dungeon => new DungeonInfo() { Dungeon = dungeon.Dungeon, Type = templateDungeonConfig.GetType() })); - var exampleDungeonConfig = DungeonConfig.Example(); - WriteTemplate(templatePath, "dungeons", templateDungeonConfig, exampleDungeonConfig); - // Item Template var itemConfig = configProvider.GetItemConfig(new List(), null); var templateItemConfig = new ItemConfig(); diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/DungeonConfig.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/DungeonConfig.cs deleted file mode 100644 index c943d093b..000000000 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/DungeonConfig.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; -using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; -using static TrackerCouncil.Smz3.Data.Configuration.ConfigTypes.SchrodingersString; - -namespace TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; - -/// -/// Config file for additional dungeon information -/// -[Description("Config file for the various Zelda dungeons with collectable treasure in them")] -public class DungeonConfig : List, IMergeable, IConfigFile -{ - /// - /// Constructor - /// - public DungeonConfig() : base() - { - } - - /// - /// Returns default dungeon information - /// - /// - public static DungeonConfig Default() - { - return new DungeonConfig - { - new() - { - Dungeon = "Eastern Palace", - Type = typeof(EasternPalace), - }, - new() - { - Dungeon = "Desert Palace", - Type = typeof(DesertPalace), - }, - new() - { - Dungeon = "Tower of Hera", - Type = typeof(TowerOfHera), - }, - new() - { - Dungeon = "Palace of Darkness", - Type = typeof(PalaceOfDarkness), - }, - new() - { - Dungeon = "Swamp Palace", - Type = typeof(SwampPalace), - }, - new() - { - Dungeon = "Skull Woods", - Type = typeof(SkullWoods), - }, - new() - { - Dungeon = "Thieves' Town", - Type = typeof(ThievesTown), - }, - new() - { - Dungeon = "Ice Palace", - Type = typeof(IcePalace), - }, - new() - { - Dungeon = "Misery Mire", - Type = typeof(MiseryMire), - }, - new() - { - Dungeon = "Turtle Rock", - Type = typeof(TurtleRock), - }, - new() - { - Dungeon = "Ganon's Tower", - Type = typeof(GanonsTower), - }, - new() - { - Dungeon = "Hyrule Castle", - Type = typeof(HyruleCastle), - }, - new() - { - Dungeon = "Castle Tower", - Type = typeof(CastleTower), - }, - }; - } - - public static object Example() - { - return new DungeonConfig() - { - new() - { - Dungeon = "Palace of Darkness", - Name = new("Palace of Darkness", new Possibility("Dark Palace", 0.1)), - Boss = new ("Helmasaur King", new Possibility("The Helmasaur King", 0.1)), - }, - }; - } -} diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/TrackerProfileConfig.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/TrackerProfileConfig.cs index ca0293b05..98ca6194a 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/TrackerProfileConfig.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/TrackerProfileConfig.cs @@ -4,4 +4,11 @@ public class TrackerProfileConfig { public bool UseAltVoice { get; set; } public ResponseConfig? ResponseConfig { get; set; } + public BossConfig? BossConfig { get; set; } + public ItemConfig? ItemConfig { get; set; } + public LocationConfig? LocationConfig { get; set; } + public RegionConfig? RegionConfig { get; set; } + public RequestConfig? RequestConfig { get; set; } + public RewardConfig? RewardConfig { get; set; } + public RoomConfig? RoomConfig { get; set; } } diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigProvider.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigProvider.cs index 8a04db6cc..29b0ffbbc 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigProvider.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigProvider.cs @@ -98,15 +98,6 @@ protected virtual T LoadConfig(string fileName) public virtual BossConfig GetBossConfig(IReadOnlyCollection profiles, string? mood) => LoadYamlConfigs("bosses.yml", profiles, mood); - /// - /// Returns the configs with additional information for dungeons - /// - /// The selected tracker profile(s) to load - /// The current tracker mood to pick the specific mood file - /// - public virtual DungeonConfig GetDungeonConfig(IReadOnlyCollection profiles, string? mood) => - LoadYamlConfigs("dungeons.yml", profiles, mood); - /// /// Returns the configs with additional information for items /// diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigServiceCollectionExtensions.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigServiceCollectionExtensions.cs index d1492dc2a..317b0903a 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigServiceCollectionExtensions.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigServiceCollectionExtensions.cs @@ -15,90 +15,85 @@ public static IServiceCollection AddConfigs(this IServiceCollection services) }); services.AddSingleton(); - services.AddTransient(); - services.AddScoped(serviceProvider => - { - var configs = serviceProvider.GetRequiredService(); - return configs.Bosses; - }); + services.AddScoped(); services.AddScoped(serviceProvider => { - var configs = serviceProvider.GetRequiredService(); - return configs.Dungeons; + var configs = serviceProvider.GetRequiredService(); + return configs.Bosses; }); services.AddScoped(serviceProvider => { - var configs = serviceProvider.GetRequiredService(); + var configs = serviceProvider.GetRequiredService(); return configs.Items; }); services.AddScoped(serviceProvider => { - var configs = serviceProvider.GetRequiredService(); + var configs = serviceProvider.GetRequiredService(); return configs.Locations; }); services.AddScoped(serviceProvider => { - var configs = serviceProvider.GetRequiredService(); + var configs = serviceProvider.GetRequiredService(); return configs.Regions; }); services.AddScoped(serviceProvider => { - var configs = serviceProvider.GetRequiredService(); + var configs = serviceProvider.GetRequiredService(); return configs.Requests; }); services.AddScoped(serviceProvider => { - var configs = serviceProvider.GetRequiredService(); + var configs = serviceProvider.GetRequiredService(); return configs.Responses; }); services.AddScoped(serviceProvider => { - var configs = serviceProvider.GetRequiredService(); + var configs = serviceProvider.GetRequiredService(); return configs.Rooms; }); services.AddScoped(serviceProvider => { - var configs = serviceProvider.GetRequiredService(); + var configs = serviceProvider.GetRequiredService(); return configs.Rewards; }); services.AddScoped(serviceProvider => { - var configs = serviceProvider.GetRequiredService(); + var configs = serviceProvider.GetRequiredService(); return configs.UILayouts; }); services.AddScoped(serviceProvider => { - var configs = serviceProvider.GetRequiredService(); + var configs = serviceProvider.GetRequiredService(); return configs.GameLines; }); services.AddScoped(serviceProvider => { - var configs = serviceProvider.GetRequiredService(); + var configs = serviceProvider.GetRequiredService(); return configs.MsuConfig; }); services.AddScoped(serviceProvider => { - var configs = serviceProvider.GetRequiredService(); - return configs.HintTileConfig; + var configs = serviceProvider.GetRequiredService(); + return configs.HintTiles; }); services.AddScoped(serviceProvider => { - var configs = serviceProvider.GetRequiredService(); - return configs.MetadataConfig; + var configs = serviceProvider.GetRequiredService(); + return configs.Metadata; }); return services; diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/DungeonInfo.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/DungeonInfo.cs deleted file mode 100644 index aad0ccfd5..000000000 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/DungeonInfo.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Newtonsoft.Json; -using TrackerCouncil.Smz3.Data.WorldData; -using TrackerCouncil.Smz3.Data.WorldData.Regions; -using YamlDotNet.Serialization; - -namespace TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; - -/// -/// Represents a dungeon in A Link to the Past. -/// -public class DungeonInfo : IMergeable -{ - public DungeonInfo() { } - - /// - /// Initializes a new instance of the class. - /// - /// The name of the dungeon. - /// The name of the boss. - public DungeonInfo(SchrodingersString name,SchrodingersString boss) - { - Name = name; - Boss = boss; - } - - /// - /// Initializes a new instance of the class. - /// - /// The name of the dungeon. - /// The name of the boss. - public DungeonInfo(string name, string boss) - { - Dungeon = name; - Name = new (name); - Boss = new (boss); - } - - /// - /// The identifier for merging configs - /// - [MergeKey] - public string Dungeon { get; init; } = ""; - - /// - /// Gets the possible names of the dungeon. - /// - public SchrodingersString? Name { get; set; } - - /// - /// Gets the possible names of the dungeon boss. - /// - public SchrodingersString? Boss { get; set; } - - /// - /// The name of the type of region that represents this dungeon - /// - [JsonIgnore, YamlIgnore] - public Type? Type { get; init; } - - /// - /// Returns a string representation of the dungeon. - /// - /// A string representing the dungeon. - public override string ToString() => Dungeon; - - /// - /// Determines whether the specified region represents this dungeon. - /// - /// The region to check. - /// - /// true if matches the dungeon; - /// otherwise, false. - /// - public bool Is(Region region) - => Type == region.GetType(); - - /// - /// Determines whether the specified area either represents this dungeon - /// or is located in this dungeon. - /// - /// The area to check. - /// - /// true if is or is contained in this - /// dungeon; otherwise, false. - /// - public bool Is(IHasLocations area) - { - if (area is Region region) - return Is(region); - else if (area is Room room) - return Is(room.Region); - else - return false; - } - - /// - /// Returns the region that represents this dungeon in the specified - /// . - /// - /// The world those regions to find. - /// - /// The in that matches - /// this dungeon. - /// - public Region GetRegion(World world) - => world.Regions.Single(Is); - - /// - /// Determines whether the dungeon is accessible with the specified set - /// of items. - /// - /// - /// The instance of the world that contains the dungeon. - /// - /// The available items. - /// - /// true if the dungeon is accessible; otherwise, false. - /// - public bool IsAccessible(World world, Progression progression) - { - var region = GetRegion(world); - return region.CanEnter(progression, true); - } - - /// - /// Returns the locations associated with the dungeon. - /// - /// - /// The instance of the world whose locations to return. - /// - /// - /// A collection of locations in the dungeon from the specified world. - /// - public IReadOnlyCollection GetLocations(World world) - { - var region = GetRegion(world); - return region.Locations.Where(x => x.Type != LocationType.NotInDungeon).ToImmutableList(); - } -} diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/SchrodingersString.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/SchrodingersString.cs index 48f42281b..4e4034b81 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/SchrodingersString.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/SchrodingersString.cs @@ -105,7 +105,7 @@ public void Add(string text, double weight = 1.0d) /// /// /// The text for tracker to speak and the image to display, if applicable - public (string SpeechText, string? TrackerImage)? GetSpeechDetails(params object?[] args) + public (string SpeechText, string? TrackerImage, List? AdditionalLines)? GetSpeechDetails(params object?[] args) { var selectedPossibility = Random(s_random); if (selectedPossibility == null) @@ -113,7 +113,7 @@ public void Add(string text, double weight = 1.0d) return null; } var formattedText = string.Format(selectedPossibility.Text, args); - return (formattedText, selectedPossibility.TrackerImage); + return (formattedText, selectedPossibility.TrackerImage, selectedPossibility.AdditionalLines); } /// @@ -143,7 +143,18 @@ public void Add(string text, double weight = 1.0d) if (Items.Count == 0) return null; - var target = random.NextDouble() * GetTotalWeight(); + var totalWeight = GetTotalWeight(); + + if (totalWeight >= 1000) + { + var item = Items.FirstOrDefault(x => x.Weight >= 1000); + if (item != null) + { + return item; + } + } + + var target = random.NextDouble() * totalWeight; foreach (var item in Items) { if (target < item.Weight) @@ -230,6 +241,12 @@ public Possibility(string text, double weight) /// public string? TrackerImage { get; init; } = null; + /// + /// Additional lines to be stated after the initial line. Used + /// for adding additional poses. + /// + public List? AdditionalLines { get; set; } + /// /// Converts the possibility to a string. /// @@ -269,3 +286,10 @@ public override bool Equals(object? other) } } } + +public class PossibilityAdditionalLine +{ + public string Text { get; init; } = ""; + + public string? TrackerImage { get; init; } = null; +} diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/Configs.cs b/src/TrackerCouncil.Smz3.Data/Configuration/Configs.cs index 33f174de1..0a9b19345 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/Configs.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/Configs.cs @@ -39,7 +39,6 @@ public void LoadConfigs() _logger.LogInformation("Tracker is feeling {Mood} today", CurrentMood); Bosses = _configProvider.GetBossConfig(profiles, CurrentMood); - Dungeons = _configProvider.GetDungeonConfig(profiles, CurrentMood); Items = _configProvider.GetItemConfig(profiles, CurrentMood); Locations = _configProvider.GetLocationConfig(profiles, CurrentMood); Regions = _configProvider.GetRegionConfig(profiles, CurrentMood); @@ -109,11 +108,6 @@ public void LoadConfigs() /// public RegionConfig Regions { get; private set; } = null!; - /// - /// Gets a collection of extra information about dungeons. - /// - public DungeonConfig Dungeons { get; private set; } = null!; - /// /// Gets a collection of bosses. /// diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/IMergeable.cs b/src/TrackerCouncil.Smz3.Data/Configuration/IMergeable.cs index 6a5c6e57a..5939859b6 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/IMergeable.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/IMergeable.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; +using TrackerCouncil.Smz3.Shared; namespace TrackerCouncil.Smz3.Data.Configuration; @@ -21,7 +22,7 @@ public interface IMergeable /// Merges the data from the other object into the current instance /// /// The object to be merged into this one - public void MergeFrom(IMergeable other) + public IMergeable MergeFrom(IMergeable other) { if (GetType().IsSubclassOf(typeof(List))) { @@ -31,6 +32,8 @@ public void MergeFrom(IMergeable other) { MergeProperties(this, other); } + + return this; } private static void MergeLists(IMergeable first, IMergeable? second) @@ -86,6 +89,21 @@ private static void MergeLists(IMergeable first, IMergeable? second) } + public static TConfig Combine(TConfig config, params TConfig?[] additionalConfigs) where TConfig : class, IMergeable + { + if (additionalConfigs.Length == 0) + { + return config; + } + + foreach (var additionalConfig in additionalConfigs.NonNull()) + { + config.MergeFrom(additionalConfig); + } + + return config; + } + private static void MergeProperties(IMergeable primary, IMergeable other) { if (primary.GetType() != other.GetType()) diff --git a/src/TrackerCouncil.Smz3.Data/Services/IMetadataService.cs b/src/TrackerCouncil.Smz3.Data/Services/IMetadataService.cs index 979797ac5..267db543b 100644 --- a/src/TrackerCouncil.Smz3.Data/Services/IMetadataService.cs +++ b/src/TrackerCouncil.Smz3.Data/Services/IMetadataService.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using TrackerCouncil.Smz3.Shared; +using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; using TrackerCouncil.Smz3.Data.WorldData; using TrackerCouncil.Smz3.Data.WorldData.Regions; @@ -17,37 +17,67 @@ public interface IMetadataService /// /// Collection of all additional region information /// - public IReadOnlyCollection Regions { get; } - - /// - /// Collection of all additional dungeon information - /// - public IReadOnlyCollection Dungeons { get; } + public RegionConfig Regions { get; } /// /// Collection of all additional room information /// - public IReadOnlyCollection Rooms { get; } + public RoomConfig Rooms { get; } /// /// Collection of all additional location information /// - public IReadOnlyCollection Locations { get; } + public LocationConfig Locations { get; } /// /// Collection of all additional boss information /// - public IReadOnlyCollection Bosses { get; } + public BossConfig Bosses { get; } /// /// Collection of all additional item information /// - public IReadOnlyCollection Items { get; } + public ItemConfig Items { get; } /// /// Collection of all additional reward information /// - public IReadOnlyCollection Rewards { get; } + public RewardConfig Rewards { get; } + + /// + /// Lines that are displayed in the rom itself + /// + public GameLinesConfig GameLines { get; } + + /// + /// Data about all of the potential hint tiles and text used + /// + public HintTileConfig HintTiles { get; } + + /// + /// General tracker metadata and settings + /// + public MetadataConfig Metadata { get; } + + /// + /// Data for MSUs and particular track responses + /// + public MsuConfig MsuConfig { get; } + + /// + /// Different requests that the user can ask tracker + /// + public IReadOnlyCollection Requests { get; } + + /// + /// Different lines for tracker to respond to the player + /// + public ResponseConfig Responses { get; } + + /// + /// Layouts used by the UI + /// + public UIConfig UILayouts { get; set; } /// /// Returns extra information for the specified region. @@ -91,64 +121,6 @@ public interface IMetadataService /// public RegionInfo Region() where TRegion : Region; - /// - /// Returns extra information for the specified dungeon. - /// - /// - /// The name or fully qualified type name of the dungeon region. - /// - /// - /// A new for the specified dungeon region, or - /// null if is not a valid dungeon. - /// - public DungeonInfo? Dungeon(string name); - - /// - /// Returns extra information for the specified dungeon. - /// - /// - /// The type of dungeon to be looked up - /// - /// - /// A new for the specified dungeon region, or - /// null if is not a valid dungeon. - /// - public DungeonInfo Dungeon(Type type); - - /// - /// Returns extra information for the specified dungeon. - /// - /// - /// The dungeon region to get extra information for. - /// - /// - /// A new for the specified dungeon region. - /// - public DungeonInfo Dungeon(Region region); - - /// - /// Returns extra information for the specified dungeon. - /// - /// - /// The type of region that represents the dungeon to get extra - /// information for. - /// - /// - /// A new for the specified dungeon region. - /// - public DungeonInfo Dungeon() where TRegion : Region; - - /// - /// Returns extra information for the specified dungeon. - /// - /// - /// The dungeon to get extra information for. - /// - /// - /// A new for the specified dungeon region. - /// - public DungeonInfo Dungeon(IHasTreasure hasTreasure); - /// /// Returns extra information for the specified room. /// diff --git a/src/TrackerCouncil.Smz3.Data/Services/MetadataService.cs b/src/TrackerCouncil.Smz3.Data/Services/MetadataService.cs index 137bfe47d..60eeee68a 100644 --- a/src/TrackerCouncil.Smz3.Data/Services/MetadataService.cs +++ b/src/TrackerCouncil.Smz3.Data/Services/MetadataService.cs @@ -4,7 +4,9 @@ using Microsoft.Extensions.Logging; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration; +using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; +using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Data.WorldData; using TrackerCouncil.Smz3.Data.WorldData.Regions; using TrackerCouncil.Smz3.Shared.Enums; @@ -24,52 +26,98 @@ public class MetadataService : IMetadataService /// /// All configs /// - public MetadataService(Configs configs, ILogger logger) + /// + /// + public MetadataService(Configs configs, ILogger logger, TrackerOptionsAccessor trackerOptionsAccessor, TrackerSpriteService trackerSpriteService) { - Regions = configs.Regions; - Dungeons = configs.Dungeons; - Rooms = configs.Rooms; - Locations = configs.Locations; - Bosses = configs.Bosses; - Items = configs.Items; - Rewards = configs.Rewards; + var options = trackerOptionsAccessor.Options; + TrackerProfileConfig? profileConfig = null; + + if (options != null) + { + var trackerPack = options.TrackerImagePackName ?? "default"; + profileConfig = trackerSpriteService.GetPack(trackerPack).ProfileConfig; + } + + if (profileConfig == null) + { + Bosses = configs.Bosses; + GameLines = configs.GameLines; + HintTiles = configs.HintTileConfig; + Items = configs.Items; + Locations = configs.Locations; + Metadata = configs.MetadataConfig; + MsuConfig = configs.MsuConfig; + Regions = configs.Regions; + Requests = configs.Requests; + Responses = configs.Responses; + Rewards = configs.Rewards; + Rooms = configs.Rooms; + UILayouts = configs.UILayouts; + } + else + { + Bosses = IMergeable.Combine(BossConfig.Default(), configs.Bosses, profileConfig.BossConfig); + GameLines = IMergeable.Combine(GameLinesConfig.Default(), configs.GameLines); + HintTiles = IMergeable.Combine(HintTileConfig.Default(), configs.HintTileConfig); + Items = IMergeable.Combine(ItemConfig.Default(), configs.Items, profileConfig.ItemConfig); + Locations = IMergeable.Combine(LocationConfig.Default(), configs.Locations, profileConfig.LocationConfig); + Metadata = IMergeable.Combine(MetadataConfig.Default(), configs.MetadataConfig); + MsuConfig = IMergeable.Combine(MsuConfig.Default(), configs.MsuConfig); + Regions = IMergeable.Combine(RegionConfig.Default(), configs.Regions, profileConfig.RegionConfig); + Requests = IMergeable.Combine(RequestConfig.Default(), configs.Requests, profileConfig.RequestConfig); + Responses = IMergeable.Combine(ResponseConfig.Default(), configs.Responses, profileConfig.ResponseConfig); + Rewards = IMergeable.Combine(RewardConfig.Default(), configs.Rewards, profileConfig.RewardConfig); + Rooms = IMergeable.Combine(RoomConfig.Default(), configs.Rooms, profileConfig.RoomConfig); + UILayouts = IMergeable.Combine(UIConfig.Default(), configs.UILayouts); + } + _logger = logger; } /// /// Collection of all additional region information /// - public IReadOnlyCollection Regions { get; } - - /// - /// Collection of all additional dungeon information - /// - public IReadOnlyCollection Dungeons { get; } + public RegionConfig Regions { get; } /// /// Collection of all additional room information /// - public IReadOnlyCollection Rooms { get; } + public RoomConfig Rooms { get; } /// /// Collection of all additional location information /// - public IReadOnlyCollection Locations { get; } + public LocationConfig Locations { get; } /// /// Collection of all additional boss information /// - public IReadOnlyCollection Bosses { get; } + public BossConfig Bosses { get; } /// /// Collection of all additional item information /// - public IReadOnlyCollection Items { get; } + public ItemConfig Items { get; } /// /// Collection of all additional reward information /// - public IReadOnlyCollection Rewards { get; } + public RewardConfig Rewards { get; } + + public GameLinesConfig GameLines { get; } + + public HintTileConfig HintTiles { get; } + + public MetadataConfig Metadata { get; } + + public MsuConfig MsuConfig { get; } + + public IReadOnlyCollection Requests { get; } + + public ResponseConfig Responses { get; } + + public UIConfig UILayouts { get; set; } /// /// Returns extra information for the specified region. @@ -117,69 +165,6 @@ public RegionInfo Region(Region region) public RegionInfo Region() where TRegion : Region => Region(typeof(TRegion)); - /// - /// Returns extra information for the specified dungeon. - /// - /// - /// The name or fully qualified type name of the dungeon region. - /// - /// - /// A new for the specified dungeon region, or - /// null if is not a valid dungeon. - /// - public DungeonInfo? Dungeon(string name) - => Dungeons.SingleOrDefault(x => x.Type?.Name == name || x.Dungeon == name); - - /// - /// Returns extra information for the specified dungeon. - /// - /// - /// The type of dungeon to be looked up - /// - /// - /// A new for the specified dungeon region, or - /// null if is not a valid dungeon. - /// - public DungeonInfo Dungeon(Type type) - => Dungeons.Single(x => type == x.Type); - - /// - /// Returns extra information for the specified dungeon. - /// - /// - /// The dungeon region to get extra information for. - /// - /// - /// A new for the specified dungeon region. - /// - public DungeonInfo Dungeon(Region region) - => Dungeon(region.GetType()); - - /// - /// Returns extra information for the specified dungeon. - /// - /// - /// The type of region that represents the dungeon to get extra - /// information for. - /// - /// - /// A new for the specified dungeon region. - /// - public DungeonInfo Dungeon() where TRegion : Region - => Dungeon(typeof(TRegion)); - - /// - /// Returns extra information for the specified dungeon. - /// - /// - /// The dungeon to get extra information for. - /// - /// - /// A new for the specified dungeon region. - /// - public DungeonInfo Dungeon(IHasTreasure hasTreasure) - => Dungeon(hasTreasure.GetType()); - /// /// Returns extra information for the specified room. /// diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/GameHintService.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/GameHintService.cs index c39d73508..1b22e78cc 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/GameHintService.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/GameHintService.cs @@ -66,12 +66,12 @@ public class GameHintService : IGameHintService private readonly PlaythroughService _playthroughService; private Random _random; - public GameHintService(Configs configs, IMetadataService metadataService, ILogger logger, PlaythroughService playthroughService) + public GameHintService(IMetadataService metadataService, ILogger logger, PlaythroughService playthroughService) { - _gameLines = configs.GameLines; + _gameLines = metadataService.GameLines; _logger = logger; _playthroughService = playthroughService; - _hintTileConfig = configs.HintTileConfig; + _hintTileConfig = metadataService.HintTiles; _metadataService = metadataService; _random = new Random(); } @@ -585,7 +585,7 @@ private List GetMedallionHints(World hintPlayerWorld) private string GetDungeonName(World hintPlayerWorld, IHasTreasure hasTreasure, Region region) { - var dungeonName = _metadataService.Dungeon(hasTreasure).Name?.ToString() ?? hasTreasure.Name; + var dungeonName = _metadataService.Region(region).Name?.ToString() ?? hasTreasure.Name; return $"{dungeonName}{GetMultiworldSuffix(hintPlayerWorld, region.World)}"; } @@ -699,7 +699,7 @@ private IEnumerable GetImportantLocations(List allWorlds) { if (tile.Type == HintTileType.Requirement && tile.MedallionType != null) { - var dungeon = _metadataService.Dungeons.FirstOrDefault(x => x.Dungeon == tile.LocationKey); + var dungeon = _metadataService.Regions.FirstOrDefault(x => x.Region == tile.LocationKey); var dungeonName = dungeon?.Name?.ToString() ?? tile.LocationKey; var itemName = GetItemName(tile.MedallionType.Value); return _gameLines.HintDungeonMedallion?.Format(dungeonName, itemName); diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3GeneratedRomLoader.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3GeneratedRomLoader.cs index f6199b480..738fdef12 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3GeneratedRomLoader.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3GeneratedRomLoader.cs @@ -20,13 +20,11 @@ namespace TrackerCouncil.Smz3.SeedGenerator.Generation; public class Smz3GeneratedRomLoader { private readonly IWorldAccessor _worldAccessor; - private readonly IMetadataService _metadata; private readonly RandomizerContext _randomizerContext; - public Smz3GeneratedRomLoader(IWorldAccessor worldAccessor, IMetadataService metadata, RandomizerContext dbContext) + public Smz3GeneratedRomLoader(IWorldAccessor worldAccessor, RandomizerContext dbContext) { _worldAccessor = worldAccessor; - _metadata = metadata; _randomizerContext = dbContext; } @@ -35,7 +33,8 @@ public Smz3GeneratedRomLoader(IWorldAccessor worldAccessor, IMetadataService met /// the given GeneratedRom /// /// - public List LoadGeneratedRom(GeneratedRom rom) + /// + public List LoadGeneratedRom(GeneratedRom rom, IMetadataService metadata) { var trackerState = rom.TrackerState; @@ -57,7 +56,7 @@ public List LoadGeneratedRom(GeneratedRom rom) var configs = Config.FromConfigString(rom.Settings); var worlds = configs.Select(config => new World(config, string.IsNullOrEmpty(config.PlayerName) ? "Player" : config.PlayerName, config.Id, config.PlayerGuid, - config.Id == trackerState.LocalWorldId, _metadata, trackerState)).ToList(); + config.Id == trackerState.LocalWorldId, metadata, trackerState)).ToList(); // Load world items from state foreach (var location in worlds.SelectMany(x => x.Locations)) @@ -66,7 +65,7 @@ public List LoadGeneratedRom(GeneratedRom rom) s.WorldId == location.World.Id && s.LocationId == location.Id); var itemState = trackerState.ItemStates.First(s => s.Type == locationState.Item && s.WorldId == locationState.ItemWorldId); - var itemMetadata = _metadata.Item(locationState.Item) ?? new ItemData(locationState.Item); + var itemMetadata = metadata.Item(locationState.Item) ?? new ItemData(locationState.Item); var itemWorld = worlds.First(w => w.Id == locationState.ItemWorldId); location.Item = new Item(locationState.Item, itemWorld, itemState.ItemName, itemMetadata, itemState, @@ -79,7 +78,7 @@ public List LoadGeneratedRom(GeneratedRom rom) // Create items for saved state items not in the world foreach (var itemState in trackerState.ItemStates.Where(s => worlds.SelectMany(w => w.LocationItems).All(i => !(i.Type == s.Type && s.WorldId == i.World.Id && i.IsLocalPlayerItem)))) { - var itemMetadata = (itemState.Type != null && itemState.Type != ItemType.Nothing ? _metadata.Item(itemState.Type ?? ItemType.Nothing) : _metadata.Item(itemState.ItemName)) ?? + var itemMetadata = (itemState.Type != null && itemState.Type != ItemType.Nothing ? metadata.Item(itemState.Type ?? ItemType.Nothing) : metadata.Item(itemState.ItemName)) ?? new ItemData(new SchrodingersString(itemState.ItemName), itemState.Type ?? ItemType.Nothing, new SchrodingersString()); var world = worlds.First(w => w.Id == itemState.WorldId); @@ -90,7 +89,7 @@ public List LoadGeneratedRom(GeneratedRom rom) var allItems = worlds.SelectMany(x => x.AllItems).ToList(); foreach (var world in worlds) { - foreach (var itemMetadata in _metadata.Items.Where(m => !allItems.Any(i => i.World == world && i.Is(m.InternalItemType, m.Item)))) + foreach (var itemMetadata in metadata.Items.Where(m => !allItems.Any(i => i.World == world && i.Is(m.InternalItemType, m.Item)))) { var itemState = new TrackerItemState { @@ -108,7 +107,7 @@ public List LoadGeneratedRom(GeneratedRom rom) // Create custom bosses from the tracker states foreach (var bossState in trackerState.BossStates.Where(x => x.Type == BossType.None)) { - var bossMetadata = _metadata.Boss(bossState.BossName) ?? new BossInfo(bossState.BossName); + var bossMetadata = metadata.Boss(bossState.BossName) ?? new BossInfo(bossState.BossName); var world = worlds.First(w => w.Id == bossState.WorldId); world.CustomBosses.Add(new Boss(BossType.None, world, bossMetadata, bossState)); } @@ -116,7 +115,7 @@ public List LoadGeneratedRom(GeneratedRom rom) // Create custom bosses for metadata items not in the world foreach (var world in worlds) { - foreach (var bossMetadata in _metadata.Bosses.Where(m => m.Type == BossType.None && !world.AllBosses.Any(b => b.Is(BossType.None, m.Boss)))) + foreach (var bossMetadata in metadata.Bosses.Where(m => m.Type == BossType.None && !world.AllBosses.Any(b => b.Is(BossType.None, m.Boss)))) { var bossState = new TrackerBossState() { diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/PyTextToSpeechCommunicator.cs b/src/TrackerCouncil.Smz3.Tracking/Services/PyTextToSpeechCommunicator.cs index 5bcd51fc0..0f86bd1e7 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/PyTextToSpeechCommunicator.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/PyTextToSpeechCommunicator.cs @@ -161,13 +161,20 @@ public void Say(SpeechRequest request) var messageId = Guid.NewGuid().ToString(); _pendingRequests.TryAdd(messageId, request); + var speechSettings = GetSpeechSettings(); + if (request.TrackerImage == "alt") + { + (speechSettings.OnnxPath, speechSettings.AltOnnxPath) = (speechSettings.AltOnnxPath, speechSettings.OnnxPath); + (speechSettings.ConfigPath, speechSettings.AltConfigPath) = (speechSettings.AltConfigPath, speechSettings.ConfigPath); + } + if (request.Wait) { - _pySpeechService.Speak(request.Text, GetSpeechSettings(), messageId); + _pySpeechService.Speak(request.Text, speechSettings, messageId); } else { - _pySpeechService.SpeakAsync(request.Text, GetSpeechSettings(), messageId); + _pySpeechService.SpeakAsync(request.Text, speechSettings, messageId); } } diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/Speech/PySpeechRecognitionService.cs b/src/TrackerCouncil.Smz3.Tracking/Services/Speech/PySpeechRecognitionService.cs index 50bb21cf1..7c58aa76c 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/Speech/PySpeechRecognitionService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/Speech/PySpeechRecognitionService.cs @@ -7,11 +7,12 @@ using PySpeechService.Client; using PySpeechService.Recognition; using TrackerCouncil.Smz3.Data.Configuration; +using TrackerCouncil.Smz3.Data.Services; namespace TrackerCouncil.Smz3.Tracking.Services.Speech; [SupportedOSPlatform("linux")] -public class PySpeechRecognitionService(IPySpeechService pySpeechService, Configs configs, ILogger logger) : SpeechRecognitionServiceBase +public class PySpeechRecognitionService(IPySpeechService pySpeechService, IMetadataService metadata, ILogger logger) : SpeechRecognitionServiceBase { private bool _isEnabled; private float _minRequiredConfidence = 0.8f; @@ -58,7 +59,7 @@ public override void StartRecognition() { _isEnabled = true; - pySpeechService.AddSpeechRecognitionReplacements(configs.MetadataConfig.PySpeechRecognitionReplacements); + pySpeechService.AddSpeechRecognitionReplacements(metadata.Metadata.PySpeechRecognitionReplacements); if (pySpeechService.IsConnected) { diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/SpeechRequest.cs b/src/TrackerCouncil.Smz3.Tracking/Services/SpeechRequest.cs index 8a656777d..03730d315 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/SpeechRequest.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/SpeechRequest.cs @@ -22,4 +22,9 @@ public class SpeechRequest(string text, string? trackerImage = null, bool wait = /// If the communicator should block the calling thread /// public bool Wait => wait; + + /// + /// If the next tracker image is a blank one + /// + public bool FollowedByBlankImage { get; set; } } diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs b/src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs index 5a93861ba..00e0c48a5 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs @@ -16,8 +16,11 @@ public class TextToSpeechCommunicator : ICommunicator private bool _canSpeak; private DateTime _startSpeakingTime; private ConcurrentQueue _pendingRequests = []; + private ConcurrentDictionary _pendingPrompts = []; private SpeechRequest? _currentSpeechRequest; + private Prompt? _currentSpeechPrompt; private ILogger _logger; + private bool _isAltVoice; /// /// Initializes a new instance of the { if (!OperatingSystem.IsWindows()) return; + + if (args.Prompt != _currentSpeechPrompt) + { + if (_pendingPrompts.TryGetValue(args.Prompt, out var request)) + { + _currentSpeechPrompt = args.Prompt; + _currentSpeechRequest = request; + } + } + VisemeReached?.Invoke(this, new SpeakingUpdatedEventArgs(args.Viseme > 0, _currentSpeechRequest)); }; @@ -82,6 +96,7 @@ public void UseAlternateVoice(bool useAlt = true) return; } + _isAltVoice = useAlt; _tts.SelectVoiceByHints(useAlt ? VoiceGender.Male : VoiceGender.Female); } @@ -179,8 +194,9 @@ public void Dispose() /// Creates a new based on the specified text. /// /// The plain text or SSML markup to parse. + /// /// A new . - protected static Prompt? GetPromptFromText(string text) + protected Prompt? GetPromptFromText(string text, bool useAlternateVoice) { if (!OperatingSystem.IsWindows()) { @@ -189,9 +205,21 @@ public void Dispose() // If text does not contain any XML elements, just interpret it as // text - if (!text.Contains("<") && !text.Contains("/>")) + if (!text.Contains("<") && !text.Contains("/>") && !useAlternateVoice) return new Prompt(text); + if (useAlternateVoice) + { + if (_isAltVoice) + { + text = "" + text + ""; + } + else + { + text = "" + text + ""; + } + } + var prompt = new PromptBuilder(); prompt.AppendSsmlMarkup(text); return new Prompt(prompt); @@ -249,13 +277,14 @@ private void SayNext(SpeechRequest? request = null) request = nextRequest; } - var prompt = GetPromptFromText(request.Text); + var useAltVoice = "alt".Equals(request.TrackerImage, StringComparison.OrdinalIgnoreCase); + var prompt = GetPromptFromText(request.Text, useAltVoice); if (prompt == null) { return; } - _currentSpeechRequest = request; + _pendingPrompts.TryAdd(prompt, request); if (request.Wait) { diff --git a/src/TrackerCouncil.Smz3.Tracking/Tracker.cs b/src/TrackerCouncil.Smz3.Tracking/Tracker.cs index 40b8c0544..eff3dfada 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Tracker.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Tracker.cs @@ -53,7 +53,6 @@ public sealed class Tracker : TrackerBase, IDisposable private bool _disposed; private string? _lastSpokenText; private string? _previousImagePackName; - private ResponseConfig _defaultResponseConfig; /// /// Initializes a new instance of the class. @@ -75,6 +74,7 @@ public sealed class Tracker : TrackerBase, IDisposable /// /// /// + /// public Tracker(IWorldAccessor worldAccessor, TrackerModuleFactory moduleFactory, IChatClient chatClient, @@ -87,7 +87,8 @@ public Tracker(IWorldAccessor worldAccessor, ITrackerStateService stateService, ITrackerTimerService timerService, IServiceProvider serviceProvider, - TrackerSpriteService trackerSpriteService) + TrackerSpriteService trackerSpriteService, + IMetadataService metadata) { if (trackerOptions.Options == null) throw new InvalidOperationException("Tracker options have not yet been activated."); @@ -106,10 +107,9 @@ public Tracker(IWorldAccessor worldAccessor, _trackerSpriteService = trackerSpriteService; // Initialize the tracker configuration - Configs = configs; - _defaultResponseConfig = Responses = configs.Responses; + Responses = metadata.Responses; SetImagePack(trackerOptions.Options.TrackerImagePackName); - Requests = configs.Requests; + Requests = metadata.Requests; PlayerProgressionService.ResetProgression(); History = historyService; @@ -230,18 +230,6 @@ public override void SetImagePack(string? packName) _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) @@ -502,6 +490,7 @@ public override bool Say( { SchrodingersString? selectedResponse = null; string? trackerImage = null; + List? additionalLines = null; if (key != null) { @@ -547,11 +536,30 @@ public override bool Say( text = trackerSpeechDetails.Value.SpeechText; trackerImage = trackerSpeechDetails.Value.TrackerImage; + additionalLines = trackerSpeechDetails.Value.AdditionalLines; } + var speechRequests = new List(); + var formattedText = FormatPlaceholders(text); + speechRequests.Add(new SpeechRequest(formattedText, trackerImage, wait)); + + if (additionalLines?.Count > 0) + { + speechRequests.AddRange(additionalLines.Select(x => + new SpeechRequest(FormatPlaceholders(x.Text), x.TrackerImage, wait))); + } + + for (var i = 0; i < speechRequests.Count; i++) + { + if (i < speechRequests.Count - 1) + { + speechRequests[i].FollowedByBlankImage = "blank".Equals(speechRequests[i + 1].TrackerImage, + StringComparison.OrdinalIgnoreCase); + } + _communicator.Say(speechRequests[i]); + } - _communicator.Say(new SpeechRequest(formattedText, trackerImage, wait)); _lastSpokenText = formattedText; return true; @@ -691,7 +699,7 @@ private void Dispose(bool disposing) if (disposing) { (_recognizer as IDisposable)?.Dispose(); - (_communicator as IDisposable)?.Dispose(); + _communicator.Dispose(); _moduleFactory.Dispose(); foreach (var timer in _idleTimers.Values) diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameStateService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameStateService.cs index 030ec025c..fb8e95577 100644 --- a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameStateService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameStateService.cs @@ -4,6 +4,7 @@ using MSURandomizerLibrary.Configs; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Options; +using TrackerCouncil.Smz3.Data.Services; using TrackerCouncil.Smz3.Data.Tracking; using TrackerCouncil.Smz3.Data.WorldData; using TrackerCouncil.Smz3.Data.WorldData.Regions; @@ -12,7 +13,7 @@ namespace TrackerCouncil.Smz3.Tracking.TrackingServices; -internal class TrackerGameStateService : TrackerService, ITrackerGameStateService +internal class TrackerGameStateService(IMetadataService metadataService) : TrackerService, ITrackerGameStateService { public bool HasBeatenGame { get; protected set; } public ViewedObject? LastViewedObject { get; set; } @@ -206,7 +207,7 @@ public void UpdateHintTile(PlayerHintTile hintTile) if (locations.All(x => x.Autotracked || x.Cleared)) { hintTile.HintState = HintState.Cleared; - Tracker.Say(response: Configs.HintTileConfig.ViewedHintTileAlreadyVisited, args: [hintTile.LocationKey]); + Tracker.Say(response: metadataService.HintTiles.ViewedHintTileAlreadyVisited, args: [hintTile.LocationKey]); } else { @@ -214,15 +215,15 @@ public void UpdateHintTile(PlayerHintTile hintTile) { case LocationUsefulness.Mandatory: hintTile.HintState = HintState.Viewed; - Tracker.Say(response: Configs.HintTileConfig.ViewedHintTileMandatory, args: [hintTile.LocationKey]); + Tracker.Say(response: metadataService.HintTiles.ViewedHintTileMandatory, args: [hintTile.LocationKey]); break; case LocationUsefulness.Useless: hintTile.HintState = HintState.Viewed; - Tracker.Say(response: Configs.HintTileConfig.ViewedHintTileUseless, args: [hintTile.LocationKey]); + Tracker.Say(response: metadataService.HintTiles.ViewedHintTileUseless, args: [hintTile.LocationKey]); break; default: hintTile.HintState = HintState.Viewed; - Tracker.Say(response: Configs.HintTileConfig.ViewedHintTile); + Tracker.Say(response: metadataService.HintTiles.ViewedHintTile); break; } @@ -253,7 +254,7 @@ public void ClearLastViewedObject(float confidence) if (hintTile.State == null) { - Tracker.Say(response: Configs.HintTileConfig.NoPreviousHintTile); + Tracker.Say(response: metadataService.HintTiles.NoPreviousHintTile); } else if (hintTile.HintState != HintState.Cleared && hintTile.Locations?.Count() > 0) { @@ -267,12 +268,12 @@ public void ClearLastViewedObject(float confidence) } else { - Tracker.Say(response: Configs.HintTileConfig.ClearHintTileFailed); + Tracker.Say(response: metadataService.HintTiles.ClearHintTileFailed); } } else { - Tracker.Say(response: Configs.HintTileConfig.ClearHintTileFailed); + Tracker.Say(response: metadataService.HintTiles.ClearHintTileFailed); } } else diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerItemService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerItemService.cs index 22c2b5a1b..264df4e56 100644 --- a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerItemService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerItemService.cs @@ -233,7 +233,7 @@ public bool TrackItem(Item item, string? trackedAs = null, float? confidence = n // Clear the location if it's for the local player's world if (location != null && location.World == World && !location.Cleared) { - Tracker.LocationTracker.Clear(location, confidence, autoTracked, stateResponse, true); + Tracker.LocationTracker.Clear(location, confidence, autoTracked, !silent, true); undoActions.Add(autoTracked ? null : PopUndo().Action); } } diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerLocationService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerLocationService.cs index 25c2b6986..6cf05254f 100644 --- a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerLocationService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerLocationService.cs @@ -26,7 +26,7 @@ internal class TrackerLocationService(ILogger logger, IP public void Clear(Location location, float? confidence = null, bool autoTracked = false, bool stateResponse = true, bool allowLocationComments = false, bool updateTreasureCount = true) { - if (!stateResponse && allowLocationComments) + if (stateResponse && allowLocationComments) { GiveLocationComment(location.Item.Type, location, isTracking: true, confidence, location.Item.Metadata); GivePreConfiguredLocationSass(location); @@ -477,22 +477,22 @@ private void GiveLocationComment(ItemType item, Location location, bool isTracki { if (!isTracking && locationInfo.WhenMarkingJunk?.Count > 0) { - Tracker.Say(text: locationInfo.WhenMarkingJunk.Random(Random)!); + Tracker.Say(response: locationInfo.WhenMarkingJunk); } else if (locationInfo.WhenTrackingJunk?.Count > 0) { - Tracker.Say(text: locationInfo.WhenTrackingJunk.Random(Random)!); + Tracker.Say(response: locationInfo.WhenTrackingJunk); } } else if (!isJunk) { if (!isTracking && locationInfo.WhenMarkingProgression?.Count > 0) { - Tracker.Say(text: locationInfo.WhenMarkingProgression.Random(Random)!); + Tracker.Say(response: locationInfo.WhenMarkingProgression); } else if (locationInfo.WhenTrackingProgression?.Count > 0) { - Tracker.Say(text: locationInfo.WhenTrackingProgression.Random(Random)!); + Tracker.Say(response: locationInfo.WhenTrackingProgression); } } } diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerService.cs index 62dbfc5b8..98efc00f2 100644 --- a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerService.cs @@ -14,7 +14,6 @@ internal abstract class TrackerService protected static readonly Random Random = new(); internal TrackerBase Tracker { get; set; } = null!; - protected Configs Configs => Tracker.Configs; protected ResponseConfig Responses => Tracker.Responses; protected World World => Tracker.World; protected TrackerOptions Options => Tracker.Options; diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/HintTileModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/HintTileModule.cs index e48dd2334..6fa667a23 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/HintTileModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/HintTileModule.cs @@ -7,6 +7,7 @@ using TrackerCouncil.Smz3.Data.Configuration; using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; +using TrackerCouncil.Smz3.Data.Services; using TrackerCouncil.Smz3.SeedGenerator.Contracts; using TrackerCouncil.Smz3.Tracking.Services; @@ -29,11 +30,11 @@ public class HintTileModule : TrackerModule /// /// /// - /// - public HintTileModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger, IGameHintService gameHintService, Configs configs) : base(tracker, playerProgressionService, worldQueryService, logger) + /// + public HintTileModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger, IGameHintService gameHintService, IMetadataService metadataService) : base(tracker, playerProgressionService, worldQueryService, logger) { _gameHintService = gameHintService; - _hintTileConfig = configs.HintTileConfig; + _hintTileConfig = metadataService.HintTiles; } /// diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MetaModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MetaModule.cs index 77182b494..705104690 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MetaModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MetaModule.cs @@ -118,7 +118,7 @@ private SpeechRecognitionGrammarBuilder GetResumeTimerRule() return new SpeechRecognitionGrammarBuilder() .Append("Hey tracker, ") .Optional("please") - .OneOf("resume the timer", "start the timer"); + .OneOf("resume the timer", "start the timer", "continue the timer", "unpause the timer"); } private SpeechRecognitionGrammarBuilder GetResetTimerRule() diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MsuModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MsuModule.cs index 96486637c..b79482b06 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MsuModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MsuModule.cs @@ -12,6 +12,7 @@ using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Configuration; using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; +using TrackerCouncil.Smz3.Data.Services; using TrackerCouncil.Smz3.Tracking.Services; namespace TrackerCouncil.Smz3.Tracking.VoiceCommands; @@ -46,7 +47,7 @@ public class MsuModule : TrackerModule, IDisposable /// /// /// - /// + /// /// /// public MsuModule( @@ -58,7 +59,7 @@ public MsuModule( IMsuMonitorService msuMonitorService, IMsuTypeService msuTypeService, IMsuUserOptionsService msuUserOptionsService, - Configs config, + IMetadataService metadataService, IGameService gameService, IMsuMessageReceiver msuMessageReceiver) : base(tracker, playerProgressionService, worldQueryService, logger) @@ -69,7 +70,7 @@ public MsuModule( _msuLookupService = msuLookupService; _msuUserOptionsService = msuUserOptionsService; var msuType = msuTypeService.GetSMZ3MsuType(); - _msuConfig = config.MsuConfig; + _msuConfig = metadataService.MsuConfig; _validTrackNumbers = msuType!.ValidTrackNumbers; if (!File.Exists(tracker.RomPath)) diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs index 08a3630a2..803ecdf2a 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs @@ -138,13 +138,13 @@ public void LoadInto(List grammar) } /// - /// Returns the that was detected in a voice + /// Returns the that was detected in a voice /// command using . /// /// The tracker instance. /// The speech recognition result. /// - /// A from the recognition result. + /// A from the recognition result. /// protected static IHasTreasure GetDungeonFromResult(TrackerBase tracker, SpeechRecognitionResult result) { @@ -154,13 +154,13 @@ protected static IHasTreasure GetDungeonFromResult(TrackerBase tracker, SpeechRe } /// - /// Returns the that was detected in a voice + /// Returns the that was detected in a voice /// command using . /// /// The tracker instance. /// The speech recognition result. /// - /// A from the recognition result. + /// A from the recognition result. /// protected static IHasTreasure? GetBossDungeonFromResult(TrackerBase tracker, SpeechRecognitionResult result) { @@ -509,11 +509,11 @@ protected virtual List GetLocationNames() if (location.Metadata.Name != null) { foreach (var name in location.Metadata.Name) - locationNames.Add(new GrammarKeyValueChoice(name.Text, (int)location.Id)); + locationNames.Add(new GrammarKeyValueChoice(name.Text, ((int)location.Id).ToString())); } else { - locationNames.Add(new GrammarKeyValueChoice(location.Name, (int)location.Id)); + locationNames.Add(new GrammarKeyValueChoice(location.Name, ((int)location.Id).ToString())); } } diff --git a/src/TrackerCouncil.Smz3.UI/Services/MainWindowService.cs b/src/TrackerCouncil.Smz3.UI/Services/MainWindowService.cs index 894846090..b3b0051a2 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/MainWindowService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/MainWindowService.cs @@ -10,6 +10,7 @@ using AvaloniaControls.Models; using AvaloniaControls.Services; using GitHubReleaseChecker; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using PySpeechService.Client; using PySpeechService.TextToSpeech; @@ -32,8 +33,8 @@ public class MainWindowService( IGitHubFileSynchronizerService gitHubFileSynchronizerService, SpriteService spriteService, TrackerSpriteService trackerSpriteService, - IPySpeechService pySpeechService, - Configs configs) : ControlService + Configs configs, + IServiceProvider serviceProvider) : ControlService { private MainWindowViewModel _model = new(); private RandomizerOptions _options = null!; @@ -98,9 +99,13 @@ public async Task StartPySpeechService() return; } - pySpeechService.AutoReconnect = true; - await pySpeechService.StartAsync(); - await pySpeechService.SetSpeechSettingsAsync(new SpeechSettings()); + var pySpeechService = serviceProvider.GetService(); + if (pySpeechService != null) + { + pySpeechService.AutoReconnect = true; + await pySpeechService.StartAsync(); + await pySpeechService.SetSpeechSettingsAsync(new SpeechSettings()); + } } public async Task DownloadConfigsAsync() { diff --git a/src/TrackerCouncil.Smz3.UI/Services/SharedCrossplatformService.cs b/src/TrackerCouncil.Smz3.UI/Services/SharedCrossplatformService.cs index 8ad0130a7..503e1262d 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/SharedCrossplatformService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/SharedCrossplatformService.cs @@ -186,12 +186,13 @@ public void OpenFolder(GeneratedRom? rom) try { - smz3GeneratedRomLoader.LoadGeneratedRom(rom); - var scope = serviceProvider.CreateScope(); var trackerOptionsAccessor = scope.ServiceProvider.GetRequiredService(); trackerOptionsAccessor.Options = Options.GeneralOptions.GetTrackerOptions(); + var metadataService = scope.ServiceProvider.GetRequiredService(); + smz3GeneratedRomLoader.LoadGeneratedRom(rom, metadataService); + s_trackerWindow = scope.ServiceProvider.GetRequiredService(); s_trackerWindow.Closed += (_, _) => { diff --git a/src/TrackerCouncil.Smz3.UI/Services/TrackerSpeechWindowService.cs b/src/TrackerCouncil.Smz3.UI/Services/TrackerSpeechWindowService.cs index 2bdec3a31..8a5a1547c 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/TrackerSpeechWindowService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/TrackerSpeechWindowService.cs @@ -94,6 +94,11 @@ public void SaveOpenStatus(bool isOpen) private void Communicator_VisemeReached(object? sender, SpeakingUpdatedEventArgs e) { + if (!_model.IsTrackerImageVisible) + { + _model.IsTrackerImageVisible = true; + } + SetReactionType(e.Request?.TrackerImage ?? "default"); if (!e.IsSpeaking) @@ -115,7 +120,7 @@ private void Communicator_VisemeReached(object? sender, SpeakingUpdatedEventArgs private void Communicator_SpeakCompleted(object? sender, SpeakCompletedEventArgs e) { - SetReactionType(); + SetReactionType(e.SpeechRequest?.FollowedByBlankImage == true ? "blank" : "default"); _model.TrackerImage = _currentSpeechImages?.IdleImage; _prevSpeaking = false; } diff --git a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerSpeechWindowViewModel.cs b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerSpeechWindowViewModel.cs index 140bd440c..e4d9a9f2d 100644 --- a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerSpeechWindowViewModel.cs +++ b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerSpeechWindowViewModel.cs @@ -9,4 +9,5 @@ public class TrackerSpeechWindowViewModel : ViewModelBase [Reactive] public string? TrackerImage { get; set; } [Reactive] public Thickness AnimationMargin { get; set; } = new(0, 0, 0, 0); [Reactive] public SolidColorBrush Background { get; set; } = new(new Color(0xFF, 0x48, 0x3D, 0x8B)); + [Reactive] public bool IsTrackerImageVisible { get; set; } = false; } diff --git a/src/TrackerCouncil.Smz3.UI/Views/TrackerSpeechWindow.axaml b/src/TrackerCouncil.Smz3.UI/Views/TrackerSpeechWindow.axaml index e11d643c6..c1c85dfff 100644 --- a/src/TrackerCouncil.Smz3.UI/Views/TrackerSpeechWindow.axaml +++ b/src/TrackerCouncil.Smz3.UI/Views/TrackerSpeechWindow.axaml @@ -17,7 +17,8 @@ + Source="{Binding TrackerImage, Converter={StaticResource StringToImageConverter}}" + IsVisible="{Binding IsTrackerImageVisible}"/> diff --git a/tests/TrackerCouncil.Smz3.Tests/ConfigFileTests.cs b/tests/TrackerCouncil.Smz3.Tests/ConfigFileTests.cs deleted file mode 100644 index bd3992ed4..000000000 --- a/tests/TrackerCouncil.Smz3.Tests/ConfigFileTests.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using FluentAssertions; -using TrackerCouncil.Smz3.Data.Configuration; -using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; -using Xunit; - -namespace TrackerCouncil.Smz3.Tests; - -public class ConfigFileTests -{ - [Fact] - public void BossConfigDoesNotContainDebugWeights() - { - var provider = new ConfigProvider(null); - var config = provider.GetBossConfig(new[] { "BCU", "Sassy" }, null); - - var strings = EnumerateSchrodingersStrings(config); - foreach (var value in strings) - { - value.Should().NotContain(x => x.Weight >= 90); - } - } - - [Fact] - public void DungeonConfigDoesNotContainDebugWeights() - { - var provider = new ConfigProvider(null); - var config = provider.GetDungeonConfig(new[] { "BCU", "Sassy" }, null); - - var strings = EnumerateSchrodingersStrings(config); - foreach (var value in strings) - { - value.Should().NotContain(x => x.Weight >= 90); - } - } - - [Fact] - public void ItemConfigDoesNotContainDebugWeights() - { - var provider = new ConfigProvider(null); - var config = provider.GetDungeonConfig(new[] { "BCU", "Sassy" }, null); - - var strings = EnumerateSchrodingersStrings(config); - foreach (var value in strings) - { - value.Should().NotContain(x => x.Weight >= 90); - } - } - - [Fact] - public void LocationConfigDoesNotContainDebugWeights() - { - var provider = new ConfigProvider(null); - var config = provider.GetDungeonConfig(new[] { "BCU", "Sassy" }, null); - - var strings = EnumerateSchrodingersStrings(config); - foreach (var value in strings) - { - value.Should().NotContain(x => x.Weight >= 90); - } - } - - [Fact] - public void RegionConfigDoesNotContainDebugWeights() - { - var provider = new ConfigProvider(null); - var config = provider.GetDungeonConfig(new[] { "BCU", "Sassy" }, null); - - var strings = EnumerateSchrodingersStrings(config); - foreach (var value in strings) - { - value.Should().NotContain(x => x.Weight >= 90); - } - } - - [Fact] - public void RequestConfigDoesNotContainDebugWeights() - { - var provider = new ConfigProvider(null); - var config = provider.GetDungeonConfig(new[] { "BCU", "Sassy" }, null); - - var strings = EnumerateSchrodingersStrings(config); - foreach (var value in strings) - { - value.Should().NotContain(x => x.Weight >= 90); - } - } - - [Fact] - public void ResponseConfigDoesNotContainDebugWeights() - { - var provider = new ConfigProvider(null); - var config = provider.GetDungeonConfig(new[] { "BCU", "Sassy" }, null); - - var strings = EnumerateSchrodingersStrings(config); - foreach (var value in strings) - { - value.Should().NotContain(x => x.Weight >= 90); - } - } - - [Fact] - public void RewardConfigDoesNotContainDebugWeights() - { - var provider = new ConfigProvider(null); - var config = provider.GetDungeonConfig(new[] { "BCU", "Sassy" }, null); - - var strings = EnumerateSchrodingersStrings(config); - foreach (var value in strings) - { - value.Should().NotContain(x => x.Weight >= 90); - } - } - - [Fact] - public void RoomConfigDoesNotContainDebugWeights() - { - var provider = new ConfigProvider(null); - var config = provider.GetDungeonConfig(new[] { "BCU", "Sassy" }, null); - - var strings = EnumerateSchrodingersStrings(config); - foreach (var value in strings) - { - value.Should().NotContain(x => x.Weight >= 90); - } - } - - [Fact] - public void GameLinesConfigHasValidStrings() - { - var provider = new ConfigProvider(null); - var config = provider.GetDungeonConfig(new[] { "BCU", "Sassy" }, null); - var strings = EnumerateSchrodingersStrings(config); - foreach (var value in strings) - { - value.Should().NotContain(x => x.Weight >= 90); - } - } - - private bool HasLongLine(SchrodingersString.Possibility possibility) - { - return possibility.Text.Split("\n").Any(x => x.Length > 19); - } - - private IEnumerable EnumerateSchrodingersStrings(object obj, int depth = 0) - { - if (obj is null) - { - yield break; - } - else if (obj is SchrodingersString str) - { - yield return str; - } - else if (obj is IEnumerable strings) - { - foreach (var x in strings) - yield return x; - } - else - { - if (depth > 3) yield break; - - if (obj is IEnumerable enumerable) - { - foreach (var nestedObj in enumerable) - { - var nestedStrings = EnumerateSchrodingersStrings(nestedObj, depth + 1); - foreach (var x in nestedStrings) - yield return x; - } - } - else - { - foreach (var property in obj.GetType().GetProperties()) - { - if (property.PropertyType == typeof(Type)) - continue; - var value = property.GetValue(obj); - if (value == null) continue; - var nestedStrings = EnumerateSchrodingersStrings(value, depth + 1); - foreach (var x in nestedStrings) - yield return x; - } - } - } - } -} diff --git a/tests/TrackerCouncil.Smz3.Tests/LogicTests/RandomizerTests.cs b/tests/TrackerCouncil.Smz3.Tests/LogicTests/RandomizerTests.cs index b702912e7..ff0bd7726 100644 --- a/tests/TrackerCouncil.Smz3.Tests/LogicTests/RandomizerTests.cs +++ b/tests/TrackerCouncil.Smz3.Tests/LogicTests/RandomizerTests.cs @@ -172,6 +172,8 @@ private static Smz3Randomizer GetRandomizer() .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() + .AddSingleton() .AddTransient() .AddConfigs() .BuildServiceProvider();