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

FREE FurnaceSplitter : 1.0.1 1.0.1

Нет прав для скачивания
C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Oxide.Core;
using Oxide.Game.Rust.Cui;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Furnace Splitter", "Skipcast", "1.0.1", ResourceId = 2406)]
    [Description("Splits up resources in furnaces automatically and shows useful furnace information.")]
    public class FurnaceSplitter : RustPlugin
    {
        private static class Extensions
        {
            public static T GetBaseEntity<T>(EntityComponent<T> entityComponent) where T : BaseEntity
            {
                var propertyInfo = entityComponent.GetType().GetProperty("baseEntity", BindingFlags.Instance | BindingFlags.NonPublic);
                return (T)propertyInfo.GetValue(entityComponent, null);
            }

            public static float GetWorkTemperature(BaseOven oven)
            {
                var property = oven.GetType().GetProperty("cookingTemperature", BindingFlags.Instance | BindingFlags.NonPublic);
                return (float)property.GetValue(oven, null);
            }

            public static IEnumerable<T> ToIEnumerable<T>(IEnumerator<T> enumerator)
            {
                while (enumerator.MoveNext())
                {
                    yield return enumerator.Current;
                }
            }
        }

        private class OvenSlot
        {
            /// <summary>The item in this slot.May be null.</summary>
            public Item Item;

            /// <summary>The slot position</summary>
            public int? Position;

            /// <summary>The slot's index in the itemList list.</summary>
            public int Index;

            /// <summary>How much should be added/removed from stack</summary>
            public int DeltaAmount;
        }

        private class OvenInfo
        {
            public float ETA;
            public float FuelNeeded;
        }

        private class StoredData
        {
            public Dictionary<ulong, PlayerOptions> AllPlayerOptions { get; private set; } = new Dictionary<ulong, PlayerOptions>();
        }

        private class PlayerOptions
        {
            public bool Enabled;
            public Dictionary<string, int> TotalStacks = new Dictionary<string, int>();
        }

        private StoredData storedData;
        private Dictionary<ulong, PlayerOptions> allPlayerOptions => storedData.AllPlayerOptions;

        private readonly Dictionary<ulong, string> openUis = new Dictionary<ulong, string>();
        private readonly Dictionary<BaseOven, List<BasePlayer>> looters = new Dictionary<BaseOven, List<BasePlayer>>();
        private readonly Stack<BaseOven> queuedUiUpdates = new Stack<BaseOven>();

        private readonly string[] compatibleOvens =
        {
            "furnace",
            "furnace.large",
            "campfire",
            "refinery_small_deployed"
        };
        
        private void OnPlayerDisconnected(BasePlayer player, string reason)
        {
            DestroyUI(player);
        }

        void OnPlayerInit(BasePlayer player)
        {
            InitPlayer(player);
        }

        private void Loaded()
        {
            storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>("FurnaceSplitterOptions");
        }

        private void OnServerInitialized()
        {
            foreach (var player in Player.Players)
            {
                InitPlayer(player);
            }

            lang.RegisterMessages(new Dictionary<string, string>
            {
                { "turnon", "Turn On" },
                { "turnoff", "Turn Off" },
                { "title", "Furnace Splitter" },
                { "eta", "ETA" },
                { "totalstacks", "Total stacks" }
            }, this, "en");
        }

        private void OnServerSave()
        {
            SaveData();
        }

        private void SaveData()
        {
            Interface.Oxide.DataFileSystem.WriteObject("FurnaceSplitterOptions", storedData);
        }

        private void InitPlayer(BasePlayer player)
        {
            if (!allPlayerOptions.ContainsKey(player.userID))
            {
                allPlayerOptions[player.userID] = new PlayerOptions
                {
                    Enabled = true,
                    TotalStacks = new Dictionary<string, int>
                    {
                        { "furnace", 4 },
                        { "furnace.large", 15 },
                        { "campfire", 2 },
                        { "refinery_small_deployed", 4 }
                    }
                };
            }
        }

        private void OnTick()
        {
            while (queuedUiUpdates.Count > 0)
            {
                var oven = queuedUiUpdates.Pop();
                var ovenInfo = GetOvenInfo(oven);

                GetLooters(oven)?.ForEach(plr =>
                {
                    if (plr && oven && !plr.IsDestroyed && !oven.IsDestroyed)
                    {
                        CreateUi(plr, oven, ovenInfo);
                    }
                });
            }
        }

        private OvenInfo GetOvenInfo(BaseOven oven)
        {
            using (TimeWarning.New("FurnaceSplitter.GetOvenInfo", 0.01f))
            {
                var result = new OvenInfo();
                var smeltTimes = GetSmeltTimes(oven);

                if (smeltTimes.Count > 0)
                {
                    var longestStack = smeltTimes.OrderByDescending(kv => kv.Value).First();
                    var fuelUnits = oven.fuelType.GetComponent<ItemModBurnable>().fuelAmount;
                    var ovenTemperature = Extensions.GetWorkTemperature(oven);
                    float neededFuel = (float) Math.Ceiling((longestStack.Value * (ovenTemperature / 200.0f)) / fuelUnits);

                    result.FuelNeeded = neededFuel;
                    result.ETA = longestStack.Value;
                }

                return result;
            }
        }

        private void Unload()
        {
            SaveData();

            foreach (var kv in openUis.ToDictionary(kv => kv.Key, kv => kv.Value))
            {
                var player = BasePlayer.FindByID(kv.Key);
                DestroyUI(player);
            }
        }

        private bool GetEnabled(BasePlayer player)
        {
            return allPlayerOptions[player.userID].Enabled;
        }

        private void SetEnabled(BasePlayer player, bool enabled)
        {
            allPlayerOptions[player.userID].Enabled = enabled;
            CreateUiIfFurnaceOpen(player);
        }
        
        private bool IsSlotCompatible(Item item, BaseOven oven, ItemDefinition itemDefinition)
        {
            var cookable = item.info.GetComponent<ItemModCookable>();

            if (item.amount < item.info.stackable && item.info == itemDefinition)
                return true;

            if (oven.allowByproductCreation && oven.fuelType.GetComponent<ItemModBurnable>().byproductItem == item.info)
                return true;

            if (cookable == null || cookable.becomeOnCooked == itemDefinition)
                return true;

            if (CanCook(cookable, oven))
                return true;

            return false;
        }

        private void OnConsumeFuel(BaseOven oven, Item fuel, ItemModBurnable burnable)
        {
            if (compatibleOvens.Contains(oven.ShortPrefabName))
                queuedUiUpdates.Push(oven);
        }

        private List<BasePlayer> GetLooters(BaseOven oven)
        {
            if (looters.ContainsKey(oven))
                return looters[oven];

            return null;
        }

        private void AddLooter(BaseOven oven, BasePlayer player)
        {
            if (!looters.ContainsKey(oven))
                looters[oven] = new List<BasePlayer>();

            var list = looters[oven];
            list.Add(player);
        }

        private void RemoveLooter(BaseOven oven, BasePlayer player)
        {
            if (!looters.ContainsKey(oven))
                return;

            looters[oven].Remove(player);
        }

        private object CanMoveItem(Item item, PlayerInventory playerLoot, uint targetContainer, int targetSlot)
        {
            Func<object> splitFunc = () =>
            {
                using (TimeWarning.New("FurnaceSplitter.CanMoveItem", 0.005f))
                {
                    var player = Extensions.GetBaseEntity(playerLoot.loot);
                    var container = playerLoot.FindContainer(targetContainer);
                    var originalContainer = item.GetRootContainer();

                    if (player == null || !GetEnabled(player))
                        return null;

                    var playerOptions = allPlayerOptions[player.userID];

                    if (container == null || container == item.GetRootContainer())
                        return null;

                    var oven = container.entityOwner as BaseOven;
                    var cookable = item.info.GetComponent<ItemModCookable>();

                    if (oven == null || cookable == null)
                        return null;

                    int totalSlots = 2 + (oven.allowByproductCreation ? 1 : 0);

                    if (playerOptions.TotalStacks.ContainsKey(oven.ShortPrefabName))
                    {
                        totalSlots = playerOptions.TotalStacks[oven.ShortPrefabName];
                    }

                    float workTemperature = Extensions.GetWorkTemperature(oven);

                    if (cookable.lowTemp > workTemperature || cookable.highTemp < workTemperature)
                        return null;

                    int invalidItemsCount = container.itemList.Count(slotItem => !IsSlotCompatible(slotItem, oven, item.info));
                    int numOreSlots = Math.Min(container.capacity - invalidItemsCount, totalSlots);
                    int totalMoved = 0;
                    int totalAmount = Math.Min(item.amount + container.itemList.Where(slotItem => slotItem.info == item.info).Take(numOreSlots).Sum(slotItem => slotItem.amount), item.info.stackable * numOreSlots);
                    
                    if (numOreSlots <= 0)
                    {
                        return true;
                    }

                    //Puts("---------------------------");
                    
                    int totalStackSize = Math.Min(totalAmount / numOreSlots, item.info.stackable);
                    int remaining = totalAmount - (totalAmount / numOreSlots) * numOreSlots;
                    
                    List<int> addedSlots = new List<int>();

                    //Puts("total: {0}, remaining: {1}, totalStackSize: {2}", totalAmount, remaining, totalStackSize);
                    
                    List<OvenSlot> ovenSlots = new List<OvenSlot>();

                    for (int i = 0; i < numOreSlots; ++i)
                    {
                        Item existingItem;
                        var slot = FindMatchingSlotIndex(container, out existingItem, item.info, addedSlots);

                        if (slot == -1) // full
                        {
                            return true;
                        }

                        addedSlots.Add(slot);

                        var ovenSlot = new OvenSlot
                        {
                            Position = existingItem?.position,
                            Index = slot,
                            Item = existingItem
                        };
                        
                        int currentAmount = existingItem?.amount ?? 0;
                        int missingAmount = totalStackSize - currentAmount + (i < remaining ? 1 : 0);
                        ovenSlot.DeltaAmount = missingAmount;
                        
                        //Puts("[{0}] current: {1}, delta: {2}, total: {3}", slot, currentAmount, ovenSlot.DeltaAmount, currentAmount + missingAmount);

                        if (currentAmount + missingAmount <= 0)
                            continue;

                        ovenSlots.Add(ovenSlot);
                    }
                    
                    foreach (var slot in ovenSlots)
                    {
                        if (slot.Item == null)
                        {
                            var newItem = ItemManager.Create(item.info, slot.DeltaAmount, item.skin);
                            slot.Item = newItem;
                            newItem.MoveToContainer(container, slot.Position ?? slot.Index);
                        }
                        else
                        {
                            slot.Item.amount += slot.DeltaAmount;
                        }

                        totalMoved += slot.DeltaAmount;
                    }
                    
                    if (totalMoved >= item.amount)
                        item.Remove();
                    else
                        item.amount -= totalMoved;

                    container.MarkDirty();
                    originalContainer.MarkDirty();
                    return true;
                }
            };

            object returnValue = splitFunc();

            {
                var container = playerLoot.FindContainer(targetContainer);
                var oven = container?.entityOwner as BaseOven ?? item.GetRootContainer().entityOwner as BaseOven;

                if (oven != null && compatibleOvens.Contains(oven.ShortPrefabName))
                    queuedUiUpdates.Push(oven);
            }

            return returnValue;
        }

        private int FindMatchingSlotIndex(ItemContainer container, out Item existingItem, ItemDefinition itemType, List<int> indexBlacklist)
        {
            existingItem = null;
            int firstIndex = -1;
            Dictionary<int, Item> existingItems = new Dictionary<int, Item>();

            for (int i = 0; i < container.capacity; ++i)
            {
                if (indexBlacklist.Contains(i))
                    continue;

                Item itemSlot = container.GetSlot(i);
                if (itemSlot == null || (itemType != null && itemSlot.info == itemType))
                {
                    if (itemSlot != null)
                        existingItems.Add(i, itemSlot);

                    if (firstIndex == -1)
                    {
                        existingItem = itemSlot;
                        firstIndex = i;
                    }
                }
            }

            if (existingItems.Count <= 0 && firstIndex != -1)
            {
                return firstIndex;
            }
            else if (existingItems.Count > 0)
            {
                var largestStackItem = existingItems.OrderByDescending(kv => kv.Value.amount).First();
                existingItem = largestStackItem.Value;
                return largestStackItem.Key;
            }

            existingItem = null;
            return -1;
        }

        private void OnLootEntity(BasePlayer player, BaseEntity entity)
        {
            var oven = entity as BaseOven;

            if (oven == null || !compatibleOvens.Contains(oven.ShortPrefabName))
                return;
            
            AddLooter(oven, player);
            queuedUiUpdates.Push(oven);
        }

        private void OnLootEntityEnd(BasePlayer player, BaseCombatEntity entity)
        {
            var oven = entity as BaseOven;

            if (oven == null || !compatibleOvens.Contains(oven.ShortPrefabName))
                return;

            DestroyUI(player);
            RemoveLooter(oven, player);
        }

        private void OnOvenToggle(BaseOven oven, BasePlayer player)
        {
            if (compatibleOvens.Contains(oven.ShortPrefabName))
                queuedUiUpdates.Push(oven);
        }

        private void CreateUiIfFurnaceOpen(BasePlayer player)
        {
            var oven = player.inventory.loot?.entitySource as BaseOven;

            if (oven != null && compatibleOvens.Contains(oven.ShortPrefabName))
                queuedUiUpdates.Push(oven);
        }

        private CuiElementContainer CreateUi(BasePlayer player, BaseOven oven, OvenInfo ovenInfo)
        {
            var options = allPlayerOptions[player.userID];
            int totalSlots = GetTotalStacksOption(player, oven) ?? oven.inventory.capacity - (oven.allowByproductCreation ? 1 : 2);
            string remainingTimeStr;
            string neededFuelStr;

            if (ovenInfo.ETA <= 0)
            {
                remainingTimeStr = "0s";
                neededFuelStr = "0";
            }
            else
            {
                remainingTimeStr = FormatTime(ovenInfo.ETA);
                neededFuelStr = ovenInfo.FuelNeeded.ToString("##,###");
            }
            
            string contentColor = "0.7 0.7 0.7 1.0";
            int contentSize = 10;
            string toggleStateStr = (!options.Enabled).ToString();
            string toggleButtonColor = !options.Enabled
                                        ? "0.415 0.5 0.258 0.4"
                                        : "0.8 0.254 0.254 0.4";
            string toggleButtonTextColor = !options.Enabled
                                            ? "0.607 0.705 0.431"
                                            : "0.705 0.607 0.431";
            string buttonColor = "0.75 0.75 0.75 0.1";
            string buttonTextColor = "0.77 0.68 0.68 1";

            int nextDecrementSlot = totalSlots - 1;
            int nextIncrementSlot = totalSlots + 1;

            DestroyUI(player);

            var result = new CuiElementContainer();
            var rootPanelName = result.Add(new CuiPanel
            {
                Image = new CuiImageComponent
                {
                    Color = "0 0 0 0"
                },
                RectTransform =
                {
                    AnchorMin = "0.6505 0.022",
                    AnchorMax = "0.829 0.133"
                }
            }, "Hud.Menu");

            var headerPanel = result.Add(new CuiPanel
            {
                Image = new CuiImageComponent
                {
                    Color = "0.75 0.75 0.75 0.1"
                },
                RectTransform =
                {
                    AnchorMin = "0 0.775",
                    AnchorMax = "1 1"
                }
            }, rootPanelName);

            // Header label
            result.Add(new CuiLabel
            {
                RectTransform =
                {
                    AnchorMin = "0.051 0",
                    AnchorMax = "1 0.95"
                },
                Text =
                {
                    Text = lang.GetMessage("title", this, player.UserIDString),
                    Align = TextAnchor.MiddleLeft,
                    Color = "0.77 0.68 0.68 1",
                    FontSize = 13
                }
            }, headerPanel);

            var contentPanel = result.Add(new CuiPanel
            {
                Image = new CuiImageComponent
                {
                    Color = "0.65 0.65 0.65 0.06"
                },
                RectTransform =
                {
                    AnchorMin = "0 0",
                    AnchorMax = "1 0.74"
                }
            }, rootPanelName);

            // ETA label
            result.Add(new CuiLabel
            {
                RectTransform =
                {
                    AnchorMin = "0.02 0.7",
                    AnchorMax = "0.98 1"
                },
                Text =
                {
                    Text = string.Format("{0}: " + (ovenInfo.ETA > 0 ? "~" : "") + remainingTimeStr + " (" + neededFuelStr +  " " + oven.fuelType.displayName.english.ToLower() + ")", lang.GetMessage("eta", this, player.UserIDString)),
                    Align = TextAnchor.MiddleLeft,
                    Color = contentColor,
                    FontSize = contentSize
                }
            }, contentPanel);

            // Toggle button
            result.Add(new CuiButton
            {
                RectTransform =
                {
                    AnchorMin = "0.02 0.4",
                    AnchorMax = "0.25 0.7"
                },
                Button =
                {
                    Command = "furnacesplitter.enabled " + toggleStateStr,
                    Color = toggleButtonColor
                },
                Text =
                {
                    Align = TextAnchor.MiddleCenter,
                    Text = options.Enabled ? lang.GetMessage("turnoff", this, player.UserIDString) : lang.GetMessage("turnon", this, player.UserIDString),
                    Color = toggleButtonTextColor,
                    FontSize = 11
                }
            }, contentPanel);

            // Decrease stack button
            result.Add(new CuiButton
            {
                RectTransform =
                {
                    AnchorMin = "0.02 0.05",
                    AnchorMax = "0.07 0.35"
                },
                Button =
                {
                    Command = "furnacesplitter.totalstacks " + nextDecrementSlot,
                    Color = buttonColor
                },
                Text =
                {
                    Align = TextAnchor.MiddleCenter,
                    Text = "<",
                    Color = buttonTextColor,
                    FontSize = contentSize
                }
            }, contentPanel);

            // Empty slots label
            result.Add(new CuiLabel
            {
                RectTransform =
                {
                    AnchorMin = "0.08 0.05",
                    AnchorMax = "0.19 0.35"
                },
                Text =
                {
                    Align = TextAnchor.MiddleCenter,
                    Text = totalSlots.ToString(),
                    Color = contentColor,
                    FontSize = contentSize
                }
            }, contentPanel);

            // Increase stack button
            result.Add(new CuiButton
            {
                RectTransform =
                {
                    AnchorMin = "0.19 0.05",
                    AnchorMax = "0.25 0.35"
                },
                Button =
                {
                    Command = "furnacesplitter.totalstacks " + nextIncrementSlot,
                    Color = buttonColor
                },
                Text =
                {
                    Align = TextAnchor.MiddleCenter,
                    Text = ">",
                    Color = buttonTextColor,
                    FontSize = contentSize
                }
            }, contentPanel);

            // Stack itemType label
            result.Add(new CuiLabel
            {
                RectTransform =
                {
                    AnchorMin = "0.27 0.05",
                    AnchorMax = "1 0.35"
                },
                Text =
                {
                    Align = TextAnchor.MiddleLeft,
                    Text = string.Format("({0})", lang.GetMessage("totalstacks", this, player.UserIDString)),
                    Color = contentColor,
                    FontSize = contentSize
                }
            }, contentPanel);

            openUis.Add(player.userID, rootPanelName);
            CuiHelper.AddUi(player, result);
            return result;
        }

        private string FormatTime(float totalSeconds)
        {
            var hours = Math.Floor(totalSeconds / 3600);
            var minutes = Math.Floor((totalSeconds / 60) % 60);
            var seconds = totalSeconds % 60;

            if (hours <= 0 && minutes <= 0)
                return seconds + "s";
            else if (hours <= 0)
                return minutes + "m" + seconds + "s";
            else
                return hours + "h" + minutes + "m" + seconds + "s";
        }

        private Dictionary<ItemDefinition, float> GetSmeltTimes(BaseOven oven)
        {
            var container = oven.inventory;
            var cookables = container.itemList.Where(item =>
            {
                var cookable = item.info.GetComponent<ItemModCookable>();
                return cookable != null && CanCook(cookable, oven);
            }).ToList();

            if (cookables.Count == 0)
                return new Dictionary<ItemDefinition, float>();

            var distinctCookables = cookables.GroupBy(item => item.info, item => item).ToList();
            Dictionary<ItemDefinition, int> amounts = new Dictionary<ItemDefinition, int>();

            foreach (var group in distinctCookables)
            {
                int biggestAmount = Extensions.ToIEnumerable(group.GetEnumerator()).Max(item => item.amount);
                amounts.Add(group.Key, biggestAmount);
            }

            var smeltTimes = amounts.ToDictionary(kv => kv.Key, kv => GetSmeltTime(kv.Key.GetComponent<ItemModCookable>(), kv.Value));
            return smeltTimes;
        }

        private bool CanCook(ItemModCookable cookable, BaseOven oven)
        {
            float workTemperature = Extensions.GetWorkTemperature(oven);
            return workTemperature >= cookable.lowTemp && workTemperature <= cookable.highTemp;
        }

        private float GetSmeltTime(ItemModCookable cookable, int amount)
        {
            float smeltTime = cookable.cookTime * amount;
            return smeltTime;
        }

        private int? GetTotalStacksOption(BasePlayer player, BaseOven oven)
        {
            var options = allPlayerOptions[player.userID];

            if (options.TotalStacks.ContainsKey(oven.ShortPrefabName))
                return options.TotalStacks[oven.ShortPrefabName];

            return null;
        }

        private void DestroyUI(BasePlayer player)
        {
            if (!openUis.ContainsKey(player.userID))
                return;

            var uiName = openUis[player.userID];
            CuiHelper.DestroyUi(player, uiName);
            openUis.Remove(player.userID);
        }

        [ConsoleCommand("furnacesplitter.enabled")]
        private void ConsoleCommand_Toggle(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();

            if (!arg.HasArgs())
            {
                player.ConsoleMessage(GetEnabled(player).ToString());
                return;
            }

            bool enabled = arg.GetBool(0);
            SetEnabled(player, enabled);
            CreateUiIfFurnaceOpen(player);
        }

        [ConsoleCommand("furnacesplitter.totalstacks")]
        private void ConsoleCommand_TotalStacks(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            var lootSource = player.inventory.loot?.entitySource as BaseOven;

            if (lootSource == null || !compatibleOvens.Contains(lootSource.ShortPrefabName))
            {
                player.ConsoleMessage("Current loot source invalid");
                return;
            }

            string ovenName = lootSource.ShortPrefabName;
            var playerOption = allPlayerOptions[player.userID];

            if (playerOption.TotalStacks.ContainsKey(ovenName))
            {
                if (!arg.HasArgs())
                {
                    player.ConsoleMessage(playerOption.TotalStacks[ovenName].ToString());
                }
                else
                {
                    var newValue = (int) Mathf.Clamp(arg.GetInt(0), 0, lootSource.inventory.capacity);
                    playerOption.TotalStacks[ovenName] = newValue;
                }
            }
            else
            {
                player.ConsoleMessage("Unsupported furnace '" + ovenName + "'");
            }

            CreateUiIfFurnaceOpen(player);
        }
    }
}

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