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
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
new file mode 100644
index 0000000..b3ca96e
--- /dev/null
+++ b/Kits.API/Cooldowns/IKitCooldownStore.cs
@@ -0,0 +1,14 @@
+using OpenMod.API.Ioc;
+using OpenMod.API.Permissions;
+using System;
+using System.Threading.Tasks;
+
+namespace Kits.API.Cooldowns;
+
+[Service]
+public interface IKitCooldownStore
+{
+ Task GetLastCooldownAsync(IPermissionActor actor, string kitName);
+
+ 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/IKitStoreProvider.cs b/Kits.API/Databases/IKitStoreProvider.cs
new file mode 100644
index 0000000..6e71deb
--- /dev/null
+++ b/Kits.API/Databases/IKitStoreProvider.cs
@@ -0,0 +1,8 @@
+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/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
new file mode 100644
index 0000000..deac115
--- /dev/null
+++ b/Kits.API/IKitManager.cs
@@ -0,0 +1,18 @@
+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 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
new file mode 100644
index 0000000..43a9ce3
--- /dev/null
+++ b/Kits.API/IKitStore.cs
@@ -0,0 +1,30 @@
+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 name);
+
+ Task IsKitExists(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/Kits.API.csproj b/Kits.API/Kits.API.csproj
new file mode 100644
index 0000000..c11c374
--- /dev/null
+++ b/Kits.API/Kits.API.csproj
@@ -0,0 +1,37 @@
+
+
+
+ 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
+ README.md
+
+
+
+
+
+
+
+
+
+
+
+
+
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..d839b3e
--- /dev/null
+++ b/Kits.API/Models/KitItem.cs
@@ -0,0 +1,45 @@
+using OpenMod.Extensions.Games.Abstractions.Items;
+using System;
+using System.IO;
+using System.Linq;
+
+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);
+ }
+
+ 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.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..69b6f13 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;
@@ -8,111 +8,123 @@
using OpenMod.Extensions.Games.Abstractions.Players;
using OpenMod.Extensions.Games.Abstractions.Vehicles;
using System;
+using System.Drawing;
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)
{
- private static readonly bool s_IsUnturned = AppDomain.CurrentDomain.GetAssemblies()
- .Any(x => x.GetName().Name.Equals("OpenMod.Unturned.Module.Shared"));
+ m_KitStore = kitStore;
+ m_StringLocalizer = stringLocalizer;
+ m_VehicleDirectory = vehicleDirectory;
+ }
- private readonly IKitStore m_KitStore;
- private readonly IStringLocalizer m_StringLocalizer;
- private readonly IVehicleDirectory m_VehicleDirectory;
+ protected override async Task OnExecuteAsync()
+ {
+ if (Context.Parameters.Count is < 1 or > 5)
+ {
+ throw new CommandWrongUsageException(Context);
+ }
- public CommandKitCreate(IServiceProvider serviceProvider, IKitStore kitStore,
- IStringLocalizer stringLocalizer, IVehicleDirectory vehicleDirectory) : base(serviceProvider)
+ var playerUser = (IPlayerUser)Context.Actor;
+ if (playerUser.Player is not IHasInventory hasInventory)
{
- m_KitStore = kitStore;
- m_StringLocalizer = stringLocalizer;
- m_VehicleDirectory = vehicleDirectory;
+ throw new UserFriendlyException("IPlayer doesn't have compatibility IHasInventory");
}
- protected override async Task OnExecuteAsync()
+ TimeSpan cooldown;
+ try
{
- 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
+ // 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;
- 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 }]);
}
+ 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];
+ // 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;
+
+ 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");
+ }
+
+ if ((await m_KitStore.FindKitByNameAsync(name)) != null)
+ {
+ 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/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/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/DataStore/KitCooldownData.cs b/Kits/Cooldowns/DataStore/KitCooldownData.cs
new file mode 100644
index 0000000..92a1e2e
--- /dev/null
+++ b/Kits/Cooldowns/DataStore/KitCooldownData.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Kits.Cooldowns.DataStore;
+
+public class KitCooldownData
+{
+ public string? KitName { get; set; }
+ public DateTime KitCooldown { get; set; }
+}
\ No newline at end of file
diff --git a/Kits/Cooldowns/DataStore/KitsCooldownData.cs b/Kits/Cooldowns/DataStore/KitsCooldownData.cs
new file mode 100644
index 0000000..34d7ab0
--- /dev/null
+++ b/Kits/Cooldowns/DataStore/KitsCooldownData.cs
@@ -0,0 +1,8 @@
+using System.Collections.Generic;
+
+namespace Kits.Cooldowns.DataStore;
+
+public class KitsCooldownData
+{
+ public Dictionary>? KitsCooldown { get; set; }
+}
\ No newline at end of file
diff --git a/Kits/Cooldowns/KitCooldownStore.cs b/Kits/Cooldowns/KitCooldownStore.cs
new file mode 100644
index 0000000..5b229a8
--- /dev/null
+++ b/Kits/Cooldowns/KitCooldownStore.cs
@@ -0,0 +1,104 @@
+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.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;
+
+[ServiceImplementation(Lifetime = ServiceLifetime.Singleton)]
+public class KitCooldownStore : IKitCooldownStore, IAsyncDisposable
+{
+ private static readonly string s_NoCooldownFullPermission = "nocooldown";
+ static KitCooldownStore()
+ {
+ s_NoCooldownFullPermission = typeof(KitCooldownStore).Assembly.GetCustomAttribute().Id + ":" + s_NoCooldownFullPermission;
+ }
+
+ private readonly IPluginAccessor m_Plugin;
+ private readonly IPermissionChecker m_PermissionChecker;
+ private readonly IOptions m_Options;
+ private readonly ILogger m_Logger;
+
+ private IKitCooldownStoreProvider m_CooldownProvider = null!;
+
+ public KitCooldownStore(IPluginAccessor plugin, IPermissionChecker permissionChecker, IOptions options, ILogger logger)
+ {
+ m_Plugin = plugin;
+ m_PermissionChecker = permissionChecker;
+ m_Options = options;
+ m_Logger = logger;
+
+ AsyncHelper.RunSync(InitAsync);
+ }
+
+ internal async Task InitAsync()
+ {
+ // 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);
+
+ if (providerType != null)
+ {
+ m_Logger.LogDebug("Cooldown store type set to `{DatabaseType}`", type);
+ try
+ {
+ var serviceProvider = plugin.LifetimeScope.Resolve();
+ m_CooldownProvider = (IKitCooldownStoreProvider)ActivatorUtilities.CreateInstance(serviceProvider, providerType);
+ }
+ catch (Exception ex)
+ {
+ m_Logger.LogError(ex, "Failed to initialize {CooldownStoreName}. Defaulting to `datastore`", providerType.Name);
+ }
+ }
+ else
+ {
+ m_Logger.LogWarning("Unable to parse {DatabaseType}. Setting to default: `datastore`", type);
+ }
+
+ m_CooldownProvider ??= new DataStoreKitCooldownStoreProvider(plugin);
+
+ await m_CooldownProvider.InitAsync();
+ }
+
+ public async Task GetLastCooldownAsync(IPermissionActor actor, string kitName)
+ {
+ return await HasNoCooldownPermission(actor) ? null : await m_CooldownProvider.GetLastCooldownAsync(actor, kitName);
+ }
+
+ public async Task RegisterCooldownAsync(IPermissionActor actor, string kitName, DateTime time)
+ {
+ if (await HasNoCooldownPermission(actor))
+ {
+ return;
+ }
+
+ await m_CooldownProvider.RegisterCooldownAsync(actor, kitName, time);
+ }
+
+ private async Task HasNoCooldownPermission(IPermissionActor actor)
+ {
+ return await m_PermissionChecker.CheckPermissionAsync(actor, s_NoCooldownFullPermission) is PermissionGrantResult.Grant;
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ return m_CooldownProvider == null ? new() : new(m_CooldownProvider.DisposeSyncOrAsync());
+ }
+}
\ No newline at end of file
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..6d80938
--- /dev/null
+++ b/Kits/Cooldowns/MySql/KitCooldownsDbContext.cs
@@ -0,0 +1,41 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+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)
+ {
+ }
+
+ 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);
+ }
+}
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
new file mode 100644
index 0000000..6538cb2
--- /dev/null
+++ b/Kits/Cooldowns/Providers/DataStoreKitCooldownStoreProvider.cs
@@ -0,0 +1,90 @@
+using Kits.API.Cooldowns;
+using Kits.Cooldowns.DataStore;
+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/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs b/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs
new file mode 100644
index 0000000..4b6542f
--- /dev/null
+++ b/Kits/Cooldowns/Providers/MySqlKitCooldownStoreProvider.cs
@@ -0,0 +1,72 @@
+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.Threading.Tasks;
+
+namespace Kits.Cooldowns.Providers;
+public class MySqlKitCooldownStoreProvider : IKitCooldownStoreProvider
+{
+ private readonly ILifetimeScope m_LifetimeScope;
+
+ public MySqlKitCooldownStoreProvider(ILifetimeScope lifetimeScope)
+ {
+ m_LifetimeScope = lifetimeScope;
+ }
+
+ public async Task InitAsync()
+ {
+ await using var context = GetDbContext();
+ await context.Database.MigrateAsync();
+ }
+
+ public async Task GetLastCooldownAsync(IPermissionActor actor, string kitName)
+ {
+ EnsureActorIsPlayer(actor);
+
+ await using var context = GetDbContext();
+ DateTime? usedTime = (await context.KitCooldowns.FirstOrDefaultAsync(c => c.Kit == kitName && c.PlayerId == actor.Id))
+ ?.UsedTime;
+
+ return usedTime == null ? null : DateTime.UtcNow - usedTime;
+ }
+
+ public async Task RegisterCooldownAsync(IPermissionActor actor, string kitName, DateTime time)
+ {
+ EnsureActorIsPlayer(actor);
+
+ await using var context = GetDbContext();
+
+ var cooldown = await context.KitCooldowns.FirstOrDefaultAsync(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();
+}
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/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/DataStoreKitStoreProvider.cs b/Kits/Databases/DataStoreKitStoreProvider.cs
new file mode 100644
index 0000000..1ef5111
--- /dev/null
+++ b/Kits/Databases/DataStoreKitStoreProvider.cs
@@ -0,0 +1,139 @@
+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 DataStoreKitStoreProvider : KitStoreProviderCore, IKitStoreProvider, IDisposable
+{
+ private const string c_KitsKey = "kits";
+
+ private KitsData m_Data = null!;
+ private IDisposable? m_FileWatcher;
+
+ public DataStoreKitStoreProvider(ILifetimeScope lifetimeScope) : base(lifetimeScope)
+ {
+ }
+
+ 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)
+ {
+ 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"]);
+ }
+
+ // 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();
+ }
+
+ 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 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();
+ }
+
+ 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;
+ }
+
+ // 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();
+ }
+
+ public Task IsKitExists(string name)
+ {
+ return Task.FromResult(m_Data.Kits?.Find(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) != null);
+ }
+
+ private Task SaveToDisk()
+ {
+ return DataStore.SaveAsync(c_KitsKey, m_Data);
+ }
+
+ public void Dispose()
+ {
+ m_FileWatcher?.Dispose();
+ m_FileWatcher = null;
+ }
+}
\ 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/KitStoreProviderCore.cs b/Kits/Databases/KitStoreProviderCore.cs
new file mode 100644
index 0000000..ab698df
--- /dev/null
+++ b/Kits/Databases/KitStoreProviderCore.cs
@@ -0,0 +1,19 @@
+using Autofac;
+using Microsoft.Extensions.Localization;
+using OpenMod.API.Persistence;
+
+namespace Kits.Databases;
+
+public abstract class KitStoreProviderCore
+{
+ protected IDataStore DataStore { get; }
+ protected IStringLocalizer StringLocalizer { get; }
+ protected ILifetimeScope LifetimeScope { get; }
+
+ protected KitStoreProviderCore(ILifetimeScope lifetimeScope)
+ {
+ StringLocalizer = lifetimeScope.Resolve();
+ DataStore = lifetimeScope.Resolve();
+ LifetimeScope = lifetimeScope;
+ }
+}
\ 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/MySqlKitStoreProvider.cs b/Kits/Databases/MySqlKitStoreProvider.cs
new file mode 100644
index 0000000..c6ef95f
--- /dev/null
+++ b/Kits/Databases/MySqlKitStoreProvider.cs
@@ -0,0 +1,182 @@
+using Autofac;
+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;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Kits.Databases;
+
+public class MySqlKitStoreProvider : KitStoreProviderCore, IKitStoreProvider, IDisposable
+{
+ private const string c_CacheKey = "evolutionplugins-kits-kits";
+ private readonly IMemoryCache m_MemoryCache;
+ private readonly AsyncLock m_AsyncLock = new();
+
+ public MySqlKitStoreProvider(ILifetimeScope lifetimeScope, IMemoryCache memoryCache) : base(lifetimeScope)
+ {
+ m_MemoryCache = memoryCache;
+ }
+
+ protected virtual KitsDbContext GetDbContext() => LifetimeScope.Resolve();
+
+ public async Task InitAsync()
+ {
+ 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);
+ await context.SaveChangesAsync();
+
+ if (m_MemoryCache.TryGetValue>(c_CacheKey, out var kits))
+ {
+ using var _ = await m_AsyncLock.GetLockAsync();
+ kits.Add(kit);
+ }
+ }
+
+ public async Task FindKitByNameAsync(string name)
+ {
+ if (TryGetCachedKits(out var kits))
+ {
+ using var _ = await m_AsyncLock.GetLockAsync();
+ return kits.Find(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
+ }
+
+ await using var context = GetDbContext();
+
+ return await context.Kits
+ .AsNoTracking()
+ .FirstOrDefaultAsync(x => x.Name == name);
+ }
+
+ public async Task> GetKitsAsync()
+ {
+ return await GetOrCreatedCachedListOfKitsAsync();
+ }
+
+ 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;
+ }
+
+ context.Kits.Remove(kit);
+ await context.SaveChangesAsync();
+
+ if (!TryGetCachedKits(out var kits))
+ {
+ return;
+ }
+
+ using var _ = await m_AsyncLock.GetLockAsync();
+
+ var kitIndex = kits.FindIndex(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
+ if (kitIndex == -1)
+ {
+ return;
+ }
+
+ kits.RemoveAt(kitIndex);
+ }
+
+ public async Task UpdateKitAsync(Kit kit)
+ {
+ await using var context = GetDbContext();
+
+ // start to track the old kit
+ var oldKit = await context.Kits.FindAsync(kit.Id);
+
+ // update values
+ UpdateValues(oldKit, kit);
+
+ await context.SaveChangesAsync();
+
+ if (!TryGetCachedKits(out var kits))
+ {
+ return;
+ }
+
+ using var _ = await m_AsyncLock.GetLockAsync();
+ var newOldKit = kits.Find(x => x.Name.Equals(kit.Name, StringComparison.InvariantCultureIgnoreCase));
+ if (newOldKit == null)
+ {
+ return;
+ }
+
+ UpdateValues(newOldKit, kit);
+
+ 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))
+ {
+ using var _ = await m_AsyncLock.GetLockAsync();
+ return kits.Exists(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
+ }
+
+ 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))
+ {
+ using var _ = await m_AsyncLock.GetLockAsync();
+ 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));
+ }
+
+ return kits;
+ }
+
+ public void Dispose()
+ {
+ m_AsyncLock.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/Kits/Databases/Mysql/KitsDbContext.cs b/Kits/Databases/Mysql/KitsDbContext.cs
index 8eac7a0..135dd12 100644
--- a/Kits/Databases/Mysql/KitsDbContext.cs
+++ b/Kits/Databases/Mysql/KitsDbContext.cs
@@ -1,44 +1,45 @@
-using Kits.API;
+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
+namespace Kits.Databases.MySql;
+
+public class KitsDbContext : OpenModDbContext
{
- public class KitsDbContext : OpenModDbContext
+ public DbSet Kits => Set();
+
+ public KitsDbContext(IServiceProvider serviceProvider) : base(serviceProvider)
+ {
+ }
+
+ public KitsDbContext(IDbContextConfigurator configurator, IServiceProvider serviceProvider) : base(configurator, serviceProvider)
{
- public DbSet Kits
- {
- get
- {
- return Set();
- }
- }
-
- public KitsDbContext(IServiceProvider serviceProvider) : base(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)
+ {
+ 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();
- }
- }
+ 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/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..8c4c710 100644
--- a/Kits/Extensions/ConvertorExtension.cs
+++ b/Kits/Extensions/ConvertorExtension.cs
@@ -1,92 +1,61 @@
-using Kits.API;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Metadata.Builders;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Kits.API.Models;
using OpenMod.Extensions.Games.Abstractions.Items;
-using System;
using System.Collections.Generic;
using System.IO;
-namespace Kits.Extensions
-{
- public static class ConvertorExtension
- {
- public static readonly byte s_SaveVersion = 1;
+namespace Kits.Extensions;
- public static KitItem ConvertIItemToKitItem(this IItem item)
- {
- return new(item.Asset.ItemAssetId, item.State);
- }
+internal static class ConvertorExtension
+{
+ public static readonly byte s_SaveVersion = 1;
- public static byte[] ConvertToByteArray(this List? items)
- {
- if (items == null)
- {
- return Array.Empty();
- }
+ public static KitItem ConvertIItemToKitItem(this IItem item)
+ {
+ return new(item.Asset.ItemAssetId, item.State);
+ }
- using var ms = new MemoryStream();
- using var bw = new BinaryWriter(ms);
+ public static byte[] ConvertToByteArray(this List? items)
+ {
+ 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 ?? 0);
+ if (items != null)
+ {
foreach (var item in items)
{
item.Serialize(bw);
}
-
- return ms.ToArray();
}
- public static List ConvertToKitItems(this byte[]? block)
- {
- var output = new List();
-
- if (block == null)
- {
- return output;
- }
-
- using var ms = new MemoryStream(block, false);
- using var br = new BinaryReader(ms);
+ return ms.ToArray();
+ }
- br.ReadByte(); // save version, for now ignored
+ public static List? ConvertToKitItems(this byte[]? block)
+ {
+ if (block == null || block.Length == 0)
+ {
+ return null;
+ }
- var count = br.ReadInt32();
- for (var i = 0; i < count; i++)
- {
- var kitItem = new KitItem();
- kitItem.Deserialize(br);
+ var output = new List();
- output.Add(kitItem);
- }
+ using var ms = new MemoryStream(block, false);
+ using var br = new BinaryReader(ms);
- return output;
- }
+ br.ReadByte(); // save version, for now ignored
- // https://stackoverflow.com/questions/44829824/how-to-store-json-in-an-entity-field-with-ef-core
- public static PropertyBuilder?> HasByteArrayConversion(this PropertyBuilder?> propertyBuilder)
+ var count = br.ReadInt32();
+ for (var i = 0; i < count; i++)
{
- var converter = new ValueConverter?, byte[]>
- (
- v => v.ConvertToByteArray(),
- v => v.ConvertToKitItems()
- );
+ var kitItem = new KitItem();
+ kitItem.Deserialize(br);
- /* 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;
+ output.Add(kitItem);
}
+
+ return output;
}
}
\ No newline at end of file
diff --git a/Kits/Extensions/UnturnedExtension.cs b/Kits/Extensions/UnturnedExtension.cs
index 214c945..d75c1d5 100644
--- a/Kits/Extensions/UnturnedExtension.cs
+++ b/Kits/Extensions/UnturnedExtension.cs
@@ -1,128 +1,128 @@
-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;
+
+// todo: remove this when https://github.com/openmod/openmod/issues/355 got implemented
+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/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();
+ }
+ }
+}
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..72df0ae 100644
--- a/Kits/Kits.csproj
+++ b/Kits/Kits.csproj
@@ -9,7 +9,7 @@
true
true
Kits
- latest
+ 10
enable
nullable
0.0.0
@@ -22,26 +22,23 @@
https://github.com/DiFFoZ/Kits
git
https://github.com/DiFFoZ/Kits
- true
- true
+ README.md
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
+
+
+
+
@@ -49,16 +46,12 @@
+
+
+
+
-
+
-
-
-
- JetBrainsAnnotations
-
-
-
-
\ No newline at end of file
diff --git a/Kits/KitsPlugin.cs b/Kits/KitsPlugin.cs
new file mode 100644
index 0000000..703b6c1
--- /dev/null
+++ b/Kits/KitsPlugin.cs
@@ -0,0 +1,30 @@
+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
+{
+ public KitsPlugin(IServiceProvider serviceProvider) : base(serviceProvider)
+ {
+ // loads the provider to add kits permission to help.md
+ _ = serviceProvider.GetRequiredService();
+ }
+
+ 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/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..2523def 100644
--- a/Kits/PluginContainerConfigurator.cs
+++ b/Kits/PluginContainerConfigurator.cs
@@ -1,14 +1,15 @@
-using Kits.Databases.Mysql;
+using Kits.Cooldowns.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();
+ context.ContainerBuilder.AddMySqlDbContext();
}
}
diff --git a/Kits/ServiceConfigurator.cs b/Kits/ServiceConfigurator.cs
index 71f1c53..48e1c0b 100644
--- a/Kits/ServiceConfigurator.cs
+++ b/Kits/ServiceConfigurator.cs
@@ -1,20 +1,34 @@
-extern alias JetBrainsAnnotations;
-using EvolutionPlugins.Economy.Stub;
-using JetBrainsAnnotations::JetBrains.Annotations;
+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;
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");
+ });
+
+ serviceCollection.Configure(o =>
+ {
+ o.AddProvider("datastore");
+ o.AddProvider("mysql");
+ });
+
+ serviceCollection.AddMemoryCache();
}
}
\ 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..0591eda 100644
--- a/Kits/Services/KitManager.cs
+++ b/Kits/Services/KitManager.cs
@@ -1,97 +1,125 @@
-using JetBrains.Annotations;
+using Autofac;
+using Cysharp.Text;
using Kits.API;
+using Kits.API.Cooldowns;
+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;
+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.Diagnostics;
+using System.Reflection;
using System.Threading.Tasks;
-namespace Kits.Providers
+namespace Kits.Services;
+
+[ServiceImplementation(Lifetime = ServiceLifetime.Transient, Priority = Priority.Lowest)]
+public class KitManager : IKitManager
{
- [PluginServiceImplementation(Lifetime = ServiceLifetime.Transient, Priority = Priority.Lowest)]
- [UsedImplicitly]
- 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 IPermissionChecker m_PermissionChecker;
+ private readonly IServiceProvider m_ServiceProvider;
+ private readonly ILogger m_Logger;
+ private readonly IStringLocalizer? m_StringLocalizer;
+
+ public KitManager(IEconomyProvider economyProvider, IKitCooldownStore kitCooldownStore, IKitStore kitStore, IPermissionChecker permissionChecker,
+ IPluginAccessor pluginAccessor, IServiceProvider serviceProvider, ILogger logger)
{
- 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)
+ m_EconomyProvider = economyProvider;
+ m_KitCooldownStore = kitCooldownStore;
+ m_KitStore = kitStore;
+ m_PermissionChecker = permissionChecker;
+ m_ServiceProvider = serviceProvider;
+ m_Logger = logger;
+
+ m_StringLocalizer = pluginAccessor.Instance?.LifetimeScope.Resolve();
+ }
+
+ 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)
- {
- var kit = await m_KitStore.FindKitByNameAsync(name);
- if (kit == null)
- {
- throw new UserFriendlyException(m_StringLocalizer["commands:kit:notFound", new { Name = name }]);
- }
+ await GiveKitAsync(user, kit, instigator, forceGiveKit);
+ }
- 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 }]);
- }
+ 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);
- if (!forceGiveKit && cooldown != null)
- {
- if (cooldown.Value.TotalSeconds < kit.Cooldown)
+ 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",
+ new
{
- throw new UserFriendlyException(m_StringLocalizer["commands:kit:cooldown",
- new { Kit = kit, Cooldown = kit.Cooldown - cooldown.Value.TotalSeconds }]);
- }
- }
+ Kit = kit,
+ Cooldown = kit.Cooldown - cooldown.Value.TotalSeconds,
+ CooldownSpan = TimeSpan.FromSeconds(kit.Cooldown) - cooldown.Value
+ }]);
+ }
- 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, kit.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 sw = m_Logger.IsEnabled(LogLevel.Debug) ? Stopwatch.StartNew() : null;
+
+ 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 CheckPermissionKitAsync(player, 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;
}
+
+ m_Logger.LogDebug("Get available kits for user was take: {SpentMs}ms", sw?.ElapsedMilliseconds ?? 0);
+
+ return list;
+ }
+
+ private Task CheckPermissionKitAsync(IPermissionActor actor, string kitName)
+ {
+ var permission = ZString.Concat(s_PrefixPermissionKits, 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 0e17351..78e6d10 100644
--- a/Kits/Services/KitStore.cs
+++ b/Kits/Services/KitStore.cs
@@ -1,144 +1,233 @@
-using JetBrains.Annotations;
+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.Logging;
+using Microsoft.Extensions.Options;
+using OpenMod.API;
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;
using System.Collections.Generic;
using System.Threading.Tasks;
-namespace Kits.Providers
+namespace Kits.Services;
+
+[ServiceImplementation(Lifetime = ServiceLifetime.Singleton)]
+public class KitStore : IKitStore, IAsyncDisposable
{
- [PluginServiceImplementation(Lifetime = ServiceLifetime.Singleton)]
- [UsedImplicitly]
- public class KitStore : IKitStore, IAsyncDisposable
- {
- public const string c_KitsKey = "kits";
+ private readonly IPermissionRegistry m_PermissionRegistry;
+ private readonly ILogger m_Logger;
+ private readonly IEventBus m_EventBus;
+ private readonly IOptions m_Options;
+ private readonly IRuntime m_Runtime;
+
+ private IOpenModPlugin? m_Plugin;
+ private IDisposable? m_ConfigurationChangedWatcher;
+ private int m_KitsCount;
- 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;
+ public IKitStoreProvider DatabaseProvider { get; private set; } = null!;
- private IKitDatabase m_Database = null!;
+ public KitStore(IPermissionRegistry permissionRegistry, ILogger logger,
+ IEventBus eventBus, IOptions options, IRuntime runtime)
+ {
+ m_PermissionRegistry = permissionRegistry;
+ m_Logger = logger;
+ m_EventBus = eventBus;
+ m_Options = options;
+ m_Runtime = runtime;
+ ScheduleWaitForPluginLoading();
+ }
- public IKitDatabase Database => m_Database;
+ private void ScheduleWaitForPluginLoading()
+ {
+ IDisposable eventHandler = NullDisposable.Instance;
- public KitStore(Kits plugin, IPermissionRegistry permissionRegistry, ILogger logger,
- IEventBus eventBus, IServiceProvider provider)
+ eventHandler = m_EventBus.Subscribe(m_Runtime, async (_, _, @event) =>
{
- m_Plugin = plugin;
- m_PermissionRegistry = permissionRegistry;
- m_Logger = logger;
- m_Provider = provider;
- AsyncHelper.RunSync(ParseLoadDatabase);
-
- m_ConfigurationChangedWatcher =
- eventBus.Subscribe(plugin, PluginConfigurationChanged);
- }
+ if (@event.Plugin is not KitsPlugin)
+ {
+ return;
+ }
+
+ m_Plugin = @event.Plugin;
+ eventHandler.Dispose();
+ await InitAsync();
+ });
+ }
+
+ public async Task InitAsync()
+ {
+ await ParseLoadDatabase();
+
+ m_ConfigurationChangedWatcher = m_EventBus.Subscribe(m_Runtime, PluginConfigurationChangedAsync);
+ }
- private Task PluginConfigurationChanged(IServiceProvider serviceprovider, object? sender,
- PluginConfigurationChangedEvent @event)
+ private Task PluginConfigurationChangedAsync(IServiceProvider _, object? __,
+ PluginConfigurationChangedEvent @event)
+ {
+ if (@event.Plugin is not KitsPlugin || m_Plugin == null)
{
- return @event.Plugin != m_Plugin ? Task.CompletedTask : ParseLoadDatabase();
+ return Task.CompletedTask;
}
- 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!
- };
+ return ParseLoadDatabase();
+ }
- // ReSharper disable once ConditionIsAlwaysTrueOrFalse
- if (m_Database == null)
+ 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);
+
+ if (databaseType != null)
+ {
+ m_Logger.LogDebug("Database type set to `{DatabaseType}`", type);
+ try
{
- m_Database = new DataStoreKitDatabase(m_Plugin);
- m_Logger.LogWarning("Unable to parse {DatabaseType}. Setting to default: `datastore`", type);
+ var serviceProvider = m_Plugin.LifetimeScope.Resolve();
+ DatabaseProvider = (IKitStoreProvider)ActivatorUtilities.CreateInstance(serviceProvider, databaseType);
}
- else
+ catch (Exception ex)
{
- m_Logger.LogInformation("Datastore type set to `{DatabaseType}`", type);
+ m_Logger.LogError(ex, "Failed to initialize {DatabaseName}. Defaulting to `datastore`", databaseType.Name);
}
-
- await m_Database.LoadDatabaseAsync();
- await RegisterPermissionsAsync();
}
-
- public Task> GetKitsAsync()
+ else
{
- return m_Database.GetKitsAsync();
+ m_Logger.LogWarning("Unable to parse {DatabaseType}. Setting to default: `datastore`", type);
}
- public async Task AddKitAsync(Kit kit)
+ DatabaseProvider ??= new DataStoreKitStoreProvider(m_Plugin.LifetimeScope);
+ try
{
- if (kit?.Name == null || kit?.Items == null)
- {
- throw new ArgumentNullException(nameof(kit));
- }
+ await DatabaseProvider.InitAsync();
+ }
+ catch (Exception ex)
+ {
+ m_Logger.LogError(ex, "Failed to initialize {DatabaseProviderName}. Resetting to the default store provider",
+ DatabaseProvider.GetType().Name);
- if (await m_Database.AddKitAsync(kit))
+ if (DatabaseProvider is not DataStoreKitStoreProvider)
{
- RegisterPermission(kit.Name);
+ // 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();
+ }
- public async Task FindKitByNameAsync(string kitName)
+ public async Task> GetKitsAsync()
+ {
+ var kits = await DatabaseProvider.GetKitsAsync();
+ if (kits.Count > m_KitsCount)
{
- if (string.IsNullOrEmpty(kitName))
- {
- throw new ArgumentException($"'{nameof(kitName)}' cannot be null or empty.", nameof(kitName));
- }
+ // new kits added from other server or manual manipulation in db
+ // registering new permission to prevent exception
- var kit = await m_Database.FindKitByNameAsync(kitName);
- if (kit?.Name is not null)
- {
- RegisterPermission(kit.Name);
- }
+ await RegisterPermissionsAsync(kits);
+ }
+
+ return kits;
+ }
- return kit;
+ public async Task AddKitAsync(Kit kit)
+ {
+ if (kit?.Name == null || kit?.Items == null)
+ {
+ throw new ArgumentNullException(nameof(kit));
}
- public Task RemoveKitAsync(string kitName)
+ await DatabaseProvider.AddKitAsync(kit);
+ RegisterPermission(kit.Name);
+ }
+
+ public async Task FindKitByNameAsync(string kitName)
+ {
+ if (string.IsNullOrEmpty(kitName))
{
- if (string.IsNullOrEmpty(kitName))
- {
- return Task.FromException(new ArgumentException(
- $"'{nameof(kitName)}' cannot be null or empty.", nameof(kitName)));
- }
+ throw new ArgumentException($"'{nameof(kitName)}' cannot be null or empty.", nameof(kitName));
+ }
- return m_Database.RemoveKitAsync(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);
}
- protected virtual async Task RegisterPermissionsAsync()
+ return kit;
+ }
+
+ public Task RemoveKitAsync(string kitName)
+ {
+ return string.IsNullOrEmpty(kitName)
+ ? Task.FromException(new ArgumentException(
+ $"'{nameof(kitName)}' cannot be null or empty.", nameof(kitName)))
+ : DatabaseProvider.RemoveKitAsync(kitName);
+ }
+
+ public Task UpdateKitAsync(Kit kit)
+ {
+ return DatabaseProvider.UpdateKitAsync(kit);
+ }
+
+ public Task IsKitExists(string name)
+ {
+ return DatabaseProvider.IsKitExists(name);
+ }
+
+ protected virtual async Task RegisterPermissionsAsync(IReadOnlyCollection? kits = null)
+ {
+ kits ??= await DatabaseProvider.GetKitsAsync();
+ m_KitsCount = kits.Count;
+
+ foreach (var kit in kits)
{
- 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()),
+ $"Grants access to the {kitName} kit");
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ m_ConfigurationChangedWatcher?.Dispose();
+ m_ConfigurationChangedWatcher = null;
- public ValueTask DisposeAsync()
+ if (DatabaseProvider != null)
{
- m_ConfigurationChangedWatcher?.Dispose();
- return new(m_Database.DisposeSyncOrAsync());
+ await DatabaseProvider.DisposeSyncOrAsync();
}
+
+ DatabaseProvider = null!;
}
}
\ No newline at end of file
diff --git a/Kits/config.yaml b/Kits/config.yaml
index 8f51975..d38737d 100644
--- a/Kits/config.yaml
+++ b/Kits/config.yaml
@@ -2,9 +2,18 @@
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"
+# 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: 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
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}"
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.
[](https://www.nuget.org/packages/DiFFoZ.Kits/)
[](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.