├── .github └── FUNDING.yml ├── SherbetVaults ├── Models │ ├── Delegates.cs │ ├── Restrictions │ │ ├── IItemRestrictor.cs │ │ ├── BadSelectorException.cs │ │ ├── SelectorAttribute.cs │ │ ├── Restrictors │ │ │ ├── ItemRestrictor.cs │ │ │ ├── ItemSlotRestrictor.cs │ │ │ ├── ItemTypeRestrictor.cs │ │ │ ├── ItemRangeRestrictor.cs │ │ │ ├── WorkshopRestrictor.cs │ │ │ └── ItemTableRestrictor.cs │ │ ├── VaultRestrictionGroup.cs │ │ └── RestrictionBuilder.cs │ ├── Enums │ │ ├── ETransactionType.cs │ │ └── EVaultAvailability.cs │ ├── VaultStoreDeniedException.cs │ ├── Data │ │ ├── ItemSpawnTable.cs │ │ └── VaultItems.cs │ ├── Config │ │ ├── Restrictions │ │ │ ├── RestrictionGroup.cs │ │ │ └── RestrictionSettings.cs │ │ ├── VaultConfig.cs │ │ ├── SherbetVaultsConfig.cs │ │ └── Translations.cs │ ├── Caching │ │ ├── VaultCache.cs │ │ └── PlayerVaultCache.cs │ ├── VaultManager.cs │ ├── Utility │ │ ├── OfflinePlayerUtility.cs │ │ ├── ItemTableTool.cs │ │ └── VaultSelector.cs │ ├── VaultPlayerLock.cs │ ├── RestrictionTool.cs │ └── Extensions.cs ├── Commands │ ├── TrashCommand.cs │ ├── VaultsCommand.cs │ ├── WipeVaultCommand.cs │ ├── SpyVaultCommand.cs │ ├── VaultCommand.cs │ └── VaultAliasCommand.cs ├── Database │ ├── Models │ │ ├── VaultAlias.cs │ │ ├── VaultItem.cs │ │ └── VaultTransaction.cs │ ├── Tables │ │ ├── VaultAliasTable.cs │ │ ├── VaultTransactionsTable.cs │ │ └── VaultItemsTable.cs │ ├── DatabaseManager.cs │ └── DatabaseQueue.cs ├── app.config ├── Properties │ └── AssemblyInfo.cs ├── packages.config ├── SherbetVaultsPlugin.cs └── SherbetVaults.csproj ├── VaultAliases.md ├── SherbetVaults.sln ├── .gitattributes ├── ItemRestrictions.md ├── README.md └── .gitignore /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ShimmyMySherbet 4 | ko_fi: ShimmyMySherbet 5 | -------------------------------------------------------------------------------- /SherbetVaults/Models/Delegates.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace SherbetVaults.Models 4 | { 5 | public delegate Task AsyncDatabaseAction(T Table); 6 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Restrictions/IItemRestrictor.cs: -------------------------------------------------------------------------------- 1 | using SDG.Unturned; 2 | 3 | namespace SherbetVaults.Models.Restrictions 4 | { 5 | public interface IItemRestrictor 6 | { 7 | bool IsMatch(ItemAsset asset); 8 | } 9 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Enums/ETransactionType.cs: -------------------------------------------------------------------------------- 1 | namespace SherbetVaults.Models.Enums 2 | { 3 | public enum ETransactionType : byte 4 | { 5 | Add = 0, 6 | Remove = 1, 7 | Update = 2, 8 | Wipe 9 | } 10 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Enums/EVaultAvailability.cs: -------------------------------------------------------------------------------- 1 | namespace SherbetVaults.Models.Enums 2 | { 3 | public enum EVaultAvailability 4 | { 5 | NoVaults, 6 | NoAllowedVaults, 7 | BadVaultID, 8 | VaultAvailable, 9 | NotAllowed 10 | } 11 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Restrictions/BadSelectorException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SherbetVaults.Models.Restrictions 4 | { 5 | public sealed class BadSelectorException : Exception 6 | { 7 | public BadSelectorException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/VaultStoreDeniedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SherbetVaults.Models 4 | { 5 | public class VaultStoreDeniedException : Exception 6 | { 7 | public VaultStoreDeniedException() : base("A player attempted to store an item in their vault that is blacklisted") 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Restrictions/SelectorAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace SherbetVaults.Models.Restrictions 5 | { 6 | public class SelectorAttribute : Attribute 7 | { 8 | public string Selector { get; } 9 | public Regex Regex { get; } 10 | 11 | public SelectorAttribute(string regexSelector) 12 | { 13 | Selector = regexSelector; 14 | Regex = new Regex(regexSelector); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Data/ItemSpawnTable.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SherbetVaults.Models.Data 4 | { 5 | public readonly struct ItemSpawnTable 6 | { 7 | public ushort TableID { get; } 8 | public HashSet Items { get; } 9 | public string Name { get; } 10 | 11 | public ItemSpawnTable(ushort tableID, HashSet items, string name) 12 | { 13 | TableID = tableID; 14 | Items = items; 15 | Name = name; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SherbetVaults/Commands/TrashCommand.cs: -------------------------------------------------------------------------------- 1 | using Cysharp.Threading.Tasks; 2 | using Rocket.API; 3 | using RocketExtensions.Models; 4 | using RocketExtensions.Plugins; 5 | using SherbetVaults.Models; 6 | 7 | namespace SherbetVaults.Commands 8 | { 9 | [CommandName("Trash")] 10 | [CommandInfo("Opens a trash can")] 11 | [AllowedCaller(AllowedCaller.Player)] 12 | public class TrashCommand : RocketCommand 13 | { 14 | public override async UniTask Execute(CommandContext context) 15 | { 16 | await context.LDMPlayer.OpenTrashAsync(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Restrictions/Restrictors/ItemRestrictor.cs: -------------------------------------------------------------------------------- 1 | using SDG.Unturned; 2 | 3 | namespace SherbetVaults.Models.Restrictions.Restrictors 4 | { 5 | [Selector(@"^\d+$")] 6 | public class ItemRestrictor : IItemRestrictor 7 | { 8 | public ushort ItemID { get; } 9 | 10 | public bool IsMatch(ItemAsset asset) => asset.id == ItemID; 11 | 12 | public ItemRestrictor(string id) 13 | { 14 | if (!ushort.TryParse(id, out var itemID)) 15 | throw new BadSelectorException($"ItemID: Invalid item ID {itemID}"); 16 | 17 | ItemID = itemID; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Config/Restrictions/RestrictionGroup.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Xml.Serialization; 3 | 4 | namespace SherbetVaults.Models.Config.Restrictions 5 | { 6 | [XmlRoot] 7 | public class RestrictionGroup 8 | { 9 | [XmlAttribute] 10 | public string GroupID = "Group1"; 11 | 12 | [XmlAttribute] 13 | public int Weight = 1; 14 | 15 | [XmlAttribute] 16 | public bool Blacklist = true; 17 | 18 | public string TranslationKey = "Restrictions_Blacklisted"; 19 | 20 | [XmlArrayItem(ElementName = "ItemSelector")] 21 | public List Selectors = new(); 22 | } 23 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Config/VaultConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Serialization; 2 | using Rocket.API; 3 | using RocketExtensions.Models; 4 | 5 | namespace SherbetVaults.Models.Config 6 | { 7 | [XmlRoot] 8 | public class VaultConfig 9 | { 10 | [XmlAttribute] 11 | public string VaultID = "default"; 12 | 13 | [XmlAttribute] 14 | public byte Width = 8; 15 | 16 | [XmlAttribute] 17 | public byte Height = 8; 18 | 19 | [XmlIgnore] 20 | public string FormattedPermission => $"Vaults.{VaultID}"; 21 | 22 | public bool HasPermission(LDMPlayer player) 23 | { 24 | return player.UnturnedPlayer.HasPermission(FormattedPermission); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Restrictions/VaultRestrictionGroup.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using SherbetVaults.Models.Config.Restrictions; 3 | 4 | namespace SherbetVaults.Models.Restrictions 5 | { 6 | public class VaultRestrictionGroup : RestrictionGroup 7 | { 8 | public List Restrictors = new(); 9 | 10 | public VaultRestrictionGroup(RestrictionGroup group, List restrictors) 11 | { 12 | Restrictors = restrictors; 13 | Blacklist = group.Blacklist; 14 | GroupID = group.GroupID; 15 | Selectors = group.Selectors; 16 | Weight = group.Weight; 17 | TranslationKey = group.TranslationKey; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /SherbetVaults/Database/Models/VaultAlias.cs: -------------------------------------------------------------------------------- 1 | using ShimmyMySherbet.MySQL.EF.Models; 2 | using ShimmyMySherbet.MySQL.EF.Models.TypeModel; 3 | 4 | namespace SherbetVaults.Database.Models 5 | { 6 | public class VaultAlias 7 | { 8 | [SQLPrimaryKey] 9 | public ulong PlayerID { get; set; } 10 | 11 | [SQLPrimaryKey, SQLVarChar(64)] 12 | public string Alias { get; set; } 13 | 14 | public string VaultID { get; set; } 15 | 16 | public static VaultAlias Create(ulong player, string vaultID, string alias) 17 | { 18 | return new VaultAlias() 19 | { 20 | Alias = alias, 21 | PlayerID = player, 22 | VaultID = vaultID 23 | }; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Restrictions/Restrictors/ItemSlotRestrictor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SDG.Unturned; 3 | 4 | namespace SherbetVaults.Models.Restrictions.Restrictors 5 | { 6 | [Selector(@"Slot:.*")] 7 | public class ItemSlotRestrictor : IItemRestrictor 8 | { 9 | public ESlotType Slot { get; } 10 | 11 | public bool IsMatch(ItemAsset asset) => 12 | asset.slot == Slot; 13 | 14 | public ItemSlotRestrictor(string selector) 15 | { 16 | var slotName = selector.Substring(5); 17 | if (!Enum.TryParse(slotName.ToUpper(), out var slot)) 18 | throw new BadSelectorException($"Slot: Invalid slot '{slotName}'. Acceptable: None, Primary, Secondary, Tertiary, Any"); 19 | Slot = slot; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Restrictions/Restrictors/ItemTypeRestrictor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using SDG.Unturned; 4 | 5 | namespace SherbetVaults.Models.Restrictions.Restrictors 6 | { 7 | [Selector(@"^Type:.*")] 8 | public class ItemTypeRestrictor : IItemRestrictor 9 | { 10 | public EItemType ItemType { get; } 11 | 12 | public bool IsMatch(ItemAsset asset) => 13 | asset.type == ItemType; 14 | 15 | public ItemTypeRestrictor(string selector) 16 | { 17 | var typeName = selector.Substring(5); 18 | if (!Enum.TryParse(typeName.ToUpper(), out var type)) 19 | throw new BadSelectorException($"Type: Invalid item type '{typeName}'. Check the wiki for acceptable types."); 20 | ItemType = type; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /SherbetVaults/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /SherbetVaults/Commands/VaultsCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Cysharp.Threading.Tasks; 3 | using Rocket.API; 4 | using RocketExtensions.Models; 5 | using RocketExtensions.Plugins; 6 | 7 | namespace SherbetVaults.Commands 8 | { 9 | [CommandName("Vaults")] 10 | [CommandInfo("Lists your available vaults")] 11 | [AllowedCaller(AllowedCaller.Player)] 12 | public class VaultsCommand : RocketCommand 13 | { 14 | public override async UniTask Execute(CommandContext context) 15 | { 16 | var vaults = Plugin.VaultSelector.GetPlayerVaults(context.LDMPlayer); 17 | 18 | if (vaults.Length == 0) 19 | { 20 | await context.ReplyKeyAsync("Vaults_No_Vaults"); 21 | return; 22 | } 23 | 24 | var vaultNames = string.Join(", ", vaults.Select(x => x.VaultID)); 25 | 26 | await context.ReplyKeyAsync("Vaults_List", vaultNames, vaults.Length); 27 | } 28 | 29 | private new SherbetVaultsPlugin Plugin => 30 | base.Plugin as SherbetVaultsPlugin; 31 | } 32 | } -------------------------------------------------------------------------------- /VaultAliases.md: -------------------------------------------------------------------------------- 1 | # Vault Aliases 2 | Allows players to set an alias for a vault. Similar to renaming a vault. 3 | This allows players to customize their vaults more. 4 | E.g., creating a vault alias for 'MVP' named 'Guns'. So, using `/Vault Guns` opens their 'MVP' vault. 5 | This feature is disabled by default, to enable it, set `VaultAliasesEnabled` in the config to True. 6 | 7 | For a player to use a previously set vault alias, the permission `SherbetVaults.Vault.Alias` needs to be given. And for a player to manage their aliases, the `/VaultAlias` command needs to be granted. 8 | 9 | Max aliases can be granted with the permission `SherbetVaults.MaxAliases.XXX`, where XXX is the max number of aliases. By default, players do not have a max number of aliases. 10 | 11 | If a player sets aliases and then loses the `SherbetVaults.Vault.Alias` permission, they will lose the ability to use the aliases, but the aliases won't be deleted. If they regain the permission, they can continue to use their previously set aliases. 12 | 13 | This feature has been designed to be suitable as a donation rank feature, as a purely QOL feature. -------------------------------------------------------------------------------- /SherbetVaults/Database/Tables/VaultAliasTable.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SherbetVaults.Database.Models; 3 | using ShimmyMySherbet.MySQL.EF.Core; 4 | 5 | namespace SherbetVaults.Database.Tables 6 | { 7 | public class VaultAliasTable : DatabaseTable 8 | { 9 | public VaultAliasTable(string tableName) : base(tableName) 10 | { 11 | } 12 | 13 | public async Task SetAliasAsync(ulong playerID, string vaultID, string alias) => 14 | await InsertUpdateAsync(VaultAlias.Create(playerID, vaultID, alias)); 15 | 16 | public async Task GetAliasAsync(ulong playerID, string alias) => 17 | (await QuerySingleAsync("SELECT * FROM @TABLE WHERE PlayerID=@0 AND Alias=@1;", playerID, alias))?.VaultID; 18 | 19 | public async Task DeleteAliasAsync(ulong playerID, string alias) => 20 | await ExecuteNonQueryAsync("DELETE FROM @TABLE WHERE PlayerID=@0 AND Alias=@1;", playerID, alias) > 0; 21 | 22 | public async Task GetAliasesAsync(ulong playerID) => 23 | (await QueryAsync("SELECT * FROM @TABLE WHERE PlayerID=@0;", playerID)).ToArray(); 24 | } 25 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Restrictions/Restrictors/ItemRangeRestrictor.cs: -------------------------------------------------------------------------------- 1 | using SDG.Unturned; 2 | 3 | namespace SherbetVaults.Models.Restrictions.Restrictors 4 | { 5 | [Selector(@"^\d+-\d+$")] 6 | public class ItemRangeRestrictor : IItemRestrictor 7 | { 8 | public ushort StartID { get; } 9 | public ushort EndID { get; } 10 | public bool IsMatch(ItemAsset asset) => asset.id >= StartID && asset.id <= EndID; 11 | 12 | public ItemRangeRestrictor(string selector) 13 | { 14 | var parts = selector.Split('-'); 15 | 16 | if (!ushort.TryParse(parts[0], out var value1)) 17 | throw new BadSelectorException($"Range: Invalid item ID {parts[0]}"); 18 | 19 | if (!ushort.TryParse(parts[1], out var value2)) 20 | throw new BadSelectorException($"Range: Invalid item ID {parts[1]}"); 21 | 22 | if (value1 > value2) 23 | { 24 | StartID = value2; 25 | EndID = value1; 26 | } 27 | else 28 | { 29 | StartID = value1; 30 | EndID = value2; 31 | } 32 | } 33 | 34 | } 35 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Config/SherbetVaultsConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Xml.Serialization; 3 | using Rocket.API; 4 | using SherbetVaults.Models.Config.Restrictions; 5 | using ShimmyMySherbet.MySQL.EF.Models; 6 | 7 | namespace SherbetVaults.Models.Config 8 | { 9 | public class SherbetVaultsConfig : IRocketPluginConfiguration 10 | { 11 | public DatabaseSettings DatabaseSettings; 12 | public bool LargestVaultIsDefault = false; 13 | public string DefaultVault = "default"; 14 | public bool VaultAliasesEnabled = false; 15 | 16 | public string DatabaseTablePrefix = "SherbetVaults"; 17 | 18 | [XmlArrayItem(ElementName = "Vault")] 19 | public List Vaults = new(); 20 | 21 | public RestrictionSettings Restrictions = RestrictionSettings.Default; 22 | 23 | public void LoadDefaults() 24 | { 25 | DatabaseSettings = DatabaseSettings.Default; 26 | Vaults = new List() { 27 | new() { VaultID = "default", Height = 8, Width = 8 }, 28 | new() { VaultID = "vip", Height = 12, Width = 12 } 29 | }; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /SherbetVaults.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31729.503 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SherbetVaults", "SherbetVaults\SherbetVaults.csproj", "{1CE64AE8-6430-4B9D-A195-E2E6BB7B9850}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {1CE64AE8-6430-4B9D-A195-E2E6BB7B9850}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {1CE64AE8-6430-4B9D-A195-E2E6BB7B9850}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {1CE64AE8-6430-4B9D-A195-E2E6BB7B9850}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {1CE64AE8-6430-4B9D-A195-E2E6BB7B9850}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {34338DB3-9E1C-4ABA-B253-652C6775C25C} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /SherbetVaults/Models/Caching/VaultCache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using SherbetVaults.Models.Data; 3 | 4 | namespace SherbetVaults.Models.Caching 5 | { 6 | public class VaultCache 7 | { 8 | private readonly ConcurrentDictionary m_Caches = new(); 9 | 10 | public VaultItems GetStorage(ulong playerID, string vaultID) 11 | { 12 | if (!m_Caches.ContainsKey(playerID)) 13 | { 14 | m_Caches[playerID] = new PlayerVaultCache(playerID); 15 | } 16 | 17 | return m_Caches[playerID].GetStorage(vaultID); 18 | } 19 | 20 | public void SetStorage(ulong playerID, string vaultID, VaultItems storage) 21 | { 22 | if (!m_Caches.ContainsKey(playerID)) 23 | { 24 | m_Caches[playerID] = new PlayerVaultCache(playerID); 25 | } 26 | 27 | m_Caches[playerID].SetStorage(vaultID, storage); 28 | } 29 | 30 | public void Clear(ulong player) 31 | { 32 | if (m_Caches.TryGetValue(player, out var cache)) 33 | { 34 | cache.Clear(); 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/VaultManager.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SherbetVaults.Database; 3 | using SherbetVaults.Models.Caching; 4 | using SherbetVaults.Models.Data; 5 | 6 | namespace SherbetVaults.Models 7 | { 8 | public class VaultManager 9 | { 10 | public SherbetVaultsPlugin VaultsPlugin { get; } 11 | public VaultCache VaultsCache { get; } 12 | public DatabaseManager Database => VaultsPlugin.Database; 13 | 14 | public VaultManager(SherbetVaultsPlugin vaultsPlugin) 15 | { 16 | VaultsPlugin = vaultsPlugin; 17 | VaultsCache = new VaultCache(); 18 | } 19 | 20 | public async Task GetVault(ulong playerID, string vaultID, bool allowCache = true) 21 | { 22 | var vaultConfig = VaultsPlugin.VaultSelector.GetVaultConfig(vaultID); 23 | 24 | if (vaultConfig == null) 25 | { 26 | return null; 27 | } 28 | 29 | var items = await Database.VaultItems.OpenVault(playerID, vaultID, vaultConfig); 30 | 31 | return items; 32 | } 33 | 34 | public void RemovePlayerCache(ulong playerID) 35 | { 36 | VaultsCache.Clear(playerID); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Config/Restrictions/RestrictionSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SherbetVaults.Models.Config.Restrictions 4 | { 5 | public class RestrictionSettings 6 | { 7 | public bool AdminsBypassRestrictions = true; 8 | public bool ShowMessages = true; 9 | public bool Enabled = false; 10 | 11 | public List Groups = new(); 12 | 13 | public static RestrictionSettings Default 14 | { 15 | get 16 | { 17 | return new RestrictionSettings() 18 | { 19 | Groups = new List() 20 | { 21 | new() 22 | { 23 | GroupID = "Group1", 24 | Selectors = new List() 25 | { 26 | "1", 27 | "255-259", 28 | "Type:Throwable", 29 | "Table:Police*", 30 | "Slot:Primary", 31 | "Workshop:2136497468", 32 | } 33 | } 34 | } 35 | }; 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Utility/OfflinePlayerUtility.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Rocket.Unturned.Player; 3 | 4 | namespace SherbetVaults.Models.Utility 5 | { 6 | public static class OfflinePlayerUtility 7 | { 8 | public static async Task<(ulong playerID, string playerName)> GetPlayer(string handle, bool fetchName = true) 9 | { 10 | var (playerID, playerNameTask) = GetPlayerParallel(handle, fetchName); 11 | 12 | if (fetchName) 13 | { 14 | return (playerID, await playerNameTask); 15 | } 16 | return (playerID, "Unknown Player"); 17 | } 18 | 19 | public static (ulong playerID, Task playerNameTask) GetPlayerParallel(string handle, bool fetchName = true) 20 | { 21 | if (ulong.TryParse(handle, out var playerID)) 22 | { 23 | var nameTask = fetchName ? playerID.GetPlayerName() : Task.FromResult("Unknown Player"); 24 | 25 | return (playerID, nameTask); 26 | } 27 | 28 | var player = UnturnedPlayer.FromName(handle); 29 | 30 | if (player != null) 31 | { 32 | return (player.CSteamID.m_SteamID, Task.FromResult(player.DisplayName)); 33 | } 34 | 35 | return (0ul, Task.FromResult(string.Empty)); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /SherbetVaults/Database/DatabaseManager.cs: -------------------------------------------------------------------------------- 1 | using SherbetVaults.Database.Tables; 2 | using ShimmyMySherbet.MySQL.EF.Core; 3 | using ShimmyMySherbet.MySQL.EF.Models.ConnectionProviders; 4 | 5 | namespace SherbetVaults.Database 6 | { 7 | public class DatabaseManager : DatabaseClient 8 | { 9 | public VaultItemsTable VaultItems { get; } 10 | public VaultAliasTable Aliases { get; } 11 | public VaultTransactionsTable Transactions { get; } 12 | public DatabaseQueue Queue { get; set; } 13 | 14 | public DatabaseManager(SherbetVaultsPlugin plugin) : base(connectionProvider: new TransientConnectionProvider(plugin.Config.DatabaseSettings), autoInit: false) 15 | { 16 | Queue = new DatabaseQueue(this); 17 | VaultItems = new VaultItemsTable(plugin, $"{plugin.Config.DatabaseTablePrefix}_Items"); 18 | Transactions = new VaultTransactionsTable($"{plugin.Config.DatabaseTablePrefix}_History"); 19 | 20 | if (plugin.Config.VaultAliasesEnabled) 21 | Aliases = new VaultAliasTable($"{plugin.Config.DatabaseTablePrefix}_Aliases"); 22 | 23 | Init(); 24 | } 25 | 26 | public void InitQueue() => 27 | Queue.StartWorker(); 28 | 29 | public void Dispose() 30 | { 31 | Queue.Dispose(); 32 | Client.Dispose(); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Caching/PlayerVaultCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.IO; 4 | using Rocket.Core; 5 | using SherbetVaults.Models.Data; 6 | 7 | namespace SherbetVaults.Models.Caching 8 | { 9 | public class PlayerVaultCache 10 | { 11 | public ulong PlayerID { get; } 12 | private readonly ConcurrentDictionary m_Vaults = new(StringComparer.InvariantCultureIgnoreCase); 13 | 14 | public PlayerVaultCache(ulong playerID) 15 | { 16 | PlayerID = playerID; 17 | } 18 | 19 | public void SetStorage(string vaultId, VaultItems storage) 20 | { 21 | if (m_Vaults.TryRemove(vaultId, out var oldStorage)) 22 | { 23 | if (oldStorage != storage) 24 | { 25 | oldStorage.DisableSync(); 26 | } 27 | } 28 | m_Vaults[vaultId] = storage; 29 | } 30 | 31 | public VaultItems GetStorage(string vaultID) 32 | { 33 | if (m_Vaults.TryGetValue(vaultID, out var storage)) 34 | { 35 | return storage; 36 | } 37 | return null; 38 | } 39 | 40 | public void Clear() 41 | { 42 | foreach (var i in m_Vaults.Values) 43 | { 44 | i.DisableSync(); 45 | } 46 | m_Vaults.Clear(); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Restrictions/Restrictors/WorkshopRestrictor.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using SDG.Unturned; 4 | 5 | namespace SherbetVaults.Models.Restrictions.Restrictors 6 | { 7 | [Selector(@"^Workshop:.*")] 8 | public class WorkshopRestrictor : IItemRestrictor 9 | { 10 | public uint WorkshopID { get; } 11 | 12 | public bool IsMatch(ItemAsset asset) => GetWorkshopID(asset.absoluteOriginFilePath) == WorkshopID; 13 | 14 | private uint GetWorkshopID(string path) 15 | { 16 | var parts = path.Split(Path.DirectorySeparatorChar).ToList(); 17 | 18 | var contentIndex = parts.LastIndexOf("content"); 19 | var workshopFolderIndex = contentIndex + 1; 20 | if (parts.Count < workshopFolderIndex + 1) 21 | { 22 | return 0; 23 | } 24 | 25 | var workshopFolderName = parts[workshopFolderIndex]; 26 | 27 | if (uint.TryParse(workshopFolderName, out var workshopID)) 28 | { 29 | return workshopID; 30 | } 31 | 32 | return 0; 33 | } 34 | 35 | public WorkshopRestrictor(string selector) 36 | { 37 | var id = selector.Substring(9); 38 | if (!uint.TryParse(id, out var workshopID)) 39 | throw new BadSelectorException($"Workshop: Invalid workshop ID: {id}"); 40 | WorkshopID = workshopID; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /SherbetVaults/Database/Models/VaultItem.cs: -------------------------------------------------------------------------------- 1 | using SDG.Unturned; 2 | using ShimmyMySherbet.MySQL.EF.Models; 3 | using ShimmyMySherbet.MySQL.EF.Models.TypeModel; 4 | 5 | namespace SherbetVaults.Database.Models 6 | { 7 | public class VaultItem 8 | { 9 | [SQLPrimaryKey] 10 | public ulong PlayerID { get; set; } 11 | 12 | [SQLPrimaryKey, SQLVarChar(64)] 13 | public string VaultID { get; set; } 14 | 15 | [SQLPrimaryKey] 16 | public byte X; 17 | 18 | [SQLPrimaryKey] 19 | public byte Y; 20 | 21 | public ushort ItemID { get; set; } 22 | 23 | public byte Rot { get; set; } 24 | public byte Quality { get; set; } 25 | public byte Amount { get; set; } 26 | 27 | public byte[] State { get; set; } 28 | 29 | public Item GetItem() 30 | { 31 | return new Item(ItemID, Amount, Quality, State); 32 | } 33 | 34 | public static VaultItem Create(ulong playerID, string vaultID, Item item, byte rot, byte x, byte y) 35 | { 36 | return new VaultItem() 37 | { 38 | ItemID = item.id, 39 | PlayerID = playerID, 40 | VaultID = vaultID, 41 | Amount = item.amount, 42 | Quality = item.quality, 43 | Rot = rot, 44 | State = item.state, 45 | X = x, 46 | Y = y 47 | }; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /SherbetVaults/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("SherbetVaults")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("SherbetVaults")] 12 | [assembly: AssemblyCopyright("Copyright © 2021")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("1ce64ae8-6430-4b9d-a195-e2e6bb7b9850")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.2.0.0")] 35 | [assembly: AssemblyFileVersion("1.2.0.0")] 36 | -------------------------------------------------------------------------------- /SherbetVaults/Commands/WipeVaultCommand.cs: -------------------------------------------------------------------------------- 1 | using Cysharp.Threading.Tasks; 2 | using Rocket.API; 3 | using RocketExtensions.Models; 4 | using RocketExtensions.Plugins; 5 | using SherbetVaults.Models.Utility; 6 | using UnityEngine; 7 | 8 | namespace SherbetVaults.Commands 9 | { 10 | [CommandName("WipeVault")] 11 | [CommandInfo("Wipes a player's vault", Syntax: "[Target Player] [VaultID]")] 12 | [AllowedCaller(AllowedCaller.Player)] 13 | public class WipeVaultCommand : RocketCommand 14 | { 15 | public override async UniTask Execute(CommandContext context) 16 | { 17 | var playerHandle = context.Arguments.Get(0, paramName: "Target Player"); 18 | 19 | var (playerID, playerNameTask) = OfflinePlayerUtility.GetPlayerParallel(playerHandle); 20 | 21 | if (playerID == 0) 22 | { 23 | await context.ReplyAsync($"Usage: /{Name} {Syntax}", Color.cyan); 24 | return; 25 | } 26 | 27 | var targetVault = context.Arguments.Get(1, paramName: "VaultID"); 28 | 29 | var items = await Plugin.Database.VaultItems.Clear(playerID, targetVault); 30 | Plugin.Database.Queue.Enqueue(async (x) => await x.Transactions.Wipe(playerID, targetVault)); 31 | 32 | await context.ReplyKeyAsync("WipeVault_Wiped", items, await playerNameTask, targetVault); 33 | } 34 | 35 | private new SherbetVaultsPlugin Plugin => 36 | base.Plugin as SherbetVaultsPlugin; 37 | } 38 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Config/Translations.cs: -------------------------------------------------------------------------------- 1 | using Rocket.API.Collections; 2 | 3 | namespace SherbetVaults 4 | { 5 | public partial class SherbetVaultsPlugin 6 | { 7 | public override TranslationList DefaultTranslations => new() 8 | { 9 | { "Vault_Fail_NotFound", "[color=red]Failed to find a vault by that ID[/color]" }, 10 | { "Vault_Fail_NoPermission", "[color=red]You do not have permission to access vault {0}[/color]"}, 11 | { "Vault_Fail_CannotLoad", "[color=red]Vault {0} is currently unavailable[/color]" }, 12 | { "Vaults_No_Vaults", "[color=yellow]You don't have access to any vaults[/color]" }, 13 | { "Vaults_List", "[color=green]Your vaults: {0}[/color]" }, 14 | { "WipeVault_Wiped", "[color=green]Wiped {0} items from {1}'s vault {2}[/color]" }, 15 | { "VaultAliases_MaxReached", "[color=red]Max vault aliases reached[/color]" }, 16 | { "VaultAliases_Set", "[color=#00ffff]Vault alias created: {0} -> {1}[/color]" }, 17 | { "VaultAliases_Removed", "[color=#00ffff]Removed alias {0}[/color]" }, 18 | { "VaultAliases_Remove_NotFound", "[color=cyan]No alias by that name found[/color]" }, 19 | { "VaultAliases_List", "[color=#00ffff]Aliases: {1}[/color]" }, 20 | { "Restrictions_Blacklisted", "[color=red]You cannot store that item in your vault[/color]" }, 21 | { "VaultAliases_Disabled", "[color=red]Vault aliases are disabled on this server[/color]" } 22 | }; 23 | } 24 | } -------------------------------------------------------------------------------- /SherbetVaults/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SherbetVaults/Models/Restrictions/Restrictors/ItemTableRestrictor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text.RegularExpressions; 4 | using SDG.Unturned; 5 | using SherbetVaults.Models.Utility; 6 | 7 | namespace SherbetVaults.Models.Restrictions.Restrictors 8 | { 9 | [Selector(@"^Table:.*")] 10 | public class ItemTableRestrictor : IItemRestrictor 11 | { 12 | public ItemTableTool ItemTables { get; } 13 | 14 | public ushort ItemTableID { get; } 15 | 16 | public Regex Regex { get; } 17 | 18 | public bool IsMatch(ItemAsset asset) 19 | { 20 | if (Regex == null) 21 | { 22 | return ItemTables.GetTableIDs(asset.id).Contains(ItemTableID); 23 | } 24 | else 25 | { 26 | return ItemTables.GetTableIDs(Regex).Contains(ItemTableID); 27 | } 28 | } 29 | 30 | public ItemTableRestrictor(ItemTableTool itemTables, string selector) 31 | { 32 | ItemTables = itemTables; 33 | var table = selector.Substring(6); 34 | 35 | if (ushort.TryParse(table, out var id)) 36 | { 37 | // Match by table ID 38 | ItemTableID = id; 39 | Regex = null; 40 | } 41 | else 42 | { 43 | // Match by name selector 44 | ItemTableID = 0; 45 | var s = table 46 | .Replace("*", "§"); 47 | s = Regex.Escape(s) 48 | .Replace("§", ".*"); 49 | Regex = new Regex($"^{s}$"); 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/VaultPlayerLock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | 4 | namespace SherbetVaults.Models 5 | { 6 | /// 7 | /// Helps prevent client-side graphical item graphical glitches if a player spams the vault command. 8 | /// 9 | public class VaultPlayerLock 10 | { 11 | public ConcurrentDictionary m_PlayerBlocks = new(); 12 | 13 | public OpenBlockToken TryObtainLock(ulong player, out bool valid) 14 | { 15 | if (m_PlayerBlocks.TryGetValue(player, out var blocked)) 16 | { 17 | if (blocked) 18 | { 19 | valid = false; 20 | return default; 21 | } 22 | } 23 | m_PlayerBlocks[player] = true; 24 | valid = true; 25 | return new OpenBlockToken(player, this); 26 | } 27 | 28 | private void Release(ulong player) 29 | { 30 | m_PlayerBlocks[player] = false; 31 | } 32 | 33 | public struct OpenBlockToken : IDisposable 34 | { 35 | public ulong PlayerID { get; } 36 | private VaultPlayerLock Parent { get; } 37 | 38 | private bool m_Disposed; 39 | 40 | public OpenBlockToken(ulong playerID, VaultPlayerLock parent) 41 | { 42 | m_Disposed = false; 43 | PlayerID = playerID; 44 | Parent = parent; 45 | } 46 | 47 | public void Dispose() 48 | { 49 | if (!m_Disposed) 50 | { 51 | m_Disposed = true; 52 | Parent?.Release(PlayerID); 53 | } 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /SherbetVaults/Commands/SpyVaultCommand.cs: -------------------------------------------------------------------------------- 1 | using Cysharp.Threading.Tasks; 2 | using Rocket.API; 3 | using RocketExtensions.Models; 4 | using RocketExtensions.Plugins; 5 | using SherbetVaults.Models.Utility; 6 | using UnityEngine; 7 | 8 | namespace SherbetVaults.Commands 9 | { 10 | [CommandName("SpyVault")] 11 | [CommandInfo("Opens another player's vault", Syntax: "[Target Player] [Vault ID]")] 12 | [AllowedCaller(AllowedCaller.Player)] 13 | public class SpyVaultCommand : RocketCommand 14 | { 15 | public override async UniTask Execute(CommandContext context) 16 | { 17 | var playerHandle = context.Arguments.Get(0, paramName: "Target Player"); 18 | 19 | var (playerID, _) = await OfflinePlayerUtility.GetPlayer(playerHandle, false); 20 | 21 | if (playerID == 0) 22 | { 23 | await context.ReplyAsync($"Usage: /{Name} {Syntax}", Color.cyan); 24 | return; 25 | } 26 | 27 | var targetVault = context.Arguments.Get(1, Plugin.Config.DefaultVault, paramName: "VaultID"); 28 | 29 | var vaultConfig = Plugin.VaultSelector.GetVaultConfig(targetVault); 30 | 31 | if (vaultConfig == null) 32 | { 33 | await context.ReplyKeyAsync("Vault_Fail_NotFound", targetVault); 34 | return; 35 | } 36 | 37 | var vault = await Plugin.VaultManager.GetVault(playerID, targetVault); 38 | 39 | if (vault == null) 40 | { 41 | await context.ReplyKeyAsync("Vault_Fail_CannotLoad", targetVault); 42 | return; 43 | } 44 | 45 | await vault.OpenForPlayerAsync(context.LDMPlayer); 46 | } 47 | 48 | private new SherbetVaultsPlugin Plugin => 49 | base.Plugin as SherbetVaultsPlugin; 50 | } 51 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Utility/ItemTableTool.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Runtime.CompilerServices; 4 | using System.Text.RegularExpressions; 5 | using SDG.Unturned; 6 | using SherbetVaults.Models.Data; 7 | 8 | namespace SherbetVaults.Models.Utility 9 | { 10 | public class ItemTableTool 11 | { 12 | public List Tables { get; private set; } = new List(); 13 | 14 | public void ReInit() 15 | { 16 | Tables = GetSpawnTables(); 17 | } 18 | 19 | public ushort[] GetTableIDs(ushort itemID) => 20 | Tables.Where(x => x.Items.Contains(itemID)).Select(x => x.TableID).ToArray(); 21 | 22 | public ushort[] GetTableIDs(Regex selector) => 23 | Tables.Where(x => selector.IsMatch(x.Name)).Select(x => x.TableID).ToArray(); 24 | 25 | private List GetSpawnTables() 26 | { 27 | var tables = new List(); 28 | 29 | foreach (var table in LevelItems.tables) 30 | { 31 | var items = ResolveItems(table); 32 | tables.Add(new ItemSpawnTable(table.tableID, items, table.name)); 33 | } 34 | return tables; 35 | } 36 | 37 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 38 | private HashSet ResolveItems(ItemTable table) 39 | { 40 | var items = new HashSet(); 41 | 42 | foreach (var tier in table.tiers) 43 | { 44 | foreach (var item in tier.table) 45 | { 46 | if (!items.Contains(item.item)) 47 | { 48 | items.Add(item.item); 49 | } 50 | } 51 | } 52 | 53 | return items; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/RestrictionTool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Rocket.API; 5 | using RocketExtensions.Models; 6 | using SDG.Unturned; 7 | using SherbetVaults.Models.Config.Restrictions; 8 | using SherbetVaults.Models.Restrictions; 9 | 10 | namespace SherbetVaults.Models 11 | { 12 | public class RestrictionTool 13 | { 14 | public SherbetVaultsPlugin Plugin { get; } 15 | 16 | public RestrictionTool(SherbetVaultsPlugin plugin) 17 | { 18 | Plugin = plugin; 19 | } 20 | 21 | public bool IsPermitted(ushort itemID, LDMPlayer player, out RestrictionGroup matchedGroup) 22 | { 23 | var settings = Plugin.Config.Restrictions; 24 | matchedGroup = null; 25 | 26 | if (!settings.Enabled) 27 | { 28 | return true; 29 | } 30 | 31 | if (settings.AdminsBypassRestrictions && player.IsAdmin) 32 | { 33 | return true; 34 | } 35 | 36 | var asset = (ItemAsset)Assets.find(EAssetType.ITEM, itemID); 37 | 38 | if (asset == null) 39 | { 40 | return false; 41 | } 42 | 43 | var groups = GetPlayerGroups(player); 44 | 45 | foreach (var group in groups) 46 | { 47 | var isMatch = group.Restrictors.Any(x => x.IsMatch(asset)); 48 | if (isMatch) 49 | { 50 | matchedGroup = group; 51 | return !group.Blacklist; 52 | } 53 | } 54 | 55 | return true; 56 | } 57 | 58 | private List GetPlayerGroups(LDMPlayer player) 59 | { 60 | var explicitPermissions = player.UnturnedPlayer.GetPermissions().Select(x => x.Name).ToArray(); 61 | return Plugin.RestrictionGroups 62 | .Where(x => explicitPermissions.Contains($"SherbetVaults.Restrict.{x.GroupID}")) 63 | .OrderByDescending(x => x.Weight) 64 | .ToList(); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /SherbetVaults/Database/Tables/VaultTransactionsTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using SDG.Unturned; 4 | using SherbetVaults.Database.Models; 5 | using SherbetVaults.Models.Enums; 6 | using ShimmyMySherbet.MySQL.EF.Core; 7 | 8 | namespace SherbetVaults.Database.Tables 9 | { 10 | public class VaultTransactionsTable : DatabaseTable 11 | { 12 | public DatabaseQueue Queue { get; } 13 | 14 | public VaultTransactionsTable(string tableName) : base(tableName) 15 | { 16 | Queue = new DatabaseQueue(this); 17 | } 18 | 19 | public async Task SaveTransaction(VaultItem item, ETransactionType type) 20 | { 21 | var transaction = VaultTransaction.FromItem(item, type); 22 | await InsertAsync(transaction); 23 | } 24 | 25 | public async Task Wipe(ulong playerID, string vaultID) 26 | { 27 | var transaction = VaultTransaction.FromWipe(playerID, vaultID); 28 | await InsertAsync(transaction); 29 | } 30 | 31 | public async Task Add(ulong playerID, string vaultID, Item item, byte rot, byte x, byte y) 32 | { 33 | var transaction = VaultTransaction.FromItem(playerID, vaultID, item, rot, x, y, ETransactionType.Add); 34 | await InsertAsync(transaction); 35 | } 36 | 37 | public async Task Remove(ulong playerID, string vaultID, byte x, byte y) 38 | { 39 | var transaction = VaultTransaction.Remove(playerID, vaultID, x, y); 40 | await InsertAsync(transaction); 41 | } 42 | 43 | public async Task Update(ulong playerID, string vaultID, ItemJar jar) 44 | { 45 | var transaction = new VaultTransaction() 46 | { 47 | Date = DateTime.Now, 48 | ItemID = jar.item.id, 49 | PlayerID = playerID, 50 | VaultID = vaultID, 51 | Amount = jar.item.amount, 52 | Quality = jar.item.quality, 53 | Rot = jar.rot, 54 | State = jar.item.state, 55 | X = jar.x, 56 | Y = jar.y, 57 | Type = ETransactionType.Update 58 | }; 59 | await InsertAsync(transaction); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /SherbetVaults/Database/Tables/VaultItemsTable.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SDG.Unturned; 3 | using SherbetVaults.Database.Models; 4 | using SherbetVaults.Models.Config; 5 | using SherbetVaults.Models.Data; 6 | using ShimmyMySherbet.MySQL.EF.Core; 7 | 8 | namespace SherbetVaults.Database.Tables 9 | { 10 | public class VaultItemsTable : DatabaseTable 11 | { 12 | public SherbetVaultsPlugin Plugin { get; } 13 | 14 | public VaultItemsTable(SherbetVaultsPlugin plugin, string tableName) : base(tableName) 15 | { 16 | Plugin = plugin; 17 | } 18 | 19 | public async Task OpenVault(ulong playerID, string vaultID, VaultConfig config) 20 | { 21 | var items = await QueryAsync("SELECT * FROM @TABLE WHERE PlayerID=@0 AND VaultID=@1", playerID, vaultID); 22 | 23 | var vaultItems = new VaultItems(playerID, vaultID, Plugin); 24 | vaultItems.loadSize(config.Width, config.Height); 25 | 26 | foreach (var item in items) 27 | { 28 | vaultItems.loadItem(item.X, item.Y, item.Rot, item.GetItem()); 29 | } 30 | 31 | vaultItems.EnableSync(); 32 | 33 | return vaultItems; 34 | } 35 | 36 | public async Task AddItem(ulong playerID, string vaultID, Item item, byte rot, byte x, byte y) => 37 | await InsertUpdateAsync(VaultItem.Create(playerID, vaultID, item, rot, x, y)); 38 | 39 | public async Task AddItem(ulong playerID, string vaultID, ItemJar jar) => 40 | await AddItem(playerID, vaultID, jar.item, jar.rot, jar.x, jar.y); 41 | 42 | public async Task RemoveItem(ulong playerID, string vaultID, byte x, byte y) => 43 | await ExecuteNonQueryAsync("DELETE FROM @TABLE WHERE PlayerID=@0 AND VaultID=@1 AND X=@2 AND Y=@3", playerID, vaultID, x, y); 44 | 45 | public async Task Clear(ulong playerID, string vaultID) => 46 | await ExecuteNonQueryAsync("DELETE FROM @TABLE WHERE PlayerID=@0 AND VaultID=@1;", playerID, vaultID); 47 | 48 | public async Task UpdateItemState(ulong playerID, string vaultID, ItemJar jar) => 49 | await ExecuteNonQueryAsync("UPDATE @TABLE SET State=@4, Quality=@5, Amount=@6 WHERE PlayerID=@0 AND VaultID=@1 AND X=@2 AND Y = @3", 50 | playerID, vaultID, jar.x, jar.y, jar.item.state, jar.item.quality, jar.item.amount); 51 | } 52 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Net; 4 | using System.Text.RegularExpressions; 5 | using System.Threading.Tasks; 6 | using System.Xml; 7 | using Cysharp.Threading.Tasks; 8 | using Rocket.Core; 9 | using RocketExtensions.Models; 10 | using RocketExtensions.Utilities.ShimmyMySherbet.Extensions; 11 | using SDG.Unturned; 12 | 13 | namespace SherbetVaults.Models 14 | { 15 | public static class Extensions 16 | { 17 | public static readonly Regex MaxAliasMatch = new(@"^SherbetVaults\.MaxAliases\.[0-9]*$"); 18 | public static readonly Regex MaxAliasValueMatch = new(@"[0-9]*$"); 19 | public static async Task OpenTrashAsync(this LDMPlayer player) 20 | { 21 | var items = new Items(7); 22 | items.resize(15, 15); 23 | 24 | await ThreadTool.RunOnGameThreadAsync(() => 25 | { 26 | player.Player.inventory.updateItems(7, items); 27 | player.Player.inventory.sendStorage(); 28 | }); 29 | } 30 | 31 | public static async Task GetPlayerName(this ulong player) 32 | { 33 | var request = WebRequest.CreateHttp($"http://steamcommunity.com/profiles/{player}?xml=1"); 34 | request.Method = "GET"; 35 | using (var response = await request.GetResponseAsync()) 36 | using (var network = response.GetResponseStream()) 37 | using (var reader = new StreamReader(network)) 38 | { 39 | var xml = new XmlDocument(); 40 | xml.LoadXml(await reader.ReadToEndAsync()); 41 | return xml["profile"]?["steamID"]?.InnerText ?? "Unknown Player"; 42 | } 43 | } 44 | 45 | public static int GetMaxAliases(this LDMPlayer player) 46 | { 47 | var permissions = R.Permissions.GetPermissions(player.UnturnedPlayer); 48 | var weights = permissions.Where(x => MaxAliasMatch.IsMatch(x.Name)) 49 | .Select(x => MaxAliasValueMatch.Match(x.Name)) 50 | .Where(x => x.Success) 51 | .Select(x => int.Parse(x.Value)) 52 | .OrderByDescending(x => x) 53 | .ToArray(); 54 | if (weights.Length == 0) 55 | { 56 | return -1; 57 | } 58 | return weights[0]; 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /SherbetVaults/Commands/VaultCommand.cs: -------------------------------------------------------------------------------- 1 | using Cysharp.Threading.Tasks; 2 | using Rocket.API; 3 | using RocketExtensions.Models; 4 | using RocketExtensions.Plugins; 5 | using SherbetVaults.Models; 6 | using SherbetVaults.Models.Enums; 7 | 8 | namespace SherbetVaults.Commands 9 | { 10 | [CommandName("Vault")] 11 | [CommandInfo("Opens a vault", Syntax: "[Vault Name]")] 12 | [AllowedCaller(AllowedCaller.Player)] 13 | public class VaultCommand : RocketCommand 14 | { 15 | private VaultPlayerLock PlayerLock { get; } = new VaultPlayerLock(); 16 | 17 | public override async UniTask Execute(CommandContext context) 18 | { 19 | using (PlayerLock.TryObtainLock(context.PlayerID, out var valid)) 20 | { 21 | if (!valid) 22 | { 23 | // This player is already opening a vault. 24 | return; 25 | } 26 | 27 | var targetVault = context.Arguments.Get(0, defaultValue: string.Empty, paramName: "Vault Name"); 28 | 29 | var (vaultConfig, availability) = await Plugin.VaultSelector.GetVault(context.LDMPlayer, targetVault); 30 | 31 | switch (availability) 32 | { 33 | case EVaultAvailability.BadVaultID: 34 | await context.ReplyKeyAsync("Vault_Fail_NotFound", targetVault); 35 | return; 36 | 37 | case EVaultAvailability.NotAllowed: 38 | await context.ReplyKeyAsync("Vault_Fail_NoPermission", targetVault); 39 | return; 40 | 41 | case EVaultAvailability.NoVaults: 42 | case EVaultAvailability.NoAllowedVaults: 43 | await context.ReplyKeyAsync("Vaults_No_Vaults"); 44 | return; 45 | } 46 | 47 | if (vaultConfig == null) 48 | { 49 | await context.ReplyKeyAsync("Vault_Fail_CannotLoad", targetVault); 50 | return; 51 | } 52 | 53 | var vault = await Plugin.VaultManager.GetVault(context.PlayerID, vaultConfig.VaultID); 54 | 55 | if (vault == null) 56 | { 57 | await context.ReplyKeyAsync("Vault_Fail_CannotLoad", vaultConfig.VaultID); 58 | return; 59 | } 60 | 61 | await vault.OpenForPlayerAsync(context.LDMPlayer); 62 | } 63 | } 64 | 65 | private new SherbetVaultsPlugin Plugin => 66 | base.Plugin as SherbetVaultsPlugin; 67 | } 68 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /SherbetVaults/Database/DatabaseQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Rocket.Core.Logging; 6 | using SherbetVaults.Models; 7 | 8 | namespace SherbetVaults.Database 9 | { 10 | public class DatabaseQueue : IDisposable 11 | { 12 | private readonly ConcurrentQueue> m_DatabaseQueue = new(); 13 | private readonly SemaphoreSlim m_QueueSemaphore = new(0); 14 | 15 | private CancellationTokenSource m_TokenSource = new(); 16 | public CancellationToken Token => m_TokenSource.Token; 17 | 18 | public T Table { get; } 19 | 20 | public DatabaseQueue(T table) 21 | { 22 | Table = table; 23 | } 24 | 25 | public void Enqueue(AsyncDatabaseAction action) 26 | { 27 | m_DatabaseQueue.Enqueue(action); 28 | m_QueueSemaphore.Release(); 29 | } 30 | 31 | public void FlushQueue() 32 | { 33 | SpinWait.SpinUntil(() => m_DatabaseQueue.IsEmpty); 34 | } 35 | 36 | public void StopWorker() 37 | { 38 | m_TokenSource.Cancel(); 39 | } 40 | 41 | public void StartWorker() 42 | { 43 | if (Token.IsCancellationRequested) 44 | { 45 | m_TokenSource.Dispose(); 46 | m_TokenSource = new CancellationTokenSource(); 47 | } 48 | Task.Run(RunDatabaseQueue); 49 | } 50 | 51 | private async Task RunDatabaseQueue() 52 | { 53 | const int maxRetries = 3; 54 | const int retryDelay = 1000; 55 | 56 | while (!Token.IsCancellationRequested) 57 | { 58 | await m_QueueSemaphore.WaitAsync(Token); 59 | if (m_DatabaseQueue.TryDequeue(out var action)) 60 | { 61 | int remainingRetries = maxRetries; 62 | 63 | RetryLabel: 64 | 65 | try 66 | { 67 | await action(Table); 68 | } 69 | catch (Exception ex) 70 | { 71 | Logger.LogWarning($"Failed to write database action: {ex.Message}"); 72 | Logger.LogWarning($"Table Type: {typeof(T).FullName}"); 73 | Logger.LogWarning(ex.StackTrace); 74 | 75 | if (remainingRetries > 0) 76 | { 77 | remainingRetries--; 78 | await Task.Delay(retryDelay); 79 | goto RetryLabel; 80 | } 81 | else 82 | { 83 | Logger.LogError($"Failed after all attempts. Skipping action."); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | 91 | public void Dispose() 92 | { 93 | StopWorker(); 94 | m_QueueSemaphore.Dispose(); 95 | m_TokenSource.Dispose(); 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /SherbetVaults/Database/Models/VaultTransaction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SDG.Unturned; 3 | using SherbetVaults.Models.Enums; 4 | using ShimmyMySherbet.MySQL.EF.Models; 5 | using ShimmyMySherbet.MySQL.EF.Models.TypeModel; 6 | 7 | namespace SherbetVaults.Database.Models 8 | { 9 | public class VaultTransaction 10 | { 11 | [SQLPrimaryKey, SQLAutoIncrement] 12 | public int TransactionID { get; set; } 13 | 14 | [SQLIndex] 15 | public ulong PlayerID { get; set; } 16 | 17 | [SQLSerialize(ESerializeFormat.JSON)] 18 | public ETransactionType Type { get; set; } 19 | 20 | public DateTime Date { get; set; } 21 | 22 | [SQLVarChar(64)] 23 | public string VaultID { get; set; } 24 | 25 | public byte X { get; set; } 26 | public byte Y { get; set; } 27 | public ushort ItemID { get; set; } 28 | public byte Rot { get; set; } 29 | public byte Quality { get; set; } 30 | public byte Amount { get; set; } 31 | public byte[] State { get; set; } 32 | 33 | public static VaultTransaction Remove(ulong playerID, string vaultID, byte x, byte y) 34 | { 35 | return new VaultTransaction() 36 | { 37 | Date = DateTime.Now, 38 | ItemID = 0, 39 | PlayerID = playerID, 40 | VaultID = vaultID, 41 | Amount = 0, 42 | Quality = 0, 43 | Rot = 0, 44 | State = new byte[0], 45 | X = x, 46 | Y = y, 47 | Type = ETransactionType.Remove 48 | }; 49 | } 50 | 51 | public static VaultTransaction FromItem(VaultItem item, ETransactionType type) 52 | { 53 | return new VaultTransaction() 54 | { 55 | Date = DateTime.Now, 56 | ItemID = item.ItemID, 57 | PlayerID = item.PlayerID, 58 | VaultID = item.VaultID, 59 | Type = type, 60 | Amount = item.Amount, 61 | Quality = item.Quality, 62 | Rot = item.Rot, 63 | State = item.State, 64 | X = item.X, 65 | Y = item.Y 66 | }; 67 | } 68 | 69 | public static VaultTransaction FromItem(ulong playerID, string vaultID, Item item, byte rot, byte x, byte y, ETransactionType type) 70 | { 71 | return new VaultTransaction() 72 | { 73 | Date = DateTime.Now, 74 | ItemID = item.id, 75 | PlayerID = playerID, 76 | VaultID = vaultID, 77 | Type = type, 78 | Amount = item.amount, 79 | Quality = item.quality, 80 | Rot = rot, 81 | State = item.state, 82 | X = x, 83 | Y = y 84 | }; 85 | } 86 | 87 | public static VaultTransaction FromWipe(ulong playerID, string vaultID) 88 | { 89 | return new VaultTransaction() 90 | { 91 | Date = DateTime.Now, 92 | ItemID = 0, 93 | PlayerID = playerID, 94 | VaultID = vaultID, 95 | Amount = 0, 96 | Quality = 0, 97 | Rot = 0, 98 | State = Array.Empty(), 99 | X = 0, 100 | Y = 0, 101 | Type = ETransactionType.Wipe 102 | }; 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /ItemRestrictions.md: -------------------------------------------------------------------------------- 1 | # Item Restrictions 2 | SherbetVaults now comes with a rather powerful item whitelist/blacklist system. You can create groups that act as a whitelist or blacklist, and assign them to players using permissions. 3 | 4 | Restriction groups can be assigned to players with the `SherbetVaults.Restrict.Group` where `Group` is the GroupID. By default any restriction groups you create will not have an effect, you must assign players the permission. 5 | 6 | You can also assign different messages to be sent to the player when they try to store a blacklisted item in their vault. This can be done by changing the `TranslationKey` setting of the group and then adding a message for it to the translations file. 7 | 8 | Groups can have a weight assigned to them. The greater the weight the earlier it is applied. This means you can create a restriction group for e.g., Lead Staff that whitelists certain items that are blacklisted globally. 9 | 10 | Rather than just listing item IDs, you can use item selectors. As of this update, the following selectors are available: 11 | * ### **Single Item** 12 | Format: _ItemID_ 13 | Example: `15` 14 | Specifies a single item ID 15 | * ### **Item Range** 16 | Format: _ItemID_-_ItemID_ 17 | Example: `288-295` 18 | Specifies a range of items, inclusive. 19 | * ### **Item Slot** 20 | Format: Slot:_ItemEquipSlot_ 21 | Example: `Slot:Primary` 22 | Specifies items based on their equitable slot. E.g., `Primary` selects primary weapons. 23 | Available Options: None _(Cannot be equipped)_, Primary, Secondary, Tertiary _(equipable items, e.g., food, binoculars)_ 24 | * ### **Item Type** 25 | Format: Type:_ItemType_ 26 | Examples: `Type:Gun`, `Type:Medical`, `Type:Food` 27 | Specifies items based on their type. E.g., `Guns` selects all guns, `Food` selects food items. 28 | Small Selection of options: _Clothing Slots, e.g., Hat_, Gun, Food, Water, Trap, Melee, Magazine, _Gun Attachment slots, e.g., Optic_, Structure, and a lot more. A wiki page will be made for this. 29 | * ### **Item Spawn Table** 30 | Formats: Table:_TableName_, Table:_TableID_ 31 | Examples: `Table:Police*`, `Table:22` 32 | Specifies an items based on what spawn tables they appear in. Spawn tables are based on the map. 33 | When using a table name, specifying `Police` matches tables that are exactly named 'Police', so 'Police_Guns' wouldn't be matched. 34 | The * Symbol matches anything, So `Police*` would match 'Police' as well as 'Police_Guns' 35 | You can see a list of spawn tables for official maps Here 36 | * ### **Item Workshop** 37 | Format: Workshop:_WorkshopID_ 38 | Example: `Workshop:2136497468` 39 | Specifies items that originate from a workshop mod. E.g., `Workshop:2136497468` specifies Elver Items, since '2136497468' is Elver's workshop ID. These are the same workshop IDs you use in your server's WorkshopDownloadConfig.json 40 | 41 | 42 | ## Default Restriction Config 43 | 44 | 45 | ```xml 46 | 47 | false 48 | true 49 | false 50 | 51 | 52 | Restrictions_Blacklisted 53 | 54 | 1 55 | 255-259 56 | Type:Throwable 57 | Table:Police* 58 | Slot:Primary 59 | Workshop:2136497468 60 | 61 | 62 | 63 | 64 | ``` 65 | 66 | By default item restrictions are disabled. So they need to be enabled, and restriction groups assigned to players before it will work. 67 | 68 | -------------------------------------------------------------------------------- /SherbetVaults/Models/Utility/VaultSelector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Rocket.API; 5 | using RocketExtensions.Models; 6 | using SherbetVaults.Models.Config; 7 | using SherbetVaults.Models.Enums; 8 | 9 | namespace SherbetVaults.Models.Utility 10 | { 11 | public class VaultSelector 12 | { 13 | public SherbetVaultsPlugin Plugin { get; } 14 | 15 | public VaultSelector(SherbetVaultsPlugin plugin) 16 | { 17 | Plugin = plugin; 18 | } 19 | 20 | private VaultConfig GetDefaultVault(LDMPlayer player, out EVaultAvailability availability, string vaultID) 21 | { 22 | if (Plugin.VaultConfigs.Count == 0) 23 | { 24 | availability = EVaultAvailability.NoVaults; 25 | return null; 26 | } 27 | 28 | var allowedVaults = GetPlayerVaults(player); 29 | 30 | if (allowedVaults.Length == 0) 31 | { 32 | availability = EVaultAvailability.NoAllowedVaults; 33 | return null; 34 | } 35 | 36 | var vaultDefaulted = false; 37 | 38 | if (string.IsNullOrWhiteSpace(vaultID)) 39 | { 40 | if (Plugin.Config.LargestVaultIsDefault) 41 | { 42 | availability = EVaultAvailability.VaultAvailable; 43 | return GetLargestVault(player); 44 | } 45 | 46 | vaultDefaulted = true; 47 | vaultID = Plugin.Config.DefaultVault; 48 | } 49 | 50 | var vault = GetVaultConfig(vaultID); 51 | 52 | if (vault == null) 53 | { 54 | if (vaultDefaulted) 55 | { 56 | availability = EVaultAvailability.VaultAvailable; 57 | return allowedVaults.FirstOrDefault(); 58 | } 59 | else 60 | { 61 | availability = EVaultAvailability.BadVaultID; 62 | return null; 63 | } 64 | } 65 | 66 | if (vault.HasPermission(player)) 67 | { 68 | availability = EVaultAvailability.VaultAvailable; 69 | return vault; 70 | } 71 | else 72 | { 73 | availability = EVaultAvailability.NotAllowed; 74 | return null; 75 | } 76 | } 77 | 78 | public async Task<(VaultConfig vault, EVaultAvailability availability)> GetVault(LDMPlayer player, string vaultID, bool allowAliases = true) 79 | { 80 | if (allowAliases 81 | && Plugin.Config.VaultAliasesEnabled 82 | && player.UnturnedPlayer.HasPermission("SherbetVaults.Vault.Alias") 83 | && Plugin.Database.Aliases != null 84 | && !string.IsNullOrWhiteSpace(vaultID)) 85 | { 86 | var alias = await Plugin.Database.Aliases.GetAliasAsync(player.PlayerID, vaultID); 87 | if (alias != null) 88 | { 89 | vaultID = alias; 90 | } 91 | } 92 | var config = GetDefaultVault(player, out var availability, vaultID); 93 | return (config, availability); 94 | } 95 | 96 | public VaultConfig GetVaultConfig(string vaultID) => 97 | Plugin.VaultConfigs.FirstOrDefault(x => x.VaultID.Equals(vaultID, StringComparison.InvariantCultureIgnoreCase)); 98 | 99 | private VaultConfig GetLargestVault(LDMPlayer player) => 100 | GetPlayerVaults(player) 101 | .OrderByDescending(x => x.Width * x.Height) 102 | .FirstOrDefault(); 103 | 104 | public VaultConfig[] GetPlayerVaults(LDMPlayer player) => 105 | Plugin.VaultConfigs.Where(x => x.HasPermission(player)).ToArray(); 106 | } 107 | } -------------------------------------------------------------------------------- /SherbetVaults/Commands/VaultAliasCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Transactions; 4 | using Cysharp.Threading.Tasks; 5 | using Rocket.API; 6 | using RocketExtensions.Models; 7 | using RocketExtensions.Plugins; 8 | using SherbetVaults.Models; 9 | 10 | namespace SherbetVaults.Commands 11 | { 12 | [CommandName("VaultAlias")] 13 | [CommandInfo("Manages vault aliases", Syntax: "[Set/Remove/List] (vault) (alias)")] 14 | [AllowedCaller(AllowedCaller.Player)] 15 | public class VaultAliasCommand : RocketCommand 16 | { 17 | public override async UniTask Execute(CommandContext context) 18 | { 19 | if (!Plugin.Config.VaultAliasesEnabled || Plugin.Database.Aliases == null) 20 | { 21 | await context.ReplyKeyAsync("VaultAliases_Disabled"); 22 | return; 23 | } 24 | 25 | var option = context.Arguments.Get(0, paramName: "Option").ToLower(); 26 | 27 | switch (option) 28 | { 29 | case "set": 30 | var vaultID = context.Arguments.Get(1, paramName: "VaultName"); 31 | var vaultAlias = context.Arguments.Get(2, paramName: "Alias"); 32 | 33 | var vaultConfig = Plugin.VaultSelector.GetVaultConfig(vaultID); 34 | if (vaultConfig == null) 35 | { 36 | await context.ReplyKeyAsync("Vault_Fail_NotFound", vaultID); 37 | return; 38 | } 39 | 40 | if (!vaultConfig.HasPermission(context.LDMPlayer)) 41 | { 42 | await context.ReplyKeyAsync("Vault_Fail_NoPermission", vaultID); 43 | return; 44 | } 45 | 46 | var currentAliases = (await Plugin.Database.Aliases.GetAliasesAsync(context.PlayerID)) 47 | .Select(x => x.Alias) 48 | .ToArray(); 49 | 50 | if (!currentAliases.Contains(vaultAlias, StringComparer.InvariantCultureIgnoreCase)) 51 | { 52 | var aliasMax = context.LDMPlayer.GetMaxAliases(); 53 | 54 | if (aliasMax >= 0 && currentAliases.Length >= aliasMax) 55 | { 56 | await context.ReplyKeyAsync("VaultAliases_MaxReached", aliasMax); 57 | return; 58 | } 59 | } 60 | 61 | await Plugin.Database.Aliases.SetAliasAsync(context.PlayerID, vaultID, vaultAlias); 62 | 63 | var newAliases = currentAliases.Append(vaultAlias); 64 | 65 | await context.ReplyKeyAsync("VaultAliases_Set", string.Join(", ", newAliases), vaultConfig.VaultID); 66 | return; 67 | 68 | case "remove": 69 | var removeAlias = context.Arguments.Get(1, paramName: "Alias"); 70 | 71 | var removed = await Plugin.Database.Aliases.DeleteAliasAsync(context.PlayerID, removeAlias); 72 | 73 | if (removed) 74 | { 75 | await context.ReplyKeyAsync("VaultAliases_Removed", removeAlias); 76 | } 77 | else 78 | { 79 | await context.ReplyKeyAsync("VaultAliases_Remove_NotFound", removeAlias); 80 | } 81 | return; 82 | 83 | case "list": 84 | 85 | var aliases = await Plugin.Database.Aliases.GetAliasesAsync(context.PlayerID); 86 | 87 | var namesOnly = string.Join(", ", aliases.Select(x => x.Alias)); 88 | var withVault = string.Join(", ", aliases.Select(x => $"{x.Alias} ({x.VaultID})")); 89 | 90 | await context.ReplyKeyAsync("VaultAliases_List", namesOnly, withVault); 91 | 92 | return; 93 | 94 | default: 95 | await context.ReplyAsync($"Usage: [color=cyan]/{Name} {Syntax}[/color]"); 96 | return; 97 | } 98 | } 99 | 100 | private new SherbetVaultsPlugin Plugin => 101 | base.Plugin as SherbetVaultsPlugin; 102 | } 103 | } -------------------------------------------------------------------------------- /SherbetVaults/SherbetVaultsPlugin.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Rocket.API; 3 | using Rocket.Core; 4 | using Rocket.Core.Logging; 5 | using Rocket.Core.Plugins; 6 | using SDG.Unturned; 7 | using SherbetVaults.Database; 8 | using SherbetVaults.Models; 9 | using SherbetVaults.Models.Config; 10 | using SherbetVaults.Models.Restrictions; 11 | using SherbetVaults.Models.Utility; 12 | 13 | namespace SherbetVaults 14 | { 15 | public partial class SherbetVaultsPlugin : RocketPlugin 16 | { 17 | #region "Properties" 18 | 19 | public DatabaseManager Database { get; private set; } 20 | public VaultManager VaultManager { get; private set; } 21 | public VaultSelector VaultSelector { get; private set; } 22 | public RestrictionBuilder RestrictionBuilder { get; private set; } 23 | public RestrictionTool RestrictionTool { get; private set; } 24 | public ItemTableTool ItemTable { get; private set; } 25 | public List RestrictionGroups { get; private set; } 26 | 27 | public SherbetVaultsConfig Config => Configuration.Instance; 28 | public List VaultConfigs => Config.Vaults; 29 | 30 | 31 | 32 | #endregion "Properties" 33 | 34 | public override void LoadPlugin() 35 | { 36 | var version = typeof(SherbetVaultsPlugin).Assembly.GetName().Version; 37 | Logger.Log($"Loading Sherbet Vaults v{version}..."); 38 | try 39 | { 40 | LoadPluginInternal(); 41 | } 42 | catch (System.Exception ex) 43 | { 44 | Logger.LogError("Failed to load SherbetVaults"); 45 | Logger.LogError(""); 46 | Logger.LogError(ex.Message); 47 | Logger.LogError(ex.StackTrace); 48 | Logger.LogError(""); 49 | Logger.LogError("Support: https://discord.shimmymysherbet.com/"); 50 | 51 | } 52 | } 53 | 54 | private void LoadPluginInternal() 55 | { 56 | var version = typeof(SherbetVaultsPlugin).Assembly.GetName().Version; 57 | VaultManager = new VaultManager(this); 58 | RestrictionBuilder = new RestrictionBuilder(this); 59 | RestrictionTool = new RestrictionTool(this); 60 | Database = new DatabaseManager(this); 61 | VaultSelector = new VaultSelector(this); 62 | ItemTable = new ItemTableTool(); 63 | RestrictionGroups = new List(); 64 | 65 | if (!Database.Connect(out var errorMessage)) 66 | { 67 | Logger.Log($"Failed to connect to database: {errorMessage}"); 68 | UnloadPlugin(PluginState.Failure); 69 | return; 70 | } 71 | 72 | Database.CheckSchema(); 73 | Database.InitQueue(); 74 | 75 | RestrictionBuilder.Init(); 76 | 77 | Logger.Log("Loading restriction settings..."); 78 | RestrictionGroups = RestrictionBuilder.BuildGroups(Config.Restrictions, out var errors); 79 | 80 | if (errors == 0) 81 | { 82 | Logger.Log($"Loaded {RestrictionGroups.Count} Restriction Groups with no errors."); 83 | } 84 | else 85 | { 86 | Logger.LogWarning($"Loaded {RestrictionGroups.Count} Restriction Groups with {errors} error/s."); 87 | } 88 | 89 | Provider.onEnemyDisconnected += OnPlayerDisconnect; 90 | Level.onLevelLoaded += OnLevelLoaded; 91 | 92 | base.LoadPlugin(); 93 | Logger.Log($"Sherbet Vaults v{version} by ShimmyMySherbet#5694 Loaded!"); 94 | } 95 | 96 | public override void UnloadPlugin(PluginState state = PluginState.Unloaded) 97 | { 98 | Provider.onEnemyDisconnected -= OnPlayerDisconnect; 99 | Level.onLevelLoaded -= OnLevelLoaded; 100 | 101 | Database.Dispose(); 102 | 103 | base.UnloadPlugin(state); 104 | } 105 | 106 | private void OnLevelLoaded(int lvl) 107 | { 108 | ItemTable.ReInit(); 109 | } 110 | 111 | private void OnPlayerDisconnect(SteamPlayer player) 112 | { 113 | VaultManager.RemovePlayerCache(player.playerID.steamID.m_SteamID); 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SherbetVaults 2 | A lag-free reliable vault plugin for Unturned. 3 | 4 | Enjoy vaults hassle free and without random wipes or losses of data, or server stutters when opening a vault. 5 | 6 | Full translation support, with easy rich text support. 7 | 8 | ## Commands 9 | ### /Vault *{Vault}* 10 | Opens one of your vaults. 11 | 12 | ### /Vaults 13 | Lists what vaults you have access to. 14 | 15 | ### /Trash 16 | Opens a 15x15 trash storage, to discard unwanted items. 17 | 18 | ### /SpyVault [Player Name/ID] [Vault] 19 | Opens another player's vault, allowing staff members to search and modify the contents of vaults. 20 | 21 | ### /WipeVault [Player Name/ID] [Vault] 22 | Wipes the contents of another player's vault. Also says how many items were deleted. 23 | 24 | ### /VaultAlias [list/set/remove] (Vault) (Alias) 25 | Allows players to view, set, and delete aliases for vaults. 26 | 27 | Aliases do not create new vaults, but creates a name that redirects to an already existing vault. 28 | 29 | For more info on vault aliases, see Vault Aliases 30 | 31 | ## Configuration 32 | 33 | ### Database Settings 34 | The MySQL settings for the plugin to use. 35 | 36 | ### Vaults 37 | Defines vaults, with their ID, and size. 38 | 39 | Specific vaults are granted with the permission format `Vaults.{VaultID}`. Players also need the `Vault` and `Vaults` permissions to access any vaults. 40 | 41 | ### Largest Vault Is Default 42 | When enabled, sets the default vault for a player to the largest one they have access to. 43 | 44 | When disabled, the default vault is the one named 'default'. 45 | 46 | ### Database Table Prefix 47 | Allows the name of the plugin's database tables to be renamed. This allows for multiple servers to store different vaults in the same database. 48 | 49 | So you can change this value if you have 2 or more servers using the same database, and you don't want vaults to be synced across them. 50 | 51 | ### Default Config 52 | ```xml 53 | 54 | 55 | 56 | 127.0.0.1 57 | Username 58 | SuperSecretPassword 59 | Unturned 60 | 3306 61 | 62 | true 63 | default 64 | false 65 | SherbetVaults 66 | 67 | 68 | 69 | 70 | 71 | false 72 | true 73 | false 74 | 75 | 76 | Restrictions_Blacklisted 77 | 78 | 1 79 | 255-259 80 | Type:Throwable 81 | Table:Police* 82 | Slot:Primary 83 | Workshop:2136497468 84 | 85 | 86 | 87 | 88 | 89 | ``` 90 | 91 | ## Item Restrictions 92 | This plugin comes with a powerful form of item restrictions. This system provides many different item selectors, weights, whitelisting, blacklisting, custom messages, ect. 93 | 94 | See Item Restrictions for more info and documentation. 95 | 96 | Note, workshop selectors/restrictors are currently broken. A work around is to manually restrict the item ID ranges for the workshop mod (e.g., 15100-15200). 97 | 98 | 99 | ## Download 100 | Downloads can be found in the Releases page. 101 | 102 | ## Donate 103 | I work on the project free of charge for everyone to use. Though, if your feeling generous, consider buying me a coffee 104 | -------------------------------------------------------------------------------- /SherbetVaults/Models/Data/VaultItems.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Rocket.Core.Logging; 4 | using RocketExtensions.Models; 5 | using RocketExtensions.Utilities; 6 | using RocketExtensions.Utilities.ShimmyMySherbet.Extensions; 7 | using SDG.Unturned; 8 | using SherbetVaults.Database; 9 | 10 | namespace SherbetVaults.Models.Data 11 | { 12 | public class VaultItems : Items 13 | { 14 | public ulong PlayerID { get; } 15 | public string VaultID { get; } 16 | 17 | #pragma warning disable IDE1006 18 | /// 19 | /// Overrides to always be the external storage page. 20 | /// 21 | public new byte page => 7; 22 | #pragma warning restore IDE1006 23 | 24 | public DatabaseQueue Database { get; } 25 | public SherbetVaultsPlugin Plugin { get; } 26 | 27 | public bool SyncToDatabase { get; private set; } = false; 28 | 29 | public LDMPlayer Player { get; private set; } 30 | 31 | public VaultItems(ulong playerID, string vaultID, SherbetVaultsPlugin plugin) : base(7) 32 | { 33 | PlayerID = playerID; 34 | VaultID = vaultID; 35 | Plugin = plugin; 36 | Database = plugin.Database.Queue; 37 | onItemUpdated += ItemUpdated; 38 | onItemAdded += ItemAdded; 39 | onItemRemoved += ItemRemoved; 40 | onItemDiscarded += ItemDiscarded; 41 | } 42 | 43 | public virtual void EnableSync() 44 | { 45 | SyncToDatabase = true; 46 | } 47 | 48 | public virtual void DisableSync() 49 | { 50 | SyncToDatabase = false; 51 | } 52 | 53 | private void ItemUpdated(byte page, byte index, ItemJar jar) 54 | { 55 | if (!SyncToDatabase) 56 | return; 57 | 58 | Database.Enqueue(async (table) => 59 | { 60 | await table.VaultItems.UpdateItemState(PlayerID, VaultID, jar); 61 | await table.Transactions.Update(PlayerID, VaultID, jar); 62 | }); 63 | } 64 | 65 | private void ItemAdded(byte page, byte index, ItemJar jar) 66 | { 67 | if (!SyncToDatabase) 68 | return; 69 | 70 | if (!Plugin.RestrictionTool.IsPermitted(jar.item.id, Player, out var group)) 71 | { 72 | var message = Plugin.Translate(group.TranslationKey, group.GroupID).ReformatColor(); 73 | ThreadPool.QueueUserWorkItem(async (_) => await Player.MessageAsync(message)); 74 | items.Remove(jar); 75 | 76 | Player.Player.inventory.tryAddItem(jar.item, true, false); 77 | 78 | Logger.Log($"({Player.DisplayName}) tried to store blacklisted item ({jar.item.id}) in vault {VaultID}."); 79 | Logger.Log("Please disregard the following error message."); 80 | 81 | // Throw an error to prevent the item being stored client-side 82 | throw new VaultStoreDeniedException(); 83 | } 84 | 85 | Database.Enqueue(async (table) => 86 | { 87 | await table.VaultItems.AddItem(PlayerID, VaultID, jar); 88 | await table.Transactions.Add(PlayerID, VaultID, jar.item, jar.rot, jar.x, jar.y); 89 | }); 90 | } 91 | 92 | private void ItemRemoved(byte page, byte index, ItemJar jar) 93 | { 94 | if (!SyncToDatabase) 95 | return; 96 | 97 | Database.Enqueue(async (table) => 98 | { 99 | await table.VaultItems.RemoveItem(PlayerID, VaultID, jar.x, jar.y); 100 | await table.Transactions.Remove(PlayerID, VaultID, jar.x, jar.y); 101 | }); 102 | } 103 | 104 | private void ItemDiscarded(byte page, byte index, ItemJar jar) 105 | { 106 | ItemRemoved(page, index, jar); 107 | } 108 | 109 | public new void clear() 110 | { 111 | base.clear(); 112 | 113 | if (!SyncToDatabase) 114 | return; 115 | 116 | Database.Enqueue(async (table) => 117 | { 118 | await table.VaultItems.Clear(PlayerID, VaultID); 119 | }); 120 | } 121 | 122 | public VaultItems(byte newPage) : base(newPage) 123 | { 124 | } 125 | 126 | public void OpenForPlayer(LDMPlayer ldm) 127 | { 128 | Player = ldm; 129 | var player = ldm.Player; 130 | player.inventory.isStoring = true; 131 | player.inventory.storage = null; 132 | player.inventory.updateItems(7, this); 133 | player.inventory.sendStorage(); 134 | } 135 | 136 | public async Task OpenForPlayerAsync(LDMPlayer player) => 137 | await ThreadTool.RunOnGameThreadAsync(OpenForPlayer, player); 138 | } 139 | } -------------------------------------------------------------------------------- /SherbetVaults/Models/Restrictions/RestrictionBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text.RegularExpressions; 6 | using Rocket.Core.Logging; 7 | using SherbetVaults.Models.Config.Restrictions; 8 | 9 | namespace SherbetVaults.Models.Restrictions 10 | { 11 | public class RestrictionBuilder 12 | { 13 | public SherbetVaultsPlugin Plugin; 14 | 15 | public List<(Regex selector, Type type)> Restrictors { get; } = new List<(Regex selector, Type t)>(); 16 | 17 | public RestrictionBuilder(SherbetVaultsPlugin plugin) 18 | { 19 | Plugin = plugin; 20 | } 21 | 22 | public void Init() 23 | { 24 | var myTypes = typeof(RestrictionBuilder).Assembly.GetExportedTypes(); 25 | 26 | foreach (var type in myTypes.Where(x => typeof(IItemRestrictor).IsAssignableFrom(x))) 27 | { 28 | if (type.IsInterface || type.IsAbstract) 29 | { 30 | continue; 31 | } 32 | 33 | var selectorTag = type.GetCustomAttribute(); 34 | if (selectorTag == null) 35 | { 36 | continue; 37 | } 38 | 39 | Restrictors.Add((selectorTag.Regex, type)); 40 | } 41 | } 42 | 43 | public List BuildGroups(RestrictionSettings settings, out int errors) 44 | { 45 | var err = 0; 46 | var groups = new List(); 47 | foreach (var group in settings.Groups) 48 | { 49 | var restrictors = new List(); 50 | foreach (var selector in group.Selectors) 51 | { 52 | try 53 | { 54 | var restrictor = Build(selector); 55 | restrictors.Add(restrictor); 56 | } 57 | catch (BadSelectorException ex) 58 | { 59 | err++; 60 | Logger.LogError($"Bad Item Selector in Restriction Group {group.GroupID}: {ex.Message}"); 61 | } 62 | catch (Exception exc) 63 | { 64 | err++; 65 | Logger.LogError($"Exception building restrictor '{selector}'"); 66 | Logger.LogError(exc.Message); 67 | Logger.LogError(exc.StackTrace); 68 | } 69 | } 70 | 71 | groups.Add(new VaultRestrictionGroup(group, restrictors)); 72 | } 73 | errors = err; 74 | return groups; 75 | } 76 | 77 | public IItemRestrictor Build(string selector) 78 | { 79 | var restrictor = Restrictors.FirstOrDefault(x => x.selector.IsMatch(selector)); 80 | 81 | if (restrictor.type == null) 82 | { 83 | throw new BadSelectorException($"Unknown selector format '{selector}'"); 84 | } 85 | 86 | return Instantiate(restrictor.type, selector, Plugin); 87 | } 88 | 89 | public IItemRestrictor Instantiate(Type t, string selector, SherbetVaultsPlugin plugin) 90 | { 91 | var constructors = t.GetConstructors(); 92 | 93 | var availableObjects = new object[] 94 | { 95 | selector, 96 | plugin, 97 | plugin.Database, 98 | plugin.Config, 99 | plugin.ItemTable, 100 | plugin.VaultManager 101 | }; 102 | 103 | foreach (var construct in constructors) 104 | { 105 | var valid = true; 106 | var parameters = construct.GetParameters(); 107 | var arguments = new object[parameters.Length]; 108 | 109 | for (int i = 0; i < parameters.Length; i++) 110 | { 111 | var instance = availableObjects.FirstOrDefault 112 | (x => parameters[i].ParameterType.IsAssignableFrom(x.GetType())); 113 | 114 | if (instance == null) 115 | { 116 | valid = false; 117 | break; 118 | } 119 | arguments[i] = instance; 120 | } 121 | 122 | if (!valid) 123 | { 124 | continue; 125 | } 126 | try 127 | { 128 | return (IItemRestrictor)construct.Invoke(arguments); 129 | } 130 | catch (TargetInvocationException ex) 131 | { 132 | if (ex.InnerException != null) 133 | { 134 | throw ex.InnerException; 135 | } 136 | throw; 137 | } 138 | } 139 | return null; 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /SherbetVaults/SherbetVaults.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {1CE64AE8-6430-4B9D-A195-E2E6BB7B9850} 8 | Library 9 | Properties 10 | SherbetVaults 11 | SherbetVaults 12 | v4.8 13 | 512 14 | true 15 | enable 16 | 9.0 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\OpenMod.Unturned.Redist.3.22.1\lib\net461\Assembly-CSharp.dll 38 | False 39 | 40 | 41 | ..\packages\BouncyCastle.1.8.5\lib\BouncyCastle.Crypto.dll 42 | 43 | 44 | ..\packages\OpenMod.Unturned.Redist.3.22.1\lib\net461\com.rlabrecque.steamworks.net.dll 45 | False 46 | 47 | 48 | ..\packages\Google.Protobuf.3.19.4\lib\net45\Google.Protobuf.dll 49 | 50 | 51 | ..\packages\K4os.Compression.LZ4.1.2.6\lib\net46\K4os.Compression.LZ4.dll 52 | 53 | 54 | ..\packages\K4os.Compression.LZ4.Streams.1.2.6\lib\net46\K4os.Compression.LZ4.Streams.dll 55 | 56 | 57 | ..\packages\K4os.Hash.xxHash.1.0.6\lib\net46\K4os.Hash.xxHash.dll 58 | 59 | 60 | ..\packages\MySql.Data.8.0.29\lib\net48\MySql.Data.dll 61 | 62 | 63 | ..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll 64 | False 65 | 66 | 67 | ..\packages\ShimmyMySherbet.RocketExtensions.RocketMod.Redist.4.9.3.5\lib\net48\Rocket.API.dll 68 | False 69 | 70 | 71 | ..\packages\ShimmyMySherbet.RocketExtensions.RocketMod.Redist.4.9.3.5\lib\net48\Rocket.Core.dll 72 | False 73 | 74 | 75 | ..\packages\ShimmyMySherbet.RocketExtensions.RocketMod.Redist.4.9.3.5\lib\net48\Rocket.Unturned.dll 76 | False 77 | 78 | 79 | ..\packages\ShimmyMySherbet.RocketExtensions.2.1.2\lib\net48\RocketExtensions.dll 80 | 81 | 82 | ..\packages\OpenMod.Unturned.Redist.3.22.1\lib\net461\SDG.NetPak.Runtime.dll 83 | False 84 | 85 | 86 | ..\packages\OpenMod.Unturned.Redist.3.22.1\lib\net461\SDG.NetTransport.dll 87 | False 88 | 89 | 90 | ..\packages\ShimmyMySherbet.MySQL.EF.1.8.8\lib\net48\ShimmyMySherbet.MySQL.EF.dll 91 | 92 | 93 | 94 | ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll 95 | False 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll 107 | False 108 | 109 | 110 | 111 | ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll 112 | False 113 | 114 | 115 | ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll 116 | False 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | ..\packages\OpenMod.Unturned.Redist.3.22.1\lib\net461\SystemEx.dll 127 | False 128 | 129 | 130 | ..\packages\MySql.Data.8.0.29\lib\net48\Ubiety.Dns.Core.dll 131 | 132 | 133 | ..\packages\OpenMod.UniTask.2019.2.2.5\lib\net461\UniTask.dll 134 | True 135 | 136 | 137 | ..\packages\OpenMod.UniTask.2019.2.2.5\lib\net461\UniTask.Linq.dll 138 | True 139 | 140 | 141 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.dll 142 | False 143 | 144 | 145 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.AccessibilityModule.dll 146 | False 147 | 148 | 149 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.AIModule.dll 150 | False 151 | 152 | 153 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.AndroidJNIModule.dll 154 | False 155 | 156 | 157 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.AnimationModule.dll 158 | False 159 | 160 | 161 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.ARModule.dll 162 | False 163 | 164 | 165 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.AssetBundleModule.dll 166 | False 167 | 168 | 169 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.AudioModule.dll 170 | False 171 | 172 | 173 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.ClothModule.dll 174 | False 175 | 176 | 177 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.ClusterInputModule.dll 178 | False 179 | 180 | 181 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.ClusterRendererModule.dll 182 | False 183 | 184 | 185 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.CoreModule.dll 186 | False 187 | 188 | 189 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.CrashLog.dll 190 | False 191 | 192 | 193 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.CrashReportingModule.dll 194 | False 195 | 196 | 197 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.DirectorModule.dll 198 | False 199 | 200 | 201 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.DSPGraphModule.dll 202 | False 203 | 204 | 205 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.GameCenterModule.dll 206 | False 207 | 208 | 209 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.GridModule.dll 210 | False 211 | 212 | 213 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.HotReloadModule.dll 214 | False 215 | 216 | 217 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.ImageConversionModule.dll 218 | False 219 | 220 | 221 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.IMGUIModule.dll 222 | False 223 | 224 | 225 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.InputLegacyModule.dll 226 | False 227 | 228 | 229 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.InputModule.dll 230 | False 231 | 232 | 233 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.JSONSerializeModule.dll 234 | False 235 | 236 | 237 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.LocalizationModule.dll 238 | False 239 | 240 | 241 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.ParticleSystemModule.dll 242 | False 243 | 244 | 245 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.PerformanceReportingModule.dll 246 | False 247 | 248 | 249 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.Physics2DModule.dll 250 | False 251 | 252 | 253 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.PhysicsModule.dll 254 | False 255 | 256 | 257 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.ProfilerModule.dll 258 | False 259 | 260 | 261 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.ScreenCaptureModule.dll 262 | False 263 | 264 | 265 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.SharedInternalsModule.dll 266 | False 267 | 268 | 269 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.SpriteMaskModule.dll 270 | False 271 | 272 | 273 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.SpriteShapeModule.dll 274 | False 275 | 276 | 277 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.StreamingModule.dll 278 | False 279 | 280 | 281 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.SubstanceModule.dll 282 | False 283 | 284 | 285 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.SubsystemsModule.dll 286 | False 287 | 288 | 289 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.TerrainModule.dll 290 | False 291 | 292 | 293 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.TerrainPhysicsModule.dll 294 | False 295 | 296 | 297 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.TextCoreModule.dll 298 | False 299 | 300 | 301 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.TextRenderingModule.dll 302 | False 303 | 304 | 305 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.TilemapModule.dll 306 | False 307 | 308 | 309 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.TLSModule.dll 310 | False 311 | 312 | 313 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.UI.dll 314 | False 315 | 316 | 317 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.UIElementsModule.dll 318 | False 319 | 320 | 321 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.UIModule.dll 322 | False 323 | 324 | 325 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.UmbraModule.dll 326 | False 327 | 328 | 329 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.UNETModule.dll 330 | False 331 | 332 | 333 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.UnityAnalyticsModule.dll 334 | False 335 | 336 | 337 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.UnityConnectModule.dll 338 | False 339 | 340 | 341 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.UnityTestProtocolModule.dll 342 | False 343 | 344 | 345 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.UnityWebRequestAssetBundleModule.dll 346 | False 347 | 348 | 349 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.UnityWebRequestAudioModule.dll 350 | False 351 | 352 | 353 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.UnityWebRequestModule.dll 354 | False 355 | 356 | 357 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.UnityWebRequestTextureModule.dll 358 | False 359 | 360 | 361 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.UnityWebRequestWWWModule.dll 362 | False 363 | 364 | 365 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.VehiclesModule.dll 366 | False 367 | 368 | 369 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.VFXModule.dll 370 | False 371 | 372 | 373 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.VideoModule.dll 374 | False 375 | 376 | 377 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.VRModule.dll 378 | False 379 | 380 | 381 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.WindModule.dll 382 | False 383 | 384 | 385 | ..\packages\OpenMod.UnityEngine.Redist.2019.4.10\lib\net461\UnityEngine.XRModule.dll 386 | False 387 | 388 | 389 | ..\packages\OpenMod.Unturned.Redist.3.22.1\lib\net461\UnityEx.dll 390 | False 391 | 392 | 393 | ..\packages\MySql.Data.8.0.29\lib\net48\ZstdNet.dll 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | --------------------------------------------------------------------------------