Иконка ресурса

FREE CustomItemDefinitions : 2.1.1 2.1.1

Нет прав для скачивания
C#:
using Facepunch;
using HarmonyLib;
using Network;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Oxide.Plugins.CustomItemDefinitionExtensions;
using SilentOrbit.ProtocolBuffers;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text.RegularExpressions;
using UnityEngine;

#region Library
namespace Oxide.Plugins
{
    [Info("CustomItemDefinitions", "0xF // dsc.gg/0xf-plugins", "2.1.1")]
    [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");
        private static readonly Regex regexNumeric = new Regex(@"^-?[0-9]+$");
        #endregion

        #region CustomItemDefinition Class
        public class CustomItemDefinition : Pool.IPooled
        {
            public int parentItemId;
            public string shortname;
            public int? itemId;
            public Translate.Phrase defaultName;
            public Translate.Phrase 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 List<(Translate.Phrase label, Translate.Phrase text)> staticOwnerships;

            public ItemDefinition ParentDefinition => ItemManager.FindItemDefinition(parentItemId);

            internal void FillEmptyValues()
            {
                if (!itemId.HasValue && !string.IsNullOrEmpty(shortname))
                    itemId = shortname.GetHashCode();

                if (defaultName != null && string.IsNullOrEmpty(defaultName.token) && !string.IsNullOrEmpty(shortname))
                    defaultName.token = shortname;

                if (defaultDescription != null && string.IsNullOrEmpty(defaultDescription.token) && !string.IsNullOrEmpty(shortname))
                    defaultDescription.token = $"{shortname}.desc";
            }

            internal void Validate(RustPlugin plugin)
            {
                if (ContainsDefinitionInHashSet(this, plugin))
                    throw new Exception(string.Format("Error by the plugin \"{0}\": The provided CustomItemDefinition ({1}) is already contained", plugin.Name, shortname));

                if (string.IsNullOrEmpty(shortname) || parentItemId == default)
                    throw new Exception(string.Format("Error of incorrect data provided by the plugin \"{0}\": The fields shortname, parentItemId is required", plugin.Name));

                if (ItemManager.FindItemDefinition(shortname) != null)
                    throw new Exception(string.Format("Error of incorrect data provided by the plugin \"{0}\": Shortname must be unique! The provided shortname - \"{1}\"", plugin.Name, shortname));

                if (ItemManager.FindItemDefinition(itemId.Value) != null)
                    throw new Exception(string.Format("Error of incorrect data provided by the plugin \"{0}\": ItemId must be unique! The provided itemId - \"{1}\"", plugin.Name, itemId));

                ItemDefinition parent = this.ParentDefinition;
                if (parent == null)
                    throw new Exception(string.Format("Error of incorrect data provided by the plugin \"{0}\": ItemDefinition by parentItemId not found! The provided parentItemId - \"{1}\"", plugin.Name, parentItemId));

                if (parent.gameObject.scene.isLoaded)
                    throw new Exception(string.Format("Error by the plugin \"{0}\": You cannot use a custom ItemDefinition as a parent!", plugin.Name));
            }

            public static CustomItemDefinition FromObject(object @object)
            {
                Type cidType = typeof(CustomItemDefinition);
                Type argType = @object.GetType();
                CustomItemDefinition @new = Pool.Get<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;
                    }
                    try
                    {
                        if (fieldInfo.FieldType == typeof(Translate.Phrase) && value is string @string)
                            value = new Translate.Phrase(string.Empty, @string);

                        fieldInfo.SetValue(cid, value);
                    }
                    catch (Exception ex)
                    {
                        PluginInstance?.PrintWarning(string.Format("The field named \"{0}\" has error [ {1} ], skipped.", fieldName, ex.Message));
                    }
                }
                return @new;
            }

            public void EnterPool()
            {
                parentItemId = default;
                shortname = default;
                itemId = default;
                defaultName = default;
                defaultDescription = default;
                defaultSkinId = default;
                maxStackSize = default;
                category = default;
                flags = default;
                itemMods = default;
                repairable = default;
                craftable = default;
                blueprintIngredients = default;
                workbenchLevelRequired = default;
                staticOwnerships = default;
            }

            public void LeavePool() { }
        }
        #endregion

        #region Variables
        private static CustomItemDefinitions PluginInstance;
        private static Dictionary<int, ItemDefinition> ExistingDefinitions;
        private static Dictionary<Plugin, HashSet<CustomItemDefinition>> CustomDefinitions = new Dictionary<Plugin, HashSet<CustomItemDefinition>>();
        private static Dictionary<Translate.Phrase, RustPlugin> pluginByPhrase = new Dictionary<Translate.Phrase, RustPlugin>();
        private static Dictionary<string, string> blueprintBaseDisplayName = new Dictionary<string, string>();

        private static AccessTools.FieldRef<NetWrite, BufferStream> NetWriteGetBufferStream = AccessTools.FieldRefAccess<NetWrite, BufferStream>(AccessTools.Field("Network.NetWrite:stream"));
        private static AccessTools.FieldRef<RustPlugin, Lang> PluginGetLang = AccessTools.FieldRefAccess<RustPlugin, Lang>(AccessTools.Field(typeof(RustPlugin), "lang"));
#if OXIDE
        private static AccessTools.FieldRef<Lang, Dictionary<string, Dictionary<string, string>>> LangGetLangFiles = AccessTools.FieldRefAccess<Lang, Dictionary<string, Dictionary<string, string>>>(AccessTools.Field(typeof(Lang), "langFiles"));
#endif

        private static ProtoBuf.Item.InstanceData BlueprintInstanceData = new ProtoBuf.Item.InstanceData()
        {
            ShouldPool = false,
            blueprintTarget = 1394042569
        };
        private const ulong BlueprintBaseIconSkinId = 3530644207;

        private bool IsTranslateInitialized { get => Translate.englishBaseStrings != null; }

        #endregion

        #region Hooks
        void Init()
        {
            PluginInstance = this;

            CacheAlreadyCreated();

            Patcher patcher = new Patcher(this, this.HarmonyInstance);
            patcher.PatchNestedClasses();
            patcher.PatchWriteToStreamClass();
        }

        private static string GetBlueprintBaseDisplayName(string language)
        {
            if (blueprintBaseDisplayName.TryGetValue(language, out string value))
                return value;

            TextAsset textAsset = FileSystem.Load<TextAsset>($"assets/localization/{language}/engine.json", true);
            if (textAsset != null)
            {
                Dictionary<string, string> @object = JsonConvert.DeserializeObject<Dictionary<string, string>>(textAsset.text);
                if (@object != null)
                {
                    value = @object["blueprintbase.name"];
                    Save(value);
                }
                @object.Clear();
            }

            if (value == null)
                Save(GetBlueprintBaseDisplayName("en"));


            return value;

            void Save(string value)
            {
                blueprintBaseDisplayName[language] = value;
            }
        }

        void Loaded()
        {
            if (IsTranslateInitialized)
                CallLibraryLoadedHookGlobal();
        }

        private void OnTranslateInitialized()
        {
            CallLibraryLoadedHookGlobal();
        }

        void OnPluginLoaded(Plugin plugin)
        {
            CallLibraryLoadedHook(plugin);
        }

        private void CallLibraryLoadedHookGlobal()
        {
            foreach (Plugin plugin in Interface.Oxide.RootPluginManager.GetPlugins())
                CallLibraryLoadedHook(plugin);
        }

        private void CallLibraryLoadedHook(Plugin plugin)
        {
            if (IsTranslateInitialized && !CustomDefinitions.ContainsKey(plugin))
                plugin.CallHook("OnCIDLoaded", this);
        }

        private void Unload()
        {
            List<Plugin> list = Pool.Get<List<Plugin>>();
            list.AddRange(CustomDefinitions.Keys);

            foreach (Plugin plugin in list)
                UnloadPluginItemDefinitions(plugin);

            Pool.FreeUnmanaged(ref list);

            CustomDefinitions.Clear();
            Interface.Oxide.CallHook("OnCIDUnloaded");
        }

        private void OnEntitySaved(BaseNetworkable entity, BaseNetworkable.SaveInfo saveInfo)
        {
            try
            {
                Mutate.ToClientSide(saveInfo.msg, saveInfo.forConnection);
            }
            catch (Exception ex)
            {
                ProtoWriteToStream.LibraryFinalizer(ex);
            }
        }

        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 (IsValidCID(itemDefinition) && player.blueprints.HasUnlocked(itemDefinition))
                SendFakeUnlockedBlueprint(player, repairableItem.info.Parent.itemid);
            else
                player.SendNetworkUpdateImmediate();
        }

        object OnItemCraft(IndustrialCrafter crafter, ItemBlueprint blueprint)
        {
            if (IsValidCID(blueprint.targetItem) && !blueprint.userCraftable)
                return false;
            return null;
        }

        void OnPlayerLanguageChanged(BasePlayer player)
        {
            PlayerLanguages.OnPlayerLanguageChanged(player.userID);

            {
                player.inventory.containerBelt.dirty = true;
                player.inventory.containerMain.dirty = true;
                player.inventory.containerWear.dirty = true;
            }
        }

        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();
        }

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["itemownership.description.phrase"] = "ITEM DESCRIPTION",
            }, this);
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["itemownership.description.phrase"] = "DESCRIPCIÓN DEL ARTÍCULO",
            }, this, "es-ES");
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["itemownership.description.phrase"] = "ОПИСАНИЕ ПРЕДМЕТА",
            }, this, "ru");
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["itemownership.description.phrase"] = "ОПИС ПРЕДМЕТУ",
            }, this, "uk");
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["itemownership.description.phrase"] = "項目説明",
            }, this, "ja");
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["itemownership.description.phrase"] = "项目描述",
            }, this, "zh-CN");
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["itemownership.description.phrase"] = "項目描述",
            }, this, "zh-TW");
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["itemownership.description.phrase"] = "항목 설명",
            }, this, "ko");
        }
        #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);
        }

        [ConsoleCommand("shortname")]
        private void shortname(ConsoleSystem.Arg arg)
        {
            if (!arg.IsAdmin)
                return;

            int itemId = arg.GetInt(0);
            if (itemId == default)
                return;

            ItemDefinition itemDefinition = ItemManager.FindItemDefinition(itemId);
            if (itemDefinition == null)
            {
                arg.ReplyWith("Item definion for the specified item id was not found.");
                return;
            }

            arg.ReplyWith(itemDefinition.shortname);
        }
        #endregion

        #region Classes
        private static class ProtoWriteToStream
        {
            internal const string HANDLE_METHOD_NAME = nameof(Handle);

            private static Connection GetConnection(BufferStream stream)
            {
                if (NetWritePool.BufferToStream.TryGetValue(stream, out NetWrite netWrite))
                    return netWrite.connections.FirstOrDefault();
                return null;
            }

            private static void Handle(ProtoBuf.Entity __instance, BufferStream stream)
            {
                Connection connection = GetConnection(stream);
                if (connection == null)
                    return;

                Mutate.ToClientSide(__instance, connection);
            }

            private static void Handle(ProtoBuf.PlayerUpdateLoot __instance, BufferStream stream)
            {
                Connection connection = GetConnection(stream);
                if (connection == null)
                    return;

                if (__instance.containers != null)
                    Mutate.ToClientSide(__instance.containers, connection);
            }

            private static void Handle(ProtoBuf.UpdateItem __instance, BufferStream stream)
            {
                Connection connection = GetConnection(stream);
                if (connection == null)
                    return;

                Mutate.ToClientSide(__instance.item, connection);
            }

            private static void Handle(ProtoBuf.UpdateItemContainer __instance, BufferStream stream)
            {
                Connection connection = GetConnection(stream);
                if (connection == null)
                    return;

                Mutate.ToClientSide(__instance.container, connection);
            }

            private static void Handle(ref ProtoBuf.VendingMachine.SellOrderContainer __instance, BufferStream stream)
            {
                __instance = __instance.Copy();
                foreach (ProtoBuf.VendingMachine.SellOrder order in __instance.sellOrders)
                    Mutate.ToClientSide(order);
            }

            private static void Handle(ProtoBuf.VendingMachinePurchaseHistoryMessage __instance, BufferStream stream)
            {
                Mutate.ToClientSide(__instance);
            }

            private static void Handle(ProtoBuf.ItemAmountList __instance, BufferStream stream)
            {
                Connection connection = GetConnection(stream);
                if (connection == null)
                    return;

                List<int> itemID = __instance.itemID;
                int count = itemID.Count;
                for (int i = 0; i < count; i++)
                {
                    int itemId = itemID[i];
                    if (Mutate.Item.ItemId(ref itemId))
                        itemID[i] = itemId;
                }
            }

            private static void Handle(ProtoBuf.AppEntityPayload __instance)
            {
                List<ProtoBuf.AppEntityPayload.Item> items = __instance.items;
                int count = items.Count;
                for (int i = 0; i < count; i++)
                {
                    int itemId = items[i].itemId;
                    if (Mutate.Item.ItemId(ref itemId))
                        items[i].itemId = itemId;
                }
            }

            private static void Handle(ProtoBuf.IndustrialConveyorTransfer __instance)
            {
                List<ProtoBuf.IndustrialConveyorTransfer.ItemTransfer> transfers = __instance.ItemTransfers;
                int count = transfers.Count;
                for (int i = 0; i < count; i++)
                {
                    ProtoBuf.IndustrialConveyorTransfer.ItemTransfer transfer = transfers[i];
                    int itemId = transfer.itemId;
                    if (Mutate.Item.ItemId(ref itemId))
                        transfers[i] = new ProtoBuf.IndustrialConveyorTransfer.ItemTransfer { itemId = itemId, amount = transfer.amount };
                }
            }

            internal static Exception LibraryFinalizer(Exception __exception)
            {
                if (__exception != null)
                    if (__exception != null)
                        Interface.Oxide.LogException($"[{nameof(CustomItemDefinitions)}] There was an error in the client side package handler! This is a very important part! Please inform the developer about it!", __exception);
                return null;
            }

            internal static Exception ExternalFinalizer(Exception __exception)
            {
                if (__exception != null)
                    Interface.Oxide.LogException(string.Format("[{0}]", __exception.TargetSite.DeclaringType), __exception);
                return null;
            }
        }

        private static class PlayerLanguages
        {
            private static Dictionary<ulong, string> playerLanguages = new();

            public static void OnPlayerLanguageChanged(ulong userId)
            {
                UpdatePlayerLanguage(userId);
            }

            private static void UpdatePlayerLanguage(ulong userId)
            {
                playerLanguages[userId] = PluginInstance.lang.GetLanguage(userId.ToString());
            }

            public static string GetPlayerLanguage(ulong userId)
            {
                if (!playerLanguages.TryGetValue(userId, out string lang))
                {
                    UpdatePlayerLanguage(userId);
                    lang = playerLanguages[userId];
                }
                return lang;
            }
        }

        private static class Mutate
        {
            public class Item
            {
                public class Dictionaries
                {
                    public static Dictionary<int, int> ItemId = new();
                    public static Dictionary<int, ulong> SkinId = new();
                    public static Dictionary<int, Translate.Phrase> NamePhrase = new();
                    public static Dictionary<int, Translate.Phrase> DescriptionPhrase = new();
                    public static Dictionary<int, List<(Translate.Phrase label, Translate.Phrase text)>> StaticOwnerships = new();

                    public static void Remove(int itemID)
                    {
                        ItemId.Remove(itemID);
                        SkinId.Remove(itemID);
                        NamePhrase.Remove(itemID);
                        StaticOwnerships.Remove(itemID);
                    }
                }

                private static void AddOwnership(ProtoBuf.Item item, string label, string text)
                {
                    item.ownership ??= Pool.Get<List<ProtoBuf.ItemOwnershipAmount>>();
                    ProtoBuf.ItemOwnershipAmount itemOwnershipAmount = Pool.Get<ProtoBuf.ItemOwnershipAmount>();
                    itemOwnershipAmount.username = label;
                    itemOwnershipAmount.reason = text;
                    itemOwnershipAmount.amount = item.amount;
                    item.ownership.Add(itemOwnershipAmount);
                }

                public static bool IsBlueprint(ProtoBuf.Item item) => item.instanceData != null && item.instanceData.blueprintTarget != default;

                public static bool ItemId(ref int itemID)
                {
                    if (Dictionaries.ItemId.TryGetValue(itemID, out int target))
                    {
                        itemID = target;
                        return true;
                    }
                    return false;
                }

                public static bool SkinId(int itemID, ref ulong skinID)
                {
                    if (Dictionaries.SkinId.TryGetValue(itemID, out ulong target))
                    {
                        skinID = target;
                        return true;
                    }
                    return false;
                }

                public static bool Name(ProtoBuf.Item item, Connection connection)
                {
                    if (!string.IsNullOrEmpty(item.name))
                        return false;

                    bool isBlueprint = IsBlueprint(item);
                    int itemId = isBlueprint ? item.instanceData.blueprintTarget : item.itemid;

                    if (!Dictionaries.NamePhrase.TryGetValue(itemId, out Translate.Phrase phrase))
                        return false;

                    if (string.IsNullOrEmpty(phrase.token))
                        return false;

                    string language = PlayerLanguages.GetPlayerLanguage(connection.userid);

                    item.name = GetMessageByLanguage(phrase.token, pluginByPhrase[phrase], language);
                    if (isBlueprint)
                        item.name = item.name + " " + GetBlueprintBaseDisplayName(language);
                    return true;
                }

                public static bool Description(ProtoBuf.Item item, Connection connection)
                {
                    if (!Dictionaries.DescriptionPhrase.TryGetValue(item.itemid, out Translate.Phrase phrase))
                        return false;

                    if (string.IsNullOrEmpty(phrase.token))
                        return false;

                    string language = PlayerLanguages.GetPlayerLanguage(connection.userid);

                    string text = GetMessageByLanguage(phrase.token, pluginByPhrase[phrase], language);
                    if (string.IsNullOrEmpty(text))
                        return false;

                    string label = "\r\r\r\r\r\r\r\r\r\r\r" + PluginInstance.GetMessageByLanguage("itemownership.description.phrase", language);
                    AddOwnership(item, label, text);
                    return true;
                }

                public static bool StaticOwnerShips(ProtoBuf.Item item, Connection connection)
                {
                    if (!Dictionaries.StaticOwnerships.TryGetValue(item.itemid, out var ownerships))
                        return false;

                    string language = null;
                    foreach (var ownership in ownerships)
                    {
                        language ??= PlayerLanguages.GetPlayerLanguage(connection.userid);

                        string label = GetTranslatedText(ownership.label);
                        string text = GetTranslatedText(ownership.text);

                        string GetTranslatedText(Translate.Phrase phrase)
                        {
                            if (phrase.IsValid())
                            {
                                string result = GetMessageByLanguage(phrase.token, pluginByPhrase[phrase], language);
                                if (!string.IsNullOrEmpty(result))
                                    return result;
                            }

                            return phrase.english;
                        }

                        AddOwnership(item, label, text);
                    }
                    return true;
                }

                public static void ToFallback(ProtoBuf.Item item)
                {
                    item.itemid = FallbackItemDefinition.itemid;
                    item.name = $"Fallback Item [{item.UID}]";
                }
            }


            public static void ToClientSide(ProtoBuf.Item item, Connection connection)
            {
                if (ItemManager.FindItemDefinition(item.itemid) == null)
                {
                    Item.ToFallback(item);
                    return;
                }

                Item.Name(item, connection);
                Item.Description(item, connection);
                Item.StaticOwnerShips(item, connection);
                Item.SkinId(item.itemid, ref item.skinid);

                if (Item.IsBlueprint(item) && Mutate.Item.Dictionaries.ItemId.ContainsKey(item.instanceData.blueprintTarget))
                {
                    item.instanceData = BlueprintInstanceData;
                    if (item.skinid == default)
                        item.skinid = BlueprintBaseIconSkinId;
                }
                else
                {
                    Item.ItemId(ref item.itemid);
                }

                if (item.contents != null)
                    ToClientSide(item.contents, connection);

            }

            public static void ToClientSide(ProtoBuf.ItemContainer container, Connection connection)
            {
                if (container != null && container.contents != null)
                {
                    foreach (ProtoBuf.Item item in container.contents)
                        ToClientSide(item, connection);
                }
            }

            public static void ToClientSide(List<ProtoBuf.ItemContainer> containers, Connection connection)
            {
                foreach (ProtoBuf.ItemContainer container in containers)
                    ToClientSide(container, connection);
            }

            public static void ToClientSide(ProtoBuf.BasePlayer player, Connection connection)
            {
                ToClientSide(player.inventory, connection);
                if (player.itemCrafter != null)
                    ToClientSide(player.itemCrafter, connection);
            }

            public static void NoteInvToClientSide(ref object[] args, Connection connection)
            {
                if (args.Length > 2 && args[0] is int itemId)
                {
                    if (Mutate.Item.Dictionaries.ItemId.ContainsKey(itemId))
                    {
                        List<object> list = Pool.Get<List<object>>();
                        list.Add(Mutate.Item.Dictionaries.ItemId[itemId].ToString());
                        list.Add(args[1]);
                        if (args.Length > 2)
                        {
                            if (args[2] is string arg2Name && !string.IsNullOrEmpty(arg2Name))
                            {
                                list.Add(arg2Name);
                            }
                            else if (Mutate.Item.Dictionaries.NamePhrase.TryGetValue(itemId, out Translate.Phrase phrase))
                            {
                                string lang = PlayerLanguages.GetPlayerLanguage(connection.userid);
                                string name = GetMessageByLanguage(phrase.token, pluginByPhrase[phrase], lang);
                                list.Add(name);
                            }
                        }
                        if (args.Length > 3)
                            list.AddRange(args.Skip(3));

                        args = list.ToArray();

                        Pool.FreeUnmanaged(ref list);
                    }
                }
            }

            public static void ToClientSide(ProtoBuf.PlayerInventory inventory, Connection connection)
            {
                if (inventory.invBelt != null)
                    ToClientSide(inventory.invBelt, connection);
                if (inventory.invMain != null)
                    ToClientSide(inventory.invMain, connection);
                if (inventory.invWear != null)
                    ToClientSide(inventory.invWear, connection);
            }

            public static void ToClientSide(ProtoBuf.ItemCrafter crafter, Connection connection)
            {
                foreach (ProtoBuf.ItemCrafter.Task task in crafter.queue)
                    Item.ItemId(ref task.itemID);
            }

            public static void ToClientSide(ProtoBuf.VendingMachine.SellOrder sellOrder)
            {
                Item.ItemId(ref sellOrder.itemToSellID);
                Item.ItemId(ref sellOrder.currencyID);
            }

            public static void ToClientSide(ProtoBuf.IndustrialConveyor.ItemFilter filter, Connection connection)
            {
                Item.ItemId(ref filter.itemDef);
            }

            public static void ToClientSide(ProtoBuf.WeaponRackItem weaponRackItem, Connection connection)
            {
                Item.SkinId(weaponRackItem.itemID, ref weaponRackItem.skinid);
                Item.ItemId(ref weaponRackItem.itemID);
            }

            public static void ToClientSide(ProtoBuf.VendingMachinePurchaseHistoryMessage purchaseHistoryMessage)
            {
                if (purchaseHistoryMessage == null)
                    return;

                if (purchaseHistoryMessage.transactions != null)
                    foreach (ProtoBuf.VendingMachinePurchaseHistoryEntryMessage entryMessage in purchaseHistoryMessage.transactions)
                        Item.ItemId(ref entryMessage.itemID);

                if (purchaseHistoryMessage.smallTransactions != null)
                    foreach (ProtoBuf.VendingMachinePurchaseHistoryEntrySmallMessage entryMessage in purchaseHistoryMessage.smallTransactions)
                        Item.ItemId(ref entryMessage.itemID);
            }

            public static void ToClientSide(ProtoBuf.Mannequin mannequin, Connection connection)
            {
                if (mannequin.clothingItems == null)
                    return;

                foreach (ProtoBuf.Mannequin.ClothingItem clothingItem in mannequin.clothingItems)
                    ToClientSide(clothingItem, connection);
            }

            public static void ToClientSide(ProtoBuf.Mannequin.ClothingItem clothingItem, Connection connection)
            {
                Item.SkinId(clothingItem.itemId, ref clothingItem.skin);
                Item.ItemId(ref clothingItem.itemId);
            }

            public static void ToClientSide(ProtoBuf.BuriedItems buriedItems, Connection connection)
            {
                if (buriedItems.buriedItems == null)
                    return;

                foreach (ProtoBuf.BuriedItems.StoredBuriedItem clothingItem in buriedItems.buriedItems)
                    ToClientSide(clothingItem, connection);
            }

            public static void ToClientSide(ProtoBuf.BuriedItems.StoredBuriedItem buriedItem, Connection connection)
            {
                Item.SkinId(buriedItem.itemId, ref buriedItem.skinId);
                Item.ItemId(ref buriedItem.itemId);
            }

            public static void ToClientSide(ProtoBuf.Entity entity, Connection connection)
            {
                if (entity.basePlayer != null)
                    ToClientSide(entity.basePlayer, connection);
                if (entity.worldItem?.item != null)
                    ToClientSide(entity.worldItem?.item, connection);
                if (entity.storageBox?.contents != null)
                    ToClientSide(entity.storageBox.contents, connection);
                if (entity.loot?.contents != null)
                    ToClientSide(entity.loot.contents, connection);
                if (entity.mailbox?.inventory != null)
                    ToClientSide(entity.mailbox?.inventory, connection);
                if (entity.lootableCorpse?.privateData?.container != null)
                    ToClientSide(entity.lootableCorpse.privateData.container, connection);
                if (entity.mannequin != null)
                    ToClientSide(entity.mannequin, connection);
                if (entity.mannequin != null)
                    ToClientSide(entity.mannequin, connection);

                if (entity.miningQuarry?.extractor is ProtoBuf.ResourceExtractor extractor)
                {
                    ToClientSide(extractor.fuelContents, connection);
                    ToClientSide(extractor.outputContents, connection);
                }

                if (entity.vendingMachine is ProtoBuf.VendingMachine vendingMachine)
                {
                    if (vendingMachine.sellOrderContainer != null)
                    {
                        vendingMachine.sellOrderContainer = vendingMachine.sellOrderContainer.Copy();
                        foreach (ProtoBuf.VendingMachine.SellOrder order in vendingMachine.sellOrderContainer.sellOrders)
                            ToClientSide(order);
                    }
                }

                if (entity.horse is ProtoBuf.Horse horse)
                {
                    ToClientSide(horse.equipmentContainer, connection);
                    ToClientSide(horse.storageContainer, connection);
                }

                if (entity.FrankensteinTable is ProtoBuf.FrankensteinTable FrankensteinTable)
                {
                    for (int i = 0; i < FrankensteinTable.itemIds.Count; i++)
                    {
                        int itemId = FrankensteinTable.itemIds[i];
                        if (Item.ItemId(ref itemId))
                            FrankensteinTable.itemIds[i] = itemId;
                    }
                }

                if (entity.industrialConveyor is ProtoBuf.IndustrialConveyor industrialConveyor)
                {
                    foreach (ProtoBuf.IndustrialConveyor.ItemFilter filter in industrialConveyor.filters)
                        ToClientSide(filter, connection);
                }

                if (entity.weaponRack is ProtoBuf.WeaponRack weaponRack)
                {
                    foreach (ProtoBuf.WeaponRackItem item in weaponRack.items)
                        ToClientSide(item, connection);
                }


            }

            internal static MethodInfo GetMethod(Type type)
            {
                return AccessTools.Method(typeof(Mutate), nameof(ToClientSide), new Type[] { type, typeof(Connection) });
            }
        }

        public class Patcher
        {
            public static Patcher Instance { get; private set; }

            private RustPlugin plugin;
            private Harmony harmonyInstance;
            private HarmonyMethod protoWriteToStreamFinalizer = new HarmonyMethod(AccessTools.Method(typeof(ProtoWriteToStream), nameof(ProtoWriteToStream.LibraryFinalizer)));
            private HarmonyMethod protoWriteToStreamExternalsFinalizer = new HarmonyMethod(AccessTools.Method(typeof(ProtoWriteToStream), nameof(ProtoWriteToStream.LibraryFinalizer)));
            private AccessTools.FieldRef<PatchClassProcessor, Dictionary<Type, MethodInfo>> PatchClassProcessor__auxilaryMethods = AccessTools.FieldRefAccess<PatchClassProcessor, Dictionary<Type, MethodInfo>>("auxilaryMethods");

            public Patcher(RustPlugin plugin, Harmony harmonyInstance)
            {
                this.plugin = plugin;
                this.harmonyInstance = harmonyInstance;

                Instance = this;
            }

            public void PatchNestedClasses()
            {
                foreach (Type type in typeof(CustomItemDefinitions).GetNestedTypes(BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
                {
                    object[] attribute = type.GetCustomAttributes(typeof(HarmonyPatch), false);
                    if (attribute.Length >= 1)
                    {
                        try
                        {
                            PatchClassProcessor patchClassProcessor = harmonyInstance.CreateClassProcessor(type);
                            patchClassProcessor.Patch();
                        }
                        catch (Exception ex)
                        {
                            this.plugin.RaiseError(ex.ToString());
                        }
                    }
                }
            }

            public void PatchWriteToStreamClass()
            {
                Type protoType = typeof(IProto);
                MethodInfo PatchWriteToStreamMethod = typeof(Patcher).GetMethod(nameof(PatchWriteToStream), BindingFlags.Public | BindingFlags.Instance);

                foreach (MethodInfo method in typeof(ProtoWriteToStream).GetMethods(BindingFlags.NonPublic | BindingFlags.Static))
                {
                    if (method.Name != ProtoWriteToStream.HANDLE_METHOD_NAME)
                        continue;

                    ParameterInfo[] parameters = method.GetParameters();
                    if (parameters.Length < 1)
                        continue;

                    ParameterInfo firstParam = parameters[0];
                    if (!firstParam.ParameterType.GetInterfaces().Contains(protoType))
                        continue;

                    PatchWriteToStreamMethod.MakeGenericMethod(firstParam.ParameterType).Invoke(this, new object[] { method });
                }
            }

            public void PatchWriteToStream<T>(MethodInfo prefix) where T : class, IProto, new()
            {
                PatchProcessor processor = harmonyInstance.CreateProcessor(GetWriteToStreamMethod<T>());
                if (prefix != null)
                    processor.AddPrefix(new HarmonyMethod(prefix));
                processor.AddFinalizer(protoWriteToStreamFinalizer);
                processor.Patch();
            }


            public List<MethodInfo> PatchMutateMethod(MethodInfo targetMethod, Type patchClass, Harmony harmonyInstance)
            {
                if (targetMethod.DeclaringType != typeof(Mutate))
                    throw new ArgumentException();

                List<MethodInfo> list = new List<MethodInfo>();

                try
                {
                    PatchClassProcessor patchClassProcessor = new PatchClassProcessor(harmonyInstance, patchClass);
                    Dictionary<Type, MethodInfo> auxilaryMethods = PatchClassProcessor__auxilaryMethods(patchClassProcessor);
                    auxilaryMethods[typeof(HarmonyTargetMethod)] = HarmonyTargetMethod();
                    auxilaryMethods.Remove(typeof(HarmonyTargetMethods));
                    list.AddRange(patchClassProcessor.Patch());
                }
                catch (Exception ex)
                {
                    throw ex;
                }
                finally
                {
                    PatchProcessor patchProcessor = new PatchProcessor(harmonyInstance, targetMethod);
                    patchProcessor.AddFinalizer(new HarmonyMethod(typeof(ProtoWriteToStream), nameof(ProtoWriteToStream.ExternalFinalizer)));
                    list.Add(patchProcessor.Patch());
                }

                return list;

                MethodInfo HarmonyTargetMethod()
                {
                    DynamicMethod method = new DynamicMethod(
                         Guid.NewGuid().ToString(),
                         typeof(MethodBase),
                         Type.EmptyTypes,
                         patchClass.Module,
                         skipVisibility: true
                     );

                    ILGenerator il = method.GetILGenerator();

                    il.Emit(OpCodes.Ldtoken, targetMethod);
                    il.Emit(OpCodes.Call, typeof(MethodBase).GetMethod(nameof(MethodBase.GetMethodFromHandle), new[] { typeof(RuntimeMethodHandle) }));
                    il.Emit(OpCodes.Ret);

                    return method.CreateDelegate(typeof(Func<MethodBase>)).Method;
                }

            }

            private MethodInfo GetWriteToStreamMethod<T>() where T : class, IProto, new()
            {
                return typeof(T).GetMethod(nameof(IProto.WriteToStream), BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
            }
        }
        #endregion

        #region Patches

        [HarmonyPatch(typeof(Translate))]
        private static class Translate_Patches
        {
            [HarmonyPatch(nameof(Translate.Init))]
            [HarmonyPostfix]
            private static void InitPostfix()
            {
                PluginInstance.CallHook("OnTranslateInitialized");
            }

            [HarmonyPatch(nameof(Translate.Get))]
            [HarmonyTranspiler]
            private static IEnumerable<CodeInstruction> FixGettingWithNonInitializedDictionary(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
            {
                CodeMatcher codeMatcher = new CodeMatcher(instructions, generator);
                codeMatcher.Advance(2);
                codeMatcher.CreateLabelWithOffsets(0, out Label ldarg_0);
                codeMatcher.CreateLabelWithOffsets(3, out Label brfalse_s);

                codeMatcher.Insert(
                        new CodeInstruction(OpCodes.Dup),
                        new CodeInstruction(OpCodes.Brtrue_S, ldarg_0),
                        new CodeInstruction(OpCodes.Pop),
                        new CodeInstruction(OpCodes.Br_S, brfalse_s)
                    );

                return codeMatcher.Instructions();
            }
        }

        [HarmonyPatch(typeof(NetWrite))]
        private static class NetWritePool
        {
            public static Dictionary<BufferStream, NetWrite> BufferToStream { get; } = new();

            [HarmonyPatch(nameof(Pool.IPooled.EnterPool))]
            [HarmonyPrefix]
            private static void EnterPool(NetWrite __instance)
            {
                lock (BufferToStream)
                    BufferToStream.Remove(NetWriteGetBufferStream(__instance));
            }


            [HarmonyPatch(nameof(NetWrite.Start))]
            [HarmonyPostfix]
            private static void Start(NetWrite __instance)
            {
                lock (BufferToStream)
                    BufferToStream[NetWriteGetBufferStream(__instance)] = __instance;
            }

            [HarmonyPatch(nameof(NetWrite.Send))]
            [HarmonyPrefix]
            private static void Send(NetWrite __instance)
            {
                __instance.connections.Clear();
            }

            [HarmonyPatch(nameof(Pool.IPooled.EnterPool))]
            [HarmonyPatch(nameof(NetWrite.Start))]
            [HarmonyPatch(nameof(NetWrite.Send))]
            [HarmonyFinalizer]
            static Exception Finalizer()
            {
                return null;
            }
        }


        [HarmonyPatch(typeof(BaseEntity))]
        private static class ClientRPCPatches
        {
            private static IEnumerable<MethodBase> TargetMethods()
            {
                Type protoType = typeof(IProto);
                foreach (MethodInfo methodInfo in typeof(BaseEntity).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
                {
                    ParameterInfo[] parameters = methodInfo.GetParameters();
                    if (methodInfo.Name == nameof(BaseEntity.ClientRPC) && parameters.Length == 2 && parameters[1].ParameterType.GetInterfaces().Contains(protoType))
                        yield return methodInfo;
                }
            }

            private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator gen)
            {
                CodeMatcher matcher = new CodeMatcher(instructions).MatchStartForward(CodeMatch.Calls(AccessTools.Method("BaseEntity:ClientRPCStart"))).ThrowIfInvalid("Could not find call to BaseEntity:ClientRPCStart").InsertAndAdvance(
                        CodeMatch.LoadArgument(1)
                    ).SetInstruction(CodeMatch.Call(typeof(ClientRPCStart), nameof(ClientRPCStart.Call)));
                return matcher.Instructions();
            }

        }

        [HarmonyPatch]
        private class ClientRPCStart
        {
            [HarmonyRevers
// ... [Code preview truncated due to length. Full source in attached file] ...

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху