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.12.0")]
[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> 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;
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.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);
if (!backpack.CanGather)
return;
GatherMode gatherMode;
if (args.Length < 1 || !TryParseGatherMode(basePlayer, args[0], out gatherMode))
{
player.Reply(string.Format(GetMessage(basePlayer, "Set Gather Syntax"), cmd, GetGatherModeDisplayOptions(basePlayer)));
return;
}
var oneBasedPageIndex = 1;
if (args.Length >= 2 && !IsKeyBindArg(args[1]) && !int.TryParse(args[1], out oneBasedPageIndex))
{
player.Reply(string.Format(GetMessage(basePlayer, "Set Gather Syntax"), cmd, GetGatherModeDisplayOptions(basePlayer)));
return;
}
if (oneBasedPageIndex < 1 || oneBasedPageIndex > backpack.PageCount)
{
player.Reply(string.Format(GetMessage(basePlayer, "Page Out Of Range"), backpack.PageCount));
return;
}
var pageIndex = oneBasedPageIndex - 1;
if (backpack.GetGatherModeForPage(pageIndex) == gatherMode)
return;
backpack.SetGatherModeForPage(basePlayer, pageIndex, gatherMode);
player.Reply(string.Format(GetMessage(basePlayer, "Set Gather Mode Success"), 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 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 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;
return true;
}
var localizedName = GetMessage(player, GetGatherModeDisplayString(player, gatherModeValue));
if (StringUtils.EqualsCaseInsensitive(arg, localizedName, StringComparison.InvariantCultureIgnoreCase))
{
gatherMode = gatherModeValue;
return true;
}
}
gatherMode = GatherMode.None;
return false;
}
private string GetGatherModeDisplayOptions(BasePlayer player)
{
return string.Join("|", new List<string>
{
GetGatherModeDisplayString(player, GatherMode.All),
GetGatherModeDisplayString(player, GatherMode.Existing),
GetGatherModeDisplayString(player, GatherMode.None)
});
}
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);
}),
["SumPlayerIte
// ... [Code preview truncated due to length. Full source in attached file] ...