C#:
// #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.Text;
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.11.6")]
[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 MinCapacity = 1;
private const int MaxCapacity = 48;
private const int SlotsPerRow = 6;
private const int ReclaimEntryMaxSize = 40;
private const float StandardLootDelay = 0.1f;
private const Item.Flag UnsearchableItemFlag = (Item.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 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 BackpackCapacityManager _backpackCapacityManager;
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 StoredData _storedData;
private readonly HashSet<ulong> _uiViewers = new HashSet<ulong>();
private Coroutine _saveRoutine;
[PluginReference]
private readonly Plugin Arena, BagOfHolding, BackpackButton, EventManager, ItemRetriever;
public Backpacks() {
_backpackCapacityManager = new BackpackCapacityManager(this);
_backpackManager = new BackpackManager(this);
_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, MinCapacity, MaxCapacity);
_backpackCapacityManager.Init(_config);
PoolUtils.ResizePools();
_storedData = StoredData.Load();
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.ClearOnWipe.Enabled) return;
_backpackManager.ClearCache();
IEnumerable<string> fileNames;
try {
fileNames = Interface.Oxide.DataFileSystem.GetFiles(Name).Select(fn => {
return fn.Split(Path.DirectorySeparatorChar).Last().Replace(".json", string.Empty);
}
);
}
catch (DirectoryNotFoundException) {
// No backpacks to clear.return;
}
var skippedBackpackCount = 0;
foreach (var fileName in fileNames) {
ulong userId;
if (!ulong.TryParse(fileName, out userId)) continue;
var ruleset = _config.ClearOnWipe.GetForPlayer(fileName);
if (ruleset == null || ruleset.DisallowsAll) {
_backpackManager.ClearBackpackFile(userId);
continue;
}
if (ruleset.AllowsAll) continue;
var backpack = _backpackManager.GetBackpackIfExists(userId);
if (backpack == null) continue;
backpack.EraseContents(ruleset);
backpack.SaveIfChanged();
}
_backpackManager.ClearCache();
LogWarning($"New save created.Backpacks were wiped according to the config and player permissions.");
}
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;
case nameof(BagOfHolding) : {
NextTick(() => {
if (!plugin.IsLoaded) return;
_backpackManager.DiscoverBags(plugin);
}
);
break;
}
}
}
private void OnPluginUnloaded(Plugin plugin) {
_subscriberManager.RemoveSubscriber(plugin);
}
private void OnPlayerDisconnected(BasePlayer player) {
_backpackCapacityManager.ForgetCachedCapacity(player.userID);
_backpackManager.GetBackpackIfCached(player.userID)?.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.HasBackpackFile(player.userID) || permission.UserHasPermission(player.UserIDString, KeepOnDeathPermission)) return;
if (_config.EraseOnDeath) {
_backpackManager.TryEraseForPlayer(player.userID);
}
else if (_config.DropOnDeath) {
_backpackManager.Drop(player.userID, player.transform.position);
}
}
private void OnGroupPermissionGranted(string groupName, string perm) {
if (!perm.StartsWith("backpacks")) return;
if (perm.StartsWith(SizePermission) || perm.StartsWith(UsagePermission)) {
_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)) {
_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)?.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(GetBackpackCapacity)] = new Func<BasePlayer, int>(GetBackpackCapacity), [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(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);
if (backpack == null) return null;
return _backpackManager.Drop(player.userID, 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) != null;
}
public int GetBackpackCapacity(BasePlayer player) {
return _plugin._backpackCapacityManager.GetCapacity(player.userID, player.UserIDString);
}
public bool IsBackpackGathering(BasePlayer player) {
return _backpackManager.GetBackpackIfCached(player.userID)?.IsGathering ?? false;
}
public bool IsBackpackRetrieving(BasePlayer player) {
return _backpackManager.GetBackpackIfCached(player.userID)?.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 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);
}
[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_GetBackpackCapacity))]
public object API_GetBackpackCapacity(BasePlayer player) {
return ObjectCache.Get(_api.GetBackpackCapacity(player));
}
[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));
}
[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_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) {
player.Reply(GetMessage(player, "Backpack Fetch Syntax"));
return;
}
if (!VerifyCanOpenBackpack(basePlayer, basePlayer.userID)) return;
ItemDefinition itemDefinition;
if (!VerifyValidItem(player, args[0], out itemDefinition)) return;
int desiredAmount;
if (!int.TryParse(args[1], out desiredAmount) || desiredAmount < 1) {
player.Reply(GetMessage(player, "Invalid Item Amount"));
return;
}
var itemLocalizedName = itemDefinition.displayName.translated;
var backpack = _backpackManager.GetBackpack(basePlayer.userID);
var itemQuery = new ItemQuery {
ItemDefinition = itemDefinition
}
;
var quantityInBackpack = backpack.SumItems(ref itemQuery);
if (quantityInBackpack == 0) {
player.Reply(string.Format(GetMessage(player, "Item Not In Backpack"), itemLocalizedName));
return;
}
if (desiredAmount > quantityInBackpack) {
desiredAmount = quantityInBackpack;
}
var amountTransferred = backpack.FetchItems(basePlayer, ref itemQuery, desiredAmount);
if (amountTransferred <= 0) {
player.Reply(string.Format(GetMessage(player, "Fetch Failed"), itemLocalizedName));
return;
}
player.Reply(string.Format(GetMessage(player, "Items Fetched"), amountTransferred.ToString(), 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) {
player.Reply(GetMessage(player, "View Backpack Syntax"));
return;
}
string failureMessage;
var targetPlayer = FindPlayer(player, args[0], out failureMessage);
if (targetPlayer == null) {
player.Reply(failureMessage);
return;
}
var targetBasePlayer = targetPlayer.Object as BasePlayer;
var desiredOwnerId = targetBasePlayer?.userID ?? ulong.Parse(targetPlayer.Id);
OpenBackpack(basePlayer, IsKeyBindArg(args.LastOrDefault()), ParsePageArg(args.ElementAtOrDefault(1)), desiredOwnerId : desiredOwnerId );
}
// 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);
private void ToggleBackpackGUICommand(IPlayer player, string cmd, string[] args) {
BasePlayer basePlayer;
if (!VerifyPlayer(player, out basePlayer) || !VerifyHasPermission(player, GUIPermission)) return;
var enabledNow = _storedData.ToggleGuiButtonPreference(basePlayer.userID, _config.GUI.EnabledByDefault);
if (enabledNow) {
MaybeCreateButtonUi(basePlayer);
}
else {
DestroyButtonUi(basePlayer);
}
player.Reply(GetMessage(player, "Toggled Backpack GUI"));
}
[Command("backpack.togglegather")]
private void ToggleGatherCommand(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.toggleretrieve")]
private void ToggleRetrieveCommand(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 CreateItemContainer(int capacity, StorageContainer entityOwner) {
var container = new ItemContainer();
container.ServerInitialize(null, capacity);
container.GiveUID();
container.entityOwner = entityOwner;
return container;
}
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 void SendEffect(BasePlayer player, string effectPrefab) {
if (string.IsNullOrWhiteSpace(effectPrefab)) return;
_reusableEffect.Init(Effect.Type.Generic, player, 0, Vector3.zero, Vector3.forward);
_reusableEffect.pooledString = effectPrefab;
EffectNetwork.Send(_reusableEffect, player.net.connection);
}
private void CheckBackpackButtonPlugin() {
if (BackpackButton == null || _config.UsingDefaults || !_config.GUI.Enabled) return;
foreach (var player in BasePlayer.activePlayerList) {
DestroyButtonUi(player);
}
_config.GUI.Enabled = false;
SaveConfig();
LogWarning($"Disabled GUI button in the config because the {nameof(BackpackButton)} plugin was detected.");
}
private void RegisterAsItemSupplier() {
ItemRetriever?.Call("API_AddSupplier", this, new Dictionary<string, object> {
["FindPlayerItems"]
= new Action<BasePlayer, Dictionary<string, object>, List<Item>>((player, rawItemQuery, collect) => {
var backpack = _backpackManager.GetBackpackIfCached(player.userID);
if (backpack == null || !backpack.CanRetrieve || !backpack.CanAccess) return;
var itemQuery = ItemQuery.Parse(rawItemQuery);
backpack.FindItems(ref itemQuery, collect, forItemRetriever : true);
}
), ["FindPlayerAmmo"] = new Action<BasePlayer, AmmoTypes, List<Item>>((player, ammoType, collect) => {
var backpack = _backpackManager.GetBackpackIfCached(player.userID);
if (backpack == null || !backpack.CanRetrieve || !backpack.CanAccess) return;
backpack.FindAmmo(ammoType, collect, forItemRetriever : true);
}
), ["SumPlayerItems"] = new Func<BasePlayer, Dictionary<string, object>, int>((player, rawItemQuery) => {
var backpack = _backpackManager.GetBackpackIfCached(player.userID);
if (backpack == null || !backpack.CanRetrieve || !backpack.CanAccess) return 0;
var itemQuery = ItemQuery.Parse(rawItemQuery);
return backpack.SumItems(ref itemQuery, forItemRetriever : true);
}
), ["TakePlayerItems"] = new Func<BasePlayer, Dictionary<string, object>, int, List<Item>, int>((player, rawItemQuery, amount, collect) => {
var backpack = _backpackManager.GetBackpackIfCached(player.userID);
if (backpack == null || !backpack.CanRetrieve || !backpack.CanAccess) return 0;
var itemQuery = ItemQuery.Parse(rawItemQuery);
return backpack.TakeItems(ref itemQuery, amount, collect, forItemRetriever : true);
}
), ["SerializeForNetwork"] = new Action<BasePlayer, List<ProtoBuf.Item>>((player, saveList) => {
var backpack = _backpackManager.GetBackpackIfCached(player.userID);
// Don't check CanAccess here to save on performance.This may result in minor issues like the
// player attempting an action they can't perform.if (backpack == null || !backpack.CanRetrieve)
return;
backpack.SerializeForNetwork(saveList, forItemRetriever : true);
}
),
}
);
}
private IEnumerator SaveRoutine(bool async, bool keepInUseBackpacks) {
if (_storedData.SaveIfChanged() && async) yield return null;
yield return _backpackManager.SaveAllAndKill(async, keepInUseBackpacks);
}
private void RestartSaveRoutine(bool async, bool keepInUseBackpacks) {
if (_saveRoutine != null) {
ServerMgr.Instance.StopCoroutine(_saveRoutine);
}
ServerMgr.Instance?.StartCoroutine(SaveRoutine(async, keepInUseBackpacks));
}
private void OpenBackpackMaybeDelayed(BasePlayer looter, ItemContainer currentContainer, Backpack backpack, int pageIndex, bool isKeyBind) {
var pageCapacity = backpack.GetAllowedPageCapacityForLooter(looter.userID, pageIndex);
var delaySeconds = CalculateOpenDelay(currentContainer, pageCapacity, isKeyBind);
if (delaySeconds > 0) {
if (currentContainer != null) {
looter.EndLooting();
looter.inventory.loot.SendImmediate();
}
var ownerId2 = backpack.OwnerId;
var looter2 = looter;
var pageIndex2 = pageIndex;
timer.Once(delaySeconds, () => _backpackManager.TryOpenBackpackPage(looter2, ownerId2, pageIndex2));
return;
}
_backpackManager.TryOpenBackpackPage(looter, backpack.OwnerId, pageIndex);
}
private void OpenBackpack(BasePlayer looter, bool isKeyBind, int desiredPageIndex = -1, bool forward = true, bool wrapAround = true, ulong desiredOwnerId = 0) {
var playerLoot = looter.inventory.loot;
var lootingContainer = playerLoot.containers.FirstOrDefault();
if (lootingContainer != null) {
Backpack currentBackpack;
int currentPageIndex;
if (_backpackManager.IsBackpack(lootingContainer, out currentBackpack, out currentPageIndex) && (currentBackpack.OwnerId == desiredOwnerId || desiredOwnerId == 0)) {
var nextPageIndex = currentBackpack.DetermineNextPageIndexForLooter(looter.userID, currentPageIndex, desiredPageIndex, forward, wrapAround, requireContents : false);
if (nextPageIndex == currentPageIndex) {
if (!wrapAround) {
// Close the backpack.looter.EndLooting();
ClosePlayerInventory(looter);
}
return;
}
var nextPageCapacity = currentBackpack.GetAllowedPageCapacityForLooter(looter.userID, nextPageIndex);
if (nextPageCapacity > lootingContainer.capacity) {
playerLoot.Clear();
playerLoot.SendImmediate();
{
var backpack2 = currentBackpack;
var looter2 = looter;
var pageIndex2 = desiredPageIndex;
timer.Once(StandardLootDelay, () => backpack2.TryOpen(looter2, pageIndex2));
}
return;
}
currentBackpack.SwitchToPage(looter, nextPageIndex);
return;
}
var parent = lootingContainer.parent?.parent;
if (parent != null && _backpackManager.IsBackpack(parent, out currentBackpack, out currentPageIndex) && (currentBackpack.OwnerId == desiredOwnerId || desiredOwnerId == 0)) {
// Player is looting a child container of the target backpack, so open the current page.OpenBackpackMaybeDelayed(looter, lootingContainer, currentBackpack, currentPageIndex, isKeyBind);
return;
}
}
// At this point, player is not looting, looting a different backpack, or looting a different container.if (desiredOwnerId == 0)
{
desiredOwnerId = looter.userID;
}
var backpack = _backpackManager.GetBackpack(desiredOwnerId);
desiredPageIndex = backpack.DetermineInitialPageForLooter(looter.userID, desiredPageIndex, forward);
OpenBackpackMaybeDelayed(looter, lootingContainer, backpack, desiredPageIndex, isKeyBind);
}
private bool ShouldDisplayGuiButton(BasePlayer player) {
return _storedData.GetGuiButtonPreference(player.userID) ?? _config.GUI.EnabledByDefault;
}
private IPlayer FindPlayer(IPlayer requester, string nameOrID, out string failureMessage) {
failureMessage = string.Empty;
ulong userId;
if (nameOrID.StartsWith("7656119") && nameOrID.Length == 17 && ulong.TryParse(nameOrID, out userId)) {
IPlayer player = covalence.Players.All.FirstOrDefault(p => p.Id == nameOrID);
if (player == null) {
failureMessage = string.Format(GetMessage(requester, "User ID not Found"), nameOrID);
}
return player;
}
var foundPlayers = new List<IPlayer>();
foreach (var player in covalence.Players.All) {
if (player.Name.Equals(nameOrID, StringComparison.InvariantCultureIgnoreCase)) return player;
if (player.Name.ToLower().Contains(nameOrID.ToLower())) {
foundPlayers.Add(player);
}
}
switch (foundPlayers.Count) {
// ... [Code preview truncated due to length. Full source in attached file] ...