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

FREE RecycleManager : 2.2.1 2.2.1

Нет прав для скачивания
C#:
// #define ENABLE_TESTS

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core.Libraries.Covalence;
using Oxide.Game.Rust.Cui;
using System;
using System.Collections.Generic;
using System.Linq;
using Oxide.Core;
using Oxide.Core.Libraries;
using UnityEngine;

#if ENABLE_TESTS
using System.Collections;
using System.Reflection;
using Oxide.Core.Plugins;
#endif

namespace Oxide.Plugins
{
    [Info("Recycle Manager", "WhiteThunder", "2.2.1")]
    [Description("Allows customizing recycler speed, input, and output")]
    internal class RecycleManager : CovalencePlugin
    {
        #region Fields

        private Configuration _config;

        private const string PermissionAdmin = "recyclemanager.admin";

        private const int ScrapItemId = -932201673;
        private const float ClassicRecycleEfficiency = 0.5f;
        private const float VanillaMaxItemsInStackFraction = 0.1f;

        private readonly object True = true;
        private readonly object False = false;

        private const int NumInputSlots = 6;
        private const int NumOutputSlots = 6;

        private readonly RecyclerComponentManager _recyclerComponentManager = new();
        private readonly RecycleEditManager _recycleEditManager;
        private readonly float[] _recycleTime = new float[1];
        private readonly float[] _recycleEfficiency = new float[1];

        private bool IsEditUIEnabled => !_config.UsingDefaults && _config.EditUISettings.Enabled;

        #if ENABLE_TESTS
        private readonly RecycleManagerTests _testRunner;
        #endif

        public RecycleManager()
        {
            #if ENABLE_TESTS
            _testRunner = new RecycleManagerTests(this);
            #endif

            _recycleEditManager = new RecycleEditManager(this);
        }

        #endregion

        #region Hooks

        private void Init()
        {
            _config.Init(this);
            _recyclerComponentManager.Init(this);

            permission.RegisterPermission(PermissionAdmin, this);

            if (!_config.Speed.Enabled && !_config.Efficiency.Enabled)
            {
                Unsubscribe(nameof(OnRecyclerToggle));
            }

            if (!IsEditUIEnabled)
            {
                Unsubscribe(nameof(OnLootEntity));
            }
        }

        private void OnServerInitialized()
        {
            #if ENABLE_TESTS
            _testRunner.Run();
            #endif

            if (IsEditUIEnabled)
            {
                foreach (var player in BasePlayer.activePlayerList)
                {
                    var container = player.inventory.loot.containers.FirstOrDefault();
                    if (container == null)
                        continue;

                    var recycler = container.entityOwner as Recycler;
                    if ((object)recycler == null)
                        continue;

                    OnLootEntity(player, recycler);
                }
            }
        }

        private void Unload()
        {
            #if ENABLE_TESTS
            _testRunner.Interrupt();
            #endif

            _recyclerComponentManager.Unload();
            _recycleEditManager.Unload();
        }

        // This hook is primarily used to determine whether an item can be placed into the recycler input,
        // but it's also called when processing each item.
        private object CanBeRecycled(Item item, Recycler recycler)
        {
            if (item == null)
                return null;

            if (_config.RestrictedInputItems.IsDisallowed(item))
            {
                // Defensively return null if vanilla would *disallow* recycling, to avoid hook conflicts.
                return IsVanillaRecyclable(item) && !RecyclableWasBlocked(item, recycler)
                    ? False
                    : null;
            }

            if (_config.OverrideOutput.GetBestOverride(item) != null)
            {
                // Defensively return null if vanilla would *allow* recycling, to avoid hook conflicts.
                return IsVanillaRecyclable(item) || RecyclableWasBlocked(item, recycler)
                    ? null
                    : True;
            }

            return null;
        }

        private object CanRecycle(Recycler recycler, Item item)
        {
            return CanBeRecycled(item, recycler);
        }

        private void OnRecyclerToggle(Recycler recycler, BasePlayer player)
        {
            _recyclerComponentManager.HandleRecyclerToggle(recycler, player);
        }

        private object OnItemRecycle(Item item, Recycler recycler)
        {
            if (RecycleItemWasBlocked(item, recycler))
                return null;

            var maxItemsInStackFraction = _config.MaxItemsPerRecycle.GetPercent(item) / 100f;
            var recycleAmount = DetermineConsumptionAmount(recycler, item, maxItemsInStackFraction);
            if (recycleAmount <= 0)
                return False;

            var customIngredientList = _config.OverrideOutput.GetBestOverride(item);
            if (customIngredientList != null)
            {
                item.UseItem(recycleAmount);

                // Overrides already account for standard recycle efficiency, so only calculate based on item condition.
                if (PopulateOutputWithOverride(recycler, customIngredientList, recycleAmount, DetermineItemRecycleEfficiency(item, 1f)))
                {
                    recycler.StopRecycling();
                }

                return False;
            }

            // If the item is not vanilla recyclable, and this plugin doesn't have an override,
            // that probably means another plugin is going to handle recycling it.
            if (!IsVanillaRecyclable(item))
                return null;

            // Issue: Last known player will always be null if speed and efficiency are both disabled, since toggle hook
            // is not subscribed to improve performance.
            var lastKnownPlayer = _recyclerComponentManager.EnsureRecyclerComponent(recycler).Player;
            var recycleEfficiency = DetermineItemRecycleEfficiency(item, GetRecyclerEfficiency(recycler, lastKnownPlayer, out var vanillaEfficiency));

            if (recycleEfficiency == vanillaEfficiency
                && maxItemsInStackFraction == VanillaMaxItemsInStackFraction
                && !IsOutputCustomized(item.info.Blueprint))
                return null;

            item.UseItem(recycleAmount);

            var outputIsFull = PopulateOutputVanilla(_config, recycler, item, recycleAmount, recycleEfficiency);
            if (outputIsFull || !recycler.HasRecyclable())
            {
                recycler.StopRecycling();
            }

            return False;
        }

        private void OnLootEntity(BasePlayer player, Recycler recycler)
        {
            if (!recycler.onlyOneUser)
                return;

            if (permission.UserHasPermission(player.UserIDString, PermissionAdmin))
            {
                var player2 = player;
                var recycler2 = recycler;
                NextTick(() =>
                {
                    var lootingContainer = player2.inventory.loot.containers.FirstOrDefault();
                    if (lootingContainer == null || lootingContainer != recycler2.inventory)
                        return;

                    _recycleEditManager.HandlePlayerStartedLooting(player2, recycler2);
                });
            }
        }

        #endregion

        #region Exposed Hooks

        private static class ExposedHooks
        {
            public static object OnRecycleManagerItemRecyclable(Item item, Recycler recycler)
            {
                return Interface.CallHook("OnRecycleManagerItemRecyclable", item, recycler);
            }

            public static object OnRecycleManagerSpeed(Recycler recycler, BasePlayer player, float[] recycleTime)
            {
                return Interface.CallHook("OnRecycleManagerSpeed", recycler, player, recycleTime);
            }

            public static void OnRecycleManagerEfficiency(Recycler recycler, BasePlayer player, float[] recyclerEfficiency)
            {
                Interface.CallHook("OnRecycleManagerEfficiency", recycler, player, recyclerEfficiency);
            }

            public static object OnRecycleManagerRecycle(Item item, Recycler recycler)
            {
                return Interface.CallHook("OnRecycleManagerRecycle", item, recycler);
            }
        }

        #endregion

        #region Commands

        [Command("recyclemanager.add", "recman.add")]
        private void CommandAddItem(IPlayer player, string cmd, string[] args)
        {
            if (!VerifyHasPermission(player, PermissionAdmin)
                || !VerifyConfigLoaded(player))
                return;

            if (!VerifyValidItemIdOrShortName(player, args.ElementAtOrDefault(0), out var itemDefinition, cmd))
                return;

            if (!_config.OverrideOutput.AddOverride(this, itemDefinition))
            {
                ReplyToPlayer(player, LangEntry.AddExists, itemDefinition.shortname);
                return;
            }

            SaveConfig();
            ReplyToPlayer(player, LangEntry.AddSuccess, itemDefinition.shortname);
        }

        [Command("recyclemanager.reset", "recman.reset")]
        private void CommandResetItem(IPlayer player, string cmd, string[] args)
        {
            if (!VerifyHasPermission(player, PermissionAdmin)
                || !VerifyConfigLoaded(player))
                return;

            if (!VerifyValidItemIdOrShortName(player, args.ElementAtOrDefault(0), out var itemDefinition, cmd))
                return;

            _config.OverrideOutput.ResetOverride(this, itemDefinition);
            SaveConfig();
            ReplyToPlayer(player, LangEntry.ResetSuccess, itemDefinition.shortname);
        }

        [Command("recyclemanager.ui")]
        private void CommandEdit(IPlayer player, string cmd, string[] args)
        {
            if (player.IsServer || !player.HasPermission(PermissionAdmin))
                return;

            var basePlayer = player.Object as BasePlayer;
            _recycleEditManager.GetController(basePlayer)?.HandleUICommand(basePlayer, args);
        }

        #endregion

        #region Utilities

        #endregion

        #region Helper Methods - Instance

        private bool VerifyHasPermission(IPlayer player, string perm)
        {
            if (player.HasPermission(perm))
                return true;

            ReplyToPlayer(player, LangEntry.ErrorNoPermission);
            return false;
        }

        private bool VerifyConfigLoaded(IPlayer player)
        {
            if (!_config.UsingDefaults)
                return true;

            ReplyToPlayer(player, LangEntry.ErrorConfig);
            return false;
        }

        private bool VerifyValidItemIdOrShortName(IPlayer player, string itemArg, out ItemDefinition itemDefinition, string command)
        {
            if (itemArg == null)
            {
                itemDefinition = (player.Object as BasePlayer)?.GetActiveItem()?.info;
                if (itemDefinition != null)
                    return true;

                ReplyToPlayer(player, LangEntry.ItemSyntax, command);
                return false;
            }

            if (int.TryParse(itemArg, out var itemId))
            {
                itemDefinition = ItemManager.FindItemDefinition(itemId);
                if (itemDefinition != null)
                    return true;
            }

            itemDefinition = ItemManager.FindItemDefinition(itemArg);
            if (itemDefinition != null)
                return true;

            ReplyToPlayer(player, LangEntry.ErrorInvalidItem, itemArg);
            return false;
        }

        private float GetRecyclerEfficiency(Recycler recycler, BasePlayer player, out float vanillaEfficiency)
        {
            vanillaEfficiency = DetermineVanillaRecycleEfficiency(recycler);

            _recycleEfficiency[0] = _config.Efficiency.Enabled
                ? _config.Efficiency.GetRecyclerEfficiency(recycler)
                : vanillaEfficiency;

            ExposedHooks.OnRecycleManagerEfficiency(recycler, player, _recycleEfficiency);
            return Mathf.Max(0, _recycleEfficiency[0]);
        }

        private bool TryDetermineRecycleTime(Recycler recycler, BasePlayer player, out float recycleTime)
        {
            _recycleTime[0] = _config.Speed.GetBaseRecycleTime(recycler)
                              * _config.Speed.GetTimeMultiplierForPlayer(player);

            if (ExposedHooks.OnRecycleManagerSpeed(recycler, player, _recycleTime) is false)
            {
                recycleTime = 0;
                return false;
            }

            recycleTime = Math.Max(0, _recycleTime[0]);
            return true;
        }

        private object CallCanBeRecycled(Item item, Recycler recycler)
        {
            Unsubscribe(nameof(CanBeRecycled));
            var hookResult = Interface.CallHook(nameof(CanBeRecycled), item, recycler);
            Subscribe(nameof(CanBeRecycled));
            return hookResult;
        }

        private bool IsOutputCustomized(ItemBlueprint blueprint)
        {
            if (blueprint.scrapFromRecycle > 0 && _config.OutputMultipliers.GetOutputMultiplier(ScrapItemId) != 1)
                return true;

            foreach (var ingredient in blueprint.ingredients)
            {
                // Skip scrap since it's handled separately.
                if (ingredient.itemDef.itemid == ScrapItemId)
                    continue;

                if (_config.OutputMultipliers.GetOutputMultiplier(ingredient.itemid) != 1)
                    return true;
            }

            return false;
        }

        #endregion

        #region Helper Methods - Static

        public static void LogError(string message) => Interface.Oxide.LogError($"[Recycle Manager] {message}");
        public static void LogWarning(string message) => Interface.Oxide.LogWarning($"[Recycle Manager] {message}");

        private static void Swap<T>(ref T a, ref T b)
        {
            (a, b) = (b, a);
        }

        private static bool IsVanillaRecyclable(Item item)
        {
            return item.info.Blueprint != null;
        }

        private static bool RecyclableWasBlocked(Item item, Recycler recycler)
        {
            return ExposedHooks.OnRecycleManagerItemRecyclable(item, recycler) is false;
        }

        private static bool RecycleItemWasBlocked(Item item, Recycler recycler)
        {
            return ExposedHooks.OnRecycleManagerRecycle(item, recycler) is false;
        }

        private static Item CreateItem(ItemDefinition itemDefinition, int amount, ulong skinId, string displayName = null)
        {
            var item = ItemManager.Create(itemDefinition, amount, skinId);

            if (!string.IsNullOrWhiteSpace(displayName))
            {
                item.name = displayName;
            }

            return item;
        }

        private static int CalculateOutputAmountVanillaRandom(int recycleAmount, float adjustedIngredientChance)
        {
            var outputAmount = 0;

            // Roll a random number for every item to consume.
            for (var i = 0; i < recycleAmount; i++)
            {
                if (UnityEngine.Random.Range(0f, 1f) <= adjustedIngredientChance)
                {
                    outputAmount++;
                }
            }

            return outputAmount;
        }

        private static int CalculateOutputAmountFast(int recycleAmount, float ingredientAmount)
        {
            // To save on performance, don't generate hundreds/thousands/millions of random numbers.
            var outputAmountDecimal = ingredientAmount * recycleAmount;

            var outputAmountInt = (int)outputAmountDecimal;

            // Roll a random number to see if the the remainder should be given.
            var remainderFractionalOutputAmount = outputAmountDecimal - outputAmountInt;
            if (remainderFractionalOutputAmount > 0 && UnityEngine.Random.Range(0f, 1f) <= remainderFractionalOutputAmount)
            {
                outputAmountInt++;
            }

            return outputAmountInt;
        }

        private static int CalculateOutputAmountRandom(int recycleAmount, float ingredientAmount, float recycleEfficiency, float outputMultiplier)
        {
            var adjustedIngredientAmount = ingredientAmount * outputMultiplier;
            if (adjustedIngredientAmount <= 1 && recycleAmount <= 100)
                return CalculateOutputAmountVanillaRandom(recycleAmount, adjustedIngredientAmount * recycleEfficiency);

            return CalculateOutputAmountFast(recycleAmount, adjustedIngredientAmount * recycleEfficiency);
        }

        private static int CalculateOutputAmountNoRandom(int recycleAmount, float ingredientAmount, float recycleEfficiency, float outputMultiplier)
        {
            var adjustedIngredientAmount = Mathf.CeilToInt(ingredientAmount * recycleEfficiency) * outputMultiplier;
            return CalculateOutputAmountFast(recycleAmount, adjustedIngredientAmount);
        }

        private static int CalculateOutputAmount(int recycleAmount, float ingredientAmount, float recycleEfficiency, float outputMultiplier = 1)
        {
            if (ingredientAmount <= 1)
                return CalculateOutputAmountRandom(recycleAmount, ingredientAmount, recycleEfficiency, outputMultiplier);

            return CalculateOutputAmountNoRandom(recycleAmount, ingredientAmount, recycleEfficiency, outputMultiplier);
        }

        private static bool AddItemToRecyclerOutput(Recycler recycler, ItemDefinition itemDefinition, int ingredientAmount, ulong skinId = 0, string displayName = null)
        {
            var outputIsFull = false;
            var numStacks = Mathf.CeilToInt(ingredientAmount / (float)itemDefinition.stackable);

            for (var i = 0; i < numStacks; i++)
            {
                var amountForStack = Math.Min(ingredientAmount, itemDefinition.stackable);
                var outputItem = CreateItem(itemDefinition, amountForStack, skinId, displayName);

                if (!recycler.MoveItemToOutput(outputItem))
                {
                    outputIsFull = true;
                }

                ingredientAmount -= amountForStack;

                if (ingredientAmount <= 0)
                    break;
            }

            return outputIsFull;
        }

        private static float DetermineVanillaRecycleEfficiency(Recycler recycler)
        {
            return recycler.lastFetchedEfficiency;
        }

        private static IngredientInfo[] GetVanillaOutput(ItemDefinition itemDefinition)
        {
            if (itemDefinition.Blueprint?.ingredients == null)
                return Array.Empty<IngredientInfo>();

            var ingredientList = new List<IngredientInfo>();

            if (itemDefinition.Blueprint.scrapFromRecycle > 0)
            {
                var ingredientInfo = new IngredientInfo
                {
                    ShortName = "scrap",
                    Amount = itemDefinition.Blueprint.scrapFromRecycle,
                };
                ingredientInfo.Init();
                ingredientList.Add(ingredientInfo);
            }

            foreach (var blueprintIngredient in itemDefinition.Blueprint.ingredients)
            {
                if (blueprintIngredient.itemid == ScrapItemId)
                    continue;

                var amount = blueprintIngredient.amount / itemDefinition.Blueprint.amountToCreate * ClassicRecycleEfficiency;
                if (amount > 1)
                {
                    amount = Mathf.CeilToInt(amount);
                }

                var ingredientInfo = new IngredientInfo
                {
                    ShortName = blueprintIngredient.itemDef.shortname,
                    Amount = amount,
                };

                ingredientInfo.Init();
                ingredientList.Add(ingredientInfo);
            }

            return ingredientList.ToArray();
        }

        private static float DetermineItemRecycleEfficiency(Item item, float recyclerEfficiency)
        {
            return item.hasCondition
                ? Mathf.Clamp01(recyclerEfficiency * Mathf.Clamp(item.conditionNormalized * item.maxConditionNormalized, 0.1f, 1f))
                : recyclerEfficiency;
        }

        private static int DetermineConsumptionAmount(Recycler recycler, Item item, float maxItemsInStackFraction)
        {
            var recycleAmount = 1;

            if (item.amount > 1)
            {
                recycleAmount = Mathf.CeilToInt(Mathf.Min(item.amount, item.MaxStackable() * maxItemsInStackFraction));

                // In case the configured multiplier is 0, ensure at least 1 item is recycled.
                recycleAmount = Math.Max(recycleAmount, 1);
            }

            // Call standard Oxide hook for compatibility.
            if (Interface.CallHook("OnItemRecycleAmount", item, recycleAmount, recycler) is int overrideAmount)
                return overrideAmount;

            return recycleAmount;
        }

        private static bool PopulateOutputWithOverride(Recycler recycler, IngredientInfo[] customIngredientList, int recycleAmount, float recycleEfficiency = 1, bool forEditor = false)
        {
            var outputIsFull = false;

            foreach (var ingredientInfo in customIngredientList)
            {
                if (ingredientInfo.ItemDefinition == null)
                    continue;

                var ingredientAmount = ingredientInfo.Amount;
                if (ingredientAmount <= 0)
                    continue;

                var outputAmount = CalculateOutputAmount(recycleAmount, ingredientAmount, recycleEfficiency);
                if (outputAmount <= 0)
                    continue;

                if (forEditor && outputAmount < 1)
                {
                    outputAmount = 1;
                }

                if (AddItemToRecyclerOutput(recycler, ingredientInfo.ItemDefinition, outputAmount, ingredientInfo.SkinId, ingredientInfo.DisplayName))
                {
                    outputIsFull = true;
                }
            }

            return outputIsFull;
        }

        private static bool PopulateOutputVanilla(Configuration config, Recycler recycler, Item item, int recycleAmount, float recycleEfficiency)
        {
            var outputIsFull = false;

            if (item.info.Blueprint.scrapFromRecycle > 0)
            {
                var scrapOutputMultiplier = config.OutputMultipliers.GetOutputMultiplier(ScrapItemId);
                var scrapAmountDecimal = item.info.Blueprint.scrapFromRecycle * (float)recycleAmount * scrapOutputMultiplier;

                if (item.MaxStackable() == 1 && item.hasCondition)
                {
                    scrapAmountDecimal *= item.conditionNormalized;
                }

                scrapAmountDecimal *= recycleEfficiency / ClassicRecycleEfficiency;
                var scrapAmountInt = Mathf.FloorToInt(scrapAmountDecimal);
                var scrapRemainderForThisCycle = scrapAmountDecimal - scrapAmountInt;

                if (scrapRemainderForThisCycle > 0)
                {
                    var recyclerScrapRemainder = recycler.scrapRemainder;
                    recyclerScrapRemainder += scrapRemainderForThisCycle;

                    var scrapRemainderToOutput = Mathf.FloorToInt(recyclerScrapRemainder);
                    if (scrapRemainderToOutput > 0)
                    {
                        recyclerScrapRemainder -= scrapRemainderToOutput;
                        scrapAmountInt += scrapRemainderToOutput;
                    }

                    recycler.scrapRemainder = recyclerScrapRemainder;
                }

                if (scrapAmountInt >= 1)
                {
                    var scrapItem = ItemManager.CreateByItemID(ScrapItemId, scrapAmountInt);
                    recycler.MoveItemToOutput(scrapItem);
                }
            }

            foreach (var ingredient in item.info.Blueprint.ingredients)
            {
                // Skip scrap since it's handled separately.
                if (ingredient.itemDef.itemid == ScrapItemId)
                    continue;

                var ingredientAmount = ingredient.amount / item.info.Blueprint.amountToCreate;
                if (ingredientAmount <= 0)
                    continue;

                var outputAmount = CalculateOutputAmount(
                    recycleAmount,
                    ingredientAmount,
                    recycleEfficiency,
                    config.OutputMultipliers.GetOutputMultiplier(ingredient.itemid)
                );

                if (outputAmount <= 0)
                    continue;

                if (AddItemToRecyclerOutput(recycler, ingredient.itemDef, outputAmount))
                {
                    outputIsFull = true;
                }
            }

            return outputIsFull;
        }

        #endregion

        #region UI

        private enum IdentificationType
        {
            Item,
            Skin,
            DisplayName,
        }

        private enum OutputType
        {
            NotRecyclable,
            Default,
            Custom,
        }

        private enum UICommand
        {
            Edit,
            Reset,
            Save,
            Cancel,
            InputPercentage,
            ChangeIdentificationType,
            ChangeOutputType,
        }

        [Flags]
        private enum LayoutOptions
        {
            AnchorBottom = 1 << 0,
            AnchorRight = 1 << 1,
            Vertical = 1 << 2,
        }

        private class LayoutProvider
        {
            public const string AnchorBottomLeft = "0 0";
            public const string AnchorBottomRight = "1 0";
            public const string AnchorTopLeft = "0 1";
            public const string AnchorTopRight = "1 1";

            public static LayoutProvider Once(float width = 0, float height = 0)
            {
                return _reusable.WithOffset().WithDimensions(width, height).WithOptions(0).WithSpacing(0);
            }

            private static LayoutProvider _reusable = new(0, 0);

            private LayoutOptions _options;
            private string _anchor;
            private float _x, _y;
            private float _xSpacing, _ySpacing;
            private float _width, _height;

            private bool _isVertical => (_options & LayoutOptions.Vertical) != 0;
            private bool _isLeftToRight => (_options & LayoutOptions.AnchorRight) == 0;
            private bool _isTopToBottom => (_options & LayoutOptions.AnchorBottom) == 0;
            private int _xSign => _isLeftToRight ? 1 : -1;
            private int _ySign => _isTopToBottom ? -1 : 1;
            private float XMin, XMax, YMin, YMax;

            public string AnchorMin => _anchor;
            public string AnchorMax => _anchor;
            public string OffsetMin => $"{XMin.ToString()} {YMin.ToString()}";
            public string OffsetMax => $"{XMax.ToString()} {YMax.ToString()}";

            public LayoutProvider(float width, float height)
            {
                WithDimensions(width, height);
                WithOptions(0);
            }

            public LayoutProvider WithDimensions(float width, float height)
            {
                _width = width;
                _height = height;
                return this;
            }

            public LayoutProvider WithOptions(LayoutOptions options)
            {
                _options = options;
                _anchor = DetermineAnchor();
                return this;
            }

            public LayoutProvider WithSpacing(float x, float y = float.MaxValue)
            {
                _xSpacing = x;
                _ySpacing = y != float.MaxValue ? y : x;
                return this;
            }

            public LayoutProvider WithOffset(float x = 0, float y = 0)
            {
                _x = x * _xSign;
                _y = y * _ySign;
                return this;
            }

            public LayoutProvider Next()
            {
                XMin = _x + _xSpacing * _xSign;
                YMin = _y + _ySpacing * _ySign;
                XMax = XMin + _width * _xSign;
                YMax = YMin + _height * _ySign;

                if (_isVertical)
                {
                    _y = YMax;
                }
                else
                {
                    _x = XMax;
                }

                if (YMin > YMax)
                {
                    Swap(ref YMin, ref YMax);
                }

                if (XMin > XMax)
                {
                    Swap(ref XMin, ref XMax);
                }

                return this;
            }

            public CuiRectTransformComponent GetRectTransform()
            {
                return new CuiRectTransformComponent
                {
                    AnchorMin = _anchor,
                    AnchorMax = _anchor,
                    OffsetMin = OffsetMin,
                    OffsetMax = OffsetMax,
                };
            }

            private string DetermineAnchor()
            {
                return _isTopToBottom
                    ? _isLeftToRight ? AnchorTopLeft : AnchorTopRight
                    : _isLeftToRight ? AnchorBottomLeft : AnchorBottomRight;
            }
        }

        private class ButtonColor
        {
            public readonly string Color;
            public readonly string TextColor;

            public ButtonColor(string color, string textColor)
            {
                Color = color;
                TextColor = textColor;
            }
        }

        private class ButtonColorScheme
        {
            public ButtonColor Active;
            public ButtonColor Enabled;
            public ButtonColor Disabled;

            public ButtonColor Get(bool active = false, bool enabled = false)
            {
                return active
                    ? Active
                    : enabled
                        ? Enabled
                        : Disabled;
            }
        }

        private class CuiInputFieldComponentHud : CuiInputFieldComponent
        {
            [JsonProperty("hudMenuInput", DefaultValueHandling = DefaultValueHandling.Ignore)]
            public bool HudMenuInput { get; set; }
        }

        private class CuiElementRecreate : CuiElement
        {
            [JsonProperty("destroyUi", DefaultValueHandling = DefaultValueHandling.Ignore)]
            public string DestroyUi { get; set; }
        }

        private static class EditUI
        {
            private const string UIName = "RecycleManager.UI";
            private const string EditPanelUIName = "RecycleManager.UI.EditPanel";
            private const string PercentagePanelUIName = "RecycleManager.UI.EditPanel.Percentages";

            private const string AnchorMin = "0.5 0";
            private const string AnchorMax = "0.5 0";

            private const float PanelWidth = 380.5f;
            private const float PanelHeight = 93f;
            private const float HeaderHeight = 21;

            private const int ItemPaddingLeft = 6;
            private const int ItemSize = 58;
            private const int ItemSpacing = 4;

            private const string BaseUICommand = "recyclemanager.ui";

            private const string TextColor = "0.8 0.8 0.8 1";
            private const string BackgroundColor = "0.25 0.25 0.25 1";

            private static ButtonColorScheme DefaultButtonColorScheme = new()
            {
                Active = new ButtonColor("0.25 0.5 0.75 1", "0.75 0.85 1 1"),
                Enabled = new ButtonColor("0.4 0.4 0.4 1", "0.71 0.71 0.71 1"),
                Disabled = new ButtonColor("0.4 0.4 0.4 0.5", "0.71 0.71 0.71 0.5"),
            };

            private static ButtonColorScheme SaveButtonColorScheme = new()
            {
                Enabled = new ButtonColor("0.451 0.553 0.271 1", "0.659 0.918 0.2 1"),
                Disabled = new ButtonColor("0.451 0.553 0.271 0.5", "0.659 0.918 0.2 0.5"),
            };

            private static ButtonColorScheme ResetButtonColorScheme = new()
            {
                Enabled = new ButtonColor("0.9 0.5 0.2 1", "1 0.9 0.7 1"),
                Disabled = new ButtonColor("0.9 0.5 0.2 0.25", "1 0.9 0.7 0.25"),
            };

            public static void DestroyUI(BasePlayer player)
            {
                CuiHelper.DestroyUi(player, UIName);
            }

            public static void DrawUI(RecycleManager plugin, BasePlayer player, EditState state)
            {
                if (state == null)
                {
                    DrawDefaultUI(plugin, player);
                }
                else
                {
                    DrawEditUI(plugin, player, state);
                }
            }

            private static void DrawDefaultUI(RecycleManager plugin, BasePlayer player)
            {
                var elements = CreateContainer();
                AddEditButton(elements, plugin, player);

                CuiHelper.AddUi(player, elements);
            }

            private static void DrawEditUI(RecycleManager plugin, BasePlayer player, EditState state)
            {
                var elements = CreateContainer();
                AddEditPanel(elements, plugin, player, state);

                CuiHelper.AddUi(player, elements);
            }

            private static CuiElementContainer CreateContainer()
            {
                var offsetY = 109.5f;
                var offsetX = 192f;

                return new CuiElementContainer
                {
                    new CuiElementRecreate
                    {
                        Parent = "Hud.Menu",
                        Name = UIName,
                        DestroyUi = UIName,
                        Components =
                        {
                            new CuiRectTransformComponent
                            {
                                AnchorMin = AnchorMin,
                                AnchorMax = AnchorMax,
                                OffsetMin = $"{offsetX} {offsetY}",
                                OffsetMax = $"{offsetX} {offsetY}",
                            },
                        },
                    },
                };
            }

            private static void AddEditButton(CuiElementContainer elements, RecycleManager plugin, BasePlayer player)
            {
                var buttonWidth = 80f;
                var offsetX = PanelWidth - buttonWidth;
                var offsetY = 266f;

                elements.Add(new CuiButton
                {
                    Text =
                    {
                        Text = plugin.GetMessage(player.UserIDString, LangEntry.UIButtonAdmin),
                        Color = SaveButtonColorScheme.Enabled.TextColor,
                        Align = TextAnchor.MiddleCenter,
                        FontSize = 15,
                    },
                    Button =
                    {
                        Color = SaveButtonColorScheme.Enabled.Color,
                        Command = $"{BaseUICommand} {UICommand.Edit}",
                        FadeIn = 0.1f,
                    },
                    RectTransform =
                    {
                        AnchorMin = "0 0",
                        AnchorMax = "0 0",
                        OffsetMin = $"{offsetX} {offsetY}",
                        OffsetMax = $"{offsetX + buttonWidth} {offsetY + HeaderHeight}",
                    },
                }, UIName);
            }

            private static void AddEditHeader(CuiElementContainer elements, RecycleManager plugin, BasePlayer player)
            {
                var offsetY = 266f;

                elements.Add(new CuiElement
                {
                    Parent = EditPanelUIName,
                    Components =
                    {
                        new CuiRawImageComponent
                        {
                            Color = BackgroundColor,
                            Sprite = "assets/content/ui/ui.background.tiletex.psd",
                        },
                        new CuiRectTransformComponent
                        {
                            AnchorMin = "0 0",
                            AnchorMax = "0 0",
                            OffsetMin = $"0 {offsetY}",
                            OffsetMax = $"{PanelWidth} {offsetY + HeaderHeight}",
                        },
                    },
                });

                elements.Add(new CuiElement
                {
                    Parent = EditPanelUIName,
                    Components =
                    {
                        new CuiTextComponent
                        {
                            Text = plugin.GetMessage(player.UserIDString, LangEntry.UIHeader),
                            Align = TextAnchor.MiddleLeft,
                            FontSize = 14,
                            Color = TextColor,
                        },
                        new CuiRectTransformComponent
                        {
                            AnchorMin = "0 0",
                            AnchorMax = "0 0",
                            OffsetMin = $"5 {offsetY}",
                            OffsetMax = $"{5 + PanelWidth} {offsetY + HeaderHeight}",
                        },
                    },
                });

                var buttonWidth = 80f;
                var offsetX = PanelWidth - buttonWidth;

                elements.Add(new CuiButton
                {
                    Text =
                    {
                        Text = plugin.GetMessage(player.UserIDString, LangEntry.UIButtonClose),
                        Color = DefaultButtonColorScheme.Enabled.TextColor,
                        Align = TextAnchor.MiddleCenter,
                        FontSize = 15,
                    },
                    Button =
                    {
                        Color = DefaultButtonColorScheme.Enabled.Color,
                        Command = $"{BaseUICommand} {UICommand.Cancel}",
                    },
                    RectTransform =
                    {
                        AnchorMin = "0 0",
                        AnchorMax = "0 0",
                        OffsetMin = $"{offsetX} {offsetY}",
                        OffsetMax = $"{offsetX + buttonWidth} {offsetY + HeaderHeight}",
                    },
                }, UIName);
            }

            private static void AddEditPanel(CuiElementContainer elements, RecycleManager plugin, BasePlayer player, EditState state)
            {
                elements.Add(new CuiElement
                {
                    Parent = UIName,
                    Name = EditPanelUIName,
                    Components =
                    {
                        new CuiRawImageComponent
                        {
                            Color = BackgroundColor,
                            Sprite = "assets/content/ui/ui.background.tiletex.psd",
                        },
                        new CuiRectTransformComponent
                        {
                            AnchorMin = AnchorMin,
                            AnchorMax = AnchorMax,
                            OffsetMin = "0 0",
                            OffsetMax = $"{PanelWidth} {PanelHeight}",
                        },
                    },
                });

                AddEditHeader(elements, plugin, player);

                if (state.BlockedByAnotherPlugin)
                {
                    elements.Add(new CuiElement
                    {
                        Parent = EditPanelUIName,
                        Components =
                        {
                            new CuiTextComponent
                            {
                                Text = plugin.GetMessage(player.UserIDString, LangEntry.UIItemBlocked),
                                Align = TextAnchor.MiddleCenter,
                                FontSize = 12,
                                Color = TextColor,
                            },
                            new CuiRectTransformComponent
                            {
                                AnchorMin = "0 0",
                                AnchorMax = "1 1",
                            },
                        },
                    });
                    return;
                }

                AddPercentageControllers(elements, state);

                if (state.InputItem == null)
                {
                    elements.Add(new CuiElement
                    {
                        Parent = EditPanelUIName,
                        Components =
                        {
                            new CuiTextComponent
                            {
                                Text = plugin.GetMessage(player.UserIDString, LangEntry.UIEmptyState),
                                Align = TextAnchor.MiddleCenter,
                                FontSize = 12,
                                Color = TextColor,
                            },
                            new CuiRectTransformComponent
                            {
                                AnchorMin = "0 0",
                                AnchorMax = "1 1",
                            },
                        },
                    });
                    return;
                }

                AddItemIdentificationControls(elements, plugin, player, state);
                AddItemAllowedControls(elements, plugin, player, state);
                AddPrimaryControls(elements, plugin, player, state);
            }

            private static void AddPercentageControllers(CuiElementContainer elements, EditState state)
            {
                var offsetY = 169;

                elements.Add(new CuiElement
                {
                    Name = PercentagePanelUIName,
                    Parent = EditPanelUIName,
                    Components =
                    {
                        new CuiRawImageComponent
                        {
                            Color = BackgroundColor,
                            Sprite = "assets/content/ui/ui.background.tiletex.psd",
                        },
                        new CuiRectTransformComponent
                        {
                            AnchorMin = "0 0",
                            AnchorMax = "0 0",
                            OffsetMin = $"0 {offsetY}",
                            OffsetMax = $"{PanelWidth} {offsetY + HeaderHeight}",
                        },
                    },
                });

                if (state.InputItem == null)
                    return;

                for (var i = 0; i < 6; i++)
                {
                    if (state.Chances[i] <= 0)
                        continue;

                    var offsetX = ItemPaddingLeft + i * (ItemSize + ItemSpacing);

                    elements.Add(new CuiElement
                    {
                        Parent = PercentagePanelUIName,
                        Components =
                        {
                            new CuiInputFieldComponentHud
                            {
                                Align = TextAnchor.MiddleCenter,
                                HudMenuInput = true,
                                Text = $"{state.Chances[i]:0.##}%",
                                Color = TextColor,
                                CharsLimit = 6,
                                Command = $"{BaseUICommand} {UICommand.InputPercentage} {i}",
                            },
                            new CuiRectTransformComponent
                            {
                                AnchorMin = "0 0",
                                AnchorMax = "0 0",
                                OffsetMin = $"{offsetX} 0",
                                OffsetMax = $"{offsetX + ItemSize} {HeaderHeight}",
                            },
                        },
                    });
                }
            }

            private static void AddButton(CuiElementContainer elements,
                LayoutProvider layoutProvider,
                ButtonColorScheme buttonColorScheme,
                string parent,
                string text,
                string command,
                bool active = false,
                bool enabled = true)
            {
                var buttonColor = buttonColorScheme.Get(active, enabled);

                elements.Add(new CuiButton
                {
                    Text =
                    {
                        Text = text,
                        Color = buttonColor.TextColor,
                        Align = TextAnchor.MiddleCenter,
                        FontSize = 12,
                    },
                    Button =
                    {
                        Color = buttonColor.Color,
                        Command = enabled && !active ? command : null,
                    },
                    RectTransform =
                    {
                        AnchorMin = layoutProvider.AnchorMin,
                        AnchorMax = layoutProvider.AnchorMax,
                        OffsetMin = layoutProvider.OffsetMin,
                        OffsetMax = layoutProvider.OffsetMax,
                    },
                }, parent);
            }

            private static void AddItemIdentificationControls(CuiElementContainer elements, RecycleManager plugin, BasePlayer player, EditState state)
            {
                var spacingX = 5;
                var spacingY = 2;
                var paddingY = 5;
                var numElements = 4;

                var columnWidth = PanelWidth / 3f;
                var elementHeight = (PanelHeight - 2 * paddingY - (numElements - 1) * spacingY) / numElements;

                var layoutProvider = LayoutProvider.Once(columnWidth - 2 * spacingX, elementHeight)
                    .WithOffset(0, paddingY - spacingY)
                    .WithSpacing(spacingX, spacingY)
                    .WithOptions(LayoutOptions.Vertical);

                elements.Add(new CuiElement
                {
                    Parent = EditPanelUIName,
                    Components =
                    {
                        new CuiTextComponent
                        {
                            Text = plugin.GetMessage(player.UserIDString, LangEntry.UILabelConfigureBy),
                            Align = TextAnchor.MiddleCenter,
                            FontSize = 12,
                            Color = TextColor,
                        },
                        layoutProvider.Next().GetRectTransform(),
                    },
                });

                AddButton(elements,
                    layoutProvider.Next(),
                    DefaultButtonColorScheme,
                    EditPanelUIName,
                    plugin.GetMessage(player.UserIDString, LangEntry.UIButtonItem),
                    $"{BaseUICommand} {UICommand.ChangeIdentificationType} {IdentificationType.Item}",
                    state.IdentificationType == IdentificationType.Item,
                    state.InputItem.skin != 0);

                AddButton(elements,
                    layoutProvider.Next(),
                    DefaultButtonColorScheme,
                    EditPanelUIName,
                    plugin.GetMessage(player.UserIDString, LangEntry.UIButtonSkin),
                    $"{BaseUICommand}
// ... [Code preview truncated due to length. Full source in attached file] ...

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