C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using HarmonyLib;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("CupboardExtraItems", "Syrrith", "1.2.1")]
[Description("Allows extra items to be stored in tool cupboards.")]
public class CupboardExtraItems : RustPlugin
{
/// <summary>TC slots 0-23 are upkeep/resource storage; 24-28 are tool slots.</summary>
private const int ResourceSlotCount = 24;
private const int ToolSlotStart = 24;
internal static CupboardExtraItems Instance { get; private set; }
private Harmony _harmony;
private ConfigData config;
private HashSet<string> allowedShortnames;
private class ConfigData
{
public List<string> AllowedItemShortnames { get; set; }
}
protected override void LoadDefaultConfig()
{
PrintWarning("Creating new config file for CupboardExtraItems...");
config = new ConfigData
{
AllowedItemShortnames = new List<string>
{
"sulfur",
"sulfur.ore",
"gunpowder",
"diesel_barrel",
"basicblueprintfragment",
"advancedblueprintfragment"
}
};
SaveConfig();
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<ConfigData>();
if (config?.AllowedItemShortnames == null)
{
throw new InvalidOperationException("Config invalid");
}
}
catch
{
PrintError("Failed to load config, creating default one.");
LoadDefaultConfig();
}
RebuildLookup();
}
protected override void SaveConfig()
{
Config.WriteObject(config, true);
}
private void Init()
{
Instance = this;
permission.RegisterPermission("cupboardextraitems.admin", this);
_harmony = new Harmony("com.syrrith.cupboardextraitems");
_harmony.PatchAll(Assembly.GetExecutingAssembly());
}
private void Unload()
{
_harmony?.UnpatchAll(_harmony.Id);
_harmony = null;
Instance = null;
}
private void RebuildLookup()
{
allowedShortnames = new HashSet<string>(
config.AllowedItemShortnames
.Where(s => !string.IsNullOrWhiteSpace(s))
.Select(s => s.Trim().ToLowerInvariant()));
}
internal bool IsAllowedItem(Item item)
{
if (item?.info == null || allowedShortnames == null || allowedShortnames.Count == 0)
{
return false;
}
return allowedShortnames.Contains(item.info.shortname);
}
private bool IsToolCupboard(ItemContainer container, out BuildingPrivlidge cupboard)
{
cupboard = container?.entityOwner as BuildingPrivlidge;
return cupboard != null;
}
private static bool IsToolSlot(int targetPos)
{
return targetPos >= ToolSlotStart;
}
/// <summary>Extra items may only use TC resource slots (0-23), never tool slots (24-28).</summary>
internal bool CanStoreInSlot(ItemContainer container, Item item, int targetPos)
{
if (!IsAllowedItem(item))
{
return false;
}
if (IsToolSlot(targetPos))
{
return false;
}
// Shift-click / auto-move often passes -1 — only allow if a resource slot can take it
if (targetPos < 0)
{
return HasResourceSlotSpace(container, item);
}
return targetPos < ResourceSlotCount;
}
private static bool HasResourceSlotSpace(ItemContainer container, Item item)
{
if (container == null || item?.info == null)
{
return false;
}
for (var i = 0; i < ResourceSlotCount; i++)
{
var slotItem = container.GetSlot(i);
if (slotItem == null)
{
return true;
}
if (slotItem.info.itemid == item.info.itemid && slotItem.amount < slotItem.MaxStackable())
{
return true;
}
}
return false;
}
/// <summary>
/// Backup hook for containers that use StorageContainer.ItemFilter.
/// </summary>
private bool? OnItemFilter(Item item, StorageContainer container, int targetSlot)
{
if (item == null || container == null)
{
return null;
}
if (!(container is BuildingPrivlidge))
{
return null;
}
if (!IsAllowedItem(item))
{
return null;
}
return CanStoreInSlot(container.inventory, item, targetSlot);
}
/// <summary>
/// Oxide hook — only runs if vanilla pre-check passes, so Harmony handles the rest.
/// </summary>
private ItemContainer.CanAcceptResult? CanAcceptItem(ItemContainer container, Item item, int targetSlot)
{
if (!IsToolCupboard(container, out _))
{
return null;
}
if (!IsAllowedItem(item))
{
return null;
}
return CanStoreInSlot(container, item, targetSlot)
? ItemContainer.CanAcceptResult.CanAccept
: ItemContainer.CanAcceptResult.CannotAccept;
}
[ConsoleCommand("cupboardextra.reload")]
private void CmdReload(ConsoleSystem.Arg arg)
{
if (arg.Connection != null && arg.Connection.player is BasePlayer player)
{
if (!permission.UserHasPermission(player.UserIDString, "cupboardextraitems.admin"))
{
player.ChatMessage("You do not have permission to reload CupboardExtraItems.");
return;
}
}
LoadConfig();
Puts("CupboardExtraItems config reloaded.");
arg.ReplyWith("CupboardExtraItems config reloaded.");
}
[HarmonyPatch(typeof(ItemContainer), nameof(ItemContainer.CanAcceptItem))]
private static class CanAcceptItemPatch
{
[HarmonyPrefix]
private static bool Prefix(ItemContainer __instance, Item item, int targetPos, ref ItemContainer.CanAcceptResult __result)
{
var plugin = Instance;
if (plugin == null || item == null || __instance == null)
{
return true;
}
if (!(__instance.entityOwner is BuildingPrivlidge))
{
return true;
}
if (!plugin.IsAllowedItem(item))
{
return true;
}
if (plugin.CanStoreInSlot(__instance, item, targetPos))
{
__result = ItemContainer.CanAcceptResult.CanAccept;
}
else
{
__result = ItemContainer.CanAcceptResult.CannotAccept;
}
return false;
}
}
}
}