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

FREE AdminStats : 2.1.0 2.1.0

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Game.Rust.Cui;
using UnityEngine;
using Network;

namespace Oxide.Plugins
{
    [Info("AdminStats", "romzar.games", "2.1.0")]
    [Description("Real-time server statistics HUD for administrators with drag & resize")]
    public class AdminStats : RustPlugin
    {
        #region Constants

        private const string PERMISSION_USE = "adminstats.use";
        private const string UI_MAIN = "AS_Main";
        private const string UI_INNER = "AS_Inner";
        private const string UI_CONTENT = "AS_Content";
        private const string UI_HANDLE_L = "AS_HL";
        private const string UI_HANDLE_R = "AS_HR";
        private const string UI_HANDLE_B = "AS_HB";

        #endregion

        #region Fields

        private Configuration _config;
        private Timer _updateTimer;
        private DateTime _serverStartTime;
        private readonly HashSet<ulong> _activeHuds = new HashSet<ulong>();
        private readonly Queue<int> _fpsHistory = new Queue<int>();
        private readonly Dictionary<ulong, PlayerData> _playerData = new Dictionary<ulong, PlayerData>();
        private readonly Dictionary<ulong, float> _lastDragTime = new Dictionary<ulong, float>();
        private readonly HashSet<ulong> _dirtyPlayers = new HashSet<ulong>();
        private CachedMetrics _cachedMetrics = new CachedMetrics();
        private bool _metricsChanged = true;

        #endregion

        #region Data Models

        private class PanelPosition
        {
            [JsonProperty("ox_min")]
            public float OxMin { get; set; } = 610f;

            [JsonProperty("ox_max")]
            public float OxMax { get; set; } = 910f;

            [JsonProperty("oy_min")]
            public float OyMin { get; set; } = -518f;

            [JsonProperty("oy_max")]
            public float OyMax { get; set; } = -302f;

            public PanelPosition Copy()
            {
                return new PanelPosition { OxMin = OxMin, OxMax = OxMax, OyMin = OyMin, OyMax = OyMax };
            }
        }

        private class PlayerData
        {
            [JsonProperty("position")]
            public PanelPosition Position { get; set; } = new PanelPosition();

            [JsonProperty("last_update")]
            public string LastUpdate { get; set; } = DateTime.UtcNow.ToString("O");
        }

        #endregion

        #region Data Persistence

        private PlayerData EnsurePlayerData(ulong userId)
        {
            if (_playerData.TryGetValue(userId, out var data))
                return data;

            data = LoadPlayerDataSafe(userId);
            if (data == null)
            {
                data = new PlayerData
                {
                    Position = _config.DefaultPosition.Copy()
                };
            }

            _playerData[userId] = data;
            return data;
        }

        private PlayerData LoadPlayerDataSafe(ulong userId)
        {
            try
            {
                return Interface.Oxide.DataFileSystem.ReadObject<PlayerData>($"{Name}/{userId}");
            }
            catch { return null; }
        }

        private void SavePlayerData(ulong userId)
        {
            if (!_playerData.TryGetValue(userId, out var data)) return;
            try
            {
                data.LastUpdate = DateTime.UtcNow.ToString("O");
                Interface.Oxide.DataFileSystem.WriteObject($"{Name}/{userId}", data);
            }
            catch (Exception ex)
            {
                PrintError($"Error saving data for {userId}: {ex.Message}");
            }
        }

        private void SaveAllDirty()
        {
            foreach (var userId in _dirtyPlayers)
                SavePlayerData(userId);
            _dirtyPlayers.Clear();
        }

        private PanelPosition GetPosition(ulong userId)
        {
            return EnsurePlayerData(userId).Position;
        }

        private static void ClampPanelSize(PanelPosition pos)
        {
            const float MIN_W = 120f;
            const float MIN_H = 80f;
            if (pos.OxMax - pos.OxMin < MIN_W)
            {
                float cx = (pos.OxMin + pos.OxMax) * 0.5f;
                pos.OxMin = cx - MIN_W * 0.5f;
                pos.OxMax = cx + MIN_W * 0.5f;
            }
            if (pos.OyMax - pos.OyMin < MIN_H)
                pos.OyMin = pos.OyMax - MIN_H;
        }

        private bool IsPlayerDragging(ulong userId)
        {
            return _lastDragTime.TryGetValue(userId, out var t) && Time.realtimeSinceStartup - t < _config.DragPauseDuration;
        }

        #endregion

        #region Cached Metrics

        private class CachedMetrics
        {
            public int ServerFps;
            public int PlayersOnline;
            public int MaxPlayers;
            public int EntityCount;
            public long MemoryMb;
            public string Uptime = "";
            public string NetworkIn = "";
            public string NetworkOut = "";

            public bool HasChanged(CachedMetrics o)
            {
                return o == null || ServerFps != o.ServerFps || PlayersOnline != o.PlayersOnline ||
                       EntityCount != o.EntityCount || MemoryMb != o.MemoryMb ||
                       Uptime != o.Uptime || NetworkIn != o.NetworkIn || NetworkOut != o.NetworkOut;
            }

            public void CopyFrom(CachedMetrics o)
            {
                if (o == null) return;
                ServerFps = o.ServerFps; PlayersOnline = o.PlayersOnline; MaxPlayers = o.MaxPlayers;
                EntityCount = o.EntityCount; MemoryMb = o.MemoryMb; Uptime = o.Uptime;
                NetworkIn = o.NetworkIn; NetworkOut = o.NetworkOut;
            }
        }

        #endregion

        #region Configuration

        private class Configuration
        {
            [JsonProperty("Update Interval (seconds)")]
            public float UpdateInterval { get; set; } = 1f;

            [JsonProperty("Background Color (R G B A)")]
            public string BackgroundColor { get; set; } = "0.06 0.06 0.08 0.55";

            [JsonProperty("Text Color (R G B A)")]
            public string TextColor { get; set; } = "0.9 0.9 0.9 1";

            [JsonProperty("Header Color (R G B A)")]
            public string HeaderColor { get; set; } = "0.2 0.6 0.9 1";

            [JsonProperty("FPS Good Color (R G B A)")]
            public string FpsGoodColor { get; set; } = "0.2 0.8 0.2 1";

            [JsonProperty("FPS Warning Color (R G B A)")]
            public string FpsWarningColor { get; set; } = "0.9 0.7 0.1 1";

            [JsonProperty("FPS Critical Color (R G B A)")]
            public string FpsCriticalColor { get; set; } = "0.9 0.2 0.2 1";

            [JsonProperty("Font Size")]
            public int FontSize { get; set; } = 11;

            [JsonProperty("Show FPS Graph")]
            public bool ShowFpsGraph { get; set; } = true;

            [JsonProperty("Minimal Mode (FPS only)")]
            public bool MinimalMode { get; set; } = false;

            [JsonProperty("Auto-enable for admins on connect")]
            public bool AutoEnableOnConnect { get; set; } = true;

            [JsonProperty("Default Panel Position")]
            public PanelPosition DefaultPosition { get; set; } = new PanelPosition();

            [JsonProperty("Minimal Mode Position")]
            public PanelPosition MinimalPosition { get; set; } = new PanelPosition
            {
                OxMin = 760f, OxMax = 910f, OyMin = -454f, OyMax = -302f
            };

            [JsonProperty("FPS Good Threshold")]
            public int FpsGoodThreshold { get; set; } = 25;

            [JsonProperty("FPS Warning Threshold")]
            public int FpsWarningThreshold { get; set; } = 15;

            [JsonProperty("FPS History Size")]
            public int FpsHistorySize { get; set; } = 30;

            [JsonProperty("Drag Pause Duration (seconds)")]
            public float DragPauseDuration { get; set; } = 2f;
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _config = Config.ReadObject<Configuration>() ?? new Configuration();
            }
            catch (Exception ex)
            {
                PrintError($"Error loading config: {ex.Message}");
                _config = new Configuration();
            }
            SaveConfig();
        }

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

        #endregion

        #region Localization

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["HUD.FPS"] = "FPS",
                ["HUD.Players"] = "Players",
                ["HUD.Entities"] = "Entities",
                ["HUD.Memory"] = "Memory",
                ["HUD.NetworkIn"] = "Net In",
                ["HUD.NetworkOut"] = "Net Out",
                ["HUD.Uptime"] = "Uptime",
                ["Command.Enabled"] = "AdminStats HUD <color=#00ff00>enabled</color>.",
                ["Command.Disabled"] = "AdminStats HUD <color=#ff0000>disabled</color>.",
                ["Command.ModeMinimal"] = "Switched to <color=#00ff00>minimal</color> mode.",
                ["Command.ModeFull"] = "Switched to <color=#00ff00>full</color> mode.",
                ["Command.Reset"] = "Panel position <color=#00ff00>reset</color>.",
                ["NoPermission"] = "You do not have permission to use this command."
            }, this);

            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["HUD.FPS"] = "FPS",
                ["HUD.Players"] = "Jogadores",
                ["HUD.Entities"] = "Entidades",
                ["HUD.Memory"] = "Memoria",
                ["HUD.NetworkIn"] = "Rede In",
                ["HUD.NetworkOut"] = "Rede Out",
                ["HUD.Uptime"] = "Uptime",
                ["Command.Enabled"] = "AdminStats HUD <color=#00ff00>ativado</color>.",
                ["Command.Disabled"] = "AdminStats HUD <color=#ff0000>desativado</color>.",
                ["Command.ModeMinimal"] = "Modo <color=#00ff00>minimal</color> ativado.",
                ["Command.ModeFull"] = "Modo <color=#00ff00>completo</color> ativado.",
                ["Command.Reset"] = "Posicao <color=#00ff00>resetada</color>.",
                ["NoPermission"] = "Sem permissao."
            }, this, "pt-BR");

            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["HUD.FPS"] = "FPS",
                ["HUD.Players"] = "Jugadores",
                ["HUD.Entities"] = "Entidades",
                ["HUD.Memory"] = "Memoria",
                ["HUD.NetworkIn"] = "Red In",
                ["HUD.NetworkOut"] = "Red Out",
                ["HUD.Uptime"] = "Tiempo",
                ["Command.Enabled"] = "AdminStats HUD <color=#00ff00>activado</color>.",
                ["Command.Disabled"] = "AdminStats HUD <color=#ff0000>desactivado</color>.",
                ["Command.ModeMinimal"] = "Modo <color=#00ff00>minimal</color> activado.",
                ["Command.ModeFull"] = "Modo <color=#00ff00>completo</color> activado.",
                ["Command.Reset"] = "Posicion <color=#00ff00>reseteada</color>.",
                ["NoPermission"] = "Sin permiso."
            }, this, "es-ES");
        }

        private string Lang(string key, string userId = null) => lang.GetMessage(key, this, userId);

        #endregion

        #region Oxide Hooks

        private void Init()
        {
            permission.RegisterPermission(PERMISSION_USE, this);
            _serverStartTime = DateTime.UtcNow;
        }

        private void OnServerInitialized()
        {
            float interval = _config.UpdateInterval + UnityEngine.Random.Range(-0.1f, 0.1f);
            _updateTimer = timer.Every(interval, UpdateAllHuds);

            if (_config.AutoEnableOnConnect)
            {
                foreach (var player in BasePlayer.activePlayerList)
                {
                    if (player != null && HasPermission(player))
                        EnableHud(player);
                }
            }
        }

        private void Unload()
        {
            _updateTimer?.Destroy();
            SaveAllDirty();
            foreach (var player in BasePlayer.activePlayerList)
            {
                if (player != null && player.IsConnected)
                    CuiHelper.DestroyUi(player, UI_MAIN);
            }
            _activeHuds.Clear();
            _playerData.Clear();
            _lastDragTime.Clear();
            _dirtyPlayers.Clear();
            _fpsHistory.Clear();
        }

        private void OnServerSave() => SaveAllDirty();

        private void OnPlayerConnected(BasePlayer player)
        {
            if (player == null) return;
            if (_config.AutoEnableOnConnect && HasPermission(player))
            {
                timer.Once(2f, () =>
                {
                    if (player != null && player.IsConnected && HasPermission(player))
                        EnableHud(player);
                });
            }
        }

        private void OnPlayerDisconnected(BasePlayer player, string reason)
        {
            if (player == null) return;
            var userId = (ulong)player.userID;
            _activeHuds.Remove(userId);
            _lastDragTime.Remove(userId);

            if (_dirtyPlayers.Remove(userId))
                SavePlayerData(userId);

            _playerData.Remove(userId);
        }

        #endregion

        #region Drag & Resize

        private void OnCuiDraggableDrag(BasePlayer player, string name, Vector3 position, CommunityEntity.DraggablePositionSendType type)
        {
            if (player == null) return;
            var userId = (ulong)player.userID;
            if (!_activeHuds.Contains(userId)) return;

            var pos = GetPosition(userId);
            _lastDragTime[userId] = Time.realtimeSinceStartup;
            _dirtyPlayers.Add(userId);

            switch (name)
            {
                case UI_MAIN:
                    pos.OxMin += position.x;
                    pos.OxMax += position.x;
                    pos.OyMin += position.y;
                    pos.OyMax += position.y;
                    break;

                case UI_HANDLE_L:
                    pos.OxMin += position.x;
                    ClampPanelSize(pos);
                    RebuildPanel(player);
                    break;

                case UI_HANDLE_R:
                    pos.OxMax += position.x;
                    ClampPanelSize(pos);
                    RebuildPanel(player);
                    break;

                case UI_HANDLE_B:
                    pos.OyMin += position.y;
                    ClampPanelSize(pos);
                    RebuildPanel(player);
                    break;
            }
        }

        private static CuiDraggableComponent CreateDraggable(CommunityEntity.DraggablePositionSendType rpc, bool limitToParent)
        {
            return new CuiDraggableComponent
            {
                LimitToParent = limitToParent,
                ParentLimitIndex = -1,
                DragAlpha = 0.75f,
                PositionRPC = rpc
            };
        }

        #endregion

        #region Commands

        [ChatCommand("adminstats")]
        private void CmdAdminStats(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!HasPermission(player))
            {
                SendReply(player, Lang("NoPermission", player.UserIDString));
                return;
            }

            if (args.Length > 0)
            {
                switch (args[0].ToLower())
                {
                    case "reset":
                        var resetId = (ulong)player.userID;
                        _playerData.Remove(resetId);
                        _dirtyPlayers.Remove(resetId);
                        try { Interface.Oxide.DataFileSystem.DeleteDataFile($"{Name}/{resetId}"); } catch { }
                        SendReply(player, Lang("Command.Reset", player.UserIDString));
                        if (_activeHuds.Contains(resetId)) RebuildPanel(player);
                        return;
                    case "mode":
                        _config.MinimalMode = !_config.MinimalMode;
                        SaveConfig();
                        SendReply(player, Lang(_config.MinimalMode ? "Command.ModeMinimal" : "Command.ModeFull", player.UserIDString));
                        foreach (var uid in _activeHuds)
                        {
                            var p = BasePlayer.FindByID(uid);
                            if (p != null && p.IsConnected) RebuildPanel(p);
                        }
                        return;
                }
            }

            if (_activeHuds.Contains(player.userID))
            {
                DisableHud(player);
                SendReply(player, Lang("Command.Disabled", player.UserIDString));
            }
            else
            {
                EnableHud(player);
                SendReply(player, Lang("Command.Enabled", player.UserIDString));
            }
        }

        [ConsoleCommand("adminstats.toggle")]
        private void CmdToggle(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;
            CmdAdminStats(player, "adminstats", new string[0]);
        }

        #endregion

        #region Core

        private bool HasPermission(BasePlayer player)
        {
            return player != null && permission.UserHasPermission(player.UserIDString, PERMISSION_USE);
        }

        private void EnableHud(BasePlayer player)
        {
            if (player == null || !player.IsConnected) return;
            _activeHuds.Add(player.userID);
            _metricsChanged = true;
            RebuildPanel(player);
        }

        private void DisableHud(BasePlayer player)
        {
            if (player == null) return;
            _activeHuds.Remove(player.userID);
            CuiHelper.DestroyUi(player, UI_MAIN);
        }

        private void RebuildPanel(BasePlayer player)
        {
            CuiHelper.DestroyUi(player, UI_MAIN);
            BuildPanel(player);
        }

        private void UpdateAllHuds()
        {
            var newMetrics = CollectMetrics();
            UpdateFpsHistory(newMetrics.ServerFps);

            if (!_cachedMetrics.HasChanged(newMetrics) && _fpsHistory.Count == _config.FpsHistorySize)
                return;

            _cachedMetrics.CopyFrom(newMetrics);
            _metricsChanged = true;

            foreach (var userId in _activeHuds)
            {
                if (IsPlayerDragging(userId)) continue;
                var player = BasePlayer.FindByID(userId);
                if (player != null && player.IsConnected)
                    RefreshContent(player);
            }

            _metricsChanged = false;
        }

        private void RefreshContent(BasePlayer player)
        {
            CuiHelper.DestroyUi(player, UI_CONTENT);
            var pos = GetPosition(player.userID);
            var container = BuildContentElements(player.UserIDString, pos);
            CuiHelper.AddUi(player, container);
        }

        private CachedMetrics CollectMetrics()
        {
            return new CachedMetrics
            {
                ServerFps = Performance.current.frameRate,
                PlayersOnline = BasePlayer.activePlayerList.Count,
                MaxPlayers = ConVar.Server.maxplayers,
                EntityCount = BaseNetworkable.serverEntities.Count,
                MemoryMb = GC.GetTotalMemory(false) / (1024 * 1024),
                Uptime = FormatUptime(DateTime.UtcNow - _serverStartTime),
                NetworkIn = GetNetworkStat(true),
                NetworkOut = GetNetworkStat(false)
            };
        }

        private string GetNetworkStat(bool incoming)
        {
            try
            {
                if (Network.Net.sv == null) return "N/A";
                ulong bytes = incoming
                    ? Network.Net.sv.GetStat(null, BaseNetwork.StatTypeLong.BytesReceived_LastSecond)
                    : Network.Net.sv.GetStat(null, BaseNetwork.StatTypeLong.BytesSent_LastSecond);
                return FormatBytes(bytes);
            }
            catch { return "N/A"; }
        }

        private void UpdateFpsHistory(int fps)
        {
            _fpsHistory.Enqueue(fps);
            while (_fpsHistory.Count > _config.FpsHistorySize) _fpsHistory.Dequeue();
        }

        #endregion

        #region Build Panel

        private void BuildPanel(BasePlayer player)
        {
            var pos = GetPosition(player.userID);
            var container = new CuiElementContainer();

            // Main draggable panel (no cursor - works when inventory/map open)
            var mainElement = new CuiElement
            {
                Name = UI_MAIN,
                Parent = "Overlay",
                Components =
                {
                    new CuiImageComponent
                    {
                        Color = _config.BackgroundColor,
                        Material = "assets/content/ui/uibackgroundblur.mat"
                    },
                    new CuiRectTransformComponent
                    {
                        AnchorMin = "0.5 0.5",
                        AnchorMax = "0.5 0.5",
                        OffsetMin = $"{pos.OxMin} {pos.OyMin}",
                        OffsetMax = $"{pos.OxMax} {pos.OyMax}"
                    }
                }
            };
            mainElement.Components.Add(CreateDraggable(CommunityEntity.DraggablePositionSendType.Relative, true));
            container.Add(mainElement);

            // Border
            container.Add(new CuiPanel
            {
                Image = { Color = "0.30 0.30 0.35 0.3" },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "-1 -1", OffsetMax = "1 1" }
            }, UI_MAIN, "AS_Border");

            container.Add(new CuiPanel
            {
                Image = { Color = "0.06 0.06 0.08 0.6" },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
            }, "AS_Border", UI_INNER);

            // Resize handles (always present, same pattern as EventCooldowns)

            // Left handle
            var handleLeft = new CuiElement
            {
                Name = UI_HANDLE_L,
                Parent = UI_INNER,
                Components =
                {
                    new CuiImageComponent { Color = "0.35 0.35 0.42 0.50" },
                    new CuiRectTransformComponent
                    {
                        AnchorMin = "0 0.05",
                        AnchorMax = "0 0.95",
                        OffsetMin = "-12 0",
                        OffsetMax = "0 0"
                    }
                }
            };
            handleLeft.Components.Add(CreateDraggable(CommunityEntity.DraggablePositionSendType.Relative, false));
            container.Add(handleLeft);
            container.Add(new CuiLabel
            {
                Text = { Text = "◄", FontSize = 10, Align = TextAnchor.MiddleCenter, Color = "0.85 0.85 0.90 0.80" },
                RectTransform = { AnchorMin = "0 0.4", AnchorMax = "1 0.6" }
            }, UI_HANDLE_L);

            // Right handle
            var handleRight = new CuiElement
            {
                Name = UI_HANDLE_R,
                Parent = UI_INNER,
                Components =
                {
                    new CuiImageComponent { Color = "0.35 0.35 0.42 0.50" },
                    new CuiRectTransformComponent
                    {
                        AnchorMin = "1 0.05",
                        AnchorMax = "1 0.95",
                        OffsetMin = "0 0",
                        OffsetMax = "12 0"
                    }
                }
            };
            handleRight.Components.Add(CreateDraggable(CommunityEntity.DraggablePositionSendType.Relative, false));
            container.Add(handleRight);
            container.Add(new CuiLabel
            {
                Text = { Text = "►", FontSize = 10, Align = TextAnchor.MiddleCenter, Color = "0.85 0.85 0.90 0.80" },
                RectTransform = { AnchorMin = "0 0.4", AnchorMax = "1 0.6" }
            }, UI_HANDLE_R);

            // Bottom handle
            var handleBottom = new CuiElement
            {
                Name = UI_HANDLE_B,
                Parent = UI_INNER,
                Components =
                {
                    new CuiImageComponent { Color = "0.35 0.35 0.42 0.50" },
                    new CuiRectTransformComponent
                    {
                        AnchorMin = "0.05 0",
                        AnchorMax = "0.95 0",
                        OffsetMin = "0 -12",
                        OffsetMax = "0 0"
                    }
                }
            };
            handleBottom.Components.Add(CreateDraggable(CommunityEntity.DraggablePositionSendType.Relative, false));
            container.Add(handleBottom);
            container.Add(new CuiLabel
            {
                Text = { Text = "▼", FontSize = 10, Align = TextAnchor.MiddleCenter, Color = "0.85 0.85 0.90 0.80" },
                RectTransform = { AnchorMin = "0.4 0", AnchorMax = "0.6 1" }
            }, UI_HANDLE_B);

            CuiHelper.AddUi(player, container);

            // Build content separately (for partial refresh)
            var contentContainer = BuildContentElements(player.UserIDString, pos);
            CuiHelper.AddUi(player, contentContainer);
        }

        private CuiElementContainer BuildContentElements(string userId, PanelPosition pos)
        {
            var container = new CuiElementContainer();

            container.Add(new CuiPanel
            {
                Image = { Color = "0 0 0 0" },
                RectTransform = { AnchorMin = "0.03 0.03", AnchorMax = "0.97 0.97" }
            }, UI_INNER, UI_CONTENT);

            if (_config.MinimalMode)
            {
                string fpsColor = GetFpsColor(_cachedMetrics.ServerFps);
                container.Add(new CuiLabel
                {
                    Text =
                    {
                        Text = $"<color=#{ColorToHex(_config.HeaderColor)}>FPS</color> <color=#{ColorToHex(fpsColor)}>{_cachedMetrics.ServerFps}</color>",
                        FontSize = _config.FontSize + 2,
                        Align = TextAnchor.MiddleCenter,
                        Color = _config.TextColor
                    },
                    RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
                }, UI_CONTENT);
                return container;
            }

            // Full mode stats
            float yPos = 0.97f;
            float rowH = 0.115f;

            string fpsC = GetFpsColor(_cachedMetrics.ServerFps);
            AddRow(container, userId, Lang("HUD.FPS", userId),
                $"<color=#{ColorToHex(fpsC)}>{_cachedMetrics.ServerFps}</color>", ref yPos, rowH);

            if (_config.ShowFpsGraph && _fpsHistory.Count > 0)
            {
                BuildFpsGraph(container, yPos);
                yPos -= 0.07f;
            }

            AddRow(container, userId, Lang("HUD.Players", userId),
                $"{_cachedMetrics.PlayersOnline}/{_cachedMetrics.MaxPlayers}", ref yPos, rowH);

            AddRow(container, userId, Lang("HUD.Entities", userId),
                FormatNumber(_cachedMetrics.EntityCount), ref yPos, rowH);

            AddRow(container, userId, Lang("HUD.Memory", userId),
                $"{_cachedMetrics.MemoryMb} MB", ref yPos, rowH);

            AddRow(container, userId, Lang("HUD.NetworkIn", userId),
                _cachedMetrics.NetworkIn, ref yPos, rowH);

            AddRow(container, userId, Lang("HUD.NetworkOut", userId),
                _cachedMetrics.NetworkOut, ref yPos, rowH);

            AddRow(container, userId, Lang("HUD.Uptime", userId),
                _cachedMetrics.Uptime, ref yPos, rowH);

            return container;
        }

        private void AddRow(CuiElementContainer container, string userId, string label, string value, ref float yPos, float h)
        {
            container.Add(new CuiLabel
            {
                Text = { Text = label, FontSize = _config.FontSize, Align = TextAnchor.MiddleLeft, Color = "0.7 0.7 0.7 1" },
                RectTransform = { AnchorMin = $"0.02 {yPos - h}", AnchorMax = $"0.48 {yPos}" }
            }, UI_CONTENT);

            container.Add(new CuiLabel
            {
                Text = { Text = value, FontSize = _config.FontSize, Align = TextAnchor.MiddleRight, Color = _config.TextColor },
                RectTransform = { AnchorMin = $"0.50 {yPos - h}", AnchorMax = $"0.98 {yPos}" }
            }, UI_CONTENT);

            yPos -= h;
        }

        private void BuildFpsGraph(CuiElementContainer container, float yPos)
        {
            if (_fpsHistory.Count == 0) return;

            float gH = 0.06f;
            container.Add(new CuiPanel
            {
                Image = { Color = "0.05 0.05 0.05 0.8" },
                RectTransform = { AnchorMin = $"0.35 {yPos - gH}", AnchorMax = $"0.98 {yPos}" }
            }, UI_CONTENT, "AS_Graph");

            var arr = _fpsHistory.ToArray();
            float bw = 1f / _config.FpsHistorySize;

            for (int i = 0; i < arr.Length; i++)
            {
                float nh = Mathf.Max(Mathf.Clamp01(arr[i] / 60f), 0.05f);
                float xMin = i * bw + 0.01f;
                float xMax = (i + 1) * bw - 0.01f;

                container.Add(new CuiPanel
                {
                    Image = { Color = GetFpsColor(arr[i]) },
                    RectTransform = { AnchorMin = $"{xMin} 0.05", AnchorMax = $"{xMax} {nh * 0.9 + 0.05}" }
                }, "AS_Graph");
            }
        }

        #endregion

        #region Helpers

        private string FormatUptime(TimeSpan t)
        {
            if (t.TotalDays >= 1) return $"{(int)t.TotalDays}d {t.Hours}h";
            if (t.TotalHours >= 1) return $"{(int)t.TotalHours}h {t.Minutes}m";
            return $"{t.Minutes}m {t.Seconds}s";
        }

        private string FormatBytes(ulong b)
        {
            if (b >= 1048576) return $"{b / 1048576.0:F1} MB/s";
            if (b >= 1024) return $"{b / 1024.0:F1} KB/s";
            return $"{b} B/s";
        }

        private string FormatNumber(int n)
        {
            if (n >= 1000000) return $"{n / 1000000.0:F1}M";
            if (n >= 1000) return $"{n / 1000.0:F1}K";
            return n.ToString();
        }

        private string GetFpsColor(int fps)
        {
            if (fps >= _config.FpsGoodThreshold) return _config.FpsGoodColor;
            if (fps >= _config.FpsWarningThreshold) return _config.FpsWarningColor;
            return _config.FpsCriticalColor;
        }

        private string ColorToHex(string rgba)
        {
            if (string.IsNullOrEmpty(rgba)) return "FFFFFF";
            var p = rgba.Split(' ');
            if (p.Length < 3) return "FFFFFF";
            try
            {
                int r = (int)(float.Parse(p[0], CultureInfo.InvariantCulture) * 255);
                int g = (int)(float.Parse(p[1], CultureInfo.InvariantCulture) * 255);
                int b = (int)(float.Parse(p[2], CultureInfo.InvariantCulture) * 255);
                return $"{r:X2}{g:X2}{b:X2}";
            }
            catch { return "FFFFFF"; }
        }

        #endregion
    }
}

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