diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 2527e17..a5c096e 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -20,6 +20,8 @@ jobs: dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore + - name: Test + run: dotnet test GangsTest --no-build --verbosity normal - name: Build run: dotnet build --no-restore - name: Test diff --git a/Gangs.sln b/Gangs.sln index aa5e2c9..a64e601 100644 --- a/Gangs.sln +++ b/Gangs.sln @@ -2,7 +2,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GangsAPI", "GangsAPI\GangsAPI.csproj", "{787D12D8-1310-4042-ADCE-102E517F269E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GangsImpl", "GangsImpl\GangsImpl.csproj", "{3EA38296-9022-4874-8309-872388D884DE}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GangsPlugin", "GangsPlugin\GangsPlugin.csproj", "{3EA38296-9022-4874-8309-872388D884DE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GangsTest", "GangsTest\GangsTest.csproj", "{B1D1E7C7-BDF3-4238-9025-4FEB2B7DAB89}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GangsImpl.Memory", "GangsImpl.Memory\GangsImpl.Memory.csproj", "{140E1706-30E8-4440-AAA0-56E8DD32F054}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -18,5 +22,13 @@ Global {3EA38296-9022-4874-8309-872388D884DE}.Debug|Any CPU.Build.0 = Debug|Any CPU {3EA38296-9022-4874-8309-872388D884DE}.Release|Any CPU.ActiveCfg = Release|Any CPU {3EA38296-9022-4874-8309-872388D884DE}.Release|Any CPU.Build.0 = Release|Any CPU + {B1D1E7C7-BDF3-4238-9025-4FEB2B7DAB89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1D1E7C7-BDF3-4238-9025-4FEB2B7DAB89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1D1E7C7-BDF3-4238-9025-4FEB2B7DAB89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1D1E7C7-BDF3-4238-9025-4FEB2B7DAB89}.Release|Any CPU.Build.0 = Release|Any CPU + {140E1706-30E8-4440-AAA0-56E8DD32F054}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {140E1706-30E8-4440-AAA0-56E8DD32F054}.Debug|Any CPU.Build.0 = Debug|Any CPU + {140E1706-30E8-4440-AAA0-56E8DD32F054}.Release|Any CPU.ActiveCfg = Release|Any CPU + {140E1706-30E8-4440-AAA0-56E8DD32F054}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/GangsImpl/Extensions/ServiceCollectionExtensions.cs b/GangsAPI/Extensions/ServiceCollectionExtensions.cs similarity index 94% rename from GangsImpl/Extensions/ServiceCollectionExtensions.cs rename to GangsAPI/Extensions/ServiceCollectionExtensions.cs index f1f125c..476939a 100644 --- a/GangsImpl/Extensions/ServiceCollectionExtensions.cs +++ b/GangsAPI/Extensions/ServiceCollectionExtensions.cs @@ -1,7 +1,6 @@ -using GangsAPI; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; -namespace GangsImpl.Extensions; +namespace GangsAPI.Extensions; public static class ServiceCollectionExtensions { /// diff --git a/GangsAPI/GangsAPI.csproj b/GangsAPI/GangsAPI.csproj index 056dd6f..92428ab 100644 --- a/GangsAPI/GangsAPI.csproj +++ b/GangsAPI/GangsAPI.csproj @@ -5,9 +5,13 @@ enable enable - - + + + + + ..\..\..\..\..\.nuget\packages\counterstrikesharp.api\1.0.263\lib\net8.0\CounterStrikeSharp.API.dll + diff --git a/GangsAPI/IBehavior.cs b/GangsAPI/IBehavior.cs new file mode 100644 index 0000000..8c320e6 --- /dev/null +++ b/GangsAPI/IBehavior.cs @@ -0,0 +1,6 @@ +namespace GangsAPI; + +public interface IBehavior : IDisposable { + void Start(); + void IDisposable.Dispose() { } +} \ No newline at end of file diff --git a/GangsAPI/IPluginBehavior.cs b/GangsAPI/IPluginBehavior.cs index 5c6e06f..6a0ff21 100644 --- a/GangsAPI/IPluginBehavior.cs +++ b/GangsAPI/IPluginBehavior.cs @@ -2,10 +2,10 @@ namespace GangsAPI; -public interface IPluginBehavior : IDisposable { - void IDisposable.Dispose() { } +public interface IPluginBehavior : IBehavior { + internal void Start(BasePlugin? plugin) { } - internal void Start(BasePlugin plugin) { } + void Start(BasePlugin? plugin, bool hotReload) { Start(plugin); } - void Start(BasePlugin plugin, bool hotReload) { Start(plugin); } + void IBehavior.Start() { Start(null); } } \ No newline at end of file diff --git a/GangsAPI/Permissions/IGangRank.cs b/GangsAPI/Permissions/IGangRank.cs index a61f5ed..8dfb19f 100644 --- a/GangsAPI/Permissions/IGangRank.cs +++ b/GangsAPI/Permissions/IGangRank.cs @@ -1,81 +1,81 @@ namespace GangsAPI.Permissions; public interface IGangRank { - string Name { get; } - int Rank { get; } - Permissions Perms { get; } - [Flags] public enum Permissions { /// - /// The member may invite others to the gang. + /// The member may invite others to the gang. /// INVITE_OTHERS = 1 << 0, /// - /// The member may kick others from the gang. + /// The member may kick others from the gang. /// KICK_OTHERS = 1 << 1, /// - /// The member may deposit money into the gang bank. - /// This also allows the member to use their own personal - /// funds to purchase perks for the gang. + /// The member may deposit money into the gang bank. + /// This also allows the member to use their own personal + /// funds to purchase perks for the gang. /// BANK_DEPOSIT = 1 << 2, /// - /// The member may withdraw money from the gang bank. - /// This also allows the member to use the gang's funds - /// to purchase perks for the gang. + /// The member may withdraw money from the gang bank. + /// This also allows the member to use the gang's funds + /// to purchase perks for the gang. /// BANK_WITHDRAW = 1 << 3, /// - /// The member may promote others, the maximum rank that - /// they may promote to is determined by the rank system. + /// The member may promote others, the maximum rank that + /// they may promote to is determined by the rank system. /// PROMOTE_OTHERS = 1 << 4, /// - /// The member may demote others. + /// The member may demote others. /// DEMOTE_OTHERS = 1 << 5, /// - /// The member may purchase perks for the gang. + /// The member may purchase perks for the gang. /// PURCHASE_PERKS = 1 << 6, /// - /// The member may manage or configure perks for the gang. + /// The member may manage or configure perks for the gang. /// MANAGE_PERKS = 1 << 7, /// - /// The member may manage the ranks of the gang, regardless - /// of rank system, the member will not be able to manage - /// their own rank. + /// The member may manage the ranks of the gang, regardless + /// of rank system, the member will not be able to manage + /// their own rank. /// MANAGE_RANKS = 1 << 8, /// - /// The member may create new ranks for the gang. - /// All ranks created must not have a rank higher than - /// the member's current rank. Depending on the rank system, - /// these ranks may also be required to have a rank lower - /// than the member's current rank. + /// The member may create new ranks for the gang. + /// All ranks created must not have a rank higher than + /// the member's current rank. Depending on the rank system, + /// these ranks may also be required to have a rank lower + /// than the member's current rank. /// CREATE_RANKS = 1 << 9, /// - /// The member has full access to all permissions. + /// The member has full access to all permissions. /// ADMINISTRATOR = 1 << 10, /// - /// The member is the owner of the gang, and can not be kicked. + /// The member is the owner of the gang, and can not be kicked. /// OWNER = 1 << 11 } + + string Name { get; } + int Rank { get; } + Permissions Perms { get; } } \ No newline at end of file diff --git a/GangsAPI/Permissions/IPermDictator.cs b/GangsAPI/Permissions/IPermDictator.cs index 6bd4c4b..4e033c2 100644 --- a/GangsAPI/Permissions/IPermDictator.cs +++ b/GangsAPI/Permissions/IPermDictator.cs @@ -1,7 +1,7 @@ namespace GangsAPI.Permissions; /// -/// The dictator of permissions. +/// The dictator of permissions. /// public interface IPermDictator : IPluginBehavior { bool CanTarget(IGangRank source, IGangRank target); diff --git a/GangsAPI/Services/IGangManager.cs b/GangsAPI/Services/IGangManager.cs index c43671b..96be2c3 100644 --- a/GangsAPI/Services/IGangManager.cs +++ b/GangsAPI/Services/IGangManager.cs @@ -1,58 +1,58 @@ -using GangsAPI.Struct; -using GangsAPI.Struct.Gang; +using GangsAPI.Struct.Gang; namespace GangsAPI.Services; /// -/// A manager for gangs. Allows for the creation, retrieval, updating, and deletion of gangs. -/// The Gang Manager should not be used to manage perks or stats. -/// Use the respective and to manage those. +/// A manager for gangs. Allows for the creation, retrieval, updating, and deletion of gangs. +/// The Gang Manager should not be used to manage perks or stats. +/// Use the respective and to manage those. /// public interface IGangManager : IPluginBehavior { /// - /// Gets all gangs. + /// Gets all gangs. /// /// The collection of all gangs, or an empty collection if there are none. Task> GetGangs(); /// - /// Gets a gang by its ID. + /// Gets a gang by its ID. /// /// The gang associated with the given id, or null if there is not one. /// Task GetGang(int id); /// - /// Gets a gang by the steam ID of one of its members. - /// In theory, a player could be in multiple gangs, but this method - /// in which case this behavior is undefined. + /// Gets a gang by the steam ID of one of its members. + /// In theory, a player could be in multiple gangs, but this method + /// in which case this behavior is undefined. /// /// /// Task GetGang(ulong steam); /// - /// Pushes a gang to the database. - /// Used for updating or creating gangs. + /// Pushes a gang to the database. + /// Used for updating or creating gangs. /// /// /// True if the update was successful Task PushGang(IGang gang); /// - /// Deletes a gang by its ID. + /// Deletes a gang by its ID. /// /// The ID of the gang to delete. /// True if deletion was succesful Task DeleteGang(int id); /// - /// Creates a gang with the given name and owner. + /// Creates a gang with the given name and owner. /// /// The name of the gang /// The owner of the gang - /// The newly created (and populated, specifically the id) gang. - /// If there was an error, this method will return null. + /// + /// The newly created (and populated, specifically the id) gang. + /// If there was an error, this method will return null. /// Task CreateGang(string name, ulong owner); diff --git a/GangsAPI/Services/IGangStatManager.cs b/GangsAPI/Services/IGangStatManager.cs new file mode 100644 index 0000000..d79cf04 --- /dev/null +++ b/GangsAPI/Services/IGangStatManager.cs @@ -0,0 +1,8 @@ +using GangsAPI.Struct.Stat; + +namespace GangsAPI.Services; + +public interface IGangStatManager { + Task?> GetForGang(int key, string id); + Task PushToGang(int key, string id, V value); +} \ No newline at end of file diff --git a/GangsAPI/Services/IPerkManager.cs b/GangsAPI/Services/IPerkManager.cs deleted file mode 100644 index be97620..0000000 --- a/GangsAPI/Services/IPerkManager.cs +++ /dev/null @@ -1,52 +0,0 @@ -using GangsAPI.Struct; -using GangsAPI.Struct.Perk; - -namespace GangsAPI.Services; - -/// -/// A manager for perks. Allows for the registration, retrieval, and updating of perks. -/// -public interface IPerkManager : IPluginBehavior { - /// - /// Retrieves all perks. - /// - /// - Task> GetPerks(); - - /// - /// Retrieves a perk by its ID. - /// - /// - /// - Task GetPerk(string id); - - /// - /// Registers a perk with the manager. - /// - /// - /// - Task RegisterPerk(IPerk perk); - - /// - /// Unregisters a perk with the manager. - /// - /// - /// - Task UnregisterPerk(string id); - - /// - /// Updates a perk with the manager. - /// - /// - /// - Task UpdatePerk(IPerk perk); - - /// - /// Updates an instance of a perk with the manager. - /// - /// - /// - /// - /// - Task UpdatePerk(IPerk perk); -} \ No newline at end of file diff --git a/GangsAPI/Services/IPlayerManager.cs b/GangsAPI/Services/IPlayerManager.cs index 7fef606..c477df3 100644 --- a/GangsAPI/Services/IPlayerManager.cs +++ b/GangsAPI/Services/IPlayerManager.cs @@ -1,21 +1,20 @@ -using GangsAPI.Struct; -using GangsAPI.Struct.Gang; +using GangsAPI.Struct.Gang; namespace GangsAPI.Services; /// -/// A manager for players. Allows for the retrieval and creation of players. +/// A manager for players. Allows for the retrieval and creation of players. /// public interface IPlayerManager : IPluginBehavior { /// - /// Gets a player by their SteamID64, creating them if they do not exist. + /// Gets a player by their SteamID64, creating them if they do not exist. /// /// The SteamID64 of the player. /// The player, or null if there was an error creating one. Task GetPlayer(ulong steamId); /// - /// Gets a player by their SteamID64. + /// Gets a player by their SteamID64. /// /// The SteamID64 of the player. /// True if the manager should create a new player if they don't exist. @@ -28,7 +27,7 @@ public interface IPlayerManager : IPluginBehavior { } /// - /// Creates a new player. + /// Creates a new player. /// /// The SteamID64 of the player. /// The name of the player. @@ -36,7 +35,7 @@ public interface IPlayerManager : IPluginBehavior { Task CreatePlayer(ulong steamId, string? name = null); /// - /// Deletes a player and all of their associated data. + /// Deletes a player and all of their associated data. /// /// The SteamID64 of the player. /// True if the player was deleted, false otherwise. diff --git a/GangsAPI/Services/IPlayerStatManager.cs b/GangsAPI/Services/IPlayerStatManager.cs new file mode 100644 index 0000000..a45d642 --- /dev/null +++ b/GangsAPI/Services/IPlayerStatManager.cs @@ -0,0 +1,8 @@ +using GangsAPI.Struct.Stat; + +namespace GangsAPI.Services; + +public interface IPlayerStatManager { + Task?> GetForPlayer(ulong key, string id); + Task PushToPlayer(ulong key, string id, V value); +} \ No newline at end of file diff --git a/GangsAPI/Services/IStatInstanceManager.cs b/GangsAPI/Services/IStatInstanceManager.cs new file mode 100644 index 0000000..b8ffc49 --- /dev/null +++ b/GangsAPI/Services/IStatInstanceManager.cs @@ -0,0 +1,24 @@ +using GangsAPI.Struct.Stat; + +namespace GangsAPI.Services; + +public interface IStatInstanceManager : IStatManager { + /// + /// Updates an instance of a statistic with the manager. + /// + /// + /// + /// + /// + Task UpdateStat(IStat stat); + + /// + /// Retrieves an instance of a statistic by its key and ID. + /// + /// + /// + /// + /// + /// + Task?> GetStat(K key, string id); +} \ No newline at end of file diff --git a/GangsAPI/Services/IStatManager.cs b/GangsAPI/Services/IStatManager.cs index 1ad863a..d0d2632 100644 --- a/GangsAPI/Services/IStatManager.cs +++ b/GangsAPI/Services/IStatManager.cs @@ -1,52 +1,53 @@ -using GangsAPI.Struct; -using GangsAPI.Struct.Stat; +using GangsAPI.Struct.Stat; namespace GangsAPI.Services; /// -/// A manager for statistics. Allows for the registration, retrieval, and updating of statistics. +/// A manager for statistics. Allows for the registration, retrieval, and updating of statistics. /// public interface IStatManager : IPluginBehavior { /// - /// Retrieves all statistics. + /// Retrieves all statistics. /// /// Task> GetStats(); /// - /// Retrieves a statistic by its ID. + /// Retrieves a statistic by its ID. /// /// /// Task GetStat(string id); /// - /// Registers a statistic with the manager. + /// Creates a statistic with the manager, but does not register it. + /// If the statistic already exists with the same ID, + /// it will return the existing statistic. + /// + /// + /// + /// + /// + Task CreateStat(string id, string name, string? description = null); + + /// + /// Registers a statistic with the manager. /// /// /// Task RegisterStat(IStat stat); /// - /// Unregisters a statistic with the manager. + /// Unregisters a statistic with the manager. /// /// /// Task UnregisterStat(string id); /// - /// Updates a statistic with the manager. + /// Updates a statistic with the manager. /// /// /// Task UpdateStat(IStat stat); - - /// - /// Updates an instance of a statistic with the manager. - /// - /// - /// - /// - /// - Task UpdateStat(IStat stat); } \ No newline at end of file diff --git a/GangsAPI/Struct/Gang/IGang.cs b/GangsAPI/Struct/Gang/IGang.cs index 357599e..030bc54 100644 --- a/GangsAPI/Struct/Gang/IGang.cs +++ b/GangsAPI/Struct/Gang/IGang.cs @@ -1,33 +1,32 @@ using GangsAPI.Permissions; -using GangsAPI.Struct.Perk; using GangsAPI.Struct.Stat; namespace GangsAPI.Struct.Gang; /// -/// Represents an instance of a gang. +/// Represents an instance of a gang. /// public interface IGang : IEqualityComparer { /// - /// The unique identifier of the gang. - /// All gangs have a unique identifier. + /// The unique identifier of the gang. + /// All gangs have a unique identifier. /// int Id { get; } /// - /// The name of the gang. - /// Gang names are not necessarily unique. - /// (Up to implementation) + /// The name of the gang. + /// Gang names are not necessarily unique. + /// (Up to implementation) /// string Name { get; set; } /// - /// The members of the gang. + /// The members of the gang. /// IDictionary Members { get; } /// - /// The amount of currency the gang has in its bank. + /// The amount of currency the gang has in its bank. /// int? Bank { get { @@ -43,42 +42,42 @@ public interface IGang : IEqualityComparer { } /// - /// The set of perks the gang has. + /// The set of perks the gang has. /// - ISet Perks { get; } + ISet Perks { get; } /// - /// The set of statistics the gang has. + /// The set of statistics the gang has. /// ISet Stats { get; } /// - /// The gang's capacity. Underlying implementation - /// should be a perk with the id "gang_capacity". + /// The gang's capacity. Underlying implementation + /// should be a perk with the id "gang_capacity". /// /// int? GangCapacity { get { - var perk = GetPerk("gang_capacity") as IGangPerk; + var perk = GetPerk("gang_capacity") as IGangStat; return perk?.Value; } set { ArgumentNullException.ThrowIfNull(value); - if (GetPerk("gang_capacity") is IGangPerk perk) + if (GetPerk("gang_capacity") is IGangStat perk) perk.Value = value.Value; } } /// - /// The gang's "message of the day". - /// Underlying implementation should be a perk with the id "gang_motd". + /// The gang's "message of the day". + /// Underlying implementation should be a perk with the id "gang_motd". /// /// string? MOTD { get { - var perk = GetPerk("gang_motd") as IGangPerk; + var perk = GetPerk("gang_motd") as IGangStat; return perk?.Value; } @@ -87,18 +86,10 @@ public interface IGang : IEqualityComparer { throw new ArgumentNullException(nameof(value), "To un-set MOTD, remove the perk from the gang."); - if (GetPerk("gang_motd") is IGangPerk perk) perk.Value = value; + if (GetPerk("gang_motd") is IGangStat perk) perk.Value = value; } } - IPerk? GetPerk(string perkId) { - return Perks.FirstOrDefault(p => p.PerkId.Equals(perkId)); - } - - IStat? GetStat(string statId) { - return Stats.FirstOrDefault(s => s.StatId.Equals(statId)); - } - bool IEqualityComparer.Equals(IGang? x, IGang? y) { if (x is null || y is null) return false; return x.Id == y.Id; @@ -107,4 +98,12 @@ bool IEqualityComparer.Equals(IGang? x, IGang? y) { int IEqualityComparer.GetHashCode(IGang obj) { return obj.Id.GetHashCode(); } + + IStat? GetPerk(string perkId) { + return Perks.FirstOrDefault(p => p.StatId.Equals(perkId)); + } + + IStat? GetStat(string statId) { + return Stats.FirstOrDefault(s => s.StatId.Equals(statId)); + } } \ No newline at end of file diff --git a/GangsAPI/Struct/Gang/IGangPlayer.cs b/GangsAPI/Struct/Gang/IGangPlayer.cs index c8ed51f..de8b9db 100644 --- a/GangsAPI/Struct/Gang/IGangPlayer.cs +++ b/GangsAPI/Struct/Gang/IGangPlayer.cs @@ -4,32 +4,32 @@ namespace GangsAPI.Struct.Gang; /// -/// A gang player is a player tracked by the gangs plugin. -/// They are not necessarily a member of a gang. +/// A gang player is a player tracked by the gangs plugin. +/// They are not necessarily a member of a gang. /// public interface IGangPlayer { /// - /// The SteamID64 of the player. + /// The SteamID64 of the player. /// ulong Steam { get; } /// - /// The cached name of the player. + /// The cached name of the player. /// string? Name { get; } /// - /// The id of the gang that the player is a member of. + /// The id of the gang that the player is a member of. /// int? GangId { get; } /// - /// The rank the player has in the gang (if in one). + /// The rank the player has in the gang (if in one). /// IGangRank? Rank { get; } /// - /// The last time the player was seen by the plugin. + /// The last time the player was seen by the plugin. /// DateTime? LastSeen { get { @@ -38,7 +38,8 @@ public interface IGangPlayer { } set { - if (GetStat("last_seen") is IStat stat) stat.Value = value; + if (GetStat("last_seen") is IStat stat) + stat.Value = value; } } @@ -52,7 +53,8 @@ public interface IGangPlayer { set { ArgumentNullException.ThrowIfNull(value); - if (GetStat("balance") is IStat stat) stat.Value = value.Value; + if (GetStat("balance") is IStat stat) + stat.Value = value.Value; } } diff --git a/GangsAPI/Struct/Perk/IGangPerk.cs b/GangsAPI/Struct/Perk/IGangPerk.cs deleted file mode 100644 index d545230..0000000 --- a/GangsAPI/Struct/Perk/IGangPerk.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace GangsAPI.Struct.Perk; - -public interface IGangPerk : IPerk { } \ No newline at end of file diff --git a/GangsAPI/Struct/Perk/IPerk.cs b/GangsAPI/Struct/Perk/IPerk.cs deleted file mode 100644 index a2440fe..0000000 --- a/GangsAPI/Struct/Perk/IPerk.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace GangsAPI.Struct.Perk; - -public interface IPerk : IEqualityComparer { - /// - /// The unique identifier of the perk. - /// - string PerkId { get; } - - /// - /// The name of the perk. - /// - string Name { get; } - - /// - /// A description of the perk. - /// - string? Description { get; } - - bool IEqualityComparer.Equals(IPerk? x, IPerk? y) { - if (x is null || y is null) return false; - return x.PerkId == y.PerkId; - } - - int IEqualityComparer.GetHashCode(IPerk obj) { - return obj.PerkId.GetHashCode(); - } -} - -public interface IPerk : IPerk { - /// - /// A key identifying an instance of the perk. - /// - K Key { get; init; } - - /// - /// The value of the perk. - /// - T Value { get; set; } -} \ No newline at end of file diff --git a/GangsAPI/Struct/Perk/IPerkNumeric.cs b/GangsAPI/Struct/Perk/IPerkNumeric.cs deleted file mode 100644 index 1bceccd..0000000 --- a/GangsAPI/Struct/Perk/IPerkNumeric.cs +++ /dev/null @@ -1,5 +0,0 @@ -using System.Numerics; - -namespace GangsAPI.Struct.Perk; - -public interface IPerkNumeric : IPerk where T : INumber { } \ No newline at end of file diff --git a/GangsAPI/Struct/Perk/IPlayerPerk.cs b/GangsAPI/Struct/Perk/IPlayerPerk.cs deleted file mode 100644 index d09acd8..0000000 --- a/GangsAPI/Struct/Perk/IPlayerPerk.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace GangsAPI.Struct.Perk; - -public interface IPlayerPerk : IPerk { } \ No newline at end of file diff --git a/GangsAPI/Struct/Stat/IStat.cs b/GangsAPI/Struct/Stat/IStat.cs index 85ffa3e..e0104b7 100644 --- a/GangsAPI/Struct/Stat/IStat.cs +++ b/GangsAPI/Struct/Stat/IStat.cs @@ -1,21 +1,21 @@ namespace GangsAPI.Struct.Stat; /// -/// Represents a numerical statistic. +/// Represents a numerical statistic. /// -public interface IStat : IEqualityComparer { +public interface IStat : IEqualityComparer, IEquatable { /// - /// The unique identifier of the statistic. + /// The unique identifier of the statistic. /// string StatId { get; } /// - /// The name of the statistic. + /// The name of the statistic. /// string Name { get; } /// - /// A description of the statistic. + /// A description of the statistic. /// string? Description { get; } @@ -27,16 +27,21 @@ bool IEqualityComparer.Equals(IStat? x, IStat? y) { int IEqualityComparer.GetHashCode(IStat obj) { return obj.StatId.GetHashCode(); } + + bool IEquatable.Equals(IStat? other) { + if (other is null) return false; + return StatId == other.StatId; + } } public interface IStat : IStat { /// - /// A key identifying an instance of the statistic. + /// A key identifying an instance of the statistic. /// K Key { get; init; } /// - /// The value of the statistic. + /// The value of the statistic. /// T Value { get; set; } } \ No newline at end of file diff --git a/GangsImpl.Memory/EphemeralStat.cs b/GangsImpl.Memory/EphemeralStat.cs new file mode 100644 index 0000000..d9dfc8d --- /dev/null +++ b/GangsImpl.Memory/EphemeralStat.cs @@ -0,0 +1,9 @@ +using GangsAPI.Struct.Stat; + +namespace GangsImpl.Memory; + +public class EphemeralStat(string statId, string name, string? desc) : IStat { + public string StatId { get; } = statId; + public string Name { get; } = name; + public string? Description { get; } = desc; +} \ No newline at end of file diff --git a/GangsImpl.Memory/GangsImpl.Memory.csproj b/GangsImpl.Memory/GangsImpl.Memory.csproj new file mode 100644 index 0000000..8ac23e8 --- /dev/null +++ b/GangsImpl.Memory/GangsImpl.Memory.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + diff --git a/GangsImpl.Memory/MemoryImpl.cs b/GangsImpl.Memory/MemoryImpl.cs new file mode 100644 index 0000000..2342f2e --- /dev/null +++ b/GangsImpl.Memory/MemoryImpl.cs @@ -0,0 +1,11 @@ +using GangsAPI.Extensions; +using GangsAPI.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace GangsImpl.Memory; + +public static class MemoryImpl { + public static void AddMemoryImpl(this IServiceCollection collection) { + collection.AddPluginBehavior(); + } +} \ No newline at end of file diff --git a/GangsImpl.Memory/MemoryStatManager.cs b/GangsImpl.Memory/MemoryStatManager.cs new file mode 100644 index 0000000..dabdc0f --- /dev/null +++ b/GangsImpl.Memory/MemoryStatManager.cs @@ -0,0 +1,41 @@ +using GangsAPI.Services; +using GangsAPI.Struct.Stat; + +namespace GangsImpl.Memory; + +public class MemoryStatManager : IStatManager { + private readonly HashSet stats = new(); + + public Task> GetStats() { + return Task.FromResult>(stats); + } + + public Task GetStat(string id) { + return Task.FromResult(stats.FirstOrDefault(stat => stat.StatId == id)); + } + + public async Task CreateStat(string id, string name, + string? description = null) { + var stat = await GetStat(id); + if (stat != null) return stat; + stat = new EphemeralStat(id, name, description); + return stat; + } + + public Task RegisterStat(IStat stat) { + return Task.FromResult(stats.Add(stat)); + } + + public Task UnregisterStat(string id) { + var matches = stats.Where(stat => stat.StatId == id).ToList(); + foreach (var stat in matches) stats.Remove(stat); + return Task.FromResult(matches.Count > 0); + } + + public Task UpdateStat(IStat stat) { + var matches = stats.Where(s => s.StatId == stat.StatId).ToList(); + foreach (var match in matches) stats.Remove(match); + stats.Add(stat); + return Task.FromResult(matches.Count > 0); + } +} \ No newline at end of file diff --git a/GangsImpl/Implementations/Memory/MemoryImpl.cs b/GangsImpl/Implementations/Memory/MemoryImpl.cs deleted file mode 100644 index afe2473..0000000 --- a/GangsImpl/Implementations/Memory/MemoryImpl.cs +++ /dev/null @@ -1,11 +0,0 @@ -using GangsAPI.Services; -using GangsImpl.Extensions; -using Microsoft.Extensions.DependencyInjection; - -namespace GangsImpl.Implementations.Memory; - -public static class MemoryImpl { - public static void AddFlatFileImpl(this IServiceCollection collection) { - collection.AddPluginBehavior(); - } -} \ No newline at end of file diff --git a/GangsImpl/Implementations/Memory/StatManager.cs b/GangsImpl/Implementations/Memory/StatManager.cs deleted file mode 100644 index e083192..0000000 --- a/GangsImpl/Implementations/Memory/StatManager.cs +++ /dev/null @@ -1,32 +0,0 @@ -using GangsAPI.Services; -using GangsAPI.Struct.Stat; - -namespace GangsImpl.Implementations.Memory; - -public class StatManager : IStatManager { - private ISet stats = new HashSet(); - - public Task> GetStats() { - return Task.FromResult>(stats); - } - - public Task GetStat(string id) { - return Task.FromResult(stats.FirstOrDefault(stat => stat.StatId == id)); - } - - public Task RegisterStat(IStat stat) { - return Task.FromResult(stats.Add(stat)); - } - - public Task UnregisterStat(string id) { - throw new NotImplementedException(); - } - - public Task UpdateStat(IStat stat) { - throw new NotImplementedException(); - } - - public Task UpdateStat(IStat stat) { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/GangsImpl/GangPlugin.cs b/GangsPlugin/GangPlugin.cs similarity index 90% rename from GangsImpl/GangPlugin.cs rename to GangsPlugin/GangPlugin.cs index 31b12df..8876eea 100644 --- a/GangsImpl/GangPlugin.cs +++ b/GangsPlugin/GangPlugin.cs @@ -1,21 +1,18 @@ using System.Collections.Immutable; using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Core.Commands; using GangsAPI; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; -namespace GangsImpl; +namespace GangsPlugin; public class GangPlugin(IServiceProvider provider) : BasePlugin, IGangPlugin { + private IServiceScope? scope; public override string ModuleName => "Gangs"; public override string ModuleVersion => "0.0.1"; public BasePlugin Base => this; public IServiceProvider Services { get; } = provider; - private IServiceScope? scope; - public override void Load(bool hotReload) { scope = Services.CreateScope(); var extensions = scope.ServiceProvider.GetServices() diff --git a/GangsImpl/GangServiceCollection.cs b/GangsPlugin/GangServiceCollection.cs similarity index 91% rename from GangsImpl/GangServiceCollection.cs rename to GangsPlugin/GangServiceCollection.cs index 1770727..95e0a7d 100644 --- a/GangsImpl/GangServiceCollection.cs +++ b/GangsPlugin/GangServiceCollection.cs @@ -2,7 +2,7 @@ using GangsAPI; using Microsoft.Extensions.DependencyInjection; -namespace GangsImpl; +namespace GangsPlugin; public class GangServiceCollection : IPluginServiceCollection { public void ConfigureServices(IServiceCollection serviceCollection) { } diff --git a/GangsImpl/GangsImpl.csproj b/GangsPlugin/GangsPlugin.csproj similarity index 53% rename from GangsImpl/GangsImpl.csproj rename to GangsPlugin/GangsPlugin.csproj index 207366a..22a754d 100644 --- a/GangsImpl/GangsImpl.csproj +++ b/GangsPlugin/GangsPlugin.csproj @@ -4,14 +4,16 @@ net8.0 enable enable + GangsImpl - + - + + diff --git a/GangsTest/GangsTest.csproj b/GangsTest/GangsTest.csproj new file mode 100644 index 0000000..cebb43f --- /dev/null +++ b/GangsTest/GangsTest.csproj @@ -0,0 +1,33 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + ..\..\..\..\..\.nuget\packages\xunit.extensibility.core\2.9.0\lib\netstandard1.1\xunit.core.dll + + + + + + + + diff --git a/GangsTest/StatTests/MemoryStatManagerTests.cs b/GangsTest/StatTests/MemoryStatManagerTests.cs new file mode 100644 index 0000000..657a4ab --- /dev/null +++ b/GangsTest/StatTests/MemoryStatManagerTests.cs @@ -0,0 +1,24 @@ +using GangsAPI.Services; + +namespace GangsTest; + +public class MemoryStatManagerTests { + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Create(IStatManager mgr) { + var dummy = await mgr.CreateStat("dummy", "name"); + Assert.NotNull(dummy); + } + + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Create_Multiple(IStatManager mgr) { + var foo = await StatTestUtil.CreateStat(mgr, "foo"); + var bar = await StatTestUtil.CreateStat(mgr, "bar"); + Assert.NotSame(foo, bar); + Assert.NotNull(foo); + Assert.NotNull(bar); + Assert.Equal("foo", foo.StatId); + Assert.Equal("bar", bar.StatId); + } +} \ No newline at end of file diff --git a/GangsTest/StatTests/StatEqualityTests.cs b/GangsTest/StatTests/StatEqualityTests.cs new file mode 100644 index 0000000..e900a51 --- /dev/null +++ b/GangsTest/StatTests/StatEqualityTests.cs @@ -0,0 +1,50 @@ +using GangsAPI.Services; + +namespace GangsTest; + +public class StatEqualityTests { + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Equality_SameEverything(IStatManager mgr) { + var foo1 = await StatTestUtil.CreateStat(mgr); + var foo2 = await StatTestUtil.CreateStat(mgr); + Assert.Equal(foo1, foo2); + Assert.StrictEqual(foo1, foo2); + } + + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Equality_DiffName(IStatManager mgr) { + var foo1 = await StatTestUtil.CreateStat(mgr); + var foo2 = await StatTestUtil.CreateStat(mgr, name: "foo"); + Assert.Equal(foo1, foo2); + Assert.StrictEqual(foo1, foo2); + } + + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Equality_DiffDesc(IStatManager mgr) { + var foo1 = await StatTestUtil.CreateStat(mgr); + var foo2 = await StatTestUtil.CreateStat(mgr, desc: "foo"); + Assert.Equal(foo1, foo2); + Assert.StrictEqual(foo1, foo2); + } + + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Equality_DiffBoth(IStatManager mgr) { + var foo1 = await StatTestUtil.CreateStat(mgr); + var foo2 = await StatTestUtil.CreateStat(mgr, name: "foo", desc: "bar"); + Assert.Equal(foo1, foo2); + Assert.StrictEqual(foo1, foo2); + } + + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Equality_DiffIDs(IStatManager mgr) { + var foo1 = await StatTestUtil.CreateStat(mgr, "foo"); + var foo2 = await StatTestUtil.CreateStat(mgr, "bar"); + Assert.NotStrictEqual(foo1, foo2); + Assert.NotEqual(foo1, foo2); + } +} \ No newline at end of file diff --git a/GangsTest/StatTests/StatFieldTests.cs b/GangsTest/StatTests/StatFieldTests.cs new file mode 100644 index 0000000..d318fed --- /dev/null +++ b/GangsTest/StatTests/StatFieldTests.cs @@ -0,0 +1,45 @@ +using GangsAPI.Services; + +namespace GangsTest; + +public class StatFieldTests { + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Fields_Name(IStatManager mgr) { + var dummy = await StatTestUtil.CreateStat(mgr); + Assert.NotNull(dummy); + Assert.Equal("name", dummy.Name); + } + + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Fields_Id(IStatManager mgr) { + var dummy = await StatTestUtil.CreateStat(mgr); + Assert.NotNull(dummy); + Assert.Equal("id", dummy.StatId); + } + + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Fields_Desc_Null(IStatManager statManager) { + var dummy = await StatTestUtil.CreateStat(statManager, desc: null); + Assert.NotNull(dummy); + Assert.Null(dummy.Description); + } + + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Fields_Desc_Empty(IStatManager statManager) { + var dummy = await StatTestUtil.CreateStat(statManager, desc: ""); + Assert.NotNull(dummy); + Assert.Equal("", dummy.Description); + } + + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Fields_Desc_Basic(IStatManager statManager) { + var dummy = await StatTestUtil.CreateStat(statManager); + Assert.NotNull(dummy); + Assert.Equal("desc", dummy.Description); + } +} \ No newline at end of file diff --git a/GangsTest/StatTests/StatManagerData.cs b/GangsTest/StatTests/StatManagerData.cs new file mode 100644 index 0000000..f850c33 --- /dev/null +++ b/GangsTest/StatTests/StatManagerData.cs @@ -0,0 +1,19 @@ +using System.Collections; +using GangsAPI; +using GangsImpl.Memory; + +namespace GangsTest; + +public class StatManagerData : IEnumerable { + private readonly IBehavior[] behaviors = [new MemoryStatManager()]; + + public StatManagerData() { + foreach (var behavior in behaviors) behavior.Start(); + } + + public IEnumerator GetEnumerator() { + return behaviors.Select(behavior => (object[]) [behavior]).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } +} \ No newline at end of file diff --git a/GangsTest/StatTests/StatRegistrationTests.cs b/GangsTest/StatTests/StatRegistrationTests.cs new file mode 100644 index 0000000..757d416 --- /dev/null +++ b/GangsTest/StatTests/StatRegistrationTests.cs @@ -0,0 +1,48 @@ +using GangsAPI.Services; + +namespace GangsTest; + +public class StatRegistrationTests { + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Register(IStatManager mgr) { + var stat = await StatTestUtil.CreateStat(mgr); + Assert.NotNull(stat); + Assert.True(await mgr.RegisterStat(stat), + "Failed to register new statistic"); + } + + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Register_Duplicate(IStatManager mgr) { + var stat = await StatTestUtil.CreateStat(mgr); + Assert.NotNull(stat); + Assert.True(await mgr.RegisterStat(stat), + "Failed to register new statistic"); + Assert.False(await mgr.RegisterStat(stat), + "Failed to recognize pre-registered statistic"); + } + + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Unregistered(IStatManager mgr) { + Assert.Null(await mgr.GetStat("id")); + await StatTestUtil.CreateStat(mgr); + Assert.Null(await mgr.GetStat("id")); + var stat = await StatTestUtil.CreateStat(mgr); + Assert.NotNull(stat); + await mgr.RegisterStat(stat); + Assert.NotNull(await mgr.GetStat("id")); + } + + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Unregister(IStatManager mgr) { + var stat = await StatTestUtil.CreateStat(mgr); + Assert.NotNull(stat); + Assert.True(await mgr.RegisterStat(stat), + "Failed to register new statistic"); + await mgr.UnregisterStat(stat.StatId); + Assert.Null(await mgr.GetStat(stat.StatId)); + } +} \ No newline at end of file diff --git a/GangsTest/StatTests/StatRetainTests.cs b/GangsTest/StatTests/StatRetainTests.cs new file mode 100644 index 0000000..5fd28e8 --- /dev/null +++ b/GangsTest/StatTests/StatRetainTests.cs @@ -0,0 +1,51 @@ +using GangsAPI.Services; + +namespace GangsTest; + +public class StatRetainTests { + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Retain(IStatManager mgr) { + var foo1 = await StatTestUtil.CreateStat(mgr); + Assert.NotNull(foo1); + await mgr.RegisterStat(foo1); + var foo2 = await StatTestUtil.CreateStat(mgr); + Assert.Same(foo1, foo2); + } + + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Retain_NonTrivial_Name(IStatManager mgr) { + var foo1 = await StatTestUtil.CreateStat(mgr, "foo", "bar"); + Assert.NotNull(foo1); + await mgr.RegisterStat(foo1); + var foo2 = await StatTestUtil.CreateStat(mgr, "foo", "foobar"); + Assert.NotNull(foo1); + Assert.NotNull(foo2); + Assert.Same(foo1, foo2); + } + + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Retain_NonTrivial_Desc(IStatManager mgr) { + var foo1 = await StatTestUtil.CreateStat(mgr, "foo", desc: "bar"); + Assert.NotNull(foo1); + await mgr.RegisterStat(foo1); + var foo2 = await StatTestUtil.CreateStat(mgr, "foo", desc: "foobar"); + Assert.NotNull(foo1); + Assert.NotNull(foo2); + Assert.Same(foo1, foo2); + } + + [Theory] + [ClassData(typeof(StatManagerData))] + public async void Stat_Retain_NonTrivial_Both(IStatManager mgr) { + var foo1 = await StatTestUtil.CreateStat(mgr, "foo", "foobar", "barfoo"); + Assert.NotNull(foo1); + await mgr.RegisterStat(foo1); + var foo2 = await StatTestUtil.CreateStat(mgr, "foo", "barfoo", "foobar"); + Assert.NotNull(foo1); + Assert.NotNull(foo2); + Assert.Same(foo1, foo2); + } +} \ No newline at end of file diff --git a/GangsTest/StatTests/StatTestUtil.cs b/GangsTest/StatTests/StatTestUtil.cs new file mode 100644 index 0000000..e44c51b --- /dev/null +++ b/GangsTest/StatTests/StatTestUtil.cs @@ -0,0 +1,12 @@ +using GangsAPI.Services; +using GangsAPI.Struct.Stat; + +namespace GangsTest; + +public class StatTestUtil { + public static async Task CreateStat(IStatManager statManager, + string id = "id", string name = "name", string? desc = "desc") { + var dummy = await statManager.CreateStat(id, name, desc); + return dummy; + } +} \ No newline at end of file