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

FREE AdminMonitor : 1.3.0 1.3.0

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

namespace Oxide.Plugins
{
    [Info("Admin Monitor", "Braga", "1.3.0")]
    [Description("Monitors admin activity and tracks their time spent in-game")]
    public class AdminMonitor : RustPlugin
    {
        #region Fields
        private const string PermissionAdmin = "adminmonitor.admin";
        private const string PermissionView = "adminmonitor.view";
        private const string PermissionTracked = "adminmonitor.tracked";
        private const string PermissionReports = "adminmonitor.reports";
        private StoredData? storedData;
        private readonly Dictionary<ulong, AdminActivity> activeAdmins = new();
        private readonly Dictionary<ulong, Timer> activityTimers = new();
        private readonly Dictionary<ulong, Timer> uiUpdateTimers = new();
        private readonly Dictionary<ulong, Vector3> lastPositions = new();
        private readonly Dictionary<ulong, Quaternion> lastRotations = new();
        private readonly Dictionary<ulong, float> lastActionTimes = new();
        private readonly Dictionary<ulong, HashSet<string>> activeActions = new();
        private readonly string LayerMain = "AdminMonitor.UI";
        private readonly string LayerContent = "AdminMonitor.Content";

        [PluginReference]
        private readonly Plugin? ImageLibrary;
        private readonly Dictionary<ulong, string> avatarUrls = new();

        /// <summary>
        /// URL Discord вебхука для отправки уведомлений
        /// </summary>
        private string DiscordWebhookUrl => Config["DiscordWebhookUrl"]?.ToString() ?? string.Empty;

        /// <summary>
        /// Порог движения для определения AFK (в единицах)
        /// </summary>
        private const float MovementThreshold = 0.1f;

        /// <summary>
        /// Порог поворота для определения AFK (в градусах)
        /// </summary>
        private const float RotationThreshold = 1f;

        /// <summary>
        /// Интервал проверки активности в секундах
        /// </summary>
        private readonly float checkInterval = 1f;

        /// <summary>
        /// Порог неактивности в секундах
        /// </summary>
        private readonly float inactivityThreshold = 60f;
        private readonly Dictionary<ulong, List<AdminAction>> recentActionsCache = new();
        private readonly TimeSpan actionsCacheExpiration = TimeSpan.FromSeconds(5);
        private readonly Dictionary<ulong, DateTime> lastActionsCacheUpdate = new();
        private readonly Dictionary<string, string> filterSettings = new();
        private const string FilterAll = "all";
        private const string FilterOnline = "online";
        private const string FilterOffline = "offline";
        private const string FilterAfk = "afk";
        private const string SortTimeActive = "active";
        private const string SortTimeAfk = "afk";
        private const string SortName = "name";
        private const string ReportsFolderName = "Reports";
        #endregion Fields

        #region Data Classes
        private sealed class StoredData
        {
            public Dictionary<ulong, AdminStats> AdminStatistics { get; set; } = new();
            public List<WipeHistory> WipeHistory { get; set; } = new();
            public DateTime? CurrentWipeStart { get; set; }
        }

        private sealed class WipeHistory
        {
            public required DateTime StartDate { get; set; }
            public required DateTime EndDate { get; set; }
            public Dictionary<ulong, AdminWipeStats> AdminStats { get; set; } = new();
        }

        private sealed class AdminWipeStats
        {
            public required string LastName { get; set; }
            public float TotalActiveTime { get; set; }
            public float TotalAfkTime { get; set; }
            public Dictionary<string, int> CommandsUsed { get; set; } = new();
            public DateTime LastActive { get; set; }
        }

        private sealed class AdminStats
        {
            public required string LastName { get; set; }
            public float TotalActiveTime { get; set; }
            public float TotalAfkTime { get; set; }
            public Dictionary<string, int> CommandsUsed { get; set; } = new();
            public List<AdminAction> RecentActions { get; set; } = new();
            public DateTime LastActive { get; set; }
            public float CurrentWipeActiveTime { get; set; }
            public float CurrentWipeAfkTime { get; set; }
        }

        private sealed class AdminActivity
        {
            public float SessionStartTime { get; set; }
            public float LastActiveTime { get; set; }
            public float TotalAfkTime { get; set; }
            public bool IsAfk { get; set; }
            public Vector3 LastPosition { get; set; }
            public float AfkStartTime { get; set; }
        }

        private sealed class AdminAction
        {
            public required string ActionType { get; set; }
            public required string Details { get; set; }
            public required string Target { get; set; }
            public DateTime Timestamp { get; set; }
            public Vector3 Location { get; set; }
        }

        private sealed class AdminReport
        {
            public required DateTime GenerationDate { get; set; }
            public required string ReportType { get; set; }
            public required string Period { get; set; }
            public Dictionary<ulong, AdminReportStats> Statistics { get; set; } = new();
        }

        private sealed class AdminReportStats
        {
            public required string AdminName { get; set; }
            public float ActiveTime { get; set; }
            public float AfkTime { get; set; }
            public int CommandsUsed { get; set; }
            public int BuildActions { get; set; }
            public int DestroyActions { get; set; }
            public int PlayerInteractions { get; set; }
            public Dictionary<string, int> ActionTypes { get; set; } = new();
        }
        #endregion Data Classes

        #region Oxide Hooks
        private void Init()
        {
            permission.RegisterPermission(PermissionAdmin, this);
            permission.RegisterPermission(PermissionView, this);
            permission.RegisterPermission(PermissionTracked, this);
            permission.RegisterPermission(PermissionReports, this);
            LoadData();

            cmd.AddChatCommand("amonitor", this, nameof(CmdAdminMonitor));
            cmd.AddConsoleCommand("amonitor.check", this, nameof(CmdCheckAdmin));
            cmd.AddConsoleCommand("amonitor.report", this, nameof(CmdGenerateReport));

            // Создаем папку для отчетов
            if (
                !Interface.Oxide.DataFileSystem.ExistsDatafile(
                    $"{DataFolderName}/{ReportsFolderName}"
                )
            )
            {
                _ = Interface.Oxide.DataFileSystem.GetDatafile(
                    $"{DataFolderName}/{ReportsFolderName}"
                );
            }

            // Добавляем таймер для сохранения статистики каждую минуту
            _ = timer.Every(
                60f,
                () =>
                {
                    if (storedData == null)
                    {
                        return;
                    }

                    UpdateAllAdminStats();
                    SaveData();
                    Puts("Admin statistics saved (1-minute interval)");
                }
            );

            ScheduleReports();
        }

        private void OnServerInitialized(bool initial)
        {
            if (ImageLibrary == null)
            {
                PrintError("ImageLibrary plugin is not installed!");
            }

            if (storedData != null && storedData.CurrentWipeStart == null)
            {
                storedData.CurrentWipeStart = DateTime.Now;
                SaveData();
            }

            foreach (BasePlayer player in BasePlayer.activePlayerList)
            {
                if (player?.IsAdmin == true)
                {
                    StartMonitoring(player);
                    LoadPlayerAvatar(player);
                }
            }
        }

        private void OnNewSave(string filename)
        {
            if (storedData == null)
            {
                return;
            }

            // Сохраняем статистику предыдущего вайпа
            if (storedData.CurrentWipeStart.HasValue)
            {
                WipeHistory wipeHistory = new()
                {
                    StartDate = storedData.CurrentWipeStart.Value,
                    EndDate = DateTime.Now,
                };

                foreach (KeyValuePair<ulong, AdminStats> kvp in storedData.AdminStatistics)
                {
                    wipeHistory.AdminStats[kvp.Key] = new AdminWipeStats
                    {
                        LastName = kvp.Value.LastName,
                        TotalActiveTime = kvp.Value.CurrentWipeActiveTime,
                        TotalAfkTime = kvp.Value.CurrentWipeAfkTime,
                        CommandsUsed = new Dictionary<string, int>(kvp.Value.CommandsUsed),
                        LastActive = kvp.Value.LastActive,
                    };

                    // Сбрасываем статистику текущего вайпа
                    kvp.Value.CurrentWipeActiveTime = 0;
                    kvp.Value.CurrentWipeAfkTime = 0;
                    kvp.Value.CommandsUsed.Clear();
                }

                storedData.WipeHistory.Add(wipeHistory);
                storedData.CurrentWipeStart = DateTime.Now;
                SaveData();
            }
        }

        private void OnServerSave()
        {
            if (storedData == null)
            {
                return;
            }

            UpdateAllAdminStats();
            SaveData();
            Puts("Admin statistics saved (server save)");
        }

        private void OnPlayerConnected(BasePlayer player)
        {
            if (player == null)
            {
                return;
            }

            if (
                player.IsAdmin
                || permission.UserHasPermission(player.UserIDString, PermissionTracked)
            )
            {
                StartMonitoring(player);
                LoadPlayerAvatar(player);
                SendDiscordLoginMessage(player);
            }
        }

        private void OnPlayerDisconnected(BasePlayer player, string reason)
        {
            if (
                player == null
                || (
                    !player.IsAdmin
                    && !permission.UserHasPermission(player.UserIDString, PermissionTracked)
                )
            )
            {
                return;
            }

            // Очищаем таймер обновления UI при отключении
            if (uiUpdateTimers.TryGetValue(player.userID, out Timer uiTimer))
            {
                uiTimer.Destroy();
                _ = uiUpdateTimers.Remove(player.userID);
            }

            SendDiscordLogoutMessage(player);
            StopMonitoring(player);
            SaveAdminStats(player);
        }

        private object? OnPlayerCommand(BasePlayer? player, string command, string[] args)
        {
            if (
                player == null
                || (
                    !player.IsAdmin
                    && !permission.UserHasPermission(player.UserIDString, PermissionTracked)
                )
            )
            {
                return null;
            }

            RecordAction(player, "Command", command, string.Join(" ", args));
            UpdateAdminStats(player, "command", command);
            return null;
        }

        private void OnPlayerViolation(BasePlayer? player, AntiHackType type, float amount, BaseEntity? entity)
        {
            if (player?.IsAdmin != true)
            {
                return;
            }

            RecordAction(player, "Violation", $"{type}", $"Amount: {amount}");
        }

        private void OnEntityBuilt(Planner planner, GameObject go)
        {
            if (planner == null || go == null)
            {
                return;
            }

            BasePlayer? player = planner.GetOwnerPlayer();
            if (player?.IsAdmin != true)
            {
                return;
            }

            string objectName = go.name ?? "unknown";
            RecordAction(player, "Build", objectName, "");
        }

        private void OnEntityKill(BaseCombatEntity entity)
        {
            if (entity == null)
            {
                return;
            }

            BasePlayer? lastAttacker = entity.lastAttacker as BasePlayer;
            if (lastAttacker?.IsAdmin != true)
            {
                return;
            }

            RecordAction(lastAttacker, "Destroy", entity.ShortPrefabName, "");
        }

        private void OnPlayerAttack(BasePlayer? attacker, HitInfo? info)
        {
            if (attacker?.IsAdmin != true)
            {
                return;
            }

            BasePlayer? victim = info?.HitEntity as BasePlayer;
            if (victim != null)
            {
                RecordAction(attacker, "Attack", "Player", victim.displayName);
            }
        }

        private void OnPlayerInput(BasePlayer player, InputState input)
        {
            if (
                player == null
                || input == null
                || (
                    !player.IsAdmin
                    && !permission.UserHasPermission(player.UserIDString, PermissionTracked)
                )
            )
            {
                return;
            }

            if (!activeActions.TryGetValue(player.userID, out HashSet<string> actions))
            {
                actions = new HashSet<string>();
                activeActions[player.userID] = actions;
            }

            // Отслеживаем нажатия клавиш
            if (input.WasJustPressed(BUTTON.FIRE_PRIMARY))
            {
                _ = actions.Add("combat");
            }
            if (input.WasJustPressed(BUTTON.FIRE_SECONDARY))
            {
                _ = actions.Add("combat");
            }
            if (input.WasJustPressed(BUTTON.USE))
            {
                _ = actions.Add("interaction");
            }
            if (input.WasJustPressed(BUTTON.SPRINT))
            {
                _ = actions.Add("movement");
            }

            // Очищаем действия через небольшую задержку
            _ = timer.Once(
                1f,
                () =>
                {
                    if (actions.Count > 0)
                    {
                        actions.Clear();
                    }
                }
            );
        }

        private void OnUserPermissionGranted(string id, string permName)
        {
            if (permName != PermissionTracked)
            {
                return;
            }

            BasePlayer? player = BasePlayer.Find(id);
            if (player == null)
            {
                return;
            }

            // Начинаем отслеживание только если игрок не является админом
            if (!player.IsAdmin)
            {
                StartMonitoring(player);
                LoadPlayerAvatar(player);
                SendReply(player, "Вы добавлены в систему мониторинга активности.");
            }
        }

        private void OnUserPermissionRevoked(string id, string permName)
        {
            if (permName != PermissionTracked)
            {
                return;
            }

            BasePlayer? player = BasePlayer.Find(id);
            if (player == null)
            {
                return;
            }

            // Останавливаем отслеживание только если игрок не является админом
            if (!player.IsAdmin)
            {
                StopMonitoring(player);
                SendReply(player, "Вы удалены из системы мониторинга активности.");
            }
        }
        #endregion Oxide Hooks

        #region Core Functions
        private void StartMonitoring(BasePlayer player)
        {
            if (
                player == null
                || (
                    !player.IsAdmin
                    && !permission.UserHasPermission(player.UserIDString, PermissionTracked)
                )
            )
            {
                return;
            }

            // Если уже отслеживается - не начинаем заново
            if (activeAdmins.ContainsKey(player.userID))
            {
                return;
            }

            activeAdmins[player.userID] = new()
            {
                SessionStartTime = Time.realtimeSinceStartup,
                LastActiveTime = Time.realtimeSinceStartup,
                LastPosition = player.transform.position,
                IsAfk = false,
            };
            lastPositions[player.userID] = player.transform.position;
            lastActionTimes[player.userID] = Time.realtimeSinceStartup;

            activityTimers[player.userID] = timer.Every(checkInterval, () => CheckActivity(player));

            if (
                storedData?.AdminStatistics != null
                && !storedData.AdminStatistics.ContainsKey(player.userID)
            )
            {
                storedData.AdminStatistics[player.userID] = new AdminStats
                {
                    LastName = player.displayName,
                    LastActive = DateTime.Now,
                };
            }

            RecordAction(player, "Login", "Session Start", "");
        }

        private void StopMonitoring(BasePlayer player)
        {
            if (player == null)
            {
                return;
            }

            if (activityTimers.TryGetValue(player.userID, out Timer timer))
            {
                timer.Destroy();
                _ = activityTimers.Remove(player.userID);
            }

            if (activeAdmins.ContainsKey(player.userID))
            {
                AdminActivity activity = activeAdmins[player.userID];
                float sessionTime =
                    Time.realtimeSinceStartup - activity.SessionStartTime - activity.TotalAfkTime;

                if (
                    storedData?.AdminStatistics != null
                    && storedData.AdminStatistics.TryGetValue(player.userID, out AdminStats stats)
                )
                {
                    stats.TotalActiveTime += sessionTime;
                    stats.TotalAfkTime += activity.TotalAfkTime;
                    stats.LastActive = DateTime.Now;
                }

                _ = activeAdmins.Remove(player.userID);
            }

            _ = lastPositions.Remove(player.userID);
            _ = lastActionTimes.Remove(player.userID);

            RecordAction(player, "Logout", "Session End", "");
            SaveData();
        }

        private void CheckActivity(BasePlayer player)
        {
            if (player?.IsConnected != true || !activeAdmins.ContainsKey(player.userID))
            {
                return;
            }

            AdminActivity activity = activeAdmins[player.userID];
            Vector3 currentPos = player.transform.position;
            Quaternion currentRot = player.eyes.rotation;
            float currentTime = Time.realtimeSinceStartup;

            // Проверяем различные типы активности
            bool hasMoved = Vector3.Distance(activity.LastPosition, currentPos) > MovementThreshold;
            bool hasRotated =
                Quaternion.Angle(
                    lastRotations.TryGetValue(player.userID, out Quaternion lastRot)
                        ? lastRot
                        : currentRot,
                    currentRot
                ) > RotationThreshold;
            bool hasActiveActions =
                activeActions.TryGetValue(player.userID, out HashSet<string> actions)
                && actions.Count > 0;
            bool isInInventory = player.inventory.loot.IsLooting();
            bool isInCrafting = player.inventory.crafting.queue.Count > 0;

            bool isActive =
                hasMoved || hasRotated || hasActiveActions || isInInventory || isInCrafting;

            if (!isActive)
            {
                if (
                    !activity.IsAfk
                    && (currentTime - activity.LastActiveTime) >= inactivityThreshold
                )
                {
                    // Игрок только что стал AFK
                    activity.IsAfk = true;
                    activity.AfkStartTime = currentTime;
                    RecordAction(
                        player,
                        "Status",
                        "AFK Start",
                        GetAfkReason(
                            hasMoved,
                            hasRotated,
                            hasActiveActions,
                            isInInventory,
                            isInCrafting
                        )
                    );
                    SendDiscordStatusChangeMessage(player, true);

                    // Отправляем уведомление игроку
                    SendReply(player, "Вы перешли в режим AFK");
                }
            }
            else if (activity.IsAfk)
            {
                // Игрок вернулся из AFK
                float afkDuration = currentTime - activity.AfkStartTime;
                activity.TotalAfkTime += afkDuration;
                activity.IsAfk = false;
                activity.LastActiveTime = currentTime;
                RecordAction(player, "Status", "AFK End", $"Duration: {afkDuration:F1}s");
                SendDiscordStatusChangeMessage(player, false, afkDuration);
                SendReply(
                    player,
                    $"Вы вернулись из AFK режима.Время отсутствия: {FormatTime(afkDuration)}"
                );
            }

            // Обновляем последние известные позиции и время активности
            if (isActive)
            {
                activity.LastActiveTime = currentTime;
            }

            activity.LastPosition = currentPos;
            lastRotations[player.userID] = currentRot;
        }

        private string GetAfkReason(
            bool hasMoved,
            bool hasRotated,
            bool hasActiveActions,
            bool isInInventory,
            bool isInCrafting
        )
        {
            List<string> reasons = new();

            if (!hasMoved && !hasRotated)
            {
                reasons.Add("отсутствие движения");
            }
            if (!hasActiveActions)
            {
                reasons.Add("нет активных действий");
            }
            if (!isInInventory && !isInCrafting)
            {
                reasons.Add("нет взаимодействия с инвентарем");
            }

            return string.Join(", ", reasons);
        }

        private void UpdateAdminStats(BasePlayer? player, string type, string detail)
        {
            if (player == null || storedData == null || player.displayName == null)
            {
                return;
            }

            string displayName = player.displayName;
            ulong userId = player.userID;

            if (!storedData.AdminStatistics.TryGetValue(userId, out AdminStats stats))
            {
                stats = new AdminStats
                {
                    LastName = displayName,
                    CommandsUsed = new Dictionary<string, int>(),
                    RecentActions = new List<AdminAction>(),
                };
                storedData.AdminStatistics[userId] = stats;
            }

            if (type == "command")
            {
                if (!stats.CommandsUsed.TryGetValue(detail, out int count))
                {
                    count = 0;
                }
                stats.CommandsUsed[detail] = count + 1;
            }

            stats.LastActive = DateTime.Now;
            SaveAdminData(userId);
        }

        private void RecordAction(
            BasePlayer? player,
            string actionType,
            string details,
            string target
        )
        {
            if (
                player == null
                || storedData == null
                || player.displayName == null
                || player.transform == null
            )
            {
                return;
            }

            string displayName = player.displayName;
            ulong userId = player.userID;
            Vector3 position = player.transform.position;

            if (!storedData.AdminStatistics.TryGetValue(userId, out AdminStats stats))
            {
                stats = new AdminStats
                {
                    LastName = displayName,
                    RecentActions = new List<AdminAction>(),
                };
                storedData.AdminStatistics[userId] = stats;
            }

            AdminAction action = new()
            {
                ActionType = actionType,
                Details = details,
                Target = target,
                Timestamp = DateTime.Now,
                Location = position,
            };

            stats.RecentActions.Add(action);

            while (stats.RecentActions.Count > 100)
            {
                stats.RecentActions.RemoveAt(0);
            }

            SaveAdminData(userId);
        }

        private void SaveAdminStats(BasePlayer player)
        {
            if (storedData == null)
            {
                return;
            }

            if (!storedData.AdminStatistics.TryGetValue(player.userID, out AdminStats stats))
            {
                return;
            }

            if (activeAdmins.TryGetValue(player.userID, out AdminActivity activity))
            {
                float currentTime = Time.realtimeSinceStartup;
                float sessionDuration = currentTime - activity.SessionStartTime;
                float totalAfkTime = activity.TotalAfkTime;

                // Если игрок был AFK в момент выхода, добавляем последнее время AFK
                if (activity.IsAfk)
                {
                    totalAfkTime += currentTime - activity.AfkStartTime;
                }

                float activeTime = sessionDuration - totalAfkTime;

                if (activeTime > 0)
                {
                    stats.TotalActiveTime += activeTime;
                    stats.CurrentWipeActiveTime += activeTime;
                }

                if (totalAfkTime > 0)
                {
                    stats.TotalAfkTime += totalAfkTime;
                    stats.CurrentWipeAfkTime += totalAfkTime;
                }
            }

            stats.LastActive = DateTime.Now;
            SaveAdminData(player.userID);
        }

        private void UpdateAllAdminStats()
        {
            if (storedData == null)
            {
                return;
            }

            float currentTime = Time.realtimeSinceStartup;

            // Обновляем статистику для всех активных администраторов
            foreach (KeyValuePair<ulong, AdminActivity> kvp in activeAdmins)
            {
                if (storedData.AdminStatistics.TryGetValue(kvp.Key, out AdminStats stats))
                {
                    float sessionDuration = currentTime - kvp.Value.SessionStartTime;
                    float afkTime = kvp.Value.TotalAfkTime;
                    float activeTime;
                    if (kvp.Value.IsAfk)
                    {
                        // Если админ сейчас AFK:
                        // 1.Считаем активное время до начала AFK
                        activeTime =
                            kvp.Value.AfkStartTime
                            - kvp.Value.SessionStartTime
                            - kvp.Value.TotalAfkTime;
                        // 2.Добавляем текущее время AFK
                        afkTime += currentTime - kvp.Value.AfkStartTime;
                    }
                    else
                    {
                        // Если админ активен:
                        // Считаем чистое активное время (общее время минус все время AFK)
                        activeTime = sessionDuration - afkTime;
                    }

                    // Обновляем статистику только если есть изменения
                    if (activeTime > 0)
                    {
                        stats.CurrentWipeActiveTime += activeTime;
                        stats.TotalActiveTime += activeTime;
                    }

                    if (afkTime > 0)
                    {
                        stats.CurrentWipeAfkTime += afkTime;
                        stats.TotalAfkTime += afkTime;
                    }

                    // Сбрасываем счетчики сессии
                    kvp.Value.SessionStartTime = currentTime;
                    kvp.Value.TotalAfkTime = 0;
                    if (kvp.Value.IsAfk)
                    {
                        kvp.Value.AfkStartTime = currentTime;
                    }
                }
            }
        }
        #endregion Core Functions

        #region Commands
        private void CmdAdminMonitor(BasePlayer player, string command, string[] args)
        {
            if (!permission.UserHasPermission(player.UserIDString, PermissionView))
            {
                SendReply(player, "У вас нет прав для использования этой команды.");
                return;
            }

            if (args.Length == 0)
            {
                ShowAdminList(player);
                return;
            }

            switch (args[0].ToLower(CultureInfo.CurrentCulture))
            {
                case "check":
                    if (args.Length < 2)
                    {
                        SendReply(player, "Использование: /amonitor check <имя/id>");
                        return;
                    }
                    CheckAdminStats(player, args[1]);
                    break;

                case "reset":
                    if (!permission.UserHasPermission(player.UserIDString, PermissionAdmin))
                    {
                        SendReply(player, "У вас нет прав для сброса статистики.");
                        return;
                    }
                    ResetStats();
                    SendReply(player, "Статистика администраторов сброшена.");
                    break;

                default:
                    SendReply(
                        player,
                        "Доступные команды:\n/amonitor - список администраторов\n/amonitor check <имя/id> - проверить статистику\n/amonitor reset - сбросить статистику"
                    );
                    break;
            }
        }

        [ConsoleCommand("amonitor.check")]
        private void CmdCheckAdmin(ConsoleSystem.Arg arg)
        {
            if (
                arg.Player() != null
                && !permission.UserHasPermission(arg.Player().UserIDString, PermissionView)
            )
            {
                arg.ReplyWith("У вас нет прав для использования этой команды.");
                return;
            }

            if (!arg.HasArgs())
            {
                arg.ReplyWith("Использование: amonitor.check <steamid>");
                return;
            }

            if (!ulong.TryParse(arg.GetString(0), out ulong targetId))
            {
                arg.ReplyWith("Неверный формат SteamID.");
                return;
            }

            if (storedData?.AdminStatistics == null)
            {
                arg.ReplyWith("Ошибка: база данных не инициализирована.");
                return;
            }

            if (!storedData.AdminStatistics.TryGetValue(targetId, out AdminStats stats))
            {
                arg.ReplyWith("Статистика для указанного администратора не найдена.");
                return;
            }

            StringBuilder reply = new();
            _ = reply.AppendLine($"Статистика администратора: {stats.LastName}");
            float totalActiveTime = stats.TotalActiveTime;
            float totalAfkTime = stats.TotalAfkTime;

            if (activeAdmins.TryGetValue(targetId, out AdminActivity activity))
            {
                float currentTime = Time.realtimeSinceStartup;

                if (activity.IsAfk)
                {
                    // Если админ сейчас AFK:
                    // 1.Добавляем активное время до начала AFK
                    float activeTimeBeforeAfk =
                        activity.AfkStartTime - activity.SessionStartTime - activity.TotalAfkTime;
                    if (activeTimeBeforeAfk > 0)
                    {
                        totalActiveTime += activeTimeBeforeAfk;
                    }
                    // 2.Добавляем текущее время AFK
                    float currentAfkTime = currentTime - activity.AfkStartTime;
                    if (currentAfkTime > 0)
                    {
                        totalAfkTime += activity.TotalAfkTime + currentAfkTime;
                    }
                }
                else
                {
                    // Если админ активен:
                    // 1.Добавляем только активное время текущей сессии
                    float sessionDuration = currentTime - activity.SessionStartTime;
                    float activeTimeInSession = sessionDuration - activity.TotalAfkTime;
                    if (activeTimeInSession > 0)
                    {
                        totalActiveTime += activeTimeInSession;
                    }
                    // 2.Добавляем только накопленное время AFK (без текущего времени, т.к.админ активен)
                    totalAfkTime += activity.TotalAfkTime;
                }
            }

            _ = reply.AppendLine($"Активное время: {FormatTime(totalActiveTime)}");
            _ = reply.AppendLine($"Время AFK: {FormatTime(totalAfkTime)}");
            _ = reply.AppendLine($"Последняя активность: {FormatDateTime(stats.LastActive)}");

            if (stats.CommandsUsed.Count > 0)
            {
                _ = reply.AppendLine("\nИспользованные команды:");
                foreach (
                    KeyValuePair<string, int> cmd in stats.CommandsUsed.OrderByDescending(x => x.Value).Take(10)
                )
                {
                    _ = reply.AppendLine($"- {cmd.Key}: {cmd.Value} раз");
                }
            }

            if (stats.RecentActions.Count > 0)
            {
                _ = reply.AppendLine("\nПоследние действия:");
                foreach (AdminAction action in GetCachedRecentActions(targetId, stats, 5))
                {
                    _ = reply.AppendLine(
                        $"- [{action.Timestamp:HH:mm:ss}] {action.ActionType}: {action.Details} {(string.IsNullOrEmpty(action.Target) ? "" : $"-> {action.Target}")}"
                    );
                }
            }

            arg.ReplyWith(reply.ToString());
        }
        #endregion Commands

        #region Helper Functions
        private void ShowAdminList(BasePlayer player)
        {
            if (storedData == null)
            {
                return;
            }

            // Очищаем предыдущий таймер обновления UI если есть
            if (uiUpdateTimers.TryGetValue(player.userID, out Timer oldTimer))
            {
                oldTimer.Destroy();
                _ = uiUpdateTimers.Remove(player.userID);
            }

            // Создаем новый таймер для обновления UI каждую секунду
            uiUpdateTimers[player.userID] = timer.Every(
                1f,
                () =>
                {
                    if (!player.IsConnected)
                    {
                        if (uiUpdateTimers.TryGetValue(player.userID, out Timer timer))
                        {
                            timer.Destroy();
                            _ = uiUpdateTimers.Remove(player.userID);
                        }
                        return;
                    }

                    // Обновляем статистику перед обновлением UI
                    UpdateAllAdminStats();
                    // Обновляем UI
                    UpdateAdminListUI(player);
                }
            );

            // Показываем UI первый раз
            UpdateAllAdminStats();
            UpdateAdminListUI(player);
        }

        private void UpdateAdminListUI(BasePlayer player)
        {
            if (storedData == null || !player.IsConnected)
            {
                return;
            }

            UpdateAllAdminStats();
            _ = CuiHelper.DestroyUi(player, LayerMain);
            CuiElementContainer container = new();

            // Основная панель с размытым фоном и градиентом
            _ = container.Add(
                new CuiPanel
                {
                    CursorEnabled = true,
                    RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
                    Image =
                    {
                        Color = "0.235 0.227 0.204 0.95",
                        Material = "assets/content/ui/uibackgroundblur-ingame.mat",
                    },
                },
                "Overlay",
                LayerMain
            );

            // Добавляем стильный градиентный фон
            _ = container.Add(
                new CuiPanel
                {
                    RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
                    Image =
                    {
                        Color = "0.235 0.227 0.204 0.7",
                        Sprite = "assets/content/ui/ui.background.transparent.radial.psd",
                    },
                },
                LayerMain
            );

            // Центральная панель с тенью
            _ = container.Add(
                new CuiPanel
                {
                    RectTransform =
                    {
                        AnchorMin = "0.5 0.5",
                        AnchorMax = "0.5 0.5",
                        OffsetMin = "-500 -350",
                        OffsetMax = "500 350",
                    },
                    Image = { Color = "0.322 0.306 0.286 0" },
                },
                LayerMain,
                LayerContent
            );

            // Стильный заголовок с информацией о вайпе
            _ = container.Add(
                new CuiPanel
                {
                    RectTransform = { AnchorMin = "0 0.92", AnchorMax = "1 1" },
                    Image =
                    {
                        Color = "0.235 0.227 0.204 0.95",
                        Material = "assets/content/ui/uibackgroundblur-ingame.mat",
                    },
                },
                LayerContent,
                "Header"
            );

            // Добавляем статистику сервера
            int totalAdmins = storedData.AdminStatistics.Count;
            int onlineAdmins = storedData.AdminStatistics.Count(x =>
                BasePlayer.FindByID(x.Key)?.IsConnected == true
            );
            int afkAdmins = activeAdmins.Count(x => x.Value.IsAfk);

            _ = container.Add(
                new CuiLabel
                {
                    RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
                    Text =
                    {
                        Text =
                            "<size=24><color=#e67e22>⚡ Мониторинг администраторов ⚡</color></size>\n"
                            + $"<size=14>Вайп начался: <color=#3498db>{storedData.CurrentWipeStart:dd.MM.yyyy HH:mm}</color> | "
                            + $"Админов онлайн: <color=#2ecc71>{onlineAdmins}</color>/<color=#95a5a6>{totalAdmins}</color> | "
                            + $"AFK: <color=#e74c3c>{afkAdmins}</color></size>",
                        Font = "robotocondensed-bold.ttf",
                        Align = TextAnchor.MiddleCenter,
                    },
                },
                "Header"
            );

            // Панель фильтров с улучшенным дизайном
            _ = container.Add(
                new CuiPanel
                {
                    RectTransform = { AnchorMin = "0.05 0.85", AnchorMax = "0.95 0.91" },
                    Image =
                    {
                        Color = "0.15 0.15 0.15 0.98",
                        Material = "assets/content/ui/uibackgroundblur-ingame.mat",
                    },
                },
                LayerContent,
                "FilterPanel"
            );

            // Улучшенные кнопки фильтров с иконками
            AddFilterButton(
                container,
                "FilterPanel",
                "0.02 0.2",
                "0.15 0.8",
                "🔍 Все",
                FilterAll,
                player.userID.ToString()
            );
            AddFilterButton(
                container,
                "FilterPanel",
                "0.16 0.2",
                "0.29 0.8",
                "🟢 Онлайн",
                FilterOnline,
                player.userID.ToString()
            );
            AddFilterButton(
                container,
                "FilterPanel",
                "0.30 0.2",
                "0.43 0.8",
                "⭕ Оффлайн",
                FilterOffline,
                player.userID.ToString()
            );
            AddFilterButton(
                container,
                "FilterPanel",
                "0.44 0.2",
                "0.57 0.8",
                "💤 AFK",
                FilterAfk,
                player.userID.ToString()
            );

            // Кнопки сортировки с иконками
            AddFilterButton(
                container,
                "FilterPanel",
                "0.65 0.2",
                "0.78 0.8",
                "⏱ По активности",
                SortTimeActive,
                player.userID.ToString()
            );
            AddFilterButton(
                container,
                "FilterPanel",
                "0.79 0.2",
                "0.92 0.8",
                "⌛ По AFK",
                SortTimeAfk,
                player.userID.ToString()
            );

            // Получаем отфильтрованный список администраторов
            IEnumerable<KeyValuePair<ulong, AdminStats>> filteredAdmins = GetFilteredAdmins(
                player.userID.ToString(),
                storedData.AdminStatistics
            );

            // Список администраторов с улучшенными карточками
            float yOffset = 0.83f;
            foreach (KeyValuePair<ulong, AdminStats> admin in filteredAdmins)
            {
                BasePlayer? adminPlayer = BasePlayer.FindByID(admin.Key);
                bool isOnline = adminPlayer?.IsConnected == true;
                _ = activeAdmins.TryGetValue(admin.Key, out AdminActivity? currentActivity);

                string cardName = $"AdminCard_{admin.Key}";
                _ = container.Add(
                    new CuiPanel
                    {
                        RectTransform =
                        {
                            AnchorMin = $"0.05 {yOffset - 0.15}",
                            AnchorMax = $"0.95 {yOffset}",
                        },
                        Image =
                        {
                            Color = "0.15 0.15 0.15 0.98",
                            Material = "assets/content/ui/uibackgroundblur-ingame.mat",
                        },
                    },
                    LayerContent,
                    cardName
                );

                // Добавляем подсветку для онлайн статуса
                string borderColor = isOnline ? "0.18 0.8 0.44 0.3" : "0.5 0.5 0.5 0.1";
                _ = container.Add(
                    new CuiPanel
                    {
                        RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
                        Image = { Color = borderColor },
                    },
                    cardName,
                    $"{cardName}_border"
                );

                // Аватар с улучшенной рамкой
                if (ImageLibrary != null && avatarUrls.TryGetValue(admin.Key, out string avatarId))
                {
                    string? avatarImage = ImageLibrary.Call("GetImage", avatarId) as string;
                    if (!string.IsNullOrEmpty(avatarImage))
                    {
                        // Фоновый круг для аватара
                        _ = container.Add(
                            new CuiPanel
                            {
                                RectTransform = { AnchorMin = "0.02 0.1", AnchorMax = "0.08 0.9" },
                                Image =
                                {
                                    Color = "0.2 0.2 0.2 1",
                                    Sprite = "assets/content/ui/ui.circle.psd",
                                },
                            },
                            cardName
                        );

                        // Аватар
                        container.Add(
                            new CuiElement
                            {
                                Parent = cardName,
                                Components =
                                {
                                    new CuiRawImageComponent
                                    {
                                        Png = avatarImage,
                                        Color = "1 1 1 1",
                                    },
                                    new CuiRectTransformComponent
                                    {
                                        AnchorMin = "0.02 0.1",
                                        AnchorMax = "0.08 0.9",
                                    },
                                },
                            }
                        );
                    }
                }

                // Информация об администраторе с трендами
                float currentSessionTime = 0f;
                float currentAfkTime = 0f;
                if (currentActivity != null)
                {
                    currentSessionTime =
                        Time.realtimeSinceStartup
                        - currentActivity.SessionStartTime
                        - currentActivity.TotalAfkTime;
                    currentAfkTime = currentActivity.TotalAfkTime;
                }

                float totalActiveTime =
                    admin.Value.CurrentWipeActiveTime + (isOnline ? currentSessionTime : 0);
                float totalAfkTime =
                    admin.Value.CurrentWipeAfkTime + (isOnline ? currentAfkTime : 0);
                float activePercentage =
                    (totalActiveTime + totalAfkTime) > 0
                        ? totalActiveTime / (totalActiveTime + totalAfkTime) * 100
                        : 0;

                // Добавляем тренд активности
                string trendIcon = GetActivityTrendIcon(admin.Value);
                string trendColor = GetActivityTrendColor(admin.Value);

                _ = container.Add(
                    new CuiLabel
                    {
                        RectTransform = { AnchorMin = "0.1 0.5", AnchorMax = "0.4 0.9" },
                        Text =
                        {
                            Text =
                                $"<size=16>{admin.Value.LastName}</size>\n<size=12><color={trendColor}>{trendIcon}</color></size>",
                            Font = "robotocondensed-bold.ttf",
                            Align = TextAnchor.MiddleLeft,
                        },
                
// ... [Code preview truncated due to length. Full source in attached file] ...

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