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

FREE LootDefender : 1.0.5 1.0.5

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

namespace Oxide.Plugins
{
    [Info("Loot Defender", "Author Egor Blagov, Maintainer nivex", "1.0.5")]
    [Description("Defends loot from other players who dealt less damage than you.")]
    class LootDefender : RustPlugin
    {
        [PluginReference]
        Plugin PersonalHeli, BlackVenom;

        private const float KillEntitySpawnRadius = 10.0f;
        private const string permUse = "lootdefender.use";
        private const string permAdm = "lootdefender.adm";
        private static LootDefender Instance;

        #region Config

        class PluginConfig
        {
            public int AttackTimeoutSeconds = 300;
            public float RelativeAdvantageMin = 0.05f;
            public bool UseTeams = true;
            public string HexColorSinglePlayer = "#6d88ff";
            public string HexColorTeam = "#ff804f";
            public string HexColorOk = "#88ff6d";
            public string HexColorNotOk = "#ff5716";

            public int LockBradleySeconds = 900;
            public int LockHeliSeconds = 900;
            public int LockNPCSeconds = 300;
            public bool RemoveFireFromCrates = true;
        }

        private PluginConfig config;

        protected override void LoadDefaultConfig()
        {
            config = new PluginConfig();
            Config.WriteObject(config, true);
        }

        #endregion

        #region Stored data

        class StoredData
        {
            public Dictionary<uint, DamageInfo> damageInfos = new Dictionary<uint, DamageInfo>();
            public Dictionary<uint, LockInfo> lockInfos = new Dictionary<uint, LockInfo>();

            public void Sanitize()
            {
                HashSet<uint> allNetIds = new HashSet<uint>();
                foreach (var ent in BaseNetworkable.serverEntities)
                {
                    if (ent?.net != null)
                    {
                        allNetIds.Add(ent.net.ID);
                    }
                }

                var damageList = new List<uint>(damageInfos.Keys);

                foreach (var id in damageList)
                {
                    if (!allNetIds.Contains(id))
                    {
                        damageInfos.Remove(id);
                    }
                }

                damageList.Clear();

                var lockList = new List<uint>(lockInfos.Keys);

                foreach (var id in lockList)
                {
                    if (!allNetIds.Contains(id))
                    {
                        lockInfos.Remove(id);
                    }
                }

                lockList.Clear();
                allNetIds.Clear();
            }
        }

        private StoredData storedData = new StoredData();

        private Dictionary<uint, LockInfo> lockInfos => storedData.lockInfos;

        private Dictionary<uint, DamageInfo> damageInfos => storedData.damageInfos;

        private void SaveData()
        {
            Interface.Oxide.DataFileSystem.WriteObject(Name, storedData, true);
        }

        private void LoadData()
        {
            try
            {
                storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);
            }
            catch { }

            if (storedData == null)
            {
                storedData = new StoredData();
                SaveData();
            }
        }

        #endregion

        #region L10N

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["NoPermission"] = "You have no permission to use this command",
                ["DamageReport"] = "Damage report for {0}",
                ["CannotLoot"] = "You cannot loot it, major damage was not from you",
                ["CannotMine"] = "You cannot mine it, major damage was not from you",
                ["Heli"] = "Patrol helicopter",
                ["Bradley"] = "Bradley APC"
            }, this, "en");

            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["NoPermission"] = "У Вас нет привилегии на использование этой команды",
                ["DamageReport"] = "Нанесенный урон по {0}",
                ["CannotLoot"] = "Это не Ваш лут, основная часть урона нанесена другими игроками",
                ["CannotMine"] = "Вы не можете добывать это, основная часть урона насена другими игроками",
                ["Heli"] = "Патрульному вертолету",
                ["Bradley"] = "Танку"
            }, this, "ru");
        }

        private static string _(string key, string userId, params object[] args)
        {
            string message = Instance.lang.GetMessage(key, Instance, userId);
            return args.Length > 0 ? string.Format(message, args) : message;
        }

        #endregion

        #region Damage and Locks calculation

        class DamageEntry
        {
            public float DamageDealt;
            public DateTime Timestamp;

            [JsonIgnore]
            public bool IsOutdated => DateTime.Now.Subtract(Timestamp).TotalSeconds > Instance.config.AttackTimeoutSeconds;
            public void AddDamage(float amount)
            {
                DamageDealt += amount;
                Timestamp = DateTime.Now;
            }
        }

        class DamageInfo
        {
            private readonly StringBuilder sb = new StringBuilder();
            public Dictionary<ulong, DamageEntry> damageEntries = new Dictionary<ulong, DamageEntry>();
            public string NameKey;
            [JsonIgnore]
            public float FullDamage => damageEntries.Values.Select(x => x.DamageDealt).Sum();
            public DamageInfo() : this("unknown")
            {

            }
            public DamageInfo(string nameKey)
            {
                NameKey = nameKey;
            }

            public void AddDamage(HitInfo info)
            {
                DamageEntry entry;
                if (!damageEntries.TryGetValue(info.InitiatorPlayer.userID, out entry))
                {
                    damageEntries[info.InitiatorPlayer.userID] = entry = new DamageEntry();
                }

                entry.AddDamage(info.damageTypes.Total());
            }

            public void OnKilled()
            {
                var damageList = new List<ulong>(damageEntries.Keys);

                foreach (var key in damageList)
                {
                    if (damageEntries[key].IsOutdated)
                    {
                        damageEntries.Remove(key);
                    }
                }

                DisplayDamageReport();
                damageList.Clear();
            }

            public void DisplayDamageReport()
            {
                foreach (var damager in damageEntries.Keys)
                {
                    var player = RelationshipManager.FindByID(damager);

                    if (!player.IsValid())
                    {
                        continue;
                    }

                    Instance.SendReply(player, GetDamageReport(player.UserIDString));
                }
            }

            public string GetDamageReport(string playerId)
            {
                var damageGroups = GetDamageGroups();
                var topDamageGroups = GetTopDamageGroups(damageGroups);

                sb.Length = 0;
                sb.AppendLine($"{_("DamageReport", playerId, $"<color={Instance.config.HexColorOk}>{_(NameKey, playerId)}</color>")}:");

                foreach (var dg in damageGroups)
                {
                    if (topDamageGroups.Contains(dg))
                    {
                        sb.Append($"<color={Instance.config.HexColorOk}>√</color> ");
                    }
                    else
                    {
                        sb.Append($"<color={Instance.config.HexColorNotOk}>✖</color> ");
                    }

                    sb.Append($"{dg.ToReport(this)}\n");
                }

                string result = sb.ToString();
                sb.Length = 0;

                return result;
            }

            public bool CanInteract(ulong playerId)
            {
                var topDamageGroups = GetTopDamageGroups(GetDamageGroups());
                var ableToInteract = topDamageGroups.SelectMany(x => x.Players).ToList();

                if (ableToInteract == null || ableToInteract.Count == 0)
                {
                    return true;
                }

                return ableToInteract.Contains(playerId);
            }

            private List<DamageGroup> GetTopDamageGroups(List<DamageGroup> damageGroups)
            {
                var topDamageGroups = new List<DamageGroup>();

                if (damageGroups.Count == 0)
                {
                    return topDamageGroups;
                }

                var topDamageGroup = damageGroups.OrderByDescending(x => x.TotalDamage).First();

                foreach (var dg in damageGroups)
                {
                    if ((topDamageGroup.TotalDamage - dg.TotalDamage) <= Instance.config.RelativeAdvantageMin * FullDamage)
                    {
                        topDamageGroups.Add(dg);
                    }
                }

                return topDamageGroups;
            }

            private List<DamageGroup> GetDamageGroups()
            {
                var result = new List<DamageGroup>();

                foreach (var damage in damageEntries)
                {
                    bool merged = false;

                    foreach (var dT in result)
                    {
                        if (dT.TryMergeDamage(damage.Key, damage.Value.DamageDealt))
                        {
                            merged = true;
                            break;
                        }
                    }

                    if (!merged)
                    {
                        if (RelationshipManager.FindByID(damage.Key) == null)
                        {
                            Instance.PrintError($"Invalid id, unable to find: {damage.Key}");
                            continue;
                        }

                        result.Add(new DamageGroup(damage.Key, damage.Value.DamageDealt));
                    }
                }

                return result;
            }
        }

        class LockInfo
        {
            public DamageInfo damageInfo;
            public DateTime LockTimestamp;
            public int LockTimeout;

            [JsonIgnore]
            public bool IsLockOutdated => DateTime.Now.Subtract(LockTimestamp).TotalSeconds >= LockTimeout;

            public LockInfo(DamageInfo damageInfo, int lockTimeout)
            {
                LockTimestamp = DateTime.Now;
                LockTimeout = lockTimeout;
                this.damageInfo = damageInfo;
            }

            public bool CanInteract(ulong playerId) => damageInfo.CanInteract(playerId);
            public string GetDamageReport(string userIdString) => damageInfo.GetDamageReport(userIdString);
        }

        class DamageGroup
        {
            public float TotalDamage { get; private set; }
            public List<ulong> Players => new List<ulong> { FirstDamagerDealer }.Concat(additionalPlayers).ToList();
            public bool IsSingle => additionalPlayers.Count == 0;
            private ulong FirstDamagerDealer { get; set; }
            private List<ulong> additionalPlayers { get; } = new List<ulong>();

            public DamageGroup(ulong playerId, float damage)
            {
                TotalDamage = damage;
                FirstDamagerDealer = playerId;

                if (!Instance.config.UseTeams)
                {
                    return;
                }

                var target = RelationshipManager.FindByID(playerId);

                if (!target.IsValid())
                {
                    return;
                }

                RelationshipManager.PlayerTeam team;
                if (!RelationshipManager.Instance.playerToTeam.TryGetValue(playerId, out team))
                {
                    return;
                }

                for (int i = 0; i < team.members.Count; i++)
                {
                    ulong member = team.members[i];

                    if (member == playerId)
                    {
                        continue;
                    }

                    additionalPlayers.Add(member);
                }
            }

            public bool TryMergeDamage(ulong playerId, float damageAmount)
            {
                if (IsPlayerInvolved(playerId))
                {
                    TotalDamage += damageAmount;
                    return true;
                }

                return false;
            }

            public bool IsPlayerInvolved(ulong playerId) => playerId == FirstDamagerDealer || additionalPlayers.Contains(playerId);

            public string ToReport(DamageInfo damageInfo)
            {
                if (IsSingle)
                {
                    return getLineForPlayer(FirstDamagerDealer, Instance.config.HexColorSinglePlayer, damageInfo);
                }

                return string.Format("({1}) {0:0}%",
                    TotalDamage / damageInfo.FullDamage * 100,
                    string.Join(" ", Players.Select(x => getLineForPlayer(x, Instance.config.HexColorTeam, damageInfo)))
                );
            }

            private string getLineForPlayer(ulong playerId, string color, DamageInfo damageInfo)
            {
                var displayName = RelationshipManager.FindByID(playerId)?.displayName ?? Instance.covalence.Players.FindPlayerById(playerId.ToString())?.Name ?? playerId.ToString();
                float damage = 0.0f;
                if (damageInfo.damageEntries.ContainsKey(playerId))
                {
                    damage = damageInfo.damageEntries[playerId].DamageDealt;
                }
                string damageLine = string.Format("{0:0}%", damage / damageInfo.FullDamage * 100);
                return $"<color={color}>{displayName}</color> {damageLine}";
            }
        }

        #endregion

        #region uMod hooks

        private void OnServerSave()
        {
            SaveData();
        }

        private void Init()
        {
            Instance = this;
            AddCovalenceCommand("testlootdef", nameof(CommandTest), permAdm);
            permission.RegisterPermission(permUse, this);

            try
            {
                config = Config.ReadObject<PluginConfig>();
            }
            catch { }

            if (config == null)
            {
                config = new PluginConfig();
            }

            Config.WriteObject(config, true);
            LoadData();

            storedData.Sanitize();
        }

        private void Unload()
        {
            SaveData();
            Instance = null;
        }

        private void OnEntityTakeDamage(BaseEntity entity, HitInfo hitInfo)
        {
            if (!entity.IsValid())
            {
                return;
            }

            var attacker = GetPlayerFromHitInfo(hitInfo);

            if (!attacker.IsValid() || attacker.IsNpc || !permission.UserHasPermission(attacker.UserIDString, permUse))
            {
                return;
            }

            string nameKey = null;

            if (entity is BaseHelicopter)
            {
                var heli = entity as BaseHelicopter;

                if (heli.IsValid())
                {
                    if (PersonalHeli != null && PersonalHeli.IsLoaded)
                    {
                        var success = PersonalHeli?.Call("IsPersonal", heli);

                        if (success != null && success is bool && (bool)success)
                        {
                            return;
                        }
                    }

                    if (BlackVenom != null && BlackVenom.IsLoaded && entity.OwnerID.IsSteamId())
                    {
                        var success = BlackVenom?.Call("IsBlackVenom", heli, attacker);

                        if (success != null && success is bool && !(bool)success)
                        {
                            return;
                        }
                    }

                    nameKey = "Heli";
                }
            }

            if (entity is BradleyAPC)
            {
                nameKey = "Bradley";
            }

            if (entity is BasePlayer) // why npcs? :p
            {
                var player = entity as BasePlayer;

                if (player.IsValid() && player.IsNpc)
                {
                    nameKey = player.displayName;
                }
            }

            if (string.IsNullOrEmpty(nameKey))
            {
                return;
            }

            DamageInfo damageInfo;
            if (!damageInfos.TryGetValue(entity.net.ID, out damageInfo))
            {
                damageInfos[entity.net.ID] = damageInfo = new DamageInfo(nameKey);
            }

            damageInfo.AddDamage(hitInfo);
        }

        private object OnPlayerAttack(BasePlayer attacker, HitInfo info)
        {
            if (!permission.UserHasPermission(attacker.UserIDString, permUse))
            {
                return null;
            }

            if (info.HitEntity is ServerGib && info.WeaponPrefab is BaseMelee)
            {
                LockInfo lockInfo;
                if (lockInfos.TryGetValue(info.HitEntity.net.ID, out lockInfo))
                {
                    if (lockInfo.IsLockOutdated)
                    {
                        lockInfos.Remove(info.HitEntity.net.ID);
                        return null;
                    }

                    if (!lockInfo.CanInteract(attacker.userID))
                    {
                        SendReply(attacker, _("CannotMine", attacker.UserIDString));
                        SendReply(attacker, lockInfo.GetDamageReport(attacker.UserIDString));
                        return false;
                    }
                }
            }

            return null;
        }

        private void OnEntityKill(BaseEntity entity)
        {
            if (!entity.IsValid() || (!damageInfos.ContainsKey(entity.net.ID) && !lockInfos.ContainsKey(entity.net.ID)))
            {
                return;
            }

            if (entity is BaseHelicopter || entity is BradleyAPC)
            {
                damageInfos[entity.net.ID].OnKilled();
                var lockInfo = new LockInfo(damageInfos[entity.net.ID], entity is BaseHelicopter ? Instance.config.LockHeliSeconds : Instance.config.LockBradleySeconds);
                LockInRadius<LootContainer>(entity.transform.position, lockInfo, KillEntitySpawnRadius);
                LockInRadius<HelicopterDebris>(entity.transform.position, lockInfo, KillEntitySpawnRadius);
            }

            if (entity is BasePlayer)
            {
                var npc = entity as BasePlayer;
                var corpses = Pool.GetList<NPCPlayerCorpse>();
                Vis.Entities(npc.transform.position, 3.0f, corpses);
                var corpse = corpses.FirstOrDefault(x => x.parentEnt == npc);
                if (corpse.IsValid())
                {
                    damageInfos[entity.net.ID].OnKilled();
                    lockInfos[corpse.net.ID] = new LockInfo(damageInfos[entity.net.ID], Instance.config.LockNPCSeconds);
                }
                Pool.FreeList(ref corpses);
            }

            if (entity is NPCPlayerCorpse)
            {
                var corpse = entity as NPCPlayerCorpse;
                var corpsePos = corpse.transform.position;
                var corpseId = corpse.playerSteamID;
                var lockInfo = lockInfos[corpse.net.ID];
                NextTick(() =>
                {
                    var containers = Pool.GetList<DroppedItemContainer>();
                    Vis.Entities(corpsePos, 1.0f, containers);
                    var container = containers.FirstOrDefault(x => x.playerSteamID == corpseId);
                    if (container.IsValid())
                    {
                        lockInfos[container.net.ID] = lockInfo;
                    }
                    Pool.FreeList(ref containers);
                });
            }

            if (lockInfos.ContainsKey(entity.net.ID))
            {
                lockInfos.Remove(entity.net.ID);
            }
            else damageInfos.Remove(entity.net.ID);
        }

        private object CanLootEntity(BasePlayer player, BaseEntity entity)
        {
            if (!permission.UserHasPermission(player.UserIDString, permUse) || !entity.IsValid())
            {
                return null;
            }

            LockInfo lockInfo;
            if (!lockInfos.TryGetValue(entity.net.ID, out lockInfo))
            {
                return null;
            }

            if (lockInfo.IsLockOutdated)
            {
                lockInfos.Remove(entity.net.ID);
                return null;
            }

            if (!lockInfo.CanInteract(player.userID))
            {
                SendReply(player, _("CannotLoot", player.UserIDString));
                SendReply(player, lockInfo.GetDamageReport(player.UserIDString));
                return false;
            }

            return null;
        }

        #endregion

        private void LockInRadius<T>(Vector3 position, LockInfo lockInfo, float radius) where T : BaseEntity
        {
            var entities = Facepunch.Pool.GetList<T>();
            Vis.Entities(position, radius, entities);
            foreach (var ent in entities)
            {
                lockInfos[ent.net.ID] = lockInfo;

                if (config.RemoveFireFromCrates)
                {
                    var e = (ent as LockedByEntCrate)?.lockingEnt?.ToBaseEntity();

                    if (e.IsValid() && !e.IsDestroyed)
                    {
                        e.Kill();
                    }
                }
            }
            Pool.FreeList(ref entities);
        }

        private BasePlayer GetPlayerFromHitInfo(HitInfo hitInfo)
        {
            var player = hitInfo?.Initiator as BasePlayer;

            if (!player.IsValid() && hitInfo?.Initiator is BaseMountable)
            {
                player = GetMountedPlayer(hitInfo.Initiator as BaseMountable);
            }

            return player;
        }

        private static BasePlayer GetMountedPlayer(BaseMountable m)
        {
            if (m.GetMounted())
            {
                return m.GetMounted();
            }

            if (m is BaseVehicle)
            {
                var vehicle = m as BaseVehicle;

                foreach (var point in vehicle.mountPoints)
                {
                    if (point.mountable.IsValid() && point.mountable.GetMounted())
                    {
                        return point.mountable.GetMounted();
                    }
                }
            }

            return null;
        }

        private void CommandTest(IPlayer p, string command, string[] args)
        {
            p.Reply($"total damage infos: {damageInfos.Count}");
            p.Reply($"total lock infos: {lockInfos.Count}");
        }
    }
}

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