From 305104632b31f10582ce1872fbb80cf96e8b7719 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Fri, 12 May 2023 02:37:26 +0700 Subject: [PATCH 01/25] Split API to other project. Code cleanup. Start of making global services --- Kits.API/Cooldowns/IKitCooldownStore.cs | 14 ++ Kits.API/Databases/IKitDatabaseProvider.cs | 18 ++ Kits.API/Databases/KitDatabaseOptions.cs | 46 ++++ Kits.API/IKitManager.cs | 16 ++ Kits.API/IKitStore.cs | 18 ++ Kits.API/Kits.API.csproj | 32 +++ Kits.API/Models/Kit.cs | 82 +++++++ Kits.API/Models/KitItem.cs | 33 +++ Kits.API/Models/KitItemState.cs | 46 ++++ Kits.sln | 18 +- Kits/API/IKitCooldownStore.cs | 15 -- Kits/API/IKitDatabase.cs | 20 -- Kits/API/IKitManager.cs | 16 -- Kits/API/IKitStore.cs | 20 -- Kits/API/Kit.cs | 89 -------- Kits/API/KitItem.cs | 35 --- Kits/API/KitItemState.cs | 46 ---- Kits/Commands/CommandKit.cs | 72 +++--- Kits/Commands/CommandKitCreate.cs | 189 ++++++++------- Kits/Commands/CommandKitMigrate.cs | 48 ---- Kits/Commands/CommandKitRemove.cs | 55 +++-- Kits/Commands/CommandKits.cs | 161 +++++++------ Kits/Cooldowns/KitCooldownStore.cs | 113 +++++++++ Kits/Cooldowns/Models/KitCooldownData.cs | 9 + Kits/Cooldowns/Models/KitsCooldownData.cs | 8 + Kits/Databases/DataStore/KitsData.cs | 9 + Kits/Databases/DataStoreKitDataStore.cs | 118 ++++++++++ Kits/Databases/DataStoreKitDatabase.cs | 115 ---------- Kits/Databases/KitDataStoreCore.cs | 19 ++ Kits/Databases/KitDatabaseCore.cs | 19 -- Kits/Databases/MySqlKitDataStore.cs | 83 +++++++ Kits/Databases/MySqlKitDatabase.cs | 85 ------- Kits/Databases/Mysql/KitsDbContext.cs | 49 ++-- Kits/Databases/Mysql/KitsDbContextFactory.cs | 7 +- Kits/Extensions/ConvertorExtension.cs | 116 +++++----- Kits/Extensions/UnturnedExtension.cs | 215 +++++++++--------- Kits/Kits.cs | 38 ---- Kits/Kits.csproj | 23 +- Kits/KitsPlugin.cs | 35 +++ .../20210623164103_Initial.Designer.cs | 2 +- Kits/Migrations/20210623164103_Initial.cs | 3 +- Kits/Migrations/KitsDbContextModelSnapshot.cs | 2 +- Kits/Models/KitCooldownData.cs | 10 - Kits/Models/KitsCooldownData.cs | 9 - Kits/Models/KitsData.cs | 10 - Kits/PluginContainerConfigurator.cs | 13 +- Kits/ServiceConfigurator.cs | 26 ++- Kits/Services/KitCooldownStore.cs | 116 ---------- Kits/Services/KitManager.cs | 124 +++++----- Kits/Services/KitStore.cs | 193 ++++++++-------- 50 files changed, 1311 insertions(+), 1347 deletions(-) create mode 100644 Kits.API/Cooldowns/IKitCooldownStore.cs create mode 100644 Kits.API/Databases/IKitDatabaseProvider.cs create mode 100644 Kits.API/Databases/KitDatabaseOptions.cs create mode 100644 Kits.API/IKitManager.cs create mode 100644 Kits.API/IKitStore.cs create mode 100644 Kits.API/Kits.API.csproj create mode 100644 Kits.API/Models/Kit.cs create mode 100644 Kits.API/Models/KitItem.cs create mode 100644 Kits.API/Models/KitItemState.cs delete mode 100644 Kits/API/IKitCooldownStore.cs delete mode 100644 Kits/API/IKitDatabase.cs delete mode 100644 Kits/API/IKitManager.cs delete mode 100644 Kits/API/IKitStore.cs delete mode 100644 Kits/API/Kit.cs delete mode 100644 Kits/API/KitItem.cs delete mode 100644 Kits/API/KitItemState.cs delete mode 100644 Kits/Commands/CommandKitMigrate.cs create mode 100644 Kits/Cooldowns/KitCooldownStore.cs create mode 100644 Kits/Cooldowns/Models/KitCooldownData.cs create mode 100644 Kits/Cooldowns/Models/KitsCooldownData.cs create mode 100644 Kits/Databases/DataStore/KitsData.cs create mode 100644 Kits/Databases/DataStoreKitDataStore.cs delete mode 100644 Kits/Databases/DataStoreKitDatabase.cs create mode 100644 Kits/Databases/KitDataStoreCore.cs delete mode 100644 Kits/Databases/KitDatabaseCore.cs create mode 100644 Kits/Databases/MySqlKitDataStore.cs delete mode 100644 Kits/Databases/MySqlKitDatabase.cs delete mode 100644 Kits/Kits.cs create mode 100644 Kits/KitsPlugin.cs delete mode 100644 Kits/Models/KitCooldownData.cs delete mode 100644 Kits/Models/KitsCooldownData.cs delete mode 100644 Kits/Models/KitsData.cs delete mode 100644 Kits/Services/KitCooldownStore.cs diff --git a/Kits.API/Cooldowns/IKitCooldownStore.cs b/Kits.API/Cooldowns/IKitCooldownStore.cs new file mode 100644 index 0000000..8d0c889 --- /dev/null +++ b/Kits.API/Cooldowns/IKitCooldownStore.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading.Tasks; +using OpenMod.API.Ioc; +using OpenMod.Extensions.Games.Abstractions.Players; + +namespace Kits.API.Cooldowns; + +[Service] +public interface IKitCooldownStore +{ + Task GetLastCooldownAsync(IPlayerUser player, string kitName); + + Task RegisterCooldownAsync(IPlayerUser player, string kitName, DateTime time); +} \ No newline at end of file diff --git a/Kits.API/Databases/IKitDatabaseProvider.cs b/Kits.API/Databases/IKitDatabaseProvider.cs new file mode 100644 index 0000000..07a55b3 --- /dev/null +++ b/Kits.API/Databases/IKitDatabaseProvider.cs @@ -0,0 +1,18 @@ +using Kits.API.Models; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Kits.API.Databases; + +public interface IKitDatabaseProvider +{ + Task LoadDatabaseAsync(); + + Task> GetKitsAsync(); + + Task FindKitByNameAsync(string name); + + Task AddKitAsync(Kit kit); + + Task RemoveKitAsync(string name); +} \ No newline at end of file diff --git a/Kits.API/Databases/KitDatabaseOptions.cs b/Kits.API/Databases/KitDatabaseOptions.cs new file mode 100644 index 0000000..500a4aa --- /dev/null +++ b/Kits.API/Databases/KitDatabaseOptions.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Kits.API.Databases; +public class KitDatabaseOptions +{ + private readonly List<(Type implementationService, string name)> m_KitDatabaseProviders = new(); + public IReadOnlyCollection<(Type implementationService, string name)> KitDatabaseProviders => m_KitDatabaseProviders.AsReadOnly(); + + public void AddProvider(string name) where TProvider : IKitDatabaseProvider + { + AddProvider(typeof(TProvider), name); + } + + public void AddProvider(Type type, string name) + { + if (!typeof(IKitDatabaseProvider).IsAssignableFrom(type)) + { + throw new Exception($"Type {type} must be an instance of IKitDatabaseProvider!"); + } + + if (m_KitDatabaseProviders.Any(x => x.name.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + return; + } + + m_KitDatabaseProviders.Add((type, name)); + } + + public void RemoveProvider() where TProvider : IKitDatabaseProvider + { + RemoveProvider(typeof(TProvider)); + } + + public void RemoveProvider(Type type) + { + m_KitDatabaseProviders.RemoveAll(c => c.implementationService == type); + } + + public Type? GetPreferredDatabase(string type) + { + return m_KitDatabaseProviders.Find(x => x.name.Equals(type, StringComparison.OrdinalIgnoreCase)) + .implementationService; + } +} diff --git a/Kits.API/IKitManager.cs b/Kits.API/IKitManager.cs new file mode 100644 index 0000000..e1f0ebf --- /dev/null +++ b/Kits.API/IKitManager.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Kits.API.Models; +using OpenMod.API.Commands; +using OpenMod.API.Ioc; +using OpenMod.Extensions.Games.Abstractions.Players; + +namespace Kits.API; + +[Service] +public interface IKitManager +{ + Task GiveKitAsync(IPlayerUser user, string name, ICommandActor? instigator = null, bool forceGiveKit = false); + + Task> GetAvailableKitsForPlayerAsync(IPlayerUser player); +} \ No newline at end of file diff --git a/Kits.API/IKitStore.cs b/Kits.API/IKitStore.cs new file mode 100644 index 0000000..16cfd20 --- /dev/null +++ b/Kits.API/IKitStore.cs @@ -0,0 +1,18 @@ +using Kits.API.Models; +using OpenMod.API.Ioc; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Kits.API; + +[Service] +public interface IKitStore +{ + Task> GetKitsAsync(); + + Task FindKitByNameAsync(string kitName); + + Task AddKitAsync(Kit kit); + + Task RemoveKitAsync(string kitName); +} \ No newline at end of file diff --git a/Kits.API/Kits.API.csproj b/Kits.API/Kits.API.csproj new file mode 100644 index 0000000..0895ec3 --- /dev/null +++ b/Kits.API/Kits.API.csproj @@ -0,0 +1,32 @@ + + + + netstandard2.0 + EUPL-1.2 + Kits.API + Kits.API + true + true + Kits.API + 10 + enable + nullable + 0.0.0 + 0.0.0 + 0.0.0 + EvolutionPlugins + EvolutionPlugins + DiFFoZ.Kits.API + API of OpenMod kits plugin + https://github.com/DiFFoZ/Kits + git + https://github.com/DiFFoZ/Kits + + + + + + + + + diff --git a/Kits.API/Models/Kit.cs b/Kits.API/Models/Kit.cs new file mode 100644 index 0000000..5b6d164 --- /dev/null +++ b/Kits.API/Models/Kit.cs @@ -0,0 +1,82 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; +using OpenMod.Extensions.Economy.Abstractions; +using OpenMod.Extensions.Games.Abstractions.Items; +using OpenMod.Extensions.Games.Abstractions.Players; +using OpenMod.Extensions.Games.Abstractions.Vehicles; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Threading.Tasks; +using YamlDotNet.Serialization; + +namespace Kits.API.Models; + +public sealed class Kit +{ + [YamlIgnore] + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [StringLength(25)] + [Required] + public string Name { get; set; } = string.Empty; + + public float Cooldown { get; set; } + + [Column(TypeName = "decimal(18,2)")] // by default it creates decimal(65, 30) + public decimal Cost { get; set; } + + [Column(TypeName = "decimal(18,2)")] // by default it creates decimal(65, 30) + public decimal Money { get; set; } + + [StringLength(5)] + public string? VehicleId { get; set; } + + [MaxLength(ushort.MaxValue)] + [Column(TypeName = "blob")] // by default it creates longblob (2^32 - 1), we need only ushort.MaxValue length ( 65535 ) + public List? Items { get; set; } + + public async Task GiveKitToPlayer(IPlayerUser playerUser, IServiceProvider serviceProvider) + { + var stringLocalizer = serviceProvider.GetRequiredService(); + var logger = serviceProvider.GetRequiredService>(); + + if (Items != null && playerUser.Player is IHasInventory hasInventory && hasInventory.Inventory != null) + { + var itemSpawner = serviceProvider.GetRequiredService(); + + foreach (var item in Items) + { + var result = await itemSpawner.GiveItemAsync(hasInventory.Inventory, item.ItemAssetId, + item.State); + if (result == null) + { + logger.LogWarning("Item {Id} was unable to give to player {Name})", item.ItemAssetId, playerUser.FullActorName); + } + } + } + + if (!string.IsNullOrEmpty(VehicleId)) + { + var vehicleSpawner = serviceProvider.GetRequiredService(); + + var result = await vehicleSpawner.SpawnVehicleAsync(playerUser.Player, VehicleId!); + if (result == null) + { + logger.LogWarning("Vehicle {Id} was unable to give to player {Name})", VehicleId, playerUser.FullActorName); + } + } + + if (Money != 0) + { + var economyProvider = serviceProvider.GetRequiredService(); + + await economyProvider.UpdateBalanceAsync(playerUser.Id, playerUser.Type, Money, + stringLocalizer["commands:kit:balanceUpdateReason:got"]); + } + } +} diff --git a/Kits.API/Models/KitItem.cs b/Kits.API/Models/KitItem.cs new file mode 100644 index 0000000..ea8b003 --- /dev/null +++ b/Kits.API/Models/KitItem.cs @@ -0,0 +1,33 @@ +using OpenMod.Extensions.Games.Abstractions.Items; +using System.IO; + +namespace Kits.API.Models; + +public class KitItem +{ + public string ItemAssetId { get; set; } + + public KitItemState State { get; set; } + + public KitItem() : this(null, null) + { + } + + public KitItem(string? itemAssetId, IItemState? itemState) + { + ItemAssetId = itemAssetId ?? string.Empty; + State = new KitItemState(itemState); + } + + public void Serialize(BinaryWriter bw) + { + bw.Write(ItemAssetId); + State.Serialize(bw); + } + + public void Deserialize(BinaryReader br) + { + ItemAssetId = br.ReadString(); + State.Deserialize(br); + } +} \ No newline at end of file diff --git a/Kits.API/Models/KitItemState.cs b/Kits.API/Models/KitItemState.cs new file mode 100644 index 0000000..87d9977 --- /dev/null +++ b/Kits.API/Models/KitItemState.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; +using OpenMod.Extensions.Games.Abstractions.Items; + +namespace Kits.API.Models; + +/// +public class KitItemState : IItemState +{ + public double ItemQuality { get; set; } + + public double ItemDurability { get; set; } + + public double ItemAmount { get; set; } + + public byte[] StateData { get; set; } + + public KitItemState() : this(null) + { + } + + public KitItemState(IItemState? itemState) + { + ItemAmount = itemState?.ItemAmount ?? 0; + ItemDurability = itemState?.ItemDurability ?? 0; + ItemQuality = itemState?.ItemQuality ?? 0; + StateData = itemState?.StateData ?? Array.Empty(); + } + + public void Serialize(BinaryWriter bw) + { + bw.Write(ItemQuality); + bw.Write(ItemDurability); + bw.Write(ItemAmount); + bw.Write(StateData.Length); + bw.Write(StateData); + } + + public void Deserialize(BinaryReader br) + { + ItemQuality = br.ReadDouble(); + ItemDurability = br.ReadDouble(); + ItemAmount = br.ReadDouble(); + StateData = br.ReadBytes(br.ReadInt32()); + } +} \ No newline at end of file diff --git a/Kits.sln b/Kits.sln index 1f024e7..0b5174f 100644 --- a/Kits.sln +++ b/Kits.sln @@ -1,14 +1,13 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30204.135 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33706.43 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kits", "Kits\Kits.csproj", "{6B69ACA9-CCA3-4132-A498-72F126E9D8F2}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3E0BB3AB-F7F2-4870-B0DC-E75A7CB14B2A}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - EndProjectSection +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kits.API", "Kits.API\Kits.API.csproj", "{C01A7068-329C-4F04-8A6E-219017B34C24}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "api", "api", "{19EB88F4-90BD-493C-BD03-625387776648}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -20,10 +19,17 @@ Global {6B69ACA9-CCA3-4132-A498-72F126E9D8F2}.Debug|Any CPU.Build.0 = Debug|Any CPU {6B69ACA9-CCA3-4132-A498-72F126E9D8F2}.Release|Any CPU.ActiveCfg = Release|Any CPU {6B69ACA9-CCA3-4132-A498-72F126E9D8F2}.Release|Any CPU.Build.0 = Release|Any CPU + {C01A7068-329C-4F04-8A6E-219017B34C24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C01A7068-329C-4F04-8A6E-219017B34C24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C01A7068-329C-4F04-8A6E-219017B34C24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C01A7068-329C-4F04-8A6E-219017B34C24}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C01A7068-329C-4F04-8A6E-219017B34C24} = {19EB88F4-90BD-493C-BD03-625387776648} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BEBF1D37-F1C1-4674-9D07-36B1EAC90A6B} EndGlobalSection diff --git a/Kits/API/IKitCooldownStore.cs b/Kits/API/IKitCooldownStore.cs deleted file mode 100644 index c0ae104..0000000 --- a/Kits/API/IKitCooldownStore.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Threading.Tasks; -using OpenMod.API.Ioc; -using OpenMod.Extensions.Games.Abstractions.Players; - -namespace Kits.API -{ - [Service] - public interface IKitCooldownStore - { - Task GetLastCooldownAsync(IPlayerUser player, string kitName); - - Task RegisterCooldownAsync(IPlayerUser player, string kitName, DateTime time); - } -} \ No newline at end of file diff --git a/Kits/API/IKitDatabase.cs b/Kits/API/IKitDatabase.cs deleted file mode 100644 index ff3e7b1..0000000 --- a/Kits/API/IKitDatabase.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Kits.API -{ - public interface IKitDatabase - { - Task LoadDatabaseAsync(); - - Task> GetKitsAsync(); - - Task FindKitByNameAsync(string name); - - Task AddKitAsync(Kit kit); - - Task RemoveKitAsync(string name); - - Task UpdateKitAsync(Kit kit); - } -} \ No newline at end of file diff --git a/Kits/API/IKitManager.cs b/Kits/API/IKitManager.cs deleted file mode 100644 index 053a249..0000000 --- a/Kits/API/IKitManager.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using OpenMod.API.Commands; -using OpenMod.API.Ioc; -using OpenMod.Extensions.Games.Abstractions.Players; - -namespace Kits.API -{ - [Service] - public interface IKitManager - { - Task GiveKitAsync(IPlayerUser user, string name, ICommandActor? instigator = null, bool forceGiveKit = false); - - Task> GetAvailableKitsForPlayerAsync(IPlayerUser player); - } -} \ No newline at end of file diff --git a/Kits/API/IKitStore.cs b/Kits/API/IKitStore.cs deleted file mode 100644 index fe002c5..0000000 --- a/Kits/API/IKitStore.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using OpenMod.API.Ioc; - -namespace Kits.API -{ - [Service] - public interface IKitStore - { - IKitDatabase Database { get; } - - Task> GetKitsAsync(); - - Task FindKitByNameAsync(string kitName); - - Task AddKitAsync(Kit kit); - - Task RemoveKitAsync(string kitName); - } -} \ No newline at end of file diff --git a/Kits/API/Kit.cs b/Kits/API/Kit.cs deleted file mode 100644 index 5172310..0000000 --- a/Kits/API/Kit.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Localization; -using Microsoft.Extensions.Logging; -using OpenMod.Extensions.Economy.Abstractions; -using OpenMod.Extensions.Games.Abstractions.Items; -using OpenMod.Extensions.Games.Abstractions.Players; -using OpenMod.Extensions.Games.Abstractions.Vehicles; -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Threading.Tasks; -using YamlDotNet.Serialization; - -namespace Kits.API -{ -#nullable disable - public class Kit - { - [YamlIgnore] - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; set; } - - [StringLength(25)] - [Required] - public string Name { get; set; } - - public float Cooldown { get; set; } - - [Column(TypeName = "decimal(18,2)")] // by default it creates decimal(65, 30) - public decimal Cost { get; set; } - - [Column(TypeName = "decimal(18,2)")] // by default it creates decimal(65, 30) - public decimal Money { get; set; } - -#nullable enable - [StringLength(5)] - public string? VehicleId { get; set; } - - [MaxLength(ushort.MaxValue)] - [Column(TypeName = "blob")] // by default it creates longblob (2^32 - 1), we need only ushort.MaxValue length ( 65535 ) - public List? Items { get; set; } - - public virtual async Task GiveKitToPlayer(IPlayerUser playerUser, IServiceProvider serviceProvider) - { - if (Items == null) - { - return; - } - - var economyProvider = serviceProvider.GetRequiredService(); - var stringLocalizer = serviceProvider.GetRequiredService(); - var logger = serviceProvider.GetRequiredService>(); - - if (playerUser.Player is IHasInventory hasInventory && hasInventory.Inventory != null) - { - var itemSpawner = serviceProvider.GetRequiredService(); - - foreach (var item in Items) - { - var result = await itemSpawner.GiveItemAsync(hasInventory.Inventory, item.ItemAssetId, - item.State); - if (result == null) - { - logger.LogWarning("Item {Id} was unable to give to player {Name})", item.ItemAssetId, playerUser.FullActorName); - } - } - } - - if (!string.IsNullOrEmpty(VehicleId)) - { - var vehicleSpawner = serviceProvider.GetRequiredService(); - - var result = await vehicleSpawner.SpawnVehicleAsync(playerUser.Player, VehicleId!); - if (result == null) - { - logger.LogWarning("Vehicle {Id} was unable to give to player {Name})", VehicleId, playerUser.FullActorName); - } - } - - if (Money != 0) - { - await economyProvider.UpdateBalanceAsync(playerUser.Id, playerUser.Type, Money, - stringLocalizer["commands:kit:balanceUpdateReason:got"]); - } - } - } -} diff --git a/Kits/API/KitItem.cs b/Kits/API/KitItem.cs deleted file mode 100644 index dcbb868..0000000 --- a/Kits/API/KitItem.cs +++ /dev/null @@ -1,35 +0,0 @@ -using OpenMod.Extensions.Games.Abstractions.Items; -using System.IO; - -namespace Kits.API -{ - public class KitItem - { - public string ItemAssetId { get; set; } - - public KitItemState State { get; set; } - - - public KitItem() : this(null, null) - { - } - - public KitItem(string? itemAssetId, IItemState? itemState) - { - ItemAssetId = itemAssetId ?? string.Empty; - State = new KitItemState(itemState); - } - - public void Serialize(BinaryWriter bw) - { - bw.Write(ItemAssetId); - State.Serialize(bw); - } - - public void Deserialize(BinaryReader br) - { - ItemAssetId = br.ReadString(); - State.Deserialize(br); - } - } -} \ No newline at end of file diff --git a/Kits/API/KitItemState.cs b/Kits/API/KitItemState.cs deleted file mode 100644 index 93da244..0000000 --- a/Kits/API/KitItemState.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.IO; -using OpenMod.Extensions.Games.Abstractions.Items; - -namespace Kits.API -{ - public class KitItemState : IItemState - { - public double ItemQuality { get; set; } - - public double ItemDurability { get; set; } - - public double ItemAmount { get; set; } - - public byte[] StateData { get; set; } - - public KitItemState() : this(null) - { - } - - public KitItemState(IItemState? itemState) - { - ItemAmount = itemState?.ItemAmount ?? 0; - ItemDurability = itemState?.ItemDurability ?? 0; - ItemQuality = itemState?.ItemQuality ?? 0; - StateData = itemState?.StateData ?? Array.Empty(); - } - - public void Serialize(BinaryWriter bw) - { - bw.Write(ItemQuality); - bw.Write(ItemDurability); - bw.Write(ItemAmount); - bw.Write(StateData.Length); - bw.Write(StateData); - } - - public void Deserialize(BinaryReader br) - { - ItemQuality = br.ReadDouble(); - ItemDurability = br.ReadDouble(); - ItemAmount = br.ReadDouble(); - StateData = br.ReadBytes(br.ReadInt32()); - } - } -} \ No newline at end of file diff --git a/Kits/Commands/CommandKit.cs b/Kits/Commands/CommandKit.cs index f7e612f..86f6d6f 100644 --- a/Kits/Commands/CommandKit.cs +++ b/Kits/Commands/CommandKit.cs @@ -1,53 +1,45 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Kits.API; +using Kits.API; using OpenMod.API.Permissions; using OpenMod.Core.Commands; using OpenMod.Core.Permissions; using OpenMod.Extensions.Games.Abstractions.Players; +using System; +using System.Linq; +using System.Threading.Tasks; -namespace Kits.Commands +namespace Kits.Commands; + +[Command("kit")] +[CommandSyntax("[create|remove] [player] ")] +[RegisterCommandPermission("give.other", Description = "Give the kit to another player but it will checks if another player has permission, money, etc..")] // will check if player has permission to get kit +[RegisterCommandPermission("give.other.force", Description = "Force give the kit to another player without checks if the player has permission, money, etc..")] // will not check if player has permission, money, etc.. to get kit +public class CommandKit : Command { - [Command("kit")] - [CommandSyntax("[create|remove] [player] ")] - [RegisterCommandPermission("give.other", Description = "Give the kit to another player but it will checks if another player has permission, money, etc..")] // will check if player has permission to get kit - [RegisterCommandPermission("give.other.force", Description = "Force give the kit to another player without checks if the player has permission, money, etc..")] // will not check if player has permission, money, etc.. to get kit - public class CommandKit : Command - { - private readonly IKitManager m_KitManager; + private readonly IKitManager m_KitManager; - public CommandKit(IServiceProvider serviceProvider, IKitManager kitManager) : base(serviceProvider) - { - m_KitManager = kitManager; - } + public CommandKit(IServiceProvider serviceProvider, IKitManager kitManager) : base(serviceProvider) + { + m_KitManager = kitManager; + } - protected override async Task OnExecuteAsync() - { - var giveKitUser = (Context.Parameters.Count == 2 ? await Context.Parameters.GetAsync(0) - : Context.Actor as IPlayerUser) ?? throw new CommandWrongUsageException(Context); + protected override async Task OnExecuteAsync() + { + var giveKitUser = (Context.Parameters.Count == 2 + ? await Context.Parameters.GetAsync(0) : Context.Actor as IPlayerUser) + ?? throw new CommandWrongUsageException(Context); - var isNotExecutor = giveKitUser != Context.Actor; - var forceGiveKit = false; - var kitName = Context.Parameters.LastOrDefault() ?? throw new CommandWrongUsageException(Context); - - if (isNotExecutor) - { - if (await CheckPermissionAsync("give.other.force") == PermissionGrantResult.Grant) - { - forceGiveKit = true; - } - else if (await CheckPermissionAsync("give.other") == PermissionGrantResult.Grant) - { - forceGiveKit = false; - } - else - { - throw new NotEnoughPermissionException(Context, "give.other"); - } - } + var isNotExecutor = giveKitUser != Context.Actor; + var forceGiveKit = false; + var kitName = Context.Parameters.LastOrDefault() ?? throw new CommandWrongUsageException(Context); - await m_KitManager.GiveKitAsync(giveKitUser, kitName, isNotExecutor ? Context.Actor : null, forceGiveKit); + if (isNotExecutor) + { + forceGiveKit = await CheckPermissionAsync("give.other.force") == PermissionGrantResult.Grant + || (await CheckPermissionAsync("give.other") == PermissionGrantResult.Grant + ? false + : throw new NotEnoughPermissionException(Context, "give.other")); } + + await m_KitManager.GiveKitAsync(giveKitUser, kitName, isNotExecutor ? Context.Actor : null, forceGiveKit); } } \ No newline at end of file diff --git a/Kits/Commands/CommandKitCreate.cs b/Kits/Commands/CommandKitCreate.cs index 7295106..b406b3c 100644 --- a/Kits/Commands/CommandKitCreate.cs +++ b/Kits/Commands/CommandKitCreate.cs @@ -1,5 +1,5 @@ -using JetBrains.Annotations; -using Kits.API; +using Kits.API; +using Kits.API.Models; using Kits.Extensions; using Microsoft.Extensions.Localization; using OpenMod.API.Commands; @@ -11,108 +11,105 @@ using System.Linq; using System.Threading.Tasks; -namespace Kits.Commands +namespace Kits.Commands; + +[Command("create")] +[CommandAlias("add")] +[CommandAlias("+")] +[CommandActor(typeof(IPlayerUser))] +[CommandParent(typeof(CommandKit))] +[CommandSyntax(" [cooldown] [cost] [money] [vehicleId]")] +public class CommandKitCreate : Command { - [Command("create")] - [CommandAlias("add")] - [CommandAlias("+")] - [CommandActor(typeof(IPlayerUser))] - [CommandParent(typeof(CommandKit))] - [CommandSyntax(" [cooldown] [cost] [money] [vehicleId]")] - [UsedImplicitly] - public class CommandKitCreate : Command + private static readonly bool s_IsUnturned = AppDomain.CurrentDomain.GetAssemblies() + .Any(x => x.GetName().Name.Equals("OpenMod.Unturned.Module.Shared")); + + private readonly IKitStore m_KitStore; + private readonly IStringLocalizer m_StringLocalizer; + private readonly IVehicleDirectory m_VehicleDirectory; + + public CommandKitCreate(IServiceProvider serviceProvider, IKitStore kitStore, + IStringLocalizer stringLocalizer, IVehicleDirectory vehicleDirectory) : base(serviceProvider) + { + m_KitStore = kitStore; + m_StringLocalizer = stringLocalizer; + m_VehicleDirectory = vehicleDirectory; + } + + protected override async Task OnExecuteAsync() { - private static readonly bool s_IsUnturned = AppDomain.CurrentDomain.GetAssemblies() - .Any(x => x.GetName().Name.Equals("OpenMod.Unturned.Module.Shared")); + if (Context.Parameters.Count is < 1 or > 5) + { + throw new CommandWrongUsageException(Context); + } + + var playerUser = (IPlayerUser)Context.Actor; + if (playerUser.Player is not IHasInventory hasInventory) + { + throw new UserFriendlyException("IPlayer doesn't have compatibility IHasInventory"); + } + + var name = Context.Parameters[0]; + var cooldown = Context.Parameters.Count >= 2 + ? await Context.Parameters.GetAsync(1) + : TimeSpan.Zero; + var cost = Context.Parameters.Count >= 3 ? await Context.Parameters.GetAsync(2) : 0; + var money = Context.Parameters.Count >= 4 ? await Context.Parameters.GetAsync(3) : 0; + var vehicleId = Context.Parameters.Count == 5 ? Context.Parameters[4] : null; + + var shouldForceCreate = cost != 0 || money != 0 || !string.IsNullOrEmpty(vehicleId); + + if (cooldown < TimeSpan.Zero) + { + throw new UserFriendlyException("The cooldown cannot be negative!"); + } + + if (cost < 0) + { + throw new UserFriendlyException("The cost cannot be negative!"); + } - private readonly IKitStore m_KitStore; - private readonly IStringLocalizer m_StringLocalizer; - private readonly IVehicleDirectory m_VehicleDirectory; + if (money < 0) + { + throw new UserFriendlyException("The money cannot be negative!"); + } - public CommandKitCreate(IServiceProvider serviceProvider, IKitStore kitStore, - IStringLocalizer stringLocalizer, IVehicleDirectory vehicleDirectory) : base(serviceProvider) + if (!string.IsNullOrEmpty(vehicleId) && await m_VehicleDirectory.FindByIdAsync(vehicleId!) == null) { - m_KitStore = kitStore; - m_StringLocalizer = stringLocalizer; - m_VehicleDirectory = vehicleDirectory; + throw new UserFriendlyException($"The vehicle {vehicleId} not found"); } - protected override async Task OnExecuteAsync() + if ((await m_KitStore.FindKitByNameAsync(name)) != null) { - if (Context.Parameters.Count is < 1 or > 5) - { - throw new CommandWrongUsageException(Context); - } - - var playerUser = (IPlayerUser)Context.Actor; - if (playerUser.Player is not IHasInventory hasInventory) - { - throw new UserFriendlyException("IPlayer doesn't have compatibility IHasInventory"); - } - - var name = Context.Parameters[0]; - var cooldown = Context.Parameters.Count >= 2 - ? await Context.Parameters.GetAsync(1) - : TimeSpan.Zero; - var cost = Context.Parameters.Count >= 3 ? await Context.Parameters.GetAsync(2) : 0; - var money = Context.Parameters.Count >= 4 ? await Context.Parameters.GetAsync(3) : 0; - var vehicleId = Context.Parameters.Count == 5 ? Context.Parameters[4] : null; - - var shouldForceCreate = cost != 0 || money != 0 || !string.IsNullOrEmpty(vehicleId); - - if (cooldown < TimeSpan.Zero) - { - throw new UserFriendlyException("The cooldown cannot be negative!"); - } - - if (cost < 0) - { - throw new UserFriendlyException("The cost cannot be negative!"); - } - - if (money < 0) - { - throw new UserFriendlyException("The money cannot be negative!"); - } - - if (!string.IsNullOrEmpty(vehicleId) && await m_VehicleDirectory.FindByIdAsync(vehicleId!) == null) - { - throw new UserFriendlyException($"The vehicle {vehicleId} not found"); - } - - var kits = await m_KitStore.GetKitsAsync(); - if (kits.Any(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase))) - { - throw new UserFriendlyException(m_StringLocalizer["commands:kit:exist", new { Name = name }]); - } - - var items = hasInventory.Inventory! - .SelectMany(x => x.Items - .Select(c => c.Item.ConvertIItemToKitItem())) - .ToList(); - - if (!shouldForceCreate && items.Count == 0) - { - throw new UserFriendlyException(m_StringLocalizer["commands:kit:create:noItems"]); - } - - var kit = new Kit - { - Cooldown = (float)cooldown.TotalSeconds, - Items = items, - Name = name, - Cost = cost, - Money = money, - VehicleId = vehicleId - }; - - if (s_IsUnturned) - { - UnturnedExtension.AddClothes(playerUser, kit.Items); - } - - await m_KitStore.AddKitAsync(kit); - await PrintAsync(m_StringLocalizer["commands:kit:create:success", new { Kit = kit }]); + throw new UserFriendlyException(m_StringLocalizer["commands:kit:exist", new { Name = name }]); } + + var items = hasInventory.Inventory! + .SelectMany(x => x.Items + .Select(c => c.Item.ConvertIItemToKitItem())) + .ToList(); + + if (!shouldForceCreate && items.Count == 0) + { + throw new UserFriendlyException(m_StringLocalizer["commands:kit:create:noItems"]); + } + + var kit = new Kit + { + Cooldown = (float)cooldown.TotalSeconds, + Items = items, + Name = name, + Cost = cost, + Money = money, + VehicleId = vehicleId + }; + + if (s_IsUnturned) + { + UnturnedExtension.AddClothes(playerUser, kit.Items); + } + + await m_KitStore.AddKitAsync(kit); + await PrintAsync(m_StringLocalizer["commands:kit:create:success", new { Kit = kit }]); } } \ No newline at end of file diff --git a/Kits/Commands/CommandKitMigrate.cs b/Kits/Commands/CommandKitMigrate.cs deleted file mode 100644 index be8fec5..0000000 --- a/Kits/Commands/CommandKitMigrate.cs +++ /dev/null @@ -1,48 +0,0 @@ -extern alias JetBrainsAnnotations; -using JetBrainsAnnotations::JetBrains.Annotations; -using Kits.API; -using Kits.Databases; -using Kits.Databases.Mysql; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using OpenMod.API.Persistence; -using OpenMod.Core.Commands; -using OpenMod.Core.Console; -using System; -using System.Threading.Tasks; -using Command = OpenMod.Core.Commands.Command; - -namespace Kits.Commands -{ - [Command("migrate")] - [CommandActor(typeof(ConsoleActor))] - [CommandParent(typeof(CommandKit))] - [UsedImplicitly] - public class CommandKitMigrate : Command - { - private readonly IServiceProvider m_ServiceProvider; - private readonly IConfiguration m_Configuration; - - public CommandKitMigrate(IServiceProvider serviceProvider, IConfiguration configuration) : base(serviceProvider) - { - m_ServiceProvider = serviceProvider; - m_Configuration = configuration; - } - - protected override async Task OnExecuteAsync() - { - var mysql = new MySqlKitDatabase(m_ServiceProvider); - await mysql.LoadDatabaseAsync(); - - await using var dbContext = mysql.GetDbContext(); - - var oldTableName = m_Configuration["database:connectionTableName"] ?? "kits"; - var newTableName = dbContext.Model.FindEntityType(typeof(Kit)).GetTableName(); - - // maybe has other option to migrate data to other table - var affected = await dbContext.Database.ExecuteSqlRawAsync($"INSERT INTO `{newTableName}` (Id, Name, Cooldown, Cost, Money, VehicleId, Items) SELECT Id, Name, Cooldown, Cost, Money, VehicleId, Items FROM `{oldTableName}`"); - - await PrintAsync($"Successfully migrated {affected} kit(s) to the new table"); - } - } -} \ No newline at end of file diff --git a/Kits/Commands/CommandKitRemove.cs b/Kits/Commands/CommandKitRemove.cs index 65f6c02..37aa791 100644 --- a/Kits/Commands/CommandKitRemove.cs +++ b/Kits/Commands/CommandKitRemove.cs @@ -1,40 +1,37 @@ -using System; -using System.Threading.Tasks; -using JetBrains.Annotations; -using Kits.API; +using Kits.API; using Microsoft.Extensions.Localization; using OpenMod.Core.Commands; +using System; +using System.Threading.Tasks; + +namespace Kits.Commands; -namespace Kits.Commands +[Command("remove")] +[CommandAlias("-")] +[CommandAlias("delete")] +[CommandParent(typeof(CommandKit))] +[CommandSyntax("")] +public class CommandKitRemove : Command { - [Command("remove")] - [CommandAlias("-")] - [CommandAlias("delete")] - [CommandParent(typeof(CommandKit))] - [CommandSyntax("")] - [UsedImplicitly] - public class CommandKitRemove : Command + private readonly IKitStore m_KitStore; + private readonly IStringLocalizer m_StringLocalizer; + + public CommandKitRemove(IServiceProvider serviceProvider, IKitStore kitStore, + IStringLocalizer stringLocalizer) : base(serviceProvider) { - private readonly IKitStore m_KitStore; - private readonly IStringLocalizer m_StringLocalizer; + m_KitStore = kitStore; + m_StringLocalizer = stringLocalizer; + } - public CommandKitRemove(IServiceProvider serviceProvider, IKitStore kitStore, - IStringLocalizer stringLocalizer) : base(serviceProvider) + protected override async Task OnExecuteAsync() + { + if (Context.Parameters.Length != 1) { - m_KitStore = kitStore; - m_StringLocalizer = stringLocalizer; + throw new CommandWrongUsageException(Context); } - protected override async Task OnExecuteAsync() - { - if (Context.Parameters.Length != 1) - { - throw new CommandWrongUsageException(Context); - } - - var kitName = Context.Parameters[0]; - await m_KitStore.RemoveKitAsync(kitName); - await PrintAsync(m_StringLocalizer["commands:kit:remove:success", new { Name = kitName }]); - } + var kitName = Context.Parameters[0]; + await m_KitStore.RemoveKitAsync(kitName); + await PrintAsync(m_StringLocalizer["commands:kit:remove:success", new { Name = kitName }]); } } \ No newline at end of file diff --git a/Kits/Commands/CommandKits.cs b/Kits/Commands/CommandKits.cs index ed8157e..2461d6e 100644 --- a/Kits/Commands/CommandKits.cs +++ b/Kits/Commands/CommandKits.cs @@ -1,119 +1,114 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Text; -using System.Threading.Tasks; -using EvolutionPlugins.Economy.Stub.Services; -using JetBrains.Annotations; +using EvolutionPlugins.Economy.Stub.Services; using Kits.API; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Localization; -using Microsoft.Extensions.Logging; using OpenMod.API.Permissions; using OpenMod.Core.Commands; using OpenMod.Core.Permissions; using OpenMod.Extensions.Economy.Abstractions; using OpenMod.Extensions.Games.Abstractions.Players; using SmartFormat.ZString; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Threading.Tasks; + +namespace Kits.Commands; -namespace Kits.Commands +[Command("kits")] +[RegisterCommandPermission("show.other", Description = "Shows the available kits of another player")] +[CommandSyntax("[player]")] +public class CommandKits : Command { - [Command("kits")] - [RegisterCommandPermission("show.other", Description = "Shows the available kits of another player")] - [CommandSyntax("[player]")] - [UsedImplicitly] - public class CommandKits : Command + private readonly IKitManager m_KitManager; + private readonly IStringLocalizer m_StringLocalizer; + private readonly IEconomyProvider m_EconomyProvider; + private readonly IConfiguration m_Configuration; + + public CommandKits(IServiceProvider serviceProvider, IKitManager kitManager, IStringLocalizer stringLocalizer, + IEconomyProvider economyProvider, IConfiguration configuration) : base(serviceProvider) { - private readonly IKitManager m_KitManager; - private readonly IStringLocalizer m_StringLocalizer; - private readonly IEconomyProvider m_EconomyProvider; - private readonly IConfiguration m_Configuration; + m_KitManager = kitManager; + m_StringLocalizer = stringLocalizer; + m_EconomyProvider = economyProvider; + m_Configuration = configuration; + } - public CommandKits(IServiceProvider serviceProvider, IKitManager kitManager, IStringLocalizer stringLocalizer, - IEconomyProvider economyProvider, IConfiguration configuration) : base(serviceProvider) - { - m_KitManager = kitManager; - m_StringLocalizer = stringLocalizer; - m_EconomyProvider = economyProvider; - m_Configuration = configuration; - } + protected override async Task OnExecuteAsync() + { + var showKitsUser = (Context.Parameters.Count == 1 ? await Context.Parameters.GetAsync(0) + : Context.Actor as IPlayerUser) ?? throw new CommandWrongUsageException(Context); - protected override async Task OnExecuteAsync() + var isNotExecutor = showKitsUser != Context.Actor; + if (isNotExecutor && await CheckPermissionAsync("show.other") != PermissionGrantResult.Grant) { - var showKitsUser = (Context.Parameters.Count == 1 ? await Context.Parameters.GetAsync(0) - : Context.Actor as IPlayerUser) ?? throw new CommandWrongUsageException(Context); - - var isNotExecutor = showKitsUser != Context.Actor; - if (isNotExecutor && await CheckPermissionAsync("show.other") != PermissionGrantResult.Grant) - { - throw new NotEnoughPermissionException(Context, "show.other"); - } + throw new NotEnoughPermissionException(Context, "show.other"); + } - var moneySymbol = "$"; - var moneyName = string.Empty; + var moneySymbol = "$"; + var moneyName = string.Empty; - // prevent some exceptions - if (m_EconomyProvider is not EconomyProviderStub) - { - moneySymbol = m_EconomyProvider.CurrencySymbol; - moneyName = m_EconomyProvider.CurrencyName; - } + // prevent some exceptions + if (m_EconomyProvider is not EconomyProviderStub) + { + moneySymbol = m_EconomyProvider.CurrencySymbol; + moneyName = m_EconomyProvider.CurrencyName; + } - var kits = await m_KitManager.GetAvailableKitsForPlayerAsync(showKitsUser); - kits = kits.Count > 0 ? kits : null; + var kits = await m_KitManager.GetAvailableKitsForPlayerAsync(showKitsUser); + kits = kits.Count > 0 ? kits : null; - await PrintAsync(m_StringLocalizer["commands:kits", new - { - Kits = kits, - MoneySymbol = moneySymbol, - MoneyName = moneyName - }]); - } + await PrintAsync(m_StringLocalizer["commands:kits", new + { + Kits = kits, + MoneySymbol = moneySymbol, + MoneyName = moneyName + }]); + } - public override async Task PrintAsync(string message) + public override async Task PrintAsync(string message) + { + if (m_Configuration.GetValue("wrapLines", true)) { - if (m_Configuration.GetValue("wrapLines", true)) + foreach (var msg in WrapLines(message)) { - foreach (var msg in WrapLines(message)) - { - await PrintAsync(msg, Color.White); - } - return; + await PrintAsync(msg, Color.White); } - - await PrintAsync(message, Color.White); + return; } - private static IEnumerable WrapLines(string line) - { - const int MaxLength = 90; + await PrintAsync(message, Color.White); + } - using var currentLine = new ZStringBuilder(false); + private static IEnumerable WrapLines(string line) + { + const int MaxLength = 90; - foreach (var currentWord in line.Split(' ')) + using var currentLine = new ZStringBuilder(false); + + foreach (var currentWord in line.Split(' ')) + { + if (currentLine.Length > MaxLength || + currentLine.Length + currentWord.Length > MaxLength) { - if (currentLine.Length > MaxLength || - currentLine.Length + currentWord.Length > MaxLength) - { - yield return currentLine.ToString(); - currentLine.Clear(); - } - - if (currentLine.Length > 0) - { - currentLine.Append(" "); - currentLine.Append(currentWord); - } - else - { - currentLine.Append(currentWord); - } + yield return currentLine.ToString(); + currentLine.Clear(); } if (currentLine.Length > 0) { - yield return currentLine.ToString(); + currentLine.Append(" "); + currentLine.Append(currentWord); } + else + { + currentLine.Append(currentWord); + } + } + + if (currentLine.Length > 0) + { + yield return currentLine.ToString(); } } } \ No newline at end of file diff --git a/Kits/Cooldowns/KitCooldownStore.cs b/Kits/Cooldowns/KitCooldownStore.cs new file mode 100644 index 0000000..917dd42 --- /dev/null +++ b/Kits/Cooldowns/KitCooldownStore.cs @@ -0,0 +1,113 @@ +using Kits.API.Cooldowns; +using Kits.Cooldowns.Models; +using Microsoft.Extensions.DependencyInjection; +using OpenMod.API.Ioc; +using OpenMod.API.Permissions; +using OpenMod.API.Persistence; +using OpenMod.Core.Helpers; +using OpenMod.Core.Permissions; +using OpenMod.Extensions.Games.Abstractions.Players; +using System; +using System.Threading.Tasks; + +[assembly: RegisterPermission("nocooldown", Description = "Allows use kit without waiting for cooldown")] + +namespace Kits.Cooldowns; + +[PluginServiceImplementation(Lifetime = ServiceLifetime.Singleton)] +public class KitCooldownStore : IKitCooldownStore, IDisposable +{ + private const string c_CooldownKey = "cooldowns"; + private const string c_NoCooldownPermission = "nocooldown"; + + private readonly IDataStore m_DataStore; + private readonly KitsPlugin m_Plugin; + private readonly IPermissionChecker m_PermissionChecker; + + private KitsCooldownData m_KitsCooldownData = null!; + private IDisposable? m_FileWatcher; + + public KitCooldownStore(KitsPlugin plugin, IPermissionChecker permissionChecker) + { + m_DataStore = plugin.DataStore; + m_Plugin = plugin; + m_PermissionChecker = permissionChecker; + + AsyncHelper.RunSync(LoadData); + } + + public async Task GetLastCooldownAsync(IPlayerUser player, string kitName) + { + if (await m_PermissionChecker.CheckPermissionAsync(player, c_NoCooldownPermission) == + PermissionGrantResult.Grant + || !m_KitsCooldownData.KitsCooldown!.TryGetValue(player.Id, out var kitCooldowns)) + { + return null; + } + + var kitCooldown = kitCooldowns!.Find(x => + x.KitName?.Equals(kitName, StringComparison.CurrentCultureIgnoreCase) == true); + return kitCooldown == null ? null : DateTime.Now - kitCooldown.KitCooldown; + } + + public async Task RegisterCooldownAsync(IPlayerUser player, string kitName, DateTime time) + { + if (await m_PermissionChecker.CheckPermissionAsync(player, c_NoCooldownPermission) == + PermissionGrantResult.Grant) + { + return; + } + + kitName = kitName.ToLower(); + + if (m_KitsCooldownData.KitsCooldown!.TryGetValue(player.Id, out var kitCooldowns)) + { + var kitCooldown = kitCooldowns!.Find(x => x.KitName == kitName); + if (kitCooldown == null) + { + kitCooldown = new() { KitName = kitName }; + kitCooldowns.Add(kitCooldown); + } + + kitCooldown.KitCooldown = time; + } + else + { + m_KitsCooldownData.KitsCooldown.Add(player.Id, + new() { new() { KitCooldown = time, KitName = kitName } }); + } + + await SaveData(); + } + + private async Task LoadFromDisk() + { + if (await m_DataStore.ExistsAsync(c_CooldownKey)) + { + m_KitsCooldownData = await m_DataStore.LoadAsync(c_CooldownKey) ?? + new() { KitsCooldown = new() }; + } + else + { + m_KitsCooldownData = new() { KitsCooldown = new() }; + await SaveData(); + } + } + + private async Task LoadData() + { + await LoadFromDisk(); + m_FileWatcher = m_DataStore.AddChangeWatcher(c_CooldownKey, m_Plugin, + () => AsyncHelper.RunSync(LoadFromDisk)); + } + + private Task SaveData() + { + return m_DataStore.SaveAsync(c_CooldownKey, m_KitsCooldownData); + } + + public void Dispose() + { + m_FileWatcher?.Dispose(); + } +} \ No newline at end of file diff --git a/Kits/Cooldowns/Models/KitCooldownData.cs b/Kits/Cooldowns/Models/KitCooldownData.cs new file mode 100644 index 0000000..f54f88a --- /dev/null +++ b/Kits/Cooldowns/Models/KitCooldownData.cs @@ -0,0 +1,9 @@ +using System; + +namespace Kits.Cooldowns.Models; + +public class KitCooldownData +{ + public string? KitName { get; set; } + public DateTime KitCooldown { get; set; } +} \ No newline at end of file diff --git a/Kits/Cooldowns/Models/KitsCooldownData.cs b/Kits/Cooldowns/Models/KitsCooldownData.cs new file mode 100644 index 0000000..700d66b --- /dev/null +++ b/Kits/Cooldowns/Models/KitsCooldownData.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Kits.Cooldowns.Models; + +public class KitsCooldownData +{ + public Dictionary>? KitsCooldown { get; set; } +} \ No newline at end of file diff --git a/Kits/Databases/DataStore/KitsData.cs b/Kits/Databases/DataStore/KitsData.cs new file mode 100644 index 0000000..d6a0f70 --- /dev/null +++ b/Kits/Databases/DataStore/KitsData.cs @@ -0,0 +1,9 @@ +using Kits.API.Models; +using System.Collections.Generic; + +namespace Kits.Databases.DataStore; + +public class KitsData +{ + public List? Kits { get; set; } +} \ No newline at end of file diff --git a/Kits/Databases/DataStoreKitDataStore.cs b/Kits/Databases/DataStoreKitDataStore.cs new file mode 100644 index 0000000..8d2cdb2 --- /dev/null +++ b/Kits/Databases/DataStoreKitDataStore.cs @@ -0,0 +1,118 @@ +using Autofac; +using Kits.API.Databases; +using Kits.API.Models; +using Kits.Databases.DataStore; +using OpenMod.API; +using OpenMod.API.Commands; +using OpenMod.Core.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Kits.Databases; + +public class DataStoreKitDataStore : KitDataStoreCore, IKitDatabaseProvider, IDisposable +{ + private const string c_KitsKey = "kits"; + + private KitsData m_Data = null!; + private IDisposable? m_FileWatcher; + + public DataStoreKitDataStore(ILifetimeScope lifetimeScope) : base(lifetimeScope) + { + } + + public async Task AddKitAsync(Kit kit) + { + if (kit is null) + { + throw new ArgumentNullException(nameof(kit)); + } + + if (m_Data.Kits.Any(x => x.Name.Equals(kit.Name, StringComparison.OrdinalIgnoreCase))) + { + throw new UserFriendlyException(StringLocalizer["commands:kit:exist"]); + } + + m_Data.Kits?.Add(kit); + await SaveToDisk(); + return true; + } + + public Task FindKitByNameAsync(string name) + { + return Task.FromResult(m_Data.Kits?.Find(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase))); + } + + public Task> GetKitsAsync() + { + return Task.FromResult((IReadOnlyCollection)(m_Data.Kits ?? new())); + } + + public async Task LoadDatabaseAsync() + { + await LoadFromDisk(); + + var component = LifetimeScope.Resolve(); + + m_FileWatcher = DataStore.AddChangeWatcher(c_KitsKey, component, + () => AsyncHelper.RunSync(LoadFromDisk)); + + await SaveToDisk(); + } + + private async Task LoadFromDisk() + { + if (await DataStore.ExistsAsync(c_KitsKey)) + { + m_Data = await DataStore.LoadAsync(c_KitsKey) ?? new(); + m_Data.Kits ??= new(); + return; + } + + m_Data = new() { Kits = new() }; + } + + public async Task RemoveKitAsync(string name) + { + var index = m_Data.Kits?.FindIndex(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + if (index < 0) + { + throw new UserFriendlyException(StringLocalizer["commands:kit:remove:fail", new { Name = name }]); + } + + m_Data.Kits?.RemoveAt(index!.Value); + await SaveToDisk(); + return true; + } + + public async Task UpdateKitAsync(Kit kit) + { + if (kit is null) + { + throw new ArgumentNullException(nameof(kit)); + } + + var index = m_Data.Kits?.FindIndex( + x => x.Name.Equals(kit.Name, StringComparison.OrdinalIgnoreCase)); + if (index < 0) + { + return false; + } + + m_Data.Kits![index!.Value] = kit; + await SaveToDisk(); + return true; + } + + public void Dispose() + { + m_FileWatcher?.Dispose(); + } + + private Task SaveToDisk() + { + return DataStore.SaveAsync(c_KitsKey, m_Data); + } +} \ No newline at end of file diff --git a/Kits/Databases/DataStoreKitDatabase.cs b/Kits/Databases/DataStoreKitDatabase.cs deleted file mode 100644 index 6903619..0000000 --- a/Kits/Databases/DataStoreKitDatabase.cs +++ /dev/null @@ -1,115 +0,0 @@ -extern alias JetBrainsAnnotations; -using Kits.API; -using Kits.Models; -using OpenMod.API.Commands; -using OpenMod.Core.Helpers; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Kits.Databases -{ - public class DataStoreKitDatabase : KitDatabaseCore, IKitDatabase, IDisposable - { - private const string c_KitsKey = "kits"; - - private KitsData m_Data = null!; - private IDisposable? m_FileWatcher; - - public DataStoreKitDatabase(Kits plugin) : base(plugin) - { - } - - public async Task AddKitAsync(Kit kit) - { - if (kit is null) - { - throw new ArgumentNullException(nameof(kit)); - } - - if (m_Data.Kits.Any(x => x.Name.Equals(kit.Name, StringComparison.OrdinalIgnoreCase))) - { - throw new UserFriendlyException(StringLocalizer["commands:kit:exist"]); - } - - m_Data.Kits?.Add(kit); - await SaveToDisk(); - return true; - } - - public Task FindKitByNameAsync(string name) - { - return Task.FromResult(m_Data.Kits?.Find(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase))); - } - - public Task> GetKitsAsync() - { - return Task.FromResult((IReadOnlyCollection)(m_Data.Kits ?? new())); - } - - public async Task LoadDatabaseAsync() - { - await LoadFromDisk(); - - m_FileWatcher = Plugin.DataStore.AddChangeWatcher(c_KitsKey, Plugin, - () => AsyncHelper.RunSync(LoadFromDisk)); - - await SaveToDisk(); - } - - private async Task LoadFromDisk() - { - if (await Plugin.DataStore.ExistsAsync(c_KitsKey)) - { - m_Data = await Plugin.DataStore.LoadAsync(c_KitsKey) ?? new() { Kits = new() }; - } - else - { - m_Data = new() { Kits = new() }; - } - } - - public async Task RemoveKitAsync(string name) - { - var index = m_Data.Kits?.FindIndex(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); - if (index < 0) - { - throw new UserFriendlyException(StringLocalizer["commands:kit:remove:fail", new { Name = name }]); - } - - m_Data.Kits?.RemoveAt(index!.Value); - await SaveToDisk(); - return true; - } - - public async Task UpdateKitAsync(Kit kit) - { - if (kit is null) - { - throw new ArgumentNullException(nameof(kit)); - } - - var index = m_Data.Kits?.FindIndex( - x => x.Name.Equals(kit.Name, StringComparison.OrdinalIgnoreCase)); - if (index < 0) - { - return false; - } - - m_Data.Kits![index!.Value] = kit; - await SaveToDisk(); - return true; - } - - public void Dispose() - { - m_FileWatcher?.Dispose(); - } - - private Task SaveToDisk() - { - return Plugin.DataStore.SaveAsync(c_KitsKey, m_Data); - } - } -} \ No newline at end of file diff --git a/Kits/Databases/KitDataStoreCore.cs b/Kits/Databases/KitDataStoreCore.cs new file mode 100644 index 0000000..1e372c8 --- /dev/null +++ b/Kits/Databases/KitDataStoreCore.cs @@ -0,0 +1,19 @@ +using Autofac; +using Microsoft.Extensions.Localization; +using OpenMod.API.Persistence; + +namespace Kits.Databases; + +public abstract class KitDataStoreCore +{ + protected IDataStore DataStore { get; } + protected IStringLocalizer StringLocalizer { get; } + protected ILifetimeScope LifetimeScope { get; } + + protected KitDataStoreCore(ILifetimeScope lifetimeScope) + { + StringLocalizer = lifetimeScope.Resolve(); + DataStore = lifetimeScope.Resolve(); + LifetimeScope = lifetimeScope; + } +} \ No newline at end of file diff --git a/Kits/Databases/KitDatabaseCore.cs b/Kits/Databases/KitDatabaseCore.cs deleted file mode 100644 index a690fe2..0000000 --- a/Kits/Databases/KitDatabaseCore.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Autofac; -using Microsoft.Extensions.Localization; - -namespace Kits.Databases -{ - public abstract class KitDatabaseCore - { - protected Kits Plugin { get; } - protected virtual string TableName => Plugin.Configuration["database:connectionTableName"]; - protected virtual string Connection => Plugin.Configuration["database:connection"]; - protected IStringLocalizer StringLocalizer { get; } - - protected KitDatabaseCore(Kits plugin) - { - StringLocalizer = plugin.LifetimeScope.Resolve(); - Plugin = plugin; - } - } -} \ No newline at end of file diff --git a/Kits/Databases/MySqlKitDataStore.cs b/Kits/Databases/MySqlKitDataStore.cs new file mode 100644 index 0000000..21a499a --- /dev/null +++ b/Kits/Databases/MySqlKitDataStore.cs @@ -0,0 +1,83 @@ +using Autofac; +using Kits.API.Databases; +using Kits.API.Models; +using Kits.Databases.MySql; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using OpenMod.API.Commands; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Kits.Databases; + +public class MySqlKitDataStore : KitDataStoreCore, IKitDatabaseProvider +{ + public MySqlKitDataStore(IServiceProvider provider) : this(provider.GetRequiredService().LifetimeScope) + { + } + + public MySqlKitDataStore(ILifetimeScope lifetimeScope) : base(lifetimeScope) + { + } + + protected virtual KitsDbContext GetDbContext() => LifetimeScope.Resolve(); + + public async Task LoadDatabaseAsync() + { + await using var context = GetDbContext(); + await context.Database.MigrateAsync(); + } + + public async Task AddKitAsync(Kit kit) + { + await using var context = GetDbContext(); + + if (await context.Kits.AnyAsync(x => x.Name == kit.Name)) + { + throw new UserFriendlyException(StringLocalizer["commands:kit:exist"]); + } + + context.Kits.Add(kit); + return await context.SaveChangesAsync() > 0; + } + + public async Task FindKitByNameAsync(string name) + { + await using var context = GetDbContext(); + + return await context.Kits + .AsNoTracking() + .FirstOrDefaultAsync(x => x.Name == name); + } + + public async Task> GetKitsAsync() + { + await using var context = GetDbContext(); + return await context.Kits.AsNoTracking().ToListAsync(); + } + + public async Task RemoveKitAsync(string name) + { + await using var context = GetDbContext(); + + var kit = await context.Kits + .FirstOrDefaultAsync(x => x.Name == name); + if (kit == null) + { + return false; + } + + context.Kits.Remove(kit); + + return await context.SaveChangesAsync() > 0; + } + + public async Task UpdateKitAsync(Kit kit) + { + await using var context = GetDbContext(); + + context.Kits.Update(kit); + return await context.SaveChangesAsync() > 0; + } +} \ No newline at end of file diff --git a/Kits/Databases/MySqlKitDatabase.cs b/Kits/Databases/MySqlKitDatabase.cs deleted file mode 100644 index 8c93c95..0000000 --- a/Kits/Databases/MySqlKitDatabase.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Autofac; -using Kits.API; -using Kits.Databases.Mysql; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using OpenMod.API.Commands; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Kits.Databases -{ - public class MySqlKitDatabase : KitDatabaseCore, IKitDatabase - { - public MySqlKitDatabase(IServiceProvider provider) : this(provider.GetRequiredService()) - { - } - - public MySqlKitDatabase(Kits plugin) : base(plugin) - { - } - - public virtual KitsDbContext GetDbContext() => Plugin.LifetimeScope.Resolve(); - - public async Task LoadDatabaseAsync() - { - await using var context = GetDbContext(); - await context.Database.MigrateAsync(); - } - - public async Task AddKitAsync(Kit kit) - { - await using var context = GetDbContext(); - - if (await context.Kits.Where(x => x.Name.Equals(kit.Name, StringComparison.OrdinalIgnoreCase)).AnyAsync()) - { - throw new UserFriendlyException(StringLocalizer["commands:kit:exist"]); - } - - context.Kits.Add(kit); - return await context.SaveChangesAsync() > 0; - } - - public async Task FindKitByNameAsync(string name) - { - await using var context = GetDbContext(); - - return await context.Kits - .Where(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) - .FirstOrDefaultAsync(); - } - - public async Task> GetKitsAsync() - { - await using var context = GetDbContext(); - return await context.Kits.ToListAsync(); - } - - public async Task RemoveKitAsync(string name) - { - await using var context = GetDbContext(); - - var kit = await context.Kits - .Where(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) - .FirstOrDefaultAsync(); - if (kit == null) - { - return false; - } - - context.Kits.Remove(kit); - - return await context.SaveChangesAsync() > 0; - } - - public async Task UpdateKitAsync(Kit kit) - { - await using var context = GetDbContext(); - - context.Kits.Update(kit); - return await context.SaveChangesAsync() > 0; - } - } -} \ No newline at end of file diff --git a/Kits/Databases/Mysql/KitsDbContext.cs b/Kits/Databases/Mysql/KitsDbContext.cs index 8eac7a0..94f4712 100644 --- a/Kits/Databases/Mysql/KitsDbContext.cs +++ b/Kits/Databases/Mysql/KitsDbContext.cs @@ -1,4 +1,4 @@ -using Kits.API; +using Kits.API.Models; using Kits.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -6,39 +6,32 @@ using OpenMod.EntityFrameworkCore.Configurator; using System; -namespace Kits.Databases.Mysql +namespace Kits.Databases.MySql; + +public class KitsDbContext : OpenModDbContext { - public class KitsDbContext : OpenModDbContext - { - public DbSet Kits - { - get - { - return Set(); - } - } + public DbSet Kits => Set(); - public KitsDbContext(IServiceProvider serviceProvider) : base(serviceProvider) - { - } + public KitsDbContext(IServiceProvider serviceProvider) : base(serviceProvider) + { + } - public KitsDbContext(IDbContextConfigurator configurator, IServiceProvider serviceProvider) : base(configurator, serviceProvider) - { - } + public KitsDbContext(IDbContextConfigurator configurator, IServiceProvider serviceProvider) : base(configurator, serviceProvider) + { + } - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.ApplyConfiguration(new KitConfiguration()); - base.OnModelCreating(modelBuilder); - } + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new KitConfiguration()); + base.OnModelCreating(modelBuilder); + } - // https://stackoverflow.com/questions/44829824/how-to-store-json-in-an-entity-field-with-ef-core - public class KitConfiguration : IEntityTypeConfiguration + // https://stackoverflow.com/questions/44829824/how-to-store-json-in-an-entity-field-with-ef-core + public class KitConfiguration : IEntityTypeConfiguration + { + public virtual void Configure(EntityTypeBuilder builder) { - public virtual void Configure(EntityTypeBuilder builder) - { - builder.Property(c => c.Items).HasByteArrayConversion(); - } + builder.Property(c => c.Items).HasByteArrayConversion(); } } } diff --git a/Kits/Databases/Mysql/KitsDbContextFactory.cs b/Kits/Databases/Mysql/KitsDbContextFactory.cs index 8fe37b2..1328e26 100644 --- a/Kits/Databases/Mysql/KitsDbContextFactory.cs +++ b/Kits/Databases/Mysql/KitsDbContextFactory.cs @@ -1,8 +1,7 @@ using OpenMod.EntityFrameworkCore.MySql; -namespace Kits.Databases.Mysql +namespace Kits.Databases.MySql; + +public class KitsDbContextFactory : OpenModMySqlDbContextFactory { - public class KitsDbContextFactory : OpenModMySqlDbContextFactory - { - } } diff --git a/Kits/Extensions/ConvertorExtension.cs b/Kits/Extensions/ConvertorExtension.cs index 34dbbcd..7575be9 100644 --- a/Kits/Extensions/ConvertorExtension.cs +++ b/Kits/Extensions/ConvertorExtension.cs @@ -1,4 +1,4 @@ -using Kits.API; +using Kits.API.Models; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -7,86 +7,76 @@ using System.Collections.Generic; using System.IO; -namespace Kits.Extensions +namespace Kits.Extensions; + +internal static class ConvertorExtension { - public static class ConvertorExtension + public static readonly byte s_SaveVersion = 1; + + public static KitItem ConvertIItemToKitItem(this IItem item) { - public static readonly byte s_SaveVersion = 1; + return new(item.Asset.ItemAssetId, item.State); + } - public static KitItem ConvertIItemToKitItem(this IItem item) + public static byte[] ConvertToByteArray(this List? items) + { + if (items == null) { - return new(item.Asset.ItemAssetId, item.State); + return Array.Empty(); } - public static byte[] ConvertToByteArray(this List? items) - { - if (items == null) - { - return Array.Empty(); - } + using var ms = new MemoryStream(); + using var bw = new BinaryWriter(ms); - using var ms = new MemoryStream(); - using var bw = new BinaryWriter(ms); + bw.Write(s_SaveVersion); + bw.Write(items.Count); - bw.Write(s_SaveVersion); - bw.Write(items.Count); + foreach (var item in items) + { + item.Serialize(bw); + } - foreach (var item in items) - { - item.Serialize(bw); - } + return ms.ToArray(); + } - return ms.ToArray(); + public static List? ConvertToKitItems(this byte[]? block) + { + if (block == null) + { + return null; } - public static List ConvertToKitItems(this byte[]? block) - { - var output = new List(); + var output = new List(); - if (block == null) - { - return output; - } + using var ms = new MemoryStream(block, false); + using var br = new BinaryReader(ms); - using var ms = new MemoryStream(block, false); - using var br = new BinaryReader(ms); + br.ReadByte(); // save version, for now ignored - br.ReadByte(); // save version, for now ignored + var count = br.ReadInt32(); + for (var i = 0; i < count; i++) + { + var kitItem = new KitItem(); + kitItem.Deserialize(br); - var count = br.ReadInt32(); - for (var i = 0; i < count; i++) - { - var kitItem = new KitItem(); - kitItem.Deserialize(br); + output.Add(kitItem); + } - output.Add(kitItem); - } + return output; + } - return output; - } + // https://stackoverflow.com/questions/44829824/how-to-store-json-in-an-entity-field-with-ef-core + public static PropertyBuilder?> HasByteArrayConversion(this PropertyBuilder?> propertyBuilder) + { + var converter = new ValueConverter?, byte[]> + ( + v => v.ConvertToByteArray(), + v => v.ConvertToKitItems() + ); - // https://stackoverflow.com/questions/44829824/how-to-store-json-in-an-entity-field-with-ef-core - public static PropertyBuilder?> HasByteArrayConversion(this PropertyBuilder?> propertyBuilder) - { - var converter = new ValueConverter?, byte[]> - ( - v => v.ConvertToByteArray(), - v => v.ConvertToKitItems() - ); - - /* var comparer = new ValueComparer?> - ( - (l, r) => l == r, - v => v == null ? 0 : v.GetHashCode(), - v => v.ConvertToByteArray().ConvertToKitItems() - );*/ - - propertyBuilder.HasConversion(converter); - propertyBuilder.Metadata.SetValueConverter(converter); - //propertyBuilder.Metadata.SetValueComparer(comparer); - //propertyBuilder.HasColumnType("longblob"); - - return propertyBuilder; - } + propertyBuilder.HasConversion(converter); + propertyBuilder.Metadata.SetValueConverter(converter); + + return propertyBuilder; } } \ No newline at end of file diff --git a/Kits/Extensions/UnturnedExtension.cs b/Kits/Extensions/UnturnedExtension.cs index 214c945..b38986b 100644 --- a/Kits/Extensions/UnturnedExtension.cs +++ b/Kits/Extensions/UnturnedExtension.cs @@ -1,128 +1,127 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Kits.API; +using Kits.API.Models; using OpenMod.Extensions.Games.Abstractions.Players; using SDG.Unturned; using Steamworks; +using System; +using System.Collections.Generic; +using System.Linq; -namespace Kits.Extensions +namespace Kits.Extensions; + +internal static class UnturnedExtension { - public static class UnturnedExtension - { - private static readonly bool s_IsUnturned = AppDomain.CurrentDomain.GetAssemblies() - .Any(x => x.GetName().Name.Equals("OpenMod.Unturned")); + private static readonly bool s_IsUnturned = AppDomain.CurrentDomain.GetAssemblies() + .Any(x => x.GetName().Name.Equals("OpenMod.Unturned")); - public static void AddClothes(IPlayerUser user, IList kitsItems) + public static void AddClothes(IPlayerUser user, IList kitsItems) + { + if (!s_IsUnturned) { - if (!s_IsUnturned) - { - return; - } + return; + } - if (!ulong.TryParse(user.Id, out var id)) - { - return; - } + if (!ulong.TryParse(user.Id, out var id)) + { + return; + } - var player = PlayerTool.getPlayer((CSteamID)id); - if (player == null) - { - return; - } + var player = PlayerTool.getPlayer((CSteamID)id); + if (player == null) + { + return; + } - var clothing = player.clothing; + var clothing = player.clothing; - if (clothing.hat != 0) - { - kitsItems.Insert(0, - new(clothing.hat.ToString(), - new KitItemState() - { - ItemAmount = 1, - ItemDurability = clothing.hatQuality, - ItemQuality = clothing.hatQuality, - StateData = clothing.hatState - })); - } + if (clothing.hat != 0) + { + kitsItems.Insert(0, + new(clothing.hat.ToString(), + new KitItemState() + { + ItemAmount = 1, + ItemDurability = clothing.hatQuality, + ItemQuality = clothing.hatQuality, + StateData = clothing.hatState + })); + } - if (clothing.glasses != 0) - { - kitsItems.Insert(0, - new(clothing.glasses.ToString(), - new KitItemState() - { - ItemAmount = 1, - ItemDurability = clothing.glassesQuality, - ItemQuality = clothing.glassesQuality, - StateData = clothing.glassesState - })); - } + if (clothing.glasses != 0) + { + kitsItems.Insert(0, + new(clothing.glasses.ToString(), + new KitItemState() + { + ItemAmount = 1, + ItemDurability = clothing.glassesQuality, + ItemQuality = clothing.glassesQuality, + StateData = clothing.glassesState + })); + } - if (clothing.mask != 0) - { - kitsItems.Insert(0, - new(clothing.mask.ToString(), - new KitItemState() - { - ItemAmount = 1, - ItemDurability = clothing.maskQuality, - ItemQuality = clothing.maskQuality, - StateData = clothing.maskState - })); - } + if (clothing.mask != 0) + { + kitsItems.Insert(0, + new(clothing.mask.ToString(), + new KitItemState() + { + ItemAmount = 1, + ItemDurability = clothing.maskQuality, + ItemQuality = clothing.maskQuality, + StateData = clothing.maskState + })); + } - if (clothing.pants != 0) - { - kitsItems.Insert(0, - new(clothing.pants.ToString(), - new KitItemState() - { - ItemAmount = 1, - ItemDurability = clothing.pantsQuality, - ItemQuality = clothing.pantsQuality, - StateData = clothing.pantsState - })); - } + if (clothing.pants != 0) + { + kitsItems.Insert(0, + new(clothing.pants.ToString(), + new KitItemState() + { + ItemAmount = 1, + ItemDurability = clothing.pantsQuality, + ItemQuality = clothing.pantsQuality, + StateData = clothing.pantsState + })); + } - if (clothing.vest != 0) - { - kitsItems.Insert(0, - new(clothing.vest.ToString(), - new KitItemState() - { - ItemAmount = 1, - ItemDurability = clothing.vestQuality, - ItemQuality = clothing.vestQuality, - StateData = clothing.vestState - })); - } + if (clothing.vest != 0) + { + kitsItems.Insert(0, + new(clothing.vest.ToString(), + new KitItemState() + { + ItemAmount = 1, + ItemDurability = clothing.vestQuality, + ItemQuality = clothing.vestQuality, + StateData = clothing.vestState + })); + } - if (clothing.shirt != 0) - { - kitsItems.Insert(0, - new(clothing.shirt.ToString(), - new KitItemState() - { - ItemAmount = 1, - ItemDurability = clothing.shirtQuality, - ItemQuality = clothing.shirtQuality, - StateData = clothing.shirtState - })); - } + if (clothing.shirt != 0) + { + kitsItems.Insert(0, + new(clothing.shirt.ToString(), + new KitItemState() + { + ItemAmount = 1, + ItemDurability = clothing.shirtQuality, + ItemQuality = clothing.shirtQuality, + StateData = clothing.shirtState + })); + } - if (clothing.backpack != 0) - { - kitsItems.Insert(0, - new(clothing.backpack.ToString(), - new KitItemState() - { - ItemAmount = 1, - ItemDurability = clothing.backpackQuality, - ItemQuality = clothing.backpackQuality, - StateData = clothing.backpackState - })); - } + if (clothing.backpack != 0) + { + kitsItems.Insert(0, + new(clothing.backpack.ToString(), + new KitItemState() + { + ItemAmount = 1, + ItemDurability = clothing.backpackQuality, + ItemQuality = clothing.backpackQuality, + StateData = clothing.backpackState + })); } } } \ No newline at end of file diff --git a/Kits/Kits.cs b/Kits/Kits.cs deleted file mode 100644 index 10ba55e..0000000 --- a/Kits/Kits.cs +++ /dev/null @@ -1,38 +0,0 @@ -using JetBrains.Annotations; -using Kits.API; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using OpenMod.API.Plugins; -using OpenMod.Core.Plugins; -using System; -using System.Threading.Tasks; - -[assembly: PluginMetadata("Kits", DisplayName = "Kits", Author = "EvolutionPlugins", - Website = "https://discord.gg/6KymqGv")] - -namespace Kits -{ - [UsedImplicitly] - public class Kits : OpenModUniversalPlugin - { - private readonly ILogger m_Logger; - private readonly IServiceProvider m_ServiceProvider; - - public Kits(ILogger logger, IServiceProvider serviceProvider) : base(serviceProvider) - { - m_Logger = logger; - m_ServiceProvider = serviceProvider; - } - - protected override Task OnLoadAsync() - { - m_Logger.LogInformation("Made with <3 by EvolutionPlugins"); - m_Logger.LogInformation("https://github.com/evolutionplugins \\ https://github.com/diffoz"); - m_Logger.LogInformation("Discord support: https://discord.gg/6KymqGv"); - - m_ServiceProvider.GetRequiredService(); /* just load database service */ - - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/Kits/Kits.csproj b/Kits/Kits.csproj index 5090a29..fe8a44a 100644 --- a/Kits/Kits.csproj +++ b/Kits/Kits.csproj @@ -9,7 +9,7 @@ true true Kits - latest + 10 enable nullable 0.0.0 @@ -22,8 +22,6 @@ https://github.com/DiFFoZ/Kits git https://github.com/DiFFoZ/Kits - true - true @@ -36,12 +34,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + @@ -50,15 +45,7 @@ - + - - - - JetBrainsAnnotations - - - - \ No newline at end of file diff --git a/Kits/KitsPlugin.cs b/Kits/KitsPlugin.cs new file mode 100644 index 0000000..a30ddcf --- /dev/null +++ b/Kits/KitsPlugin.cs @@ -0,0 +1,35 @@ +using Kits.API; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using OpenMod.API.Plugins; +using OpenMod.Core.Plugins; +using System; +using System.Threading.Tasks; + +[assembly: PluginMetadata("Kits", DisplayName = "Kits", Author = "EvolutionPlugins", + Website = "https://discord.gg/6KymqGv")] + +namespace Kits; + +public class KitsPlugin : OpenModUniversalPlugin +{ + private readonly ILogger m_Logger; + private readonly IServiceProvider m_ServiceProvider; + + public KitsPlugin(ILogger logger, IServiceProvider serviceProvider) : base(serviceProvider) + { + m_Logger = logger; + m_ServiceProvider = serviceProvider; + } + + protected override Task OnLoadAsync() + { + m_Logger.LogInformation("Made with <3 by EvolutionPlugins"); + m_Logger.LogInformation("https://github.com/evolutionplugins \\ https://github.com/diffoz"); + m_Logger.LogInformation("Discord support: https://discord.gg/6KymqGv"); + + m_ServiceProvider.GetRequiredService(); /* just load database service */ + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Kits/Migrations/20210623164103_Initial.Designer.cs b/Kits/Migrations/20210623164103_Initial.Designer.cs index 841de86..4c9d8a8 100644 --- a/Kits/Migrations/20210623164103_Initial.Designer.cs +++ b/Kits/Migrations/20210623164103_Initial.Designer.cs @@ -1,6 +1,6 @@ // using System; -using Kits.Databases.Mysql; +using Kits.Databases.MySql; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/Kits/Migrations/20210623164103_Initial.cs b/Kits/Migrations/20210623164103_Initial.cs index 045b6d8..ca07516 100644 --- a/Kits/Migrations/20210623164103_Initial.cs +++ b/Kits/Migrations/20210623164103_Initial.cs @@ -1,5 +1,4 @@ -using System; -using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; namespace Kits.Migrations diff --git a/Kits/Migrations/KitsDbContextModelSnapshot.cs b/Kits/Migrations/KitsDbContextModelSnapshot.cs index ef2e332..8d32938 100644 --- a/Kits/Migrations/KitsDbContextModelSnapshot.cs +++ b/Kits/Migrations/KitsDbContextModelSnapshot.cs @@ -1,6 +1,6 @@ // using System; -using Kits.Databases.Mysql; +using Kits.Databases.MySql; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; diff --git a/Kits/Models/KitCooldownData.cs b/Kits/Models/KitCooldownData.cs deleted file mode 100644 index 3a6f8f0..0000000 --- a/Kits/Models/KitCooldownData.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Kits.Models -{ - public class KitCooldownData - { - public string? KitName { get; set; } - public DateTime KitCooldown { get; set; } - } -} \ No newline at end of file diff --git a/Kits/Models/KitsCooldownData.cs b/Kits/Models/KitsCooldownData.cs deleted file mode 100644 index 859bcb1..0000000 --- a/Kits/Models/KitsCooldownData.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Kits.Models -{ - public class KitsCooldownData - { - public Dictionary>? KitsCooldown { get; set; } - } -} \ No newline at end of file diff --git a/Kits/Models/KitsData.cs b/Kits/Models/KitsData.cs deleted file mode 100644 index 9744024..0000000 --- a/Kits/Models/KitsData.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Kits.API; -using System.Collections.Generic; - -namespace Kits.Models -{ - public class KitsData - { - public List? Kits { get; set; } - } -} \ No newline at end of file diff --git a/Kits/PluginContainerConfigurator.cs b/Kits/PluginContainerConfigurator.cs index a67ae84..0c26027 100644 --- a/Kits/PluginContainerConfigurator.cs +++ b/Kits/PluginContainerConfigurator.cs @@ -1,14 +1,13 @@ -using Kits.Databases.Mysql; +using Kits.Databases.MySql; using OpenMod.API.Plugins; using OpenMod.EntityFrameworkCore.MySql.Extensions; -namespace Kits +namespace Kits; + +public class PluginContainerConfigurator : IPluginContainerConfigurator { - public class PluginContainerConfigurator : IPluginContainerConfigurator + public void ConfigureContainer(IPluginServiceConfigurationContext context) { - public void ConfigureContainer(IPluginServiceConfigurationContext context) - { - context.ContainerBuilder.AddMySqlDbContext(); - } + context.ContainerBuilder.AddMySqlDbContext(); } } diff --git a/Kits/ServiceConfigurator.cs b/Kits/ServiceConfigurator.cs index 71f1c53..e53e377 100644 --- a/Kits/ServiceConfigurator.cs +++ b/Kits/ServiceConfigurator.cs @@ -1,20 +1,24 @@ -extern alias JetBrainsAnnotations; -using EvolutionPlugins.Economy.Stub; -using JetBrainsAnnotations::JetBrains.Annotations; +using EvolutionPlugins.Economy.Stub; +using Kits.API.Databases; +using Kits.Databases; using Microsoft.Extensions.DependencyInjection; using OpenMod.API.Ioc; using OpenMod.API.Prioritization; -namespace Kits +namespace Kits; + +[Priority(Priority = Priority.Lowest)] +public class ServiceConfigurator : IServiceConfigurator { - [UsedImplicitly] - [Priority(Priority = Priority.Lowest)] - public class ServiceConfigurator : IServiceConfigurator + public void ConfigureServices(IOpenModServiceConfigurationContext openModStartupContext, + IServiceCollection serviceCollection) { - public void ConfigureServices(IOpenModServiceConfigurationContext openModStartupContext, - IServiceCollection serviceCollection) + serviceCollection.AddEconomyStub(); + + serviceCollection.Configure(o => { - serviceCollection.AddEconomyStub(); - } + o.AddProvider("datastore"); + o.AddProvider("mysql"); + }); } } \ No newline at end of file diff --git a/Kits/Services/KitCooldownStore.cs b/Kits/Services/KitCooldownStore.cs deleted file mode 100644 index 49646ab..0000000 --- a/Kits/Services/KitCooldownStore.cs +++ /dev/null @@ -1,116 +0,0 @@ -using JetBrains.Annotations; -using Kits.API; -using Kits.Models; -using Microsoft.Extensions.DependencyInjection; -using OpenMod.API.Ioc; -using OpenMod.API.Permissions; -using OpenMod.API.Persistence; -using OpenMod.Core.Helpers; -using OpenMod.Core.Permissions; -using OpenMod.Extensions.Games.Abstractions.Players; -using System; -using System.Threading.Tasks; - -[assembly: RegisterPermission("nocooldown", Description = "Allows use kit without waiting for cooldown")] - -namespace Kits.Providers -{ - [PluginServiceImplementation(Lifetime = ServiceLifetime.Singleton)] - [UsedImplicitly] - public class KitCooldownStore : IKitCooldownStore, IDisposable - { - private const string c_CooldownKey = "cooldowns"; - private const string c_NoCooldownPermission = "nocooldown"; - - private readonly IDataStore m_DataStore; - private readonly Kits m_Plugin; - private readonly IPermissionChecker m_PermissionChecker; - - private KitsCooldownData m_KitsCooldownData = null!; - private IDisposable? m_FileWatcher = null!; - - public KitCooldownStore(Kits plugin, IPermissionChecker permissionChecker) - { - m_DataStore = plugin.DataStore; - m_Plugin = plugin; - m_PermissionChecker = permissionChecker; - - AsyncHelper.RunSync(LoadData); - } - - public async Task GetLastCooldownAsync(IPlayerUser player, string kitName) - { - if (await m_PermissionChecker.CheckPermissionAsync(player, c_NoCooldownPermission) == - PermissionGrantResult.Grant - || !m_KitsCooldownData.KitsCooldown!.TryGetValue(player.Id, out var kitCooldowns)) - { - return null; - } - - var kitCooldown = kitCooldowns!.Find(x => - x.KitName?.Equals(kitName, StringComparison.CurrentCultureIgnoreCase) == true); - return kitCooldown == null ? null : DateTime.Now - kitCooldown.KitCooldown; - } - - public async Task RegisterCooldownAsync(IPlayerUser player, string kitName, DateTime time) - { - if (await m_PermissionChecker.CheckPermissionAsync(player, c_NoCooldownPermission) == - PermissionGrantResult.Grant) - { - return; - } - - kitName = kitName.ToLower(); - - if (m_KitsCooldownData.KitsCooldown!.TryGetValue(player.Id, out var kitCooldowns)) - { - var kitCooldown = kitCooldowns!.Find(x => x.KitName == kitName); - if (kitCooldown == null) - { - kitCooldown = new() { KitName = kitName }; - kitCooldowns.Add(kitCooldown); - } - - kitCooldown.KitCooldown = time; - } - else - { - m_KitsCooldownData.KitsCooldown.Add(player.Id, - new() { new() { KitCooldown = time, KitName = kitName } }); - } - - await SaveData(); - } - - private async Task LoadFromDisk() - { - if (await m_DataStore.ExistsAsync(c_CooldownKey)) - { - m_KitsCooldownData = await m_DataStore.LoadAsync(c_CooldownKey) ?? - new() { KitsCooldown = new() }; - } - else - { - m_KitsCooldownData = new() { KitsCooldown = new() }; - await SaveData(); - } - } - - private async Task LoadData() - { - await LoadFromDisk(); - m_FileWatcher = m_DataStore.AddChangeWatcher(c_CooldownKey, m_Plugin, - () => AsyncHelper.RunSync(LoadFromDisk)); - } - - private Task SaveData() - { - return m_DataStore.SaveAsync(c_CooldownKey, m_KitsCooldownData); - } - - public void Dispose() - { - m_FileWatcher?.Dispose(); - } - } -} \ No newline at end of file diff --git a/Kits/Services/KitManager.cs b/Kits/Services/KitManager.cs index 1039dd5..0272e13 100644 --- a/Kits/Services/KitManager.cs +++ b/Kits/Services/KitManager.cs @@ -1,5 +1,6 @@ -using JetBrains.Annotations; -using Kits.API; +using Kits.API; +using Kits.API.Cooldowns; +using Kits.API.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using OpenMod.API.Commands; @@ -12,86 +13,81 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Kits.Providers +namespace Kits.Services; + +[PluginServiceImplementation(Lifetime = ServiceLifetime.Transient, Priority = Priority.Lowest)] +public class KitManager : IKitManager { - [PluginServiceImplementation(Lifetime = ServiceLifetime.Transient, Priority = Priority.Lowest)] - [UsedImplicitly] - public class KitManager : IKitManager + private readonly IEconomyProvider m_EconomyProvider; + private readonly IKitCooldownStore m_KitCooldownStore; + private readonly IKitStore m_KitStore; + private readonly IStringLocalizer m_StringLocalizer; + private readonly IPermissionChecker m_PermissionChecker; + private readonly IServiceProvider m_ServiceProvider; + + public KitManager(IEconomyProvider economyProvider, IKitCooldownStore kitCooldownStore, IKitStore kitStore, + IStringLocalizer stringLocalizer, IPermissionChecker permissionChecker, IServiceProvider serviceProvider) { - private readonly IEconomyProvider m_EconomyProvider; - private readonly IKitCooldownStore m_KitCooldownStore; - private readonly IKitStore m_KitStore; - private readonly IStringLocalizer m_StringLocalizer; - private readonly IPermissionChecker m_PermissionChecker; - private readonly IServiceProvider m_ServiceProvider; + m_EconomyProvider = economyProvider; + m_KitCooldownStore = kitCooldownStore; + m_KitStore = kitStore; + m_StringLocalizer = stringLocalizer; + m_PermissionChecker = permissionChecker; + m_ServiceProvider = serviceProvider; + } - public KitManager(IEconomyProvider economyProvider, IKitCooldownStore kitCooldownStore, IKitStore kitStore, - IStringLocalizer stringLocalizer, IPermissionChecker permissionChecker, IServiceProvider serviceProvider) + public async Task GiveKitAsync(IPlayerUser user, string name, ICommandActor? instigator = null, + bool forceGiveKit = false) + { + var kit = await m_KitStore.FindKitByNameAsync(name); + if (kit == null) { - m_EconomyProvider = economyProvider; - m_KitCooldownStore = kitCooldownStore; - m_KitStore = kitStore; - m_StringLocalizer = stringLocalizer; - m_PermissionChecker = permissionChecker; - m_ServiceProvider = serviceProvider; + throw new UserFriendlyException(m_StringLocalizer["commands:kit:notFound", new { Name = name }]); } - public async Task GiveKitAsync(IPlayerUser user, string name, ICommandActor? instigator = null, - bool forceGiveKit = false) + if (!forceGiveKit && await m_PermissionChecker.CheckPermissionAsync(user, + $"{KitStore.c_KitsKey}.{kit.Name}") != PermissionGrantResult.Grant) { - var kit = await m_KitStore.FindKitByNameAsync(name); - if (kit == null) - { - throw new UserFriendlyException(m_StringLocalizer["commands:kit:notFound", new { Name = name }]); - } - - if (!forceGiveKit && await m_PermissionChecker.CheckPermissionAsync(user, - $"{KitStore.c_KitsKey}.{kit.Name}") != PermissionGrantResult.Grant) - { - throw new UserFriendlyException(m_StringLocalizer["commands:kit:noPermission", new { Kit = kit }]); - } + throw new UserFriendlyException(m_StringLocalizer["commands:kit:noPermission", new { Kit = kit }]); + } - var cooldown = await m_KitCooldownStore.GetLastCooldownAsync(user, name); - if (!forceGiveKit && cooldown != null) - { - if (cooldown.Value.TotalSeconds < kit.Cooldown) - { - throw new UserFriendlyException(m_StringLocalizer["commands:kit:cooldown", - new { Kit = kit, Cooldown = kit.Cooldown - cooldown.Value.TotalSeconds }]); - } - } + var cooldown = await m_KitCooldownStore.GetLastCooldownAsync(user, name); + if (!forceGiveKit && cooldown != null && cooldown.Value.TotalSeconds < kit.Cooldown) + { + throw new UserFriendlyException(m_StringLocalizer["commands:kit:cooldown", + new { Kit = kit, Cooldown = kit.Cooldown - cooldown.Value.TotalSeconds }]); + } - if (!forceGiveKit && kit.Cost != 0) - { - await m_EconomyProvider.UpdateBalanceAsync(user.Id, user.Type, -kit.Cost, - m_StringLocalizer["commands:kit:balanceUpdateReason:buy", new { Kit = kit }]); - } + if (!forceGiveKit && kit.Cost != 0) + { + await m_EconomyProvider.UpdateBalanceAsync(user.Id, user.Type, -kit.Cost, + m_StringLocalizer["commands:kit:balanceUpdateReason:buy", new { Kit = kit }]); + } - await m_KitCooldownStore.RegisterCooldownAsync(user, name, DateTime.Now); + await m_KitCooldownStore.RegisterCooldownAsync(user, name, DateTime.Now); - await kit.GiveKitToPlayer(user, m_ServiceProvider); + await kit.GiveKitToPlayer(user, m_ServiceProvider); - await user.PrintMessageAsync(m_StringLocalizer["commands:kit:success", new { Kit = kit }]); + await user.PrintMessageAsync(m_StringLocalizer["commands:kit:success", new { Kit = kit }]); - if (instigator != null) - { - await instigator.PrintMessageAsync(m_StringLocalizer["commands:kit:success", new { Kit = kit }]); - } + if (instigator != null) + { + await instigator.PrintMessageAsync(m_StringLocalizer["commands:kit:success", new { Kit = kit }]); } + } - public async Task> GetAvailableKitsForPlayerAsync(IPlayerUser player) + public async Task> GetAvailableKitsForPlayerAsync(IPlayerUser player) + { + var list = new List(); + foreach (var kit in await m_KitStore.GetKitsAsync()) { - var list = new List(); - foreach (var kit in await m_KitStore.GetKitsAsync()) + if (await m_PermissionChecker.CheckPermissionAsync(player, + $"{KitStore.c_KitsKey}.{kit.Name}") == PermissionGrantResult.Grant) { - if (await m_PermissionChecker.CheckPermissionAsync(player, - $"{KitStore.c_KitsKey}.{kit.Name}") == PermissionGrantResult.Grant) - { - list.Add(kit); - } + list.Add(kit); } - - return list; } + + return list; } } \ No newline at end of file diff --git a/Kits/Services/KitStore.cs b/Kits/Services/KitStore.cs index 0e17351..9355349 100644 --- a/Kits/Services/KitStore.cs +++ b/Kits/Services/KitStore.cs @@ -1,8 +1,11 @@ -using JetBrains.Annotations; +using Cysharp.Text; using Kits.API; +using Kits.API.Databases; +using Kits.API.Models; using Kits.Databases; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using OpenMod.API.Eventing; using OpenMod.API.Ioc; using OpenMod.API.Permissions; @@ -12,133 +15,133 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Kits.Providers +namespace Kits.Services; + +[PluginServiceImplementation(Lifetime = ServiceLifetime.Singleton)] +public class KitStore : IKitStore, IAsyncDisposable { - [PluginServiceImplementation(Lifetime = ServiceLifetime.Singleton)] - [UsedImplicitly] - public class KitStore : IKitStore, IAsyncDisposable - { - public const string c_KitsKey = "kits"; + public const string c_KitsKey = "kits"; - private readonly Kits m_Plugin; - private readonly IPermissionRegistry m_PermissionRegistry; - private readonly ILogger m_Logger; - private readonly IServiceProvider m_Provider; - private readonly IDisposable? m_ConfigurationChangedWatcher; + private readonly KitsPlugin m_Plugin; + private readonly IPermissionRegistry m_PermissionRegistry; + private readonly ILogger m_Logger; + private readonly IOptions m_Options; + private readonly IServiceProvider m_ServiceProvider; + private readonly IDisposable? m_ConfigurationChangedWatcher; - private IKitDatabase m_Database = null!; + public IKitDatabaseProvider Database { get; private set; } = null!; + + public KitStore(KitsPlugin plugin, IPermissionRegistry permissionRegistry, ILogger logger, + IEventBus eventBus, IOptions options, IServiceProvider serviceProvider) + { + m_Plugin = plugin; + m_PermissionRegistry = permissionRegistry; + m_Logger = logger; + m_Options = options; + m_ServiceProvider = serviceProvider; + AsyncHelper.RunSync(ParseLoadDatabase); + + m_ConfigurationChangedWatcher = + eventBus.Subscribe(plugin, PluginConfigurationChanged); + } + + private Task PluginConfigurationChanged(IServiceProvider serviceprovider, object? sender, + PluginConfigurationChangedEvent @event) + { + return @event.Plugin != m_Plugin ? Task.CompletedTask : ParseLoadDatabase(); + } - public IKitDatabase Database => m_Database; + private async Task ParseLoadDatabase() + { + var type = m_Plugin.Configuration["database:connectionType"]; + var databaseType = m_Options.Value.GetPreferredDatabase(type); - public KitStore(Kits plugin, IPermissionRegistry permissionRegistry, ILogger logger, - IEventBus eventBus, IServiceProvider provider) + if (databaseType == null) { - m_Plugin = plugin; - m_PermissionRegistry = permissionRegistry; - m_Logger = logger; - m_Provider = provider; - AsyncHelper.RunSync(ParseLoadDatabase); - - m_ConfigurationChangedWatcher = - eventBus.Subscribe(plugin, PluginConfigurationChanged); + m_Logger.LogWarning("Unable to parse {DatabaseType}. Setting to default: `datastore`", type); + Database = new DataStoreKitDataStore(m_Plugin.LifetimeScope); } - - private Task PluginConfigurationChanged(IServiceProvider serviceprovider, object? sender, - PluginConfigurationChangedEvent @event) + else { - return @event.Plugin != m_Plugin ? Task.CompletedTask : ParseLoadDatabase(); + m_Logger.LogInformation("Database type set to `{DatabaseType}`", type); + Database = (ActivatorUtilities.CreateInstance(m_ServiceProvider, databaseType) as IKitDatabaseProvider) + ?? throw new Exception($"Failed to create database provider of type {databaseType.Name}"); } - private async Task ParseLoadDatabase() - { - var type = m_Plugin.Configuration["database:connectionType"]; - m_Database = type.ToLower() switch - { - "mysql" => new MySqlKitDatabase(m_Provider), - "datastore" => new DataStoreKitDatabase(m_Plugin), - _ => null! - }; + await Database.LoadDatabaseAsync(); + await RegisterPermissionsAsync(); + } - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (m_Database == null) - { - m_Database = new DataStoreKitDatabase(m_Plugin); - m_Logger.LogWarning("Unable to parse {DatabaseType}. Setting to default: `datastore`", type); - } - else - { - m_Logger.LogInformation("Datastore type set to `{DatabaseType}`", type); - } + public Task> GetKitsAsync() + { + return Database.GetKitsAsync(); + } - await m_Database.LoadDatabaseAsync(); - await RegisterPermissionsAsync(); + public async Task AddKitAsync(Kit kit) + { + if (kit?.Name == null || kit?.Items == null) + { + throw new ArgumentNullException(nameof(kit)); } - public Task> GetKitsAsync() + if (await Database.AddKitAsync(kit)) { - return m_Database.GetKitsAsync(); + RegisterPermission(kit.Name); } + } - public async Task AddKitAsync(Kit kit) + public async Task FindKitByNameAsync(string kitName) + { + if (string.IsNullOrEmpty(kitName)) { - if (kit?.Name == null || kit?.Items == null) - { - throw new ArgumentNullException(nameof(kit)); - } - - if (await m_Database.AddKitAsync(kit)) - { - RegisterPermission(kit.Name); - } + throw new ArgumentException($"'{nameof(kitName)}' cannot be null or empty.", nameof(kitName)); } - public async Task FindKitByNameAsync(string kitName) + var kit = await Database.FindKitByNameAsync(kitName); + if (kit?.Name is not null) { - if (string.IsNullOrEmpty(kitName)) - { - throw new ArgumentException($"'{nameof(kitName)}' cannot be null or empty.", nameof(kitName)); - } - - var kit = await m_Database.FindKitByNameAsync(kitName); - if (kit?.Name is not null) - { - RegisterPermission(kit.Name); - } - - return kit; + RegisterPermission(kit.Name); } - public Task RemoveKitAsync(string kitName) - { - if (string.IsNullOrEmpty(kitName)) - { - return Task.FromException(new ArgumentException( - $"'{nameof(kitName)}' cannot be null or empty.", nameof(kitName))); - } + return kit; + } - return m_Database.RemoveKitAsync(kitName); + public Task RemoveKitAsync(string kitName) + { + if (string.IsNullOrEmpty(kitName)) + { + return Task.FromException(new ArgumentException( + $"'{nameof(kitName)}' cannot be null or empty.", nameof(kitName))); } - protected virtual async Task RegisterPermissionsAsync() + return Database.RemoveKitAsync(kitName); + } + + protected virtual async Task RegisterPermissionsAsync() + { + foreach (var kit in await Database.GetKitsAsync()) { - foreach (var kit in await m_Database.GetKitsAsync()) + if (kit.Name != null) { - if (kit.Name != null) - { - RegisterPermission(kit.Name); - } + RegisterPermission(kit.Name); } } + } - protected virtual void RegisterPermission(string kitName) - { - m_PermissionRegistry.RegisterPermission(m_Plugin, "kits." + kitName.ToLower()); - } + protected virtual void RegisterPermission(string kitName) + { + m_PermissionRegistry.RegisterPermission(m_Plugin, ZString.Concat("kits.", kitName.ToLower())); + } - public ValueTask DisposeAsync() + public ValueTask DisposeAsync() + { + m_ConfigurationChangedWatcher?.Dispose(); + + if (Database == null) { - m_ConfigurationChangedWatcher?.Dispose(); - return new(m_Database.DisposeSyncOrAsync()); + return new(); } + + return new(Database.DisposeSyncOrAsync()); } } \ No newline at end of file From f84c4fbc25418393f4fc12cdb08d46d6d77d83f4 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Mon, 15 May 2023 23:09:46 +0700 Subject: [PATCH 02/25] Add support for configurable cooldown store provider --- Kits.API/Configuration/GenericOptions.cs | 46 ++++++++ Kits.API/Cooldowns/IKitCooldownStore.cs | 10 +- .../Cooldowns/IKitCooldownStoreProvider.cs | 7 ++ Kits.API/Cooldowns/KitCooldownOptions.cs | 6 + Kits.API/Databases/IKitDatabaseProvider.cs | 18 --- Kits.API/Databases/IKitStoreProvider.cs | 5 + Kits.API/Databases/KitDatabaseOptions.cs | 46 -------- Kits.API/Databases/KitStoreOptions.cs | 6 + Kits.API/IKitManager.cs | 2 + Kits.API/IKitStore.cs | 24 +++- Kits/Cooldowns/KitCooldownStore.cs | 109 ++++++++---------- .../DataStoreKitCooldownStoreProvider.cs | 90 +++++++++++++++ ...aStore.cs => DataStoreKitStoreProvider.cs} | 22 ++-- ...taStoreCore.cs => KitStoreProviderCore.cs} | 4 +- ...tDataStore.cs => MySqlKitStoreProvider.cs} | 29 +++-- Kits/Extensions/ConvertorExtension.cs | 17 ++- Kits/KitsPlugin.cs | 4 +- Kits/ServiceConfigurator.cs | 13 ++- Kits/Services/KitManager.cs | 24 ++-- Kits/Services/KitStore.cs | 97 ++++++++++------ Kits/config.yaml | 7 ++ 21 files changed, 370 insertions(+), 216 deletions(-) create mode 100644 Kits.API/Configuration/GenericOptions.cs create mode 100644 Kits.API/Cooldowns/IKitCooldownStoreProvider.cs create mode 100644 Kits.API/Cooldowns/KitCooldownOptions.cs delete mode 100644 Kits.API/Databases/IKitDatabaseProvider.cs create mode 100644 Kits.API/Databases/IKitStoreProvider.cs delete mode 100644 Kits.API/Databases/KitDatabaseOptions.cs create mode 100644 Kits.API/Databases/KitStoreOptions.cs create mode 100644 Kits/Cooldowns/Providers/DataStoreKitCooldownStoreProvider.cs rename Kits/Databases/{DataStoreKitDataStore.cs => DataStoreKitStoreProvider.cs} (84%) rename Kits/Databases/{KitDataStoreCore.cs => KitStoreProviderCore.cs} (80%) rename Kits/Databases/{MySqlKitDataStore.cs => MySqlKitStoreProvider.cs} (66%) diff --git a/Kits.API/Configuration/GenericOptions.cs b/Kits.API/Configuration/GenericOptions.cs new file mode 100644 index 0000000..0ceea20 --- /dev/null +++ b/Kits.API/Configuration/GenericOptions.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Kits.API.Configuration; +public class GenericOptions where TProvider : class +{ + private readonly List<(Type implementationService, string name)> m_KitProviders = new(); + public IReadOnlyCollection<(Type implementationService, string name)> KitProviders => m_KitProviders.AsReadOnly(); + + public void AddProvider(string name) where T : TProvider + { + AddProvider(typeof(T), name); + } + + public void AddProvider(Type type, string name) + { + if (!typeof(TProvider).IsAssignableFrom(type)) + { + throw new Exception($"Type {type} must be an instance of {typeof(TProvider).Name}!"); + } + + if (m_KitProviders.Any(x => x.name.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + return; + } + + m_KitProviders.Add((type, name)); + } + + public void RemoveProvider() where T : TProvider + { + RemoveProvider(typeof(T)); + } + + public void RemoveProvider(Type type) + { + m_KitProviders.RemoveAll(c => c.implementationService == type); + } + + public Type? FindType(string type) + { + return m_KitProviders.Find(x => x.name.Equals(type, StringComparison.OrdinalIgnoreCase)) + .implementationService; + } +} diff --git a/Kits.API/Cooldowns/IKitCooldownStore.cs b/Kits.API/Cooldowns/IKitCooldownStore.cs index 8d0c889..b3ca96e 100644 --- a/Kits.API/Cooldowns/IKitCooldownStore.cs +++ b/Kits.API/Cooldowns/IKitCooldownStore.cs @@ -1,14 +1,14 @@ -using System; +using OpenMod.API.Ioc; +using OpenMod.API.Permissions; +using System; using System.Threading.Tasks; -using OpenMod.API.Ioc; -using OpenMod.Extensions.Games.Abstractions.Players; namespace Kits.API.Cooldowns; [Service] public interface IKitCooldownStore { - Task GetLastCooldownAsync(IPlayerUser player, string kitName); + Task GetLastCooldownAsync(IPermissionActor actor, string kitName); - Task RegisterCooldownAsync(IPlayerUser player, string kitName, DateTime time); + Task RegisterCooldownAsync(IPermissionActor actor, string kitName, DateTime time); } \ No newline at end of file diff --git a/Kits.API/Cooldowns/IKitCooldownStoreProvider.cs b/Kits.API/Cooldowns/IKitCooldownStoreProvider.cs new file mode 100644 index 0000000..8e43e6f --- /dev/null +++ b/Kits.API/Cooldowns/IKitCooldownStoreProvider.cs @@ -0,0 +1,7 @@ +using System.Threading.Tasks; + +namespace Kits.API.Cooldowns; +public interface IKitCooldownStoreProvider : IKitCooldownStore +{ + Task InitAsync(); +} diff --git a/Kits.API/Cooldowns/KitCooldownOptions.cs b/Kits.API/Cooldowns/KitCooldownOptions.cs new file mode 100644 index 0000000..e8b2f38 --- /dev/null +++ b/Kits.API/Cooldowns/KitCooldownOptions.cs @@ -0,0 +1,6 @@ +using Kits.API.Configuration; + +namespace Kits.API.Cooldowns; +public class KitCooldownOptions : GenericOptions +{ +} diff --git a/Kits.API/Databases/IKitDatabaseProvider.cs b/Kits.API/Databases/IKitDatabaseProvider.cs deleted file mode 100644 index 07a55b3..0000000 --- a/Kits.API/Databases/IKitDatabaseProvider.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Kits.API.Models; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Kits.API.Databases; - -public interface IKitDatabaseProvider -{ - Task LoadDatabaseAsync(); - - Task> GetKitsAsync(); - - Task FindKitByNameAsync(string name); - - Task AddKitAsync(Kit kit); - - Task RemoveKitAsync(string name); -} \ No newline at end of file diff --git a/Kits.API/Databases/IKitStoreProvider.cs b/Kits.API/Databases/IKitStoreProvider.cs new file mode 100644 index 0000000..b3f14a7 --- /dev/null +++ b/Kits.API/Databases/IKitStoreProvider.cs @@ -0,0 +1,5 @@ +namespace Kits.API.Databases; + +public interface IKitStoreProvider : IKitStore +{ +} \ No newline at end of file diff --git a/Kits.API/Databases/KitDatabaseOptions.cs b/Kits.API/Databases/KitDatabaseOptions.cs deleted file mode 100644 index 500a4aa..0000000 --- a/Kits.API/Databases/KitDatabaseOptions.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Kits.API.Databases; -public class KitDatabaseOptions -{ - private readonly List<(Type implementationService, string name)> m_KitDatabaseProviders = new(); - public IReadOnlyCollection<(Type implementationService, string name)> KitDatabaseProviders => m_KitDatabaseProviders.AsReadOnly(); - - public void AddProvider(string name) where TProvider : IKitDatabaseProvider - { - AddProvider(typeof(TProvider), name); - } - - public void AddProvider(Type type, string name) - { - if (!typeof(IKitDatabaseProvider).IsAssignableFrom(type)) - { - throw new Exception($"Type {type} must be an instance of IKitDatabaseProvider!"); - } - - if (m_KitDatabaseProviders.Any(x => x.name.Equals(name, StringComparison.OrdinalIgnoreCase))) - { - return; - } - - m_KitDatabaseProviders.Add((type, name)); - } - - public void RemoveProvider() where TProvider : IKitDatabaseProvider - { - RemoveProvider(typeof(TProvider)); - } - - public void RemoveProvider(Type type) - { - m_KitDatabaseProviders.RemoveAll(c => c.implementationService == type); - } - - public Type? GetPreferredDatabase(string type) - { - return m_KitDatabaseProviders.Find(x => x.name.Equals(type, StringComparison.OrdinalIgnoreCase)) - .implementationService; - } -} diff --git a/Kits.API/Databases/KitStoreOptions.cs b/Kits.API/Databases/KitStoreOptions.cs new file mode 100644 index 0000000..72529c7 --- /dev/null +++ b/Kits.API/Databases/KitStoreOptions.cs @@ -0,0 +1,6 @@ +using Kits.API.Configuration; + +namespace Kits.API.Databases; +public class KitStoreOptions : GenericOptions +{ +} diff --git a/Kits.API/IKitManager.cs b/Kits.API/IKitManager.cs index e1f0ebf..deac115 100644 --- a/Kits.API/IKitManager.cs +++ b/Kits.API/IKitManager.cs @@ -12,5 +12,7 @@ public interface IKitManager { Task GiveKitAsync(IPlayerUser user, string name, ICommandActor? instigator = null, bool forceGiveKit = false); + Task GiveKitAsync(IPlayerUser user, Kit kit, ICommandActor? instigator = null, bool forceGiveKit = false); + Task> GetAvailableKitsForPlayerAsync(IPlayerUser player); } \ No newline at end of file diff --git a/Kits.API/IKitStore.cs b/Kits.API/IKitStore.cs index 16cfd20..d671c6a 100644 --- a/Kits.API/IKitStore.cs +++ b/Kits.API/IKitStore.cs @@ -1,18 +1,36 @@ -using Kits.API.Models; +using Kits.API.Databases; +using Kits.API.Models; using OpenMod.API.Ioc; using System.Collections.Generic; using System.Threading.Tasks; namespace Kits.API; +/// +/// The service for getting kits +/// [Service] public interface IKitStore { + /// + /// Gets the kits from + /// + /// Kits list Task> GetKitsAsync(); - Task FindKitByNameAsync(string kitName); + Task FindKitByNameAsync(string name); + + Task IsKitExists(string name); Task AddKitAsync(Kit kit); - Task RemoveKitAsync(string kitName); + Task RemoveKitAsync(string name); + + Task UpdateKitAsync(Kit kit); + + /// + /// Initializing the kit store and . + /// Should not be called from plugins + /// + Task InitAsync(); } \ No newline at end of file diff --git a/Kits/Cooldowns/KitCooldownStore.cs b/Kits/Cooldowns/KitCooldownStore.cs index 917dd42..f0a989c 100644 --- a/Kits/Cooldowns/KitCooldownStore.cs +++ b/Kits/Cooldowns/KitCooldownStore.cs @@ -1,12 +1,14 @@ -using Kits.API.Cooldowns; -using Kits.Cooldowns.Models; +using Autofac; +using Kits.API.Cooldowns; +using Kits.Cooldowns.Providers; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using OpenMod.API.Ioc; using OpenMod.API.Permissions; -using OpenMod.API.Persistence; using OpenMod.Core.Helpers; using OpenMod.Core.Permissions; -using OpenMod.Extensions.Games.Abstractions.Players; using System; using System.Threading.Tasks; @@ -15,99 +17,80 @@ namespace Kits.Cooldowns; [PluginServiceImplementation(Lifetime = ServiceLifetime.Singleton)] -public class KitCooldownStore : IKitCooldownStore, IDisposable +public class KitCooldownStore : IKitCooldownStore, IAsyncDisposable { - private const string c_CooldownKey = "cooldowns"; private const string c_NoCooldownPermission = "nocooldown"; - private readonly IDataStore m_DataStore; private readonly KitsPlugin m_Plugin; private readonly IPermissionChecker m_PermissionChecker; + private readonly IOptions m_Options; + private readonly IServiceProvider m_ServiceProvider; + private readonly ILogger m_Logger; - private KitsCooldownData m_KitsCooldownData = null!; - private IDisposable? m_FileWatcher; + private IKitCooldownStoreProvider m_CooldownProvider = null!; - public KitCooldownStore(KitsPlugin plugin, IPermissionChecker permissionChecker) + public KitCooldownStore(KitsPlugin plugin, IPermissionChecker permissionChecker, IOptions options, IServiceProvider serviceProvider, + ILogger logger) { - m_DataStore = plugin.DataStore; m_Plugin = plugin; m_PermissionChecker = permissionChecker; + m_Options = options; + m_ServiceProvider = serviceProvider; + m_Logger = logger; - AsyncHelper.RunSync(LoadData); + AsyncHelper.RunSync(InitAsync); } - public async Task GetLastCooldownAsync(IPlayerUser player, string kitName) + private async Task InitAsync() { - if (await m_PermissionChecker.CheckPermissionAsync(player, c_NoCooldownPermission) == - PermissionGrantResult.Grant - || !m_KitsCooldownData.KitsCooldown!.TryGetValue(player.Id, out var kitCooldowns)) - { - return null; - } - - var kitCooldown = kitCooldowns!.Find(x => - x.KitName?.Equals(kitName, StringComparison.CurrentCultureIgnoreCase) == true); - return kitCooldown == null ? null : DateTime.Now - kitCooldown.KitCooldown; - } - - public async Task RegisterCooldownAsync(IPlayerUser player, string kitName, DateTime time) - { - if (await m_PermissionChecker.CheckPermissionAsync(player, c_NoCooldownPermission) == - PermissionGrantResult.Grant) - { - return; - } + var configuration = m_Plugin.LifetimeScope.Resolve(); + var type = configuration["cooldowns:connectionType"] ?? string.Empty; + var providerType = m_Options.Value.FindType(type); - kitName = kitName.ToLower(); - - if (m_KitsCooldownData.KitsCooldown!.TryGetValue(player.Id, out var kitCooldowns)) + if (providerType != null) { - var kitCooldown = kitCooldowns!.Find(x => x.KitName == kitName); - if (kitCooldown == null) + m_Logger.LogInformation("Cooldown store type set to `{DatabaseType}`", type); + try { - kitCooldown = new() { KitName = kitName }; - kitCooldowns.Add(kitCooldown); + m_CooldownProvider = (ActivatorUtilities.CreateInstance(m_ServiceProvider, providerType) as IKitCooldownStoreProvider)!; + } + catch (Exception ex) + { + m_Logger.LogError(ex, "Failed to initialize {CooldownStoreName}. Defaulting to `datastore`", providerType.Name); } - - kitCooldown.KitCooldown = time; } else { - m_KitsCooldownData.KitsCooldown.Add(player.Id, - new() { new() { KitCooldown = time, KitName = kitName } }); + m_Logger.LogWarning("Unable to parse {DatabaseType}. Setting to default: `datastore`", type); } - await SaveData(); + m_CooldownProvider ??= new DataStoreKitCooldownStoreProvider(m_Plugin); + + await m_CooldownProvider.InitAsync(); } - private async Task LoadFromDisk() + public async Task GetLastCooldownAsync(IPermissionActor actor, string kitName) { - if (await m_DataStore.ExistsAsync(c_CooldownKey)) - { - m_KitsCooldownData = await m_DataStore.LoadAsync(c_CooldownKey) ?? - new() { KitsCooldown = new() }; - } - else - { - m_KitsCooldownData = new() { KitsCooldown = new() }; - await SaveData(); - } + return await HasNoCooldownPermission(actor) ? null : await m_CooldownProvider.GetLastCooldownAsync(actor, kitName); } - private async Task LoadData() + public async Task RegisterCooldownAsync(IPermissionActor actor, string kitName, DateTime time) { - await LoadFromDisk(); - m_FileWatcher = m_DataStore.AddChangeWatcher(c_CooldownKey, m_Plugin, - () => AsyncHelper.RunSync(LoadFromDisk)); + if (await HasNoCooldownPermission(actor)) + { + return; + } + + await m_CooldownProvider.RegisterCooldownAsync(actor, kitName, time); } - private Task SaveData() + private async Task HasNoCooldownPermission(IPermissionActor actor) { - return m_DataStore.SaveAsync(c_CooldownKey, m_KitsCooldownData); + return await m_PermissionChecker.CheckPermissionAsync(actor, c_NoCooldownPermission) is PermissionGrantResult.Grant; } - public void Dispose() + public ValueTask DisposeAsync() { - m_FileWatcher?.Dispose(); + return m_CooldownProvider == null ? new() : new(m_CooldownProvider.DisposeSyncOrAsync()); } } \ No newline at end of file diff --git a/Kits/Cooldowns/Providers/DataStoreKitCooldownStoreProvider.cs b/Kits/Cooldowns/Providers/DataStoreKitCooldownStoreProvider.cs new file mode 100644 index 0000000..ebc3d94 --- /dev/null +++ b/Kits/Cooldowns/Providers/DataStoreKitCooldownStoreProvider.cs @@ -0,0 +1,90 @@ +using Kits.API.Cooldowns; +using Kits.Cooldowns.Models; +using OpenMod.API.Permissions; +using OpenMod.API.Persistence; +using OpenMod.Core.Helpers; +using System; +using System.Threading.Tasks; + +namespace Kits.Cooldowns.Providers; +public class DataStoreKitCooldownStoreProvider : IKitCooldownStoreProvider, IDisposable +{ + private const string c_CooldownKey = "cooldowns"; + + private readonly KitsPlugin m_Plugin; + + private KitsCooldownData m_KitsCooldownData = null!; + private IDisposable? m_FileWatcher; + + private IDataStore DataStore => m_Plugin.DataStore; + + public DataStoreKitCooldownStoreProvider(KitsPlugin plugin) + { + m_Plugin = plugin; + } + + public Task GetLastCooldownAsync(IPermissionActor actor, string kitName) + { + if (!m_KitsCooldownData.KitsCooldown!.TryGetValue(actor.Id, out var kitCooldowns)) + { + return Task.FromResult(null); + } + + var kitCooldown = kitCooldowns!.Find(x => + x.KitName?.Equals(kitName, StringComparison.CurrentCultureIgnoreCase) == true); + return Task.FromResult(kitCooldown == null ? null : DateTime.Now - kitCooldown.KitCooldown); + } + + public async Task RegisterCooldownAsync(IPermissionActor actor, string kitName, DateTime time) + { + if (m_KitsCooldownData.KitsCooldown!.TryGetValue(actor.Id, out var kitCooldowns)) + { + var kitCooldown = kitCooldowns!.Find(x => x.KitName == kitName); + if (kitCooldown == null) + { + kitCooldown = new() { KitName = kitName }; + kitCooldowns.Add(kitCooldown); + } + + kitCooldown.KitCooldown = time; + } + else + { + m_KitsCooldownData.KitsCooldown.Add(actor.Id, + new() { new() { KitCooldown = time, KitName = kitName } }); + } + await SaveData(); + } + + private async Task LoadFromDisk() + { + if (await DataStore.ExistsAsync(c_CooldownKey)) + { + m_KitsCooldownData = await DataStore.LoadAsync(c_CooldownKey) ?? + new() { KitsCooldown = new() }; + } + else + { + m_KitsCooldownData = new() { KitsCooldown = new() }; + await SaveData(); + } + } + + public async Task InitAsync() + { + await LoadFromDisk(); + m_FileWatcher = DataStore.AddChangeWatcher(c_CooldownKey, m_Plugin, + () => AsyncHelper.RunSync(LoadFromDisk)); + } + + private Task SaveData() + { + return DataStore.SaveAsync(c_CooldownKey, m_KitsCooldownData); + } + + public void Dispose() + { + m_FileWatcher?.Dispose(); + m_FileWatcher = null; + } +} diff --git a/Kits/Databases/DataStoreKitDataStore.cs b/Kits/Databases/DataStoreKitStoreProvider.cs similarity index 84% rename from Kits/Databases/DataStoreKitDataStore.cs rename to Kits/Databases/DataStoreKitStoreProvider.cs index 8d2cdb2..36faef6 100644 --- a/Kits/Databases/DataStoreKitDataStore.cs +++ b/Kits/Databases/DataStoreKitStoreProvider.cs @@ -12,18 +12,18 @@ namespace Kits.Databases; -public class DataStoreKitDataStore : KitDataStoreCore, IKitDatabaseProvider, IDisposable +public class DataStoreKitStoreProvider : KitStoreProviderCore, IKitStoreProvider, IDisposable { private const string c_KitsKey = "kits"; private KitsData m_Data = null!; private IDisposable? m_FileWatcher; - public DataStoreKitDataStore(ILifetimeScope lifetimeScope) : base(lifetimeScope) + public DataStoreKitStoreProvider(ILifetimeScope lifetimeScope) : base(lifetimeScope) { } - public async Task AddKitAsync(Kit kit) + public async Task AddKitAsync(Kit kit) { if (kit is null) { @@ -37,7 +37,6 @@ public async Task AddKitAsync(Kit kit) m_Data.Kits?.Add(kit); await SaveToDisk(); - return true; } public Task FindKitByNameAsync(string name) @@ -50,7 +49,7 @@ public Task> GetKitsAsync() return Task.FromResult((IReadOnlyCollection)(m_Data.Kits ?? new())); } - public async Task LoadDatabaseAsync() + public async Task InitAsync() { await LoadFromDisk(); @@ -74,7 +73,7 @@ private async Task LoadFromDisk() m_Data = new() { Kits = new() }; } - public async Task RemoveKitAsync(string name) + public async Task RemoveKitAsync(string name) { var index = m_Data.Kits?.FindIndex(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); if (index < 0) @@ -84,10 +83,9 @@ public async Task RemoveKitAsync(string name) m_Data.Kits?.RemoveAt(index!.Value); await SaveToDisk(); - return true; } - public async Task UpdateKitAsync(Kit kit) + public async Task UpdateKitAsync(Kit kit) { if (kit is null) { @@ -98,12 +96,11 @@ public async Task UpdateKitAsync(Kit kit) x => x.Name.Equals(kit.Name, StringComparison.OrdinalIgnoreCase)); if (index < 0) { - return false; + return; } m_Data.Kits![index!.Value] = kit; await SaveToDisk(); - return true; } public void Dispose() @@ -115,4 +112,9 @@ private Task SaveToDisk() { return DataStore.SaveAsync(c_KitsKey, m_Data); } + + public Task IsKitExists(string name) + { + throw new NotImplementedException(); + } } \ No newline at end of file diff --git a/Kits/Databases/KitDataStoreCore.cs b/Kits/Databases/KitStoreProviderCore.cs similarity index 80% rename from Kits/Databases/KitDataStoreCore.cs rename to Kits/Databases/KitStoreProviderCore.cs index 1e372c8..ab698df 100644 --- a/Kits/Databases/KitDataStoreCore.cs +++ b/Kits/Databases/KitStoreProviderCore.cs @@ -4,13 +4,13 @@ namespace Kits.Databases; -public abstract class KitDataStoreCore +public abstract class KitStoreProviderCore { protected IDataStore DataStore { get; } protected IStringLocalizer StringLocalizer { get; } protected ILifetimeScope LifetimeScope { get; } - protected KitDataStoreCore(ILifetimeScope lifetimeScope) + protected KitStoreProviderCore(ILifetimeScope lifetimeScope) { StringLocalizer = lifetimeScope.Resolve(); DataStore = lifetimeScope.Resolve(); diff --git a/Kits/Databases/MySqlKitDataStore.cs b/Kits/Databases/MySqlKitStoreProvider.cs similarity index 66% rename from Kits/Databases/MySqlKitDataStore.cs rename to Kits/Databases/MySqlKitStoreProvider.cs index 21a499a..90eecc4 100644 --- a/Kits/Databases/MySqlKitDataStore.cs +++ b/Kits/Databases/MySqlKitStoreProvider.cs @@ -11,25 +11,25 @@ namespace Kits.Databases; -public class MySqlKitDataStore : KitDataStoreCore, IKitDatabaseProvider +public class MySqlKitStoreProvider : KitStoreProviderCore, IKitStoreProvider { - public MySqlKitDataStore(IServiceProvider provider) : this(provider.GetRequiredService().LifetimeScope) + public MySqlKitStoreProvider(IServiceProvider provider) : this(provider.GetRequiredService().LifetimeScope) { } - public MySqlKitDataStore(ILifetimeScope lifetimeScope) : base(lifetimeScope) + public MySqlKitStoreProvider(ILifetimeScope lifetimeScope) : base(lifetimeScope) { } protected virtual KitsDbContext GetDbContext() => LifetimeScope.Resolve(); - public async Task LoadDatabaseAsync() + public async Task InitAsync() { await using var context = GetDbContext(); await context.Database.MigrateAsync(); } - public async Task AddKitAsync(Kit kit) + public async Task AddKitAsync(Kit kit) { await using var context = GetDbContext(); @@ -39,7 +39,7 @@ public async Task AddKitAsync(Kit kit) } context.Kits.Add(kit); - return await context.SaveChangesAsync() > 0; + await context.SaveChangesAsync(); } public async Task FindKitByNameAsync(string name) @@ -57,7 +57,7 @@ public async Task> GetKitsAsync() return await context.Kits.AsNoTracking().ToListAsync(); } - public async Task RemoveKitAsync(string name) + public async Task RemoveKitAsync(string name) { await using var context = GetDbContext(); @@ -65,19 +65,24 @@ public async Task RemoveKitAsync(string name) .FirstOrDefaultAsync(x => x.Name == name); if (kit == null) { - return false; + return; } context.Kits.Remove(kit); - - return await context.SaveChangesAsync() > 0; + await context.SaveChangesAsync(); } - public async Task UpdateKitAsync(Kit kit) + public async Task UpdateKitAsync(Kit kit) { await using var context = GetDbContext(); context.Kits.Update(kit); - return await context.SaveChangesAsync() > 0; + await context.SaveChangesAsync(); + } + + public async Task IsKitExists(string name) + { + await using var context = GetDbContext(); + return await context.Kits.AnyAsync(x => x.Name == name); } } \ No newline at end of file diff --git a/Kits/Extensions/ConvertorExtension.cs b/Kits/Extensions/ConvertorExtension.cs index 7575be9..9b44614 100644 --- a/Kits/Extensions/ConvertorExtension.cs +++ b/Kits/Extensions/ConvertorExtension.cs @@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using OpenMod.Extensions.Games.Abstractions.Items; -using System; using System.Collections.Generic; using System.IO; @@ -20,20 +19,18 @@ public static KitItem ConvertIItemToKitItem(this IItem item) public static byte[] ConvertToByteArray(this List? items) { - if (items == null) - { - return Array.Empty(); - } - using var ms = new MemoryStream(); using var bw = new BinaryWriter(ms); bw.Write(s_SaveVersion); - bw.Write(items.Count); + bw.Write(items?.Count ?? 0); - foreach (var item in items) + if (items != null) { - item.Serialize(bw); + foreach (var item in items) + { + item.Serialize(bw); + } } return ms.ToArray(); @@ -41,7 +38,7 @@ public static byte[] ConvertToByteArray(this List? items) public static List? ConvertToKitItems(this byte[]? block) { - if (block == null) + if (block == null || block.Length == 0) { return null; } diff --git a/Kits/KitsPlugin.cs b/Kits/KitsPlugin.cs index a30ddcf..0b88d96 100644 --- a/Kits/KitsPlugin.cs +++ b/Kits/KitsPlugin.cs @@ -28,8 +28,6 @@ protected override Task OnLoadAsync() m_Logger.LogInformation("https://github.com/evolutionplugins \\ https://github.com/diffoz"); m_Logger.LogInformation("Discord support: https://discord.gg/6KymqGv"); - m_ServiceProvider.GetRequiredService(); /* just load database service */ - - return Task.CompletedTask; + return m_ServiceProvider.GetRequiredService().InitAsync(); } } \ No newline at end of file diff --git a/Kits/ServiceConfigurator.cs b/Kits/ServiceConfigurator.cs index e53e377..1a0dbed 100644 --- a/Kits/ServiceConfigurator.cs +++ b/Kits/ServiceConfigurator.cs @@ -1,5 +1,7 @@ using EvolutionPlugins.Economy.Stub; +using Kits.API.Cooldowns; using Kits.API.Databases; +using Kits.Cooldowns.Providers; using Kits.Databases; using Microsoft.Extensions.DependencyInjection; using OpenMod.API.Ioc; @@ -15,10 +17,15 @@ public void ConfigureServices(IOpenModServiceConfigurationContext openModStartup { serviceCollection.AddEconomyStub(); - serviceCollection.Configure(o => + serviceCollection.Configure(o => { - o.AddProvider("datastore"); - o.AddProvider("mysql"); + o.AddProvider("datastore"); + o.AddProvider("mysql"); + }); + + serviceCollection.Configure(o => + { + o.AddProvider("datastore"); }); } } \ No newline at end of file diff --git a/Kits/Services/KitManager.cs b/Kits/Services/KitManager.cs index 0272e13..46a3f3a 100644 --- a/Kits/Services/KitManager.cs +++ b/Kits/Services/KitManager.cs @@ -36,8 +36,7 @@ public KitManager(IEconomyProvider economyProvider, IKitCooldownStore kitCooldow m_ServiceProvider = serviceProvider; } - public async Task GiveKitAsync(IPlayerUser user, string name, ICommandActor? instigator = null, - bool forceGiveKit = false) + public async Task GiveKitAsync(IPlayerUser user, string name, ICommandActor? instigator = null, bool forceGiveKit = false) { var kit = await m_KitStore.FindKitByNameAsync(name); if (kit == null) @@ -45,13 +44,17 @@ public async Task GiveKitAsync(IPlayerUser user, string name, ICommandActor? ins throw new UserFriendlyException(m_StringLocalizer["commands:kit:notFound", new { Name = name }]); } - if (!forceGiveKit && await m_PermissionChecker.CheckPermissionAsync(user, - $"{KitStore.c_KitsKey}.{kit.Name}") != PermissionGrantResult.Grant) + await GiveKitAsync(user, kit, instigator, forceGiveKit); + } + + public async Task GiveKitAsync(IPlayerUser user, Kit kit, ICommandActor? instigator = null, bool forceGiveKit = false) + { + if (!forceGiveKit && await CheckPermissionKitAsync(user, kit.Name) != PermissionGrantResult.Grant) { throw new UserFriendlyException(m_StringLocalizer["commands:kit:noPermission", new { Kit = kit }]); } - var cooldown = await m_KitCooldownStore.GetLastCooldownAsync(user, name); + var cooldown = await m_KitCooldownStore.GetLastCooldownAsync(user, kit.Name); if (!forceGiveKit && cooldown != null && cooldown.Value.TotalSeconds < kit.Cooldown) { throw new UserFriendlyException(m_StringLocalizer["commands:kit:cooldown", @@ -64,7 +67,7 @@ await m_EconomyProvider.UpdateBalanceAsync(user.Id, user.Type, -kit.Cost, m_StringLocalizer["commands:kit:balanceUpdateReason:buy", new { Kit = kit }]); } - await m_KitCooldownStore.RegisterCooldownAsync(user, name, DateTime.Now); + await m_KitCooldownStore.RegisterCooldownAsync(user, kit.Name, DateTime.Now); await kit.GiveKitToPlayer(user, m_ServiceProvider); @@ -81,8 +84,7 @@ public async Task> GetAvailableKitsForPlayerAsync(IPlay var list = new List(); foreach (var kit in await m_KitStore.GetKitsAsync()) { - if (await m_PermissionChecker.CheckPermissionAsync(player, - $"{KitStore.c_KitsKey}.{kit.Name}") == PermissionGrantResult.Grant) + if (await CheckPermissionKitAsync(player, kit.Name) == PermissionGrantResult.Grant) { list.Add(kit); } @@ -90,4 +92,10 @@ public async Task> GetAvailableKitsForPlayerAsync(IPlay return list; } + + private Task CheckPermissionKitAsync(IPermissionActor actor, string kitName) + { + var permission = "kits." + kitName; + return m_PermissionChecker.CheckPermissionAsync(actor, permission); + } } \ No newline at end of file diff --git a/Kits/Services/KitStore.cs b/Kits/Services/KitStore.cs index 9355349..1b43764 100644 --- a/Kits/Services/KitStore.cs +++ b/Kits/Services/KitStore.cs @@ -1,14 +1,18 @@ -using Cysharp.Text; +using Autofac; +using Cysharp.Text; using Kits.API; using Kits.API.Databases; using Kits.API.Models; using Kits.Databases; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OpenMod.API.Eventing; using OpenMod.API.Ioc; using OpenMod.API.Permissions; +using OpenMod.API.Plugins; using OpenMod.Core.Helpers; using OpenMod.Core.Plugins.Events; using System; @@ -20,32 +24,42 @@ namespace Kits.Services; [PluginServiceImplementation(Lifetime = ServiceLifetime.Singleton)] public class KitStore : IKitStore, IAsyncDisposable { - public const string c_KitsKey = "kits"; - - private readonly KitsPlugin m_Plugin; private readonly IPermissionRegistry m_PermissionRegistry; private readonly ILogger m_Logger; - private readonly IOptions m_Options; + private readonly IEventBus m_EventBus; + private readonly IOptions m_Options; private readonly IServiceProvider m_ServiceProvider; - private readonly IDisposable? m_ConfigurationChangedWatcher; - public IKitDatabaseProvider Database { get; private set; } = null!; + private IOpenModPlugin m_Plugin = null!; + private IDisposable? m_ConfigurationChangedWatcher; + + public IKitStoreProvider DatabaseProvider { get; private set; } = null!; - public KitStore(KitsPlugin plugin, IPermissionRegistry permissionRegistry, ILogger logger, - IEventBus eventBus, IOptions options, IServiceProvider serviceProvider) + public KitStore(IPermissionRegistry permissionRegistry, ILogger logger, + IEventBus eventBus, IOptions options, IServiceProvider serviceProvider) { - m_Plugin = plugin; m_PermissionRegistry = permissionRegistry; m_Logger = logger; + m_EventBus = eventBus; m_Options = options; m_ServiceProvider = serviceProvider; - AsyncHelper.RunSync(ParseLoadDatabase); + } + + public async Task InitAsync() + { + // already initialized + if (m_Plugin != null) + { + return; + } - m_ConfigurationChangedWatcher = - eventBus.Subscribe(plugin, PluginConfigurationChanged); + m_Plugin = m_ServiceProvider.GetRequiredService(); + await ParseLoadDatabase(); + + m_ConfigurationChangedWatcher = m_EventBus.Subscribe(m_Plugin, PluginConfigurationChangedAsync); } - private Task PluginConfigurationChanged(IServiceProvider serviceprovider, object? sender, + private Task PluginConfigurationChangedAsync(IServiceProvider _, object? __, PluginConfigurationChangedEvent @event) { return @event.Plugin != m_Plugin ? Task.CompletedTask : ParseLoadDatabase(); @@ -53,28 +67,36 @@ private Task PluginConfigurationChanged(IServiceProvider serviceprovider, object private async Task ParseLoadDatabase() { - var type = m_Plugin.Configuration["database:connectionType"]; - var databaseType = m_Options.Value.GetPreferredDatabase(type); + var configuration = m_Plugin.LifetimeScope.Resolve(); + var type = configuration["database:connectionType"] ?? string.Empty; + var databaseType = m_Options.Value.FindType(type); - if (databaseType == null) + if (databaseType != null) { - m_Logger.LogWarning("Unable to parse {DatabaseType}. Setting to default: `datastore`", type); - Database = new DataStoreKitDataStore(m_Plugin.LifetimeScope); + m_Logger.LogInformation("Database type set to `{DatabaseType}`", type); + try + { + DatabaseProvider = (ActivatorUtilities.CreateInstance(m_ServiceProvider, databaseType) as IKitStoreProvider)!; + } + catch (Exception ex) + { + m_Logger.LogError(ex, "Failed to initialize {DatabaseName}. Defaulting to datastore", databaseType.Name); + } } else { - m_Logger.LogInformation("Database type set to `{DatabaseType}`", type); - Database = (ActivatorUtilities.CreateInstance(m_ServiceProvider, databaseType) as IKitDatabaseProvider) - ?? throw new Exception($"Failed to create database provider of type {databaseType.Name}"); + m_Logger.LogWarning("Unable to parse {DatabaseType}. Setting to default: `datastore`", type); } - await Database.LoadDatabaseAsync(); + DatabaseProvider ??= new DataStoreKitStoreProvider(m_Plugin.LifetimeScope); + + await DatabaseProvider.InitAsync(); await RegisterPermissionsAsync(); } public Task> GetKitsAsync() { - return Database.GetKitsAsync(); + return DatabaseProvider.GetKitsAsync(); } public async Task AddKitAsync(Kit kit) @@ -84,10 +106,8 @@ public async Task AddKitAsync(Kit kit) throw new ArgumentNullException(nameof(kit)); } - if (await Database.AddKitAsync(kit)) - { - RegisterPermission(kit.Name); - } + await DatabaseProvider.AddKitAsync(kit); + RegisterPermission(kit.Name); } public async Task FindKitByNameAsync(string kitName) @@ -97,9 +117,10 @@ public async Task AddKitAsync(Kit kit) throw new ArgumentException($"'{nameof(kitName)}' cannot be null or empty.", nameof(kitName)); } - var kit = await Database.FindKitByNameAsync(kitName); + var kit = await DatabaseProvider.FindKitByNameAsync(kitName); if (kit?.Name is not null) { + // kit was maybe manually added, registering the permission for safety reason RegisterPermission(kit.Name); } @@ -114,12 +135,22 @@ public Task RemoveKitAsync(string kitName) $"'{nameof(kitName)}' cannot be null or empty.", nameof(kitName))); } - return Database.RemoveKitAsync(kitName); + return DatabaseProvider.RemoveKitAsync(kitName); + } + + public async Task UpdateKitAsync(Kit kit) + { + + } + + public Task IsKitExists(string name) + { + throw new NotImplementedException(); } protected virtual async Task RegisterPermissionsAsync() { - foreach (var kit in await Database.GetKitsAsync()) + foreach (var kit in await DatabaseProvider.GetKitsAsync()) { if (kit.Name != null) { @@ -137,11 +168,11 @@ public ValueTask DisposeAsync() { m_ConfigurationChangedWatcher?.Dispose(); - if (Database == null) + if (DatabaseProvider == null) { return new(); } - return new(Database.DisposeSyncOrAsync()); + return new(DatabaseProvider.DisposeSyncOrAsync()); } } \ No newline at end of file diff --git a/Kits/config.yaml b/Kits/config.yaml index 8f51975..5cba1e4 100644 --- a/Kits/config.yaml +++ b/Kits/config.yaml @@ -7,4 +7,11 @@ # mysql: will create table that saves all kits connectionType: "datastore" +# Wrap lines when pritting list of kits? It was before handled by OpenMod, but now it's per plugin configuration wrapLines: true + +cooldowns: + # Type of store for cooldowns that will be used + # datastore (default): will create 'cooldowns.data.yaml' file that saves all cooldowns + # mysql: will create table that saves all cooldowns (NOT IMPLEMENTED YET) + connectionType: "datastore" \ No newline at end of file From 05a20dff9d64ea4e334780ad9c78b9f5c16eee19 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Wed, 17 May 2023 17:22:07 +0700 Subject: [PATCH 03/25] Make `IKitCooldownStore` as global service --- Kits/Cooldowns/KitCooldownStore.cs | 28 +++++++++++++++++++--------- Kits/KitsPlugin.cs | 4 ++-- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Kits/Cooldowns/KitCooldownStore.cs b/Kits/Cooldowns/KitCooldownStore.cs index f0a989c..a3a5073 100644 --- a/Kits/Cooldowns/KitCooldownStore.cs +++ b/Kits/Cooldowns/KitCooldownStore.cs @@ -7,21 +7,27 @@ using Microsoft.Extensions.Options; using OpenMod.API.Ioc; using OpenMod.API.Permissions; +using OpenMod.API.Plugins; using OpenMod.Core.Helpers; using OpenMod.Core.Permissions; using System; +using System.Reflection; using System.Threading.Tasks; [assembly: RegisterPermission("nocooldown", Description = "Allows use kit without waiting for cooldown")] namespace Kits.Cooldowns; -[PluginServiceImplementation(Lifetime = ServiceLifetime.Singleton)] +[ServiceImplementation(Lifetime = ServiceLifetime.Singleton)] public class KitCooldownStore : IKitCooldownStore, IAsyncDisposable { - private const string c_NoCooldownPermission = "nocooldown"; + private static readonly string s_NoCooldownFullPermission = "nocooldown"; + static KitCooldownStore() + { + s_NoCooldownFullPermission = typeof(KitCooldownStore).Assembly.GetCustomAttribute().Id + ":" + s_NoCooldownFullPermission; + } - private readonly KitsPlugin m_Plugin; + private readonly IPluginAccessor m_Plugin; private readonly IPermissionChecker m_PermissionChecker; private readonly IOptions m_Options; private readonly IServiceProvider m_ServiceProvider; @@ -29,8 +35,8 @@ public class KitCooldownStore : IKitCooldownStore, IAsyncDisposable private IKitCooldownStoreProvider m_CooldownProvider = null!; - public KitCooldownStore(KitsPlugin plugin, IPermissionChecker permissionChecker, IOptions options, IServiceProvider serviceProvider, - ILogger logger) + public KitCooldownStore(IPluginAccessor plugin, IPermissionChecker permissionChecker, IOptions options, + IServiceProvider serviceProvider, ILogger logger) { m_Plugin = plugin; m_PermissionChecker = permissionChecker; @@ -41,9 +47,13 @@ public KitCooldownStore(KitsPlugin plugin, IPermissionChecker permissionChecker, AsyncHelper.RunSync(InitAsync); } - private async Task InitAsync() + internal async Task InitAsync() { - var configuration = m_Plugin.LifetimeScope.Resolve(); + // todo: reinit if plugin configuration was updated + + var plugin = m_Plugin.Instance ?? throw new Exception("Plugin is not loaded"); + var configuration = plugin.LifetimeScope.Resolve(); + var type = configuration["cooldowns:connectionType"] ?? string.Empty; var providerType = m_Options.Value.FindType(type); @@ -64,7 +74,7 @@ private async Task InitAsync() m_Logger.LogWarning("Unable to parse {DatabaseType}. Setting to default: `datastore`", type); } - m_CooldownProvider ??= new DataStoreKitCooldownStoreProvider(m_Plugin); + m_CooldownProvider ??= new DataStoreKitCooldownStoreProvider(plugin); await m_CooldownProvider.InitAsync(); } @@ -86,7 +96,7 @@ public async Task RegisterCooldownAsync(IPermissionActor actor, string kitName, private async Task HasNoCooldownPermission(IPermissionActor actor) { - return await m_PermissionChecker.CheckPermissionAsync(actor, c_NoCooldownPermission) is PermissionGrantResult.Grant; + return await m_PermissionChecker.CheckPermissionAsync(actor, s_NoCooldownFullPermission) is PermissionGrantResult.Grant; } public ValueTask DisposeAsync() diff --git a/Kits/KitsPlugin.cs b/Kits/KitsPlugin.cs index 0b88d96..422ebfa 100644 --- a/Kits/KitsPlugin.cs +++ b/Kits/KitsPlugin.cs @@ -22,12 +22,12 @@ public KitsPlugin(ILogger logger, IServiceProvider serviceProvider) m_ServiceProvider = serviceProvider; } - protected override Task OnLoadAsync() + protected override async Task OnLoadAsync() { m_Logger.LogInformation("Made with <3 by EvolutionPlugins"); m_Logger.LogInformation("https://github.com/evolutionplugins \\ https://github.com/diffoz"); m_Logger.LogInformation("Discord support: https://discord.gg/6KymqGv"); - return m_ServiceProvider.GetRequiredService().InitAsync(); + await m_ServiceProvider.GetRequiredService().InitAsync(); } } \ No newline at end of file From 28fc16fb9208e1aacc09962b68a8d3d6d3e8f574 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Thu, 18 May 2023 04:21:30 +0700 Subject: [PATCH 04/25] Make `IKitStore` as global service --- Kits.API/Databases/IKitStoreProvider.cs | 5 +- Kits.API/IKitStore.cs | 6 --- Kits/Databases/MySqlKitStoreProvider.cs | 6 --- Kits/KitsPlugin.cs | 16 ++----- Kits/Services/KitStore.cs | 64 +++++++++++++------------ 5 files changed, 43 insertions(+), 54 deletions(-) diff --git a/Kits.API/Databases/IKitStoreProvider.cs b/Kits.API/Databases/IKitStoreProvider.cs index b3f14a7..6e71deb 100644 --- a/Kits.API/Databases/IKitStoreProvider.cs +++ b/Kits.API/Databases/IKitStoreProvider.cs @@ -1,5 +1,8 @@ -namespace Kits.API.Databases; +using System.Threading.Tasks; + +namespace Kits.API.Databases; public interface IKitStoreProvider : IKitStore { + Task InitAsync(); } \ No newline at end of file diff --git a/Kits.API/IKitStore.cs b/Kits.API/IKitStore.cs index d671c6a..43a9ce3 100644 --- a/Kits.API/IKitStore.cs +++ b/Kits.API/IKitStore.cs @@ -27,10 +27,4 @@ public interface IKitStore Task RemoveKitAsync(string name); Task UpdateKitAsync(Kit kit); - - /// - /// Initializing the kit store and . - /// Should not be called from plugins - /// - Task InitAsync(); } \ No newline at end of file diff --git a/Kits/Databases/MySqlKitStoreProvider.cs b/Kits/Databases/MySqlKitStoreProvider.cs index 90eecc4..fa03163 100644 --- a/Kits/Databases/MySqlKitStoreProvider.cs +++ b/Kits/Databases/MySqlKitStoreProvider.cs @@ -3,9 +3,7 @@ using Kits.API.Models; using Kits.Databases.MySql; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; using OpenMod.API.Commands; -using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -13,10 +11,6 @@ namespace Kits.Databases; public class MySqlKitStoreProvider : KitStoreProviderCore, IKitStoreProvider { - public MySqlKitStoreProvider(IServiceProvider provider) : this(provider.GetRequiredService().LifetimeScope) - { - } - public MySqlKitStoreProvider(ILifetimeScope lifetimeScope) : base(lifetimeScope) { } diff --git a/Kits/KitsPlugin.cs b/Kits/KitsPlugin.cs index 422ebfa..5229e9d 100644 --- a/Kits/KitsPlugin.cs +++ b/Kits/KitsPlugin.cs @@ -13,21 +13,15 @@ namespace Kits; public class KitsPlugin : OpenModUniversalPlugin { - private readonly ILogger m_Logger; - private readonly IServiceProvider m_ServiceProvider; - - public KitsPlugin(ILogger logger, IServiceProvider serviceProvider) : base(serviceProvider) + public KitsPlugin(IServiceProvider serviceProvider) : base(serviceProvider) { - m_Logger = logger; - m_ServiceProvider = serviceProvider; + _ = serviceProvider.GetRequiredService(); } protected override async Task OnLoadAsync() { - m_Logger.LogInformation("Made with <3 by EvolutionPlugins"); - m_Logger.LogInformation("https://github.com/evolutionplugins \\ https://github.com/diffoz"); - m_Logger.LogInformation("Discord support: https://discord.gg/6KymqGv"); - - await m_ServiceProvider.GetRequiredService().InitAsync(); + Logger.LogInformation("Made with <3 by EvolutionPlugins"); + Logger.LogInformation("https://github.com/evolutionplugins \\ https://github.com/diffoz"); + Logger.LogInformation("Discord support: https://discord.gg/6KymqGv"); } } \ No newline at end of file diff --git a/Kits/Services/KitStore.cs b/Kits/Services/KitStore.cs index 1b43764..0344103 100644 --- a/Kits/Services/KitStore.cs +++ b/Kits/Services/KitStore.cs @@ -6,9 +6,9 @@ using Kits.Databases; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using OpenMod.API; using OpenMod.API.Eventing; using OpenMod.API.Ioc; using OpenMod.API.Permissions; @@ -21,42 +21,53 @@ namespace Kits.Services; -[PluginServiceImplementation(Lifetime = ServiceLifetime.Singleton)] +[ServiceImplementation(Lifetime = ServiceLifetime.Singleton)] public class KitStore : IKitStore, IAsyncDisposable { private readonly IPermissionRegistry m_PermissionRegistry; private readonly ILogger m_Logger; private readonly IEventBus m_EventBus; private readonly IOptions m_Options; - private readonly IServiceProvider m_ServiceProvider; + private readonly IRuntime m_Runtime; - private IOpenModPlugin m_Plugin = null!; + private IOpenModPlugin? m_Plugin; private IDisposable? m_ConfigurationChangedWatcher; public IKitStoreProvider DatabaseProvider { get; private set; } = null!; public KitStore(IPermissionRegistry permissionRegistry, ILogger logger, - IEventBus eventBus, IOptions options, IServiceProvider serviceProvider) + IEventBus eventBus, IOptions options, IRuntime runtime) { m_PermissionRegistry = permissionRegistry; m_Logger = logger; m_EventBus = eventBus; m_Options = options; - m_ServiceProvider = serviceProvider; + m_Runtime = runtime; + ScheduleWaitForPluginLoading(); } - public async Task InitAsync() + private void ScheduleWaitForPluginLoading() { - // already initialized - if (m_Plugin != null) + IDisposable eventHandler = NullDisposable.Instance; + + eventHandler = m_EventBus.Subscribe(m_Runtime, async (_, _, @event) => { - return; - } + if (@event.Plugin is not KitsPlugin) + { + return; + } + + m_Plugin = @event.Plugin; + eventHandler.Dispose(); + await InitAsync(); + }); + } - m_Plugin = m_ServiceProvider.GetRequiredService(); + public async Task InitAsync() + { await ParseLoadDatabase(); - m_ConfigurationChangedWatcher = m_EventBus.Subscribe(m_Plugin, PluginConfigurationChangedAsync); + m_ConfigurationChangedWatcher = m_EventBus.Subscribe(m_Plugin!, PluginConfigurationChangedAsync); } private Task PluginConfigurationChangedAsync(IServiceProvider _, object? __, @@ -67,7 +78,7 @@ private Task PluginConfigurationChangedAsync(IServiceProvider _, object? __, private async Task ParseLoadDatabase() { - var configuration = m_Plugin.LifetimeScope.Resolve(); + var configuration = m_Plugin!.LifetimeScope.Resolve(); var type = configuration["database:connectionType"] ?? string.Empty; var databaseType = m_Options.Value.FindType(type); @@ -76,11 +87,12 @@ private async Task ParseLoadDatabase() m_Logger.LogInformation("Database type set to `{DatabaseType}`", type); try { - DatabaseProvider = (ActivatorUtilities.CreateInstance(m_ServiceProvider, databaseType) as IKitStoreProvider)!; + var serviceProvider = m_Plugin.LifetimeScope.Resolve(); + DatabaseProvider = (ActivatorUtilities.CreateInstance(serviceProvider, databaseType) as IKitStoreProvider)!; } catch (Exception ex) { - m_Logger.LogError(ex, "Failed to initialize {DatabaseName}. Defaulting to datastore", databaseType.Name); + m_Logger.LogError(ex, "Failed to initialize {DatabaseName}. Defaulting to `datastore`", databaseType.Name); } } else @@ -129,13 +141,10 @@ public async Task AddKitAsync(Kit kit) public Task RemoveKitAsync(string kitName) { - if (string.IsNullOrEmpty(kitName)) - { - return Task.FromException(new ArgumentException( - $"'{nameof(kitName)}' cannot be null or empty.", nameof(kitName))); - } - - return DatabaseProvider.RemoveKitAsync(kitName); + return string.IsNullOrEmpty(kitName) + ? Task.FromException(new ArgumentException( + $"'{nameof(kitName)}' cannot be null or empty.", nameof(kitName))) + : DatabaseProvider.RemoveKitAsync(kitName); } public async Task UpdateKitAsync(Kit kit) @@ -161,18 +170,13 @@ protected virtual async Task RegisterPermissionsAsync() protected virtual void RegisterPermission(string kitName) { - m_PermissionRegistry.RegisterPermission(m_Plugin, ZString.Concat("kits.", kitName.ToLower())); + m_PermissionRegistry.RegisterPermission(m_Plugin!, ZString.Concat("kits.", kitName.ToLower())); } public ValueTask DisposeAsync() { m_ConfigurationChangedWatcher?.Dispose(); - if (DatabaseProvider == null) - { - return new(); - } - - return new(DatabaseProvider.DisposeSyncOrAsync()); + return DatabaseProvider == null ? new() : new(DatabaseProvider.DisposeSyncOrAsync()); } } \ No newline at end of file From b893810b5ff1c55ee1c65a0de6bc663bb80c25c1 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Sun, 21 May 2023 15:36:40 +0700 Subject: [PATCH 05/25] Make `IKitManager` as global service --- Kits/Services/KitManager.cs | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/Kits/Services/KitManager.cs b/Kits/Services/KitManager.cs index 46a3f3a..581aebd 100644 --- a/Kits/Services/KitManager.cs +++ b/Kits/Services/KitManager.cs @@ -1,4 +1,6 @@ -using Kits.API; +using Autofac; +using Cysharp.Text; +using Kits.API; using Kits.API.Cooldowns; using Kits.API.Models; using Microsoft.Extensions.DependencyInjection; @@ -6,34 +8,44 @@ using OpenMod.API.Commands; using OpenMod.API.Ioc; using OpenMod.API.Permissions; +using OpenMod.API.Plugins; using OpenMod.API.Prioritization; using OpenMod.Extensions.Economy.Abstractions; using OpenMod.Extensions.Games.Abstractions.Players; using System; using System.Collections.Generic; +using System.Reflection; using System.Threading.Tasks; namespace Kits.Services; -[PluginServiceImplementation(Lifetime = ServiceLifetime.Transient, Priority = Priority.Lowest)] +[ServiceImplementation(Lifetime = ServiceLifetime.Transient, Priority = Priority.Lowest)] public class KitManager : IKitManager { + private static readonly string s_PrefixPermissionKits; + static KitManager() + { + s_PrefixPermissionKits = typeof(KitManager).Assembly.GetCustomAttribute().Id + ":kits."; + } + private readonly IEconomyProvider m_EconomyProvider; private readonly IKitCooldownStore m_KitCooldownStore; private readonly IKitStore m_KitStore; - private readonly IStringLocalizer m_StringLocalizer; private readonly IPermissionChecker m_PermissionChecker; private readonly IServiceProvider m_ServiceProvider; - public KitManager(IEconomyProvider economyProvider, IKitCooldownStore kitCooldownStore, IKitStore kitStore, - IStringLocalizer stringLocalizer, IPermissionChecker permissionChecker, IServiceProvider serviceProvider) + private IStringLocalizer? m_StringLocalizer; + + public KitManager(IEconomyProvider economyProvider, IKitCooldownStore kitCooldownStore, IKitStore kitStore, IPermissionChecker permissionChecker, + IPluginAccessor pluginAccessor, IServiceProvider serviceProvider) { m_EconomyProvider = economyProvider; m_KitCooldownStore = kitCooldownStore; m_KitStore = kitStore; - m_StringLocalizer = stringLocalizer; m_PermissionChecker = permissionChecker; m_ServiceProvider = serviceProvider; + + m_StringLocalizer = pluginAccessor.Instance?.LifetimeScope.Resolve(); } public async Task GiveKitAsync(IPlayerUser user, string name, ICommandActor? instigator = null, bool forceGiveKit = false) @@ -41,7 +53,7 @@ public async Task GiveKitAsync(IPlayerUser user, string name, ICommandActor? ins var kit = await m_KitStore.FindKitByNameAsync(name); if (kit == null) { - throw new UserFriendlyException(m_StringLocalizer["commands:kit:notFound", new { Name = name }]); + throw new UserFriendlyException(m_StringLocalizer!["commands:kit:notFound", new { Name = name }]); } await GiveKitAsync(user, kit, instigator, forceGiveKit); @@ -51,27 +63,27 @@ public async Task GiveKitAsync(IPlayerUser user, Kit kit, ICommandActor? instiga { if (!forceGiveKit && await CheckPermissionKitAsync(user, kit.Name) != PermissionGrantResult.Grant) { - throw new UserFriendlyException(m_StringLocalizer["commands:kit:noPermission", new { Kit = kit }]); + throw new UserFriendlyException(m_StringLocalizer!["commands:kit:noPermission", new { Kit = kit }]); } var cooldown = await m_KitCooldownStore.GetLastCooldownAsync(user, kit.Name); if (!forceGiveKit && cooldown != null && cooldown.Value.TotalSeconds < kit.Cooldown) { - throw new UserFriendlyException(m_StringLocalizer["commands:kit:cooldown", + throw new UserFriendlyException(m_StringLocalizer!["commands:kit:cooldown", new { Kit = kit, Cooldown = kit.Cooldown - cooldown.Value.TotalSeconds }]); } if (!forceGiveKit && kit.Cost != 0) { await m_EconomyProvider.UpdateBalanceAsync(user.Id, user.Type, -kit.Cost, - m_StringLocalizer["commands:kit:balanceUpdateReason:buy", new { Kit = kit }]); + m_StringLocalizer!["commands:kit:balanceUpdateReason:buy", new { Kit = kit }]); } await m_KitCooldownStore.RegisterCooldownAsync(user, kit.Name, DateTime.Now); await kit.GiveKitToPlayer(user, m_ServiceProvider); - await user.PrintMessageAsync(m_StringLocalizer["commands:kit:success", new { Kit = kit }]); + await user.PrintMessageAsync(m_StringLocalizer!["commands:kit:success", new { Kit = kit }]); if (instigator != null) { @@ -95,7 +107,7 @@ public async Task> GetAvailableKitsForPlayerAsync(IPlay private Task CheckPermissionKitAsync(IPermissionActor actor, string kitName) { - var permission = "kits." + kitName; + var permission = ZString.Concat(s_PrefixPermissionKits, kitName); return m_PermissionChecker.CheckPermissionAsync(actor, permission); } } \ No newline at end of file From 5a31bab945c0528120dc862bbdc96717db4631cf Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Sun, 21 May 2023 21:00:21 +0700 Subject: [PATCH 06/25] Add debug log to measure available kits method --- Kits/Services/KitManager.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Kits/Services/KitManager.cs b/Kits/Services/KitManager.cs index 581aebd..882738c 100644 --- a/Kits/Services/KitManager.cs +++ b/Kits/Services/KitManager.cs @@ -5,6 +5,7 @@ using Kits.API.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; using OpenMod.API.Commands; using OpenMod.API.Ioc; using OpenMod.API.Permissions; @@ -14,6 +15,7 @@ using OpenMod.Extensions.Games.Abstractions.Players; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Reflection; using System.Threading.Tasks; @@ -33,17 +35,18 @@ static KitManager() private readonly IKitStore m_KitStore; private readonly IPermissionChecker m_PermissionChecker; private readonly IServiceProvider m_ServiceProvider; - - private IStringLocalizer? m_StringLocalizer; + private readonly ILogger m_Logger; + private readonly IStringLocalizer? m_StringLocalizer; public KitManager(IEconomyProvider economyProvider, IKitCooldownStore kitCooldownStore, IKitStore kitStore, IPermissionChecker permissionChecker, - IPluginAccessor pluginAccessor, IServiceProvider serviceProvider) + IPluginAccessor pluginAccessor, IServiceProvider serviceProvider, ILogger logger) { m_EconomyProvider = economyProvider; m_KitCooldownStore = kitCooldownStore; m_KitStore = kitStore; m_PermissionChecker = permissionChecker; m_ServiceProvider = serviceProvider; + m_Logger = logger; m_StringLocalizer = pluginAccessor.Instance?.LifetimeScope.Resolve(); } @@ -93,6 +96,8 @@ await m_EconomyProvider.UpdateBalanceAsync(user.Id, user.Type, -kit.Cost, public async Task> GetAvailableKitsForPlayerAsync(IPlayerUser player) { + var sw = m_Logger.IsEnabled(LogLevel.Debug) ? Stopwatch.StartNew() : null; + var list = new List(); foreach (var kit in await m_KitStore.GetKitsAsync()) { @@ -102,6 +107,8 @@ public async Task> GetAvailableKitsForPlayerAsync(IPlay } } + m_Logger.LogDebug("Get available kits for user was take: {SpentMs}ms", sw?.ElapsedMilliseconds ?? 0); + return list; } From 14959575a63dc35d8b9099dc5891fe9427a1445a Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Tue, 23 May 2023 21:26:57 +0700 Subject: [PATCH 07/25] Update deploy workflow --- .github/workflows/deployment.yml | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 1057fe4..0af4a55 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -15,28 +15,33 @@ jobs: name: "NuGet Deployment" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - name: Checkout Repository - with: - fetch-depth: 0 + - uses: actions/checkout@v3 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: - dotnet-version: 5.0.x + dotnet-version: 6.0.x - name: Install dependencies run: dotnet restore + + - name: API Update version + run: "sed -i \"s#0.0.0#${{ github.event.inputs.version }}#\" Kits.API/Kits.API.csproj" + - name: API Update package version + run: "sed -i \"s#0.0.0#${{ github.event.inputs.version }}#\" Kits.API/Kits.API.csproj" + - name: API Update informational version + run: "sed -i \"s#0.0.0#${{ github.event.inputs.version }}#\" Kits.API/Kits.API.csproj" + - name: Update version run: "sed -i \"s#0.0.0#${{ github.event.inputs.version }}#\" Kits/Kits.csproj" - name: Update package version run: "sed -i \"s#0.0.0#${{ github.event.inputs.version }}#\" Kits/Kits.csproj" - name: Update informational version - run: "sed -i \"s#0.0.0#${{ github.event.inputs.version }}#\" Kits/Kits.csproj" - - name: Build + run: "sed -i \"s#0.0.0#${{ github.event.inputs.version }}#\" Kits/Kits.csproj" + - name: Build API + run: dotnet build Kits.API/Kits.API.csproj --configuration Release --no-restore + - name: Build Kits run: dotnet build Kits/Kits.csproj --configuration Release --no-restore - name: Deploy to NuGet - run: dotnet nuget push Kits/bin/Release/*.nupkg - --api-key ${{ secrets.NUGET_DEPLOY_KEY }} - --source https://api.nuget.org/v3/index.json + run: dotnet nuget push *.nupkg --api-key ${{ secrets.NUGET_DEPLOY_KEY }} --source https://api.nuget.org/v3/index.json - name: Release to GitHub uses: actions/create-release@master env: @@ -48,4 +53,4 @@ jobs: Changelog: ${{ github.event.inputs.update_notes }} release_name: DiFFoZ.Kits v${{ github.event.inputs.version }} - tag_name: v${{ github.event.inputs.version }} + tag_name: v${{ github.event.inputs.version }} \ No newline at end of file From f3bfc639a3ba34efbae7cbf22ebe0007f536cae2 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Tue, 23 May 2023 21:27:11 +0700 Subject: [PATCH 08/25] Include readme to package --- Kits.API/Kits.API.csproj | 5 +++++ Kits/Kits.csproj | 5 +++++ README.md | 4 ++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Kits.API/Kits.API.csproj b/Kits.API/Kits.API.csproj index 0895ec3..759bd7a 100644 --- a/Kits.API/Kits.API.csproj +++ b/Kits.API/Kits.API.csproj @@ -21,6 +21,7 @@ https://github.com/DiFFoZ/Kits git https://github.com/DiFFoZ/Kits + README.md @@ -29,4 +30,8 @@ + + + + diff --git a/Kits/Kits.csproj b/Kits/Kits.csproj index fe8a44a..90f95a8 100644 --- a/Kits/Kits.csproj +++ b/Kits/Kits.csproj @@ -22,6 +22,7 @@ https://github.com/DiFFoZ/Kits git https://github.com/DiFFoZ/Kits + README.md @@ -44,6 +45,10 @@ + + + + diff --git a/README.md b/README.md index 424a553..c026e10 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Kits -OpenMod universal plugin. Add kits system. +OpenMod universal plugin. Adds the kits system. [![Nuget](https://img.shields.io/nuget/v/DiFFoZ.Kits)](https://www.nuget.org/packages/DiFFoZ.Kits/) [![Nuget](https://img.shields.io/nuget/dt/DiFFoZ.Kits?label=NuGet%20downloads)](https://www.nuget.org/packages/DiFFoZ.Kits/) @@ -11,7 +11,7 @@ Run command `openmod install DiFFoZ.Kits` # Commands _Maybe is outdated so check help.md to get all commands_ - kit <name> - Give a kit to yourself. -- kit create <name> [cooldown] - Create a kit. +- kit create <name> [cooldown] [cost] [money] [vehicleId] - Create a kit. - kit remove <name> - Remove a kit. - kits - Shows a list of available kits for player. From 8da9801a21d767a9b970204e01e47579533ed990 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Tue, 23 May 2023 21:38:49 +0700 Subject: [PATCH 09/25] Implement forgotten methods --- Kits/Databases/DataStoreKitStoreProvider.cs | 10 +++++----- Kits/KitsPlugin.cs | 4 +++- Kits/Services/KitStore.cs | 6 +++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Kits/Databases/DataStoreKitStoreProvider.cs b/Kits/Databases/DataStoreKitStoreProvider.cs index 36faef6..5bbf38f 100644 --- a/Kits/Databases/DataStoreKitStoreProvider.cs +++ b/Kits/Databases/DataStoreKitStoreProvider.cs @@ -103,6 +103,11 @@ public async Task UpdateKitAsync(Kit kit) await SaveToDisk(); } + public Task IsKitExists(string name) + { + return Task.FromResult(m_Data.Kits?.Find(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) != null); + } + public void Dispose() { m_FileWatcher?.Dispose(); @@ -112,9 +117,4 @@ private Task SaveToDisk() { return DataStore.SaveAsync(c_KitsKey, m_Data); } - - public Task IsKitExists(string name) - { - throw new NotImplementedException(); - } } \ No newline at end of file diff --git a/Kits/KitsPlugin.cs b/Kits/KitsPlugin.cs index 5229e9d..1e1ffa6 100644 --- a/Kits/KitsPlugin.cs +++ b/Kits/KitsPlugin.cs @@ -18,10 +18,12 @@ public KitsPlugin(IServiceProvider serviceProvider) : base(serviceProvider) _ = serviceProvider.GetRequiredService(); } - protected override async Task OnLoadAsync() + protected override Task OnLoadAsync() { Logger.LogInformation("Made with <3 by EvolutionPlugins"); Logger.LogInformation("https://github.com/evolutionplugins \\ https://github.com/diffoz"); Logger.LogInformation("Discord support: https://discord.gg/6KymqGv"); + + return Task.CompletedTask; } } \ No newline at end of file diff --git a/Kits/Services/KitStore.cs b/Kits/Services/KitStore.cs index 0344103..22e8d6a 100644 --- a/Kits/Services/KitStore.cs +++ b/Kits/Services/KitStore.cs @@ -147,14 +147,14 @@ public Task RemoveKitAsync(string kitName) : DatabaseProvider.RemoveKitAsync(kitName); } - public async Task UpdateKitAsync(Kit kit) + public Task UpdateKitAsync(Kit kit) { - + return DatabaseProvider.UpdateKitAsync(kit); } public Task IsKitExists(string name) { - throw new NotImplementedException(); + return DatabaseProvider.IsKitExists(name); } protected virtual async Task RegisterPermissionsAsync() From 80255fc1221e0abfacbce6917d52386990c2e6b6 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Wed, 24 May 2023 00:38:41 +0700 Subject: [PATCH 10/25] Make store provider safe to load. Fix plugin configuration event is not working --- Kits/Cooldowns/KitCooldownStore.cs | 4 +- Kits/Databases/DataStoreKitStoreProvider.cs | 55 ++++++++++----------- Kits/Services/KitStore.cs | 51 ++++++++++++++++--- 3 files changed, 72 insertions(+), 38 deletions(-) diff --git a/Kits/Cooldowns/KitCooldownStore.cs b/Kits/Cooldowns/KitCooldownStore.cs index a3a5073..31f43b8 100644 --- a/Kits/Cooldowns/KitCooldownStore.cs +++ b/Kits/Cooldowns/KitCooldownStore.cs @@ -59,10 +59,10 @@ internal async Task InitAsync() if (providerType != null) { - m_Logger.LogInformation("Cooldown store type set to `{DatabaseType}`", type); + m_Logger.LogDebug("Cooldown store type set to `{DatabaseType}`", type); try { - m_CooldownProvider = (ActivatorUtilities.CreateInstance(m_ServiceProvider, providerType) as IKitCooldownStoreProvider)!; + m_CooldownProvider = (IKitCooldownStoreProvider)ActivatorUtilities.CreateInstance(m_ServiceProvider, providerType); } catch (Exception ex) { diff --git a/Kits/Databases/DataStoreKitStoreProvider.cs b/Kits/Databases/DataStoreKitStoreProvider.cs index 5bbf38f..ed6ee73 100644 --- a/Kits/Databases/DataStoreKitStoreProvider.cs +++ b/Kits/Databases/DataStoreKitStoreProvider.cs @@ -23,6 +23,28 @@ public DataStoreKitStoreProvider(ILifetimeScope lifetimeScope) : base(lifetimeSc { } + public async Task InitAsync() + { + await LoadFromDisk(); + + var component = LifetimeScope.Resolve(); + m_FileWatcher = DataStore.AddChangeWatcher(c_KitsKey, component, + () => AsyncHelper.RunSync(LoadFromDisk)); + } + + private async Task LoadFromDisk() + { + if (await DataStore.ExistsAsync(c_KitsKey)) + { + m_Data = await DataStore.LoadAsync(c_KitsKey) ?? new(); + m_Data.Kits ??= new(); + return; + } + + m_Data = new() { Kits = new() }; + await SaveToDisk(); + } + public async Task AddKitAsync(Kit kit) { if (kit is null) @@ -49,30 +71,6 @@ public Task> GetKitsAsync() return Task.FromResult((IReadOnlyCollection)(m_Data.Kits ?? new())); } - public async Task InitAsync() - { - await LoadFromDisk(); - - var component = LifetimeScope.Resolve(); - - m_FileWatcher = DataStore.AddChangeWatcher(c_KitsKey, component, - () => AsyncHelper.RunSync(LoadFromDisk)); - - await SaveToDisk(); - } - - private async Task LoadFromDisk() - { - if (await DataStore.ExistsAsync(c_KitsKey)) - { - m_Data = await DataStore.LoadAsync(c_KitsKey) ?? new(); - m_Data.Kits ??= new(); - return; - } - - m_Data = new() { Kits = new() }; - } - public async Task RemoveKitAsync(string name) { var index = m_Data.Kits?.FindIndex(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); @@ -108,13 +106,14 @@ public Task IsKitExists(string name) return Task.FromResult(m_Data.Kits?.Find(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) != null); } - public void Dispose() + private Task SaveToDisk() { - m_FileWatcher?.Dispose(); + return DataStore.SaveAsync(c_KitsKey, m_Data); } - private Task SaveToDisk() + public void Dispose() { - return DataStore.SaveAsync(c_KitsKey, m_Data); + m_FileWatcher?.Dispose(); + m_FileWatcher = null; } } \ No newline at end of file diff --git a/Kits/Services/KitStore.cs b/Kits/Services/KitStore.cs index 22e8d6a..657c1d2 100644 --- a/Kits/Services/KitStore.cs +++ b/Kits/Services/KitStore.cs @@ -67,13 +67,18 @@ public async Task InitAsync() { await ParseLoadDatabase(); - m_ConfigurationChangedWatcher = m_EventBus.Subscribe(m_Plugin!, PluginConfigurationChangedAsync); + m_ConfigurationChangedWatcher = m_EventBus.Subscribe(m_Runtime, PluginConfigurationChangedAsync); } private Task PluginConfigurationChangedAsync(IServiceProvider _, object? __, PluginConfigurationChangedEvent @event) { - return @event.Plugin != m_Plugin ? Task.CompletedTask : ParseLoadDatabase(); + if (@event.Plugin is not KitsPlugin || m_Plugin == null) + { + return Task.CompletedTask; + } + + return ParseLoadDatabase(); } private async Task ParseLoadDatabase() @@ -84,11 +89,11 @@ private async Task ParseLoadDatabase() if (databaseType != null) { - m_Logger.LogInformation("Database type set to `{DatabaseType}`", type); + m_Logger.LogDebug("Database type set to `{DatabaseType}`", type); try { var serviceProvider = m_Plugin.LifetimeScope.Resolve(); - DatabaseProvider = (ActivatorUtilities.CreateInstance(serviceProvider, databaseType) as IKitStoreProvider)!; + DatabaseProvider = (IKitStoreProvider)ActivatorUtilities.CreateInstance(serviceProvider, databaseType); } catch (Exception ex) { @@ -101,8 +106,31 @@ private async Task ParseLoadDatabase() } DatabaseProvider ??= new DataStoreKitStoreProvider(m_Plugin.LifetimeScope); + try + { + await DatabaseProvider.InitAsync(); + } + catch (Exception ex) + { + m_Logger.LogError(ex, "Failed to initialize {DatabaseProviderName}. Resetting to the default store provider", + DatabaseProvider.GetType().Name); - await DatabaseProvider.InitAsync(); + if (DatabaseProvider is not DataStoreKitStoreProvider) + { + // dispose safely + try + { + await DatabaseProvider.DisposeSyncOrAsync(); + } + catch (Exception ex2) + { + m_Logger.LogError(ex2, "Failed to dispose {DatabaseProviderName}", DatabaseProvider.GetType().Name); + } + + DatabaseProvider = new DataStoreKitStoreProvider(m_Plugin.LifetimeScope); + await DatabaseProvider.InitAsync(); + } + } await RegisterPermissionsAsync(); } @@ -170,13 +198,20 @@ protected virtual async Task RegisterPermissionsAsync() protected virtual void RegisterPermission(string kitName) { - m_PermissionRegistry.RegisterPermission(m_Plugin!, ZString.Concat("kits.", kitName.ToLower())); + m_PermissionRegistry.RegisterPermission(m_Plugin!, ZString.Concat("kits.", kitName.ToLower()), + $"Grants access to the {kitName} kit"); } - public ValueTask DisposeAsync() + public async ValueTask DisposeAsync() { m_ConfigurationChangedWatcher?.Dispose(); + m_ConfigurationChangedWatcher = null; + + if (DatabaseProvider != null) + { + await DatabaseProvider.DisposeSyncOrAsync(); + } - return DatabaseProvider == null ? new() : new(DatabaseProvider.DisposeSyncOrAsync()); + DatabaseProvider = null!; } } \ No newline at end of file From dfc23d04185c71e7d55e1f39da35997be25b2fc1 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Fri, 26 May 2023 14:20:58 +0700 Subject: [PATCH 11/25] Dispose previous store provider when reloading --- Kits/Services/KitStore.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Kits/Services/KitStore.cs b/Kits/Services/KitStore.cs index 657c1d2..6aa971a 100644 --- a/Kits/Services/KitStore.cs +++ b/Kits/Services/KitStore.cs @@ -83,6 +83,9 @@ private Task PluginConfigurationChangedAsync(IServiceProvider _, object? __, private async Task ParseLoadDatabase() { + DatabaseProvider?.DisposeSyncOrAsync(); + DatabaseProvider = null!; + var configuration = m_Plugin!.LifetimeScope.Resolve(); var type = configuration["database:connectionType"] ?? string.Empty; var databaseType = m_Options.Value.FindType(type); From 2ab294cfad71be0eba367e12326f056b55bdab80 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Fri, 26 May 2023 14:49:49 +0700 Subject: [PATCH 12/25] Add value comparer for Items list --- Kits.API/Models/KitItem.cs | 12 ++++++++++++ Kits/Databases/Mysql/KitsDbContext.cs | 28 +++++++++++++++++---------- Kits/Extensions/ConvertorExtension.cs | 18 ----------------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/Kits.API/Models/KitItem.cs b/Kits.API/Models/KitItem.cs index ea8b003..d839b3e 100644 --- a/Kits.API/Models/KitItem.cs +++ b/Kits.API/Models/KitItem.cs @@ -1,5 +1,7 @@ using OpenMod.Extensions.Games.Abstractions.Items; +using System; using System.IO; +using System.Linq; namespace Kits.API.Models; @@ -30,4 +32,14 @@ public void Deserialize(BinaryReader br) ItemAssetId = br.ReadString(); State.Deserialize(br); } + + public override int GetHashCode() + { + var itemStateDataHash = State.StateData.Aggregate(new HashCode(), (hash, i) => + { + hash.Add(i); + return hash; + }).ToHashCode(); + return HashCode.Combine(ItemAssetId, State.ItemAmount, State.ItemQuality, State.ItemDurability, itemStateDataHash); + } } \ No newline at end of file diff --git a/Kits/Databases/Mysql/KitsDbContext.cs b/Kits/Databases/Mysql/KitsDbContext.cs index 94f4712..135dd12 100644 --- a/Kits/Databases/Mysql/KitsDbContext.cs +++ b/Kits/Databases/Mysql/KitsDbContext.cs @@ -1,10 +1,12 @@ using Kits.API.Models; using Kits.Extensions; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.ChangeTracking; using OpenMod.EntityFrameworkCore; using OpenMod.EntityFrameworkCore.Configurator; using System; +using System.Collections.Generic; +using System.Linq; namespace Kits.Databases.MySql; @@ -22,16 +24,22 @@ public KitsDbContext(IDbContextConfigurator configurator, IServiceProvider servi protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.ApplyConfiguration(new KitConfiguration()); base.OnModelCreating(modelBuilder); - } - // https://stackoverflow.com/questions/44829824/how-to-store-json-in-an-entity-field-with-ef-core - public class KitConfiguration : IEntityTypeConfiguration - { - public virtual void Configure(EntityTypeBuilder builder) - { - builder.Property(c => c.Items).HasByteArrayConversion(); - } + // https://stackoverflow.com/questions/44829824/how-to-store-json-in-an-entity-field-with-ef-core + var property = modelBuilder + .Entity() + .Property(x => x.Items); + + property.HasConversion( + v => v.ConvertToByteArray(), + v => v.ConvertToKitItems()); + + var comparer = new ValueComparer?>( + (c1, c2) => c1.SequenceEqual(c2), + c => c.Aggregate(0, (a, i) => HashCode.Combine(a, i.GetHashCode())), + c => c.ToList()); + + property.Metadata.SetValueComparer(comparer); } } diff --git a/Kits/Extensions/ConvertorExtension.cs b/Kits/Extensions/ConvertorExtension.cs index 9b44614..8c4c710 100644 --- a/Kits/Extensions/ConvertorExtension.cs +++ b/Kits/Extensions/ConvertorExtension.cs @@ -1,7 +1,4 @@ using Kits.API.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using OpenMod.Extensions.Games.Abstractions.Items; using System.Collections.Generic; using System.IO; @@ -61,19 +58,4 @@ public static byte[] ConvertToByteArray(this List? items) return output; } - - // https://stackoverflow.com/questions/44829824/how-to-store-json-in-an-entity-field-with-ef-core - public static PropertyBuilder?> HasByteArrayConversion(this PropertyBuilder?> propertyBuilder) - { - var converter = new ValueConverter?, byte[]> - ( - v => v.ConvertToByteArray(), - v => v.ConvertToKitItems() - ); - - propertyBuilder.HasConversion(converter); - propertyBuilder.Metadata.SetValueConverter(converter); - - return propertyBuilder; - } } \ No newline at end of file From b206bc6f45fa43e717c7eca792a51e62a411c24f Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Fri, 26 May 2023 15:02:42 +0700 Subject: [PATCH 13/25] Add message on how to use cooldown parameter --- Kits/Commands/CommandKitCreate.cs | 21 ++++++++++++++++++--- Kits/Extensions/UnturnedExtension.cs | 1 + Kits/KitsPlugin.cs | 1 + 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/Kits/Commands/CommandKitCreate.cs b/Kits/Commands/CommandKitCreate.cs index b406b3c..69b6f13 100644 --- a/Kits/Commands/CommandKitCreate.cs +++ b/Kits/Commands/CommandKitCreate.cs @@ -8,6 +8,7 @@ using OpenMod.Extensions.Games.Abstractions.Players; using OpenMod.Extensions.Games.Abstractions.Vehicles; using System; +using System.Drawing; using System.Linq; using System.Threading.Tasks; @@ -49,10 +50,24 @@ protected override async Task OnExecuteAsync() throw new UserFriendlyException("IPlayer doesn't have compatibility IHasInventory"); } + TimeSpan cooldown; + try + { + // many users reports that cooldown not working (it actually works, but require a specific format) + cooldown = Context.Parameters.Count >= 2 + ? await Context.Parameters.GetAsync(1) + : TimeSpan.Zero; + } + catch (UserFriendlyException ufe) + { + await PrintAsync(ufe.Message, Color.DarkRed); // "Invalid time span format" + await PrintAsync("Valid examples format for cooldown parameter: 5s / 5m / 5h / 5d", Color.DarkRed); + + return; + } + var name = Context.Parameters[0]; - var cooldown = Context.Parameters.Count >= 2 - ? await Context.Parameters.GetAsync(1) - : TimeSpan.Zero; + // cooldown [1] var cost = Context.Parameters.Count >= 3 ? await Context.Parameters.GetAsync(2) : 0; var money = Context.Parameters.Count >= 4 ? await Context.Parameters.GetAsync(3) : 0; var vehicleId = Context.Parameters.Count == 5 ? Context.Parameters[4] : null; diff --git a/Kits/Extensions/UnturnedExtension.cs b/Kits/Extensions/UnturnedExtension.cs index b38986b..d75c1d5 100644 --- a/Kits/Extensions/UnturnedExtension.cs +++ b/Kits/Extensions/UnturnedExtension.cs @@ -8,6 +8,7 @@ namespace Kits.Extensions; +// todo: remove this when https://github.com/openmod/openmod/issues/355 got implemented internal static class UnturnedExtension { private static readonly bool s_IsUnturned = AppDomain.CurrentDomain.GetAssemblies() diff --git a/Kits/KitsPlugin.cs b/Kits/KitsPlugin.cs index 1e1ffa6..703b6c1 100644 --- a/Kits/KitsPlugin.cs +++ b/Kits/KitsPlugin.cs @@ -15,6 +15,7 @@ public class KitsPlugin : OpenModUniversalPlugin { public KitsPlugin(IServiceProvider serviceProvider) : base(serviceProvider) { + // loads the provider to add kits permission to help.md _ = serviceProvider.GetRequiredService(); } From c648150b559e60a37fc623da9ec7302eeb9e656b Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Thu, 1 Jun 2023 20:03:58 +0700 Subject: [PATCH 14/25] Add CooldownSpan parameter for `cooldown` localizable string --- Kits/Services/KitManager.cs | 7 ++++++- Kits/translations.yaml | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Kits/Services/KitManager.cs b/Kits/Services/KitManager.cs index 882738c..0591eda 100644 --- a/Kits/Services/KitManager.cs +++ b/Kits/Services/KitManager.cs @@ -73,7 +73,12 @@ public async Task GiveKitAsync(IPlayerUser user, Kit kit, ICommandActor? instiga if (!forceGiveKit && cooldown != null && cooldown.Value.TotalSeconds < kit.Cooldown) { throw new UserFriendlyException(m_StringLocalizer!["commands:kit:cooldown", - new { Kit = kit, Cooldown = kit.Cooldown - cooldown.Value.TotalSeconds }]); + new + { + Kit = kit, + Cooldown = kit.Cooldown - cooldown.Value.TotalSeconds, + CooldownSpan = TimeSpan.FromSeconds(kit.Cooldown) - cooldown.Value + }]); } if (!forceGiveKit && kit.Cost != 0) diff --git a/Kits/translations.yaml b/Kits/translations.yaml index 4a5ac3e..6eff9f6 100644 --- a/Kits/translations.yaml +++ b/Kits/translations.yaml @@ -1,9 +1,11 @@ +# Learn about of SmartFormat.NET: https://github.com/axuno/SmartFormat/wiki/Syntax%2C-Terminology + commands: kits: "Your available kits: {Kits:cond:{Kits:list:{Name}{Cost:cond:!=0?({}{MoneySymbol})|}|, }|'none'}" kit: success: "Kit {Kit.Name} successfully received" exist: "Kit with name {Name} already exists" - cooldown: "Kit {Kit.Name} have cooldown. Needs wait for {Cooldown:0.##} sec" + cooldown: "Kit {Kit.Name} have cooldown. Needs wait for {CooldownSpan:abbr}" noPermission: "You don't have permission to get a kit {Kit.Name}" notFound: "Kit {Name} not found" noMoney: "You don't have money to get a kit {Kit.Name}. Needs at least {Money}{MoneyName}{MoneySymbol}" From e83a5c80986cbb338bf0029df4922e23e7e93cb2 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Thu, 1 Jun 2023 20:07:58 +0700 Subject: [PATCH 15/25] Copy array of item state when saving the kit to datastore provider --- Kits/Databases/DataStoreKitStoreProvider.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Kits/Databases/DataStoreKitStoreProvider.cs b/Kits/Databases/DataStoreKitStoreProvider.cs index ed6ee73..8b7be9f 100644 --- a/Kits/Databases/DataStoreKitStoreProvider.cs +++ b/Kits/Databases/DataStoreKitStoreProvider.cs @@ -57,6 +57,16 @@ public async Task AddKitAsync(Kit kit) throw new UserFriendlyException(StringLocalizer["commands:kit:exist"]); } + // Tries to fix the issue when sometimes state of a item resetting to zero + // https://github.com/EvolutionPlugins/Kits/issues/7 + if (kit.Items != null) + { + foreach (var item in kit.Items.Where(x => x?.State != null)) + { + item.State.StateData = item.State.StateData?.ToArray() ?? Array.Empty(); + } + } + m_Data.Kits?.Add(kit); await SaveToDisk(); } From c731c8e777726f73d9a529babd8e27177666c3fb Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Thu, 1 Jun 2023 20:32:40 +0700 Subject: [PATCH 16/25] Update packages --- Kits.API/Kits.API.csproj | 6 +++--- Kits/Kits.csproj | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Kits.API/Kits.API.csproj b/Kits.API/Kits.API.csproj index 759bd7a..c11c374 100644 --- a/Kits.API/Kits.API.csproj +++ b/Kits.API/Kits.API.csproj @@ -25,9 +25,9 @@ - - - + + + diff --git a/Kits/Kits.csproj b/Kits/Kits.csproj index 90f95a8..cde719b 100644 --- a/Kits/Kits.csproj +++ b/Kits/Kits.csproj @@ -27,17 +27,17 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + @@ -46,7 +46,7 @@ - + From d59cd9069d21075fb2dc69cc094e19f3af102b2f Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Tue, 6 Jun 2023 18:27:05 +0700 Subject: [PATCH 17/25] Register permission when new kit added to DB --- Kits/Services/KitStore.cs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Kits/Services/KitStore.cs b/Kits/Services/KitStore.cs index 6aa971a..78e6d10 100644 --- a/Kits/Services/KitStore.cs +++ b/Kits/Services/KitStore.cs @@ -32,6 +32,7 @@ public class KitStore : IKitStore, IAsyncDisposable private IOpenModPlugin? m_Plugin; private IDisposable? m_ConfigurationChangedWatcher; + private int m_KitsCount; public IKitStoreProvider DatabaseProvider { get; private set; } = null!; @@ -137,9 +138,18 @@ private async Task ParseLoadDatabase() await RegisterPermissionsAsync(); } - public Task> GetKitsAsync() + public async Task> GetKitsAsync() { - return DatabaseProvider.GetKitsAsync(); + var kits = await DatabaseProvider.GetKitsAsync(); + if (kits.Count > m_KitsCount) + { + // new kits added from other server or manual manipulation in db + // registering new permission to prevent exception + + await RegisterPermissionsAsync(kits); + } + + return kits; } public async Task AddKitAsync(Kit kit) @@ -188,9 +198,12 @@ public Task IsKitExists(string name) return DatabaseProvider.IsKitExists(name); } - protected virtual async Task RegisterPermissionsAsync() + protected virtual async Task RegisterPermissionsAsync(IReadOnlyCollection? kits = null) { - foreach (var kit in await DatabaseProvider.GetKitsAsync()) + kits ??= await DatabaseProvider.GetKitsAsync(); + m_KitsCount = kits.Count; + + foreach (var kit in kits) { if (kit.Name != null) { From 23b8c913bfd5abdbfa83e81a95f33cdc3f26ece5 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Wed, 7 Jun 2023 03:39:23 +0700 Subject: [PATCH 18/25] Add kit update command --- Kits/Commands/CommandKitUpdate.cs | 56 +++++++++++++++++++++ Kits/Databases/DataStoreKitStoreProvider.cs | 10 ++++ Kits/Databases/MySqlKitStoreProvider.cs | 12 ++++- 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 Kits/Commands/CommandKitUpdate.cs diff --git a/Kits/Commands/CommandKitUpdate.cs b/Kits/Commands/CommandKitUpdate.cs new file mode 100644 index 0000000..617a1fc --- /dev/null +++ b/Kits/Commands/CommandKitUpdate.cs @@ -0,0 +1,56 @@ +using Kits.API; +using Kits.Extensions; +using Microsoft.Extensions.Localization; +using OpenMod.API.Commands; +using OpenMod.Core.Commands; +using OpenMod.Extensions.Games.Abstractions.Items; +using OpenMod.Extensions.Games.Abstractions.Players; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Kits.Commands; +[Command("update")] +[CommandParent(typeof(CommandKit))] +[CommandActor(typeof(IPlayerUser))] +[CommandSyntax("")] +public class CommandKitUpdate : Command +{ + private readonly IKitStore m_KitStore; + private readonly IStringLocalizer m_StringLocalizer; + + public CommandKitUpdate(IServiceProvider serviceProvider, IKitStore kitStore, IStringLocalizer stringLocalizer) : base(serviceProvider) + { + m_KitStore = kitStore; + m_StringLocalizer = stringLocalizer; + } + + protected override async Task OnExecuteAsync() + { + if (Context.Parameters.Count != 1) + { + throw new CommandWrongUsageException(Context); + } + + var name = Context.Parameters[0]; + var kit = await m_KitStore.FindKitByNameAsync(name) ?? throw new UserFriendlyException(m_StringLocalizer["commands:kit:notFound", + new + { + Name = name + }]); + + var user = (IPlayerUser)Context.Actor; + if (user.Player is not IHasInventory hasInventory) + { + throw new Exception("IPlayer doesn't have compatibility IHasInventory"); + } + + kit.Items = hasInventory.Inventory! + .SelectMany(x => x.Items + .Select(c => c.Item.ConvertIItemToKitItem())) + .ToList(); + await m_KitStore.UpdateKitAsync(kit); + + await PrintAsync("Updated the kit."); + } +} diff --git a/Kits/Databases/DataStoreKitStoreProvider.cs b/Kits/Databases/DataStoreKitStoreProvider.cs index 8b7be9f..1ef5111 100644 --- a/Kits/Databases/DataStoreKitStoreProvider.cs +++ b/Kits/Databases/DataStoreKitStoreProvider.cs @@ -107,6 +107,16 @@ public async Task UpdateKitAsync(Kit kit) return; } + // Tries to fix the issue when sometimes state of a item resetting to zero + // https://github.com/EvolutionPlugins/Kits/issues/7 + if (kit.Items != null) + { + foreach (var item in kit.Items.Where(x => x?.State != null)) + { + item.State.StateData = item.State.StateData?.ToArray() ?? Array.Empty(); + } + } + m_Data.Kits![index!.Value] = kit; await SaveToDisk(); } diff --git a/Kits/Databases/MySqlKitStoreProvider.cs b/Kits/Databases/MySqlKitStoreProvider.cs index fa03163..97fbd9d 100644 --- a/Kits/Databases/MySqlKitStoreProvider.cs +++ b/Kits/Databases/MySqlKitStoreProvider.cs @@ -70,7 +70,17 @@ public async Task UpdateKitAsync(Kit kit) { await using var context = GetDbContext(); - context.Kits.Update(kit); + // start to track the old kit + var oldKit = await context.Kits.FindAsync(kit.Id); + + // update values + oldKit.Name = kit.Name; + oldKit.Cooldown = kit.Cooldown; + oldKit.Cost = kit.Cost; + oldKit.Money = kit.Money; + oldKit.VehicleId = kit.VehicleId; + oldKit.Items = kit.Items; + await context.SaveChangesAsync(); } From d8c4ce12c64c661a046a4b0267d8cea491b814c8 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Thu, 27 Jul 2023 22:36:38 +0700 Subject: [PATCH 19/25] Start working on EF Core storing cooldowns It's time to make it --- .../{Models => DataStore}/KitCooldownData.cs | 2 +- .../{Models => DataStore}/KitsCooldownData.cs | 2 +- Kits/Cooldowns/MySql/KitCooldown.cs | 20 +++++++++++++ Kits/Cooldowns/MySql/KitCooldownsDbContext.cs | 24 +++++++++++++++ .../MySql/KitCooldownsDbContextFactory.cs | 6 ++++ .../DataStoreKitCooldownStoreProvider.cs | 2 +- .../MySqlKitCooldownStoreProvider.cs | 29 +++++++++++++++++++ Kits/PluginContainerConfigurator.cs | 4 ++- Kits/config.yaml | 1 + 9 files changed, 86 insertions(+), 4 deletions(-) rename Kits/Cooldowns/{Models => DataStore}/KitCooldownData.cs (79%) rename Kits/Cooldowns/{Models => DataStore}/KitsCooldownData.cs (80%) create mode 100644 Kits/Cooldowns/MySql/KitCooldown.cs create mode 100644 Kits/Cooldowns/MySql/KitCooldownsDbContext.cs create mode 100644 Kits/Cooldowns/MySql/KitCooldownsDbContextFactory.cs create mode 100644 Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs diff --git a/Kits/Cooldowns/Models/KitCooldownData.cs b/Kits/Cooldowns/DataStore/KitCooldownData.cs similarity index 79% rename from Kits/Cooldowns/Models/KitCooldownData.cs rename to Kits/Cooldowns/DataStore/KitCooldownData.cs index f54f88a..92a1e2e 100644 --- a/Kits/Cooldowns/Models/KitCooldownData.cs +++ b/Kits/Cooldowns/DataStore/KitCooldownData.cs @@ -1,6 +1,6 @@ using System; -namespace Kits.Cooldowns.Models; +namespace Kits.Cooldowns.DataStore; public class KitCooldownData { diff --git a/Kits/Cooldowns/Models/KitsCooldownData.cs b/Kits/Cooldowns/DataStore/KitsCooldownData.cs similarity index 80% rename from Kits/Cooldowns/Models/KitsCooldownData.cs rename to Kits/Cooldowns/DataStore/KitsCooldownData.cs index 700d66b..34d7ab0 100644 --- a/Kits/Cooldowns/Models/KitsCooldownData.cs +++ b/Kits/Cooldowns/DataStore/KitsCooldownData.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Kits.Cooldowns.Models; +namespace Kits.Cooldowns.DataStore; public class KitsCooldownData { diff --git a/Kits/Cooldowns/MySql/KitCooldown.cs b/Kits/Cooldowns/MySql/KitCooldown.cs new file mode 100644 index 0000000..cdd77f9 --- /dev/null +++ b/Kits/Cooldowns/MySql/KitCooldown.cs @@ -0,0 +1,20 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Kits.Cooldowns.MySql; +public sealed class KitCooldown +{ + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Key] + public int Id { get; set; } + + [Required] + [StringLength(50)] + public string PlayerId { get; set; } = string.Empty; + + [StringLength(25)] + public string Kit { get; set; } = string.Empty; + + public DateTime UsedTime { get; set; } +} diff --git a/Kits/Cooldowns/MySql/KitCooldownsDbContext.cs b/Kits/Cooldowns/MySql/KitCooldownsDbContext.cs new file mode 100644 index 0000000..8704c1c --- /dev/null +++ b/Kits/Cooldowns/MySql/KitCooldownsDbContext.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore; +using OpenMod.EntityFrameworkCore; +using OpenMod.EntityFrameworkCore.Configurator; +using System; + +namespace Kits.Cooldowns.MySql; + +// Due to how EF Core works, we cannot change the table name in runtime +// so if user want to store cooldowns in MySQL, but each server should use the own set of cooldowns then +// the user should create another DBs (let's say Server1, Server2) and set the connection string in configuration. +// Of course if user wants only global cooldowns then no configuration needed. +[ConnectionString("cooldown")] +public class KitCooldownsDbContext : OpenModDbContext +{ + public DbSet KitCooldowns => Set(); + + public KitCooldownsDbContext(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + + public KitCooldownsDbContext(IDbContextConfigurator configurator, IServiceProvider serviceProvider) : base(configurator, serviceProvider) + { + } +} diff --git a/Kits/Cooldowns/MySql/KitCooldownsDbContextFactory.cs b/Kits/Cooldowns/MySql/KitCooldownsDbContextFactory.cs new file mode 100644 index 0000000..01e1115 --- /dev/null +++ b/Kits/Cooldowns/MySql/KitCooldownsDbContextFactory.cs @@ -0,0 +1,6 @@ +using OpenMod.EntityFrameworkCore.MySql; + +namespace Kits.Cooldowns.MySql; +public class KitCooldownsDbContextFactory : OpenModMySqlDbContextFactory +{ +} diff --git a/Kits/Cooldowns/Providers/DataStoreKitCooldownStoreProvider.cs b/Kits/Cooldowns/Providers/DataStoreKitCooldownStoreProvider.cs index ebc3d94..6538cb2 100644 --- a/Kits/Cooldowns/Providers/DataStoreKitCooldownStoreProvider.cs +++ b/Kits/Cooldowns/Providers/DataStoreKitCooldownStoreProvider.cs @@ -1,5 +1,5 @@ using Kits.API.Cooldowns; -using Kits.Cooldowns.Models; +using Kits.Cooldowns.DataStore; using OpenMod.API.Permissions; using OpenMod.API.Persistence; using OpenMod.Core.Helpers; diff --git a/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs b/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs new file mode 100644 index 0000000..4847b8f --- /dev/null +++ b/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs @@ -0,0 +1,29 @@ +using Kits.API.Cooldowns; +using OpenMod.API.Permissions; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Kits.Cooldowns.Providers; +public class MySqlKitCooldownStoreProvider : IKitCooldownStoreProvider +{ + public MySqlKitCooldownStoreProvider() + { + } + + public Task GetLastCooldownAsync(IPermissionActor actor, string kitName) + { + throw new NotImplementedException(); + } + + public Task InitAsync() + { + throw new NotImplementedException(); + } + + public Task RegisterCooldownAsync(IPermissionActor actor, string kitName, DateTime time) + { + throw new NotImplementedException(); + } +} diff --git a/Kits/PluginContainerConfigurator.cs b/Kits/PluginContainerConfigurator.cs index 0c26027..2523def 100644 --- a/Kits/PluginContainerConfigurator.cs +++ b/Kits/PluginContainerConfigurator.cs @@ -1,4 +1,5 @@ -using Kits.Databases.MySql; +using Kits.Cooldowns.MySql; +using Kits.Databases.MySql; using OpenMod.API.Plugins; using OpenMod.EntityFrameworkCore.MySql.Extensions; @@ -9,5 +10,6 @@ public class PluginContainerConfigurator : IPluginContainerConfigurator public void ConfigureContainer(IPluginServiceConfigurationContext context) { context.ContainerBuilder.AddMySqlDbContext(); + context.ContainerBuilder.AddMySqlDbContext(); } } diff --git a/Kits/config.yaml b/Kits/config.yaml index 5cba1e4..130a27a 100644 --- a/Kits/config.yaml +++ b/Kits/config.yaml @@ -2,6 +2,7 @@ ConnectionStrings: # https://www.connectionstrings.com/mysql/ default: "Server=127.0.0.1;Database=unturned;User=root;Password=root;" + cooldown: "Server=127.0.0.1;Database=unturned;User=root;Password=root;" # Type of datastore that will be used # datastore (default): will create 'kits.data.yaml' file that saves all kits # mysql: will create table that saves all kits From 60476d3c0b5554d4ca88e867faffb19d24689bc7 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Fri, 28 Jul 2023 04:21:50 +0700 Subject: [PATCH 20/25] Add implementation of mysql kit cooldowns --- Kits/Cooldowns/KitCooldownStore.cs | 3 +- .../MySqlKitCooldownStoreProvider.cs | 65 ++++++++++++++++--- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/Kits/Cooldowns/KitCooldownStore.cs b/Kits/Cooldowns/KitCooldownStore.cs index 31f43b8..4c53f80 100644 --- a/Kits/Cooldowns/KitCooldownStore.cs +++ b/Kits/Cooldowns/KitCooldownStore.cs @@ -62,7 +62,8 @@ internal async Task InitAsync() m_Logger.LogDebug("Cooldown store type set to `{DatabaseType}`", type); try { - m_CooldownProvider = (IKitCooldownStoreProvider)ActivatorUtilities.CreateInstance(m_ServiceProvider, providerType); + var serviceProvider = plugin.LifetimeScope.Resolve(); + m_CooldownProvider = (IKitCooldownStoreProvider)ActivatorUtilities.CreateInstance(serviceProvider, providerType); } catch (Exception ex) { diff --git a/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs b/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs index 4847b8f..d352894 100644 --- a/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs +++ b/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs @@ -1,29 +1,74 @@ -using Kits.API.Cooldowns; +using Autofac; +using Kits.API.Cooldowns; +using Kits.Cooldowns.MySql; +using Microsoft.EntityFrameworkCore; using OpenMod.API.Permissions; +using OpenMod.Core.Users; using System; -using System.Collections.Generic; -using System.Text; +using System.Linq; using System.Threading.Tasks; namespace Kits.Cooldowns.Providers; public class MySqlKitCooldownStoreProvider : IKitCooldownStoreProvider { - public MySqlKitCooldownStoreProvider() + private readonly ILifetimeScope m_LifetimeScope; + + public MySqlKitCooldownStoreProvider(ILifetimeScope lifetimeScope) { + m_LifetimeScope = lifetimeScope; } - public Task GetLastCooldownAsync(IPermissionActor actor, string kitName) + public async Task InitAsync() { - throw new NotImplementedException(); + await using var context = GetDbContext(); + await context.Database.MigrateAsync(); } - public Task InitAsync() + public async Task GetLastCooldownAsync(IPermissionActor actor, string kitName) { - throw new NotImplementedException(); + EnsureActorIsPlayer(actor); + + await using var context = GetDbContext(); + var usedTime = context.KitCooldowns + .Where(c => c.Kit == kitName && c.PlayerId == actor.Id) + .FirstOrDefault()?.UsedTime; + + return usedTime == null ? null : DateTime.UtcNow - usedTime; } - public Task RegisterCooldownAsync(IPermissionActor actor, string kitName, DateTime time) + public async Task RegisterCooldownAsync(IPermissionActor actor, string kitName, DateTime time) { - throw new NotImplementedException(); + EnsureActorIsPlayer(actor); + + await using var context = GetDbContext(); + + var cooldown = context.KitCooldowns.FirstOrDefault(c => c.Kit == kitName && c.PlayerId == actor.Id); + if (cooldown == null) + { + cooldown = new() + { + Kit = kitName, + PlayerId = actor.Id, + UsedTime = time + }; + + context.KitCooldowns.Add(cooldown); + } + else + { + cooldown.UsedTime = time; + } + + await context.SaveChangesAsync(); } + + private void EnsureActorIsPlayer(IPermissionActor actor) + { + if (!actor.Type.Equals(KnownActorTypes.Player, StringComparison.OrdinalIgnoreCase)) + { + throw new Exception("Cooldowns are only handled for Player actor type"); + } + } + + protected virtual KitCooldownsDbContext GetDbContext() => m_LifetimeScope.Resolve(); } From b86421438077070136f4e20a707436b84b587adb Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Fri, 28 Jul 2023 20:54:50 +0700 Subject: [PATCH 21/25] Add memory caching for mysql kit store provider --- Kits/Cooldowns/KitCooldownStore.cs | 5 +- Kits/Databases/MySqlKitStoreProvider.cs | 156 ++++++++++++++++++++++-- Kits/Kits.csproj | 1 + Kits/ServiceConfigurator.cs | 2 + 4 files changed, 150 insertions(+), 14 deletions(-) diff --git a/Kits/Cooldowns/KitCooldownStore.cs b/Kits/Cooldowns/KitCooldownStore.cs index 4c53f80..5b229a8 100644 --- a/Kits/Cooldowns/KitCooldownStore.cs +++ b/Kits/Cooldowns/KitCooldownStore.cs @@ -30,18 +30,15 @@ static KitCooldownStore() private readonly IPluginAccessor m_Plugin; private readonly IPermissionChecker m_PermissionChecker; private readonly IOptions m_Options; - private readonly IServiceProvider m_ServiceProvider; private readonly ILogger m_Logger; private IKitCooldownStoreProvider m_CooldownProvider = null!; - public KitCooldownStore(IPluginAccessor plugin, IPermissionChecker permissionChecker, IOptions options, - IServiceProvider serviceProvider, ILogger logger) + public KitCooldownStore(IPluginAccessor plugin, IPermissionChecker permissionChecker, IOptions options, ILogger logger) { m_Plugin = plugin; m_PermissionChecker = permissionChecker; m_Options = options; - m_ServiceProvider = serviceProvider; m_Logger = logger; AsyncHelper.RunSync(InitAsync); diff --git a/Kits/Databases/MySqlKitStoreProvider.cs b/Kits/Databases/MySqlKitStoreProvider.cs index 97fbd9d..fdab3b3 100644 --- a/Kits/Databases/MySqlKitStoreProvider.cs +++ b/Kits/Databases/MySqlKitStoreProvider.cs @@ -3,16 +3,24 @@ using Kits.API.Models; using Kits.Databases.MySql; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; using OpenMod.API.Commands; +using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Kits.Databases; -public class MySqlKitStoreProvider : KitStoreProviderCore, IKitStoreProvider +public class MySqlKitStoreProvider : KitStoreProviderCore, IKitStoreProvider, IDisposable { - public MySqlKitStoreProvider(ILifetimeScope lifetimeScope) : base(lifetimeScope) + private const string c_CacheKey = "evolutionplugins-kits-kits"; + private readonly IMemoryCache m_MemoryCache; + private readonly SemaphoreSlim m_Lock = new(1); + + public MySqlKitStoreProvider(ILifetimeScope lifetimeScope, IMemoryCache memoryCache) : base(lifetimeScope) { + m_MemoryCache = memoryCache; } protected virtual KitsDbContext GetDbContext() => LifetimeScope.Resolve(); @@ -34,10 +42,38 @@ public async Task AddKitAsync(Kit kit) context.Kits.Add(kit); await context.SaveChangesAsync(); + + if (m_MemoryCache.TryGetValue>(c_CacheKey, out var kits)) + { + try + { + await m_Lock.WaitAsync(); + + kits.Add(kit); + } + finally + { + m_Lock.Release(); + } + } } public async Task FindKitByNameAsync(string name) { + if (TryGetCachedKits(out var kits)) + { + try + { + await m_Lock.WaitAsync(); + + return kits.Find(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); + } + finally + { + m_Lock.Release(); + } + } + await using var context = GetDbContext(); return await context.Kits @@ -47,8 +83,7 @@ public async Task AddKitAsync(Kit kit) public async Task> GetKitsAsync() { - await using var context = GetDbContext(); - return await context.Kits.AsNoTracking().ToListAsync(); + return await GetOrCreatedCachedListOfKitsAsync(); } public async Task RemoveKitAsync(string name) @@ -64,6 +99,28 @@ public async Task RemoveKitAsync(string name) context.Kits.Remove(kit); await context.SaveChangesAsync(); + + if (!TryGetCachedKits(out var kits)) + { + return; + } + + try + { + await m_Lock.WaitAsync(); + + var kitIndex = kits.FindIndex(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); + if (kitIndex == -1) + { + return; + } + + kits.RemoveAt(kitIndex); + } + finally + { + m_Lock.Release(); + } } public async Task UpdateKitAsync(Kit kit) @@ -74,19 +131,98 @@ public async Task UpdateKitAsync(Kit kit) var oldKit = await context.Kits.FindAsync(kit.Id); // update values - oldKit.Name = kit.Name; - oldKit.Cooldown = kit.Cooldown; - oldKit.Cost = kit.Cost; - oldKit.Money = kit.Money; - oldKit.VehicleId = kit.VehicleId; - oldKit.Items = kit.Items; + UpdateValues(oldKit, kit); await context.SaveChangesAsync(); + + if (!TryGetCachedKits(out var kits)) + { + return; + } + + try + { + await m_Lock.WaitAsync(); + + var newOldKit = kits.Find(x => x.Name.Equals(kit.Name, StringComparison.InvariantCultureIgnoreCase)); + if (newOldKit == null) + { + return; + } + + UpdateValues(newOldKit, kit); + } + finally + { + m_Lock.Release(); + } + + static void UpdateValues(Kit oldKit, Kit newKit) + { + oldKit.Name = newKit.Name; + oldKit.Cooldown = newKit.Cooldown; + oldKit.Cost = newKit.Cost; + oldKit.Money = newKit.Money; + oldKit.VehicleId = newKit.VehicleId; + oldKit.Items = newKit.Items; + } } public async Task IsKitExists(string name) { + if (TryGetCachedKits(out var kits)) + { + try + { + await m_Lock.WaitAsync(); + + return kits.Exists(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); + } + finally + { + m_Lock.Release(); + } + } + await using var context = GetDbContext(); return await context.Kits.AnyAsync(x => x.Name == name); } + + private bool TryGetCachedKits(out List kits) + { + return m_MemoryCache.TryGetValue(c_CacheKey, out kits); + } + + private async Task> GetOrCreatedCachedListOfKitsAsync() + { + if (!TryGetCachedKits(out var kits)) + { + try + { + await m_Lock.WaitAsync(); + + if (TryGetCachedKits(out kits)) + { + return kits; + } + + await using var context = GetDbContext(); + kits = await context.Kits.ToListAsync(); + + // todo: maybe configure the cache time + m_MemoryCache.Set(c_CacheKey, kits, TimeSpan.FromHours(1)); + } + finally + { + m_Lock.Release(); + } + } + + return kits; + } + + public void Dispose() + { + m_Lock.Dispose(); + } } \ No newline at end of file diff --git a/Kits/Kits.csproj b/Kits/Kits.csproj index cde719b..72df0ae 100644 --- a/Kits/Kits.csproj +++ b/Kits/Kits.csproj @@ -35,6 +35,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Kits/ServiceConfigurator.cs b/Kits/ServiceConfigurator.cs index 1a0dbed..ab8bdb7 100644 --- a/Kits/ServiceConfigurator.cs +++ b/Kits/ServiceConfigurator.cs @@ -27,5 +27,7 @@ public void ConfigureServices(IOpenModServiceConfigurationContext openModStartup { o.AddProvider("datastore"); }); + + serviceCollection.AddMemoryCache(); } } \ No newline at end of file From 2b9d62c9db833f36926a84a9492eaf72bbc7b165 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Sun, 30 Jul 2023 17:43:28 +0700 Subject: [PATCH 22/25] Add helper AsyncLock --- Kits/Databases/MySqlKitStoreProvider.cs | 104 +++++++----------------- Kits/Helpers/AsyncLock.cs | 53 ++++++++++++ 2 files changed, 82 insertions(+), 75 deletions(-) create mode 100644 Kits/Helpers/AsyncLock.cs diff --git a/Kits/Databases/MySqlKitStoreProvider.cs b/Kits/Databases/MySqlKitStoreProvider.cs index fdab3b3..c6ef95f 100644 --- a/Kits/Databases/MySqlKitStoreProvider.cs +++ b/Kits/Databases/MySqlKitStoreProvider.cs @@ -2,6 +2,7 @@ using Kits.API.Databases; using Kits.API.Models; using Kits.Databases.MySql; +using Kits.Helpers; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using OpenMod.API.Commands; @@ -16,7 +17,7 @@ public class MySqlKitStoreProvider : KitStoreProviderCore, IKitStoreProvider, ID { private const string c_CacheKey = "evolutionplugins-kits-kits"; private readonly IMemoryCache m_MemoryCache; - private readonly SemaphoreSlim m_Lock = new(1); + private readonly AsyncLock m_AsyncLock = new(); public MySqlKitStoreProvider(ILifetimeScope lifetimeScope, IMemoryCache memoryCache) : base(lifetimeScope) { @@ -45,16 +46,8 @@ public async Task AddKitAsync(Kit kit) if (m_MemoryCache.TryGetValue>(c_CacheKey, out var kits)) { - try - { - await m_Lock.WaitAsync(); - - kits.Add(kit); - } - finally - { - m_Lock.Release(); - } + using var _ = await m_AsyncLock.GetLockAsync(); + kits.Add(kit); } } @@ -62,16 +55,8 @@ public async Task AddKitAsync(Kit kit) { if (TryGetCachedKits(out var kits)) { - try - { - await m_Lock.WaitAsync(); - - return kits.Find(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); - } - finally - { - m_Lock.Release(); - } + using var _ = await m_AsyncLock.GetLockAsync(); + return kits.Find(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); } await using var context = GetDbContext(); @@ -105,22 +90,15 @@ public async Task RemoveKitAsync(string name) return; } - try - { - await m_Lock.WaitAsync(); - - var kitIndex = kits.FindIndex(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); - if (kitIndex == -1) - { - return; - } + using var _ = await m_AsyncLock.GetLockAsync(); - kits.RemoveAt(kitIndex); - } - finally + var kitIndex = kits.FindIndex(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); + if (kitIndex == -1) { - m_Lock.Release(); + return; } + + kits.RemoveAt(kitIndex); } public async Task UpdateKitAsync(Kit kit) @@ -140,23 +118,15 @@ public async Task UpdateKitAsync(Kit kit) return; } - try + using var _ = await m_AsyncLock.GetLockAsync(); + var newOldKit = kits.Find(x => x.Name.Equals(kit.Name, StringComparison.InvariantCultureIgnoreCase)); + if (newOldKit == null) { - await m_Lock.WaitAsync(); - - var newOldKit = kits.Find(x => x.Name.Equals(kit.Name, StringComparison.InvariantCultureIgnoreCase)); - if (newOldKit == null) - { - return; - } - - UpdateValues(newOldKit, kit); - } - finally - { - m_Lock.Release(); + return; } + UpdateValues(newOldKit, kit); + static void UpdateValues(Kit oldKit, Kit newKit) { oldKit.Name = newKit.Name; @@ -172,16 +142,8 @@ public async Task IsKitExists(string name) { if (TryGetCachedKits(out var kits)) { - try - { - await m_Lock.WaitAsync(); - - return kits.Exists(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); - } - finally - { - m_Lock.Release(); - } + using var _ = await m_AsyncLock.GetLockAsync(); + return kits.Exists(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); } await using var context = GetDbContext(); @@ -197,25 +159,17 @@ private async Task> GetOrCreatedCachedListOfKitsAsync() { if (!TryGetCachedKits(out var kits)) { - try + using var _ = await m_AsyncLock.GetLockAsync(); + if (TryGetCachedKits(out kits)) { - await m_Lock.WaitAsync(); - - if (TryGetCachedKits(out kits)) - { - return kits; - } + return kits; + } - await using var context = GetDbContext(); - kits = await context.Kits.ToListAsync(); + await using var context = GetDbContext(); + kits = await context.Kits.ToListAsync(); - // todo: maybe configure the cache time - m_MemoryCache.Set(c_CacheKey, kits, TimeSpan.FromHours(1)); - } - finally - { - m_Lock.Release(); - } + // todo: maybe configure the cache time + m_MemoryCache.Set(c_CacheKey, kits, TimeSpan.FromHours(1)); } return kits; @@ -223,6 +177,6 @@ private async Task> GetOrCreatedCachedListOfKitsAsync() public void Dispose() { - m_Lock.Dispose(); + m_AsyncLock.Dispose(); } } \ No newline at end of file diff --git a/Kits/Helpers/AsyncLock.cs b/Kits/Helpers/AsyncLock.cs new file mode 100644 index 0000000..60aee10 --- /dev/null +++ b/Kits/Helpers/AsyncLock.cs @@ -0,0 +1,53 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Kits.Helpers; +internal class AsyncLock : IDisposable +{ + private readonly SemaphoreSlim m_SemaphoreSlim; + private readonly ReleaserLock m_ReleaserLock; + + public AsyncLock() + { + m_SemaphoreSlim = new SemaphoreSlim(1); + m_ReleaserLock = new ReleaserLock(this); + } + + public async ValueTask GetLockAsync() + { + await m_SemaphoreSlim.WaitAsync(); + return m_ReleaserLock; + } + + public ReleaserLock GetLock() + { + m_SemaphoreSlim.Wait(); + return m_ReleaserLock; + } + + internal void Release() + { + m_SemaphoreSlim.Release(); + } + + public void Dispose() + { + m_SemaphoreSlim.Dispose(); + } + + public readonly struct ReleaserLock : IDisposable + { + private readonly AsyncLock m_Lock; + + internal ReleaserLock(AsyncLock @lock) + { + m_Lock = @lock; + } + + public readonly void Dispose() + { + m_Lock.Release(); + } + } +} From 57b43b74e71aafe14073230015d0b37dab0c123a Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Wed, 2 Aug 2023 18:22:51 +0700 Subject: [PATCH 23/25] Fixed missing async calls to EF database --- Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs b/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs index d352894..8fe0d25 100644 --- a/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs +++ b/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs @@ -29,9 +29,9 @@ public async Task InitAsync() EnsureActorIsPlayer(actor); await using var context = GetDbContext(); - var usedTime = context.KitCooldowns + var usedTime = (await context.KitCooldowns .Where(c => c.Kit == kitName && c.PlayerId == actor.Id) - .FirstOrDefault()?.UsedTime; + .FirstOrDefaultAsync())?.UsedTime; return usedTime == null ? null : DateTime.UtcNow - usedTime; } @@ -42,7 +42,7 @@ public async Task RegisterCooldownAsync(IPermissionActor actor, string kitName, await using var context = GetDbContext(); - var cooldown = context.KitCooldowns.FirstOrDefault(c => c.Kit == kitName && c.PlayerId == actor.Id); + var cooldown = await context.KitCooldowns.FirstOrDefaultAsync(c => c.Kit == kitName && c.PlayerId == actor.Id); if (cooldown == null) { cooldown = new() From 8d177d8d3e4f4a3f7c588960f431f97c1aca6c01 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Wed, 2 Aug 2023 18:38:19 +0700 Subject: [PATCH 24/25] Add conversion of DateTime to save it as UTC and get back as UTC --- Kits/Cooldowns/MySql/KitCooldownsDbContext.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Kits/Cooldowns/MySql/KitCooldownsDbContext.cs b/Kits/Cooldowns/MySql/KitCooldownsDbContext.cs index 8704c1c..6d80938 100644 --- a/Kits/Cooldowns/MySql/KitCooldownsDbContext.cs +++ b/Kits/Cooldowns/MySql/KitCooldownsDbContext.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using OpenMod.EntityFrameworkCore; using OpenMod.EntityFrameworkCore.Configurator; using System; @@ -21,4 +22,20 @@ public KitCooldownsDbContext(IServiceProvider serviceProvider) : base(servicePro public KitCooldownsDbContext(IDbContextConfigurator configurator, IServiceProvider serviceProvider) : base(configurator, serviceProvider) { } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + var dateTimeConverter = new ValueConverter( + v => v.ToUniversalTime(), + v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); + + var property = modelBuilder + .Entity() + .Property(x => x.UsedTime); + + // adding conversion to save DateTime as UTC and get back as UTC + property.HasConversion(dateTimeConverter); + } } From 263c2fc6cfe1c3614f8516b9340b657322982249 Mon Sep 17 00:00:00 2001 From: DiFFoZ <48765566+DiFFoZ@users.noreply.github.com> Date: Wed, 2 Aug 2023 18:38:35 +0700 Subject: [PATCH 25/25] Add MySQL cooldowns provider to list --- .../Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs | 6 ++---- Kits/ServiceConfigurator.cs | 1 + Kits/config.yaml | 9 +++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs b/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs index 8fe0d25..4b6542f 100644 --- a/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs +++ b/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs @@ -5,7 +5,6 @@ using OpenMod.API.Permissions; using OpenMod.Core.Users; using System; -using System.Linq; using System.Threading.Tasks; namespace Kits.Cooldowns.Providers; @@ -29,9 +28,8 @@ public async Task InitAsync() EnsureActorIsPlayer(actor); await using var context = GetDbContext(); - var usedTime = (await context.KitCooldowns - .Where(c => c.Kit == kitName && c.PlayerId == actor.Id) - .FirstOrDefaultAsync())?.UsedTime; + DateTime? usedTime = (await context.KitCooldowns.FirstOrDefaultAsync(c => c.Kit == kitName && c.PlayerId == actor.Id)) + ?.UsedTime; return usedTime == null ? null : DateTime.UtcNow - usedTime; } diff --git a/Kits/ServiceConfigurator.cs b/Kits/ServiceConfigurator.cs index ab8bdb7..48e1c0b 100644 --- a/Kits/ServiceConfigurator.cs +++ b/Kits/ServiceConfigurator.cs @@ -26,6 +26,7 @@ public void ConfigureServices(IOpenModServiceConfigurationContext openModStartup serviceCollection.Configure(o => { o.AddProvider("datastore"); + o.AddProvider("mysql"); }); serviceCollection.AddMemoryCache(); diff --git a/Kits/config.yaml b/Kits/config.yaml index 130a27a..d38737d 100644 --- a/Kits/config.yaml +++ b/Kits/config.yaml @@ -2,9 +2,10 @@ ConnectionStrings: # https://www.connectionstrings.com/mysql/ default: "Server=127.0.0.1;Database=unturned;User=root;Password=root;" + # Not needed if you're using datastore cooldowns provider cooldown: "Server=127.0.0.1;Database=unturned;User=root;Password=root;" # Type of datastore that will be used - # datastore (default): will create 'kits.data.yaml' file that saves all kits + # datastore: will create 'kits.data.yaml' file that saves all kits (default) # mysql: will create table that saves all kits connectionType: "datastore" @@ -12,7 +13,7 @@ wrapLines: true cooldowns: - # Type of store for cooldowns that will be used - # datastore (default): will create 'cooldowns.data.yaml' file that saves all cooldowns - # mysql: will create table that saves all cooldowns (NOT IMPLEMENTED YET) + # Type of store for cooldowns that will be used: + # datastore: will create 'cooldowns.data.yaml' file that saves all cooldowns (default) + # mysql: will create table that saves all cooldowns connectionType: "datastore" \ No newline at end of file