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

FREE TeamSkins : 2.2.0 2.2.0

Нет прав для скачивания
C#:
using System.Collections.Generic;
using System.Linq;
using Facepunch;
using Network;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Core.Libraries.Covalence;
using Oxide.Game.Rust;
using Oxide.Game.Rust.Cui;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Team Skins", "Orangemart", "2.2.0")]
    [Description("Skin sharing system.Supports Redirects, Team Sharing, and Configurable Skins.")]
    public class TeamSkins : RustPlugin
    {
        [PluginReference] private Plugin PlayerDLCAPI;

        private const string PermUse = "teamskins.use";
        private const string UIPanelName = "teamskins.pagination";
        
        // --- Cache Data ---
        private readonly Dictionary<string, List<ulong>> _autoSkinCache = new Dictionary<string, List<ulong>>();
        private readonly Dictionary<string, List<SkinConfigEntry>> _manualSkinCache = new Dictionary<string, List<SkinConfigEntry>>();
        private readonly Dictionary<ulong, ItemContainer> _openContainers = new Dictionary<ulong, ItemContainer>();
        private readonly HashSet<ulong> _openingPlayers = new HashSet<ulong>();
        private readonly Dictionary<ulong, int> _playerPages = new Dictionary<ulong, int>();
        private readonly HashSet<ItemId> _ghostItemIds = new HashSet<ItemId>();

        // --- Configuration ---
        private PluginConfig _config;

        private class PluginConfig
        {
            [JsonProperty("Commands", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> Commands = new List<string> { "skin", "skins", "sb" };

            [JsonProperty("Container Panel Name")]
            public string PanelName = "generic";

            [JsonProperty("Container Capacity")]
            public int Capacity = 36;

            [JsonProperty("Extra Skins", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<SkinConfigEntry> ExtraSkins = new List<SkinConfigEntry>(); 
        }

        private class SkinConfigEntry
        {
            [JsonProperty("Item Shortname")]
            public string Shortname;

            [JsonProperty("Permission")]
            public string Permission;

            [JsonProperty("Skins")]
            public List<ulong> Skins;
        }

        #region Setup & Config

        protected override void LoadDefaultConfig()
        {
            _config = new PluginConfig
            {
                ExtraSkins = new List<SkinConfigEntry>
                {
                    // Example provided for server admins to understand JSON structure
                    new SkinConfigEntry
                    {
                        Shortname = "hoodie",
                        Permission = "teamskins.admin",
                        Skins = new List<ulong> { 3492377614 }
                    }
                }
            };
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _config = Config.ReadObject<PluginConfig>();
                if (_config == null) throw new System.Exception();
            }
            catch
            {
                PrintError("Configuration file is corrupt! Loading default config.");
                LoadDefaultConfig();
            }
            SaveConfig();
        }

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

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

            // Register Permissions from Config
            if (_config.ExtraSkins != null)
            {
                foreach (var entry in _config.ExtraSkins)
                {
                    if (!string.IsNullOrEmpty(entry.Permission))
                    {
                        if (!permission.PermissionExists(entry.Permission, this))
                            permission.RegisterPermission(entry.Permission, this);
                    }
                }
            }

            // Optimize Config Skins for Lookup
            BuildManualCache();
        }

        private Timer _initTimer;

        private void OnServerInitialized()
        {
            // Register Commands
            foreach (var cmd in _config.Commands)
            {
                AddCovalenceCommand(cmd, nameof(CmdSkin));
            }

            Puts($"[Team Skins] Registered {_config.Commands.Count} commands.Waiting for Steam Inventory...");

            // Check every 10 seconds, up to 12 times (2 minutes total)
            _initTimer = timer.Repeat(10f, 12, TryBuildCache);
        }

        private void TryBuildCache()
        {
            // Check if Steam definitions are actually populated
            var defs = Steamworks.SteamInventory.Definitions;
            int count = (defs != null) ? defs.Length : 0;

            // Rust usually has 5000+ skins.If we have less than 500, Steam hasn't finished loading.if (count > 500)
            {
                Puts($"[Team Skins] Steam Inventory detected ({count} items).Building Cache...");
                BuildUniversalCache();
                
                // Stop the timer so we don't keep building
                if (_initTimer != null && !_initTimer.Destroyed)
                {
                    _initTimer.Destroy();
                    _initTimer = null;
                }
            }
            else
            {
                Puts($"[Team Skins] Still waiting for skin definitions...(Found: {count})");
            }
        }

        private void Unload()
        {
            foreach (var kvp in _openContainers)
            {
                var player = BasePlayer.FindByID(kvp.Key);
                if (kvp.Value != null)
                {
                    if (player != null)
                    {
                        player.GiveItem(kvp.Value.GetSlot(0));
                        DestroyPaginationUI(player);
                    }
                    kvp.Value.Kill();
                }
            }
            _openContainers.Clear();
            _openingPlayers.Clear();
            _ghostItemIds.Clear();
        }

        #endregion

        #region Commands & Logic

        private void CmdSkin(IPlayer iplayer, string command, string[] args)
        {
            var player = iplayer.Object as BasePlayer;
            if (player == null) return;

            if (args.Length > 0 && args[0].ToLower() == "refresh" && player.IsAdmin)
            {
                player.ChatMessage("Rebuilding skin cache...");
                BuildUniversalCache();
                LoadConfig();
                BuildManualCache();
                return;
            }

            if (!iplayer.HasPermission(PermUse))
            {
                player.ChatMessage("You do not have permission (teamskins.use).");
                return;
            }

            _openingPlayers.Add(player.userID);
            if (player.inventory.loot.IsLooting()) player.EndLooting();
            
            timer.In(0.2f, () => OpenVirtualBox(player));
        }

        private void OpenVirtualBox(BasePlayer player)
        {
            if (player == null || !player.IsConnected)
            {
                _openingPlayers.Remove(player.userID);
                return;
            }

            if (_openContainers.ContainsKey(player.userID))
            {
                var old = _openContainers[player.userID];
                if(old != null) old.Kill();
                _openContainers.Remove(player.userID);
            }

            var container = new ItemContainer();
            container.entityOwner = player;
            container.capacity = _config.Capacity;
            container.isServer = true;
            container.allowedContents = ItemContainer.ContentsType.Generic;
            container.GiveUID();
            
            _openContainers[player.userID] = container;

            player.inventory.loot.Clear();
            player.inventory.loot.PositionChecks = false;
            player.inventory.loot.entitySource = player;
            player.inventory.loot.itemSource = null;
            player.inventory.loot.AddContainer(container);
            player.inventory.loot.SendImmediate();
            
            player.ClientRPC(RpcTarget.Player("RPC_OpenLootPanel", player), _config.PanelName);
            timer.In(1.0f, () => _openingPlayers.Remove(player.userID));
        }

        #endregion

        #region Interaction Hooks

        private object CanLootPlayer(BasePlayer looter, BasePlayer target)
        {
            if (looter != target) return null;
            if (_openContainers.ContainsKey(looter.userID)) return true;
            return null;
        }

        private void OnPlayerLootEnd(PlayerLoot inventory)
        {
            var player = inventory.GetComponent<BasePlayer>();
            if (player == null) return;
            if (_openingPlayers.Contains(player.userID)) return;

            if (_openContainers.ContainsKey(player.userID))
            {
                if (inventory.entitySource == player)
                {
                    var container = _openContainers[player.userID];
                    var item = container.GetSlot(0);
                    
                    // Give back the item in Slot 0 (if it exists)
                    if (item != null) player.GiveItem(item);

                    for (int i = 1; i < container.capacity; i++)
                    {
                        var ghost = container.GetSlot(i);
                        if (ghost != null) _ghostItemIds.Remove(ghost.uid);
                    }

                    container.Kill();
                    _openContainers.Remove(player.userID);
                    DestroyPaginationUI(player);
                }
            }
        }

        // Fix: Visual Glitch - Clear ghosts if you drag the item out of Slot 0 safely
        private void OnItemRemovedFromContainer(ItemContainer container, Item item)
        {
            var ownerId = _openContainers.FirstOrDefault(x => x.Value == container).Key;
            if (ownerId == 0) return;

            // If Slot 0 is now empty, wipe the skin preview immediately
            if (container.GetSlot(0) == null)
            {
                for (int i = 1; i < container.capacity; i++)
                {
                    var ghost = container.GetSlot(i);
                    if (ghost != null)
                    {
                        SafeRemoveItem(ghost);
                    }
                }
                
                var player = BasePlayer.FindByID(ownerId);
                if (player != null) DestroyPaginationUI(player);
            }
        }

        private void OnItemAddedToContainer(ItemContainer container, Item item)
        {
            var ownerId = _openContainers.FirstOrDefault(x => x.Value == container).Key;
            if (ownerId == 0) return;

            if (item.position == 0)
            {
                _playerPages[ownerId] = 0; // Reset pagination
                var player = BasePlayer.FindByID(ownerId);
                if (player != null) NextFrame(() => DisplaySkins(player, container, item));
            }
        }

        private void DisplaySkins(BasePlayer player, ItemContainer container, Item targetItem)
        {
            // Clear existing ghosts safely
            for (int i = 1; i < container.capacity; i++)
            {
                var ghost = container.GetSlot(i);
                if (ghost != null) SafeRemoveItem(ghost);
            }

            var baseDef = GetBaseItemDef(targetItem.info);
            var skins = GetCombinedSkins(player, baseDef.shortname);

            if (skins.Count == 0)
            {
                player.ChatMessage("No skins found for this item.");
                DestroyPaginationUI(player);
                return;
            }

            // Get current page
            if (!_playerPages.TryGetValue(player.userID, out int currentPage))
                currentPage = 0;

            // Slot 0 is item.Rest of slots are for skin previews.int maxSkinsPerPage = container.capacity - 1; 
            int startIndex = currentPage * maxSkinsPerPage;

            // Safety fallback
            if (startIndex >= skins.Count) 
            {
                currentPage = 0;
                _playerPages[player.userID] = 0;
                startIndex = 0;
            }

            int slot = 1;
            for (int i = startIndex; i < skins.Count && slot <= maxSkinsPerPage; i++)
            {
                var skinId = skins[i];
                Item ghost = ItemManager.Create(baseDef, 1, skinId);
                if (ghost != null)
                {
                    ghost.condition = targetItem.condition;
                    ghost.maxCondition = targetItem.maxCondition;
                    
                    var projectile = ghost.GetHeldEntity() as BaseProjectile;
                    if (projectile != null && projectile.primaryMagazine != null)
                    {
                        projectile.primaryMagazine.contents = 0;
                    }
                    
                    ghost.MoveToContainer(container, slot);
                    _ghostItemIds.Add(ghost.uid);
                    slot++;
                }
            }

            // Draw CUI pagination buttons
            int totalPages = Mathf.CeilToInt((float)skins.Count / maxSkinsPerPage);
            if (totalPages > 1)
            {
                DrawPaginationUI(player, currentPage, totalPages);
            }
            else
            {
                DestroyPaginationUI(player);
            }
        }

        private object CanMoveItem(Item item, PlayerInventory playerLoot, ItemContainerId targetContainer, int targetSlot, int amount)
        {
            var player = playerLoot.GetComponent<BasePlayer>();
            if (player == null || !_openContainers.TryGetValue(player.userID, out var box)) return null;

            // Block any placement/movement directly into the ghost slots (slots 1 to capacity-1)
            if (targetContainer == box.uid && targetSlot > 0) return false;

            // Interaction: Clicking/Dragging a Skin Ghost
            if (_ghostItemIds.Contains(item.uid))
            {
                var originalItem = box.GetSlot(0);
                if (originalItem != null && originalItem.IsValid() && originalItem.parent == box)
                {
                    bool isRedirect = item.info.shortname != originalItem.info.shortname;
                    ulong skinId = item.skin;

                    NextFrame(() =>
                    {
                        if (player == null || !player.IsConnected) return;

                        ItemContainer activeBox;
                        if (!_openContainers.TryGetValue(player.userID, out activeBox) || activeBox != box) return;

                        if (originalItem == null || !originalItem.IsValid() || originalItem.parent != box) return;
                        if (item == null || !item.IsValid() || item.parent != box) return;

                        if (isRedirect)
                        {
                            _ghostItemIds.Remove(item.uid);
                            TransferItemProps(originalItem, item);
                            
                            ItemContainer target = player.inventory.FindContainer(targetContainer);
                            if (target == null || !item.MoveToContainer(target, targetSlot))
                            {
                                player.GiveItem(item);
                            }
                            
                            originalItem.Remove();
                        }
                        else
                        {
                            originalItem.skin = skinId;
                            originalItem.MarkDirty(); 

                            var heldEntity = originalItem.GetHeldEntity();
                            if (heldEntity != null)
                            {
                                heldEntity.skinID = skinId;
                                heldEntity.SendNetworkUpdate();
                            }

                            ItemContainer target = player.inventory.FindContainer(targetContainer);
                            if (target == null || !originalItem.MoveToContainer(target, targetSlot))
                            {
                                player.GiveItem(originalItem);
                            }
                        }

                        player.ChatMessage($"Skin Applied! ({item.info.displayName.translated})");
                        
                        player.inventory.SendSnapshot();
                        
                        if (player.IsConnected) player.EndLooting();
                    });
                }

                return false; 
            }

            return null;
        }

        [ConsoleCommand("teamskins.page")]
        private void CmdConsolePage(ConsoleSystem.Arg arg)
        {
            var player = arg.Connection?.player as BasePlayer;
            if (player == null) return;

            ItemContainer box;
            if (!_openContainers.TryGetValue(player.userID, out box) || box == null)
                return;

            var originalItem = box.GetSlot(0);
            if (originalItem == null || !originalItem.IsValid())
                return;

            if (arg.Args == null || arg.Args.Length == 0) return;

            string action = arg.Args[0].ToString().ToLower();
            if (action == "next")
            {
                if (!_playerPages.ContainsKey(player.userID)) _playerPages[player.userID] = 0;
                _playerPages[player.userID]++;
            }
            else if (action == "prev")
            {
                if (_playerPages.TryGetValue(player.userID, out int page) && page > 0)
                {
                    _playerPages[player.userID] = page - 1;
                }
            }

            DisplaySkins(player, box, originalItem);
        }

        private void DestroyPaginationUI(BasePlayer player)
        {
            if (player != null)
            {
                CuiHelper.DestroyUi(player, UIPanelName);
            }
        }

        private void DrawPaginationUI(BasePlayer player, int currentPage, int totalPages)
        {
            CuiHelper.DestroyUi(player, UIPanelName);

            var elements = new CuiElementContainer();

            // Main Background Panel - Shifted to the right to expose the LOOT header
            elements.Add(new CuiPanel
            {
                Image = { Color = "0.08 0.08 0.08 0.9", Material = "assets/content/ui/uibackgroundblur-ingamemenu.mat" },
                RectTransform = { AnchorMin = "0.71 0.70", AnchorMax = "0.85 0.745" },
                CursorEnabled = false
            }, "Overlay", UIPanelName);

            // Previous Button
            if (currentPage > 0)
            {
                elements.Add(new CuiButton
                {
                    Button = { Command = "teamskins.page prev", Color = "0.2 0.2 0.2 0.9" },
                    RectTransform = { AnchorMin = "0.02 0.12", AnchorMax = "0.32 0.88" },
                    Text = { Text = "◀ PREV", FontSize = 9, Align = TextAnchor.MiddleCenter, Color = "0.9 0.9 0.9 1" }
                }, UIPanelName);
            }
            else
            {
                elements.Add(new CuiPanel
                {
                    Image = { Color = "0.15 0.15 0.15 0.4" },
                    RectTransform = { AnchorMin = "0.02 0.12", AnchorMax = "0.32 0.88" }
                }, UIPanelName);
                
                elements.Add(new CuiLabel
                {
                    Text = { Text = "◀ PREV", FontSize = 9, Align = TextAnchor.MiddleCenter, Color = "0.4 0.4 0.4 1" },
                    RectTransform = { AnchorMin = "0.02 0.12", AnchorMax = "0.32 0.88" }
                }, UIPanelName);
            }

            // Page Indicator Label
            elements.Add(new CuiLabel
            {
                Text = { Text = $"PAGE {currentPage + 1} / {totalPages}", FontSize = 9, Align = TextAnchor.MiddleCenter, Color = "0.75 0.75 0.75 1" },
                RectTransform = { AnchorMin = "0.34 0.0", AnchorMax = "0.66 1.0" }
            }, UIPanelName);

            // Next Button
            if (currentPage < totalPages - 1)
            {
                elements.Add(new CuiButton
                {
                    Button = { Command = "teamskins.page next", Color = "0.2 0.2 0.2 0.9" },
                    RectTransform = { AnchorMin = "0.68 0.12", AnchorMax = "0.98 0.88" },
                    Text = { Text = "NEXT ▶", FontSize = 9, Align = TextAnchor.MiddleCenter, Color = "0.9 0.9 0.9 1" }
                }, UIPanelName);
            }
            else
            {
                elements.Add(new CuiPanel
                {
                    Image = { Color = "0.15 0.15 0.15 0.4" },
                    RectTransform = { AnchorMin = "0.68 0.12", AnchorMax = "0.98 0.88" }
                }, UIPanelName);
                
                elements.Add(new CuiLabel
                {
                    Text = { Text = "NEXT ▶", FontSize = 9, Align = TextAnchor.MiddleCenter, Color = "0.4 0.4 0.4 1" },
                    RectTransform = { AnchorMin = "0.68 0.12", AnchorMax = "0.98 0.88" }
                }, UIPanelName);
            }

            CuiHelper.AddUi(player, elements);

        }

        // --- SPLITTING EXPLOIT FIX ---
        private object OnItemSplit(Item item, int amount)
        {
            if (item == null) return null;

            if (_ghostItemIds.Contains(item.uid))
            {
                return true; // Block splitting ghost items
            }

            if (item.parent == null) return null;

            // Check if the split happened inside one of our skin containers
            if (_openContainers.ContainsValue(item.parent))
            {
                var ownerId = _openContainers.FirstOrDefault(x => x.Value == item.parent).Key;
                var player = BasePlayer.FindByID(ownerId);
                
                if (player != null)
                {
                    // Move the split offshoot directly back to the player's inventory
                    NextFrame(() =>
                    {
                        if (item != null && item.IsValid())
                        {
                            player.GiveItem(item);
                        }
                    });
                }
            }

            return null;
        }

        private void OnItemDropped(Item item, BaseEntity entity)
        {
            if (item == null) return;
            if (_ghostItemIds.Contains(item.uid))
            {
                _ghostItemIds.Remove(item.uid);
                item.Remove();
                if (entity != null && !entity.IsDestroyed)
                {
                    entity.Kill();
                }
            }
        }

        private object OnItemAction(Item item, string action, BasePlayer player)
        {
            if (item == null) return null;
            if (_ghostItemIds.Contains(item.uid))
            {
                return true; // Block UI actions (Drop, Split, etc.) on ghost items
            }
            return null;
        }

        // --- SAFE REMOVE ITEM LOGIC ---
        private void SafeRemoveItem(Item item)
        {
            if (item == null) return;
            _ghostItemIds.Remove(item.uid);
            item.Remove(); 
        }

        private void TransferItemProps(Item source, Item destination)
        {
            destination.amount = source.amount;
            destination.condition = source.condition;
            destination.maxCondition = source.maxCondition;
            
            if (source.contents != null && destination.contents != null)
            {
                for (int i = source.contents.itemList.Count - 1; i >= 0; i--)
                {
                    var child = source.contents.itemList[i];
                    child.MoveToContainer(destination.contents);
                }
            }
            
            var sourceProjectile = source.GetHeldEntity() as BaseProjectile;
            var destProjectile = destination.GetHeldEntity() as BaseProjectile;
            
            if (sourceProjectile != null && destProjectile != null)
            {
                destProjectile.primaryMagazine.contents = sourceProjectile.primaryMagazine.contents;
                destProjectile.primaryMagazine.ammoType = sourceProjectile.primaryMagazine.ammoType;
            }
        }

        #endregion

        #region Cache & Lookup

        private void BuildManualCache()
        {
            _manualSkinCache.Clear();
            if (_config.ExtraSkins == null) return;

            foreach (var entry in _config.ExtraSkins)
            {
                if (string.IsNullOrEmpty(entry.Shortname)) continue;

                if (!_manualSkinCache.ContainsKey(entry.Shortname))
                    _manualSkinCache[entry.Shortname] = new List<SkinConfigEntry>();

                _manualSkinCache[entry.Shortname].Add(entry);
            }
        }

        private void BuildUniversalCache()
        {
            _autoSkinCache.Clear();

            // 1.Internal Scan
            foreach (var def in ItemManager.itemList)
            {
                if (def.skins != null)
                {
                    foreach (var skin in def.skins)
                    {
                        ulong skinId = (ulong)skin.id;
                        if (skinId == 0) continue;
                        var uiDef = def.isRedirectOf ?? def;
                        AddAutoCache(uiDef.shortname, skinId);
                    }
                }
            }

            // 2.Workshop Scan
            if (Steamworks.SteamInventory.Definitions != null)
            {
                foreach (var def in Steamworks.SteamInventory.Definitions)
                {
                    string shortname = def.GetProperty("itemshortname");
                    string workshopIdStr = def.GetProperty("workshopid");
                    if (string.IsNullOrEmpty(shortname)) continue;

                    ulong skinId = 0;
                    if (!string.IsNullOrEmpty(workshopIdStr) && ulong.TryParse(workshopIdStr, out ulong wId)) skinId = wId;
                    else skinId = (ulong)def.Id;

                    if (skinId != 0) AddAutoCache(shortname, skinId);
                }
            }
        }

        private void AddAutoCache(string shortname, ulong skinId)
        {
            if (!_autoSkinCache.ContainsKey(shortname)) _autoSkinCache[shortname] = new List<ulong>();
            if (!_autoSkinCache[shortname].Contains(skinId)) _autoSkinCache[shortname].Add(skinId);
        }

        private ItemDefinition GetBaseItemDef(ItemDefinition currentDef)
        {
            return currentDef.isRedirectOf ?? currentDef;
        }

        private List<ulong> GetCombinedSkins(BasePlayer player, string shortname)
        {
            var results = new HashSet<ulong>();

            // 1.Resolve Team Members
            var teamMembers = new List<ulong> { player.userID };
            if (player.currentTeam != 0)
            {
                var team = RelationshipManager.ServerInstance.FindTeam(player.currentTeam);
                if (team != null) teamMembers = team.members;
            }

            foreach (var memberId in teamMembers)
            {
                var member = BasePlayer.Find(memberId.ToString());
                if (member == null || !member.IsConnected) continue;

                // 2.Check Auto Cache (Steam/Game Ownership)
                if (_autoSkinCache.TryGetValue(shortname, out var autoSkins))
                {
                    foreach (var skinId in autoSkins)
                    {
                        if (HasSkinAccess(member, skinId)) results.Add(skinId);
                    }
                }

                // 3.Check Manual Config Skins (Permission)
                if (_manualSkinCache.TryGetValue(shortname, out var manualEntries))
                {
                    foreach (var entry in manualEntries)
                    {
                        // Check if member has permission for this set
                        if (string.IsNullOrEmpty(entry.Permission) || 
                            permission.UserHasPermission(member.UserIDString, entry.Permission))
                        {
                            foreach (var s in entry.Skins) results.Add(s);
                        }
                    }
                }
            }

            return results.ToList();
        }

        private bool HasSkinAccess(BasePlayer player, ulong skinId)
        {
            if (PlayerDLCAPI == null) return false;
            object result = PlayerDLCAPI.Call("IsOwnedOrFreeSkin", player, skinId);
            return result is bool b && b;
        }

        #endregion
    }
}

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