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

FREE TurretLootSiphon : 1.0.0 1.0.0

Нет прав для скачивания
C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Carbon.Extensions;
using Carbon.Plugins;
using Facepunch;
using Network;

namespace Oxide.Plugins
{
    [Info("TurretLootSiphon", "WhiteThunder", "1.0.0")]
    [Description("Enhanced turret loot siphon with improved performance and error handling")]
    public class TurretLootSiphon : RustPlugin
    {
        #region Configuration

        private Configuration config;

        public class Configuration
        {
            [JsonProperty("Plugin Settings")]
            public PluginSettings Plugin { get; set; } = new PluginSettings();

            [JsonProperty("Siphon Settings")]
            public SiphonSettings Siphon { get; set; } = new SiphonSettings();

            [JsonProperty("Performance Settings")]
            public PerformanceSettings Performance { get; set; } = new PerformanceSettings();

            [JsonProperty("Debug Settings")]
            public DebugSettings Debug { get; set; } = new DebugSettings();
        }

        public class PluginSettings
        {
            [JsonProperty("Enabled")]
            public bool Enabled { get; set; } = true;

            [JsonProperty("Admin Permission")]
            public string AdminPermission { get; set; } = "turretlootsiphon.admin";

            [JsonProperty("Use Permission")]
            public string UsePermission { get; set; } = "turretlootsiphon.use";

            [JsonProperty("Require Turret Or Trap Killer")]
            public bool RequireTurretOrTrapKiller { get; set; } = true;
        }

        public class SiphonSettings
        {
            [JsonProperty("Kill Proximity From Turret")]
            public float KillProximityFromTurret { get; set; } = 12f;

            [JsonProperty("Box Search Radius")]
            public float BoxSearchRadius { get; set; } = 4f;

            [JsonProperty("Max Boxes Per Turret")]
            public int MaxBoxesPerTurret { get; set; } = 4;

            [JsonProperty("Allowed Box Short Prefabs")]
            public List<string> AllowedBoxShortPrefabs { get; set; } = new List<string>
            {
                "box.wooden.large",
                "box.wooden",
                "coffinstorage"
            };

            [JsonProperty("Skip Authorized Players")]
            public bool SkipAuthorizedPlayers { get; set; } = true;

            [JsonProperty("Siphon Timeout")]
            public float SiphonTimeout { get; set; } = 30f;
        }

        public class PerformanceSettings
        {
            [JsonProperty("Max Pending Siphons")]
            public int MaxPendingSiphons { get; set; } = 100;

            [JsonProperty("Cleanup Interval")]
            public float CleanupInterval { get; set; } = 60f;

            [JsonProperty("Enable Caching")]
            public bool EnableCaching { get; set; } = true;

            [JsonProperty("Cache Duration")]
            public float CacheDuration { get; set; } = 10f;
        }

        public class DebugSettings
        {
            [JsonProperty("Debug Log")]
            public bool DebugLog { get; set; } = false;

            [JsonProperty("Log Siphon Details")]
            public bool LogSiphonDetails { get; set; } = true;

            [JsonProperty("Log Performance")]
            public bool LogPerformance { get; set; } = false;
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                config = Config.ReadObject<Configuration>();
                if (config == null)
                {
                    throw new JsonException();
                }
            }
            catch
            {
                LogWarning("Configuration file is corrupt, using defaults");
                LoadDefaultConfig();
            }
        }

        protected override void LoadDefaultConfig()
        {
            config = new Configuration();
        }

        protected override void SaveConfig()
        {
            Config.WriteObject(config);
        }

        #endregion

        #region Localization

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["NoPermission"] = "You don't have permission to use Turret Loot Siphon!",
                ["PluginEnabled"] = "Turret Loot Siphon enabled",
                ["PluginDisabled"] = "Turret Loot Siphon disabled",
                ["NoTurretFound"] = "No turret found within {0}m",
                ["NoBoxesFound"] = "Turret {0}: no boxes within {1}m",
                ["TurretBoxes"] = "Turret {0} boxes:\n - {1}",
                ["SiphonStarted"] = "Pending siphon for {0} near turret {1}",
                ["SiphonCompleted"] = "Siphoned {0}/{1} items from corpse {2} into {3} boxes near turret {4}",
                ["TurretDisappeared"] = "Nearest turret disappeared before siphon",
                ["CorpseTooFar"] = "Corpse too far from turret, aborting siphon",
                ["InvalidTarget"] = "Please look at a turret or storage container",
                ["ScanResults"] = "Scan Results:",
                ["ItemsMoved"] = "Items moved: {0}",
                ["TotalItems"] = "Total items: {0}",
                ["BoxesUsed"] = "Boxes used: {0}",
                ["Distance"] = "Distance: {0:F1}m",
                ["Status"] = "Status: {0}",
                ["Help"] = "Turret Loot Siphon Commands:\n/tls scan - Scan nearby turrets and boxes\n/tls toggle - Toggle plugin on/off (admin)\n/tls help - Show this help"
            }, this);
        }

        private string GetMessage(string key, string playerId = null, params object[] args)
        {
            return string.Format(lang.GetMessage(key, this, playerId), args);
        }

        #endregion

        #region Fields

        private readonly Dictionary<ulong, PendingSiphon> pendingSiphons = new Dictionary<ulong, PendingSiphon>();
        private readonly Dictionary<string, CachedTurretData> turretCache = new Dictionary<string, CachedTurretData>();
        private Timer cleanupTimer;

        private class PendingSiphon
        {
            public ulong UserId { get; set; }
            public Vector3 DeathPosition { get; set; }
            public NetworkableId TurretNetId { get; set; }
            public DateTime CreatedTime { get; set; }
            public string PlayerName { get; set; }
        }

        private class CachedTurretData
        {
            public AutoTurret Turret { get; set; }
            public List<StorageContainer> NearbyBoxes { get; set; }
            public DateTime CacheTime { get; set; }
        }

        private class SiphonResult
        {
            public int ItemsMoved { get; set; }
            public int TotalItems { get; set; }
            public int BoxesUsed { get; set; }
            public bool Success { get; set; }
        }

        #endregion

        #region Hooks

        private void Init()
        {
            permission.RegisterPermission(config.Plugin.AdminPermission, this);
            permission.RegisterPermission(config.Plugin.UsePermission, this);

            AddCovalenceCommand("tls", "TurretLootSiphonCommand");
            AddCovalenceCommand("turretlootsiphon", "TurretLootSiphonCommand");

            Puts("TurretLootSiphon Enhanced v2.0.0 initialized successfully");
        }

        private void OnServerInitialized()
        {
            if (config.Performance.CleanupInterval > 0)
            {
                cleanupTimer = timer.Every(config.Performance.CleanupInterval, CleanupExpiredData);
            }
        }

        private void Unload()
        {
            cleanupTimer?.Destroy();
            pendingSiphons.Clear();
            turretCache.Clear();
        }

        private void OnPlayerDisconnected(BasePlayer player, string reason)
        {
            if (player == null) return;
            pendingSiphons.Remove(player.userID);
        }

        private void OnEntityDeath(BaseCombatEntity entity, HitInfo info)
        {
            if (!config.Plugin.Enabled) return;

            var player = entity as BasePlayer;
            if (player == null || player.IsNpc) return;

            // Check if we've hit the pending siphon limit
            if (pendingSiphons.Count >= config.Performance.MaxPendingSiphons)
            {
                if (config.Debug.DebugLog)
                    LogWarning("Max pending siphons reached, skipping new siphon");
                return;
            }

            // Determine if death should trigger siphon
            bool killerIsTrap = info != null && IsValidKiller(info.Initiator);

            if (config.Plugin.RequireTurretOrTrapKiller && !killerIsTrap)
                return;

            // Find the nearest AutoTurret to victim
            var nearestTurret = FindNearestAutoTurret(player.transform.position, config.Siphon.KillProximityFromTurret);
            if (nearestTurret == null) return;

            // If victim is authorized on that turret, skip (don't steal your own/ally's items)
            if (config.Siphon.SkipAuthorizedPlayers && IsAuthorizedOnTurret(nearestTurret, player.userID))
                return;

            if (config.Debug.DebugLog)
                Puts(GetMessage("SiphonStarted", null, player.displayName, nearestTurret.net.ID.Value));

            pendingSiphons[player.userID] = new PendingSiphon
            {
                UserId = player.userID,
                DeathPosition = player.transform.position,
                TurretNetId = nearestTurret.net.ID,
                CreatedTime = DateTime.Now,
                PlayerName = player.displayName
            };
        }

        private void OnEntitySpawned(PlayerCorpse corpse)
        {
            if (!config.Plugin.Enabled || corpse == null) return;

            if (!pendingSiphons.TryGetValue(corpse.playerSteamID, out var pendingSiphon))
                return;

            NextTick(() => ProcessCorpseSiphon(corpse, pendingSiphon));
        }

        #endregion

        #region Commands

        [Command("tls", "turretlootsiphon")]
        private void TurretLootSiphonCommand(IPlayer player, string command, string[] args)
        {
            if (!permission.UserHasPermission(player.Id, config.Plugin.UsePermission))
            {
                player.Reply(GetMessage("NoPermission", player.Id));
                return;
            }

            var basePlayer = player.Object as BasePlayer;
            if (basePlayer == null) return;

            if (args.Length > 0)
            {
                HandleTurretLootSiphonCommand(basePlayer, args);
            }
            else
            {
                ShowHelp(basePlayer);
            }
        }

        #endregion

        #region Core Methods

        private void HandleTurretLootSiphonCommand(BasePlayer player, string[] args)
        {
            var subCommand = args[0].ToLower();

            switch (subCommand)
            {
                case "scan":
                    ScanNearbyTurrets(player);
                    break;
                case "toggle":
                    if (permission.UserHasPermission(player.UserIDString, config.Plugin.AdminPermission))
                        TogglePlugin(player);
                    else
                        player.ChatMessage(GetMessage("NoPermission", player.UserIDString));
                    break;
                case "help":
                    ShowHelp(player);
                    break;
                default:
                    ShowHelp(player);
                    break;
            }
        }

        private void ScanNearbyTurrets(BasePlayer player)
        {
            var turret = FindNearestAutoTurret(player.transform.position, 30f);
            if (turret == null)
            {
                player.ChatMessage(GetMessage("NoTurretFound", player.UserIDString, "30"));
                return;
            }

            var boxes = FindNearbyBoxes(turret.transform.position, config.Siphon.BoxSearchRadius, config.Siphon.MaxBoxesPerTurret);
            if (boxes.Count == 0)
            {
                player.ChatMessage(GetMessage("NoBoxesFound", player.UserIDString, turret.net.ID.Value, config.Siphon.BoxSearchRadius));
                return;
            }

            var boxInfo = boxes.Select(b => $"{b.ShortPrefabName} @ {Vector3.Distance(turret.transform.position, b.transform.position):F1}m");
            player.ChatMessage(GetMessage("TurretBoxes", player.UserIDString, turret.net.ID.Value, string.Join("\n - ", boxInfo)));
        }

        private void TogglePlugin(BasePlayer player)
        {
            config.Plugin.Enabled = !config.Plugin.Enabled;
            SaveConfig();

            var message = config.Plugin.Enabled ? "PluginEnabled" : "PluginDisabled";
            player.ChatMessage(GetMessage(message, player.UserIDString));
        }

        private void ShowHelp(BasePlayer player)
        {
            player.ChatMessage(GetMessage("Help", player.UserIDString));
        }

        private void ProcessCorpseSiphon(PlayerCorpse corpse, PendingSiphon pendingSiphon)
        {
            try
            {
                // Sanity check distance (corpse can ragdoll slightly)
                var turret = BaseNetworkable.serverEntities.Find(pendingSiphon.TurretNetId) as AutoTurret;
                if (turret == null)
                {
                    if (config.Debug.DebugLog)
                        PrintWarning(GetMessage("TurretDisappeared"));
                    pendingSiphons.Remove(corpse.playerSteamID);
                    return;
                }

                if (Vector3.Distance(turret.transform.position, corpse.transform.position) > config.Siphon.KillProximityFromTurret + 3f)
                {
                    if (config.Debug.DebugLog)
                        PrintWarning(GetMessage("CorpseTooFar"));
                    pendingSiphons.Remove(corpse.playerSteamID);
                    return;
                }

                // Get destination boxes
                var destBoxes = GetCachedNearbyBoxes(turret);
                if (destBoxes.Count == 0)
                {
                    if (config.Debug.DebugLog)
                        PrintWarning(GetMessage("NoBoxesFound", null, turret.net.ID.Value, config.Siphon.BoxSearchRadius));
                    pendingSiphons.Remove(corpse.playerSteamID);
                    return;
                }

                // Perform the siphon
                var result = PerformSiphon(corpse, destBoxes);

                if (config.Debug.LogSiphonDetails)
                {
                    Puts(GetMessage("SiphonCompleted", null, result.ItemsMoved, result.TotalItems, 
                    pendingSiphon.PlayerName, result.BoxesUsed, turret.net.ID.Value));
                }

                pendingSiphons.Remove(corpse.playerSteamID);
            }
            catch (Exception ex)
            {
                PrintError($"Error processing corpse siphon: {ex.Message}");
                pendingSiphons.Remove(corpse.playerSteamID);
            }
        }

        private SiphonResult PerformSiphon(PlayerCorpse corpse, List<StorageContainer> destBoxes)
        {
            var result = new SiphonResult();
            var destContainers = destBoxes.Select(b => b.inventory).Where(inv => inv != null).ToList();
            var usedBoxes = new HashSet<BaseEntity>();

            // Process all containers (main, wear, belt)
            foreach (var container in corpse.containers)
            {
                if (container?.itemList == null) continue;

                // Create a copy to avoid modification during iteration
                var items = container.itemList.ToList();
                foreach (var item in items)
                {
                    if (item == null) continue;

                    result.TotalItems += item.amount;

                    if (TryMoveItemToAny(item, destContainers, usedBoxes))
                    {
                        result.ItemsMoved += item.amount;
                    }
                }
            }

            result.BoxesUsed = usedBoxes.Count;
            result.Success = result.ItemsMoved > 0;

            return result;
        }

        private bool TryMoveItemToAny(Item item, List<ItemContainer> destinations, HashSet<BaseEntity> usedBoxes)
        {
            foreach (var dest in destinations)
            {
                if (dest?.parent == null || dest.IsFull()) continue;

                var storageContainer = dest.entityOwner as BaseEntity;
                if (storageContainer == null) continue;

                // Try to move the item
                if (item.MoveToContainer(dest, -1, true))
                {
                    usedBoxes.Add(storageContainer);
                    return true;
                }
            }
            return false;
        }

        private List<StorageContainer> GetCachedNearbyBoxes(AutoTurret turret)
        {
            var cacheKey = $"{turret.net.ID.Value}_{(int)turret.transform.position.x}_{(int)turret.transform.position.z}";

            if (config.Performance.EnableCaching && 
                turretCache.TryGetValue(cacheKey, out var cached) &&
                (DateTime.Now - cached.CacheTime).TotalSeconds < config.Performance.CacheDuration)
            {
                return cached.NearbyBoxes ?? new List<StorageContainer>();
            }

            var boxes = FindNearbyBoxes(turret.transform.position, config.Siphon.BoxSearchRadius, config.Siphon.MaxBoxesPerTurret);

            if (config.Performance.EnableCaching)
            {
                turretCache[cacheKey] = new CachedTurretData
                {
                    Turret = turret,
                    NearbyBoxes = boxes,
                    CacheTime = DateTime.Now
                };
            }

            return boxes;
        }

        private void CleanupExpiredData()
        {
            var now = DateTime.Now;
            var expiredSiphons = pendingSiphons.Where(kvp => 
                (now - kvp.Value.CreatedTime).TotalSeconds > config.Siphon.SiphonTimeout).ToList();

            foreach (var expired in expiredSiphons)
            {
                pendingSiphons.Remove(expired.Key);
            }

            if (config.Performance.EnableCaching)
            {
                var expiredCache = turretCache.Where(kvp => 
                    (now - kvp.Value.CacheTime).TotalSeconds > config.Performance.CacheDuration).ToList();

                foreach (var expired in expiredCache)
                {
                    turretCache.Remove(expired.Key);
                }
            }

            if (config.Debug.LogPerformance && (expiredSiphons.Count > 0 || turretCache.Count > 0))
            {
                Puts($"Cleanup: Removed {expiredSiphons.Count} expired siphons, cache size: {turretCache.Count}");
            }
        }

        #endregion

        #region Utility Methods

        private bool IsValidKiller(BaseEntity killer)
        {
            return killer is AutoTurret || killer is GunTrap || killer is FlameTurret;
        }

        private AutoTurret FindNearestAutoTurret(Vector3 position, float maxDistance)
        {
            var turrets = new List<AutoTurret>();
            Vis.Entities(position, maxDistance, turrets);

            AutoTurret nearest = null;
            var bestDistance = float.MaxValue;

            foreach (var turret in turrets)
            {
                if (turret?.IsDestroyed != false) continue;

                var distance = Vector3.Distance(position, turret.transform.position);
                if (distance < bestDistance)
                {
                    bestDistance = distance;
                    nearest = turret;
                }
            }

            return nearest;
        }

        private bool IsAuthorizedOnTurret(AutoTurret turret, ulong userId)
        {
            if (turret?.authorizedPlayers == null) return false;

            foreach (var authorizedPlayer in turret.authorizedPlayers)
            {
                if (authorizedPlayer?.userid == userId) return true;
            }

            return false;
        }

        private List<StorageContainer> FindNearbyBoxes(Vector3 center, float radius, int maxCount)
        {
            var allContainers = new List<StorageContainer>();
            Vis.Entities(center, radius, allContainers);

            var validBoxes = new List<StorageContainer>();

            foreach (var container in allContainers)
            {
                if (container?.IsDestroyed != false) continue;

                var shortName = container.ShortPrefabName;
                if (config.Siphon.AllowedBoxShortPrefabs?.Count > 0)
                {
                    if (!config.Siphon.AllowedBoxShortPrefabs.Contains(shortName))
                        continue;
                }

                validBoxes.Add(container);
                if (validBoxes.Count >= maxCount) break;
            }

            // Sort by distance and return closest boxes
            return validBoxes.OrderBy(box => Vector3.Distance(center, box.transform.position)).Take(maxCount).ToList();
        }

        #endregion

        #region API

        private bool IsSiphonEnabled()
        {
            return config.Plugin.Enabled;
        }

        private int GetPendingSiphonCount()
        {
            return pendingSiphons.Count;
        }

        private List<StorageContainer> GetNearbyBoxesForTurret(AutoTurret turret)
        {
            if (turret == null) return new List<StorageContainer>();
            return GetCachedNearbyBoxes(turret);
        }

        #endregion
    }
}

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