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

FREE AntiTeamGuard : 1.0.1 1.0.1

Нет прав для скачивания
C#:
// AntiTeamGuard v1.0.1

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 UnityEngine;

//  ____  _       _ _       
// / ___|(_) __ _(_) | ___  
// \___ \| |/ _` | | |/ _ \ 
//  ___) | | (_| | | | (_) |
// |____/|_|\__, |_|_|\___/ 
//          |___/             
// ☽✧✵✧✵✧✵✧✵✧✵✧✵✧☾
// ✦    RUST PLUGINS   ✦
// ✦     Sigilo.dev    ✦
// ☽✧✵✧✵✧✵✧✵✧✵✧✵✧☾

namespace Oxide.Plugins
{
    [Info("AntiTeamGuard", "Sigilo", "1.0.1")]
    [Description("Detects teaming violations and applies progressive damage penalties")]
    public class AntiTeamGuard : RustPlugin
    {
        [PluginReference]
        private Plugin Clans;

        private static AntiTeamGuard _instance;
        private Configuration _config;
        private ProximityTracker _proximityTracker;
        private GameObject _proximityObject;

        private Dictionary<ulong, Dictionary<ulong, PlayerPairData>> _pairData = new Dictionary<ulong, Dictionary<ulong, PlayerPairData>>();
        private Dictionary<ulong, PlayerPenalty> _penalties = new Dictionary<ulong, PlayerPenalty>();
        private Dictionary<ulong, Dictionary<ulong, int>> _voiceChatTicks = new Dictionary<ulong, Dictionary<ulong, int>>();
        private Dictionary<ulong, ulong> _corpseLooting = new Dictionary<ulong, ulong>();
        private Dictionary<ulong, float> _cooldowns = new Dictionary<ulong, float>();
        private Dictionary<ulong, float> _scanCooldowns = new Dictionary<ulong, float>();

        private const string PERMISSION_ADMIN = "antiteamguard.admin";
        private const string PERMISSION_BYPASS = "antiteamguard.bypass";
        private const string PERMISSION_TEAMSTATUS = "antiteamguard.teamstatus";
        private const string DATA_PAIR = "AntiTeamGuard_PairData";
        private const string DATA_PENALTIES = "AntiTeamGuard_Penalties";

        #region Initialization

        void Init()
        {
            _instance = this;
            permission.RegisterPermission(PERMISSION_ADMIN, this);
            permission.RegisterPermission(PERMISSION_BYPASS, this);
            permission.RegisterPermission(PERMISSION_TEAMSTATUS, this);
        }

        void OnServerInitialized(bool initial)
        {
            LoadData();

            if (_config.Proximity.Enabled)
            {
                _proximityObject = new GameObject("AntiTeamGuard_ProximityTracker");
                _proximityTracker = _proximityObject.AddComponent<ProximityTracker>();
                _proximityTracker.Initialize(this, _config.Proximity);
            }

            AddCovalenceCommand("atg.status", nameof(CommandStatus));
            AddCovalenceCommand("teamstatus", nameof(CommandPlayerStatus));
            AddCovalenceCommand("atg.check", nameof(CommandCheck));
            AddCovalenceCommand("atg.clear", nameof(CommandClear));
            AddCovalenceCommand("atg.penalty", nameof(CommandPenalty));

            CleanupExpiredPenalties();
            timer.Every(300f, CleanupExpiredPenalties);
            timer.Every(600f, SaveData);
        }

        void Unload()
        {
            SaveData();

            if (_proximityTracker != null)
                _proximityTracker.Destroy();
            if (_proximityObject != null)
                UnityEngine.Object.Destroy(_proximityObject);

            _instance = null;
        }

        void OnNewSave(string filename)
        {
            _pairData.Clear();
            SaveData();
            Puts("Wipe detected - cleared pair data (penalties preserved)");
        }

        #endregion

        #region Configuration

        protected override void LoadDefaultConfig() => _config = Configuration.Default();

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _config = Config.ReadObject<Configuration>();
                if (_config == null) throw new Exception();
                SaveConfig();
            }
            catch
            {
                PrintError("Invalid configuration - loading defaults");
                LoadDefaultConfig();
            }
        }

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

        private class Configuration
        {
            [JsonProperty("Team Size Limit")]
            public int TeamLimit { get; set; } = 8;

            [JsonProperty("Ignore Admins")]
            public bool IgnoreAdmins { get; set; } = true;

            [JsonProperty("Server Name (for Discord)")]
            public string ServerName { get; set; } = "";

            [JsonProperty("Discord Webhook URL")]
            public string DiscordWebhook { get; set; } = "";

            [JsonProperty("Public Sanction Webhook URL")]
            public string PublicDiscordWebhook { get; set; } = "";

            [JsonProperty("Detection Settings")]
            public DetectionSettings Detection { get; set; } = new DetectionSettings();

            [JsonProperty("Proximity Tracking")]
            public ProximitySettings Proximity { get; set; } = new ProximitySettings();

            [JsonProperty("Penalty Settings")]
            public PenaltySettings Penalties { get; set; } = new PenaltySettings();

            [JsonProperty("Alert Weights")]
            public Dictionary<string, float> Weights { get; set; } = new Dictionary<string, float>
            {
                { "CodeLock", 0.4f },
                { "Cupboard", 0.3f },
                { "Turret", 0.3f },
                { "SharedVehicle", 0.3f },
                { "SleepingBag", 0.3f },
                { "TeamKill", 0.5f },
                { "Revive", 0.35f },
                { "Looting", 0.25f },
                { "VoiceChat", 0.2f },
                { "ProximityTime", 0.3f },
                { "ProximityKills", 0.35f }
            };

            [JsonProperty("Penalty Threshold (total weight to trigger penalty)")]
            public float PenaltyThreshold { get; set; } = 1.0f;

            [JsonProperty("Offense Decay (Days per level)")]
            public int OffenseDecayDays { get; set; } = 7;

            public static Configuration Default()
            {
                var config = new Configuration();
                config.Penalties.Tiers = new List<OffenseTier>
                {
                    new OffenseTier { Offense = 1, DamageReduction = 10, DurationHours = 2, BanDurationMinutes = 10 },
                    new OffenseTier { Offense = 2, DamageReduction = 20, DurationHours = 4, BanDurationMinutes = 30 },
                    new OffenseTier { Offense = 3, DamageReduction = 30, DurationHours = 8, BanDurationMinutes = 60 },
                    new OffenseTier { Offense = 4, DamageReduction = 40, DurationHours = 12, BanDurationMinutes = 120 },
                    new OffenseTier { Offense = 5, DamageReduction = 50, DurationHours = 16, BanDurationMinutes = 180 },
                    new OffenseTier { Offense = 6, DamageReduction = 60, DurationHours = 24, BanDurationMinutes = 300 },
                    new OffenseTier { Offense = 7, DamageReduction = 70, DurationHours = 32, BanDurationMinutes = 480 },
                    new OffenseTier { Offense = 8, DamageReduction = 80, DurationHours = 40, BanDurationMinutes = 720 },
                    new OffenseTier { Offense = 9, DamageReduction = 90, DurationHours = 48, BanDurationMinutes = 1080 },
                    new OffenseTier { Offense = 10, DamageReduction = 95, DurationHours = 48, BanDurationMinutes = 1440 }
                };

                config.Detection.VoiceChat = false;
                config.Proximity.TimeThreshold = 600f;

                config.Weights["SharedVehicle"] = 0.3f;

                return config;
            }
        }

        private class DetectionSettings
        {
            [JsonProperty("Code Lock Detection")]
            public bool CodeLock { get; set; } = true;

            [JsonProperty("Cupboard Detection")]
            public bool Cupboard { get; set; } = true;

            [JsonProperty("Turret Detection")]
            public bool Turret { get; set; } = true;

            [JsonProperty("Shared Vehicle Detection")]
            public bool SharedVehicle { get; set; } = true;

            [JsonProperty("Sleeping Bag Detection")]
            public bool SleepingBag { get; set; } = true;

            [JsonProperty("Team Kill Detection")]
            public bool TeamKill { get; set; } = true;

            [JsonProperty("Revive Detection")]
            public bool Revive { get; set; } = true;

            [JsonProperty("Shared Looting Detection")]
            public bool Looting { get; set; } = true;

            [JsonProperty("Voice Chat Detection")]
            public bool VoiceChat { get; set; } = true;

            [JsonProperty("Team Kill - Max Distance Between Players")]
            public float TeamKillMaxDistance { get; set; } = 8f;

            [JsonProperty("Team Kill - Min Distance To Target")]
            public float TeamKillMinTargetDistance { get; set; } = 15f;

            [JsonProperty("Voice Chat - Seconds To Trigger Alert")]
            public int VoiceChatSeconds { get; set; } = 60;

            [JsonProperty("Looting - Window Seconds")]
            public float LootingWindow { get; set; } = 5f;
        }

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

            [JsonProperty("Tracking Distance")]
            public float Distance { get; set; } = 25f;

            [JsonProperty("Check Visibility")]
            public bool CheckVisibility { get; set; } = true;

            [JsonProperty("Time Threshold (seconds)")]
            public float TimeThreshold { get; set; } = 900f;

            [JsonProperty("Kill Threshold")]
            public int KillThreshold { get; set; } = 3;
        }

        private class PenaltySettings
        {
            [JsonProperty("Enable Damage Penalties")]
            public bool Enabled { get; set; } = true;

            [JsonProperty("Enable Ban Penalties")]
            public bool BanEnabled { get; set; } = false;

            [JsonProperty("Offense Tiers")]
            public List<OffenseTier> Tiers { get; set; } = new List<OffenseTier>();

            public OffenseTier GetTier(int offenseCount)
            {
                if (Tiers == null || Tiers.Count == 0) return null;
                var sorted = Tiers.OrderBy(t => t.Offense).ToList();
                return sorted.LastOrDefault(t => t.Offense <= offenseCount) ?? sorted.Last();
            }
        }

        private class OffenseTier
        {
            [JsonProperty("Offense Number")]
            public int Offense { get; set; }

            [JsonProperty("Damage Reduction Percent")]
            public int DamageReduction { get; set; }

            [JsonProperty("Duration (Hours)")]
            public int DurationHours { get; set; }

            [JsonProperty("Ban Duration (Minutes)")]
            public int BanDurationMinutes { get; set; }
        }

        #endregion

        #region Data Classes

        private class PlayerPairData
        {
            public List<string> Alerts { get; set; } = new List<string>();
            public float AlertScore { get; set; } = 0f;
            public DateTime LastDecay { get; set; } = DateTime.UtcNow;
            public Dictionary<string, DateTime> AlertCooldowns { get; set; } = new Dictionary<string, DateTime>();
            public float ProximityTime { get; set; } = 0f;
            public int ProximityKills { get; set; } = 0;
            public int SelfKills { get; set; } = 0;
            public bool Warned { get; set; } = false;
            public DateTime LastActivity { get; set; } = DateTime.UtcNow;
        }

        private class PlayerPenalty
        {
            public int OffenseCount { get; set; } = 0;
            public float CurrentReduction { get; set; } = 0f;
            public bool IsBan { get; set; } = false;
            public DateTime ExpiresAt { get; set; } = DateTime.MinValue;
            public DateTime LastOffenseDate { get; set; } = DateTime.MinValue;
            public bool PendingExpirationNotice { get; set; } = false;
        }

        #endregion

        #region Data Management

        void LoadData()
        {
            try
            {
                if (Interface.Oxide.DataFileSystem.ExistsDatafile(DATA_PAIR))
                    _pairData = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, Dictionary<ulong, PlayerPairData>>>(DATA_PAIR)
                        ?? new Dictionary<ulong, Dictionary<ulong, PlayerPairData>>();

                if (Interface.Oxide.DataFileSystem.ExistsDatafile(DATA_PENALTIES))
                    _penalties = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, PlayerPenalty>>(DATA_PENALTIES)
                        ?? new Dictionary<ulong, PlayerPenalty>();

                float loadMultiplier = _config.TeamLimit >= 2 ? 1.0f : Math.Max(1.0f, 2.0f / _config.TeamLimit);
                foreach (var outer in _pairData)
                {
                    foreach (var inner in outer.Value)
                    {
                        if (inner.Value.AlertScore == 0f && inner.Value.Alerts.Count > 0)
                        {
                            foreach (var alert in inner.Value.Alerts)
                            {
                                if (_config.Weights.TryGetValue(alert, out var weight))
                                    inner.Value.AlertScore += weight * loadMultiplier;
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                PrintError($"Error loading data: {ex.Message}");
                _pairData = new Dictionary<ulong, Dictionary<ulong, PlayerPairData>>();
                _penalties = new Dictionary<ulong, PlayerPenalty>();
            }
        }

        void SaveData()
        {
            try
            {
                Interface.Oxide.DataFileSystem.WriteObject(DATA_PAIR, _pairData);
                Interface.Oxide.DataFileSystem.WriteObject(DATA_PENALTIES, _penalties);
            }
            catch (Exception ex)
            {
                PrintError($"Error saving data: {ex.Message}");
            }
        }

        void CleanupExpiredPenalties()
        {
            var now = DateTime.UtcNow;
            var expired = _penalties.Where(p => p.Value.ExpiresAt < now && (p.Value.CurrentReduction > 0 || p.Value.IsBan)).Select(p => p.Key).ToList();

            bool changed = false;
            foreach (var id in expired)
            {
                _penalties[id].CurrentReduction = 0f;
                _penalties[id].IsBan = false;

                var player = BasePlayer.FindByID(id);
                if (player != null && player.IsConnected)
                {
                    player.ChatMessage("<color=#00ff00>Your teaming penalty has expired.</color>");
                }
                else
                {
                    _penalties[id].PendingExpirationNotice = true;
                }
                changed = true;
            }

            if (changed) SaveData();
        }

        private void CheckOffenseDecay(ulong playerId)
        {
            if (!_penalties.TryGetValue(playerId, out var penalty)) return;
            if (penalty.OffenseCount <= 0) return;

            double daysSinceLast = (DateTime.UtcNow - penalty.LastOffenseDate).TotalDays;
            int decayAmount = (int)(daysSinceLast / _config.OffenseDecayDays);

            if (decayAmount > 0)
            {
                penalty.OffenseCount = Math.Max(0, penalty.OffenseCount - decayAmount);

                penalty.LastOffenseDate = penalty.LastOffenseDate.AddDays(decayAmount * _config.OffenseDecayDays);
                SaveData();
            }
        }

        void OnPlayerConnected(BasePlayer player)
        {
            CheckOffenseDecay(player.userID);

            if (_penalties.TryGetValue(player.userID, out var penalty))
            {

                if (penalty.PendingExpirationNotice)
                {
                    penalty.PendingExpirationNotice = false;
                    player.ChatMessage("<color=#00ff00>Your teaming penalty has expired.</color>");
                    SaveData();
                }

                else if (penalty.CurrentReduction > 0 && penalty.ExpiresAt > DateTime.UtcNow)
                {
                    TimeSpan remaining = penalty.ExpiresAt - DateTime.UtcNow;
                    string durationStr = remaining.TotalMinutes < 60
                        ? $"{Math.Ceiling(remaining.TotalMinutes)} minutes"
                        : remaining.TotalHours < 24
                            ? $"{Math.Ceiling(remaining.TotalHours)} hours"
                            : $"{remaining.Days} days";

                    string msg = $"<color=#ff4444>⚠ ACTIVE TEAMING PENALTY ⚠</color>\n" +
                                 $"<color=#ffffff>You have an active penalty for past teaming violations.</color>\n" +
                                 $"<color=#ffaa00>Status: {penalty.CurrentReduction}% Damage Reduction</color>\n" +
                                 $"<color=#ffaa00>Time Remaining: {durationStr}</color>";

                    player.ChatMessage(msg);
                }
            }
        }

        #endregion

        #region Pair Data Access

        private (ulong, ulong) NormalizePair(ulong id1, ulong id2)
        {
            return id1 > id2 ? (id1, id2) : (id2, id1);
        }

        private PlayerPairData GetPairData(ulong id1, ulong id2)
        {
            var (primary, secondary) = NormalizePair(id1, id2);

            if (!_pairData.TryGetValue(primary, out var subDict))
            {
                subDict = new Dictionary<ulong, PlayerPairData>();
                _pairData[primary] = subDict;
            }

            if (!subDict.TryGetValue(secondary, out var data))
            {
                data = new PlayerPairData();
                subDict[secondary] = data;
            }

            return data;
        }

        private float GetTeamLimitMultiplier()
        {
            if (_config.TeamLimit >= 2) return 1.0f;
            return Math.Max(1.0f, 2.0f / _config.TeamLimit);
        }

public void AddAlert(ulong id1, ulong id2, string alertType, bool checkCooldown = true)
        {
            var data = GetPairData(id1, id2);
            ApplyDecay(data);

            if (checkCooldown && data.AlertCooldowns.TryGetValue(alertType, out var lastTime))
            {
                if ((DateTime.UtcNow - lastTime).TotalMinutes < 15.0)
                    return;
            }

            if (_config.Weights.TryGetValue(alertType, out var weight))
            {
                float multiplier = GetTeamLimitMultiplier();
                data.AlertScore += weight * multiplier;
                data.AlertCooldowns[alertType] = DateTime.UtcNow;

                data.Alerts.Add(alertType);
                if (data.Alerts.Count > 20) data.Alerts.RemoveAt(0);
            }

            data.LastActivity = DateTime.UtcNow;
        }

        #endregion

        #region Helpers

        private bool ShouldIgnore(BasePlayer player)
        {
            if (player == null) return true;
            if (!player.userID.IsSteamId()) return true;
            if (_config.IgnoreAdmins && player.IsAdmin) return true;
            if (permission.UserHasPermission(player.UserIDString, PERMISSION_BYPASS)) return true;
            return false;
        }

        private bool IsTeammate(ulong player1, ulong player2)
        {
            if (RelationshipManager.ServerInstance.playerToTeam.TryGetValue(player1, out var team))
            {
                if (team.members.Contains(player2)) return true;
            }

            if (Clans != null)
            {
                var clanMembers = Clans.Call<List<string>>("GetClanMembers", player1);
                if (clanMembers != null && clanMembers.Contains(player2.ToString()))
                    return true;
            }

            return false;
        }

        private List<ulong> GetTeammates(ulong playerId)
        {
            var teammates = new List<ulong>();

            if (RelationshipManager.ServerInstance.playerToTeam.TryGetValue(playerId, out var team))
                teammates.AddRange(team.members);

            if (Clans != null)
            {
                var clanMembers = Clans.Call<List<string>>("GetClanMembers", playerId);
                if (clanMembers != null)
                {
                    foreach (var member in clanMembers)
                    {
                        if (ulong.TryParse(member, out var id) && !teammates.Contains(id))
                            teammates.Add(id);
                    }
                }
            }

            return teammates;
        }

        private bool IsOnCooldown(ulong playerId)
        {
            if (_cooldowns.TryGetValue(playerId, out var cooldownEnd))
            {
                if (Time.time < cooldownEnd) return true;
                _cooldowns.Remove(playerId);
            }
            return false;
        }

        private void SetCooldown(ulong playerId, float seconds)
        {
            _cooldowns[playerId] = Time.time + seconds;
        }

        private bool CanScan(ulong playerId, float interval = 1.0f)
        {
            if (_scanCooldowns.TryGetValue(playerId, out var nextScan))
            {
                if (Time.time < nextScan) return false;
            }
            _scanCooldowns[playerId] = Time.time + interval;
            return true;
        }

        private static bool IsVisible(BasePlayer from, BasePlayer to)
        {
            if (to == null || from == null) return false;

            Vector3 viewPos = from.eyes.position;

            return to.IsVisible(viewPos, to.CenterPoint()) ||
                   to.IsVisible(viewPos, to.eyes.position);
        }

        #endregion

        #region Score Calculation

        private void ApplyDecay(PlayerPairData data)
        {
            double hours = (DateTime.UtcNow - data.LastDecay).TotalHours;
            if (hours >= 6.0)
            {
                int steps = (int)(hours / 6.0);
                float decay = steps * 0.2f;

                if (data.AlertScore > 0)
                {
                    data.AlertScore = Math.Max(0f, data.AlertScore - decay);
                }

                data.LastDecay = data.LastDecay.AddHours(steps * 6.0);
            }
        }

        private float CalculateScore(ulong id1, ulong id2)
        {
            var data = GetPairData(id1, id2);
            ApplyDecay(data);
            return data.AlertScore;
        }

        private List<ulong> GetTeamingPartners(ulong targetId, float threshold)
        {
            var partners = new List<ulong>();

            if (_pairData.TryGetValue(targetId, out var subDict))
            {
                foreach (var kvp in subDict)
                {
                    if (CalculateScore(targetId, kvp.Key) >= threshold)
                        partners.Add(kvp.Key);
                }
            }

            foreach (var kvp in _pairData)
            {
                if (kvp.Key <= targetId) continue;

                if (kvp.Value.ContainsKey(targetId))
                {
                    if (CalculateScore(kvp.Key, targetId) >= threshold)
                        partners.Add(kvp.Key);
                }
            }

            return partners;
        }

        private void CheckAndApplyPenalty(ulong id1, ulong id2, string alertType)
        {

            var cluster = new HashSet<ulong> { id1, id2 };
            cluster.UnionWith(GetTeammates(id1));
            cluster.UnionWith(GetTeammates(id2));

            if (cluster.Count <= _config.TeamLimit * 2)
            {
                var queue = new Queue<ulong>(cluster);
                var processed = new HashSet<ulong>(cluster);
                float linkThreshold = _config.PenaltyThreshold * 0.4f;

                int safetyBreak = 0;
                while (queue.Count > 0 && safetyBreak < 50)
                {
                    var current = queue.Dequeue();
                    safetyBreak++;

                    var partners = GetTeamingPartners(current, linkThreshold);
                    foreach (var p in partners)
                    {
                        if (!processed.Contains(p))
                        {

                            cluster.Add(p);
                            processed.Add(p);

                            var teammates = GetTeammates(p);
                            foreach (var tm in teammates)
                            {
                                if (!processed.Contains(tm))
                                {
                                    cluster.Add(tm);
                                    processed.Add(tm);
                                    queue.Enqueue(tm);
                                }
                            }

                            queue.Enqueue(p);
                        }
                    }
                }
            }

            if (cluster.Count <= _config.TeamLimit)
                return;

            float score = CalculateScore(id1, id2);
            var data = GetPairData(id1, id2);

            float warningThreshold = _config.PenaltyThreshold * 0.6f;
            if (score >= warningThreshold && score < _config.PenaltyThreshold && !data.Warned)
            {
                data.Warned = true;
                SendWarning(id1, id2, score, alertType, data);
            }

            if (score >= _config.PenaltyThreshold)
            {

                SendDiscordAlert(id1, id2, alertType, score, data, cluster.Count);

                ApplyPenalty(id1, id2, data);

            }
        }

        #endregion

        #region Penalty Application

        private void ApplyPenalty(ulong id1, ulong id2, PlayerPairData pairData)
        {
            if (!_config.Penalties.Enabled && !_config.Penalties.BanEnabled) return;

            var players = new List<ulong> { id1, id2 };
            players.AddRange(GetTeammates(id1));
            players.AddRange(GetTeammates(id2));
            players = players.Distinct().ToList();

            foreach (var playerId in players)
            {
                if (!_penalties.TryGetValue(playerId, out var penalty))
                {
                    penalty = new PlayerPenalty();
                    _penalties[playerId] = penalty;
                }

                if ((DateTime.UtcNow - penalty.LastOffenseDate).TotalSeconds < 5.0) continue;

                penalty.OffenseCount++;
                penalty.LastOffenseDate = DateTime.UtcNow;

                var tier = _config.Penalties.GetTier(penalty.OffenseCount);
                if (tier != null)
                {
                    if (_config.Penalties.BanEnabled)
                    {
                        penalty.IsBan = true;
                        penalty.CurrentReduction = 0f;
                        int durationMinutes = tier.BanDurationMinutes > 0 ? tier.BanDurationMinutes : 1;
                        penalty.ExpiresAt = DateTime.UtcNow.AddMinutes(durationMinutes);

                        string timeStr = durationMinutes >= 60
                            ? $"{durationMinutes / 60} hours"
                            : $"{durationMinutes} minutes";

                        var player = BasePlayer.FindByID(playerId);
                        string playerName = player?.displayName;

                        if (player != null && player.IsConnected)
                        {
                            player.Kick($"Teaming Violation: Temp Ban for {timeStr}.");
                        }

                        SendPublicDiscordSanction(playerId, "Temporary Ban", penalty.ExpiresAt, playerName);
                    }
                    else if (_config.Penalties.Enabled)
                    {
                        penalty.IsBan = false;
                        penalty.CurrentReduction = tier.DamageReduction;
                        penalty.ExpiresAt = DateTime.UtcNow.AddHours(tier.DurationHours);

                        var player = BasePlayer.FindByID(playerId);
                        string playerName = player?.displayName;

                        if (player != null && player.IsConnected)
                        {
                            TimeSpan remaining = penalty.ExpiresAt - DateTime.UtcNow;
                            string durationStr = remaining.TotalMinutes < 60
                                ? $"{Math.Ceiling(remaining.TotalMinutes)} minutes"
                                : remaining.TotalHours < 24
                                    ? $"{Math.Ceiling(remaining.TotalHours)} hours"
                                    : $"{remaining.Days} days";

                            string msg = $"<color=#ff4444>⚠ TEAMING VIOLATION ⚠</color>\n" +
                                         $"<color=#ffffff>Offense #{penalty.OffenseCount}</color>\n" +
                                         $"<color=#ffaa00>Penalty: {penalty.CurrentReduction}% damage reduction for {durationStr}</color>";
                            player.ChatMessage(msg);
                        }

                        SendPublicDiscordSanction(playerId, $"{tier.DamageReduction}% Damage Reduction", penalty.ExpiresAt, playerName);
                    }
                }
            }

            pairData.Alerts.Clear();
            pairData.Warned = false;
            SaveData();
        }

        private void SendWarning(ulong id1, ulong id2, float score, string alertType = "Warning", PlayerPairData data = null)
        {
            var player1 = BasePlayer.FindByID(id1);
            var player2 = BasePlayer.FindByID(id2);

            string riskMsg = _config.Penalties.BanEnabled
                ? "Continue at risk of BAN!"
                : "Continue at risk of damage penalty!";

            string warning = $"<color=#ff6b35>⚠ TEAMING WARNING ⚠</color>\n" +
                            $"<color=#ffffff>Suspicious activity detected with players outside your team.</color>\n" +
                            $"<color=#ffaa00>Score: {score:F2}/{_config.PenaltyThreshold:F2}</color>\n" +
                            $"<color=#ff4444>{riskMsg}</color>";

            if (player1?.IsConnected == true) player1.ChatMessage(warning);
            if (player2?.IsConnected == true) player2.ChatMessage(warning);

            if (!string.IsNullOrEmpty(_config.DiscordWebhook))
            {
                string name1 = player1?.displayName ?? id1.ToString();
                string name2 = player2?.displayName ?? id2.ToString();
                string alertHistory = data != null && data.Alerts.Count > 0 ? string.Join(", ", data.Alerts) : "None";

                var embed = new
                {
                    title = "⚠️ Teaming Warning Issued",
                    color = 0xFFAA00,
                    description = string.IsNullOrEmpty(_config.ServerName) ? "" : _config.ServerName,
                    fields = new[]
                    {
                        new { name = "Player 1", value = $"[{name1}](https://steamcommunity.com/profiles/{id1})", inline = true },
                        new { name = "Player 2", value = $"[{name2}](https://steamcommunity.com/profiles/{id2})", inline = true },
                        new { name = "Trigger", value = alertType, inline = true },
                        new { name = "Current Score", value = $"{score:F2}/{_config.PenaltyThreshold:F2}", inline = true },
                        new { name = "History", value = alertHistory, inline = false }
                    },
                    footer = new { text = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss UTC") }
                };

                var payload = JsonConvert.SerializeObject(new { embeds = new[] { embed } });

                webrequest.Enqueue(_config.DiscordWebhook, payload, (code, response) => {}, this, Core.Libraries.RequestMethod.POST,
                    new Dictionary<string, string> { { "Content-Type", "application/json" } });
            }
        }

        #endregion

        #region Detection Hooks

        void OnCodeEntered(CodeLock codeLock, BasePlayer player, string code)
        {
            if (!_config.Detection.CodeLock) return;
            if (ShouldIgnore(player)) return;
            if (codeLock.code != code) return;

            var authList = codeLock.whitelistPlayers.Where(x => x.IsSteamId()).ToList();
            if (!authList.Contains(player.userID))
                authList.Add(player.userID);

            if (authList.Count > _config.TeamLimit)
            {
                foreach (var otherId in authList)
                {
                    if (otherId == player.userID) continue;
                    if (IsTeammate(player.userID, otherId)) continue;

                    AddAlert(player.userID, otherId, "CodeLock");
                    CheckAndApplyPenalty(player.userID, otherId, "CodeLock");
                    break;
                }
            }
        }

        void OnCupboardAuthorize(BuildingPrivlidge privilege, BasePlayer player)
        {
            if (!_config.Detection.Cupboard) return;
            if (ShouldIgnore(player)) return;

            var authList = new List<ulong>();
            foreach (var entry in privilege.authorizedPlayers)
            {
                ulong id = (ulong)entry;
                if (id.IsSteamId())
                    authList.Add(id);
            }

            if (!authList.Contains(player.userID))
                authList.Add(player.userID);

            if (privilege.GetProtectedMinutes() > 0 && authList.Count > _config.TeamLimit)
            {
                foreach (var otherId in authList)
                {
                    if (otherId == player.userID) continue;
                    if (IsTeammate(player.userID, otherId)) continue;

                    AddAlert(player.userID, otherId, "Cupboard");
                    CheckAndApplyPenalty(player.userID, otherId, "Cupboard");
                    break;
                }
            }
        }

        void OnTurretAuthorize(AutoTurret turret, BasePlayer player)
        {
            if (!_config.Detection.Turret) return;
            if (ShouldIgnore(player)) return;

            var authList = new List<ulong>();
            foreach (var entry in turret.authorizedPlayers)
            {
                ulong id = (ulong)entry;
                if (id.IsSteamId())
                    authList.Add(id);
            }

            if (!authList.Contains(player.userID))
                authList.Add(player.userID);

            var cupboard = turret.GetBuildingPrivilege();
            if (cupboard?.GetProtectedMinutes() <= 0) return;

            if (authList.Count > _config.TeamLimit)
            {
                foreach (var otherId in authList)
                {
                    if (otherId == player.userID) continue;
                    if (IsTeammate(player.userID, otherId)) continue;

                    AddAlert(player.userID, otherId, "Turret");
                    CheckAndApplyPenalty(player.userID, otherId, "Turret");
                    break;
                }
            }
        }

        void OnEntityMounted(BaseMountable entity, BasePlayer player)
        {
            if (!_config.Detection.SharedVehicle) return;
            if (ShouldIgnore(player)) return;
            if (entity.GetParentEntity() == null) return;
            if (player.InSafeZone()) return;

            if (entity.GetParentEntity() is BaseVehicle vehicle)
            {
                var mounted = new List<ulong>();
                foreach (var mount in vehicle.mountPoints)
                {
                    var mountedPlayer = mount?.mountable?.GetMounted();
                    if (mountedPlayer != null && mountedPlayer.userID.IsSteamId())
                    {
                        if (!(_config.IgnoreAdmins && mountedPlayer.IsAdmin))
                            mounted.Add(mountedPlayer.userID);
                    }
                }

                if (!mounted.Contains(player.userID))
                    mounted.Add(player.userID);

                if (mounted.Count > _config.TeamLimit)
                {
                    for (int i = 0; i < mounted.Count; i++)
                    {
                        for (int j = i + 1; j < mounted.Count; j++)
                        {
                            if (IsTeammate(mounted[i], mounted[j])) continue;

                            AddAlert(mounted[i], mounted[j], "SharedVehicle");
                            CheckAndApplyPenalty(mounted[i], mounted[j], "SharedVehicle");
                            return;
                        }
                    }
                }
            }
        }

        void OnEntityBuilt(Planner plan, GameObject go)
        {
            if (!_config.Detection.SleepingBag) return;
            if (plan == null || go == null) return;

            var player = plan.GetOwnerPlayer();
            var entity = go.ToBaseEntity();

            if (entity == null || player == null) return;
            if (ShouldIgnore(player)) return;

            if (entity is SleepingBag bag)
                CheckBag(player, bag, player.userID);
        }

        object CanAssignBed(BasePlayer player, SleepingBag bag, ulong targetPlayerId)
        {
            if (_config.Detection.SleepingBag && player != null && !ShouldIgnore(player))
                CheckBag(player, bag, targetPlayerId);
            return null;
        }

        void CheckBag(BasePlayer player, SleepingBag bag, ulong targetPlayerId)
        {
            if (player.userID == targetPlayerId) return;

            var bagsInBuilding = SleepingBag.sleepingBags
                .Where(x => x.buildingID == bag.buildingID && x != bag && x.deployerUserID.IsSteamId())
                .Select(x => x.deployerUserID)
                .Distinct()
                .ToList();

            if (targetPlayerId.IsSteamId() && !bagsInBuilding.Contains(targetPlayerId))
                bagsInBuilding.Add(targetPlayerId);

            if (bagsInBuilding.Count > _config.TeamLimit)
            {
                foreach (var otherId in bagsInBuilding)
                {
                    if (otherId == player.userID) continue;
                    if (IsTeammate(player.userID, otherId)) continue;

                    AddAlert(player.userID, otherId, "SleepingBag");
                    CheckAndApplyPenalty(player.userID, otherId, "SleepingBag");
                    break;
                }
            }
        }

        void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info)
        {
            if (entity == null || info == null) return;

            var attacker = info.InitiatorPlayer;
            if (attacker == null || attacker.IsNpc) return;

            if (_config.Penalties.Enabled && _penalties.TryGetValue(attacker.userID, out var penalty))
            {
                if (penalty.CurrentReduction > 0 && penalty.ExpiresAt > DateTime.UtcNow)
                {

                    bool isPlayer = entity is BasePlayer p && !p.IsNpc && p != attacker;
                    bool isStructure = entity is BuildingBlock || entity is Door || entity is SimpleBuildingBlock;

                    if (isPlayer || isStructure)
                    {
                        float scale = 1f - (penalty.CurrentReduction / 100f);
                        info.damageTypes.ScaleAll(scale);
                    }
                }
            }

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

            if (!IsTeammate(attacker.userID, victim.userID))
            {
                var pairData = GetPairData(attacker.userID, victim.userID);
                pairData.ProximityTime = 0f;
            }

            if (!_config.Detection.TeamKill) return;
            if (ShouldIgnore(attacker)) return;
            if (info.ProjectileID == 0) return;
            if (IsOnCooldown(attacker.userID)) return;

            if (!CanScan(attacker.userID, 1.0f)) return;

            List<BasePlayer> nearbyPlayers = Facepunch.Pool.Get<List<BasePlayer>>();
            try
            {
                Vis.Entities(attacker.transform.position, _config.Detection.TeamKillMaxDistance, nearbyPlayers);

                foreach (var nearby in nearbyPlayers)
                {
                    if (nearby == attacker || nearby == victim) continue;
                    if (ShouldIgnore(nearby)) continue;
                    if (nearby.IsSleeping() || !nearby.IsConnected) continue;
                    if (IsTeammate(attacker.userID, nearby.userID)) continue;

                    var heldEntity = nearby.GetActiveItem()?.GetHeldEntity() as BaseProjectile;
                    if (heldEntity == null) continue;

                    float distanceToVictim = Vector3.Distance(nearby.transform.position, victim.transform.position);
                    if (distanceToVictim < _config.Detection.TeamKillMinTargetDistance) continue;

                    Vector3 lookDir = nearby.eyes.HeadForward();
                    Vector3 toVictim = (victim.transform.position - nearby.transform.position).normalized;
                    float angle = Vector3.Angle(lookDir, toVictim);

                    if (angle < 30f)
                    {
                        if (!IsVisible(attacker, nearby)) continue;

                        AddAlert(attacker.userID, nearby.userID, "TeamKill");
                        CheckAndApplyPenalty(attacker.userID, nearby.userID, "TeamKill");
                        SetCooldown(attacker.userID, 60f);
                        break;
                    }
                }
            }
            finally
            {
                Facepunch.Pool.FreeUnmanaged(ref nearbyPlayers);
            }
        }

        void OnPlayerRecovered(BasePlayer player)
        {
            if (!_config.Detection.Revive) return;
            if (player.InSafeZone()) return;

            List<BasePlayer> nearbyPlayers = Facepunch.Pool.Get<List<BasePlayer>>();

            try
            {
                Vis.Entities(player.transform.position, 3f, nearbyPlayers);

                foreach (var reviver in nearbyPlayers)
                {
                    if (reviver == player) continue;
                    if (ShouldIgnore(reviver)) continue;
                    if (!reviver.serverInput.IsDown(BUTTON.USE)) continue;
                    if (IsTeammate(player.userID, reviver.userID)) continue;

                    timer.Once(3f, () =>
                    {
                        if (player != null && !player.IsDead())
                        {
                            AddAlert(player.userID, reviver.userID, "Revive");
                            CheckAndApplyPenalty(player.userID, reviver.userID, "Revive");
                        }
                    });
                    break;
                }
            }
            finally
            {
                Facepunch.Pool.FreeUnmanaged(ref nearbyPlayers);
            }
        }

        void OnLootEntity(BasePlayer player, BaseEntity entity)
        {
            if (!_config.Detection.Looting) return;
            if (ShouldIgnore(player)) return;
            if (!(entity is PlayerCorpse corpse)) return;
            if (!corpse.playerSteamID.IsSteamId()) return;
            if (player.InSafeZone()) return;

            var netId = corpse.net.ID.Value;

            if (_corpseLooting.TryGetValue(netId, out var firstLooter))
            {
                if (firstLooter != player.userID && !IsTeammate(player.userID, firstLooter))
                {
                    AddAlert(player.userID, firstLooter, "Looting");
                    CheckAndApplyPenalty(player.userID, firstLooter, "Looting");
                }
                return;
            }

            _corpseLooting[netId] = player.userID;
            timer.Once(_config.Detection.LootingWindow, () => _corpseLooting.Remove(netId));
        }

        void OnPlayerVoice(BasePlayer player, byte[] data)
        {
            if (!_config.Detection.VoiceChat) return;
            if (ShouldIgnore(player)) return;
            if (player.InSafeZone() || player.IsBuildingAuthed()) return;

            if (!CanScan(player.userID, 2.0f)) return;

            List<BasePlayer> nearbyPlayers = Facepunch.Pool.Get<List<BasePlayer>>();
            try
            {
                Vis.Entities(player.transform.position, 10f, nearbyPlayers);

                foreach (var other in nearbyPlayers)
                {
                    if (other == player) continue;
                    if (other.IsSleeping() || !other.IsConnected || !other.IsAlive()) continue;
                    if (ShouldIgnore(other)) continue;
                    if (other.InSafeZone() || other.IsBuildingAuthed()) continue;
                    if (IsTeammate(player.userID, other.userID)) continue;
                    if (!IsVisible(player, other)) continue;

                    IncrementVoiceChat(player.userID, other.userID);
                }
            }
            finally
            {
                Facepunch.Pool.FreeUnmanaged(ref nearbyPlayers);
            }
        }

        void IncrementVoiceChat(ulong id1, ulong id2)
        {
            var (primary, secondary) = NormalizePair(id1, id2);

            if (!_voiceChatTicks.TryGetValue(primary, out var subDict))
            {
                subDict = new Dictionary<ulong, int>();
                _voiceChatTicks[primary] = subDict;
            }

            if (!subDict.TryGetValue(secondary, out var ticks))
                ticks = 0;

            ticks++;
            subDict[secondary] = ticks;

            if (ticks == _config.Detection.VoiceChatSeconds * 10)
            {
                AddAlert(primary, secondary, "VoiceChat");
                CheckAndApplyPenalty(primary, secondary, "VoiceChat");
            }
        }

        void OnPlayerDeath(BasePlayer player, HitInfo info)
        {
            if (_proximityTracker == null) return;
            if (info?.InitiatorPlayer == null) return;
            if (info.InitiatorPlayer == player) return;
            if (!player.userID.IsSteamId()) return;
            if (!info.InitiatorPlayer.userID.IsSteamId()) return;

            var killer = info.Initiato
// ... [Code preview truncated due to length. Full source in attached file] ...

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