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

FREE QuickStack : 1.1.3 1.1.3

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Oxide.Core.Plugins;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("QuickStack", "Arkto", "1.1.3")]
    [Description("Quickly moves matching inventory items into storage containers.")]
    public class QuickStack : RustPlugin
    {
        private const string PermUse = "quickstack.use";
        private const string UiName = "QuickStack.UI";
        private const string UiParent = "Hud.Menu";

        private ConfigData _config;

        private class ConfigData
        {
            [JsonProperty("Inventory")]
            public InventoryConfig Inventory = new InventoryConfig();

            [JsonProperty("Containers")]
            public ContainerConfig Containers = new ContainerConfig();

            [JsonProperty("UI")]
            public UiConfig UI = new UiConfig();

            [JsonProperty("Notifications")]
            public NotificationConfig Notifications = new NotificationConfig();

            [JsonProperty("Quick Command")]
            public CommandConfig Command = new CommandConfig();
        }

        private class UiConfig
        {
            [JsonProperty("Enabled")] public bool Enabled = true;
            [JsonProperty("Button Text")] public string Text = "STACK";
            [JsonProperty("Button Color")] public string Color = "#333D30F2";
            [JsonProperty("Text Color")] public string TextColor = "#E6E6DB";
            [JsonProperty("Position X")] public float PositionX = 120f;
            [JsonProperty("Position Y")] public float PositionY = 355f;
            [JsonProperty("Width")] public float Width = 60f;
            [JsonProperty("Height")] public float Height = 20f;

            // Kept only for automatic migration from <= 1.1.5 configs.
            [JsonProperty("Offset Min", NullValueHandling = NullValueHandling.Ignore)] public string LegacyOffsetMin;
            [JsonProperty("Offset Max", NullValueHandling = NullValueHandling.Ignore)] public string LegacyOffsetMax;
        }

        private class CommandConfig
        {
            [JsonProperty("Enabled")] public bool Enabled = true;
            [JsonProperty("Command")] public string Name = "qs";
            [JsonProperty("Raycast Distance")] public float Distance = 3f;
        }

        private class InventoryConfig
        {
            [JsonProperty("Use Main Inventory")] public bool Main = true;
            [JsonProperty("Use Belt")] public bool Belt = false;
            [JsonProperty("Use Wear")] public bool Wear = false;
        }

        private class ContainerConfig
        {
            [JsonProperty("Storage Containers")] public bool Storage = true;
            [JsonProperty("Tool Cupboards")] public bool Cupboards = true;
            [JsonProperty("Furnaces")] public bool Furnaces = false;
            [JsonProperty("Recyclers")] public bool Recyclers = false;
            [JsonProperty("Vending Machines")] public bool Vending = false;
            [JsonProperty("Allow Prefabs", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List<string> Allow = new List<string>();
            [JsonProperty("Deny Prefabs", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List<string> Deny = new List<string>();
        }

        private class NotificationConfig
        {
            [JsonProperty("Enabled")] public bool Enabled = true;
            [JsonProperty("Show Total Types")] public bool ShowTypes = true;
            [JsonProperty("Show Item Breakdown")] public bool ShowItems = true;
            [JsonProperty("Max Item Lines")] public int MaxLines = 8;
        }

        protected override void LoadDefaultConfig()
        {
            _config = new ConfigData();
            SaveConfig();
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _config = Config.ReadObject<ConfigData>() ?? new ConfigData();
            }
            catch
            {
                PrintWarning("Invalid configuration. Creating a new one.");
                _config = new ConfigData();
            }
            ValidateConfig();
            SaveConfig();
        }

        protected override void SaveConfig() => Config.WriteObject(_config, true);

        private void ValidateConfig()
        {
            if (_config.Inventory == null) _config.Inventory = new InventoryConfig();
            if (_config.Containers == null) _config.Containers = new ContainerConfig();
            if (_config.UI == null) _config.UI = new UiConfig();
            if (_config.Notifications == null) _config.Notifications = new NotificationConfig();
            if (_config.Command == null) _config.Command = new CommandConfig();
            if (_config.Containers.Allow == null) _config.Containers.Allow = new List<string>();
            if (_config.Containers.Deny == null) _config.Containers.Deny = new List<string>();

            MigrateUiOffsets();

            _config.UI.Width = Mathf.Max(1f, _config.UI.Width);
            _config.UI.Height = Mathf.Max(1f, _config.UI.Height);
            _config.Notifications.MaxLines = Mathf.Max(0, _config.Notifications.MaxLines);
            _config.Command.Distance = Mathf.Max(0.5f, _config.Command.Distance);
        }

        private void MigrateUiOffsets()
        {
            if (string.IsNullOrWhiteSpace(_config.UI.LegacyOffsetMin) ||
                string.IsNullOrWhiteSpace(_config.UI.LegacyOffsetMax))
                return;

            bool oldOverlayDefault = _config.UI.LegacyOffsetMin == "120 -57" &&
                                     _config.UI.LegacyOffsetMax == "180 -37";
            bool oldInventoryDefault = _config.UI.LegacyOffsetMin == "120 -20" &&
                                       _config.UI.LegacyOffsetMax == "180 0";

            if (oldOverlayDefault || oldInventoryDefault)
            {
                // Known defaults from older layouts need the new Hud.Menu coordinate space.
                _config.UI.PositionX = 120f;
                _config.UI.PositionY = 345f;
                _config.UI.Width = 60f;
                _config.UI.Height = 20f;
            }
            else
            {
                float minX, minY, maxX, maxY;
                if (TryParseOffset(_config.UI.LegacyOffsetMin, out minX, out minY) &&
                    TryParseOffset(_config.UI.LegacyOffsetMax, out maxX, out maxY))
                {
                    // Preserve custom 1.1.5 placement while separating position from size.
                    _config.UI.PositionX = minX;
                    _config.UI.PositionY = minY;
                    _config.UI.Width = Mathf.Max(1f, maxX - minX);
                    _config.UI.Height = Mathf.Max(1f, maxY - minY);
                }
            }

            // Remove legacy fields from the next saved config.
            _config.UI.LegacyOffsetMin = null;
            _config.UI.LegacyOffsetMax = null;
        }

        private bool TryParseOffset(string value, out float x, out float y)
        {
            x = 0f;
            y = 0f;

            if (string.IsNullOrWhiteSpace(value))
                return false;

            var parts = value.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
            return parts.Length == 2 &&
                   float.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out x) &&
                   float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out y);
        }

        private void Init()
        {
            permission.RegisterPermission(PermUse, this);
            RegisterMessages();

            if (_config.Command.Enabled && !string.IsNullOrWhiteSpace(_config.Command.Name))
            {
                cmd.AddConsoleCommand(_config.Command.Name, this, nameof(CommandQuickStack));
                cmd.AddChatCommand(_config.Command.Name, this, nameof(ChatQuickStack));
            }
        }

        private void Unload()
        {
            foreach (var player in BasePlayer.activePlayerList)
                DestroyUi(player);
        }

        private void RegisterMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["NoPermission"] = "You don't have permission to use QuickStack.",
                ["NoContainer"] = "Look at or open a supported container.",
                ["NothingToStack"] = "Nothing to stack.",
                ["StoredTypes"] = "QuickStack: stored {0} different item{1}.",
                ["MoreTypes"] = "+{0} more item type{1}"
            }, this);
        }

        private string Lang(string key, BasePlayer player)
        {
            return lang.GetMessage(key, this, player?.UserIDString);
        }

        private void OnLootEntity(BasePlayer player, BaseEntity entity)
        {
            if (!_config.UI.Enabled || !CanUse(player) || !IsAllowed(entity))
                return;

            NextTick(() =>
            {
                if (player == null || !player.IsConnected || player.inventory?.loot?.entitySource != entity)
                    return;

                ShowUi(player);
            });
        }

        private void OnLootEntityEnd(BasePlayer player, BaseCombatEntity entity) => DestroyUi(player);

        [ConsoleCommand("quickstack.ui")]
        private void UiQuickStack(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (!CanUse(player))
                return;

            var entity = player.inventory?.loot?.entitySource;
            if (entity == null || !IsAllowed(entity))
                return;

            QuickStackItems(player, GetInventory(entity), true);
        }

        private void CommandQuickStack(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player != null)
                RunCommand(player);
        }

        private void ChatQuickStack(BasePlayer player, string command, string[] args) => RunCommand(player);

        private void RunCommand(BasePlayer player)
        {
            if (!CanUse(player))
            {
                SendReply(player, Lang("NoPermission", player));
                return;
            }

            var entity = player.inventory?.loot?.entitySource ?? LookEntity(player);
            if (entity == null || !IsAllowed(entity))
            {
                SendReply(player, Lang("NoContainer", player));
                return;
            }

            if (QuickStackItems(player, GetInventory(entity), true) == 0)
                SendReply(player, Lang("NothingToStack", player));
        }

        private int QuickStackItems(BasePlayer player, ItemContainer target, bool notify)
        {
            if (player == null || target == null || target.itemList.Count == 0)
                return 0;

            // Snapshot before moving anything: only item types already in the target qualify.
            var allowed = new HashSet<int>();
            for (int i = 0; i < target.itemList.Count; i++)
            {
                var targetItem = target.itemList[i];
                if (targetItem?.info != null)
                    allowed.Add(targetItem.info.itemid);
            }

            var moved = new Dictionary<int, int>();

            foreach (var source in SourceContainers(player))
            {
                foreach (var item in source.itemList.ToArray())
                {
                    if (item?.info == null || !allowed.Contains(item.info.itemid))
                        continue;

                    int itemId = item.info.itemid;
                    int before = item.amount;

                    item.MoveToContainer(target, -1, true);

                    int remaining = item.parent == source ? item.amount : 0;
                    int amount = before - remaining;
                    if (amount <= 0)
                        continue;

                    int current;
                    moved.TryGetValue(itemId, out current);
                    moved[itemId] = current + amount;
                }
            }

            int total = 0;
            foreach (var entry in moved)
                total += entry.Value;
            if (total == 0)
                return 0;

            target.MarkDirty();
            player.inventory.ServerUpdate(0f);

            if (notify && _config.Notifications.Enabled)
                Notify(player, moved);

            return total;
        }

        private IEnumerable<ItemContainer> SourceContainers(BasePlayer player)
        {
            if (_config.Inventory.Main && player.inventory.containerMain != null)
                yield return player.inventory.containerMain;

            if (_config.Inventory.Belt && player.inventory.containerBelt != null)
                yield return player.inventory.containerBelt;

            if (_config.Inventory.Wear && player.inventory.containerWear != null)
                yield return player.inventory.containerWear;
        }

        private void Notify(BasePlayer player, Dictionary<int, int> moved)
        {
            var lines = new List<string>();

            if (_config.Notifications.ShowTypes)
                lines.Add(string.Format(Lang("StoredTypes", player), moved.Count, moved.Count == 1 ? string.Empty : "s"));

            if (_config.Notifications.ShowItems)
            {
                int max = Mathf.Max(0, _config.Notifications.MaxLines);
                var entries = new List<KeyValuePair<int, int>>(moved);
                entries.Sort((a, b) => b.Value.CompareTo(a.Value));
                int shown = Math.Min(entries.Count, max);

                for (int i = 0; i < shown; i++)
                {
                    var entry = entries[i];
                    var def = ItemManager.FindItemDefinition(entry.Key);
                    string name = def?.displayName?.english ?? def?.shortname ?? entry.Key.ToString();
                    lines.Add($"{name}: +{entry.Value:N0}");
                }

                int hidden = moved.Count - shown;
                if (hidden > 0)
                    lines.Add(string.Format(Lang("MoreTypes", player), hidden, hidden == 1 ? string.Empty : "s"));
            }

            if (lines.Count > 0)
                SendReply(player, string.Join("\n", lines));
        }

        private void ShowUi(BasePlayer player)
        {
            DestroyUi(player);

            var ui = new Oxide.Game.Rust.Cui.CuiElementContainer();
            ui.Add(new Oxide.Game.Rust.Cui.CuiButton
            {
                Button =
                {
                    Command = "quickstack.ui",
                    Color = CuiColor(_config.UI.Color, "0.20 0.24 0.19 0.95")
                },
                RectTransform =
                {
                    // Rust's inventory is laid out from the lower center of Hud.Menu.
                    // Position is the button's lower-left offset from that anchor; size is configured separately.
                    AnchorMin = "0.5 0.0",
                    AnchorMax = "0.5 0.0",
                    OffsetMin = FormatOffset(_config.UI.PositionX, _config.UI.PositionY),
                    OffsetMax = FormatOffset(_config.UI.PositionX + _config.UI.Width, _config.UI.PositionY + _config.UI.Height)
                },
                Text =
                {
                    Text = _config.UI.Text,
                    FontSize = 12,
                    Align = TextAnchor.MiddleCenter,
                    Color = CuiColor(_config.UI.TextColor, "0.90 0.90 0.86 1")
                }
            }, UiParent, UiName);

            Oxide.Game.Rust.Cui.CuiHelper.AddUi(player, ui);
        }

        private void DestroyUi(BasePlayer player)
        {
            if (player != null)
                Oxide.Game.Rust.Cui.CuiHelper.DestroyUi(player, UiName);
        }

        private bool CanUse(BasePlayer player) =>
            player != null && permission.UserHasPermission(player.UserIDString, PermUse);

        private ItemContainer GetInventory(BaseEntity entity)
        {
            if (entity is StorageContainer storage) return storage.inventory;
            if (entity is BuildingPrivlidge cupboard) return cupboard.inventory;
            if (entity is Recycler recycler) return recycler.inventory;
            if (entity is VendingMachine vending) return vending.inventory;
            if (entity is BaseOven oven) return oven.inventory;
            return null;
        }

        private bool IsAllowed(BaseEntity entity)
        {
            if (entity == null || GetInventory(entity) == null)
                return false;

            string prefab = entity.ShortPrefabName ?? string.Empty;
            if (Matches(prefab, _config.Containers.Deny)) return false;
            if (Matches(prefab, _config.Containers.Allow)) return true;

            if (entity is BuildingPrivlidge) return _config.Containers.Cupboards;
            if (entity is Recycler) return _config.Containers.Recyclers;
            if (entity is VendingMachine) return _config.Containers.Vending;
            if (entity is BaseOven) return _config.Containers.Furnaces;
            return entity is StorageContainer && _config.Containers.Storage;
        }

        private bool Matches(string prefab, List<string> list)
        {
            if (list == null)
                return false;

            for (int i = 0; i < list.Count; i++)
            {
                string value = list[i];
                if (!string.IsNullOrWhiteSpace(value) &&
                    prefab.IndexOf(value.Trim(), StringComparison.OrdinalIgnoreCase) >= 0)
                    return true;
            }

            return false;
        }

        private BaseEntity LookEntity(BasePlayer player)
        {
            RaycastHit hit;
            var ray = new Ray(player.eyes.position, player.eyes.HeadForward());
            int mask = LayerMask.GetMask("Construction", "Deployed", "Default");

            return Physics.Raycast(ray, out hit, Mathf.Max(0.5f, _config.Command.Distance), mask,
                QueryTriggerInteraction.Ignore) ? hit.GetEntity() : null;
        }

        private string FormatOffset(float x, float y)
        {
            return string.Format(CultureInfo.InvariantCulture, "{0:0.###} {1:0.###}", x, y);
        }

        private string CuiColor(string value, string fallback)
        {
            if (string.IsNullOrWhiteSpace(value))
                return fallback;

            string input = value.Trim();

            // Keep native Rust CUI RGBA untouched.
            var parts = input.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
            if (parts.Length == 4)
            {
                bool valid = true;
                float number;

                for (int i = 0; i < parts.Length; i++)
                {
                    if (float.TryParse(parts[i], NumberStyles.Float, CultureInfo.InvariantCulture, out number))
                        continue;

                    valid = false;
                    break;
                }

                if (valid)
                    return input;
            }

            // Convert #RRGGBB / #RRGGBBAA to normalized CUI RGBA.
            string hex = input.TrimStart('#');
            if (hex.Length != 6 && hex.Length != 8)
                return fallback;

            uint rgba;
            if (!uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out rgba))
                return fallback;

            byte r, g, b, a;
            if (hex.Length == 6)
            {
                r = (byte)(rgba >> 16);
                g = (byte)(rgba >> 8);
                b = (byte)rgba;
                a = 255;
            }
            else
            {
                r = (byte)(rgba >> 24);
                g = (byte)(rgba >> 16);
                b = (byte)(rgba >> 8);
                a = (byte)rgba;
            }

            return string.Format(CultureInfo.InvariantCulture, "{0:0.###} {1:0.###} {2:0.###} {3:0.###}",
                r / 255f, g / 255f, b / 255f, a / 255f);
        }

        [HookMethod("API_QuickStack")]
        private object API_QuickStack(BasePlayer player, BaseEntity entity)
        {
            if (!CanUse(player) || !IsAllowed(entity))
                return 0;

            return QuickStackItems(player, GetInventory(entity), false);
        }
    }
}

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