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

FREE ZenPanel : 1.0.1 1.0.1

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;
using Network;
using System.Globalization;
using UnityEngine.UI;
#if CARBON
using Carbon;
using Carbon.Base;
using Carbon.Components;
using Carbon.Modules;
using Carbon.Plugins;
#else
using Oxide.Ext.CarbonAliases;
#endif
namespace Oxide.Plugins {
    [Info("ZenPanel", "BradCopp", "1.0.1")]
    [Description("Core panel/shared features for my Zen UI plugins.")]
#if CARBON
    public class ZenPanel : CarbonPlugin
#else
    public class ZenPanel : RustPlugin
#endif
    {
        private const string TabIdMail = "mail";
        private const string TabIdTickets = "tickets";
#if !CARBON
        public static CUI.Handler CuiHandler = new();
#endif
#region config
        private Configuration _config;
        private class Configuration {
            [JsonProperty("UI - Tabs In Order (omit to hide)")]
            public List<string> TabOrder = new List<string> {
                TabIdMail, TabIdTickets
            }
            ;
            [JsonProperty("Notification Icon Image URL (HUD bell)")]
            public string HudIconUrl = "https://i.ibb.co/FkBKbkVc/bell.png";
            [JsonProperty("UI - Default Theme (Dark/Light)")]
            public string DefaultTheme = "Dark";
            [JsonProperty("UI - Dark Theme")]
            public UiPaletteConfig DarkTheme = UiPaletteConfig.CreateDark();
            [JsonProperty("UI - Light Theme")]
            public UiPaletteConfig LightTheme = UiPaletteConfig.CreateLight();
            [JsonProperty("UI - HUD Anchor Min")]
            public string HudAnchorMin = "0.5 0.00";
            [JsonProperty("UI - HUD Anchor Max")]
            public string HudAnchorMax = "0.5 0.00";
            [JsonProperty("UI - HUD Offset Min (px)")]
            public string HudOffsetMin = "-326 4";
            [JsonProperty("UI - HUD Offset Max (px)")]
            public string HudOffsetMax = "-294 36";
            [JsonProperty("UI - HUD Unread Counter Font Size")]
            public int HudUnreadFontSize = 15;
            [JsonProperty("UI - HUD Unread Counter Text Color")]
            public string HudUnreadTextColor = "0.98 0.99 1.00 1.00";
            [JsonProperty("UI - HUD Unread Counter Shadow Color")]
            public string HudUnreadTextShadowColor = "0.04 0.05 0.07 0.98";
            [JsonProperty("Timestamp Offset (minutes)")]
            public int TimestampOffsetMinutes = 0;
        }
        private class UiPaletteConfig {
            [JsonProperty("Overlay")]
            public string Overlay;
            [JsonProperty("Window")]
            public string Window;
            [JsonProperty("Header")]
            public string Header;
            [JsonProperty("Panel")]
            public string Panel;
            [JsonProperty("PanelAlt")]
            public string PanelAlt;
            [JsonProperty("Card")]
            public string Card;
            [JsonProperty("CardAlt")]
            public string CardAlt;
            [JsonProperty("Border")]
            public string Border;
            [JsonProperty("BorderStrong")]
            public string BorderStrong;
            [JsonProperty("Text")]
            public string Text;
            [JsonProperty("TextMuted")]
            public string TextMuted;
            [JsonProperty("Accent")]
            public string Accent;
            [JsonProperty("AccentSoft")]
            public string AccentSoft;
            [JsonProperty("AccentText")]
            public string AccentText;
            [JsonProperty("Success")]
            public string Success;
            [JsonProperty("SuccessSoft")]
            public string SuccessSoft;
            [JsonProperty("Warning")]
            public string Warning;
            [JsonProperty("WarningSoft")]
            public string WarningSoft;
            [JsonProperty("Danger")]
            public string Danger;
            [JsonProperty("DangerSoft")]
            public string DangerSoft;
            [JsonProperty("Offline")]
            public string Offline;
            [JsonProperty("OfflineSoft")]
            public string OfflineSoft;
            [JsonProperty("Input")]
            public string Input;
            [JsonProperty("AvatarFill")]
            public string AvatarFill;
            public static UiPaletteConfig CreateDark() {
                return new UiPaletteConfig {
                    Overlay = "0.03 0.04 0.06 0.78", Window = "0.05 0.08 0.12 0.985", Header = "0.06 0.09 0.14 1.00", Panel = "0.07 0.10 0.16 1.00", PanelAlt = "0.08 0.11 0.17 1.00", Card = "0.11 0.15 0.22 1.00", CardAlt = "0.09 0.13 0.20 1.00", Border = "0.16 0.21 0.31 1.00", BorderStrong = "0.26 0.34 0.48 1.00", Text = "0.92 0.95 0.98 1.00", TextMuted = "0.60 0.67 0.76 1.00", Accent = "0.45 0.66 1.00 1.00", AccentSoft = "0.18 0.25 0.39 1.00", AccentText = "1.00 1.00 1.00 1.00", Success = "0.29 0.77 0.48 1.00", SuccessSoft = "0.12 0.24 0.17 1.00", Warning = "0.92 0.72 0.33 1.00", WarningSoft = "0.28 0.19 0.06 1.00", Danger = "0.92 0.42 0.42 1.00", DangerSoft = "0.28 0.11 0.11 1.00", Offline = "0.54 0.61 0.70 1.00", OfflineSoft = "0.16 0.19 0.25 1.00", Input = "0.09 0.13 0.20 1.00", AvatarFill = "0.08 0.11 0.18 1.00"
                }
                ;
            }
            public static UiPaletteConfig CreateLight() {
                return new UiPaletteConfig {
                    Overlay = "0.10 0.14 0.20 0.12", Window = "0.87 0.90 0.94 0.985", Header = "0.84 0.88 0.93 1.00", Panel = "0.86 0.89 0.93 1.00", PanelAlt = "0.83 0.87 0.92 1.00", Card = "0.92 0.94 0.97 1.00", CardAlt = "0.89 0.92 0.96 1.00", Border = "0.69 0.76 0.85 1.00", BorderStrong = "0.56 0.66 0.82 1.00", Text = "0.16 0.20 0.25 1.00", TextMuted = "0.41 0.48 0.58 1.00", Accent = "0.30 0.51 0.92 1.00", AccentSoft = "0.80 0.87 0.98 1.00", AccentText = "1.00 1.00 1.00 1.00", Success = "0.24 0.62 0.36 1.00", SuccessSoft = "0.83 0.93 0.86 1.00", Warning = "0.79 0.58 0.18 1.00", WarningSoft = "0.94 0.90 0.80 1.00", Danger = "0.79 0.34 0.34 1.00", DangerSoft = "0.95 0.86 0.86 1.00", Offline = "0.55 0.62 0.70 1.00", OfflineSoft = "0.86 0.90 0.95 1.00", Input = "0.96 0.97 0.99 1.00", AvatarFill = "0.86 0.90 0.96 1.00"
                }
                ;
            }
        }
        private static bool NormalizeTabOrder(ref List<string> list) {
            if (list == null) {
                list = new List<string>();
                return true;
            }
            bool changed = false;
            var cleaned = new List<string>(list.Count);
            var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            for (int i = 0; i < list.Count; i++) {
                var raw = list[i];
                var value = raw == null ? string.Empty : raw.Trim().ToLowerInvariant();
                if (value.Length == 0) {
                    changed = true;
                    continue;
                }
                if (!seen.Add(value)) {
                    changed = true;
                    continue;
                }
                if (!string.Equals(raw, value, StringComparison.Ordinal)) changed = true;
                cleaned.Add(value);
            }
            if (cleaned.Count != list.Count) changed = true;
            list = cleaned;
            return changed;
        }
        protected override void LoadDefaultConfig() {
            _config = new Configuration();
            _config.TabOrder = new List<string> {
                TabIdMail, TabIdTickets
            }
            ;
            _config.HudAnchorMin = "0.5 0.00";
            _config.HudAnchorMax = "0.5 0.00";
            _config.HudOffsetMin = "-326 4";
            _config.HudOffsetMax = "-294 36";
            _config.TimestampOffsetMinutes = 0;
            SaveConfig();
        }
        protected override void LoadConfig() {
            base.LoadConfig();
            bool changed = false;
            try {
                _config = Config.ReadObject<Configuration>();
                if (_config == null) throw new Exception("Config is null");
            }
            catch (Exception ex) {
                PrintWarning($"Config corrupted or invalid; loading defaults. ({ex.Message})");
                LoadDefaultConfig();
                return;
            }
            if (_config.DarkTheme == null) {
                _config.DarkTheme = UiPaletteConfig.CreateDark();
                changed = true;
            }
            if (_config.LightTheme == null) {
                _config.LightTheme = UiPaletteConfig.CreateLight();
                changed = true;
            }
            if (string.IsNullOrWhiteSpace(_config.DefaultTheme)) {
                _config.DefaultTheme = "Dark";
                changed = true;
            }
            if (string.IsNullOrWhiteSpace(_config.HudUnreadTextColor)) {
                _config.HudUnreadTextColor = "0.98 0.99 1.00 1.00";
                changed = true;
            }
            if (string.IsNullOrWhiteSpace(_config.HudUnreadTextShadowColor)) {
                _config.HudUnreadTextShadowColor = "0.04 0.05 0.07 0.98";
                changed = true;
            }
            if (_config.HudUnreadTextColor == "0.09 0.11 0.14 1.00" && _config.HudUnreadTextShadowColor == "0.96 0.97 0.99 0.95") {
                _config.HudUnreadTextColor = "0.98 0.99 1.00 1.00";
                _config.HudUnreadTextShadowColor = "0.04 0.05 0.07 0.98";
                changed = true;
            }
            changed |= NormalizeTabOrder(ref _config.TabOrder);
            if (changed) SaveConfig();
        }
        protected override void SaveConfig() => Config.WriteObject(_config, true);
#endregion
#region perms and commands
        private const string PermAdmin = "zenpanel.admin";
        [ChatCommand("zp")]
        private void CmdCommunicationCenter(BasePlayer player, string command, string[] args) {
            if (player == null || !player.IsConnected) return;
            OpenMainUi(player);
        }
        [ChatCommand("notif_iconpos")]
        private void CmdHudPosEditor(BasePlayer player, string command, string[] args) {
            if (player == null) return;
            if (!HasPerm(player, PermAdmin)) {
                ReplyL(player, LangNoPermission);
                return;
            }
            OpenHudEditor(player);
        }
#endregion
#region theme state
        private enum UiThemeMode {
            Dark, Light
        }
        private readonly Dictionary<ulong, UiThemeMode> _uiThemes = new Dictionary<ulong, UiThemeMode>();
        private UiThemeMode ParseTheme(string raw) {
            if (!string.IsNullOrWhiteSpace(raw) && raw.Equals("Light", StringComparison.OrdinalIgnoreCase)) return UiThemeMode.Light;
            return UiThemeMode.Dark;
        }
        private UiThemeMode GetThemeMode(ulong userId) {
            UiThemeMode mode;
            if (!_uiThemes.TryGetValue(userId, out mode)) {
                mode = ParseTheme(_config.DefaultTheme);
                _uiThemes[userId] = mode;
            }
            return mode;
        }
        private UiPaletteConfig GetPalette(BasePlayer player) {
            return GetThemeMode(player.userID) == UiThemeMode.Light ? _config.LightTheme : _config.DarkTheme;
        }
        private string GetThemeToggleText(BasePlayer player) {
            return GetThemeMode(player.userID) == UiThemeMode.Dark ? Lang(LangUiThemeLight, player) : Lang(LangUiThemeDark, player);
        }
        [ConsoleCommand("pn.theme")]
        private void CcmdTheme(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            _uiThemes[player.userID] = GetThemeMode(player.userID) == UiThemeMode.Dark ? UiThemeMode.Light : UiThemeMode.Dark;
            OpenMainUi(player);
        }
        private const string LangUiTitle = "Ui.Title";
        private const string LangUiThemeDark = "Ui.ThemeDark";
        private const string LangUiThemeLight = "Ui.ThemeLight";
        private const string LangUiNotice = "Ui.Notice";
        private const string LangUiRefresh = "Ui.Refresh";
        private const string LangNoPermission = "NoPermission";
        private const string LangUiNoSections = "Ui.NoSections";
        private const string LangUiNoTabProvider = "Ui.NoTabProvider";
        private const string LangUiRenderFailed = "Ui.RenderFailed";
        private const string LangUiNoRenderHook = "Ui.NoRenderHook";
#endregion
#region ui helpers
        private string CreateFullscreenRoot(LUI ui, string name, string overlayColor, bool cursor = false) {
            AddPanel(ui, "Overlay", name, "0 0", "1 1", overlayColor, cursor : cursor);
            return name;
        }
        private float GetMainTabButtonWidth(string text) {
            int len = string.IsNullOrWhiteSpace(text) ? 6 : text.Trim().Length;
            float width = 0.05f + (len * 0.008f);
            return Mathf.Clamp(width, 0.07f, 0.15f);
        }
        private string CreatePixelWindow(LUI ui, string parent, string name, float widthPx, float heightPx, string borderColor, string fillColor, float borderSize = 2f) {
            float halfW = Mathf.Max(1f, widthPx) * 0.5f;
            float halfH = Mathf.Max(1f, heightPx) * 0.5f;
            AddBorderBox(ui, parent, name, "0.5 0.5", "0.5 0.5", borderColor, fillColor, borderSize, $"{-halfW:0.#} {-halfH:0.#}", $"{halfW:0.#} {halfH:0.#}");
            return name + ".inner";
        }
        private void AddPanel(LUI ui, string parent, string name, string anchorMin, string anchorMax, string color, string offsetMin = "0 0", string offsetMax = "0 0", bool cursor = false) {
            var cont = ui.CreatePanel(parent, ToLuiPosition(anchorMin, anchorMax), ToLuiOffset(offsetMin, offsetMax), color, name);
            if (cursor && cont != null) cont.AddCursor();
        }
        private void AddBorderBox(LUI ui, string parent, string name, string anchorMin, string anchorMax, string borderColor, string fillColor, float borderSize, string offsetMin = "0 0", string offsetMax = "0 0") {
            AddPanel(ui, parent, name, anchorMin, anchorMax, borderColor, offsetMin, offsetMax);
            string inner = name + ".inner";
            string inOffMin = $"{borderSize:0.#} {borderSize:0.#}";
            string inOffMax = $"{-borderSize:0.#} {-borderSize:0.#}";
            AddPanel(ui, name, inner, "0 0", "1 1", fillColor, inOffMin, inOffMax);
        }
        private void AddLabel(LUI ui, string parent, string text, int size, TextAnchor align, string color, string anchorMin, string anchorMax, string offsetMin = "0 0", string offsetMax = "0 0") {
            ui.CreateText(parent, ToLuiPosition(anchorMin, anchorMax), ToLuiOffset(offsetMin, offsetMax), size, color, text ?? string.Empty, align);
        }
        private void AddTransparentButton(LUI ui, string parent, string command, string anchorMin, string anchorMax) {
            if (string.IsNullOrWhiteSpace(parent) || string.IsNullOrWhiteSpace(command)) return;
            ui.CreateButton(parent, ToLuiPosition(anchorMin, anchorMax), LuiOffset.None, command, LuiColors.Transparent, false);
        }
        private void AddBorderButton(LUI ui, string parent, string text, int fontSize, string borderColor, string fillColor, string textColor, string command, string anchorMin, string anchorMax) {
            string outer = CuiHelper.GetGuid();
            AddBorderBox(ui, parent, outer, anchorMin, anchorMax, borderColor, fillColor, 1f);
            string inner = outer + ".inner";
            AddLabel(ui, inner, text, fontSize, TextAnchor.MiddleCenter, textColor, "0 0", "1 1");
            if (!string.IsNullOrEmpty(command)) AddTransparentButton(ui, inner, command, "0 0", "1 1");
        }
        private void AddChip(LUI ui, string parent, string text, string bgColor, string textColor, string anchorMin, string anchorMax, int fontSize) {
            string chip = CuiHelper.GetGuid();
            AddBorderBox(ui, parent, chip, anchorMin, anchorMax, LuiColors.Transparent, bgColor, 0f);
            AddLabel(ui, chip + ".inner", text, fontSize, TextAnchor.MiddleCenter, textColor, "0 0", "1 1");
        }
        private string AddScrollView(LUI ui, string parent, string name, string viewAnchorMin, string viewAnchorMax, float contentHeightPx, bool showScrollbar = false, bool startAtBottom = false) {
            var vBar = new LuiScrollbar {
                disabled = !showScrollbar, autoHide = !showScrollbar, size = showScrollbar ? 12f : 0f, handleColor = showScrollbar ? "1 1 1 0.65" : "0 0 0 0", highlightColor = showScrollbar ? "1 1 1 0.85" : "0 0 0 0", pressedColor = showScrollbar ? "1 1 1 1" : "0 0 0 0", trackColor = "0 0 0 0"
            }
            ;
            var hBar = new LuiScrollbar {
                disabled = true, autoHide = true, size = 0f, handleColor = "0 0 0 0", highlightColor = "0 0 0 0", pressedColor = "0 0 0 0", trackColor = "0 0 0 0"
            }
            ;
            var scroll = ui.CreateScrollView(parent, ToLuiPosition(viewAnchorMin, viewAnchorMax), LuiOffset.None, true, false, ScrollRect.MovementType.Clamped, 0f, true, 0.135f, 24f, vBar, hBar, name);
            if (scroll != null) {
                scroll.SetScrollContent(ToLuiPosition("0 1", "1 1"), new LuiOffset(0f, -Mathf.Max(1f, contentHeightPx), 0f, 0f));
                scroll.SetScrollPivot(new Vector2(0.5f, 1f));
                scroll.SetScrollbarPosition(0f, startAtBottom ? 0.0001f : 1f);
            }
            return name;
        }
        private void AddDbImage(LUI ui, string parent, string imageName, string anchorMin, string anchorMax, string color = LuiColors.White) {
            if (string.IsNullOrWhiteSpace(parent) || string.IsNullOrWhiteSpace(imageName)) return;
            if (!_imagesRegistered) return;
#if CARBON
            ui.CreateRawImageFromDb(parent, ToLuiPosition(anchorMin, anchorMax), LuiOffset.None, imageName, color);
#else
            if (_oxideImageUrls.TryGetValue(imageName, out var url) && !string.IsNullOrWhiteSpace(url)) {
                ui.CreateUrlImage(parent, ToLuiPosition(anchorMin, anchorMax), LuiOffset.None, url, color);
            }
#endif
        }
#endregion
#region ui ids
        private const string UiHud = "PN.HUD";
        private const string UiInbox = "PN.INBOX";
        private const string UiPopup = "PN.POPUP";
        private const string UiHudEdit = "PN.HUDEDIT";
#endregion
#region hud editor state
        private class HudEditState {
            public float StepSmall = 0.002f;
            public float StepBig = 0.01f;
            public float SizeSmall = 2f;
            public float SizeBig = 10f;
        }
        private readonly Dictionary<ulong, HudEditState> _hudEdit = new Dictionary<ulong, HudEditState>();
        private HudEditState GetHudEditState(ulong id) {
            if (!_hudEdit.TryGetValue(id, out var s)) _hudEdit[id] = s = new HudEditState();
            return s;
        }
#endregion
#region permissions
        private bool _permsRegistered;
        private void RegisterPerms() {
            if (_permsRegistered) return;
            permission.RegisterPermission(PermAdmin, this);
            _permsRegistered = true;
            Puts($"Registered permissions: {PermAdmin}");
        }
#endregion
#region server hooks
        private void Init() {
            RegisterPerms();
        }
        private void OnServerInitialized() {
            timer.Once(0f, () => {
                foreach (var player in BasePlayer.activePlayerList) ShowHudIcon(player);
            }
            );
            timer.Once(1f, TryInitImages);
        }
        private void Unload() {
            foreach (var player in BasePlayer.activePlayerList) DestroyAllUi(player);
            _lastHudUnread.Clear();
            _pendingApiImageSets.Clear();
            _oxideImageUrls.Clear();
        }
        private void OnPluginUnloaded(Plugin plugin) {
            if (plugin == null) return;
            var pluginPrefix = SanitizeApiImageKeyPart(plugin.Name, "PLUGIN") + ":";
            var pendingRemove = new List<string>();
            foreach (var kv in _pendingApiImageSets) {
                if (kv.Key.StartsWith(pluginPrefix, StringComparison.OrdinalIgnoreCase)) pendingRemove.Add(kv.Key);
            }
            for (int i = 0; i < pendingRemove.Count; i++) _pendingApiImageSets.Remove(pendingRemove[i]);
            var imageKeyPrefix = SanitizeApiImageKeyPart(plugin.Name, "PLUGIN") + "_";
            var oxideImageRemove = new List<string>();
            foreach (var kv in _oxideImageUrls) {
                if (kv.Key.StartsWith(imageKeyPrefix, StringComparison.OrdinalIgnoreCase)) oxideImageRemove.Add(kv.Key);
            }
            for (int i = 0; i < oxideImageRemove.Count; i++) _oxideImageUrls.Remove(oxideImageRemove[i]);
            var removeIds = new List<string>();
            foreach (var kv in _tabProviders) {
                var provider = kv.Value;
                if (provider == null) continue;
                if (provider.OwnerPlugin == plugin) removeIds.Add(kv.Key);
            }
            for (int i = 0; i < removeIds.Count; i++) _tabProviders.Remove(removeIds[i]);
        }
        private void OnPlayerConnected(BasePlayer player) {
            if (player == null) return;
            ShowHudIcon(player);
        }
        private void OnPlayerDisconnected(BasePlayer player, string reason) {
            if (player == null) return;
            DestroyAllUi(player);
            _mainUiState.Remove(player.userID);
            _hudEdit.Remove(player.userID);
            _uiThemes.Remove(player.userID);
            _lastHudUnread.Remove(player.userID);
        }
        private void OnPlayerDeath(BasePlayer player, HitInfo info) {
            if (player == null) return;
            DestroyAllUi(player);
        }
        private void OnPlayerSleepEnded(BasePlayer player) {
            if (player == null) return;
            NextTick(() => {
                if (player == null || !player.IsConnected) return;
                RefreshHudIcon(player, true);
                if (GetUnreadCount(player) > 0) PlayFx(player, NotificationFx);
            }
            );
        }
#endregion
#region image db
        private bool _imagesRegistered;
        private const string KeyHudIcon = "PN_HUDICON";
        private class ApiImageSetRegistration {
            public string Id;
            public string KeyPrefix;
            public List<string> Urls = new List<string>();
            public List<string> Keys = new List<string>();
        }
        private readonly Dictionary<string, ApiImageSetRegistration> _pendingApiImageSets = new Dictionary<string, ApiImageSetRegistration>(StringComparer.OrdinalIgnoreCase);
        private readonly Dictionary<string, string> _oxideImageUrls = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        private static string SanitizeApiImageKeyPart(string value, string fallback) {
            value = SafeUiText(value ?? string.Empty, 64).Trim();
            if (string.IsNullOrWhiteSpace(value)) value = fallback;
            var sb = new System.Text.StringBuilder(value.Length);
            bool lastWasUnderscore = false;
            for (int i = 0; i < value.Length; i++) {
                char c = value[i];
                if (char.IsLetterOrDigit(c)) {
                    sb.Append(char.ToUpperInvariant(c));
                    lastWasUnderscore = false;
                    continue;
                }
                if (!lastWasUnderscore) {
                    sb.Append('_');
                    lastWasUnderscore = true;
                }
            }
            var result = sb.ToString().Trim('_');
            return string.IsNullOrWhiteSpace(result) ? fallback : result;
        }
        private static List<string> BuildApiImageKeys(string keyPrefix, int count) {
            var keys = new List<string>(count);
            for (int i = 0; i < count; i++) keys.Add($"{keyPrefix}_{i}");
            return keys;
        }
        private bool TryQueueApiImageSet(ApiImageSetRegistration set) {
            if (set == null || set.Urls == null || set.Keys == null) return false;
            if (set.Urls.Count == 0 || set.Urls.Count != set.Keys.Count) return false;
#if CARBON
            var imageDb = GetCarbonImageDb();
            if (imageDb == null) return false;
            for (int i = 0; i < set.Urls.Count; i++) {
                var url = set.Urls[i];
                var key = set.Keys[i];
                if (string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(key)) continue;
                imageDb.Queue(key, url);
            }
            return true;
#else
            for (int i = 0; i < set.Urls.Count; i++) {
                var url = set.Urls[i];
                var key = set.Keys[i];
                if (string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(key)) continue;
                _oxideImageUrls[key] = url.Trim();
            }
            return true;
#endif
        }
        private void FlushPendingApiImageSets() {
            if (_pendingApiImageSets.Count == 0) return;
            var removeIds = new List<string>();
            bool queuedAny = false;
            foreach (var kv in _pendingApiImageSets) {
                var reg = kv.Value;
                if (reg == null) {
                    removeIds.Add(kv.Key);
                    continue;
                }
                if (TryQueueApiImageSet(reg)) {
                    removeIds.Add(kv.Key);
                    queuedAny = true;
                }
            }
            for (int i = 0; i < removeIds.Count; i++) _pendingApiImageSets.Remove(removeIds[i]);
            if (queuedAny) {
                timer.Once(1f, RefreshAllImageConsumers);
                timer.Once(3f, RefreshAllImageConsumers);
                timer.Once(6f, RefreshAllImageConsumers);
            }
        }
        private ImageDatabaseModule GetCarbonImageDb() {
#if CARBON
            return Carbon.Base.BaseModule.GetModule<ImageDatabaseModule>();
#else
            return null;
#endif
        }
        private void TryInitImages() {
            if (_imagesRegistered) return;
            RegisterImages();
        }
        private void RegisterImages() {
#if CARBON
            var imageDb = GetCarbonImageDb();
            if (imageDb == null) {
                PrintWarning("Carbon ImageDatabaseModule not available yet. Retrying...");
                timer.Once(1f, TryInitImages);
                return;
            }
            if (!string.IsNullOrWhiteSpace(_config.HudIconUrl)) imageDb.Queue(KeyHudIcon, _config.HudIconUrl.Trim());
#else
            if (!string.IsNullOrWhiteSpace(_config.HudIconUrl)) _oxideImageUrls[KeyHudIcon] = _config.HudIconUrl.Trim();
#endif
            _imagesRegistered = true;
            FlushPendingApiImageSets();
            timer.Once(1f, RefreshAllImageConsumers);
            timer.Once(3f, RefreshAllImageConsumers);
            timer.Once(6f, RefreshAllImageConsumers);
        }
        private void RefreshAllImageConsumers() {
            foreach (var player in BasePlayer.activePlayerList) {
                if (player == null || !player.IsConnected) continue;
                RefreshHudIcon(player, true);
            }
        }
#endregion
#region lui layout
        private LUI CreateLui() {
            var cui = new CUI(CuiHandler);
            return cui.v2 ?? new LUI(cui);
        }
        private static Vector2 ParseVector2(string value, Vector2 fallback) {
            if (string.IsNullOrWhiteSpace(value)) return fallback;
            var parts = value.Trim().Split(new[] {
                ' '
            }
            , StringSplitOptions.RemoveEmptyEntries);
            if (parts.Length < 2) return fallback;
            float x;
            float y;
            if (!float.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out x)) x = fallback.x;
            if (!float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out y)) y = fallback.y;
            return new Vector2(x, y);
        }
        private static LuiPosition ToLuiPosition(string anchorMin, string anchorMax) {
            var min = ParseVector2(anchorMin, Vector2.zero);
            var max = ParseVector2(anchorMax, Vector2.one);
            return new LuiPosition(min.x, min.y, max.x, max.y);
        }
        private static LuiOffset ToLuiOffset(string offsetMin, string offsetMax) {
            var min = ParseVector2(offsetMin, Vector2.zero);
            var max = ParseVector2(offsetMax, Vector2.zero);
            return new LuiOffset(min.x, min.y, max.x, max.y);
        }
        private void SendLui(BasePlayer player, LUI ui) {
            if (player == null) return;
            var bytes = ui.GetUiBytes();
            ui.SendUiBytes(player, bytes);
        }
#endregion
#region tabs
        private class MainUiState {
            public string SelectedTabId = string.Empty;
        }
        private class TabProvider {
            public string Id;
            public string TitleLangKey;
            public string StaticTitle;
            public Func<BasePlayer, bool> IsVisible;
            public Func<ulong, int> GetUnreadCount;
            public Action<LUI, string, BasePlayer, UiPaletteConfig> RenderContent;
            public Plugin OwnerPlugin;
            public string VisibleHook;
            public string UnreadHook;
            public string RenderHook;
        }
        private readonly Dictionary<ulong, MainUiState> _mainUiState = new Dictionary<ulong, MainUiState>();
        private readonly Dictionary<string, TabProvider> _tabProviders = new Dictionary<string, TabProvider>(StringComparer.OrdinalIgnoreCase);
        private readonly Dictionary<ulong, int> _lastHudUnread = new Dictionary<ulong, int>();
        private MainUiState GetMainUiState(ulong userId) {
            if (!_mainUiState.TryGetValue(userId, out var st)) {
                st = new MainUiState();
                _mainUiState[userId] = st;
            }
            return st;
        }
        private static int ReadHookInt(object value, int fallback = 0) {
            if (value == null) return fallback;
            if (value is int i) return i;
            if (value is long l) {
                if (l > int.MaxValue) return int.MaxValue;
                if (l < int.MinValue) return int.MinValue;
                return (int)l;
            }
            if (value is float f) return Mathf.RoundToInt(f);
            if (value is double d) return (int)Math.Round(d);
            try {
                return Convert.ToInt32(value, CultureInfo.InvariantCulture);
            }
            catch {
                return fallback;
            }
        }
        private Dictionary<string, string> BuildPaletteMap(UiPaletteConfig p) {
            return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) {
                ["Overlay"]
                = p.Overlay, ["Window"] = p.Window, ["Header"] = p.Header, ["Panel"] = p.Panel, ["PanelAlt"] = p.PanelAlt, ["Card"] = p.Card, ["CardAlt"] = p.CardAlt, ["Border"] = p.Border, ["BorderStrong"] = p.BorderStrong, ["Text"] = p.Text, ["TextMuted"] = p.TextMuted, ["Accent"] = p.Accent, ["AccentSoft"] = p.AccentSoft, ["AccentText"] = p.AccentText, ["Success"] = p.Success, ["SuccessSoft"] = p.SuccessSoft, ["Warning"] = p.Warning, ["WarningSoft"] = p.WarningSoft, ["Danger"] = p.Danger, ["DangerSoft"] = p.DangerSoft, ["Offline"] = p.Offline, ["OfflineSoft"] = p.OfflineSoft, ["Input"] = p.Input, ["AvatarFill"] = p.AvatarFill
            }
            ;
        }
        private string GetProviderTitle(BasePlayer player, TabProvider provider) {
            if (provider == null) return string.Empty;
            if (!string.IsNullOrWhiteSpace(provider.TitleLangKey)) return Lang(provider.TitleLangKey, player);
            if (!string.IsNullOrWhiteSpace(provider.StaticTitle)) return provider.StaticTitle;
            return provider.Id ?? string.Empty;
        }
        private bool IsProviderVisible(BasePlayer player, TabProvider provider) {
            if (player == null || provider == null) return false;
            if (provider.IsVisible != null) return provider.IsVisible(player);
            if (provider.OwnerPlugin != null && !string.IsNullOrWhiteSpace(provider.VisibleHook)) {
                try {
                    var result = provider.OwnerPlugin.Call(provider.VisibleHook, player, provider.Id);
                    if (result == null) return true;
                    if (result is bool visible) return visible;
                    return false;
                }
                catch (Exception ex) {
                    PrintWarning($"Tab '{provider.Id}' visible hook failed: {ex.Message}");
                    return false;
                }
            }
            return true;
        }
        private int GetUnreadCount(BasePlayer player) {
            return GetUnreadCount(player, player?.userID ?? 0UL);
        }
        private int GetUnreadCount(ulong userId) {
            var player = BasePlayer.FindByID(userId) ?? BasePlayer.FindSleeping(userId);
            return GetUnreadCount(player, userId);
        }
        private int GetUnreadCount(BasePlayer player, ulong userId) {
            int unread = 0;
            foreach (var provider in _tabProviders.Values) {
                if (provider == null) continue;
                if (player != null && !IsProviderVisible(player, provider)) continue;
                unread += GetProviderUnreadCount(userId, provider);
            }
            return unread;
        }
        private int GetProviderUnreadCount(ulong userId, TabProvider provider) {
            if (provider == null) return 0;
            if (provider.GetUnreadCount != null) return Mathf.Max(0, provider.GetUnreadCount(userId));
            if (provider.OwnerPlugin != null && !string.IsNullOrWhiteSpace(provider.UnreadHook)) {
                try {
                    var result = provider.OwnerPlugin.Call(provider.UnreadHook, userId, provider.Id);
                    return Mathf.Max(0, ReadHookInt(result));
                }
                catch (Exception ex) {
                    PrintWarning($"Tab '{provider.Id}' unread hook failed: {ex.Message}");
                    return 0;
                }
            }
            return 0;
        }
        private void RenderProviderContent(LUI ui, string root, BasePlayer player, UiPaletteConfig p, TabProvider provider) {
            if (provider == null) {
                AddLabel(ui, root, Lang(LangUiNoTabProvider, player), 16, TextAnchor.MiddleCenter, p.TextMuted, "0 0", "1 1");
                return;
            }
            if (provider.RenderContent != null) {
                provider.RenderContent(ui, root, player, p);
                return;
            }
            if (provider.OwnerPlugin != null && !string.IsNullOrWhiteSpace(provider.RenderHook)) {
                try {
                    provider.OwnerPlugin.Call(provider.RenderHook, ui, root, player, BuildPaletteMap(p), provider.Id);
                    return;
                }
                catch (Exception ex) {
                    PrintWarning($"Tab '{provider.Id}' render hook failed: {ex}");
                    AddLabel(ui, root, Lang(LangUiRenderFailed, player, provider.Id), 16, TextAnchor.MiddleCenter, p.TextMuted, "0 0", "1 1");
                    return;
                }
            }
            AddLabel(ui, root, Lang(LangUiNoRenderHook, player, provider.Id), 16, TextAnchor.MiddleCenter, p.TextMuted, "0 0", "1 1");
        }
        private List<TabProvider> GetVisibleTabProviders(BasePlayer player) {
            var result = new List<TabProvider>();
            var added = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            var order = _config?.TabOrder;
            if (order != null && order.Count > 0) {
                for (int i = 0; i < order.Count; i++) {
                    var id = order[i];
                    if (string.IsNullOrWhiteSpace(id)) continue;
                    TabProvider provider;
                    if (!_tabProviders.TryGetValue(id, out provider) || provider == null) continue;
                    if (!IsProviderVisible(player, provider)) continue;
                    if (!added.Add(provider.Id)) continue;
                    result.Add(provider);
                }
                return result;
            }
            foreach (var kv in _tabProviders) {
                var provider = kv.Value;
                if (provider == null) continue;
                if (!IsProviderVisible(player, provider)) continue;
                if (!added.Add(provider.Id)) continue;
                result.Add(provider);
            }
            return result;
        }
        private string GetTabButtonText(BasePlayer player, TabProvider provider) {
            if (provider == null) return string.Empty;
            return GetProviderTitle(player, provider);
        }
        private string FormatTabChipText(int unread) {
            if (unread <= 0) return string.Empty;
            return unread > 99 ? "99+" : unread.ToString(CultureInfo.InvariantCulture);
        }
        private string FormatHudCountText(int unread) {
            if (unread <= 0) return string.Empty;
            return Mathf.Min(unread, 99).ToString(CultureInfo.InvariantCulture);
        }
        private void AddMainTabButton(LUI ui, string parent, string text, int unread, bool active, string command, UiPaletteConfig p, string anchorMin, string anchorMax) {
            string outer = CuiHelper.GetGuid();
            AddBorderBox(ui, parent, outer, anchorMin, anchorMax, active ? p.BorderStrong : p.Border, active ? p.AccentSoft : p.CardAlt, 1f);
            string inner = outer + ".inner";
            AddLabel(ui, inner, text ?? string.Empty, 12, TextAnchor.MiddleCenter, active ? p.Accent : p.TextMuted, "0.04 0", unread > 0 ? "0.80 1" : "0.96 1");
            if (unread > 0) {
                AddChip(ui, inner, FormatTabChipText(unread), p.Danger, p.AccentText, "0.79 0.20", "0.97 0.80", 9);
            }
            AddTransparentButton(ui, inner, command, "0 0", "1 1");
        }
        private void RenderMainTabBar(LUI ui, string parent, BasePlayer player, UiPaletteConfig p, string activeTabId, List<TabProvider> tabs) {
            if (tabs == null || tabs.Count == 0) return;
            const float startX = 0.01f;
            const float endX = 0.78f;
            const float gap = 0.006f;
            var visibleTabs = new List<TabProvider>();
            var unreadCounts = new List<int>();
            float uniformWidth = 0f;
            for (int i = 0; i < tabs.Count; i++) {
                var tab = tabs[i];
                if (tab == null) continue;
                visibleTabs.Add(tab);
                int unread = GetProviderUnreadCount(player.userID, tab);
                unreadCounts.Add(unread);
                float needed = GetMainTabButtonWidth(GetTabButtonText(player, tab));
                if (needed > uniformWidth) uniformWidth = needed;
            }
            if (visibleTabs.Count == 0) return;
            float available = endX - startX;
            float totalGap = gap * (visibleTabs.Count - 1);
            float maxAllowedWidth = (available - totalGap) / visibleTabs.Count;
            uniformWidth = Mathf.Min(uniformWidth, maxAllowedWidth);
            uniformWidth = Mathf.Clamp(uniformWidth, 0.07f, 0.15f);
            float x = startX;
            for (int i = 0; i < visibleTabs.Count; i++) {
                var tab = visibleTabs[i];
                int unread = unreadCounts[i];
                AddMainTabButton(ui, parent, GetTabButtonText(player, tab), unread, string.Equals(tab.Id, activeTabId, StringComparison.OrdinalIgnoreCase), $"pn.opentab {tab.Id}", p, $"{x:0.####} 0.16", $"{(x + uniformWidth):0.####} 0.84");
                x += uniformWidth + gap;
            }
        }
        private void OpenMainUi(BasePlayer player, string preferredTabId = null) {
            if (player == null || !player.IsConnected) return;
            CuiHelper.DestroyUi(player, UiInbox);
            CuiHelper.DestroyUi(player, UiPopup);
            var p = GetPalette(player);
            var mainState = GetMainUiState(player.userID);
            if (!string.IsNullOrWhiteSpace(preferredTabId)) mainState.SelectedTabId = preferredTabId.Trim().ToLowerInvariant();
            var visibleTabs = GetVisibleTabProviders(player);
            TabProvider active = null;
            if (visibleTabs.Count > 0) {
                if (string.IsNullOrWhiteSpace(mainState.SelectedTabId) || visibleTabs.All(x => !string.Equals(x.Id, mainState.SelectedTabId, StringComparison.OrdinalIgnoreCase))) {
                    mainState.SelectedTabId = visibleTabs[0].Id;
                }
                active = visibleTabs.FirstOrDefault(x => string.Equals(x.Id, mainState.SelectedTabId, StringComparison.OrdinalIgnoreCase));
                if (active == null) {
                    active = visibleTabs[0];
                    mainState.SelectedTabId = active.Id;
                }
            }
            else {
                mainState.SelectedTabId = string.Empty;
            }
            using var ui = CreateLui();
            CreateFullscreenRoot(ui, UiInbox, p.Overlay, true);
            string root = CreatePixelWindow(ui, UiInbox, UiInbox + ".main", 1280f, 720f, p.BorderStrong, p.Window, 2f);
            AddBorderBox(ui, root, root + ".header", "0 0.93", "1 1", p.Border, p.Header, 1f);
            AddLabel(ui, root + ".header.inner", Lang(LangUiTitle, player), 22, TextAnchor.MiddleLeft, p.Text, "0.02 0", "0.50 1");
            AddBorderButton(ui, root + ".header.inner", GetThemeToggleText(player), 11, p.Border, p.Card, p.Text, "pn.theme main", "0.84 0.22", "0.90 0.78");
            AddBorderButton(ui, root + ".header.inner", Lang(LangUiRefresh, player), 13, p.Border, p.Card, p.Text, "pn.refreshinbox", "0.905 0.22", "0.945 0.78");
            AddBorderButton(ui, root + ".header.inner", "✕", 14, p.Border, p.Header, p.TextMuted, "pn.closeinbox", "0.95 0.22", "0.985 0.78");
            AddBorderBox(ui, root, root + ".nav", "0 0.87", "1 0.93", p.Border, p.Window, 1f);
            if (visibleTabs.Count > 0) {
                RenderMainTabBar(ui, root + ".nav.inner", player, p, active != null ? active.Id : string.Empty, visibleTabs);
            }
            else {
                AddLabel(ui, root + ".nav.inner", Lang(LangUiNoSections, player), 12, TextAnchor.MiddleLeft, p.TextMuted, "0.02 0", "0.98 1");
            }
            AddBorderBox(ui, root, root + ".content", "0 0", "1 0.87", p.Border, p.Window, 1f);
            string content = root + ".content.inner";
            if (active != null) {
                RenderProviderContent(ui, content, player, p, active);
            }
            else {
                AddLabel(ui, content, Lang(LangUiNoSections, player), 18, TextAnchor.MiddleCenter, p.TextMuted, "0 0", "1 1");
            }
            SendLui(player, ui);
            RefreshHudIcon(player, true);
        }
        [ConsoleCommand("pn.openinbox")]
        private void CcmdOpenInbox(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            OpenMainUi(player);
        }
        [ConsoleCommand("pn.opentab")]
        private void CcmdOpenTab(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            var tabId = arg.GetString(0, string.Empty);
            OpenMainUi(player, tabId);
        }
        [ConsoleCommand("pn.closeinbox")]
        private void CcmdCloseInbox(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            CuiHelper.DestroyUi(player, UiInbox);
            CuiHelper.DestroyUi(player, UiPopup);
        }
        [ConsoleCommand("pn.refreshinbox")]
        private void CcmdRefreshInbox(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            OpenMainUi(player);
        }
#endregion
#region addon api
        private object API_RegisterTab(Plugin plugin, string id, string title, string renderHook, string unreadHook = null, string visibleHook = null) {
            if (plugin == null) return false;
            id = (id ?? string.Empty).Trim().ToLowerInvariant();
            title = SafeUiText(title ?? string.Empty, 32);
            renderHook = (renderHook ?? string.Empty).Trim();
            unreadHook = string.IsNullOrWhiteSpace(unreadHook) ? null : unreadHook.Trim();
            visibleHook = string.IsNullOrWhiteSpace(visibleHook) ? null : visibleHook.Trim();
            if (string.IsNullOrWhiteSpace(id)) return false;
            if (string.IsNullOrWhiteSpace(renderHook)) return false;
            _tabProviders[id] = new TabProvider {
                Id = id, StaticTitle = string.IsNullOrWhiteSpace(title) ? id : title, OwnerPlugin = plugin, VisibleHook = visibleHook, UnreadHook = unreadHook, RenderHook = renderHook
            }
            ;
            return true;
        }
        private object API_UnregisterTab(Plugin plugin, string id) {
            if (plugin == null) return false;
            id = (id ?? string.Empty).Trim().ToLowerInvariant();
            if (string.IsNullOrWhiteSpace(id)) return false;
            if (!_tabProviders.TryGetValue(id, out var provider) || provider == null) return false;
            if (provider.OwnerPlugin != plugin) return false;
            _tabProviders.Remove(id);
            return true;
        }
        private object API_OpenTab(BasePlayer player, string tabId) {
            if (player == null || !player.IsConnected) return false;
            OpenMainUi(player, tabId);
            return true;
        }
        private object API_RefreshUi(BasePlayer player, string preferredTabId = null) {
            if (player == null || !player.IsConnected) return false;
            OpenMainUi(player, preferredTabId);
            return true;
        }
        private object API_ShowPopup(BasePlayer player, string message) {
            if (player == null || !player.IsConnected) return false;
            ShowPopup(player, message ?? string.Empty);
            return true;
        }
        private object API_GetPalette(BasePlayer player) {
            if (player == null) return null;
            return BuildPaletteMap(GetPalette(player));
        }
        private object API_RefreshHud(BasePlayer player, bool force = true) {
            if (player == null || !player.IsConnected) return false;
            RefreshHudIcon(player, force);
            return true;
        }
        private object API_ToggleTheme(BasePlayer player) {
            if (player == null) return false;
            _uiThemes[player.userID] = GetThemeMode(player.userID) == UiThemeMode.Dark ? UiThemeMode.Light : UiThemeMode.Dark;
            return true;
        }
 
// ... [Code preview truncated due to length. Full source in attached file] ...

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