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

FREE SupplyLock : 2.0.0 2.0.0

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

namespace Oxide.Plugins
{
    [Info("SupplyLock", "Ghosty", "2.0.0")]
    [Description("Locks supply drops to a player, their team, or clan members.")]
    public class SupplyLock : RustPlugin
    {
        #region References

        [PluginReference] private Plugin Clans, ClansReborn, InstantAirdrop;

        #endregion

        #region Config

        private Configuration _config;

        private class Configuration
        {
            public GeneralSettings General { get; set; } = new GeneralSettings();
            public AccessSettings Access { get; set; } = new AccessSettings();
            public InstantAirdropSettings InstantAirdropIntegration { get; set; } = new InstantAirdropSettings();
            public MessageSettings Messages { get; set; } = new MessageSettings();
            public bool DebugEnabled { get; set; } = false;
        }

        private class GeneralSettings
        {
            public float SignalMatchRadius { get; set; } = 50f;
            public float SignalExpirySeconds { get; set; } = 300f;
            public bool LockRandomAirdrops { get; set; } = false;
            public float UnlockAfterSeconds { get; set; } = 0f;
        }

        private class AccessSettings
        {
            public bool EnableTeamAccess { get; set; } = true;
            public bool EnableClanAccess { get; set; } = true;
        }

        private class InstantAirdropSettings
        {
            public bool Enabled { get; set; } = true;
            public bool LockInstantDrops { get; set; } = true;
            public float InstantDropUnlockSeconds { get; set; } = 0f;
            public float InstantDropMatchRadius { get; set; } = 25f;
        }

        private class MessageSettings
        {
            public bool ShowLockMessage { get; set; } = true;
            public string LockMessageFormat { get; set; } = "<size=16><color=#e74c3c>SupplyLock</color></size>\n<size=13>This supply drop belongs to <color=#3498db>{0}</color>.</size>";
            public string UnlockedMessageFormat { get; set; } = "<size=16><color=#2ecc71>SupplyLock</color></size>\n<size=13>This supply drop is now unlocked!</size>";
            public bool NotifyOwnerOnSpawn { get; set; } = true;
            public string OwnerNotifyFormat { get; set; } = "<size=16><color=#2ecc71>SupplyLock</color></size>\n<size=13>Your supply drop has landed and is locked to you!</size>";
            public string OwnerNotifyInstantFormat { get; set; } = "<size=16><color=#2ecc71>SupplyLock</color></size>\n<size=13>Your instant supply drop has spawned and is locked to you!</size>";
        }

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

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _config = Config.ReadObject<Configuration>();
                if (_config == null)
                    LoadDefaultConfig();
            }
            catch
            {
                PrintWarning("Configuration file is corrupt, loading defaults...");
                LoadDefaultConfig();
            }
        }

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

        #endregion

        #region Data Structures

        private class SupplySignalData
        {
            public ulong PlayerId;
            public string PlayerName;
            public Vector3 Position;
            public float ThrownTime;
            public NetworkableId SignalNetId;
            public bool IsLanded;

            public SupplySignalData(ulong playerId, string playerName, Vector3 position, NetworkableId signalNetId)
            {
                PlayerId = playerId;
                PlayerName = playerName;
                Position = position;
                ThrownTime = Time.realtimeSinceStartup;
                SignalNetId = signalNetId;
                IsLanded = false;
            }

            public bool IsExpired(float expirySeconds) => Time.realtimeSinceStartup - ThrownTime > expirySeconds;
            public float Age => Time.realtimeSinceStartup - ThrownTime;
        }

        private class SupplyDropData
        {
            public ulong OwnerId;
            public string OwnerName;
            public bool IsInstantDrop;

            public SupplyDropData(ulong ownerId, string ownerName, bool isInstant = false)
            {
                OwnerId = ownerId;
                OwnerName = ownerName;
                IsInstantDrop = isInstant;
            }
        }

        private readonly Dictionary<NetworkableId, SupplySignalData> _activeSignals = new Dictionary<NetworkableId, SupplySignalData>();
        private readonly Dictionary<NetworkableId, SupplyDropData> _lockedDrops = new Dictionary<NetworkableId, SupplyDropData>();
        private readonly Dictionary<NetworkableId, SupplySignal> _trackedSignalEntities = new Dictionary<NetworkableId, SupplySignal>();
        private readonly Dictionary<NetworkableId, SupplyDrop> _pendingLandingNotifications = new Dictionary<NetworkableId, SupplyDrop>();
        private readonly Dictionary<ulong, float> _lastNotificationTime = new Dictionary<ulong, float>();
        private readonly Dictionary<ulong, int> _pendingNotificationCount = new Dictionary<ulong, int>();
        private readonly HashSet<ulong> _pendingFlushTimers = new HashSet<ulong>();
        private Timer _positionUpdateTimer;
        private const float NOTIFICATION_COOLDOWN = 5f;

        #endregion

        #region Oxide Hooks

        private void OnServerInitialized()
        {
            _positionUpdateTimer = timer.Every(0.5f, UpdateTrackedSignalPositions);

            if (_config.InstantAirdropIntegration.Enabled && InstantAirdrop != null && InstantAirdrop.IsLoaded)
                Puts("InstantAirdrop plugin detected - integration enabled");

            if (Clans != null && Clans.IsLoaded)
                Puts("Clans plugin detected - clan access enabled");

            if (ClansReborn != null && ClansReborn.IsLoaded)
                Puts("ClansReborn plugin detected - clan access enabled");
        }

        private void Unload()
        {
            _positionUpdateTimer?.Destroy();
            _activeSignals.Clear();
            _lockedDrops.Clear();
            _trackedSignalEntities.Clear();
            _pendingLandingNotifications.Clear();
            _lastNotificationTime.Clear();
            _pendingNotificationCount.Clear();
            _pendingFlushTimers.Clear();
        }

        private void OnExplosiveThrown(BasePlayer player, BaseEntity entity, ThrownWeapon item)
        {
            if (player == null || !(entity is SupplySignal signal) || signal.net == null)
                return;
            var signalData = new SupplySignalData(player.userID, player.displayName, signal.transform.position, signal.net.ID);
            _activeSignals[signal.net.ID] = signalData;
            _trackedSignalEntities[signal.net.ID] = signal;

            DebugLog($"Signal thrown by {player.displayName} at {signal.transform.position}");

            NextTick(() =>
            {
                if (signal != null && !signal.IsDestroyed && _activeSignals.TryGetValue(signal.net.ID, out var data))
                    data.Position = signal.transform.position;
            });
        }

        private void OnExplosiveDropped(BasePlayer player, BaseEntity entity, ThrownWeapon item)
            => OnExplosiveThrown(player, entity, item);

        private void UpdateTrackedSignalPositions()
        {
            if (_trackedSignalEntities.Count == 0)
                return;
            var toRemove = new List<NetworkableId>();
            foreach (var kvp in _trackedSignalEntities)
            {
                if (kvp.Value == null || kvp.Value.IsDestroyed || !_activeSignals.TryGetValue(kvp.Key, out var signalData))
                {
                    toRemove.Add(kvp.Key);
                    continue;
                }
                Vector3 newPos = kvp.Value.transform.position;
                if (Vector3.Distance(signalData.Position, newPos) > 0.1f)signalData.Position = newPos;
            }
            foreach (var id in toRemove)
                _trackedSignalEntities.Remove(id);
        }

        private void OnSupplyDropLanded(SupplyDrop drop)
        {
            if (drop?.net == null || !_pendingLandingNotifications.Remove(drop.net.ID))
                return;
            DebugLog($"Drop landed [NetID: {drop.net.ID}]");
            if (_lockedDrops.TryGetValue(drop.net.ID, out var dropData))
                QueueLandingNotification(dropData.OwnerId, dropData.IsInstantDrop);
        }

        private void QueueLandingNotification(ulong playerId, bool isInstantDrop)
        {
            float currentTime = Time.realtimeSinceStartup;
            if (_lastNotificationTime.TryGetValue(playerId, out float lastTime))
            {
                float timeSinceLast = currentTime - lastTime;
                if (timeSinceLast < NOTIFICATION_COOLDOWN)
                {
                    if (!_pendingNotificationCount.ContainsKey(playerId))
                        _pendingNotificationCount[playerId] = 0;
                    _pendingNotificationCount[playerId]++;

                    if (!_pendingFlushTimers.Contains(playerId))
                    {
                        _pendingFlushTimers.Add(playerId);
                        timer.Once(NOTIFICATION_COOLDOWN - timeSinceLast + 0.1f, () => FlushPendingNotifications(playerId));
                    }
                    return;
                }
            }
            _pendingFlushTimers.Remove(playerId);
            SendLandingNotification(playerId, isInstantDrop);
        }

        private void SendLandingNotification(ulong playerId, bool isInstantDrop, bool isFlush = false)
        {
            var player = BasePlayer.FindByID(playerId);
            if (player == null || !player.IsConnected)
            {
                _pendingNotificationCount.Remove(playerId);
                _pendingFlushTimers.Remove(playerId);
                return;
            }

            _pendingNotificationCount.TryGetValue(playerId, out int pendingCount);
            _pendingNotificationCount.Remove(playerId);

            int totalDrops = isFlush ? pendingCount : pendingCount + 1;

            string message = totalDrops > 1
                ? $"<size=16><color=#2ecc71>SupplyLock</color></size>\n<size=13>{totalDrops} supply drops have landed and are locked to you!</size>"
                : isInstantDrop ? _config.Messages.OwnerNotifyInstantFormat : _config.Messages.OwnerNotifyFormat;

            player.ChatMessage(message);
            _lastNotificationTime[playerId] = Time.realtimeSinceStartup;
        }

        private void FlushPendingNotifications(ulong playerId)
        {
            _pendingFlushTimers.Remove(playerId);

            if (_pendingNotificationCount.TryGetValue(playerId, out int count) && count > 0)
                SendLandingNotification(playerId, false, true);
        }

        private void OnEntityKill(SupplySignal signal)
        {
            if (signal?.net == null)
                return;

            _trackedSignalEntities.Remove(signal.net.ID);

            if (_activeSignals.TryGetValue(signal.net.ID, out var signalData))
            {
                signalData.Position = signal.transform.position;
                signalData.IsLanded = true;
            }
        }

        private void OnEntitySpawned(SupplyDrop drop)
        {
            if (drop?.net?.ID == null)
                return;

            Vector3 spawnPosition = drop.transform.position;
            DebugLog($"Drop spawned [NetID: {drop.net.ID}] at {spawnPosition}");
            NextTick(() => ProcessSupplyDrop(drop, spawnPosition));
        }

        private void ProcessSupplyDrop(SupplyDrop drop, Vector3 spawnPosition)
        {
            if (drop == null || drop.IsDestroyed)
                return;

            CleanupExpiredSignals();

            bool isInstantDrop = IsInstantAirdropDrop(drop);

            if (isInstantDrop && _config.InstantAirdropIntegration.Enabled && !_config.InstantAirdropIntegration.LockInstantDrops)
                return;

            float matchRadius = isInstantDrop && _config.InstantAirdropIntegration.Enabled
                ? _config.InstantAirdropIntegration.InstantDropMatchRadius
                : _config.General.SignalMatchRadius;

            var matchedSignal = FindMatchingSignal(spawnPosition, matchRadius);

            if (matchedSignal == null && _activeSignals.Count > 0)
                matchedSignal = TryMatchFromNearbySignalEntities(spawnPosition, matchRadius);

            if (matchedSignal != null)
            {
                var owner = BasePlayer.FindByID(matchedSignal.PlayerId);
                string ownerName = owner?.displayName ?? matchedSignal.PlayerName ?? "Unknown";

                _lockedDrops[drop.net.ID] = new SupplyDropData(matchedSignal.PlayerId, ownerName, isInstantDrop);
                _activeSignals.Remove(matchedSignal.SignalNetId);
                _trackedSignalEntities.Remove(matchedSignal.SignalNetId);

                DebugLog($"Drop matched to {ownerName}");

                if (_config.Messages.NotifyOwnerOnSpawn)
                {
                    if (isInstantDrop)
                        QueueLandingNotification(matchedSignal.PlayerId, true);
                    else
                        _pendingLandingNotifications[drop.net.ID] = drop;
                }

                float unlockTime = isInstantDrop && _config.InstantAirdropIntegration.Enabled
                    ? _config.InstantAirdropIntegration.InstantDropUnlockSeconds
                    : _config.General.UnlockAfterSeconds;

                if (unlockTime > 0)
                    timer.Once(unlockTime, () => UnlockDrop(drop));
            }
            else if (_config.General.LockRandomAirdrops)
            {
                DebugLog("No matching signal found, random airdrop locking not implemented");
            }
        }

        private void OnEntityKill(SupplyDrop drop)
        {
            if (drop?.net?.ID == null)
                return;

            _pendingLandingNotifications.Remove(drop.net.ID);
            _lockedDrops.Remove(drop.net.ID);
        }

        private object CanLootEntity(BasePlayer player, SupplyDrop drop)
        {
            if (player == null || drop?.net?.ID == null)
                return null;

            if (!_lockedDrops.TryGetValue(drop.net.ID, out var dropData))
                return null;

            if (HasAccess(player, dropData.OwnerId))
                return null;

            if (_config.Messages.ShowLockMessage)
                player.ChatMessage(string.Format(_config.Messages.LockMessageFormat, dropData.OwnerName));

            return false;
        }

        #endregion

        #region Main

        private void DebugLog(string message)
        {
            if (_config.DebugEnabled)
                Puts($"[DEBUG] {message}");
        }

        private bool IsInstantAirdropDrop(SupplyDrop drop)
        {
            if (!_config.InstantAirdropIntegration.Enabled || InstantAirdrop == null || !InstantAirdrop.IsLoaded)
                return false;

            try { return InstantAirdrop.Call<bool>("IsInstantAirdrop", drop); }
            catch { return false; }
        }

        private SupplySignalData FindMatchingSignal(Vector3 dropPosition, float matchRadius)
        {
            var candidates = new List<(SupplySignalData Signal, float Distance)>();

            foreach (var kvp in _activeSignals)
            {
                var signal = kvp.Value;
                if (signal.IsExpired(_config.General.SignalExpirySeconds))
                    continue;

                float horizontalDist = Vector3.Distance(
                    new Vector3(signal.Position.x, 0, signal.Position.z),
                    new Vector3(dropPosition.x, 0, dropPosition.z)
                );

                if (horizontalDist <= matchRadius)
                    candidates.Add((signal, horizontalDist));
            }
            if (candidates.Count == 0)
                return null;
            if (candidates.Count == 1)
                return candidates[0].Signal;
            var landedCandidates = candidates.Where(c => c.Signal.IsLanded).ToList();
            if (landedCandidates.Count == 1)
                return landedCandidates[0].Signal;
            var workingCandidates = landedCandidates.Count > 0 ? landedCandidates : candidates;
            var sorted = workingCandidates.OrderBy(c => c.Distance).ToList();
            if (sorted.Count >= 2 && sorted[1].Distance - sorted[0].Distance > 10f)
                return sorted[0].Signal;
            return workingCandidates.OrderByDescending(c => c.Signal.Age).ThenBy(c => c.Signal.SignalNetId.Value).First().Signal;
        }

        private SupplySignalData TryMatchFromNearbySignalEntities(Vector3 dropPosition, float searchRadius)
        {
            var nearbySignals = new List<SupplySignal>();
            Vis.Entities(dropPosition, searchRadius * 1.5f, nearbySignals);
            foreach (var signalEntity in nearbySignals)
            {
                if (signalEntity?.net == null)
                    continue;
                if (_activeSignals.TryGetValue(signalEntity.net.ID, out var signalData))
                {
                    Vector3 actualPos = signalEntity.transform.position;
                    float distToActual = Vector3.Distance(
                        new Vector3(actualPos.x, 0, actualPos.z),
                        new Vector3(dropPosition.x, 0, dropPosition.z)
                    );

                    if (distToActual <= searchRadius)
                    {
                        signalData.Position = actualPos;
                        signalData.IsLanded = true;
                        return signalData;
                    }
                }
            }
            return null;
        }

        private void CleanupExpiredSignals()
        {
            var expired = _activeSignals.Where(kvp => kvp.Value.IsExpired(_config.General.SignalExpirySeconds)).Select(kvp => kvp.Key).ToList();
            foreach (var signalId in expired)
                _activeSignals.Remove(signalId);
        }

        private bool HasAccess(BasePlayer player, ulong ownerId)
        {
            if (player.userID == ownerId)
                return true;
            if (_config.Access.EnableTeamAccess && IsTeammate(player.userID, ownerId))
                return true;
            if (_config.Access.EnableClanAccess && IsClanMember(player.userID, ownerId))
                return true;
            return false;
        }

        private bool IsTeammate(ulong playerId, ulong ownerId)
        {
            var ownerTeam = RelationshipManager.ServerInstance?.FindPlayersTeam(ownerId);
            return ownerTeam != null && ownerTeam.members.Contains(playerId);
        }

        private bool IsClanMember(ulong playerId, ulong ownerId)
        {
            if (Clans != null && Clans.IsLoaded)
            {
                try
                {
                    var playerClan = Clans.Call<string>("GetClanOf", playerId);
                    var ownerClan = Clans.Call<string>("GetClanOf", ownerId);
                    if (!string.IsNullOrEmpty(playerClan) && playerClan == ownerClan)
                        return true;
                }
                catch { }
            }
            if (ClansReborn != null && ClansReborn.IsLoaded)
            {
                try
                {
                    var playerClan = ClansReborn.Call<string>("GetClanOf", playerId);
                    var ownerClan = ClansReborn.Call<string>("GetClanOf", ownerId);
                    if (!string.IsNullOrEmpty(playerClan) && playerClan == ownerClan)
                        return true;
                }
                catch { }
            }
            return false;
        }

        private void UnlockDrop(SupplyDrop drop)
        {
            if (drop == null || drop.IsDestroyed)
                return;
            if (!_lockedDrops.TryGetValue(drop.net.ID, out var dropData))
                return;
            _lockedDrops.Remove(drop.net.ID);
            var owner = BasePlayer.FindByID(dropData.OwnerId);
            if (owner != null && owner.IsConnected)
                owner.ChatMessage(_config.Messages.UnlockedMessageFormat);
        }

        #endregion

        #region Commands

        [ConsoleCommand("supplylock.debug")]
        private void CmdDebugToggle(ConsoleSystem.Arg arg)
        {
            if (!arg.IsAdmin)
                return;
            _config.DebugEnabled = !_config.DebugEnabled;
            SaveConfig();
            arg.ReplyWith($"SupplyLock debug mode: {(_config.DebugEnabled ? "ENABLED" : "DISABLED")}");
        }

        [ConsoleCommand("supplylock.status")]
        private void CmdStatus(ConsoleSystem.Arg arg)
        {
            if (!arg.IsAdmin)
                return;
            arg.ReplyWith($"SupplyLock Status:\n" +
                $"  Active signals: {_activeSignals.Count}\n" +
                $"  Locked drops: {_lockedDrops.Count}\n" +
                $"  Debug mode: {(_config.DebugEnabled ? "ON" : "OFF")}\n" +
                $"  InstantAirdrop: {(InstantAirdrop != null && InstantAirdrop.IsLoaded ? "Detected" : "Not found")}\n" +
                $"  Clans: {(Clans != null && Clans.IsLoaded ? "Detected" : "Not found")}\n" +
                $"  ClansReborn: {(ClansReborn != null && ClansReborn.IsLoaded ? "Detected" : "Not found")}");
        }
        #endregion

        #region API

        private bool IsDropLocked(SupplyDrop drop)
            => drop?.net?.ID != null && _lockedDrops.ContainsKey(drop.net.ID);

        private ulong GetDropOwner(SupplyDrop drop)
        {
            if (drop?.net?.ID != null && _lockedDrops.TryGetValue(drop.net.ID, out var data))
                return data.OwnerId;
            return 0;
        }

        private bool CanPlayerLoot(BasePlayer player, SupplyDrop drop)
        {
            if (player == null || drop?.net?.ID == null || !_lockedDrops.TryGetValue(drop.net.ID, out var data))
                return true;
            return HasAccess(player, data.OwnerId);
        }

        private void ForceUnlock(SupplyDrop drop)
        {
            if (drop?.net?.ID != null)
                _lockedDrops.Remove(drop.net.ID);
        }

        private void ForceLock(SupplyDrop drop, ulong ownerId, string ownerName = null)
        {
            if (drop?.net?.ID == null)
                return;

            ownerName ??= BasePlayer.FindByID(ownerId)?.displayName ?? "Unknown";
            _lockedDrops[drop.net.ID] = new SupplyDropData(ownerId, ownerName);
        }
        #endregion
    }
}

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