C#:
using Oxide.Core.Plugins;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using Oxide.Game.Rust.Cui;
using System;
using SilentOrbit.ProtocolBuffers;
using System.Linq;
using System.Diagnostics;
using System.IO;
using Network;
using HarmonyLib;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Plugins.CustomItemDefinitionExtensions;
using UnityEngine.SceneManagement;
using Oxide.Core;
using System.Collections;
using System.Text.RegularExpressions;
namespace Oxide.Plugins
{
[Info("CustomItemDefinitions", "0xF [dsc.gg/0xf-plugins]", "1.3.2")]
[Description("Library of the Future. Allows you to create your own full-fledged custom items with own item definition.")]
public class CustomItemDefinitions : RustPlugin
{
#region Consts
public const ItemDefinition.Flag CUSTOM_DEFINITION_FLAG = (ItemDefinition.Flag)128;
public static readonly ItemDefinition FallbackItemDefinition = ItemManager.FindItemDefinition("coal");
public static readonly ItemDefinition FakeBlueprintItemDefinition = ItemManager.FindItemDefinition("rhib");
private static readonly Regex regexNumeric = new Regex(@"^-?[0-9]+$");
#endregion
#region CustomItemDefinition Class
public class CustomItemDefinition
{
public int parentItemId;
public string shortname;
public int? itemId;
public string defaultName;
public string defaultDescription;
public ulong defaultSkinId;
public int? maxStackSize;
public ItemCategory? category;
public ItemDefinition.Flag flags;
public ItemMod[] itemMods;
public bool repairable;
public bool craftable;
public List<ItemAmount> blueprintIngredients;
public int workbenchLevelRequired;
public static CustomItemDefinition FromObject(object @object)
{
Type cidType = typeof(CustomItemDefinition);
Type argType = @object.GetType();
CustomItemDefinition @new = new CustomItemDefinition();
foreach (PropertyInfo propertyInfo in argType.GetProperties())
SetField(@new, propertyInfo.Name, propertyInfo.GetValue(@object));
foreach (FieldInfo fieldInfo in argType.GetFields())
SetField(@new, fieldInfo.Name, fieldInfo.GetValue(@object));
void SetField(CustomItemDefinition cid, string fieldName, object value)
{
FieldInfo fieldInfo = cidType.GetField(fieldName);
if (fieldInfo == null)
{
PluginInstance?.PrintWarning(string.Format("The field named \"{0}\" is missing in the CustomItemDefinition class, skipped", fieldName));
return;
}
fieldInfo.SetValue(cid, value);
}
return @new;
}
}
#endregion
#region Variables
private static CustomItemDefinitions PluginInstance;
private static Dictionary<int, ItemDefinition> AlreadyCreatedItemDefinitions;
private static Dictionary<Plugin, HashSet<CustomItemDefinition>> CustomDefinitions = new Dictionary<Plugin, HashSet<CustomItemDefinition>>();
#endregion
#region Hooks
void Init()
{
PluginInstance = this;
CacheAlreadyCreated();
FieldsToMutate.Initialize();
#if CARBON
HarmonyInstance.Patch(AccessTools.Method(typeof(Carbon.Core.ModLoader), "UninitializePlugin"), prefix: new HarmonyMethod(typeof(Patches), nameof(Patches.UnloadPlugin)));
#else
HarmonyInstance.Patch(AccessTools.Method(typeof(Oxide.Core.OxideMod), "UnloadPlugin"), prefix: new HarmonyMethod(typeof(Patches), nameof(Patches.UnloadPlugin)));
#endif
HarmonyInstance.Patch(AccessTools.Method(typeof(Item), "Load"), prefix: new HarmonyMethod(typeof(Patches), nameof(Patches.Item_Load1)), postfix: new HarmonyMethod(typeof(Patches), nameof(Patches.Item_Load2)));
HarmonyInstance.Patch(AccessTools.Method(typeof(VendingMachineMapMarker), "GetAppMarkerData"), postfix: new HarmonyMethod(typeof(Patches), nameof(Patches.VendingMachineMapMarker_GetAppMarkerData)));
HarmonyInstance.Patch(AccessTools.Method(typeof(StorageMonitor), "FillEntityPayload"), postfix: new HarmonyMethod(typeof(Patches), nameof(Patches.StorageMonitor_FillEntityPayload)));
HarmonyInstance.Patch(AccessTools.Method(typeof(ProtoBuf.PlayerUpdateLoot), "WriteToStream"), new HarmonyMethod(typeof(Patches), nameof(Patches.PlayerUpdateLoot_WriteToStream)));
HarmonyInstance.Patch(AccessTools.Method(typeof(ProtoBuf.UpdateItem), "WriteToStream"), new HarmonyMethod(typeof(Patches), nameof(Patches.UpdateItem_WriteToStream)));
HarmonyInstance.Patch(AccessTools.Method(typeof(ProtoBuf.UpdateItemContainer), "WriteToStream"), new HarmonyMethod(typeof(Patches), nameof(Patches.UpdateItemContainer_WriteToStream)));
HarmonyInstance.Patch(AccessTools.Method(typeof(ProtoBuf.VendingMachine.SellOrderContainer), "WriteToStream"), new HarmonyMethod(typeof(Patches), nameof(Patches.SellOrderContainer_WriteToStream)));
HarmonyInstance.Patch(AccessTools.Method(typeof(ProtoBuf.ItemAmountList), "WriteToStream"), new HarmonyMethod(typeof(Patches), nameof(Patches.ItemAmountList_WriteToStream)));
HarmonyInstance.Patch(AccessTools.Method(typeof(ProtoBuf.IndustrialConveyor.ItemFilterList), "WriteToStream"), new HarmonyMethod(typeof(Patches), nameof(Patches.ItemFilterList_WriteToStream)));
HarmonyInstance.Patch(AccessTools.Method(typeof(ProtoBuf.IndustrialConveyorTransfer), "WriteToStream"), new HarmonyMethod(typeof(Patches), nameof(Patches.IndustrialConveyorTransfer_WriteToStream)));
HarmonyInstance.Patch(AccessTools.Method(typeof(CuiImageComponent), "get_ItemId"), postfix: new HarmonyMethod(typeof(Patches), nameof(Patches.CuiItemIconPatch)));
}
private void Loaded()
{
Interface.Oxide.CallHook("OnCIDLoaded", this);
}
private void Unload()
{
foreach (Plugin plugin in CustomDefinitions.Keys)
UnloadPluginItemDefinitions(plugin);
CustomDefinitions.Clear();
Interface.Oxide.CallHook("OnCIDUnloaded");
}
private static void OnItemDefinitionBroken(Item item, ProtoBuf.Item protoItem)
{
UnityEngine.Debug.LogWarning("Item has broken definition, the fallback item definition will be applied to it.");
item.info = FallbackItemDefinition;
item.text = protoItem.itemid.ToString();
}
void OnEntitySaved(BaseNetworkable entity, BaseNetworkable.SaveInfo saveInfo)
{
if (!saveInfo.forDisk)
Mutate.ToClientSide(saveInfo.msg);
}
void OnLootNetworkUpdate(PlayerLoot loot)
{
RepairBench repairBench = loot.entitySource as RepairBench;
if (repairBench == null)
return;
Item repairableItem = repairBench.inventory?.GetSlot(0);
if (repairableItem == null)
return;
ItemDefinition itemDefinition = repairableItem.info;
if (!itemDefinition.Blueprint || !itemDefinition.condition.repairable)
return;
BasePlayer player = loot.baseEntity;
if (IsValidCustomItemDefinition(itemDefinition) && player.blueprints.HasUnlocked(itemDefinition))
SendFakeUnlockedBlueprint(player, repairableItem.info.Parent.itemid);
else
player.SendNetworkUpdateImmediate();
}
void OnActiveItemChanged(BasePlayer player, Item oldItem, Item newItem)
{
if (newItem == null || newItem.info != ItemManager.blueprintBaseDef)
return;
ItemDefinition blueprintTargetDef = newItem.blueprintTargetDef;
if (blueprintTargetDef == null)
return;
player.ShowToast(GameTip.Styles.Blue_Normal, $"{blueprintTargetDef.displayName.translated} BLUEPRINT");
}
object OnItemCraft(IndustrialCrafter crafter, ItemBlueprint blueprint)
{
if (IsValidCustomItemDefinition(blueprint.targetItem) && !blueprint.userCraftable)
return false;
return null;
}
#endregion
#region Commands
[ConsoleCommand("itemid")]
private void itemid(ConsoleSystem.Arg arg)
{
if (!arg.IsAdmin)
return;
string shortname = arg.GetString(0);
if (string.IsNullOrEmpty(shortname))
return;
ItemDefinition itemDefinition = ItemManager.FindItemDefinition(shortname);
if (itemDefinition == null)
{
arg.ReplyWith("Item definion for the specified short name was not found.");
return;
}
arg.ReplyWith(itemDefinition.itemid);
}
#endregion
#region Classes
public static class FieldsToMutate
{
public static List<FieldInfo> validFields;
public static void Initialize()
{
validFields = CollectFields(typeof(ProtoBuf.Entity));
}
private static List<FieldInfo> CollectFields(Type type)
{
List<FieldInfo> result = new List<FieldInfo>();
foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
{
if (IsValidField(field))
{
result.Add(field);
}
else if (typeof(IProto).IsAssignableFrom(field.FieldType))
{
List<FieldInfo> collectedFields = CollectFields(field.FieldType);
if (collectedFields.Count > 0)
{
result.Add(field);
result.AddRange(CollectFields(field.FieldType));
}
}
}
return result;
}
public static bool IsValidField(FieldInfo field)
{
return Mutate.TypeToMethodMap.ContainsKey(field.FieldType);
}
}
public static class Mutate
{
public static readonly Dictionary<Type, Action<object>> TypeToMethodMap = new Dictionary<Type, Action<object>>
{
{ typeof(ProtoBuf.Item), obj => ToClientSide((ProtoBuf.Item)obj) },
{ typeof(ProtoBuf.ItemContainer), obj => ToClientSide((ProtoBuf.ItemContainer)obj) },
{ typeof(ProtoBuf.VendingMachine.SellOrder), obj => ToClientSide((ProtoBuf.VendingMachine.SellOrder)obj) },
{ typeof(ProtoBuf.WeaponRackItem), obj => ToClientSide((ProtoBuf.WeaponRackItem)obj) },
{ typeof(ProtoBuf.IndustrialConveyor.ItemFilter), obj => ToClientSide((ProtoBuf.IndustrialConveyor.ItemFilter)obj) },
{ typeof(ProtoBuf.ItemCrafter.Task), obj => ToClientSide((ProtoBuf.ItemCrafter.Task)obj) },
{ typeof(ProtoBuf.AppEntityPayload.Item), obj => ToClientSide((ProtoBuf.AppEntityPayload.Item)obj) },
{ typeof(ProtoBuf.ItemAmountList), obj => ToClientSide((ProtoBuf.ItemAmountList)obj) },
{ typeof(ProtoBuf.FrankensteinTable), obj => ToClientSide((ProtoBuf.FrankensteinTable)obj) },
{ typeof(List<ProtoBuf.Item>), obj => ToClientSide((List<ProtoBuf.Item>)obj) },
{ typeof(List<ProtoBuf.ItemContainer>), obj => ToClientSide((List<ProtoBuf.ItemContainer>)obj) },
{ typeof(List<ProtoBuf.VendingMachine.SellOrder>), obj => ToClientSide((List<ProtoBuf.VendingMachine.SellOrder>)obj) },
{ typeof(List<ProtoBuf.WeaponRackItem>), obj => ToClientSide((List<ProtoBuf.WeaponRackItem>)obj) },
{ typeof(List<ProtoBuf.IndustrialConveyor.ItemFilter>), obj => ToClientSide((List<ProtoBuf.IndustrialConveyor.ItemFilter>)obj) },
{ typeof(List<ProtoBuf.ItemCrafter.Task>), obj => ToClientSide((List<ProtoBuf.ItemCrafter.Task>)obj) },
{ typeof(List<ProtoBuf.AppEntityPayload.Item>), obj => ToClientSide((List<ProtoBuf.AppEntityPayload.Item>)obj) },
};
public static void ToClientSide(ProtoBuf.Entity protoEntity)
{
if (FieldsToMutate.validFields == null)
return;
Stack<object> stack = new Stack<object>();
stack.Push(protoEntity);
for (int i = 0; i < FieldsToMutate.validFields.Count; i++)
{
FieldInfo field = FieldsToMutate.validFields[i];
object peek = stack.Peek();
if (field.DeclaringType == peek.GetType())
{
object fieldValue = field.GetValue(peek);
if (fieldValue != null)
{
if (FieldsToMutate.IsValidField(field))
{
Mutate.ToClientSide(fieldValue);
while (i + 1 < FieldsToMutate.validFields.Count && FieldsToMutate.validFields[i + 1]?.DeclaringType != peek.GetType())
{
stack.Pop();
peek = stack.Peek();
}
}
else
{
stack.Push(fieldValue);
}
}
}
}
}
public static void ToClientSide(object obj)
{
if (TypeToMethodMap.TryGetValue(obj.GetType(), out var method))
{
method.Invoke(obj);
}
else
{
UnityEngine.Debug.LogError("Unknown type to mutate: " + obj.GetType());
}
}
public static void ToClientSide(ProtoBuf.VendingMachine.SellOrderContainer sellOrderContainer)
{
if (sellOrderContainer.sellOrders != null)
ToClientSide(sellOrderContainer.sellOrders);
}
public static void ToClientSide(List<ProtoBuf.VendingMachine.SellOrder> sellOrders)
{
foreach (ProtoBuf.VendingMachine.SellOrder order in sellOrders)
ToClientSide(order);
}
public static void ToClientSide(ProtoBuf.VendingMachine.SellOrder sellOrder)
{
ItemDefinition itemDefToSell = ItemManager.FindItemDefinition(sellOrder.itemToSellID);
if (IsValidCustomItemDefinition(itemDefToSell))
sellOrder.itemToSellID = itemDefToSell.Parent.itemid;
ItemDefinition currencyDef = ItemManager.FindItemDefinition(sellOrder.currencyID);
if (IsValidCustomItemDefinition(currencyDef))
sellOrder.currencyID = currencyDef.Parent.itemid;
}
public static void ToClientSide(List<ProtoBuf.WeaponRackItem> weaponRackItems)
{
foreach (ProtoBuf.WeaponRackItem weaponRackItem in weaponRackItems)
ToClientSide(weaponRackItem);
}
public static void ToClientSide(ProtoBuf.WeaponRackItem rackItem)
{
ItemDefinition itemDef = ItemManager.FindItemDefinition(rackItem.itemID);
if (IsValidCustomItemDefinition(itemDef))
{
rackItem.itemID = itemDef.Parent.itemid;
if (rackItem.skinid == 0)
{
DefaultProperties defaultProperties = itemDef.GetComponent<DefaultProperties>();
if (defaultProperties != null && defaultProperties.skinId != 0UL)
rackItem.skinid = defaultProperties.skinId;
}
}
}
public static void ToClientSide(List<ProtoBuf.IndustrialConveyor.ItemFilter> filters)
{
foreach (ProtoBuf.IndustrialConveyor.ItemFilter filter in filters)
ToClientSide(filter);
}
public static void ToClientSide(ProtoBuf.IndustrialConveyor.ItemFilter filter)
{
ItemDefinition itemDef = ItemManager.FindItemDefinition(filter.itemDef);
if (IsValidCustomItemDefinition(itemDef))
filter.itemDef = itemDef.Parent.itemid;
}
public static void ToClientSide(List<ProtoBuf.IndustrialConveyorTransfer.ItemTransfer> transfers)
{
for (int i = 0; i < transfers.Count; i++)
{
ProtoBuf.IndustrialConveyorTransfer.ItemTransfer transfer = transfers[i];
ItemDefinition itemDef = ItemManager.FindItemDefinition(transfers[i].itemId);
if (IsValidCustomItemDefinition(itemDef))
transfers[i] = new ProtoBuf.IndustrialConveyorTransfer.ItemTransfer { itemId = itemDef.Parent.itemid, amount = transfer.amount };
}
}
public static void ToClientSide(List<ProtoBuf.ItemCrafter.Task> tasks)
{
foreach (ProtoBuf.ItemCrafter.Task task in tasks)
ToClientSide(tasks);
}
public static void ToClientSide(ProtoBuf.ItemCrafter.Task task)
{
ItemDefinition itemDef = ItemManager.FindItemDefinition(task.itemID);
if (IsValidCustomItemDefinition(itemDef))
{
task.itemID = itemDef.Parent.itemid;
if (task.skinID == 0)
{
DefaultProperties defaultProperties = itemDef.GetComponent<DefaultProperties>();
if (defaultProperties != null && defaultProperties.skinId != 0UL)
task.skinID = (int)defaultProperties.skinId;
}
}
}
public static void ToClientSide(ProtoBuf.ItemAmountList itemAmountList)
{
if (itemAmountList.itemID == null)
return;
for (int i = 0; i < itemAmountList.itemID.Count; i++)
{
ItemDefinition itemDef = ItemManager.FindItemDefinition(itemAmountList.itemID[i]);
if (IsValidCustomItemDefinition(itemDef))
itemAmountList.itemID[i] = itemDef.Parent.itemid;
}
}
public static void ToClientSide(List<ProtoBuf.AppEntityPayload.Item> items)
{
foreach (ProtoBuf.AppEntityPayload.Item item in items)
ToClientSide(item);
}
public static void ToClientSide(ProtoBuf.AppEntityPayload.Item item)
{
ItemDefinition itemDef = ItemManager.FindItemDefinition(item.itemId);
if (IsValidCustomItemDefinition(itemDef))
{
item.itemId = itemDef.Parent.itemid;
}
}
public static void ToClientSide(List<ProtoBuf.AppMarker.SellOrder> sellOrders)
{
foreach (ProtoBuf.AppMarker.SellOrder sellOrder in sellOrders)
ToClientSide(sellOrder);
}
public static void ToClientSide(ProtoBuf.AppMarker.SellOrder sellOrder)
{
ItemDefinition itemDef = ItemManager.FindItemDefinition(sellOrder.itemId);
if (IsValidCustomItemDefinition(itemDef))
sellOrder.itemId = itemDef.Parent.itemid;
}
public static void ToClientSide(ProtoBuf.FrankensteinTable frankensteinTable)
{
if (frankensteinTable.itemIds == null)
return;
for (int i = 0; i < frankensteinTable.itemIds.Count; i++)
{
ItemDefinition itemDef = ItemManager.FindItemDefinition(frankensteinTable.itemIds[i]);
if (IsValidCustomItemDefinition(itemDef))
frankensteinTable.itemIds[i] = itemDef.Parent.itemid;
}
}
public static void ToClientSide(List<ProtoBuf.ItemContainer> containers)
{
foreach (ProtoBuf.ItemContainer container in containers)
ToClientSide(container);
}
public static void ToClientSide(ProtoBuf.ItemContainer container)
{
if (container.contents != null)
ToClientSide(container.contents);
}
public static void ToClientSide(List<ProtoBuf.Item> items)
{
foreach (ProtoBuf.Item item in items)
ToClientSide(item);
}
public static void ToClientSide(ProtoBuf.Item protoItem)
{
ItemDefinition itemDefinition = ItemManager.FindItemDefinition(protoItem.itemid);
if (itemDefinition == null)
{
protoItem.itemid = FallbackItemDefinition.itemid;
return;
}
if (protoItem.contents != null)
ToClientSide(protoItem.contents);
bool IsBlueprint = itemDefinition == ItemManager.blueprintBaseDef;
if (IsBlueprint && protoItem.instanceData != null)
{
ItemDefinition blueprintItemDef = ItemManager.FindItemDefinition(protoItem.instanceData.blueprintTarget);
if (blueprintItemDef == null)
return;
itemDefinition = blueprintItemDef;
}
if (!IsValidCustomItemDefinition(itemDefinition))
return;
if (!IsBlueprint)
{
protoItem.itemid = itemDefinition.Parent.itemid;
DefaultProperties defaultProperties = itemDefinition.GetComponent<DefaultProperties>();
if (string.IsNullOrEmpty(protoItem.name) && defaultProperties.name != null)
protoItem.name = defaultProperties.name;
if (protoItem.skinid == 0UL && defaultProperties.skinId != 0UL)
protoItem.skinid = defaultProperties.skinId;
}
else if (protoItem.instanceData != null)
{
protoItem.instanceData = new ProtoBuf.Item.InstanceData() { ShouldPool = true };
protoItem.instanceData.blueprintTarget = FakeBlueprintItemDefinition.itemid;
}
if (itemDefinition.displayDescription != null && !itemDefinition.displayDescription.IsValid())
{
if (protoItem.ownership == null)
protoItem.ownership = Facepunch.Pool.Get<List<ProtoBuf.ItemOwnershipAmount>>();
ProtoBuf.ItemOwnershipAmount itemOwnershipAmount = new ProtoBuf.ItemOwnershipAmount();
itemOwnershipAmount.username = "ITEM DESCRIPTION";
itemOwnershipAmount.reason = itemDefinition.displayDescription.translated;
itemOwnershipAmount.amount = protoItem.amount;
protoItem.ownership.Add(itemOwnershipAmount);
}
}
}
#endregion
#region Methods
private object Register(object @object, Plugin plugin)
{
if (@object is IEnumerable enumerable)
{
List<CustomItemDefinition> list = new List<CustomItemDefinition>();
foreach (object item in enumerable)
list.Add(CustomItemDefinition.FromObject(item));
return RegisterPluginItemDefinitions(list, plugin);
}
else
{
return RegisterPluginItemDefinition(CustomItemDefinition.FromObject(@object), plugin);
}
}
private ulong GetSkin(int itemId)
{
ItemDefinition itemDef = ItemManager.FindItemDefinition(itemId);
if (IsValidCustomItemDefinition(itemDef) && itemDef.TryGetComponent(out DefaultProperties defaultProperties))
return defaultProperties.skinId;
return 0UL;
}
private static void CacheAlreadyCreated()
{
if (AlreadyCreatedItemDefinitions == null)
{
AlreadyCreatedItemDefinitions = new Dictionary<int, ItemDefinition>();
if (ServerMgr.Instance != null)
{
foreach (GameObject gameObject in ServerMgr.Instance.gameObject.scene.GetRootGameObjects())
{
if (!regexNumeric.IsMatch(gameObject.name))
continue;
if (gameObject.TryGetComponent(out ItemDefinition itemDefinition))
AlreadyCreatedItemDefinitions[itemDefinition.itemid] = itemDefinition;
}
}
}
}
public static HashSet<ItemDefinition> RegisterPluginItemDefinitions(IEnumerable<CustomItemDefinition> definitions, Plugin plugin)
{
HashSet<ItemDefinition> hashSet = new HashSet<ItemDefinition>();
foreach (var definition in definitions)
{
ItemDefinition resultItemDefinition = RegisterPluginItemDefinition(definition, plugin);
if (resultItemDefinition != null)
hashSet.Add(resultItemDefinition);
};
return hashSet;
}
public static ItemDefinition RegisterPluginItemDefinition(CustomItemDefinition definition, Plugin plugin)
{
if (!definition.itemId.HasValue)
definition.itemId = definition.shortname.GetHashCode();
HashSet<CustomItemDefinition> pluginHashSet = null;
if (!CustomDefinitions.TryGetValue(plugin, out pluginHashSet))
pluginHashSet = (CustomDefinitions[plugin] = new HashSet<CustomItemDefinition>());
if (pluginHashSet.Contains(definition))
{
PluginInstance.PrintError("Error by the plugin \"{0}\": The provided CustomItemDefinition ({1}) is already contained", plugin.Name, definition.shortname);
return null;
}
if (string.IsNullOrEmpty(definition.shortname) || definition.parentItemId == 0)
{
PluginInstance.PrintError("Error of incorrect data provided by the plugin \"{0}\": The fields shortname, parentItemId is required", plugin.Name);
return null;
}
else if (ItemManager.FindItemDefinition(definition.shortname) != null)
{
PluginInstance.PrintError("Error of incorrect data provided by the plugin \"{0}\": Shortname must be unique! The provided shortname - \"{1}\"", plugin.Name, definition.shortname);
return null;
}
else if (ItemManager.FindItemDefinition(definition.itemId.Value) != null)
{
PluginInstance.PrintError("Error of incorrect data provided by the plugin \"{0}\": ItemId must be unique! The provided itemId - \"{1}\"", plugin.Name, definition.itemId);
return null;
}
ItemDefinition parentItemDefinition = ItemManager.FindItemDefinition(definition.parentItemId);
if (parentItemDefinition == null)
{
PluginInstance.PrintError("Error of incorrect data provided by the plugin \"{0}\": ItemDefinition by parentItemId not found! The provided parentItemId - \"{1}\"", plugin.Name, definition.parentItemId);
return null;
}
if (parentItemDefinition.gameObject.scene.isLoaded)
{
PluginInstance.PrintError("Error by the plugin \"{0}\": You cannot use a custom ItemDefinition as a parent!", plugin.Name);
return null;
}
ItemDefinition newItemDefinition;
if (!AlreadyCreatedItemDefinitions.TryGetValue(definition.itemId.Value, out newItemDefinition))
{
if (newItemDefinition == null)
{
newItemDefinition = CloneItemDefinition(parentItemDefinition);
newItemDefinition.name = definition.itemId.ToString();
}
AlreadyCreatedItemDefinitions[definition.itemId.Value] = newItemDefinition;
}
if (newItemDefinition == null)
{
PluginInstance.PrintError("Error by the plugin \"{0}\": Failure to create or search for a new item definition ({1})!", plugin.Name, definition.shortname);
return null;
}
newItemDefinition.shortname = definition.shortname;
newItemDefinition.itemid = definition.itemId.Value;
if (definition.defaultName != null)
newItemDefinition.displayName = definition.defaultName;
if (definition.defaultDescription != null)
newItemDefinition.displayDescription = definition.defaultDescription;
newItemDefinition.Parent = parentItemDefinition;
newItemDefinition.flags = CUSTOM_DEFINITION_FLAG;
newItemDefinition.flags |= definition.flags;
if (definition.category.HasValue)
newItemDefinition.category = definition.category.Value;
if (definition.maxStackSize.HasValue)
newItemDefinition.stackable = definition.maxStackSize.Value;
newItemDefinition.condition.repairable = definition.repairable;
newItemDefinition.itemMods = null;
foreach (ItemMod mod in newItemDefinition.GetComponentsInChildren<ItemMod>(true))
UnityEngine.Object.DestroyImmediate(mod);
if (!newItemDefinition.TryGetComponent(out ItemBlueprint itemBlueprint) && (definition.craftable || definition.repairable))
itemBlueprint = newItemDefinition.gameObject.AddComponent<ItemBlueprint>();
if (itemBlueprint)
{
itemBlueprint.defaultBlueprint = false;
itemBlueprint.userCraftable = definition.craftable;
itemBlueprint.isResearchable = definition.craftable || definition.repairable;
itemBlueprint.workbenchLevelRequired = definition.workbenchLevelRequired;
itemBlueprint.ingredients = definition.blueprintIngredients != null && definition.blueprintIngredients.Count > 0 ? definition.blueprintIngredients : parentItemDefinition.Blueprint?.ingredients ?? new List<ItemAmount>();
}
DefaultProperties defaultProperties = newItemDefinition.gameObject.AddComponent<DefaultProperties>();
defaultProperties.name = definition.defaultName;
defaultProperties.description = definition.defaultDescription;
defaultProperties.skinId = definition.defaultSkinId;
if (definition.itemMods != null)
{
foreach (ItemMod mod in definition.itemMods)
{
if ((mod as object) == null)
continue;
Type type = mod.GetType();
Component component = newItemDefinition.gameObject.AddComponent(type);
mod.CopyFields(component as ItemMod);
}
}
newItemDefinition.Initialize(ItemManager.itemList);
ItemManager.itemDictionary[definition.itemId.Value] = newItemDefinition;
ItemManager.itemDictionaryByName[definition.shortname] = newItemDefinition;
if (!ItemManager.itemList.Contains(newItemDefinition))
ItemManager.itemList.Add(newItemDefinition);
pluginHashSet.Add(definition);
return newItemDefinition;
}
public static void UnloadPluginItemDefinitions(Plugin plugin)
{
if (CustomDefinitions.TryGetValue(plugin, out HashSet<CustomItemDefinition> definitions))
{
foreach (var definition in definitions)
UnloadCustomItemDefinition(definition);
definitions.Clear();
}
}
public static void UnloadPluginItemDefinition(Plugin plugin, CustomItemDefinition definition)
{
if (CustomDefinitions.TryGetValue(plugin, out HashSet<CustomItemDefinition> definitions))
{
if (definitions.Contains(definition))
{
UnloadCustomItemDefinition(definition);
definitions.Remove(definition);
}
}
}
private static void UnloadCustomItemDefinition(CustomItemDefinition definition)
{
if (ItemManager.itemDictionaryByName.TryGetValue(definition.shortname, out ItemDefinition itemDefinition))
{
itemDefinition.itemMods = null;
foreach (ItemMod mod in itemDefinition.GetComponentsInChildren<ItemMod>(true))
UnityEngine.Object.DestroyImmediate(mod);
itemDefinition.Initialize(ItemManager.itemList);
ItemManager.itemDictionary.Remove(definition.itemId.Value);
ItemManager.itemDictionaryByName.Remove(definition.shortname);
ItemManager.itemList.Remove(itemDefinition);
}
}
private static ItemDefinition CloneItemDefinition(ItemDefinition itemDefinition)
{
if (itemDefinition == null)
return null;
GameObject clone = UnityEngine.Object.Instantiate(itemDefinition.gameObject);
UnityEngine.Object.DontDestroyOnLoad(clone);
return clone.GetComponent<ItemDefinition>();
}
private static ItemDefinition CloneItemDefinition(string shortname) => CloneItemDefinition(ItemManager.FindItemDefinition(shortname));
private static ItemDefinition CloneItemDefinition(int itemId) => CloneItemDefinition(ItemManager.FindItemDefinition(itemId));
private static bool IsValidCustomItemDefinition(ItemDefinition itemDefinition)
{
if (itemDefinition == null)
return false;
return itemDefinition.HasFlag(CUSTOM_DEFINITION_FLAG) && itemDefinition.Parent != null;
}
private static void SendSnapshotWithUnlockedBlueprint(BasePlayer player, int itemId)
{
if (player == null || player.net == null || !player.IsConnected)
return;
Network.Connection connection = player.net.connection;
try
{
NetWrite netWrite = Network.Net.sv.StartWrite();
global::BaseNetworkable.SaveInfo saveInfo = new global::BaseNetworkable.SaveInfo
{
forConnection = player.net.connection,
forDisk = false
};
netWrite.PacketID(Message.Type.Entities);
netWrite.UInt32(connection.validate.entityUpdates + 1U);
using (saveInfo.msg = Facepunch.Pool.Get<ProtoBuf.Entity>())
{
player.Save(saveInfo);
ProtoBuf.PersistantPlayer persistantData = saveInfo.msg.basePlayer.persistantData;
if (persistantData != null && persistantData.unlockedItems != null && !persistantData.unlockedItems.Contains(itemId))
persistantData.unlockedItems.Add(itemId);
saveInfo.msg.WriteToStream(netWrite);
}
netWrite.Send(new SendInfo(player.net.connection));
}
catch (Exception ex)
{
UnityEngine.Debug.LogException(ex);
}
finally
{
connection.validate.entityUpdates++;
}
}
private static void SendFakeUnlockedBlueprint(BasePlayer player, int itemId)
{
SendSnapshotWithUnlockedBlueprint(player, itemId);
player.ClientRPCPlayer<int>(null, player, "UnlockedBlueprint", 0); // [Clientside RPC] Update blueprints
}
private static string FormatNameWithDescription(string name, string description)
{
if (!string.IsNullOrEmpty(description))
return $"{string.Concat(Enumerable.Repeat("\n", description.Count(c => c == '\n')))}\n{name}\n\t\t\t\t\t\t\t\t\t\t\t\t{description.Replace("\n", "\n\t\t\t\t\t\t\t\t\t\t\t\t")}";
return name;
}
#endregion
#region Default Mods
private class DefaultProperties : ItemMod
{
public string name;
public string description;
public ulong skinId;
}
#endregion
#region Converters
public class ItemAmountConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteNull();
return;
}
ItemAmount itemAmount = (ItemAmount)value;
writer.WriteStartObject();
writer.WritePropertyName("ShortName");
writer.WriteValue(itemAmount.itemDef?.shortname);
writer.WritePropertyName("Amount");
writer.WriteValue(itemAmount.amount);
writer.WriteEndObject();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ItemAmount);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
JObject jobject = JObject.Load(reader);
ItemDefinition itemDefinition;
string shortname = (string)jobject["ShortName"];
if (string.IsNullOrEmpty(shortname))
itemDefinition = null;
else
itemDefinition = ItemManager.FindItemDefinition(shortname);
return new ItemAmount(itemDefinition, (float)jobject["Amount"]);
}
public override bool CanRead
{
get
{
return true;
}
}
}
#endregion
#region Harmony Patches
internal static class Patches
{
#if CARBON
internal static void UnloadPlugin(RustPlugin plugin)
{
if (CustomDefinitions.ContainsKey(plugin))
UnloadPluginItemDefinitions(plugin);
}
#else
internal static void UnloadPlugin(string name)
{
Plugin plugin = Oxide.Core.Interface.Oxide.RootPluginManager.GetPlugin(name);
if (plugin == null || (plugin.IsCorePlugin && !Oxide.Core.Interface.Oxide.IsShuttingDown))
return;
if (CustomDefinitions.ContainsKey(plugin))
UnloadPluginItemDefinitions(plugin);
}
#endif
internal static void Item_Load1(Item __instance, ProtoBuf.Item __0)
{
if (__0.itemid == FallbackItemDefinition.itemid &&
__0.text != null &&
int.TryParse(__0.text, out int restoreItemId) &&
ItemManager.itemDictionary.ContainsKey(restoreItemId))
__0.itemid = restoreItemId;
}
internal static void Item_Load2(Item __instance, ProtoBuf.Item __0)
{
if (__instance.info == null)
OnItemDefinitionBroken(__instance, __0);
}
internal static void VendingMachineMapMarker_GetAppMarkerData(ref ProtoBuf.AppMarker __result)
{
if (__result.sellOrders != null)
Mutate.ToClientSide(__result.sellOrders);
}
internal static void StorageMonitor_FillEntityPayload(ref ProtoBuf.AppEntityPayload payload)
{
if (payload.items != null)
Mutate.ToClientSide(payload.items);
}
internal static bool PlayerUpdateLoot_WriteToStream(ProtoBuf.PlayerUpdateLoot __instance)
{
if (__instance.containers != null)
Mutate.ToClientSide(__instance.containers);
return true;
}
internal static bool UpdateItem_WriteToStream(ProtoBuf.UpdateItem __instance)
{
if (__instance.item != null)
Mutate.ToClientSide(__instance.item);
return true;
}
internal static bool UpdateItemContainer_WriteToStream(ProtoBuf.UpdateItemContainer __instance)
{
if (__instance.container != null)
Mutate.ToClientSide(__instance.container);
return true;
}
internal static bool SellOrderContainer_WriteToStream(ref ProtoBuf.VendingMachine.SellOrderContainer __instance)
{
if (__instance.sellOrders != null)
{
__instance = __instance.Copy();
Mutate.ToClientSide(__instance.sellOrders);
}
return true;
}
internal static bool ItemAmountList_WriteToStream(ProtoBuf.ItemAmountList __instance)
{
if (__instance.itemID != null)
Mutate.ToClientSide(__instance);
return true;
}
internal static bool ItemFilterList_WriteToStream(ProtoBuf.IndustrialConveyor.ItemFilterList __instance)
{
if (__instance.filters != null)
Mutate.ToClientSide(__instance.filters);
return true;
}
internal static bool IndustrialConveyorTransfer_WriteToStream(ProtoBuf.IndustrialConveyorTransfer __instance)
{
if (__instance.ItemTransfers != null)
Mutate.ToClientSide(__instance.ItemTransfers);
return true;
}
internal static void CuiItemIconPatch(CuiImageComponent __instance, ref int __result)
{
ItemDefinition itemDef = ItemManager.FindItemDefinition(__result);
if (itemDef == null)
return;
if (IsValidCustomItemDefinition(itemDef) && itemDef.TryGetComponent(out DefaultProperties defaultProperties))
{
__result = itemDef.Parent.itemid;
if (__instance.SkinId == 0)
__instance.SkinId = defaultProperties.skinId;
}
}
}
#endregion
}
}
#region Extensions
namespace Oxide.Plugins.CustomItemDefinitionExtensions
{
public static class ItemModExtensions
{
public static void CopyFields(this ItemMod from, ItemMod to)
{
if ((from as object) == null || (to as object) == null)
return;
Type type = from.GetType();
do
{
foreach (FieldInfo field in from.GetType().GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetField))
field.SetValue(to, field.GetValue(from));
if (type == typeof(ItemMod))
break;
type = type.BaseType;
}
while (type != null);
}
}
}
#endregion