C#:
using Oxide.Core;
using Oxide.Core.Plugins;
using Rust;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Oxide.Plugins
{
[Info("Item Retriever", "WhiteThunder", "0.7.8")]
[Description("Allows players to build, craft, reload and more using items from external containers.")]
internal class ItemRetriever : CovalencePlugin
{
#region Fields
[PluginReference]
private readonly Plugin InstantCraft, SuperCrafter;
private const int InventorySize = 24;
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 int BlueprintItemId = -996920608;
// The CustomItemDefinitions plugin adds this flag to all custom item definitions.
// Custom items to be skipped when adding items to network messages, since they have invalid item ids.
private const ItemDefinition.Flag CustomItemDefinitionFlag = (ItemDefinition.Flag)128;
private static readonly object True = true;
private static readonly object False = false;
private readonly CustomItemDefinitionTracker _customItemDefinitionTracker = new();
private readonly SupplierManager _supplierManager;
private readonly ContainerManager _containerManager;
private readonly ApiInstance _api;
private readonly Dictionary<int, int> _overridenIngredients = new();
private readonly List<Item> _reusableItemList = new();
private Func<ItemDefinition, bool> _isBlockedByInstantCraft;
public ItemRetriever()
{
_api = new ApiInstance(this);
_supplierManager = new(_customItemDefinitionTracker);
_containerManager = new(_customItemDefinitionTracker);
}
#endregion
#region Hooks
private void OnServerInitialized()
{
RefreshInstantCraftCallback();
}
private void Unload()
{
_containerManager.RemoveContainers();
ObjectCache.Clear<int>();
ObjectCache.Clear<float>();
ObjectCache.Clear<ulong>();
ObjectCache.Clear<Item.Flag>();
}
private void OnPluginLoaded(Plugin plugin)
{
if (plugin.Name == nameof(InstantCraft))
{
RefreshInstantCraftCallback();
}
}
private void OnPluginUnloaded(Plugin plugin)
{
_supplierManager.RemoveSupplier(plugin);
_containerManager.RemoveContainers(plugin);
if (plugin.Name == nameof(InstantCraft))
{
RefreshInstantCraftCallback();
}
}
private void OnEntitySaved(BasePlayer player, BaseNetworkable.SaveInfo saveInfo)
{
SerializeForNetwork(player, saveInfo.msg.basePlayer.inventory.invMain);
}
private void OnInventoryNetworkUpdate(PlayerInventory inventory, ItemContainer container, ProtoBuf.UpdateItemContainer updatedItemContainer, PlayerInventory.Type inventoryType)
{
if (inventoryType != PlayerInventory.Type.Main)
return;
SerializeForNetwork(inventory.baseEntity, updatedItemContainer.container[0]);
}
private object OnInventoryItemsCount(PlayerInventory inventory, int itemId)
{
var basePlayer = inventory.baseEntity;
if (basePlayer.activeTalkingToNpc is LivestockVendor)
{
// Disable retrieving while talking to a livestock vendor, until PlayerInventory.UseAmount has been
// properly hooked, to prevent free purchases.
return null;
}
var itemQuery = new ItemIdQuery(itemId);
return ObjectCache.Get(SumPlayerItems(basePlayer, ref itemQuery, countPlayerWearables: true));
}
private object OnInventoryItemsTake(PlayerInventory inventory, List<Item> collect, int itemId, int amount)
{
var itemQuery = new ItemIdQuery(itemId);
return ObjectCache.Get(TakePlayerItems(inventory.baseEntity, ref itemQuery, amount, collect, includePlayerWearables: true));
}
private object OnInventoryItemsFind(PlayerInventory inventory, int itemId, List<Item> collect)
{
var itemQuery = new ItemIdQuery(itemId);
FindPlayerItems(inventory.baseEntity, ref itemQuery, collect, includePlayerWearables: true);
return False;
}
private object OnInventoryItemFind(PlayerInventory inventory, int itemId)
{
var itemQuery = new ItemIdQuery(itemId);
_reusableItemList.Clear();
FindPlayerItems(inventory.baseEntity, ref itemQuery, _reusableItemList, includePlayerWearables: true);
return _reusableItemList.FirstOrDefault();
}
private object OnInventoryAmmoFind(PlayerInventory inventory, List<Item> collect, AmmoTypes ammoType)
{
FindPlayerAmmo(inventory.baseEntity, ammoType, collect);
return False;
}
private Item OnInventoryAmmoItemFind(PlayerInventory inventory, ItemDefinition itemDefinition)
{
if ((object)itemDefinition == null)
return null;
_reusableItemList.Clear();
var itemQuery = new ItemIdQuery(itemDefinition.itemid);
FindPlayerItems(inventory.baseEntity, ref itemQuery, _reusableItemList, includePlayerWearables: true);
return _reusableItemList.FirstOrDefault();
}
private Item OnInventoryAmmoItemFind(PlayerInventory inventory, AmmoTypes ammoType)
{
_reusableItemList.Clear();
FindPlayerAmmo(inventory.baseEntity, ammoType, _reusableItemList);
return _reusableItemList.FirstOrDefault();
}
private object OnIngredientsCollect(ItemCrafter itemCrafter, ItemBlueprint blueprint, ItemCraftTask task, int amount, BasePlayer player)
{
ExposedHooks.OnIngredientsDetermine(_overridenIngredients, blueprint, amount, player);
var collect = new List<Item>();
if (_overridenIngredients.Count > 0)
{
foreach (var entry in _overridenIngredients)
{
if (entry.Value <= 0)
continue;
var itemQuery = new ItemIdQuery(entry.Key);
TakePlayerItems(player, ref itemQuery, entry.Value, collect, task);
}
}
else
{
for (var i = 0; i < blueprint.ingredients.Count; i++)
{
var ingredient = blueprint.ingredients[i];
var itemQuery = new ItemIdQuery(ingredient.itemid);
TakePlayerItems(player, ref itemQuery, (int)ingredient.amount * amount, collect, task);
}
}
for (var i = 0; i < collect.Count; i++)
{
collect[i].CollectedForCrafting(player);
}
task.takenItems = collect;
return False;
}
private object CanCraft(ItemCrafter itemCrafter, ItemBlueprint blueprint, int amount, bool free)
{
if (_isBlockedByInstantCraft?.Invoke(blueprint.targetItem) == true)
return null;
if (SuperCrafter?.Call("API_CanCraft", itemCrafter, blueprint, amount) is false)
return null;
var basePlayer = itemCrafter.baseEntity;
ExposedHooks.OnIngredientsDetermine(_overridenIngredients, blueprint, amount, basePlayer);
if (_overridenIngredients.Count > 0)
{
foreach (var entry in _overridenIngredients)
{
if (entry.Value <= 0)
continue;
var itemQuery = new ItemIdQuery(entry.Key);
if (SumPlayerItems(basePlayer, ref itemQuery) < entry.Value)
return False;
}
}
else
{
for (var i = 0; i < blueprint.ingredients.Count; i++)
{
var ingredient = blueprint.ingredients[i];
var itemQuery = new ItemIdQuery(ingredient.itemid);
if (SumPlayerItems(basePlayer, ref itemQuery) < ingredient.amount * amount)
return False;
}
}
return True;
}
#endregion
#region API
private class ApiInstance
{
public readonly Dictionary<string, object> ApiWrapper;
private readonly ItemRetriever _plugin;
private SupplierManager _supplierManager => _plugin._supplierManager;
private ContainerManager _containerManager => _plugin._containerManager;
public ApiInstance(ItemRetriever plugin)
{
_plugin = plugin;
ApiWrapper = new Dictionary<string, object>
{
[nameof(AddSupplier)] = new Action<Plugin, Dictionary<string, object>>(AddSupplier),
[nameof(RemoveSupplier)] = new Action<Plugin>(RemoveSupplier),
[nameof(HasContainer)] = new Func<BasePlayer, ItemContainer, bool>(HasContainer),
[nameof(AddContainer)] = new Action<Plugin, BasePlayer, IItemContainerEntity, ItemContainer, Func<Plugin, BasePlayer, ItemContainer, bool>>(AddContainer),
[nameof(RemoveContainer)] = new Action<Plugin, BasePlayer, ItemContainer>(RemoveContainer),
[nameof(RemoveAllContainersForPlayer)] = new Action<Plugin, BasePlayer>(RemoveAllContainersForPlayer),
[nameof(RemoveAllContainersForPlugin)] = new Action<Plugin>(RemoveAllContainersForPlugin),
[nameof(FindPlayerItems)] = new Action<BasePlayer, Dictionary<string, object>, List<Item>>(FindPlayerItems),
[nameof(SumPlayerItems)] = new Func<BasePlayer, Dictionary<string, object>, int>(SumPlayerItems),
[nameof(TakePlayerItems)] = new Func<BasePlayer, Dictionary<string, object>, int, List<Item>, int>(TakePlayerItems),
[nameof(FindPlayerAmmo)] = new Action<BasePlayer, AmmoTypes, List<Item>>(FindPlayerAmmo),
};
}
public void AddSupplier(Plugin plugin, Dictionary<string, object> spec)
{
if (plugin == null)
throw new ArgumentNullException(nameof(plugin));
_supplierManager.AddSupplier(plugin, spec);
}
public void RemoveSupplier(Plugin plugin)
{
if (plugin == null)
throw new ArgumentNullException(nameof(plugin));
_supplierManager.RemoveSupplier(plugin);
}
public bool HasContainer(BasePlayer player, ItemContainer container)
{
return _containerManager.HasContainer(player, container);
}
public void AddContainer(Plugin plugin, BasePlayer player, IItemContainerEntity containerEntity, ItemContainer container, Func<Plugin, BasePlayer, ItemContainer, bool> canUseContainer)
{
if (_containerManager.AddContainer(plugin, player, containerEntity, container, canUseContainer))
{
MarkInventoryDirty(player);
}
}
public void RemoveContainer(Plugin plugin, BasePlayer player, ItemContainer container)
{
if (_containerManager.RemoveContainer(plugin, player, container))
{
MarkInventoryDirty(player);
}
}
public void RemoveAllContainersForPlayer(Plugin plugin, BasePlayer player)
{
if (_containerManager.RemoveContainers(plugin, player))
{
MarkInventoryDirty(player);
}
}
public void RemoveAllContainersForPlugin(Plugin plugin)
{
var updatedPlayers = _containerManager.RemoveContainers(plugin);
if ((updatedPlayers?.Count ?? 0) > 0)
{
foreach (var player in updatedPlayers)
{
MarkInventoryDirty(player);
}
}
}
public void FindPlayerItems(BasePlayer player, Dictionary<string, object> itemQueryDict, List<Item> collect)
{
var itemQuery = ItemQuery.Parse(itemQueryDict);
_plugin.FindPlayerItems(player, ref itemQuery, collect);
}
public int SumPlayerItems(BasePlayer player, Dictionary<string, object> itemQueryDict)
{
var itemQuery = ItemQuery.Parse(itemQueryDict);
return _plugin.SumPlayerItems(player, ref itemQuery);
}
public int TakePlayerItems(BasePlayer player, Dictionary<string, object> itemQueryDict, int amount, List<Item> collect)
{
var itemQuery = ItemQuery.Parse(itemQueryDict);
return _plugin.TakePlayerItems(player, ref itemQuery, amount, collect);
}
public void FindPlayerAmmo(BasePlayer player, AmmoTypes ammoType, List<Item> collect)
{
_plugin.FindPlayerAmmo(player, ammoType, collect);
}
}
[HookMethod(nameof(API_GetApi))]
public Dictionary<string, object> API_GetApi()
{
return _api.ApiWrapper;
}
[HookMethod(nameof(API_AddSupplier))]
public void API_AddSupplier(Plugin plugin, Dictionary<string, object> spec)
{
_api.AddSupplier(plugin, spec);
}
[HookMethod(nameof(API_RemoveSupplier))]
public void API_RemoveSupplier(Plugin plugin)
{
_api.RemoveSupplier(plugin);
}
[HookMethod(nameof(API_HasContainer))]
public object API_HasContainer(BasePlayer player, ItemContainer container)
{
return ObjectCache.Get(_api.HasContainer(player, container));
}
[HookMethod(nameof(API_AddContainer))]
public void API_AddContainer(Plugin plugin, BasePlayer player, IItemContainerEntity containerEntity, ItemContainer container, Func<Plugin, BasePlayer, ItemContainer, bool> canUseContainer = null)
{
_api.AddContainer(plugin, player, containerEntity, container, canUseContainer);
}
[HookMethod(nameof(API_RemoveContainer))]
public void API_RemoveContainer(Plugin plugin, BasePlayer player, ItemContainer container)
{
_api.RemoveContainer(plugin, player, container);
}
[HookMethod(nameof(API_RemoveAllContainersForPlayer))]
public void API_RemoveAllContainersForPlayer(Plugin plugin, BasePlayer player)
{
_api.RemoveAllContainersForPlayer(plugin, player);
}
[HookMethod(nameof(API_RemoveAllContainersForPlugin))]
public void API_RemoveAllContainersForPlugin(Plugin plugin)
{
_api.RemoveAllContainersForPlugin(plugin);
}
[HookMethod(nameof(API_FindPlayerItems))]
public void API_FindPlayerItems(BasePlayer player, Dictionary<string, object> itemQuery, List<Item> collect)
{
_api.FindPlayerItems(player, itemQuery, collect);
}
[HookMethod(nameof(API_SumPlayerItems))]
public object API_SumPlayerItems(BasePlayer player, Dictionary<string, object> itemQuery)
{
return ObjectCache.Get(_api.SumPlayerItems(player, itemQuery));
}
[HookMethod(nameof(API_TakePlayerItems))]
public object API_TakePlayerItems(BasePlayer player, Dictionary<string, object> itemQuery, int amount, List<Item> collect)
{
return ObjectCache.Get(_api.TakePlayerItems(player, itemQuery, amount, collect));
}
[HookMethod(nameof(API_FindPlayerAmmo))]
public void API_FindPlayerAmmo(BasePlayer player, AmmoTypes ammoType, List<Item> collect)
{
_api.FindPlayerAmmo(player, ammoType, collect);
}
#endregion
#region Exposed Hooks
private static class ExposedHooks
{
public static void OnIngredientsDetermine(Dictionary<int, int> overridenIngredients, ItemBlueprint blueprint, int amount, BasePlayer player)
{
overridenIngredients.Clear();
Interface.CallHook("OnIngredientsDetermine", overridenIngredients, blueprint, ObjectCache.Get(amount), player);
}
}
#endregion
#region Helper Methods
private static void MarkInventoryDirty(BasePlayer player)
{
player.inventory?.containerMain?.MarkDirty();
}
private static int GetHighestUsedSlot(ProtoBuf.ItemContainer containerData)
{
var highestUsedSlot = -1;
for (var i = 0; i < containerData.contents.Count; i++)
{
var item = containerData.contents[i];
if (item.slot > highestUsedSlot)
{
highestUsedSlot = item.slot;
}
}
return highestUsedSlot;
}
private void RefreshInstantCraftCallback()
{
_isBlockedByInstantCraft = InstantCraft?.Call("API_GetIsItemBlockedCallback") as Func<ItemDefinition, bool>;
}
private void SerializeForNetwork(BasePlayer player, ProtoBuf.ItemContainer containerData)
{
if (containerData == null)
return;
var firstAvailableInvisibleSlot = Math.Max(InventorySize, GetHighestUsedSlot(containerData) + 1);
var nextInvisibleSlot = firstAvailableInvisibleSlot;
var itemsAdded = ItemUtils.SerializeForNetwork(_customItemDefinitionTracker, player.inventory.containerMain.itemList, containerData, ref nextInvisibleSlot, addChildContainersOnly: true)
+ ItemUtils.SerializeForNetwork(_customItemDefinitionTracker, player.inventory.containerBelt.itemList, containerData, ref nextInvisibleSlot, addChildContainersOnly: true)
+ ItemUtils.SerializeForNetwork(_customItemDefinitionTracker, player.inventory.containerWear.itemList, containerData, ref nextInvisibleSlot, addChildContainersOnly: true)
+ _supplierManager.SerializeForNetwork(player, containerData, ref nextInvisibleSlot)
+ _containerManager.SerializeForNetwork(player, containerData, ref nextInvisibleSlot);
if (itemsAdded > 0)
{
if (containerData.slots >= 24)
{
containerData.slots = firstAvailableInvisibleSlot + itemsAdded;
}
}
}
private void FindPlayerItems<T>(BasePlayer player, ref T itemQuery, List<Item> collect, bool includePlayerWearables = false) where T : IItemQuery
{
_supplierManager.FindPlayerItems(player, ref itemQuery, collect, beforeInventory: true);
ItemUtils.FindItems(player.inventory.containerMain.itemList, ref itemQuery, collect);
ItemUtils.FindItems(player.inventory.containerBelt.itemList, ref itemQuery, collect);
ItemUtils.FindItems(player.inventory.containerWear.itemList, ref itemQuery, collect, childItemsOnly: !includePlayerWearables);
_supplierManager.FindPlayerItems(player, ref itemQuery, collect);
_containerManager.FindPlayerItems(player, ref itemQuery, collect);
}
private int SumPlayerItems<T>(BasePlayer player, ref T itemQuery, bool countPlayerWearables = false) where T : IItemQuery
{
return ItemUtils.SumItems(player.inventory.containerMain.itemList, ref itemQuery)
+ ItemUtils.SumItems(player.inventory.containerBelt.itemList, ref itemQuery)
+ ItemUtils.SumItems(player.inventory.containerWear.itemList, ref itemQuery, childItemsOnly: !countPlayerWearables)
+ _supplierManager.SumPlayerItems(player, ref itemQuery)
+ _containerManager.SumPlayerItems(player, ref itemQuery);
}
private int TakePlayerItems<T>(BasePlayer player, ref T itemQuery, int amountToTake, List<Item> collect, ItemCraftTask itemCraftTask = null, bool includePlayerWearables = false) where T : IItemQuery
{
var amountTaken = _supplierManager.TakePlayerItems(player, ref itemQuery, amountToTake, collect, itemCraftTask, beforeInventory: true);
if (amountTaken >= amountToTake)
return amountTaken;
amountTaken += ItemUtils.TakeItems(player.inventory.containerMain.itemList, ref itemQuery, amountToTake - amountTaken, collect);
if (amountTaken >= amountToTake)
return amountTaken;
amountTaken += ItemUtils.TakeItems(player.inventory.containerBelt.itemList, ref itemQuery, amountToTake - amountTaken, collect);
if (amountTaken >= amountToTake)
return amountTaken;
amountTaken += ItemUtils.TakeItems(player.inventory.containerWear.itemList, ref itemQuery, amountToTake - amountTaken, collect, childItemsOnly: !includePlayerWearables);
if (amountTaken >= amountToTake)
return amountTaken;
amountTaken += _supplierManager.TakePlayerItems(player, ref itemQuery, amountToTake - amountTaken, collect, itemCraftTask);
if (amountTaken >= amountToTake)
return amountTaken;
amountTaken += _containerManager.TakePlayerItems(player, ref itemQuery, amountToTake - amountTaken, collect);
return amountTaken;
}
private void FindPlayerAmmo(BasePlayer player, AmmoTypes ammoType, List<Item> collect)
{
_supplierManager.FindPlayerAmmo(player, ammoType, collect, beforeInventory: true);
player.inventory.containerMain?.FindAmmo(collect, ammoType);
player.inventory.containerBelt?.FindAmmo(collect, ammoType);
player.inventory.containerWear?.FindAmmo(collect, ammoType);
_supplierManager.FindPlayerAmmo(player, ammoType, collect);
_containerManager.FindPlayerAmmo(player, ammoType, collect);
}
#endregion
#region Helper Classes
private static class StringUtils
{
public static bool EqualsIgnoreCase(string a, string b)
{
return string.Compare(a, b, StringComparison.OrdinalIgnoreCase) == 0;
}
}
private static class ObjectCache
{
private static class StaticObjectCache<T>
{
private static readonly Dictionary<T, object> _cacheByValue = new();
public static object Get(T value)
{
if (!_cacheByValue.TryGetValue(value, out var cachedObject))
{
cachedObject = value;
_cacheByValue[value] = cachedObject;
}
return cachedObject;
}
public static void Clear()
{
_cacheByValue.Clear();
}
}
public static object Get<T>(T value)
{
return StaticObjectCache<T>.Get(value);
}
public static object Get(bool value)
{
return value ? True : False;
}
public static void Clear<T>()
{
StaticObjectCache<T>.Clear();
}
}
private class CustomItemDefinitionTracker
{
private readonly HashSet<int> _customItemDefinitionIds = new();
public bool IsCustomItemDefinition(ItemDefinition itemDefinition)
{
return itemDefinition.HasFlag(CustomItemDefinitionFlag);
}
public bool IsCustomItemDefinition(Item item)
{
return IsCustomItemDefinition(item.info);
}
public bool IsCustomItemDefinition(int itemId)
{
if (_customItemDefinitionIds.Contains(itemId))
return true;
var itemDefinition = ItemManager.FindItemDefinition(itemId);
if (itemDefinition == null)
return true;
if (IsCustomItemDefinition(itemDefinition))
{
_customItemDefinitionIds.Add(itemId);
return true;
}
return false;
}
}
#endregion
#region Item Utils
private static class ItemUtils
{
public static int SerializeForNetwork(CustomItemDefinitionTracker customItemDefinitionTracker,
List<Item> itemList, ProtoBuf.ItemContainer containerData, ref int nextInvisibleSlot, bool addChildContainersOnly = false)
{
var itemsAdded = 0;
for (var i = 0; i < itemList.Count; i++)
{
var item = itemList[i];
if (item.IsBlueprint())
continue;
if (customItemDefinitionTracker.IsCustomItemDefinition(item))
continue;
if (!addChildContainersOnly)
{
var itemData = item.Save();
itemData.slot = nextInvisibleSlot++;
containerData.contents.Add(itemData);
itemsAdded++;
}
if (HasSearchableContainer(item, out var childItemList))
{
itemsAdded += SerializeForNetwork(customItemDefinitionTracker, childItemList, containerData, ref nextInvisibleSlot);
}
}
return itemsAdded;
}
public static int SerializeForNetwork(CustomItemDefinitionTracker customItemDefinitionTracker,
List<ProtoBuf.Item> itemList, ProtoBuf.ItemContainer containerData, ref int nextInvisibleSlot)
{
var itemsAdded = 0;
for (var i = 0; i < itemList.Count; i++)
{
var itemData = itemList[i];
if (itemData.itemid == BlueprintItemId)
continue;
if (customItemDefinitionTracker.IsCustomItemDefinition(itemData.itemid))
continue;
itemData.slot = nextInvisibleSlot++;
if (itemData.UID.Value == 0)
{
// Items that don't have UIDs (fake items) need unique UIDs.
itemData.UID = new ItemId(ulong.MaxValue - (ulong)nextInvisibleSlot);
}
containerData.contents.Add(itemData);
itemsAdded++;
if (HasSearchableContainer(itemData, out var childItemList))
{
itemsAdded += SerializeForNetwork(customItemDefinitionTracker, childItemList, containerData, ref nextInvisibleSlot);
}
}
return itemsAdded;
}
public static void FindItems<T>(List<Item> itemList, ref T itemQuery, List<Item> collect, bool childItemsOnly = false) where T : IItemQuery
{
for (var i = 0; i < itemList.Count; i++)
{
var item = itemList[i];
var usableAmount = childItemsOnly ? 0 : itemQuery.GetUsableAmount(item);
if (usableAmount > 0)
{
collect.Add(item);
}
if (HasSearchableContainer(item, out var childItemList))
{
FindItems(childItemList, ref itemQuery, collect);
}
}
}
public static int SumItems<T>(List<Item> itemList, ref T itemQuery, bool childItemsOnly = false) where T : IItemQuery
{
var sum = 0;
for (var i = 0; i < itemList.Count; i++)
{
var item = itemList[i];
sum += childItemsOnly ? 0 : itemQuery.GetUsableAmount(item);
if (HasSearchableContainer(item, out var childItemList))
{
sum += SumItems(childItemList, ref itemQuery);
}
}
return sum;
}
public static int TakeItems<T>(List<Item> itemList, ref T itemQuery, int totalAmountToTake, List<Item> collect, bool childItemsOnly = false) where T : IItemQuery
{
var totalAmountTaken = 0;
for (var i = itemList.Count - 1; i >= 0; i--)
{
var amountToTake = totalAmountToTake - totalAmountTaken;
if (amountToTake <= 0)
break;
var item = itemList[i];
var usableAmount = childItemsOnly ? 0 : itemQuery.GetUsableAmount(item);
if (usableAmount > 0)
{
amountToTake = Math.Min(item.amount, amountToTake);
if (item.amount > amountToTake)
{
if (collect != null)
{
var splitItem = item.SplitItem(amountToTake);
var playerOwner = splitItem.GetOwnerPlayer();
if (playerOwner != null)
{
splitItem.CollectedForCrafting(playerOwner);
}
collect.Add(splitItem);
}
else
{
item.amount -= amountToTake;
item.MarkDirty();
}
}
else
{
item.RemoveFromContainer();
if (collect != null)
{
collect.Add(item);
}
else
{
item.Remove();
}
}
totalAmountTaken += amountToTake;
}
if (HasSearchableContainer(item, out var childItemList))
{
totalAmountTaken += TakeItems(childItemList, ref itemQuery, amountToTake, collect);
}
if (totalAmountTaken >= totalAmountToTake)
return totalAmountTaken;
}
return totalAmountTaken;
}
private static bool IsSearchableItemDefinition(ItemDefinition itemDefinition)
{
return (itemDefinition.flags & (ItemDefinition.Flag.Backpack | SearchableItemDefinitionFlag)) != 0;
}
private static bool IsSearchableItemDefinition(int itemId)
{
var itemDefinition = ItemManager.FindItemDefinition(itemId);
if ((object)itemDefinition == null)
return false;
return IsSearchableItemDefinition(itemDefinition);
}
private static bool HasSearchableContainer(Item item, out List<Item> itemList)
{
itemList = item.contents?.itemList;
if (itemList is not { Count: > 0 })
return false;
if (item.HasFlag(SearchableItemFlag))
return true;
if (item.HasFlag(UnsearchableItemFlag))
return false;
return IsSearchableItemDefinition(item.info);
}
private static bool HasSearchableContainer(ProtoBuf.Item itemData, out List<ProtoBuf.Item> itemList)
{
itemList = itemData.contents?.contents;
if (itemList is not { Count: > 0 })
return false;
var itemFlags = (Item.Flag)itemData.flags;
if (itemFlags.HasFlag(SearchableItemFlag))
return true;
if (itemFlags.HasFlag(UnsearchableItemFlag))
return false;
return IsSearchableItemDefinition(itemData.itemid);
}
}
#endregion
#region Item Query
private interface IItemQuery
{
int GetUsableAmount(Item item);
void PopulateItemQuery(Dictionary<string, object> itemQueryDict);
}
private struct ItemIdQuery : IItemQuery
{
public int ItemId;
public ItemIdQuery(int itemId)
{
ItemId = itemId;
}
public int GetUsableAmount(Item item)
{
return ItemId != item.info.itemid ? 0 : item.amount;
}
public void PopulateItemQuery(Dictionary<string, object> itemQueryDict)
{
itemQueryDict.Clear();
itemQueryDict[ItemQuery.ItemIdField] = ObjectCache.Get(ItemId);
}
}
private struct ItemQuery : IItemQuery
{
public const string BlueprintIdField = "BlueprintId";
public const string DisplayNameField = "DisplayName";
public const string DataIntField = "DataInt";
public const string FlagsContainField = "FlagsContain";
public const string FlagsEqualField = "FlagsEqual";
public const string IgnoreItemField = "IgnoreItem";
public const string ItemDefinitionField = "ItemDefinition";
public const string ItemIdField = "ItemId";
public const string MinConditionField = "MinCondition";
public const string RequireEmptyField = "RequireEmpty";
public const string SkinIdField = "SkinId";
public static ItemQuery Parse(Dictionary<string, object> raw)
{
var itemQuery = new ItemQuery();
GetOption(raw, BlueprintIdField, out itemQuery.BlueprintId);
GetOption(raw, DisplayNameField, out itemQuery.DisplayName);
GetOption(raw, DataIntField, out itemQuery.DataInt);
GetOption(raw, FlagsContainField, out itemQuery.FlagsContain);
GetOption(raw, FlagsEqualField, out itemQuery.FlagsEqual);
GetOption(raw, IgnoreItemField, out itemQuery.IgnoreItem);
GetOption(raw, ItemDefinitionField, out itemQuery.ItemDefinition);
GetOption(raw, ItemIdField, out itemQuery.ItemId);
GetOption(raw, MinConditionField, out itemQuery.MinCondition);
GetOption(raw, RequireEmptyField, out itemQuery.RequireEmpty);
GetOption(raw, SkinIdField, out itemQuery.SkinId);
return itemQuery;
}
private static void GetOption<T>(Dictionary<string, object> dict, string key, out T result)
{
result = dict.TryGetValue(key, out var value) && value is T valueOfType
? valueOfType
: default;
}
public int? BlueprintId;
public int? DataInt;
public string DisplayName;
public Item.Flag? FlagsContain;
public Item.Flag? FlagsEqual;
public Item IgnoreItem;
public ItemDefinition ItemDefinition;
public int? ItemId;
public float MinCondition;
public bool RequireEmpty;
public ulong? SkinId;
public int GetUsableAmount(Item item)
{
if (IgnoreItem != null && item == IgnoreItem)
return 0;
var itemId = GetItemId();
if (itemId.HasValue && itemId != item.info.itemid)
return 0;
if (SkinId.HasValue && SkinId != item.skin)
return 0;
if (BlueprintId.HasValue && BlueprintId != item.blueprintTarget)
return 0;
if (DataInt.HasValue && DataInt != (item.instanceData?.dataInt ?? 0))
return 0;
if (FlagsContain.HasValue && !item.flags.HasFlag(FlagsContain.Value))
return 0;
if (FlagsEqual.HasValue && FlagsEqual != item.flags)
return 0;
if (MinCondition > 0 && HasCondition() && (item.conditionNormalized < MinCondition || item.maxConditionNormalized < MinCondition))
return 0;
if (!string.IsNullOrEmpty(DisplayName) && !StringUtils.EqualsIgnoreCase(DisplayName, item.name))
return 0;
return RequireEmpty && item.contents?.itemList?.Count > 0
? Math.Max(0, item.amount - 1)
: item.amount;
}
public void PopulateItemQuery(Dictionary<string, object> itemQueryDict)
{
itemQueryDict.Clear();
if (BlueprintId.HasValue)
itemQueryDict[BlueprintIdField] = ObjectCache.Get(BlueprintId.Value);
if (DataInt.HasValue)
itemQueryDict[DataIntField] = ObjectCache.Get(DataInt.Value);
if (DisplayName != null)
itemQueryDict[DisplayNameField] = DisplayName;
if (FlagsContain.HasValue)
itemQueryDict[FlagsContainField] = ObjectCache.Get(FlagsContain.Value);
if (FlagsEqual.HasValue)
itemQueryDict[FlagsEqualField] = ObjectCache.Get(FlagsEqual.Value);
var itemId = GetItemId();
if (itemId.HasValue)
itemQueryDict[ItemIdField] = ObjectCache.Get(itemId.Value);
if (MinCondition > 0)
itemQueryDict[MinConditionField] = ObjectCache.Get(MinCondition);
if (RequireEmpty)
itemQueryDict[RequireEmptyField] = True;
if (SkinId.HasValue)
itemQueryDict[SkinIdField] = ObjectCache.Get(SkinId.Value);
if (IgnoreItem != null)
itemQueryDict[IgnoreItemField] = IgnoreItem;
}
private int? GetItemId()
{
if (ItemDefinition != null)
return ItemDefinition?.itemid ?? ItemId;
return ItemId;
}
private ItemDefinition GetItemDefinition()
{
if ((object)ItemDefinition == null && ItemId.HasValue)
{
ItemDefinition = ItemManager.FindItemDefinition(ItemId.Value);
}
return ItemDefinition;
}
private bool HasCondition()
{
return GetItemDefinition()?.condition.enabled ?? false;
}
}
#endregion
#region Item Supplier Manager
private class ItemSupplier
{
public static ItemSupplier FromSpec(Plugin plugin, Dictionary<string, object> spec)
{
var supplier = new ItemSupplier { Plugin = plugin };
GetOption(spec, "Priority", out supplier.Priority);
GetOption(spec, "FindPlayerItems", out supplier.FindPlayerItems);
GetOption(spec, "SumPlayerItems", out supplier.SumPlayerItems);
GetOption(spec, "TakePlayerItems", out supplier.TakePlayerItems);
GetOption(spec, "TakePlayerItemsV2", out supplier.TakePlayerItemsV2);
GetOption(spec, "FindPlayerAmmo", out supplier.FindPlayerAmmo);
GetOption(spec, "SerializeForNetwork", out supplier.SerializeForNetwork);
return supplier;
}
private static void GetOption<T>(Dictionary<string, object> dict, string key, out T result)
{
result = dict.TryGetValue(key, out var value) && value is T valueOfType
? valueOfType
: default;
}
public Plugin Plugin { get; private set; }
public int Priority;
public Action<BasePlayer, Dictionary<string, object>, List<Item>> FindPlayerItems;
public Func<BasePlayer, Dictionary<string, object>, int> SumPlayerItems;
public Func<BasePlayer, Dictionary<string, object>, int, List<Item>, int> TakePlayerItems;
public Func<BasePlayer, Dictionary<string, object>, int, List<Item>, ItemCraftTask, int> TakePlayerItemsV2;
public Action<BasePlayer, AmmoTypes, List<Item>> FindPlayerAmmo;
public Action<BasePlayer, List<ProtoBuf.Item>> SerializeForNetwork;
}
private class SupplierManager
{
private static void RemoveSupplier(List<ItemSupplier> supplierList, Plugin plugin)
{
for (var i = 0; i < supplierList.Count; i++)
{
var supplier = supplierList[i];
if (supplier.Plugin.Name != plugin.Name)
continue;
supplierList.RemoveAt(i);
return;
}
}
private static void SortSupplierList(List<ItemSupplier> supplierList)
{
supplierList.Sort((a, b) =>
{
var priorityOrder = a.Priority.CompareTo(b.Priority);
if (priorityOrder != 0)
return priorityOrder;
return string.Compare(a.Plugin.Name, b.Plugin.Name, StringComparison.OrdinalIgnoreCase);
});
}
private CustomItemDefinitionTracker _customItemDefinitionTracker;
// Use a list with standard for loops for high performance.
private List<ItemSupplier> _allSuppliers = new();
private List<ItemSupplier> _beforeInventorySuppliers = new();
private List<ItemSupplier> _afterInventorySuppliers = new();
private List<ProtoBuf.Item> _reusableItemListForNetwork = new(32);
private Dictionary<string, object> _reusableItemQuery = new();
public SupplierManager(CustomItemDefinitionTracker customItemDefinitionTracker)
{
_customItemDefinitionTracker = customItemDefinitionTracker;
}
public void AddSupplier(Plugin plugin, Dictionary<string, object> spec)
{
RemoveSupplier(plugin);
var supplier = ItemSupplier.FromSpec(plugin, spec);
_allSuppliers.Add(supplier);
if (supplier.Priority < 0)
{
_beforeInventorySuppliers.Add(supplier);
SortSupplierList(_beforeInventorySuppliers);
}
else
{
_afterInventorySuppliers.Add(supplier);
SortSupplierList(_afterInventorySuppliers);
}
}
public void RemoveSupplier(Plugin plugin)
{
RemoveSupplier(_allSuppliers, plugin);
RemoveSupplier(_beforeInventorySuppliers, plugin);
RemoveSupplier(_afterInventorySuppliers, plugin);
}
public int SerializeForNetwork(BasePlayer player, ProtoBuf.ItemContainer containerData, ref int nextInvisibleSlot)
{
if (_allSuppliers.Count == 0)
return 0;
_reusableItemListForNetwork.Clear();
for (var i = 0; i < _allSuppliers.Count; i++)
{
_allSuppliers[i].SerializeForNetwork?.Invoke(player, _reusableItemListForNetwork);
}
var itemsAdded = ItemUtils.SerializeForNetwork(_customItemDefinitionTracker, _reusableItemListForNetwork, containerData, ref nextInvisibleSlot);
_reusableItemListForNetwork.Clear();
return itemsAdded;
}
public void FindPlayerItems<T>(BasePlayer player, ref T itemQuery, List<Item> collect, bool beforeInventory = false) where T : IItemQuery
{
var suppliers = beforeInventory ? _beforeInventorySuppliers : _afterInventorySuppliers;
if (suppliers.Count == 0)
return;
itemQuery.PopulateItemQuery(_reusableItemQuery);
for (var i = 0; i < suppliers.Count; i++)
{
suppliers[i].FindPlayerItems?.Invoke(player, _reusableItemQuery, collect);
}
}
public int SumPlayerItems<T>(BasePlayer player, ref T itemQuery) where T : IItemQuery
{
if (_allSuppliers.Count == 0)
return 0;
itemQuery.PopulateItemQuery(_reusableItemQuery);
var sum = 0;
for (var i = 0; i < _allSuppliers.Count; i++)
{
sum += _allSuppliers[i].SumPlayerItems?.Invoke(player, _reusableItemQuery) ?? 0;
}
return sum;
}
public int TakePlayerItems<T>(BasePlayer player, ref T itemQuery, int amountToTake, List<Item> collect, ItemCraftTask itemCraftTask, bool beforeInventory = false) where T : IItemQuery
{
var suppliers = beforeInventory ? _beforeInventorySuppliers : _afterInventorySuppliers;
if (suppliers.Count == 0)
return 0;
itemQuery.PopulateItemQuery(_reusableItemQuery);
var amountTaken = 0;
for (var i = 0; i < suppliers.Count; i++)
{
amountTaken += suppliers[i].TakePlayerItemsV2?.Invoke(player, _reusableItemQuery, amountToTake - amountTaken, collect, itemCraftTask)
?? suppliers[i].TakePlayerItems?.Invoke(player, _reusableItemQuery, amountToTake - amountTaken, collect)
?? 0;
if (amountTaken >= amountToTake)
return amountTaken;
}
return amountTaken;
}
public void FindPlayerAmmo(BasePlayer player, AmmoTypes ammoType, List<Item> collect, bool beforeInventory = false)
{
var suppliers = beforeInventory ? _beforeInventorySuppliers : _afterInventorySuppliers;
if (suppliers.Count == 0)
return;
for (var i = 0; i < suppliers.Count; i++)
{
suppliers[i].FindPlayerAmmo?.In
// ... [Code preview truncated due to length. Full source in attached file] ...