using Oxide.Core.Plugins; using System.Collections.Generic; using System.Reflection; using UnityEngine; using Oxide.Game.Rust.Cui; using System; #if CARBON using HarmonyLib; #else // Reference: 0Harmony using Harmony; #endif namespace Oxide.Plugins { [Info("CustomItemDefinitions", "0xF [dsc.gg/0xf-plugins]", "1.0.1")] public class CustomItemDefinitions : RustPlugin { #region Consts public const ItemDefinition.Flag CUSTOM_DEFINITION_FLAG = (ItemDefinition.Flag)128; public static readonly ItemDefinition FallbackItemDefinition = ItemManager.FindItemDefinition("coal"); #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; } #endregion #region Variables private static Dictionary> CustomDefinitions = new Dictionary>(); private static CustomItemDefinitions PluginInstance; #if CARBON private static HarmonyLib.Harmony HarmonyInstance = new HarmonyLib.Harmony("com.0xf.customitemdefinitions"); #else private static HarmonyInstance HarmonyInstance = HarmonyInstance.Create("com.0xf.customitemdefinitions"); #endif #endregion #region Hooks void Init() { PluginInstance = this; if (!HarmonyInstance.HasAnyPatches(HarmonyInstance.Id)) { HarmonyInstance.Patch(AccessTools.Method(typeof(Item), "Load"), prefix: new HarmonyMethod(typeof(Patches), "Item_Load1"), postfix: new HarmonyMethod(typeof(Patches), "Item_Load2")); HarmonyInstance.Patch(AccessTools.Method(typeof(Item), "Save"), postfix: new HarmonyMethod(typeof(Patches), "Item_Save")); HarmonyInstance.Patch(AccessTools.Method(typeof(CuiImageComponent), "get_ItemId"), postfix: new HarmonyMethod(typeof(Patches), "CUI_ItemID_PATCH")); HarmonyInstance.Patch(AccessTools.Method(typeof(CuiImageComponent), "get_SkinId"), postfix: new HarmonyMethod(typeof(Patches), "CUI_SkinID_PATCH")); } } void Unload() { HarmonyInstance.UnpatchAll(HarmonyInstance.Id); } void OnPluginUnloaded(Plugin plugin) { if (CustomDefinitions.ContainsKey(plugin)) { UnloadPluginItemDefinitions(plugin); } } 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(); } #endregion #region Methods public static HashSet RegisterPluginItemDefinitions(IEnumerable definitions, Plugin plugin) { HashSet hashSet = new HashSet(); 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) { HashSet pluginCustomDefinitions = null; if (!CustomDefinitions.TryGetValue(plugin, out pluginCustomDefinitions)) pluginCustomDefinitions = (CustomDefinitions[plugin] = new HashSet()); if (pluginCustomDefinitions.Contains(definition)) { PluginInstance.PrintError("Error by the plugin \"{0}\": The provided CustomItemDefinition is already contained", plugin.Name); return null; } if (string.IsNullOrEmpty(definition.shortname) || definition.itemId == 0 || definition.parentItemId == 0) { PluginInstance.PrintError("Error of incorrect data provided by the plugin \"{0}\": The fields shortname, itemId and 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) != 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 = null; if ((newItemDefinition = GameObject.Find(definition.itemId.ToString())?.GetComponent()) == null) { newItemDefinition = CloneItemDefinition(parentItemDefinition); newItemDefinition.name = definition.itemId.ToString(); } 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; 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.itemMods = null; foreach (ItemMod mod in newItemDefinition.GetComponentsInChildren(true)) UnityEngine.Object.DestroyImmediate(mod); DefaultChangerMod defaultChanger = newItemDefinition.gameObject.AddComponent(); defaultChanger.name = definition.defaultName; defaultChanger.skinId = definition.defaultSkinId; if (definition.itemMods != null) { foreach (ItemMod mod in definition.itemMods) { Component addedMod = newItemDefinition.gameObject.AddComponent(mod.GetType()); foreach (var field in mod.GetType().GetFields((BindingFlags)(-1))) field.SetValue(addedMod, field.GetValue(mod)); } } newItemDefinition.Initialize(ItemManager.itemList); ItemManager.itemDictionary[definition.itemId] = newItemDefinition; ItemManager.itemDictionaryByName[definition.shortname] = newItemDefinition; if (!ItemManager.itemList.Contains(newItemDefinition)) ItemManager.itemList.Add(newItemDefinition); CustomDefinitions[plugin].Add(definition); return newItemDefinition; } public static void UnloadPluginItemDefinitions(Plugin plugin) { if (CustomDefinitions.TryGetValue(plugin, out HashSet definitions)) foreach (var definition in definitions) UnloadCustomItemDefinition(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(true)) UnityEngine.Object.DestroyImmediate(mod); itemDefinition.Initialize(ItemManager.itemList); ItemManager.itemDictionary.Remove(definition.itemId); ItemManager.itemDictionaryByName.Remove(definition.shortname); ItemManager.itemList.Remove(itemDefinition); } } private static ItemDefinition CloneItemDefinition(ItemDefinition itemDefinition) { if (itemDefinition == null) return null; return UnityEngine.Object.Instantiate(itemDefinition.gameObject).GetComponent(); } private static ItemDefinition CloneItemDefinition(string shortname) => CloneItemDefinition(ItemManager.FindItemDefinition(shortname)); private static ItemDefinition CloneItemDefinition(int itemId) => CloneItemDefinition(ItemManager.FindItemDefinition(itemId)); #endregion #region Default Mods public class DefaultChangerMod : ItemMod { public string name; public ulong skinId; public override void OnChanged(Item item) { if (item.name == null && name != null) item.name = name; if (item.skin == 0 && skinId != 0) item.skin = skinId; } } #endregion #region Harmony Patches internal static class Patches { 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 Item_Save(Item __instance, ref ProtoBuf.Item __result) { if (__result == null) return; if (!SaveRestore.IsSaving && __instance.info != null && __instance.info.HasFlag(CUSTOM_DEFINITION_FLAG) && __instance.info.Parent != null) __result.itemid = __instance.info.Parent.itemid; } internal static void CUI_ItemID_PATCH(CuiImageComponent __instance, ref int __result) { ItemDefinition itemDef = ItemManager.FindItemDefinition(__result); if (itemDef == null) return; if (itemDef.HasFlag(CUSTOM_DEFINITION_FLAG)) { __result = itemDef.Parent.itemid; if (__instance.SkinId == 0) __instance.SkinId = itemDef.GetComponent().skinId; } } } #endregion } }