From abb64a1f7f5f9dedd234b64f4439e3d5ed653a43 Mon Sep 17 00:00:00 2001 From: erri120 Date: Thu, 7 Nov 2024 14:38:22 +0100 Subject: [PATCH] Add game support and feature tracking --- .../NexusMods.Abstractions.Games/AGame.cs | 8 +- .../NexusMods.Abstractions.Games/Feature.cs | 77 +++++++++++++++++++ .../NexusMods.Abstractions.Games/Features.cs | 19 +++++ .../NexusMods.Abstractions.Games/IGame.cs | 4 + .../SupportLevel.cs | 25 ++++++ .../BaldursGate3/BaldursGate3.cs | 9 +++ .../MountAndBlade2Bannerlord.cs | 11 ++- .../Cyberpunk2077/Cyberpunk2077Game.cs | 10 +++ .../StardewValley.cs | 9 +++ .../StardewValleyLoadoutSynchronizer.cs | 2 - .../StubbedGames/StubbedGame.cs | 2 + 11 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 src/Abstractions/NexusMods.Abstractions.Games/Feature.cs create mode 100644 src/Abstractions/NexusMods.Abstractions.Games/Features.cs create mode 100644 src/Abstractions/NexusMods.Abstractions.Games/SupportLevel.cs diff --git a/src/Abstractions/NexusMods.Abstractions.Games/AGame.cs b/src/Abstractions/NexusMods.Abstractions.Games/AGame.cs index 9cf76b1a97..9252a00153 100644 --- a/src/Abstractions/NexusMods.Abstractions.Games/AGame.cs +++ b/src/Abstractions/NexusMods.Abstractions.Games/AGame.cs @@ -44,7 +44,13 @@ protected virtual ILoadoutSynchronizer MakeSynchronizer(IServiceProvider provide /// public abstract string Name { get; } - + + /// + public abstract SupportType SupportType { get; } + + /// + public virtual HashSet Features { get; } = []; + /// public abstract GameId GameId { get; } diff --git a/src/Abstractions/NexusMods.Abstractions.Games/Feature.cs b/src/Abstractions/NexusMods.Abstractions.Games/Feature.cs new file mode 100644 index 0000000000..a109abf03a --- /dev/null +++ b/src/Abstractions/NexusMods.Abstractions.Games/Feature.cs @@ -0,0 +1,77 @@ +using JetBrains.Annotations; + +namespace NexusMods.Abstractions.Games; + +/// +/// Represents a feature a game can support. +/// +/// Description +[PublicAPI] +public readonly record struct Feature(string Description) +{ + /// + /// Identifier. + /// + public readonly Guid Id = Guid.NewGuid(); + + /// + public override int GetHashCode() => Id.GetHashCode(); + + /// + /// Equality. + /// + public bool Equals(Feature? other) => other is not null && Id.Equals(other.Value.Id); +} + +/// +/// Status of a feature. +/// +/// The feature. +/// Whether the feature is implemented or not. +[PublicAPI] +public readonly record struct FeatureStatus(Feature Feature, bool IsImplemented); + +/// +/// Status of all game features. +/// +[PublicAPI] +public enum GameFeatureStatus +{ + /// + /// Default value. + /// + None = 0, + + /// + /// The minimum amount of features is implemented. + /// + Minimal = 1, + + /// + /// All features are implemented. + /// + Full = 2, +} + +/// +/// Extension methods. +/// +[PublicAPI] +public static class FeatureExtensions +{ + /// + /// + /// + public static GameFeatureStatus ToStatus(this HashSet features) + { + var implemented = features.Count(status => status.IsImplemented); + var total = features.Count; + if (implemented == total) return GameFeatureStatus.Full; + + return implemented switch + { + 0 => GameFeatureStatus.None, + _ => GameFeatureStatus.Minimal, + }; + } +} diff --git a/src/Abstractions/NexusMods.Abstractions.Games/Features.cs b/src/Abstractions/NexusMods.Abstractions.Games/Features.cs new file mode 100644 index 0000000000..392fcc9c47 --- /dev/null +++ b/src/Abstractions/NexusMods.Abstractions.Games/Features.cs @@ -0,0 +1,19 @@ +using JetBrains.Annotations; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace NexusMods.Abstractions.Games; + +/// +/// List of features. +/// +[PublicAPI] +public static class BaseFeatures +{ + public static readonly Feature GameLocatable = new(Description: "The game can be located."); + + public static readonly Feature HasInstallers = new(Description: "The extension provides mod installers."); + + public static readonly Feature HasDiagnostics = new(Description: "The extension provides diagnostics."); + + public static readonly Feature HasLoadOrder = new(Description: "The extension provides load-order support."); +} diff --git a/src/Abstractions/NexusMods.Abstractions.Games/IGame.cs b/src/Abstractions/NexusMods.Abstractions.Games/IGame.cs index 418bcbfe13..9815ec9fee 100644 --- a/src/Abstractions/NexusMods.Abstractions.Games/IGame.cs +++ b/src/Abstractions/NexusMods.Abstractions.Games/IGame.cs @@ -13,6 +13,10 @@ namespace NexusMods.Abstractions.Games; /// public interface IGame : ILocatableGame { + SupportType SupportType { get; } + HashSet Features { get; } + GameFeatureStatus FeatureStatus => Features.ToStatus(); + /// /// Stream factory for the game's icon, must be square but need not be small. /// diff --git a/src/Abstractions/NexusMods.Abstractions.Games/SupportLevel.cs b/src/Abstractions/NexusMods.Abstractions.Games/SupportLevel.cs new file mode 100644 index 0000000000..255956ceea --- /dev/null +++ b/src/Abstractions/NexusMods.Abstractions.Games/SupportLevel.cs @@ -0,0 +1,25 @@ +using JetBrains.Annotations; + +namespace NexusMods.Abstractions.Games; + +/// +/// Game support type. +/// +[PublicAPI] +public enum SupportType +{ + /// + /// The game is unsupported. + /// + Unsupported = 0, + + /// + /// The game is officially supported and the extension maintained by Nexus Mods. + /// + Official = 1, + + /// + /// The game is supported and the extension is maintained by the community. + /// + Community = 2, +} diff --git a/src/Games/NexusMods.Games.Larian/BaldursGate3/BaldursGate3.cs b/src/Games/NexusMods.Games.Larian/BaldursGate3/BaldursGate3.cs index 23f3907e54..95110529a6 100644 --- a/src/Games/NexusMods.Games.Larian/BaldursGate3/BaldursGate3.cs +++ b/src/Games/NexusMods.Games.Larian/BaldursGate3/BaldursGate3.cs @@ -27,6 +27,15 @@ public class BaldursGate3 : AGame, ISteamGame, IGogGame public IEnumerable SteamIds => [1086940u]; public IEnumerable GogIds => [1456460669]; public override GameId GameId => GameId.From(3474); + public override SupportType SupportType => SupportType.Official; + + public override HashSet Features { get; } = + [ + new(BaseFeatures.GameLocatable, IsImplemented: true), + new(BaseFeatures.HasInstallers, IsImplemented: true), + new(BaseFeatures.HasDiagnostics, IsImplemented: true), + new(BaseFeatures.HasLoadOrder, IsImplemented: false), + ]; public BaldursGate3(IServiceProvider provider) : base(provider) { diff --git a/src/Games/NexusMods.Games.MountAndBlade2Bannerlord/MountAndBlade2Bannerlord.cs b/src/Games/NexusMods.Games.MountAndBlade2Bannerlord/MountAndBlade2Bannerlord.cs index 8451f2a00f..5baecdf550 100644 --- a/src/Games/NexusMods.Games.MountAndBlade2Bannerlord/MountAndBlade2Bannerlord.cs +++ b/src/Games/NexusMods.Games.MountAndBlade2Bannerlord/MountAndBlade2Bannerlord.cs @@ -35,7 +35,16 @@ public sealed class MountAndBlade2Bannerlord : AGame, ISteamGame, IGogGame, IEpi public override string Name => DisplayName; public override GameId GameId => GameIdStatic; - + public override SupportType SupportType => SupportType.Official; + + public override HashSet Features { get; } = + [ + new(BaseFeatures.GameLocatable, IsImplemented: true), + new(BaseFeatures.HasInstallers, IsImplemented: true), + new(BaseFeatures.HasDiagnostics, IsImplemented: false), + new(BaseFeatures.HasLoadOrder, IsImplemented: false), + ]; + public IEnumerable SteamIds => [261550u]; public IEnumerable GogIds => [1802539526, 1564781494]; public IEnumerable EpicCatalogItemId => ["Chickadee"]; diff --git a/src/Games/NexusMods.Games.RedEngine/Cyberpunk2077/Cyberpunk2077Game.cs b/src/Games/NexusMods.Games.RedEngine/Cyberpunk2077/Cyberpunk2077Game.cs index a543ab1834..d6fc6eb97b 100644 --- a/src/Games/NexusMods.Games.RedEngine/Cyberpunk2077/Cyberpunk2077Game.cs +++ b/src/Games/NexusMods.Games.RedEngine/Cyberpunk2077/Cyberpunk2077Game.cs @@ -34,6 +34,16 @@ protected override ILoadoutSynchronizer MakeSynchronizer(IServiceProvider provid public override string Name => "Cyberpunk 2077"; public override GameId GameId => GameIdStatic; + public override SupportType SupportType => SupportType.Official; + + public override HashSet Features { get; } = + [ + new(BaseFeatures.GameLocatable, IsImplemented: true), + new(BaseFeatures.HasInstallers, IsImplemented: true), + new(BaseFeatures.HasDiagnostics, IsImplemented: true), + new(BaseFeatures.HasLoadOrder, IsImplemented: false), + ]; + public override GamePath GetPrimaryFile(GameStore store) => new(LocationId.Game, "bin/x64/Cyberpunk2077.exe"); protected override IReadOnlyDictionary GetLocations(IFileSystem fileSystem, GameLocatorResult installation) diff --git a/src/Games/NexusMods.Games.StardewValley/StardewValley.cs b/src/Games/NexusMods.Games.StardewValley/StardewValley.cs index 695f2a7699..7513d4aee4 100644 --- a/src/Games/NexusMods.Games.StardewValley/StardewValley.cs +++ b/src/Games/NexusMods.Games.StardewValley/StardewValley.cs @@ -32,6 +32,15 @@ public class StardewValley : AGame, ISteamGame, IGogGame, IXboxGame public override string Name => "Stardew Valley"; public override GameId GameId => GameId.From(1303); + public override SupportType SupportType => SupportType.Official; + + public override HashSet Features { get; } = + [ + new(BaseFeatures.GameLocatable, IsImplemented: true), + new(BaseFeatures.HasInstallers, IsImplemented: true), + new(BaseFeatures.HasDiagnostics, IsImplemented: true), + ]; + public StardewValley( IOSInformation osInformation, IEnumerable gameLocators, diff --git a/src/Games/NexusMods.Games.StardewValley/StardewValleyLoadoutSynchronizer.cs b/src/Games/NexusMods.Games.StardewValley/StardewValleyLoadoutSynchronizer.cs index f2936049a8..1a9d4669fd 100644 --- a/src/Games/NexusMods.Games.StardewValley/StardewValleyLoadoutSynchronizer.cs +++ b/src/Games/NexusMods.Games.StardewValley/StardewValleyLoadoutSynchronizer.cs @@ -39,8 +39,6 @@ protected override ValueTask MoveNewFilesToMods(Loadout.ReadOnly loadout, IEnume foreach (var newFile in newFiles) { - GamePath gamePath; - if (!IsModFile(newFile.LoadoutItemWithTargetPath.TargetPath, out var modDirectoryName)) { continue; diff --git a/tests/NexusMods.StandardGameLocators.TestHelpers/StubbedGames/StubbedGame.cs b/tests/NexusMods.StandardGameLocators.TestHelpers/StubbedGames/StubbedGame.cs index 48521975a7..61d5a2b74a 100644 --- a/tests/NexusMods.StandardGameLocators.TestHelpers/StubbedGames/StubbedGame.cs +++ b/tests/NexusMods.StandardGameLocators.TestHelpers/StubbedGames/StubbedGame.cs @@ -28,6 +28,8 @@ public class StubbedGame : AGame, IEADesktopGame, IEpicGame, IOriginGame, ISteam public override string Name => "Stubbed Game"; public override GameId GameId => GameId.From(uint.MaxValue); + public override SupportType SupportType => SupportType.Unsupported; + private readonly IServiceProvider _serviceProvider; public StubbedGame(ILogger logger, IEnumerable locators, IFileSystem fileSystem, IServiceProvider provider) : base(provider)