C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
// #define DEBUG_DROP_ON_DEATH
// #define DEBUG_POOLING
// #define DEBUG_BACKPACK_LIFECYCLE
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Facepunch;
using Network;
using Newtonsoft.Json.Converters;
using Oxide.Core.Configuration;
using Oxide.Core.Libraries;
using Rust;
using UnityEngine;
using UnityEngine.UI;
using Time = UnityEngine.Time;
namespace Oxide.Plugins {
[Info("Backpacks", "WhiteThunder", "3.13.5")]
[Description("Allows players to have a Backpack which provides them extra inventory space.")]
internal class Backpacks : CovalencePlugin {
#region Fields
private static int _maxCapacityPerPage = 48;
private const int MinRows = 1;
private const int MaxRows = 8;
private const int MinContainerCapacity = 1;
private const int MaxContainerCapacity = 48;
private const int SlotsPerRow = 6;
private const int ReclaimEntryMaxSize = 40;
private const float StandardLootDelay = 0.1f;
private const Item.Flag SearchableItemFlag = (Item.Flag)(1 << 24);
private const Item.Flag UnsearchableItemFlag = (Item.Flag)(1 << 25);
private const ItemDefinition.Flag SearchableItemDefinitionFlag = (ItemDefinition.Flag)(1 << 24);
private const string UsagePermission = "backpacks.use";
private const string SizePermission = "backpacks.size";
private const string GUIPermission = "backpacks.gui";
private const string FetchPermission = "backpacks.fetch";
private const string GatherPermission = "backpacks.gather";
private const string RetrievePermission = "backpacks.retrieve";
private const string AdminPermission = "backpacks.admin";
private const string CapacityProfilePermission = "backpacks.size.profile";
private const string KeepOnDeathPermission = "backpacks.keepondeath";
private const string LegacyKeepOnWipePermission = "backpacks.keeponwipe";
private const string LegacyNoBlacklistPermission = "backpacks.noblacklist";
private const string CoffinPrefab = "assets/prefabs/misc/halloween/coffin/coffinstorage.prefab";
private const string DroppedBackpackPrefab = "assets/prefabs/misc/item drop/item_drop_backpack.prefab";
private const string ResizableLootPanelName = "generic_resizable";
private const int SaddleBagItemId = 1400460850;
private readonly CapacityManager _capacityManager;
private readonly BackpackManager _backpackManager;
private readonly SubscriberManager _subscriberManager = new SubscriberManager();
private ProtectionProperties _immortalProtection;
private Effect _reusableEffect = new Effect();
private string _cachedButtonUi;
private readonly ApiInstance _api;
private Configuration _config;
private PreferencesData _preferencesData;
private CapacityData _capacityData;
private readonly HashSet<ulong> _uiViewers = new HashSet<ulong>();
private Coroutine _saveRoutine;
private static FieldInfo _groundWatchFailsField = typeof(GroundWatch).GetField("fails", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
[PluginReference]
private readonly Plugin Arena, BackpackButton, EventManager, ItemRetriever;
public Backpacks() {
_backpackManager = new BackpackManager(this);
_capacityManager = new CapacityManager(this, _backpackManager);
_api = new ApiInstance(this);
}
#endregion
#region Hooks
private void Init() {
permission.RegisterPermission(UsagePermission, this);
permission.RegisterPermission(GUIPermission, this);
permission.RegisterPermission(FetchPermission, this);
permission.RegisterPermission(GatherPermission, this);
permission.RegisterPermission(RetrievePermission, this);
permission.RegisterPermission(AdminPermission, this);
permission.RegisterPermission(KeepOnDeathPermission, this);
_config.Init(this);
_maxCapacityPerPage = Mathf.Clamp(_config.BackpackSize.MaxCapacityPerPage, MinContainerCapacity, MaxContainerCapacity);
PoolUtils.ResizePools();
_preferencesData = PreferencesData.Load();
_capacityData = CapacityData.Exists() ? CapacityData.Load() : new CapacityData();
_capacityManager.Init(_config, _capacityData);
Unsubscribe(nameof(OnPlayerSleep));
Unsubscribe(nameof(OnPlayerSleepEnded));
if (_config.GUI.Enabled) {
AddCovalenceCommand("backpackgui", nameof(ToggleBackpackGUICommand));
}
else {
Unsubscribe(nameof(OnPlayerConnected));
Unsubscribe(nameof(OnNpcConversationStart));
Unsubscribe(nameof(OnNpcConversationEnded));
}
}
private void OnServerInitialized() {
_immortalProtection = ScriptableObject.CreateInstance<ProtectionProperties>();
_immortalProtection.name = "BackpacksProtection";
_immortalProtection.Add(1);
CheckBackpackButtonPlugin();
RegisterAsItemSupplier();
if (_config.GUI.Enabled) {
Subscribe(nameof(OnPlayerSleep));
Subscribe(nameof(OnPlayerSleepEnded));
Subscribe(nameof(OnPlayerConnected));
Subscribe(nameof(OnNpcConversationStart));
Subscribe(nameof(OnNpcConversationEnded));
foreach (var player in BasePlayer.activePlayerList) {
MaybeCreateButtonUi(player);
}
}
}
private void Unload() {
UnityEngine.Object.Destroy(_immortalProtection);
RestartSaveRoutine(async : false, keepInUseBackpacks : false);
BackpackNetworkController.ResetNetworkGroupId();
foreach (var player in BasePlayer.activePlayerList) {
DestroyButtonUi(player);
}
PoolUtils.ResizePools(empty : true);
}
private void OnNewSave(string filename) {
if (_config.BackpackSize.DynamicSize.Enabled) {
_capacityData.Clear();
_capacityData.SaveIfChanged();
}
if (_config.ClearOnWipe.Enabled) {
_backpackManager.ClearCache();
IEnumerable<string> backpackFileNameList;
try {
backpackFileNameList = Interface.Oxide.DataFileSystem.GetFiles(Name).Select(fn => fn.Split(Path.DirectorySeparatorChar).Last().Replace(".json", string.Empty));
}
catch (DirectoryNotFoundException) {
// No backpacks to clear.return;
}
var retainedDueToContents = 0;
var retainedDueToPreferences = 0;
var deletedBackpackFiles = 0;
foreach (var backpackFileName in backpackFileNameList) {
ulong userId;
if (!ulong.TryParse(backpackFileName, out userId)) continue;
var backpack = _backpackManager.GetBackpackIfExists(userId);
if (backpack == null) continue;
backpack.EraseContents(_config.ClearOnWipe.GetForPlayer(backpackFileName));
// Only delete the backpack data file if it's empty and has no saved preferences.if (backpack.HasItems || backpack.HasPreferences)
{
backpack.SaveIfChanged();
if (backpack.HasItems) {
retainedDueToContents++;
}
else if (backpack.HasPreferences) {
retainedDueToPreferences++;
}
}
else {
_backpackManager.DeleteBackpackFile(userId);
deletedBackpackFiles++;
}
}
_backpackManager.ClearCache();
var logMessage = "New save created.Backpacks were wiped according to the config and player permissions.";
if (deletedBackpackFiles > 0) {
logMessage += $"\n- {deletedBackpackFiles} file(s) were deleted because those backpacks are now empty.";
}
if (retainedDueToContents > 0) {
logMessage += $"\n- {retainedDueToContents} file(s) were retained because those backpacks were not empty after applying the player's wipe ruleset.";
}
if (retainedDueToPreferences > 0) {
logMessage += $"\n- {retainedDueToPreferences} file(s) were retained even though those backpacks are empty because they contain player gather/retrieve preferences.";
}
LogWarning(logMessage);
}
}
private void OnServerSave() {
RestartSaveRoutine(async : true, keepInUseBackpacks : true);
}
private void OnPluginLoaded(Plugin plugin) {
switch (plugin.Name) {
case nameof(BackpackButton) : CheckBackpackButtonPlugin();
break;
case nameof(ItemRetriever) : RegisterAsItemSupplier();
break;
}
}
private void OnPluginUnloaded(Plugin plugin) {
_subscriberManager.RemoveSubscriber(plugin);
}
private void OnPlayerDisconnected(BasePlayer player) {
_capacityManager.ForgetCachedCapacity(player.userID.Get());
_backpackManager.GetBackpackIfCached(player.userID.Get())?.NetworkController?.Unsubscribe(player);
}
// Handle player death by normal means.private void OnEntityDeath(BasePlayer player, HitInfo info) =>
OnEntityKill(player);
// Handle player death while sleeping in a safe zone.private void OnEntityKill(BasePlayer player)
{
if (player.IsNpc) return;
DestroyButtonUi(player);
if (!_backpackManager.HasBackpack(player.userID.Get())) return;
if (permission.UserHasPermission(player.UserIDString, KeepOnDeathPermission)) {
#if DEBUG_DROP_ON_DEATH
LogWarning($"[DEBUG_DROP_ON_DEATH] [Player {player.UserIDString}] Backpack not dropped because the player has the {KeepOnDeathPermission} permission.");
#endif
return;
}
if (_config.EraseOnDeath) {
_backpackManager.TryEraseForPlayer(player.userID.Get());
}
else if (_config.DropOnDeath) {
_backpackManager.Drop(player.userID.Get(), player.transform.position);
}
else {
#if DEBUG_DROP_ON_DEATH
LogWarning($"[DEBUG_DROP_ON_DEATH] [Player {player.UserIDString}] Backpack not dropped because \"Drop on Death (true/false)\" is set to false in the config.");
#endif
}
}
private void OnGroupPermissionGranted(string groupName, string perm) {
if (!perm.StartsWith("backpacks")) return;
if (perm.StartsWith(SizePermission) || perm.StartsWith(UsagePermission) || perm.StartsWith(CapacityProfilePermission)) {
_backpackManager.HandleCapacityPermissionChangedForGroup(groupName);
}
else if (perm.StartsWith(RestrictionRuleset.FullPermissionPrefix) || perm.Equals(LegacyNoBlacklistPermission)) {
_backpackManager.HandleRestrictionPermissionChangedForGroup(groupName);
}
else if (perm.Equals(GatherPermission)) {
_backpackManager.HandleGatherPermissionChangedForGroup(groupName);
}
else if (perm.Equals(RetrievePermission)) {
_backpackManager.HandleRetrievePermissionChangedForGroup(groupName);
}
else if (_config.GUI.Enabled && perm.Equals(GUIPermission)) {
var groupName2 = groupName;
foreach (var player in BasePlayer.activePlayerList.Where(p => permission.UserHasGroup(p.UserIDString, groupName2))) {
CreateOrDestroyButtonUi(player);
}
}
}
private void OnGroupPermissionRevoked(string groupName, string perm) {
OnGroupPermissionGranted(groupName, perm);
}
private void OnUserPermissionGranted(string userId, string perm) {
if (!perm.StartsWith("backpacks")) return;
if (perm.StartsWith(SizePermission) || perm.StartsWith(UsagePermission) || perm.StartsWith(CapacityProfilePermission)) {
_backpackManager.HandleCapacityPermissionChangedForUser(userId);
}
else if (perm.StartsWith(RestrictionRuleset.FullPermissionPrefix) || perm.Equals(LegacyNoBlacklistPermission)) {
_backpackManager.HandleRestrictionPermissionChangedForUser(userId);
}
else if (perm.Equals(GatherPermission)) {
_backpackManager.HandleGatherPermissionChangedForUser(userId);
}
else if (perm.Equals(RetrievePermission)) {
_backpackManager.HandleRetrievePermissionChangedForUser(userId);
}
else if (_config.GUI.Enabled && perm.Equals(GUIPermission)) {
var player = BasePlayer.Find(userId);
if (player != null) {
CreateOrDestroyButtonUi(player);
}
}
}
private void OnUserPermissionRevoked(string userId, string perm) {
OnUserPermissionGranted(userId, perm);
}
private void OnUserGroupAdded(string userId, string groupName) {
_backpackManager.HandleGroupChangeForUser(userId);
}
private void OnUserGroupRemoved(string userId, string groupName) {
_backpackManager.HandleGroupChangeForUser(userId);
}
// Only subscribed while the GUI button is enabled.private void OnPlayerConnected(BasePlayer player) => MaybeCreateButtonUi(player);
private void OnPlayerRespawned(BasePlayer player) {
MaybeCreateButtonUi(player);
_backpackManager.GetBackpackIfCached(player.userID.Get())?.PauseGatherMode(1f);
}
// Only subscribed while the GUI button is enabled.private void OnPlayerSleepEnded(BasePlayer player) => OnPlayerRespawned(player);
// Only subscribed while the GUI button is enabled.private void OnPlayerSleep(BasePlayer player) => DestroyButtonUi(player);
// Only subscribed while the GUI button is enabled.private void OnNpcConversationStart(NPCTalking npcTalking, BasePlayer player, ConversationData conversationData)
{
// This delay can be removed in the future if an OnNpcConversationStarted hook is created.NextTick(() =>
{
// Verify the conversation started, since another plugin may have blocked it.if (!npcTalking.conversingPlayers.Contains(player))
return;
DestroyButtonUi(player);
}
);
}
// Only subscribed while the GUI button is enabled.private void OnNpcConversationEnded(NPCTalking npcTalking, BasePlayer player) => MaybeCreateButtonUi(player);
private void OnNetworkSubscriptionsUpdate(Network.Networkable networkable, List<Network.Visibility.Group> groupsToAdd, List<Network.Visibility.Group> groupsToRemove) {
if (groupsToRemove == null) return;
for (var i = groupsToRemove.Count - 1; i >= 0; i--) {
var group = groupsToRemove[i];
if (BackpackNetworkController.IsBackpackNetworkGroup(group)) {
// Prevent automatically unsubscribing from backpack network groups.// This allows the subscriptions to persist while players move around.groupsToRemove.Remove(group);
}
}
}
#endregion
#region API
private class ApiInstance {
public readonly Dictionary<string, object> ApiWrapper;
private readonly Backpacks _plugin;
private BackpackManager _backpackManager => _plugin._backpackManager;
public ApiInstance(Backpacks plugin) {
_plugin = plugin;
ApiWrapper = new Dictionary<string, object> {
[nameof(AddSubscriber)]
= new Action<Plugin, Dictionary<string, object>>(AddSubscriber), [nameof(RemoveSubscriber)] = new Action<Plugin>(RemoveSubscriber), [nameof(GetExistingBackpacks)] = new Func<Dictionary<ulong, ItemContainer>>(GetExistingBackpacks), [nameof(EraseBackpack)] = new Action<ulong>(EraseBackpack), [nameof(DropBackpack)] = new Func<BasePlayer, List<DroppedItemContainer>, DroppedItemContainer>(DropBackpack), [nameof(GetBackpackOwnerId)] = new Func<ItemContainer, ulong>(GetBackpackOwnerId), [nameof(IsBackpackLoaded)] = new Func<BasePlayer, bool>(IsBackpackLoaded), [nameof(IsDynamicCapacityEnabled)] = new Func<bool>(IsDynamicCapacityEnabled), [nameof(GetBackpackCapacity)] = new Func<BasePlayer, int>(GetBackpackCapacity), [nameof(GetBackpackInitialCapacity)] = new Func<BasePlayer, int>(GetBackpackInitialCapacity), [nameof(GetBackpackMaxCapacity)] = new Func<BasePlayer, int>(GetBackpackMaxCapacity), [nameof(AddBackpackCapacity)] = new Func<BasePlayer, int, int>(AddBackpackCapacity), [nameof(SetBackpackCapacity)] = new Func<BasePlayer, int, int>(SetBackpackCapacity), [nameof(IsBackpackGathering)] = new Func<BasePlayer, bool>(IsBackpackGathering), [nameof(IsBackpackRetrieving)] = new Func<BasePlayer, bool>(IsBackpackRetrieving), [nameof(GetBackpackContainer)] = new Func<ulong, ItemContainer>(GetBackpackContainer), [nameof(GetBackpackItemAmount)] = new Func<ulong, int, ulong, int>(GetBackpackItemAmount), [nameof(TryOpenBackpack)] = new Func<BasePlayer, ulong, bool>(TryOpenBackpack), [nameof(TryOpenBackpackContainer)] = new Func<BasePlayer, ulong, ItemContainer, bool>(TryOpenBackpackContainer), [nameof(TryOpenBackpackPage)] = new Func<BasePlayer, ulong, int, bool>(TryOpenBackpackPage), [nameof(SumBackpackItems)] = new Func<ulong, Dictionary<string, object>, int>(SumBackpackItems), [nameof(CountBackpackItems)] = new Func<ulong, Dictionary<string, object>, int>(CountBackpackItems), [nameof(TakeBackpackItems)] = new Func<ulong, Dictionary<string, object>, int, List<Item>, int>(TakeBackpackItems), [nameof(MutateBackpackItems)] = new Func<ulong, Dictionary<string, object>, Dictionary<string, object>, int>(MutateBackpackItems), [nameof(TryDepositBackpackItem)] = new Func<ulong, Item, bool>(TryDepositBackpackItem), [nameof(WriteBackpackContentsFromJson)] = new Action<ulong, string>(WriteBackpackContentsFromJson), [nameof(ReadBackpackContentsAsJson)] = new Func<ulong, string>(ReadBackpackContentsAsJson),
}
;
}
public void AddSubscriber(Plugin plugin, Dictionary<string, object> spec) {
if (plugin == null) throw new ArgumentNullException(nameof(plugin));
if (spec == null) throw new ArgumentNullException(nameof(spec));
_plugin._subscriberManager.AddSubscriber(plugin, spec);
}
public void RemoveSubscriber(Plugin plugin) {
if (plugin == null) throw new ArgumentNullException(nameof(plugin));
_plugin._subscriberManager.RemoveSubscriber(plugin);
}
public Dictionary<ulong, ItemContainer> GetExistingBackpacks() {
return _backpackManager.GetAllCachedContainers();
}
public void EraseBackpack(ulong userId) {
_backpackManager.TryEraseForPlayer(userId);
}
public DroppedItemContainer DropBackpack(BasePlayer player, List<DroppedItemContainer> collect) {
var backpack = _backpackManager.GetBackpackIfExists(player.userID.Get());
if (backpack == null) return null;
return _backpackManager.Drop(player.userID.Get(), player.transform.position, collect);
}
public ulong GetBackpackOwnerId(ItemContainer container) {
return _backpackManager.GetCachedBackpackForContainer(container)?.OwnerId ?? 0;
}
public bool IsBackpackLoaded(BasePlayer player) {
return _backpackManager.GetBackpackIfCached(player.userID.Get()) != null;
}
public bool IsDynamicCapacityEnabled() {
return _plugin._config.BackpackSize.DynamicSize.Enabled;
}
public int GetBackpackCapacity(BasePlayer player) {
return _plugin._capacityManager.GetCapacity(player.userID.Get(), player.UserIDString);
}
public int GetBackpackInitialCapacity(BasePlayer player) {
return _plugin._capacityManager.GetInitialCapacity(player.userID.Get(), player.UserIDString);
}
public int GetBackpackMaxCapacity(BasePlayer player) {
return _plugin._capacityManager.GetMaxCapacity(player.userID.Get(), player.UserIDString);
}
public int AddBackpackCapacity(BasePlayer player, int amount) {
return _plugin._capacityManager.AddCapacity(player, amount);
}
public int SetBackpackCapacity(BasePlayer player, int capacity) {
return _plugin._capacityManager.SetCapacity(player, capacity);
}
public bool IsBackpackGathering(BasePlayer player) {
return _backpackManager.GetBackpackIfCached(player.userID.Get())?.IsGathering ?? false;
}
public bool IsBackpackRetrieving(BasePlayer player) {
return _backpackManager.GetBackpackIfCached(player.userID.Get())?.IsRetrieving ?? false;
}
public ItemContainer GetBackpackContainer(ulong ownerId) {
return _backpackManager.GetBackpackIfExists(ownerId)?.GetContainer(ensureContainer : true);
}
public int GetBackpackItemAmount(ulong ownerId, int itemId, ulong skinId) {
var itemQuery = new ItemQuery {
ItemId = itemId, SkinId = skinId
}
;
return _backpackManager.GetBackpackIfExists(ownerId)?.SumItems(ref itemQuery) ?? 0;
}
public bool TryOpenBackpack(BasePlayer player, ulong ownerId) {
return _backpackManager.TryOpenBackpack(player, ownerId);
}
public bool TryOpenBackpackContainer(BasePlayer player, ulong ownerId, ItemContainer container) {
return _backpackManager.TryOpenBackpackContainer(player, ownerId, container);
}
public bool TryOpenBackpackPage(BasePlayer player, ulong ownerId, int page) {
return _backpackManager.TryOpenBackpackPage(player, ownerId, page);
}
public int SumBackpackItems(ulong ownerId, Dictionary<string, object> dict) {
var itemQuery = ItemQuery.Parse(dict);
return _backpackManager.GetBackpackIfExists(ownerId)?.SumItems(ref itemQuery) ?? 0;
}
public int CountBackpackItems(ulong ownerId, Dictionary<string, object> dict) {
var backpack = _backpackManager.GetBackpackIfExists(ownerId);
if (backpack == null) return 0;
if (dict == null) return backpack.ItemCount;
var itemQuery = ItemQuery.Parse(dict);
return backpack.CountItems(ref itemQuery);
}
public int TakeBackpackItems(ulong ownerId, Dictionary<string, object> dict, int amount, List<Item> collect) {
var itemQuery = ItemQuery.Parse(dict);
return _backpackManager.GetBackpackIfExists(ownerId)?.TakeItems(ref itemQuery, amount, collect) ?? 0;
}
public int MutateBackpackItems(ulong ownerId, Dictionary<string, object> itemQueryDict, Dictionary<string, object> mutationRequestDict) {
var itemQuery = ItemQuery.Parse(itemQueryDict);
var mutationRequest = MutationRequest.Parse(mutationRequestDict);
return _backpackManager.GetBackpackIfExists(ownerId)?.MutateItems(ref itemQuery, ref mutationRequest) ?? 0;
}
public bool TryDepositBackpackItem(ulong ownerId, Item item) {
return _backpackManager.GetBackpack(ownerId).TryDepositItem(item);
}
public void WriteBackpackContentsFromJson(ulong ownerId, string json) {
_backpackManager.GetBackpack(ownerId).WriteContentsFromJson(json);
}
public string ReadBackpackContentsAsJson(ulong ownerId) {
return _backpackManager.GetBackpackIfExists(ownerId)?.SerializeContentsAsJson();
}
}
[HookMethod(nameof(API_GetApi))]
public Dictionary<string, object> API_GetApi() {
return _api.ApiWrapper;
}
[HookMethod(nameof(API_AddSubscriber))]
public void API_AddSubscriber(Plugin plugin, Dictionary<string, object> spec) {
_api.AddSubscriber(plugin, spec);
}
[HookMethod(nameof(API_RemoveSubscriber))]
public void API_RemoveSubscriber(Plugin plugin) {
_api.RemoveSubscriber(plugin);
}
// Deprecated, only returns container for first page.Use higher level APIs instead.[HookMethod(nameof(API_GetExistingBackpacks))]
public Dictionary<ulong, ItemContainer> API_GetExistingBackpacks() {
return _api.GetExistingBackpacks();
}
[HookMethod(nameof(API_EraseBackpack))]
public void API_EraseBackpack(ulong userId) {
_api.EraseBackpack(userId);
}
[HookMethod(nameof(API_DropBackpack))]
public DroppedItemContainer API_DropBackpack(BasePlayer player, List<DroppedItemContainer> collect = null) {
return _api.DropBackpack(player, collect);
}
[HookMethod(nameof(API_GetBackpackOwnerId))]
public object API_GetBackpackOwnerId(ItemContainer container) {
return ObjectCache.Get(_api.GetBackpackOwnerId(container));
}
[HookMethod(nameof(API_IsBackpackLoaded))]
public object API_IsBackpackLoaded(BasePlayer player) {
return ObjectCache.Get(_api.IsBackpackLoaded(player));
}
[HookMethod(nameof(API_IsDynamicCapacityEnabled))]
public object API_IsDynamicCapacityEnabled(BasePlayer player) {
return ObjectCache.Get(_api.IsDynamicCapacityEnabled());
}
[HookMethod(nameof(API_GetBackpackCapacity))]
public object API_GetBackpackCapacity(BasePlayer player) {
return ObjectCache.Get(_api.GetBackpackCapacity(player));
}
[HookMethod(nameof(API_GetBackpackInitialCapacity))]
public object API_GetBackpackInitialCapacity(BasePlayer player) {
return ObjectCache.Get(_api.GetBackpackInitialCapacity(player));
}
[HookMethod(nameof(API_GetBackpackMaxCapacity))]
public object API_GetBackpackMaxCapacity(BasePlayer player) {
return ObjectCache.Get(_api.GetBackpackMaxCapacity(player));
}
[HookMethod(nameof(API_AddBackpackCapacity))]
public object API_AddBackpackCapacity(BasePlayer player, int amount) {
return ObjectCache.Get(_api.AddBackpackCapacity(player, amount));
}
[HookMethod(nameof(API_SetBackpackCapacity))]
public object API_SetBackpackCapacity(BasePlayer player, int capacity) {
return ObjectCache.Get(_api.SetBackpackCapacity(player, capacity));
}
[HookMethod(nameof(API_IsBackpackGathering))]
public object API_IsBackpackGathering(BasePlayer player) {
return ObjectCache.Get(_api.IsBackpackGathering(player));
}
[HookMethod(nameof(API_IsBackpackRetrieving))]
public object API_IsBackpackRetrieving(BasePlayer player) {
return ObjectCache.Get(_api.IsBackpackRetrieving(player));
}
// Deprecated, only returns container for first page.Use higher level APIs instead.[HookMethod(nameof(API_GetBackpackContainer))]
public ItemContainer API_GetBackpackContainer(ulong ownerId) {
return _api.GetBackpackContainer(ownerId);
}
[HookMethod(nameof(API_GetBackpackItemAmount))]
public int API_GetBackpackItemAmount(ulong ownerId, int itemId, ulong skinId = 0) {
return _api.GetBackpackItemAmount(ownerId, itemId, skinId);
}
[HookMethod(nameof(API_TryOpenBackpack))]
public object API_TryOpenBackpack(BasePlayer player, ulong ownerId = 0) {
return ObjectCache.Get(_api.TryOpenBackpack(player, ownerId));
}
[HookMethod(nameof(API_TryOpenBackpackContainer))]
public object API_TryOpenBackpackContainer(BasePlayer player, ulong ownerId, ItemContainer container) {
return ObjectCache.Get(_api.TryOpenBackpackContainer(player, ownerId, container));
}
[HookMethod(nameof(API_TryOpenBackpackPage))]
public object API_TryOpenBackpackPage(BasePlayer player, ulong ownerId = 0, int page = 0) {
return ObjectCache.Get(_api.TryOpenBackpackPage(player, ownerId, page));
}
[HookMethod(nameof(API_SumBackpackItems))]
public object API_SumBackpackItems(ulong ownerId, Dictionary<string, object> dict) {
return ObjectCache.Get(_api.SumBackpackItems(ownerId, dict));
}
[HookMethod(nameof(API_CountBackpackItems))]
public object API_CountBackpackItems(ulong ownerId, Dictionary<string, object> dict) {
return ObjectCache.Get(_api.CountBackpackItems(ownerId, dict));
}
[HookMethod(nameof(API_TakeBackpackItems))]
public object API_TakeBackpackItems(ulong ownerId, Dictionary<string, object> dict, int amount, List<Item> collect) {
return ObjectCache.Get(_api.TakeBackpackItems(ownerId, dict, amount, collect));
}
[HookMethod(nameof(API_MutateBackpackItems))]
public object API_MutateBackpackItems(ulong ownerId, Dictionary<string, object> itemQueryDict, Dictionary<string, object> mutationRequestDict) {
return ObjectCache.Get(_api.MutateBackpackItems(ownerId, itemQueryDict, mutationRequestDict));
}
[HookMethod(nameof(API_TryDepositBackpackItem))]
public object API_TryDepositBackpackItem(ulong ownerId, Item item) {
return ObjectCache.Get(_api.TryDepositBackpackItem(ownerId, item));
}
[HookMethod(nameof(API_WriteBackpackContentsFromJson))]
public void API_WriteBackpackContentsFromJson(ulong ownerId, string json) {
_api.WriteBackpackContentsFromJson(ownerId, json);
}
[HookMethod(nameof(API_ReadBackpackContentsAsJson))]
public object API_ReadBackpackContentsAsJson(ulong ownerId) {
return _api.ReadBackpackContentsAsJson(ownerId);
}
#endregion
#region Exposed Hooks
private static class ExposedHooks {
public static object CanOpenBackpack(BasePlayer looter, ulong ownerId) {
return Interface.CallHook("CanOpenBackpack", looter, ObjectCache.Get(ownerId));
}
public static void OnBackpackClosed(BasePlayer looter, ulong ownerId, ItemContainer container) {
Interface.CallHook("OnBackpackClosed", looter, ObjectCache.Get(ownerId), container);
}
public static void OnBackpackOpened(BasePlayer looter, ulong ownerId, ItemContainer container) {
Interface.CallHook("OnBackpackOpened", looter, ObjectCache.Get(ownerId), container);
}
public static object CanDropBackpack(ulong ownerId, Vector3 position) {
return Interface.CallHook("CanDropBackpack", ObjectCache.Get(ownerId), position);
}
public static object CanEraseBackpack(ulong ownerId) {
return Interface.CallHook("CanEraseBackpack", ObjectCache.Get(ownerId));
}
public static object CanBackpackAcceptItem(ulong ownerId, ItemContainer container, Item item) {
return Interface.CallHook("CanBackpackAcceptItem", ObjectCache.Get(ownerId), container, item);
}
}
#endregion
#region Commands
[Command("backpack", "backpack.open")]
private void BackpackOpenCommand(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyCanInteract(player, out basePlayer) || !VerifyHasPermission(player, UsagePermission)) return;
OpenBackpack(basePlayer, IsKeyBindArg(args.LastOrDefault()), ParsePageArg(args.FirstOrDefault()), wrapAround : false );
}
[Command("backpack.next")]
private void BackpackNextCommand(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyCanInteract(player, out basePlayer) || !VerifyHasPermission(player, UsagePermission)) return;
OpenBackpack(basePlayer, IsKeyBindArg(args.LastOrDefault()) );
}
[Command("backpack.previous", "backpack.prev")]
private void BackpackPreviousCommand(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyCanInteract(player, out basePlayer) || !VerifyHasPermission(player, UsagePermission)) return;
OpenBackpack(basePlayer, IsKeyBindArg(args.LastOrDefault()), forward : false );
}
[Command("backpack.fetch")]
private void BackpackFetchCommand(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyCanInteract(player, out basePlayer) || !VerifyHasPermission(player, FetchPermission)) return;
if (args.Length < 2) {
ReplyToPlayer(player, LangEntry.BackpackFetchSyntax);
return;
}
if (!VerifyCanOpenBackpack(basePlayer, basePlayer.userID.Get())) return;
ItemDefinition itemDefinition;
if (!VerifyValidItem(player, args[0], out itemDefinition)) return;
int desiredAmount;
if (!int.TryParse(args[1], out desiredAmount) || desiredAmount < 1) {
ReplyToPlayer(player, LangEntry.InvalidItemAmount);
return;
}
var itemLocalizedName = itemDefinition.displayName.translated;
var backpack = _backpackManager.GetBackpack(basePlayer.userID.Get());
var itemQuery = new ItemQuery {
ItemDefinition = itemDefinition
}
;
var quantityInBackpack = backpack.SumItems(ref itemQuery);
if (quantityInBackpack == 0) {
ReplyToPlayer(player, LangEntry.ItemNotInBackpack, itemLocalizedName);
return;
}
if (desiredAmount > quantityInBackpack) {
desiredAmount = quantityInBackpack;
}
var amountTransferred = backpack.FetchItems(basePlayer, ref itemQuery, desiredAmount);
if (amountTransferred <= 0) {
ReplyToPlayer(player, LangEntry.FetchFailed, itemLocalizedName);
return;
}
ReplyToPlayer(player, LangEntry.ItemsFetched, amountTransferred, itemLocalizedName);
}
[Command("backpack.erase")]
private void EraseBackpackCommand(IPlayer player, string cmd, string[] args) {
if (!player.IsServer) return;
ulong userId;
if (args.Length < 1 || !ulong.TryParse(args[0], out userId)) {
player.Reply($"Syntax: {cmd} <id>");
return;
}
if (!_backpackManager.TryEraseForPlayer(userId)) {
LogWarning($"Player {userId.ToString()} has no backpack to erase.");
return;
}
LogWarning($"Erased backpack for player {userId.ToString()}.");
}
[Command("viewbackpack")]
private void ViewBackpackCommand(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyCanInteract(player, out basePlayer) || !VerifyHasPermission(player, AdminPermission)) return;
if (args.Length < 1) {
ReplyToPlayer(player, LangEntry.ViewBackpackSyntax);
return;
}
ulong targetPlayerId;
string targetPlayerIdString;
if (!VerifyTargetPlayer(player, args[0], out targetPlayerId, out targetPlayerIdString)) return;
OpenBackpack(basePlayer, IsKeyBindArg(args.LastOrDefault()), ParsePageArg(args.ElementAtOrDefault(1)), desiredOwnerId : targetPlayerId );
}
// Alias for older versions of Player Administration (which should ideally not be calling this method directly).private void ViewBackpack(BasePlayer player, string cmd, string[] args) =>
ViewBackpackCommand(player.IPlayer, cmd, args);
[Command("backpack.addsize")]
private void AddBackpackCapacityCommand(IPlayer player, string cmd, string[] args) {
if (!VerifyHasPermission(player, AdminPermission)) return;
int amount;
if (args.Length < 2 || !int.TryParse(args[1], out amount)) {
ReplyToPlayer(player, LangEntry.BackpackCapacitySyntax, cmd);
return;
}
ulong targetPlayerId;
string targetPlayerIdString;
if (!VerifyTargetPlayer(player, args[0], out targetPlayerId, out targetPlayerIdString) || !VerifyDynamicCapacityEnabled(player, targetPlayerIdString)) return;
var newCapacity = _capacityManager.AddCapacity(targetPlayerId, targetPlayerIdString, amount);
ReplyToPlayer(player, LangEntry.ChangeCapacitySuccess, targetPlayerId, newCapacity);
}
[Command("backpack.setsize")]
private void SetBackpackCapacityCommand(IPlayer player, string cmd, string[] args) {
if (!VerifyHasPermission(player, AdminPermission)) return;
int amount;
if (args.Length < 2 || !int.TryParse(args[1], out amount)) {
ReplyToPlayer(player, LangEntry.BackpackCapacitySyntax, cmd);
return;
}
ulong targetPlayerId;
string targetPlayerIdString;
if (!VerifyTargetPlayer(player, args[0], out targetPlayerId, out targetPlayerIdString) || !VerifyDynamicCapacityEnabled(player, targetPlayerIdString)) return;
var newCapacity = _capacityManager.SetCapacity(targetPlayerId, targetPlayerIdString, amount);
ReplyToPlayer(player, LangEntry.ChangeCapacitySuccess, targetPlayerId, newCapacity);
}
private void ToggleBackpackGUICommand(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyPlayer(player, out basePlayer) || !VerifyHasPermission(player, GUIPermission)) return;
var enabledNow = _preferencesData.ToggleGuiButtonPreference(basePlayer.userID.Get(), _config.GUI.EnabledByDefault);
if (enabledNow) {
MaybeCreateButtonUi(basePlayer);
}
else {
DestroyButtonUi(basePlayer);
}
ReplyToPlayer(player, LangEntry.ToggledBackpackGUI);
}
[Command("backpack.setgathermode")]
private void SetGatherCommand(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyCanInteract(player, out basePlayer) || !VerifyHasPermission(player, UsagePermission) || !VerifyHasPermission(player, GatherPermission)) return;
var backpack = _backpackManager.GetBackpack(basePlayer.userID.Get());
if (!backpack.CanGather) return;
GatherMode gatherMode;
if (args.Length < 1 || !TryParseGatherMode(basePlayer, args[0], out gatherMode)) {
ReplyToPlayer(player, LangEntry.SetGatherSyntax, cmd, GetGatherModeDisplayOptions(basePlayer));
return;
}
var oneBasedPageIndex = 1;
if (args.Length >= 2 && !IsKeyBindArg(args[1]) && !int.TryParse(args[1], out oneBasedPageIndex)) {
ReplyToPlayer(player, LangEntry.SetGatherSyntax, cmd, GetGatherModeDisplayOptions(basePlayer));
return;
}
if (oneBasedPageIndex < 1 || oneBasedPageIndex > backpack.PageCount) {
ReplyToPlayer(player, LangEntry.PageOutOfRange, backpack.PageCount);
return;
}
var pageIndex = oneBasedPageIndex - 1;
if (backpack.GetGatherModeForPage(pageIndex) == gatherMode) return;
backpack.SetGatherModeForPage(basePlayer, pageIndex, gatherMode);
ReplyToPlayer(player, LangEntry.SetGatherModeSuccess, oneBasedPageIndex, GetGatherModeDisplayString(basePlayer, gatherMode));
}
[Command("backpack.ui.togglegather")]
private void ToggleGatherUICommand(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyPlayer(player, out basePlayer)) return;
var lootingContainer = basePlayer.inventory.loot.containers.FirstOrDefault();
if (lootingContainer == null) return;
Backpack backpack;
int pageIndex;
if (!_backpackManager.IsBackpack(lootingContainer, out backpack, out pageIndex) || backpack.OwnerId != basePlayer.userID || !backpack.CanGather) return;
backpack.ToggleGatherMode(basePlayer, pageIndex);
}
[Command("backpack.ui.toggleretrieve")]
private void ToggleRetrieveUICommand(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyPlayer(player, out basePlayer)) return;
var lootingContainer = basePlayer.inventory.loot.containers.FirstOrDefault();
if (lootingContainer == null) return;
Backpack backpack;
int pageIndex;
if (!_backpackManager.IsBackpack(lootingContainer, out backpack, out pageIndex) || pageIndex > 31 || backpack.OwnerId != basePlayer.userID || !backpack.CanRetrieve) return;
backpack.ToggleRetrieve(basePlayer, pageIndex);
}
#endregion
#region Helper Methods
public static void LogDebug(string message) => Interface.Oxide.LogDebug($"[Backpacks] {message}");
public static void LogInfo(string message) => Interface.Oxide.LogInfo($"[Backpacks] {message}");
public static void LogWarning(string message) => Interface.Oxide.LogWarning($"[Backpacks] {message}");
public static void LogError(string message) => Interface.Oxide.LogError($"[Backpacks] {message}");
private static T[] ParseEnumList<T>(string[] list, string errorFormat) where T : struct {
var valueList = new List<T>(list?.Length ?? 0);
if (list != null) {
foreach (var itemName in list) {
T result;
if (Enum.TryParse(itemName, ignoreCase : true, result : out result)) {
valueList.Add(result);
}
else {
LogError(string.Format(errorFormat, itemName));
}
}
}
return valueList.ToArray();
}
private static bool IsKeyBindArg(string arg) {
return arg == "True";
}
private static int ParsePageArg(string arg) {
if (arg == null) return -1;
int pageIndex;
return int.TryParse(arg, out pageIndex) ? Math.Max(0, pageIndex - 1) : -1;
}
private static string DetermineLootPanelName(ItemContainer container) {
return (container.entityOwner as StorageContainer)?.panelName ?? (container.entityOwner as ContainerIOEntity)?.lootPanelName ?? (container.entityOwner as LootableCorpse)?.lootPanelName ?? (container.entityOwner as DroppedItemContainer)?.lootPanelName ?? (container.entityOwner as BaseRidableAnimal)?.lootPanelName ?? ResizableLootPanelName;
}
private static void ClosePlayerInventory(BasePlayer player) {
player.ClientRPCPlayer(null, player, "OnRespawnInformation");
}
private static float CalculateOpenDelay(ItemContainer currentContainer, int nextContainerCapacity, bool isKeyBind = false) {
if (currentContainer != null) {
// Can instantly switch to a smaller container.if (nextContainerCapacity <= currentContainer.capacity)
return 0;
// Can instantly switch to a generic resizable loot panel from a different loot panel.if (DetermineLootPanelName(currentContainer) != ResizableLootPanelName)
return 0;
// Need a short delay so the generic_resizable loot panel can be redrawn properly.return StandardLootDelay;
}
// Can open instantly since not looting and chat is assumed to be closed.if (isKeyBind)
return 0;
// Not opening via key bind, so the chat window may be open.// Must delay in case the chat is still closing or else the loot panel may close instantly.return StandardLootDelay;
}
private static void StartLooting(BasePlayer player, ItemContainer container, StorageContainer entitySource) {
if (player.CanInteract() && Interface.CallHook("CanLootEntity", player, entitySource) == null && player.inventory.loot.StartLootingEntity(entitySource, doPositionChecks : false)) {
player.inventory.loot.AddContainer(container);
player.inventory.loot.SendImmediate();
player.ClientRPCPlayer(null, player, "RPC_OpenLootPanel", entitySource.panelName);
}
}
private static ItemContainer GetRootContainer(Item item) {
var container = item.parent;
if (container == null) return null;
while (container.parent?.parent != null && container.parent != item) {
container = container.parent.parent;
}
return container;
}
private bool TryParseGatherMode(BasePlayer player, string arg, out GatherMode gatherMode) {
foreach (var enumValue in typeof(GatherMode).GetEnumValues()) {
var name = Enum.GetName(typeof(GatherMode), enumValue);
var gatherModeValue = (GatherMode)enumValue;
if (StringUtils.EqualsCaseInsensitive(arg, name)) {
gatherMode = gatherModeValue;
// ... [Code preview truncated due to length. Full source in attached file] ...