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

FREE HydroUI : 2.4.0 2.4.0

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins {
    [Info("HydroUI", "belisario-afk + copilot", "2.4.0")]
    [Description("Fullscreen welcome, menu toggle, player quick panel, full Track Editor wired to /hydro chat, with input-focus fixes and race-mode HUD tag.")]
    public class HydroUI : RustPlugin {
#region Plugin References
        [PluginReference]
        private Plugin HydroRust;
        [PluginReference]
        private Plugin HydroLobby;
        [PluginReference]
        private Plugin ImageLibrary;
#endregion
#region Constants
        private const string ROOT_START_MENU = "HydroUI.StartMenu";
        private const string ROOT_HUD = "HydroUI.HUD";
        private const string ROOT_TOGGLE = "HydroUI.MenuToggle";
        private const string ROOT_ADMIN = "HydroUI.Admin";
        private const string ROOT_PLAYER = "HydroUI.PlayerPanel";
        private const string START_HEADER = "HydroUI.Start.Header";
        private const string START_BODY = "HydroUI.Start.Body";
        private const string START_TAB_WELCOME = "HydroUI.Start.Tab.Welcome";
        private const string START_TAB_PLAY = "HydroUI.Start.Tab.Play";
        private const string HUD_TEXT = "HydroUI.HUD.Text";
        private const string HUD_PROGRESS_FILL = "HydroUI.HUD.Progress.Fill";
        private const string HUD_BOOST_FILL = "HydroUI.HUD.Boost.Fill";
        private const string HUD_SPEED_TEXT = "HydroUI.HUD.Speed.Text";
        private const string HUD_LAP_TEXT = "HydroUI.HUD.Lap.Text";
        private const string HUD_COMPASS_TEXT = "HydroUI.HUD.Compass.Text";
        private const string ADMIN_HEADER = "HydroUI.Admin.Header";
        private const string ADMIN_SUMMARY_BAR = "HydroUI.Admin.SummaryBar";
        private const string ADMIN_SUMMARY_BAR_INNER = "HydroUI.Admin.SummaryBar.Inner";
        private const string ADMIN_TRACK_LIST = "HydroUI.Admin.TrackList";
        private const string ADMIN_TRACKLIST_BODY = "HydroUI.Admin.TrackList.Body";
        private const string ADMIN_INSPECTOR = "HydroUI.Admin.Inspector";
        private const string ADMIN_INSPECTOR_BODY = "HydroUI.Admin.Inspector.Body";
        private const string IMG_START_BG = "hydro_ui_start_bg";
        private const string IMG_TRACK_DEFAULT = "hydro_ui_track_default";
        private const float TICK_HUD = 0.10f;
        private const float TICK_ANIM = 0.05f;
        private const float TICK_LOCK = 0.10f;
        private const float TICK_EDITOR_REFRESH = 0.50f;
        private const int TRACKS_PER_PAGE = 16;
#endregion
#region Config
        private class Theme {
            [JsonProperty]
            public string Name = "HydroBlue";
            [JsonProperty]
            public string Primary = "0.0 0.75 1.0 0.95";
            [JsonProperty]
            public string Accent = "0.2 0.9 1.0 0.95";
            [JsonProperty]
            public string Background = "0.02 0.05 0.08 0.92";
            [JsonProperty]
            public string Panel = "0.07 0.10 0.15 0.96";
            [JsonProperty]
            public string Text = "1 1 1 1";
            [JsonProperty]
            public string MutedText = "0.85 0.92 1 0.90";
            [JsonProperty]
            public string Progress = "0.0 0.75 1.0 0.95";
            [JsonProperty]
            public string Boost = "1.0 0.7 0.1 0.95";
            [JsonProperty]
            public string Alert = "1.0 0.25 0.25 0.95";
            [JsonProperty]
            public string Good = "0.25 1.0 0.4 0.95";
            [JsonProperty]
            public string Battle = "1.0 0.3 0.3 0.95";
        }
        private class UITextures {
            [JsonProperty]
            public string StartBackgroundUrl = "https://i.imgur.com/z7X6G8R.png";
            [JsonProperty]
            public string TrackDefaultUrl = "https://i.imgur.com/GXq8mZZ.png";
            [JsonProperty]
            public string LogoUrl = "";
        }
        private class ChatCmds {
            [JsonProperty]
            public string NewTrack = "/hydro track.new {name}";
            [JsonProperty]
            public string SelectTrack = "/hydro track.select {name}";
            [JsonProperty]
            public string Save = "/hydro track.save";
            [JsonProperty]
            public string Rename = "/hydro track.rename {name}";
            [JsonProperty]
            public string SetLaps = "/hydro track.laps {laps}";
            [JsonProperty]
            public string SetStart = "/hydro track.setstart";
            [JsonProperty]
            public string SetFinish = "/hydro track.setfinish";
            [JsonProperty]
            public string MoveSelectedToAim = "/hydro track.move";
            [JsonProperty]
            public string AddCpAim = "/hydro track.addcp.aim {radius} {index}";
            [JsonProperty]
            public string AddBoostAim = "/hydro track.addboost.aim {strength} {duration} {radius}";
            [JsonProperty]
            public string DeleteSelected = "/hydro track.delete.selected";
            [JsonProperty]
            public string DeleteTrackByName = "/hydro track.delete {name}";
            [JsonProperty]
            public string ReloadMarkers = "/hydro track.reload";
            [JsonProperty]
            public string RadiusPlus = "/hydro track.radius +";
            [JsonProperty]
            public string RadiusMinus = "/hydro track.radius -";
            [JsonProperty]
            public string SetRadius = "/hydro track.setradius {radius}";
            [JsonProperty]
            public string SetColor = "/hydro track.setcolor {color}";
            [JsonProperty]
            public string RaceJoin = "/hydro race.join";
            [JsonProperty]
            public string RaceLeave = "/hydro race.leave";
            [JsonProperty]
            public string Stats = "/hydro stats";
            // Voting
            [JsonProperty]
            public string VoteNormal = "/hydro vote normal";
            [JsonProperty]
            public string VoteBattle = "/hydro vote battle";
        }
        private class PluginConfig {
            [JsonProperty]
            public float HudScaleDefault = 1.0f;
            [JsonProperty]
            public bool HudCompactDefault = false;
            [JsonProperty]
            public List<Theme> Themes = new List<Theme> {
                new Theme {
                    Name = "HydroBlue"
                }
            }
            ;
            [JsonProperty]
            public UITextures Textures = new UITextures();
            [JsonProperty]
            public ChatCmds Chat = new ChatCmds();
            [JsonProperty]
            public float FadeInSeconds = 0.35f;
            [JsonProperty]
            public float FadeOutSeconds = 0.25f;
            [JsonProperty]
            public float TweenSpeed = 8f;
            [JsonProperty]
            public string StartMenuAnchorMin = "0 0";
            [JsonProperty]
            public string StartMenuAnchorMax = "1 1";
            [JsonProperty]
            public float StartHeaderHeight = 0.14f;
            [JsonProperty]
            public string HudAnchorMin = "0.23 0.92";
            [JsonProperty]
            public string HudAnchorMax = "0.77 0.99";
            [JsonProperty]
            public string HudCompactAnchorMin = "0.35 0.94";
            [JsonProperty]
            public string HudCompactAnchorMax = "0.65 0.99";
            [JsonProperty]
            public string AdminAnchorMin = "0.76 0.05";
            [JsonProperty]
            public string AdminAnchorMax = "0.99 0.95";
            [JsonProperty]
            public string MenuToggleAnchorMin = "0.96 0.46";
            [JsonProperty]
            public string MenuToggleAnchorMax = "0.995 0.54";
            [JsonProperty]
            public string PlayerPanelAnchorMin = "0.84 0.02";
            [JsonProperty]
            public string PlayerPanelAnchorMax = "0.995 0.16";
            [JsonProperty]
            public float RadiusStep = 0.5f;
        }
        private PluginConfig config;
        protected override void LoadDefaultConfig() {
            config = new PluginConfig();
            SaveConfig();
        }
        protected override void LoadConfig() {
            base.LoadConfig();
            try {
                config = Config.ReadObject<PluginConfig>() ?? new PluginConfig();
            }
            catch {
                PrintWarning("Invalid config; using defaults.");
                config = new PluginConfig();
            }
            SaveConfig();
        }
        protected override void SaveConfig() => Config.WriteObject(config, true);
#endregion
#region Prefs & State
        private class PlayerPrefs {
            public ulong UserId;
            public float HudScale;
            public bool HudCompact;
            public string ThemeName;
        }
        private class HudData {
            public string TrackName = "";
            public int Lap;
            public int TotalLaps;
            public int Checkpoint;
            public int TotalCheckpoints;
            public float Progress01;
            public float Speed;
            public float Boost01;
            public int Position;
            public int Racers;
            public bool IsRace;
            public bool Finished;
            public float FinishTime;
            public string RaceMode = "";
            public bool Voting;
            public int VoteSecondsRemaining;
        }
        private class PlayerUIState {
            public BasePlayer Player;
            public bool StartMenuVisible;
            public int CurrentTab = 1;
            public bool HudVisible;
            public bool AdminVisible;
            public float StartAlpha;
            public float HudAlpha;
            public float AdminAlpha;
            public HudData Target = new HudData();
            public HudData Display = new HudData();
            public string LastHudText = "";
            public string LastSpeedText = "";
            public string LastLapText = "";
            public string LastCompassText = "";
            public float TargetYaw;
            public float DisplayYaw;
            public bool MenuToggleAllowed;
            public bool MenuToggleVisible;
            public bool WelcomeLocked;
            public Vector3 LockPosition;
            public int TrackListPage;
            public List<string> TrackList = new List<string>();
            public string EditorTrackName = "";
            public string EditorSelectedType = "None";
            public int EditorCpIndex = -1;
            public int EditorBoostIndex = -1;
            public int EditorCpCount;
            public int EditorBoostCount;
            public int EditorLaps = 1;
            public string EditorDesigner = "Unknown";
            public string EditorImageKey = "";
            public bool AdminCursorEnabled = true;
            public bool PlayerPanelVisible;
            // Input debounce markers by key ("new","select","delete")
            public Dictionary<string, float> ActiveInputEditing = new Dictionary<string, float>();
            // Avoid rebuilding track list during periodic refresh
            public bool TrackListDirty;
        }
        private readonly Dictionary<ulong, PlayerPrefs> prefs = new Dictionary<ulong, PlayerPrefs>();
        private readonly Dictionary<ulong, PlayerUIState> states = new Dictionary<ulong, PlayerUIState>();
        private const string DATA_PREFS = "HydroUI_Prefs";
        private Timer hudTick;
        private Timer animTick;
        private Timer lockTick;
        private Timer editorRefreshTick;
#endregion
#region Images
        private void InitImages() {
            if (ImageLibrary == null) {
                PrintWarning("ImageLibrary not found; UI will use colors.");
                return;
            }
            AddImage(IMG_START_BG, config.Textures.StartBackgroundUrl);
            AddImage(IMG_TRACK_DEFAULT, config.Textures.TrackDefaultUrl);
            if (!string.IsNullOrEmpty(config.Textures.LogoUrl)) AddImage("hydro_ui_logo", config.Textures.LogoUrl);
        }
        private void AddImage(string shortname, string url) {
            if (ImageLibrary == null || string.IsNullOrEmpty(url)) return;
            ImageLibrary.CallHook("AddImage", url, shortname);
        }
        private string GetImage(string shortname) {
            if (ImageLibrary == null) return string.Empty;
            return ImageLibrary.CallHook("GetImage", shortname) as string ?? "";
        }
#endregion
#region Hooks & Timers
        private void Init() {
            LoadConfig();
            LoadPrefs();
        }
        private void OnServerInitialized() {
            InitImages();
            StartTimers();
            foreach (var p in BasePlayer.activePlayerList) {
                EnsureState(p);
                AutoShowLockedWelcome(p);
            }
        }
        private void Unload() {
            StopTimers();
            DestroyAllUI();
            SavePrefs();
        }
        private void OnPlayerConnected(BasePlayer player) {
            EnsureState(player);
            AutoShowLockedWelcome(player);
        }
        private void OnPlayerDisconnected(BasePlayer player, string reason) {
            HideAll(player, false);
            states.Remove(player.userID);
        }
        private void StartTimers() {
            hudTick?.Destroy();
            animTick?.Destroy();
            lockTick?.Destroy();
            editorRefreshTick?.Destroy();
            hudTick = timer.Every(TICK_HUD, HudTick);
            animTick = timer.Every(TICK_ANIM, AnimTick);
            lockTick = timer.Every(TICK_LOCK, LockTick);
            editorRefreshTick = timer.Every(TICK_EDITOR_REFRESH, EditorRefreshTick);
        }
        private void StopTimers() {
            hudTick?.Destroy();
            animTick?.Destroy();
            lockTick?.Destroy();
            editorRefreshTick?.Destroy();
            hudTick = animTick = lockTick = editorRefreshTick = null;
        }
        private void HudTick() {
            foreach (var st in states.Values) {
                var player = st.Player;
                if (player == null || !player.IsConnected) continue;
                PullHudTarget(player, st);
                LerpHud(st, config.TweenSpeed, TICK_HUD);
                if (st.HudVisible) UpdateHudElements(st);
            }
        }
        private void AnimTick() {
            foreach (var st in states.Values) {
                st.StartAlpha = MoveToward(st.StartAlpha, st.StartMenuVisible ? 1f : 0f, TICK_ANIM / config.FadeInSeconds);
                st.HudAlpha = MoveToward(st.HudAlpha, st.HudVisible ? 1f : 0f, TICK_ANIM / config.FadeInSeconds);
                st.AdminAlpha = MoveToward(st.AdminAlpha, st.AdminVisible ? 1f : 0f, TICK_ANIM / config.FadeInSeconds);
            }
        }
        private void LockTick() {
            foreach (var st in states.Values) {
                var player = st.Player;
                if (player == null || !player.IsConnected) continue;
                if (!st.WelcomeLocked) continue;
                if (Vector3.Distance(player.transform.position, st.LockPosition) > 0.25f) {
                    player.EnsureDismounted();
                    player.Teleport(st.LockPosition);
                    player.UpdateNetworkGroup();
                    player.SendNetworkUpdateImmediate();
                }
            }
        }
        private void EditorRefreshTick() {
            foreach (var st in states.Values) {
                if (!st.AdminVisible) continue;
                var player = st.Player;
                if (player == null || !player.IsConnected) continue;
                // Only refresh summary/inspector periodically to preserve input focus in track list
                var ctxObj = HydroRust?.Call("UI_GetEditorContext", player) as IDictionary<string, object>;
                if (ctxObj != null) {
                    st.EditorTrackName = SafeString(ctxObj, "TrackName");
                    st.EditorSelectedType = SafeString(ctxObj, "SelectedType");
                    st.EditorCpIndex = SafeInt(ctxObj, "SelectedCheckpointIndex");
                    st.EditorBoostIndex = SafeInt(ctxObj, "SelectedBoostIndex");
                    st.EditorCpCount = SafeInt(ctxObj, "CheckpointCount");
                    st.EditorBoostCount = SafeInt(ctxObj, "BoostCount");
                    st.EditorLaps = SafeInt(ctxObj, "Laps");
                    st.EditorDesigner = SafeString(ctxObj, "Designer");
                    st.EditorImageKey = SafeString(ctxObj, "ImageKey");
                }
                UpdateSummaryBar(player, st);
                UpdateInspectorPanel(player, st);
                // Track list is not rebuilt here to avoid stealing input focus
                // It is refreshed explicitly on open/pagination and after name mutations
            }
        }
        private static float MoveToward(float current, float target, float step) {
            if (Math.Abs(current - target) <= step) return target;
            return current + Mathf.Sign(target - current) * step;
        }
        private void LerpHud(PlayerUIState st, float speed, float dt) {
            st.Display.Speed = Mathf.Lerp(st.Display.Speed, st.Target.Speed, 1f - Mathf.Exp(-speed * dt));
            st.Display.Progress01 = Mathf.Lerp(st.Display.Progress01, st.Target.Progress01, 1f - Mathf.Exp(-speed * dt));
            st.Display.Boost01 = Mathf.Lerp(st.Display.Boost01, st.Target.Boost01, 1f - Mathf.Exp(-speed * dt));
            st.Display.Lap = st.Target.Lap;
            st.Display.TotalLaps = st.Target.TotalLaps;
            st.Display.Checkpoint = st.Target.Checkpoint;
            st.Display.TotalCheckpoints = st.Target.TotalCheckpoints;
            st.Display.TrackName = st.Target.TrackName;
            st.Display.Position = st.Target.Position;
            st.Display.Racers = st.Target.Racers;
            st.Display.IsRace = st.Target.IsRace;
            st.Display.Finished = st.Target.Finished;
            st.Display.FinishTime = st.Target.FinishTime;
            st.Display.RaceMode = st.Target.RaceMode;
            st.Display.Voting = st.Target.Voting;
            st.Display.VoteSecondsRemaining = st.Target.VoteSecondsRemaining;
            st.DisplayYaw = Mathf.LerpAngle(st.DisplayYaw, st.TargetYaw, 1f - Mathf.Exp(-speed * dt));
        }
#endregion
#region HUD
        private void PullHudTarget(BasePlayer player, PlayerUIState st) {
            var data = HydroRust?.Call("UI_GetHudData", player) as IDictionary<string, object>;
            if (data != null) {
                st.Target.TrackName = SafeString(data, "TrackName");
                st.Target.Lap = SafeInt(data, "Lap");
                st.Target.TotalLaps = SafeInt(data, "TotalLaps");
                st.Target.Checkpoint = SafeInt(data, "Checkpoint");
                st.Target.TotalCheckpoints = SafeInt(data, "TotalCheckpoints");
                st.Target.Progress01 = Mathf.Clamp01(SafeFloat(data, "Progress01"));
                st.Target.Speed = Mathf.Max(0f, SafeFloat(data, "Speed"));
                st.Target.Boost01 = Mathf.Clamp01(SafeFloat(data, "Boost01"));
                st.Target.Position = Mathf.Max(0, SafeInt(data, "Position"));
                st.Target.Racers = Mathf.Max(0, SafeInt(data, "Racers"));
                st.Target.IsRace = SafeBool(data, "IsRace");
                st.Target.Finished = SafeBool(data, "Finished");
                st.Target.FinishTime = Mathf.Max(0f, SafeFloat(data, "FinishTime"));
                st.Target.RaceMode = SafeString(data, "RaceMode");
                st.Target.Voting = SafeBool(data, "Voting");
                st.Target.VoteSecondsRemaining = SafeInt(data, "VoteSecondsRemaining");
            }
            else {
                st.Target.TrackName = "";
                st.Target.Lap = 0;
                st.Target.TotalLaps = 0;
                st.Target.Checkpoint = 0;
                st.Target.TotalCheckpoints = 0;
                st.Target.Progress01 = 0f;
                st.Target.Boost01 = 0f;
                st.Target.Position = 0;
                st.Target.Racers = 0;
                st.Target.IsRace = false;
                st.Target.Finished = false;
                st.Target.FinishTime = 0f;
                var boat = player.GetMounted()?.GetComponentInParent<BaseBoat>();
                var rb = boat ? boat.GetComponent<Rigidbody>() ?? boat.GetComponentInChildren<Rigidbody>() : null;
                st.Target.Speed = rb != null ? rb.velocity.magnitude : 0f;
            }
            st.TargetYaw = player?.eyes != null ? player.eyes.rotation.eulerAngles.y : 0f;
        }
        private void UpdateHudElements(PlayerUIState st) {
            var player = st.Player;
            if (player == null || !player.IsConnected) return;
            var theme = GetTheme(GetPrefs(player.userID).ThemeName);
            if (!HasUi(player, ROOT_HUD)) BuildHudShell(player, theme);
            SetBarFill(player, HUD_PROGRESS_FILL, st.Display.Progress01, theme.Progress);
            SetBarFill(player, HUD_BOOST_FILL, st.Display.Boost01, theme.Boost);
            string speedText = st.Display.Speed.ToString("0.0", CultureInfo.InvariantCulture) + " m/s";
            if (speedText != st.LastSpeedText) {
                st.LastSpeedText = speedText;
                CuiHelper.DestroyUi(player, HUD_SPEED_TEXT);
                var c = new CuiElementContainer();
                c.Add(new CuiLabel {
                    Text = {
                        Text = speedText, FontSize = 14, Align = TextAnchor.MiddleRight, Color = theme.Text
                    }
                    , RectTransform = {
                        AnchorMin = "0.70 0.05", AnchorMax = "0.98 0.95"
                    }
                }
                , ROOT_HUD, HUD_SPEED_TEXT);
                CuiHelper.AddUi(player, c);
            }
            string lapText = st.Display.TotalLaps > 0 ? "Lap " + Mathf.Clamp(st.Display.Lap, 0, st.Display.TotalLaps) + "/" + st.Display.TotalLaps : "";
            if (lapText != st.LastLapText) {
                st.LastLapText = lapText;
                CuiHelper.DestroyUi(player, HUD_LAP_TEXT);
                var c = new CuiElementContainer();
                c.Add(new CuiLabel {
                    Text = {
                        Text = lapText, FontSize = 14, Align = TextAnchor.MiddleLeft, Color = theme.Text
                    }
                    , RectTransform = {
                        AnchorMin = "0.02 0.05", AnchorMax = "0.30 0.95"
                    }
                }
                , ROOT_HUD, HUD_LAP_TEXT);
                CuiHelper.AddUi(player, c);
            }
            string modeTag = "";
            if (st.Display.Voting) modeTag = "[Voting]";
            else if (!string.IsNullOrEmpty(st.Display.RaceMode)) modeTag = st.Display.RaceMode == "Battle" ? "[Battle]" : "[Race]";
            string cpLine = st.Display.TotalCheckpoints > 0 ? "CP " + Mathf.Clamp(st.Display.Checkpoint, 0, st.Display.TotalCheckpoints) + "/" + st.Display.TotalCheckpoints : "";
            string racePos = st.Display.IsRace && st.Display.Position > 0 && st.Display.Racers > 0 ? "Pos " + st.Display.Position + "/" + st.Display.Racers : (st.Display.IsRace ? "RACE" : "TIME");
            string title = string.IsNullOrEmpty(st.Display.TrackName) ? "HydroRust" : (string.IsNullOrEmpty(modeTag) ? st.Display.TrackName : st.Display.TrackName + " " + modeTag);
            string status = st.Display.Finished ? ("Finished: " + st.Display.FinishTime.ToString("0.00", CultureInfo.InvariantCulture) + "s") : "";
            string hudLine = (title + "  " + cpLine + "  " + racePos + "  " + status).Trim();
            if (hudLine != st.LastHudText) {
                st.LastHudText = hudLine;
                CuiHelper.DestroyUi(player, HUD_TEXT);
                var c = new CuiElementContainer();
                c.Add(new CuiLabel {
                    Text = {
                        Text = hudLine, FontSize = 13, Align = TextAnchor.MiddleCenter, Color = theme.MutedText
                    }
                    , RectTransform = {
                        AnchorMin = "0.18 0.05", AnchorMax = "0.82 0.95"
                    }
                }
                , ROOT_HUD, HUD_TEXT);
                CuiHelper.AddUi(player, c);
            }
            string compass = Mathf.Repeat(st.DisplayYaw + 360f, 360f).ToString("0", CultureInfo.InvariantCulture) + "°";
            if (compass != st.LastCompassText) {
                st.LastCompassText = compass;
                CuiHelper.DestroyUi(player, HUD_COMPASS_TEXT);
                var c = new CuiElementContainer();
                c.Add(new CuiLabel {
                    Text = {
                        Text = compass, FontSize = 12, Align = TextAnchor.MiddleCenter, Color = theme.MutedText
                    }
                    , RectTransform = {
                        AnchorMin = "0.45 0.65", AnchorMax = "0.55 0.95"
                    }
                }
                , ROOT_HUD, HUD_COMPASS_TEXT);
                CuiHelper.AddUi(player, c);
            }
        }
        private void BuildHudShell(BasePlayer player, Theme theme) {
            var p = GetPrefs(player.userID);
            string min = p.HudCompact ? config.HudCompactAnchorMin : config.HudAnchorMin;
            string max = p.HudCompact ? config.HudCompactAnchorMax : config.HudAnchorMax;
            var container = new CuiElementContainer();
            container.Add(new CuiPanel {
                Image = {
                    Color = theme.Background
                }
                , RectTransform = {
                    AnchorMin = min, AnchorMax = max
                }
                , CursorEnabled = false
            }
            , "Hud", ROOT_HUD);
            container.Add(new CuiPanel {
                Image = {
                    Color = theme.Panel
                }
                , RectTransform = {
                    AnchorMin = "0.02 0.55", AnchorMax = "0.98 0.75"
                }
                , CursorEnabled = false
            }
            , ROOT_HUD);
            container.Add(new CuiPanel {
                Image = {
                    Color = theme.Progress
                }
                , RectTransform = {
                    AnchorMin = "0.02 0.55", AnchorMax = "0.02 0.75"
                }
                , CursorEnabled = false
            }
            , ROOT_HUD, HUD_PROGRESS_FILL);
            container.Add(new CuiPanel {
                Image = {
                    Color = theme.Panel
                }
                , RectTransform = {
                    AnchorMin = "0.02 0.20", AnchorMax = "0.98 0.40"
                }
                , CursorEnabled = false
            }
            , ROOT_HUD);
            container.Add(new CuiPanel {
                Image = {
                    Color = theme.Boost
                }
                , RectTransform = {
                    AnchorMin = "0.02 0.20", AnchorMax = "0.02 0.40"
                }
                , CursorEnabled = false
            }
            , ROOT_HUD, HUD_BOOST_FILL);
            CuiHelper.AddUi(player, container);
        }
        private void SetBarFill(BasePlayer player, string name, float t, string color) {
            t = Mathf.Clamp01(t);
            float maxX = Mathf.Lerp(0.02f, 0.98f, t);
            string min = name == HUD_PROGRESS_FILL ? "0.02 0.55" : "0.02 0.20";
            string mx = name == HUD_PROGRESS_FILL ? maxX.ToString("0.00", CultureInfo.InvariantCulture) + " 0.75" : maxX.ToString("0.00", CultureInfo.InvariantCulture) + " 0.40";
            CuiHelper.DestroyUi(player, name);
            var c = new CuiElementContainer();
            c.Add(new CuiPanel {
                Image = {
                    Color = color
                }
                , RectTransform = {
                    AnchorMin = min, AnchorMax = mx
                }
                , CursorEnabled = false
            }
            , ROOT_HUD, name);
            CuiHelper.AddUi(player, c);
        }
#endregion
#region Welcome Menu
        private void AutoShowLockedWelcome(BasePlayer player) {
            var st = EnsureState(player);
            st.MenuToggleAllowed = false;
            st.WelcomeLocked = true;
            st.LockPosition = player.transform.position;
            st.CurrentTab = 1;
            st.StartMenuVisible = true;
            BuildStartMenu(player);
        }
        [ChatCommand("hydro.menu")]
        private void CmdMenu(BasePlayer player, string cmd, string[] args) {
            var st = EnsureState(player);
            st.CurrentTab = 1;
            st.StartMenuVisible = true;
            if (!st.MenuToggleAllowed) {
                st.WelcomeLocked = true;
                st.LockPosition = player.transform.position;
            }
            BuildStartMenu(player);
        }
        [ConsoleCommand("hydro_ui.menu.tab")]
        private void CCMenuTab(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            int tab = Mathf.Clamp(arg.GetInt(0), 1, 2);
            var st = EnsureState(player);
            st.CurrentTab = tab;
            if (!st.StartMenuVisible) st.StartMenuVisible = true;
            BuildStartMenu(player);
        }
        [ConsoleCommand("hydro_ui.menu.toggle")]
        private void CCMenuToggle(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            var st = EnsureState(player);
            if (!st.MenuToggleAllowed) {
                player.ChatMessage("Menu locked until lobby join.");
                return;
            }
            if (st.StartMenuVisible) {
                DestroyStartMenu(player);
                st.StartMenuVisible = false;
            }
            else {
                st.CurrentTab = 2;
                st.StartMenuVisible = true;
                BuildStartMenu(player);
            }
        }
        [ConsoleCommand("hydro_ui.joinlobby")]
        private void CCJoinLobby(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            if (HydroLobby != null) HydroLobby.Call("SendToLobby", player);
            else player.ChatMessage("Lobby plugin missing.");
            var st = EnsureState(player);
            st.WelcomeLocked = false;
            st.MenuToggleAllowed = true;
            DestroyStartMenu(player);
            st.StartMenuVisible = false;
            ShowMenuToggle(player);
            ShowPlayerPanel(player);
        }
        private void BuildStartMenu(BasePlayer player) {
            var st = EnsureState(player);
            var theme = GetTheme(GetPrefs(player.userID).ThemeName);
            DestroyStartMenu(player);
            var c = new CuiElementContainer();
            c.Add(new CuiPanel {
                Image = {
                    Color = theme.Background
                }
                , RectTransform = {
                    AnchorMin = "0 0", AnchorMax = "1 1"
                }
                , CursorEnabled = true
            }
            , "Overlay", ROOT_START_MENU);
            var bg = GetImage(IMG_START_BG);
            if (!string.IsNullOrEmpty(bg)) {
                c.Add(new CuiElement {
                    Parent = ROOT_START_MENU, Components = {
                        new CuiRawImageComponent {
                            Png = bg, Color = "1 1 1 0.85"
                        }
                        , new CuiRectTransformComponent {
                            AnchorMin = "0 0", AnchorMax = "1 1"
                        }
                    }
                }
                );
            }
            float headerMinY = Mathf.Clamp01(1f - config.StartHeaderHeight);
            string headerMin = "0 " + headerMinY.ToString("0.###", CultureInfo.InvariantCulture);
            c.Add(new CuiPanel {
                Image = {
                    Color = theme.Panel
                }
                , RectTransform = {
                    AnchorMin = headerMin, AnchorMax = "1 1"
                }
                , CursorEnabled = true
            }
            , ROOT_START_MENU, START_HEADER);
            AddTextButton(c, START_HEADER, "Welcome", "0.02 0.15", "0.20 0.85", "hydro_ui.menu.tab 1", theme.Panel, theme.Text, 16);
            AddTextButton(c, START_HEADER, "Play", "0.22 0.15", "0.40 0.85", "hydro_ui.menu.tab 2", theme.Panel, theme.Text, 16);
            string bodyMax = "1 " + headerMinY.ToString("0.###", CultureInfo.InvariantCulture);
            c.Add(new CuiPanel {
                Image = {
                    Color = "0 0 0 0"
                }
                , RectTransform = {
                    AnchorMin = "0 0", AnchorMax = bodyMax
                }
                , CursorEnabled = true
            }
            , ROOT_START_MENU, START_BODY);
            if (st.CurrentTab == 1) BuildWelcomeTab(c, START_BODY, player, theme);
            else BuildPlayTab(c, START_BODY, player, theme);
            CuiHelper.AddUi(player, c);
        }
        private void BuildWelcomeTab(CuiElementContainer c, string parent, BasePlayer player, Theme theme) {
            c.Add(new CuiLabel {
                Text = {
                    Text = "Welcome to HydroRust", FontSize = 30, Align = TextAnchor.MiddleCenter, Color = theme.Text
                }
                , RectTransform = {
                    AnchorMin = "0.18 0.62", AnchorMax = "0.82 0.74"
                }
            }
            , parent, START_TAB_WELCOME + ".title");
            string logo = GetImage("hydro_ui_logo");
            if (!string.IsNullOrEmpty(logo)) {
                c.Add(new CuiElement {
                    Parent = parent, Components = {
                        new CuiRawImageComponent {
                            Png = logo, Color = "1 1 1 1"
                        }
                        , new CuiRectTransformComponent {
                            AnchorMin = "0.47 0.48", AnchorMax = "0.53 0.58"
                        }
                    }
                }
                );
            }
            c.Add(new CuiLabel {
                Text = {
                    Text = "Go to the Play tab and click Join Lobby to start racing. You are locked here until you join.", FontSize = 16, Align = TextAnchor.MiddleCenter, Color = theme.MutedText
                }
                , RectTransform = {
                    AnchorMin = "0.20 0.40", AnchorMax = "0.80 0.48"
                }
            }
            , parent, START_TAB_WELCOME + ".subtitle");
            var st = EnsureState(player);
            st.WelcomeLocked = !st.MenuToggleAllowed;
            st.LockPosition = player.transform.position;
        }
        private void BuildPlayTab(CuiElementContainer c, string parent, BasePlayer player, Theme theme) {
            c.Add(new CuiPanel {
                Image = {
                    Color = theme.Panel
                }
                , RectTransform = {
                    AnchorMin = "0.30 0.40", AnchorMax = "0.70 0.65"
                }
                , CursorEnabled = true
            }
            , parent, START_TAB_PLAY + ".tile");
            string png = GetImage(IMG_TRACK_DEFAULT);
            if (!string.IsNullOrEmpty(png)) {
                c.Add(new CuiElement {
                    Parent = START_TAB_PLAY + ".tile", Components = {
                        new CuiRawImageComponent {
                            Png = png, Color = "1 1 1 0.85"
                        }
                        , new CuiRectTransformComponent {
                            AnchorMin = "0 0.35", AnchorMax = "1 1"
                        }
                    }
                }
                );
            }
            c.Add(new CuiLabel {
                Text = {
                    Text = "Join Lobby", FontSize = 20, Align = TextAnchor.MiddleCenter, Color = theme.Text
                }
                , RectTransform = {
                    AnchorMin = "0.05 0.10", AnchorMax = "0.95 0.30"
                }
            }
            , START_TAB_PLAY + ".tile");
            c.Add(new CuiButton {
                Button = {
                    Command = "hydro_ui.joinlobby", Color = "0 0 0 0"
                }
                , Text = {
                    Text = ""
                }
                , RectTransform = {
                    AnchorMin = "0 0", AnchorMax = "1 1"
                }
            }
            , START_TAB_PLAY + ".tile");
        }
        private void DestroyStartMenu(BasePlayer player) => CuiHelper.DestroyUi(player, ROOT_START_MENU);
#endregion
#region Toggle Button
        private void ShowMenuToggle(BasePlayer player) {
            var st = EnsureState(player);
            var theme = GetTheme(GetPrefs(player.userID).ThemeName);
            DestroyMenuToggle(player);
            var c = new CuiElementContainer();
            c.Add(new CuiPanel {
                Image = {
                    Color = theme.Panel
                }
                , RectTransform = {
                    AnchorMin = config.MenuToggleAnchorMin, AnchorMax = config.MenuToggleAnchorMax
                }
                , CursorEnabled = false
            }
            , "Overlay", ROOT_TOGGLE);
            c.Add(new CuiButton {
                Button = {
                    Command = "hydro_ui.menu.toggle", Color = theme.Primary
                }
                , Text = {
                    Text = "≡", FontSize = 18, Align = TextAnchor.MiddleCenter, Color = theme.Text
                }
                , RectTransform = {
                    AnchorMin = "0.05 0.05", AnchorMax = "0.95 0.95"
                }
            }
            , ROOT_TOGGLE);
            CuiHelper.AddUi(player, c);
            st.MenuToggleVisible = true;
        }
        private void DestroyMenuToggle(BasePlayer player) {
            CuiHelper.DestroyUi(player, ROOT_TOGGLE);
            EnsureState(player).MenuToggleVisible = false;
        }
#endregion
#region Player Quick Panel
        private void ShowPlayerPanel(BasePlayer player) {
            var st = EnsureState(player);
            var theme = GetTheme(GetPrefs(player.userID).ThemeName);
            DestroyPlayerPanel(player);
            var c = new CuiElementContainer();
            c.Add(new CuiPanel {
                Image = {
                    Color = theme.Background
                }
                , RectTransform = {
                    AnchorMin = config.PlayerPanelAnchorMin, AnchorMax = config.PlayerPanelAnchorMax
                }
                , CursorEnabled = false
            }
            , "Overlay", ROOT_PLAYER);
            AddTextButton(c, ROOT_PLAYER, "Join Race", "0.05 0.60", "0.60 0.90", "hydroui.player.join", theme.Good, theme.Text, 14);
            AddTextButton(c, ROOT_PLAYER, "Leave Race", "0.62 0.60", "0.95 0.90", "hydroui.player.leave", theme.Alert, theme.Text, 14);
            AddTextButton(c, ROOT_PLAYER, "Stats", "0.05 0.10", "0.22 0.45", "hydroui.player.stats", theme.Panel, theme.Text, 12);
            AddTextButton(c, ROOT_PLAYER, "Vote Normal", "0.24 0.10", "0.52 0.45", "hydroui.vote.normal", theme.Panel, theme.Text, 12);
            AddTextButton(c, ROOT_PLAYER, "Vote Battle", "0.54 0.10", "0.82 0.45", "hydroui.vote.battle", theme.Battle, theme.Text, 12);
            CuiHelper.AddUi(player, c);
            st.PlayerPanelVisible = true;
        }
        private void DestroyPlayerPanel(BasePlayer player) {
            CuiHelper.DestroyUi(player, ROOT_PLAYER);
            EnsureState(player).PlayerPanelVisible = false;
        }
        [ConsoleCommand("hydroui.player.join")]
        private void CCPlayerJoin(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            RunChat(player, config.Chat.RaceJoin);
        }
        [ConsoleCommand("hydroui.player.leave")]
        private void CCPlayerLeave(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            RunChat(player, config.Chat.RaceLeave);
        }
        [ConsoleCommand("hydroui.player.stats")]
        private void CCPlayerStats(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            RunChat(player, config.Chat.Stats);
        }
        [ConsoleCommand("hydroui.vote.normal")]
        private void CCVoteNormal(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            RunChat(player, config.Chat.VoteNormal);
        }
        [ConsoleCommand("hydroui.vote.battle")]
        private void CCVoteBattle(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            RunChat(player, config.Chat.VoteBattle);
        }
#endregion
#region Admin Track Editor
        [ChatCommand("hydroui")]
        private void CmdHydroAdminUI(BasePlayer player, string cmd, string[] args) {
            if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, "hydrorust.admin")) {
                player.ChatMessage("No permission.");
                return;
            }
            DestroyStartMenu(player);
            ShowAdminUI(player);
            player.ChatMessage("Editor opened. Use New or the New Track field (press Enter), then Save.");
        }
        [ConsoleCommand("hydroui.toggle")]
        private void CCToggleEditor(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            var st = EnsureState(player);
            if (st.AdminVisible) {
                DestroyAdminUI(player);
                st.AdminVisible = false;
            }
            else {
                DestroyStartMenu(player);
                ShowAdminUI(player);
            }
        }
        [ConsoleCommand("hydroui.cursor")]
        private void CCCursor(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            var st = EnsureState(player);
            st.AdminCursorEnabled = !st.AdminCursorEnabled;
            if (st.AdminVisible) ShowAdminUI(player);
            player.ChatMessage("Editor cursor " + (st.AdminCursorEnabled ? "ENABLED" : "DISABLED"));
        }
        [ConsoleCommand("hydroui.setpanel")]
        private void CCSetPanel(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            float minX = arg.GetFloat(0), minY = arg.GetFloat(1), maxX = arg.GetFloat(2), maxY = arg.GetFloat(3);
            minX = Mathf.Clamp01(minX);
            minY = Mathf.Clamp01(minY);
            maxX = Mathf.Clamp01(maxX);
            maxY = Mathf.Clamp01(maxY);
            if (maxX <= minX || maxY <= minY) {
                player.ChatMessage("Invalid anchors. Example: hydroui.setpanel 0.76 0.05 0.99 0.95");
                return;
            }
            config.AdminAnchorMin = minX.ToString("0.##", CultureInfo.InvariantCulture) + " " + minY.ToString("0.##", CultureInfo.InvariantCulture);
            config.AdminAnchorMax = maxX.ToString("0.##", CultureInfo.InvariantCulture) + " " + maxY.ToString("0.##", CultureInfo.InvariantCulture);
            SaveConfig();
            if (EnsureState(player).AdminVisible) ShowAdminUI(player);
            player.ChatMessage("Admin panel anchors updated.");
        }
        [ConsoleCommand("hydroui.resetpanel")]
        private void CCResetPanel(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null) return;
            config.AdminAnchorMin = "0.76 0.05";
            config.AdminAnchorMax = "0.99 0.95";
            SaveConfig();
            if (EnsureState(player).AdminVisible) ShowAdminUI(player);
            player.ChatMessage("Admin panel anchors reset.");
        }
        private void ShowAdminUI(BasePlayer player) {
            var st = EnsureState(player);
            st.AdminVisible = true;
            DestroyAdminUI(player);
            var theme = GetTheme(GetPrefs(player.userID).ThemeName);
            var c = new CuiElementContainer();
            // Root side panel
            c.Add(new CuiPanel {
                Image = {
                    Color = theme.Background
                }
                , RectTransform = {
                    AnchorMin = config.AdminAnchorMin, AnchorMax = config.AdminAnchorMax
                }
                , CursorEnabled = st.AdminCursorEnabled
            }
            , "Overlay", ROOT_ADMIN);
            // Header (New, Save, Reload, Cursor, Close)
            c.Add(new CuiPanel {
                Image = {
                    Color = theme.Panel
                }
                , RectTransform = {
                    AnchorMin = "0 0.94", AnchorMax = "1 1"
                }
                , CursorEnabled = st.AdminCursorEnabled
            }
            , ROOT_ADMIN, ADMIN_HEADER);
            AddTextButton(c, ADMIN_HEADER, "New", "0.01 0.10", "0.14 0.90", "hydroui.autonew", theme.Primary, theme.Text, 14);
            AddTextButton(c, ADMIN_HEADER, "Save", "0.16 0.10", "0.29 0.90", "hydroui.savetrack", theme.Good, theme.Text, 14);
            AddTextButton(c, ADMIN_HEADER, "Reload", "0.31 0.10", "0.46 0.90", "hydroui.reload", theme.Panel, theme.Text, 14);
            AddTextButton(c, ADMIN_HEADER, st.AdminCursorEnabled ? "Cursor Off" : "Cursor On", "0.48 0.10", "0.70 0.90", "hydroui.cursor", theme.Panel, theme.Text, 14);
            AddTextButton(c, ADMIN_HEADER, "Close", "0.82 0.10", "0.99 0.90", "hydroui.close", theme.Alert, theme.Text, 14);
            // Summary bar
            c.Add(new CuiPanel {
                Image = {
                    Color = theme.Panel
                }
                , RectTransform = {
                    AnchorMin = "0 0.88", AnchorMax = "1 0.94"
                }
                , CursorEnabled = false
            }
            , ROOT_ADMIN, ADMIN_SUMMARY_BAR);
            c.Add(new CuiPanel {
                Image = {
                    Color = "0 0 0 0"
                }
                , RectTransform = {
                    AnchorMin = "0.01 0.05", AnchorMax = "0.99 0.95"
                }
                , CursorEnabled = false
            }
            , ADMIN_SUMMARY_BAR, ADMIN_SUMMARY_BAR_INNER);
            // Track list
            c.Add(new CuiPanel {
                Image = {
                    Color = theme.Panel
                }
                , RectTransform = {
                    AnchorMin = "0.02 0.46", AnchorMax = "0.98 0.86"
                }
                , CursorEnabled = st.AdminCursor
// ... [Code preview truncated due to length. Full source in attached file] ...

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