From 16f378eada4b4685b4ae74cee02760fcf55bbe5c Mon Sep 17 00:00:00 2001 From: Vantz Stockwell Date: Sun, 22 Feb 2026 01:12:34 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20Add=20CorrosionTeleportGUI=20uMod=20plu?= =?UTF-8?q?gin=20=E2=80=94=20in-game=20teleport=20CUI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standalone C# uMod plugin that provides a full-screen CUI teleport interface for Rust game servers, integrating with NTeleportation. Features: - /tpgui chat command opens tabbed overlay (Teleport, Homes, Warps, Settings) - 4x5 player grid with search filtering and pagination for TPR - Home management (teleport, set, delete) via NTeleportation API - Server warp list with teleport buttons - Incoming TPR accept/deny popup with 30s auto-dismiss - Settings tab showing cooldowns, limits, NTeleportation status - Oxide-orange color scheme matching Corrosion brand Co-Authored-By: Claude Opus 4.6 --- plugin/CorrosionTeleportGUI.cs | 1555 ++++++++++++++++++++++++++++++++ 1 file changed, 1555 insertions(+) create mode 100644 plugin/CorrosionTeleportGUI.cs diff --git a/plugin/CorrosionTeleportGUI.cs b/plugin/CorrosionTeleportGUI.cs new file mode 100644 index 0000000..e026253 --- /dev/null +++ b/plugin/CorrosionTeleportGUI.cs @@ -0,0 +1,1555 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Oxide.Core; +using Oxide.Core.Plugins; +using Oxide.Game.Rust.Cui; +using UnityEngine; + +namespace Oxide.Plugins +{ + [Info("CorrosionTeleportGUI", "Corrosion", "1.0.0")] + [Description("Beautiful in-game teleport GUI that integrates with NTeleportation")] + public class CorrosionTeleportGUI : RustPlugin + { + #region Constants + + private const string MainPanel = "CorrosionTP_Main"; + private const string PopupPanel = "CorrosionTP_Popup"; + + private const int GridColumns = 4; + private const int GridRows = 5; + private const int PlayersPerPage = GridColumns * GridRows; + private const float PopupTimeout = 30f; + + // Color scheme + private const string ColorOverlay = "0.05 0.05 0.05 0.95"; + private const string ColorPanel = "0.1 0.1 0.1 0.98"; + private const string ColorTabActive = "0.8 0.4 0.2 1"; + private const string ColorTabInactive = "0.2 0.2 0.2 1"; + private const string ColorButtonHover = "0.3 0.3 0.3 1"; + private const string ColorText = "1 1 1 1"; + private const string ColorTextMuted = "0.7 0.7 0.7 1"; + private const string ColorAccept = "0.2 0.6 0.2 0.9"; + private const string ColorDeny = "0.6 0.2 0.2 0.9"; + private const string ColorClose = "0.6 0.2 0.2 0.8"; + private const string ColorTransparent = "0 0 0 0"; + private const string ColorInputBg = "0.15 0.15 0.15 1"; + private const string ColorCardBg = "0.14 0.14 0.14 1"; + private const string ColorDivider = "0.25 0.25 0.25 1"; + private const string ColorSettingLabel = "0.5 0.5 0.5 1"; + + #endregion + + #region Fields + + [PluginReference] + private Plugin NTeleportation; + + private Dictionary playerPages = new Dictionary(); + private Dictionary playerSearches = new Dictionary(); + private Dictionary playerTabs = new Dictionary(); + private Dictionary popupTimers = new Dictionary(); + private List cachedPlayers = new List(); + + #endregion + + #region Lifecycle Hooks + + private void OnServerInitialized() + { + if (NTeleportation == null) + { + PrintWarning("NTeleportation plugin not found! Teleport functionality will be limited."); + PrintWarning("Install NTeleportation from uMod for full functionality."); + } + else + { + Puts("NTeleportation detected — full integration enabled."); + } + + RefreshPlayerCache(); + Puts("CorrosionTeleportGUI initialized."); + } + + private void Unload() + { + foreach (var player in BasePlayer.activePlayerList) + { + DestroyUI(player); + DestroyPopupUI(player); + } + + foreach (var kvp in popupTimers) + { + kvp.Value?.Destroy(); + } + + popupTimers.Clear(); + playerPages.Clear(); + playerSearches.Clear(); + playerTabs.Clear(); + cachedPlayers.Clear(); + } + + private void OnPlayerConnected(BasePlayer player) + { + if (player == null) return; + if (!cachedPlayers.Contains(player)) + cachedPlayers.Add(player); + } + + private void OnPlayerDisconnected(BasePlayer player, string reason) + { + if (player == null) return; + cachedPlayers.Remove(player); + playerPages.Remove(player.userID); + playerSearches.Remove(player.userID); + playerTabs.Remove(player.userID); + + if (popupTimers.TryGetValue(player.userID, out var popupTimer)) + { + popupTimer?.Destroy(); + popupTimers.Remove(player.userID); + } + } + + #endregion + + #region Chat Command + + [ChatCommand("tpgui")] + private void CmdTeleportGUI(BasePlayer player, string command, string[] args) + { + if (player == null) return; + + playerPages[player.userID] = 0; + playerSearches[player.userID] = string.Empty; + playerTabs[player.userID] = "teleport"; + + ShowMainPanel(player); + } + + #endregion + + #region Console Commands + + [ConsoleCommand("tpgui.close")] + private void CmdClose(ConsoleSystem.Arg arg) + { + var player = arg.Connection?.player as BasePlayer; + if (player == null) return; + + DestroyUI(player); + playerPages.Remove(player.userID); + playerSearches.Remove(player.userID); + playerTabs.Remove(player.userID); + } + + [ConsoleCommand("tpgui.tab")] + private void CmdTab(ConsoleSystem.Arg arg) + { + var player = arg.Connection?.player as BasePlayer; + if (player == null) return; + + string tab = arg.GetString(0, "teleport"); + playerTabs[player.userID] = tab; + playerPages[player.userID] = 0; + playerSearches[player.userID] = string.Empty; + + ShowMainPanel(player); + } + + [ConsoleCommand("tpgui.search")] + private void CmdSearch(ConsoleSystem.Arg arg) + { + var player = arg.Connection?.player as BasePlayer; + if (player == null) return; + + string search = arg.GetString(0, string.Empty); + playerSearches[player.userID] = search; + playerPages[player.userID] = 0; + + ShowMainPanel(player); + } + + [ConsoleCommand("tpgui.page")] + private void CmdPage(ConsoleSystem.Arg arg) + { + var player = arg.Connection?.player as BasePlayer; + if (player == null) return; + + string direction = arg.GetString(0, "next"); + int currentPage = GetPlayerPage(player); + + if (direction == "next") + playerPages[player.userID] = currentPage + 1; + else if (direction == "prev" && currentPage > 0) + playerPages[player.userID] = currentPage - 1; + + ShowMainPanel(player); + } + + [ConsoleCommand("tpgui.tpr")] + private void CmdTPR(ConsoleSystem.Arg arg) + { + var player = arg.Connection?.player as BasePlayer; + if (player == null) return; + + string targetName = arg.GetString(0, string.Empty); + if (string.IsNullOrEmpty(targetName)) + { + player.ChatMessage("[Corrosion TP] No target specified."); + return; + } + + DestroyUI(player); + player.SendConsoleCommand("chat.say", "/tpr " + targetName); + player.ChatMessage($"[Corrosion TP] Teleport request sent to {targetName}."); + } + + [ConsoleCommand("tpgui.home")] + private void CmdHome(ConsoleSystem.Arg arg) + { + var player = arg.Connection?.player as BasePlayer; + if (player == null) return; + + string homeName = arg.GetString(0, string.Empty); + if (string.IsNullOrEmpty(homeName)) + { + player.ChatMessage("[Corrosion TP] No home specified."); + return; + } + + DestroyUI(player); + player.SendConsoleCommand("chat.say", "/home " + homeName); + } + + [ConsoleCommand("tpgui.sethome")] + private void CmdSetHome(ConsoleSystem.Arg arg) + { + var player = arg.Connection?.player as BasePlayer; + if (player == null) return; + + string homeName = arg.GetString(0, "home"); + DestroyUI(player); + player.SendConsoleCommand("chat.say", "/sethome " + homeName); + player.ChatMessage($"[Corrosion TP] Setting home '{homeName}' at current position..."); + } + + [ConsoleCommand("tpgui.removehome")] + private void CmdRemoveHome(ConsoleSystem.Arg arg) + { + var player = arg.Connection?.player as BasePlayer; + if (player == null) return; + + string homeName = arg.GetString(0, string.Empty); + if (string.IsNullOrEmpty(homeName)) + { + player.ChatMessage("[Corrosion TP] No home specified."); + return; + } + + player.SendConsoleCommand("chat.say", "/removehome " + homeName); + player.ChatMessage($"[Corrosion TP] Removing home '{homeName}'..."); + + // Refresh the panel after a short delay to allow NTeleportation to process + timer.Once(0.5f, () => + { + if (player != null && player.IsConnected) + ShowMainPanel(player); + }); + } + + [ConsoleCommand("tpgui.warp")] + private void CmdWarp(ConsoleSystem.Arg arg) + { + var player = arg.Connection?.player as BasePlayer; + if (player == null) return; + + string warpName = arg.GetString(0, string.Empty); + if (string.IsNullOrEmpty(warpName)) + { + player.ChatMessage("[Corrosion TP] No warp specified."); + return; + } + + DestroyUI(player); + player.SendConsoleCommand("chat.say", "/warp " + warpName); + } + + [ConsoleCommand("tpgui.accept")] + private void CmdAccept(ConsoleSystem.Arg arg) + { + var player = arg.Connection?.player as BasePlayer; + if (player == null) return; + + DestroyPopupUI(player); + player.SendConsoleCommand("chat.say", "/tpa"); + } + + [ConsoleCommand("tpgui.deny")] + private void CmdDeny(ConsoleSystem.Arg arg) + { + var player = arg.Connection?.player as BasePlayer; + if (player == null) return; + + DestroyPopupUI(player); + player.SendConsoleCommand("chat.say", "/tpc"); + } + + [ConsoleCommand("tpgui.popup.close")] + private void CmdPopupClose(ConsoleSystem.Arg arg) + { + var player = arg.Connection?.player as BasePlayer; + if (player == null) return; + + DestroyPopupUI(player); + } + + #endregion + + #region NTeleportation Hooks + + /// + /// Hook called by NTeleportation when a player receives a teleport request. + /// Shows the accept/deny popup to the target player. + /// + private void OnTeleportRequested(BasePlayer target, BasePlayer requester) + { + if (target == null || requester == null) return; + ShowTPRPopup(target, requester.displayName); + } + + #endregion + + #region Main Panel + + private void ShowMainPanel(BasePlayer player) + { + DestroyUI(player); + RefreshPlayerCache(); + + string activeTab = GetPlayerTab(player); + var container = new CuiElementContainer(); + + // Full-screen overlay background + container.Add(new CuiPanel + { + Image = { Color = ColorOverlay }, + RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }, + CursorEnabled = true + }, "Overlay", MainPanel); + + // Main panel body + container.Add(new CuiPanel + { + Image = { Color = ColorPanel }, + RectTransform = { AnchorMin = "0.15 0.1", AnchorMax = "0.85 0.9" } + }, MainPanel, MainPanel + "_Body"); + + // --- Header --- + AddHeader(ref container, player, activeTab); + + // --- Tab Content --- + switch (activeTab) + { + case "teleport": + ShowTeleportTab(ref container, player); + break; + case "homes": + ShowHomesTab(ref container, player); + break; + case "warps": + ShowWarpsTab(ref container, player); + break; + case "settings": + ShowSettingsTab(ref container, player); + break; + } + + CuiHelper.AddUi(player, container); + } + + private void AddHeader(ref CuiElementContainer container, BasePlayer player, string activeTab) + { + string body = MainPanel + "_Body"; + + // Header background + container.Add(new CuiPanel + { + Image = { Color = "0.08 0.08 0.08 1" }, + RectTransform = { AnchorMin = "0 0.88", AnchorMax = "1 1" } + }, body, body + "_Header"); + + // Title + container.Add(new CuiLabel + { + Text = + { + Text = "CORROSION TELEPORT", + FontSize = 18, + Align = TextAnchor.MiddleLeft, + Color = ColorTabActive, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.03 0", AnchorMax = "0.3 1" } + }, body + "_Header"); + + // Tab buttons + float tabStart = 0.32f; + float tabWidth = 0.12f; + float tabGap = 0.005f; + + string[] tabs = { "teleport", "homes", "warps" }; + string[] tabLabels = { "TELEPORT", "HOMES", "WARPS" }; + + for (int i = 0; i < tabs.Length; i++) + { + float xMin = tabStart + i * (tabWidth + tabGap); + float xMax = xMin + tabWidth; + string color = activeTab == tabs[i] ? ColorTabActive : ColorTabInactive; + + container.Add(new CuiButton + { + Button = + { + Color = color, + Command = $"tpgui.tab {tabs[i]}" + }, + Text = + { + Text = tabLabels[i], + FontSize = 12, + Align = TextAnchor.MiddleCenter, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = $"{xMin} 0.2", AnchorMax = $"{xMax} 0.8" } + }, body + "_Header"); + } + + // Settings gear button + float settingsX = tabStart + tabs.Length * (tabWidth + tabGap); + string settingsColor = activeTab == "settings" ? ColorTabActive : ColorTabInactive; + container.Add(new CuiButton + { + Button = + { + Color = settingsColor, + Command = "tpgui.tab settings" + }, + Text = + { + Text = "⚙", + FontSize = 16, + Align = TextAnchor.MiddleCenter, + Color = ColorText + }, + RectTransform = { AnchorMin = $"{settingsX} 0.2", AnchorMax = $"{settingsX + 0.05f} 0.8" } + }, body + "_Header"); + + // Close button + container.Add(new CuiButton + { + Button = + { + Color = ColorClose, + Command = "tpgui.close" + }, + Text = + { + Text = "✕", + FontSize = 16, + Align = TextAnchor.MiddleCenter, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.93 0.2", AnchorMax = "0.98 0.8" } + }, body + "_Header"); + + // Divider line below header + container.Add(new CuiPanel + { + Image = { Color = ColorTabActive }, + RectTransform = { AnchorMin = "0 0.875", AnchorMax = "1 0.878" } + }, body); + } + + #endregion + + #region Teleport Tab + + private void ShowTeleportTab(ref CuiElementContainer container, BasePlayer player) + { + string body = MainPanel + "_Body"; + + // Content area + container.Add(new CuiPanel + { + Image = { Color = ColorTransparent }, + RectTransform = { AnchorMin = "0.02 0.02", AnchorMax = "0.98 0.86" } + }, body, body + "_TeleportContent"); + + string content = body + "_TeleportContent"; + + // Search bar background + container.Add(new CuiPanel + { + Image = { Color = ColorInputBg }, + RectTransform = { AnchorMin = "0 0.9", AnchorMax = "0.75 0.98" } + }, content, content + "_SearchBg"); + + // Search label + container.Add(new CuiLabel + { + Text = + { + Text = " 🔍 Search players...", + FontSize = 12, + Align = TextAnchor.MiddleLeft, + Color = ColorTextMuted, + Font = "robotocondensed-regular.ttf" + }, + RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" } + }, content + "_SearchBg"); + + // Search input field + container.Add(new CuiElement + { + Name = content + "_SearchInput", + Parent = content + "_SearchBg", + Components = + { + new CuiInputFieldComponent + { + Text = GetPlayerSearch(player), + FontSize = 12, + Color = ColorText, + Command = "tpgui.search", + Font = "robotocondensed-regular.ttf", + Align = TextAnchor.MiddleLeft + }, + new CuiRectTransformComponent + { + AnchorMin = "0.03 0", + AnchorMax = "0.97 1" + } + } + }); + + // Online count label + int totalOnline = cachedPlayers.Count(p => p != null && p.IsConnected && p.userID != player.userID); + container.Add(new CuiLabel + { + Text = + { + Text = $"{totalOnline} ONLINE", + FontSize = 11, + Align = TextAnchor.MiddleRight, + Color = ColorTextMuted, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.78 0.9", AnchorMax = "0.98 0.98" } + }, content); + + // Filter and paginate players + string search = GetPlayerSearch(player); + var filteredPlayers = cachedPlayers + .Where(p => p != null && p.IsConnected && p.userID != player.userID) + .Where(p => string.IsNullOrEmpty(search) || + p.displayName.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0) + .OrderBy(p => p.displayName) + .ToList(); + + int currentPage = GetPlayerPage(player); + int totalPages = Math.Max(1, (int)Math.Ceiling((double)filteredPlayers.Count / PlayersPerPage)); + + // Clamp page + if (currentPage >= totalPages) currentPage = totalPages - 1; + if (currentPage < 0) currentPage = 0; + playerPages[player.userID] = currentPage; + + var pagePlayers = filteredPlayers + .Skip(currentPage * PlayersPerPage) + .Take(PlayersPerPage) + .ToList(); + + // Player grid area + container.Add(new CuiPanel + { + Image = { Color = ColorTransparent }, + RectTransform = { AnchorMin = "0 0.08", AnchorMax = "1 0.88" } + }, content, content + "_Grid"); + + // Render player cards + float cardGap = 0.01f; + float cardWidth = (1f - (GridColumns + 1) * cardGap) / GridColumns; + float cardHeight = (1f - (GridRows + 1) * cardGap) / GridRows; + + for (int i = 0; i < pagePlayers.Count; i++) + { + var target = pagePlayers[i]; + int col = i % GridColumns; + int row = i / GridColumns; + + // Invert row so first player appears at top-left + float xMin = cardGap + col * (cardWidth + cardGap); + float xMax = xMin + cardWidth; + float yMax = 1f - (cardGap + row * (cardHeight + cardGap)); + float yMin = yMax - cardHeight; + + string cardName = content + $"_Card_{i}"; + + // Card background + container.Add(new CuiPanel + { + Image = { Color = ColorCardBg }, + RectTransform = { AnchorMin = $"{xMin} {yMin}", AnchorMax = $"{xMax} {yMax}" } + }, content + "_Grid", cardName); + + // Player name + container.Add(new CuiLabel + { + Text = + { + Text = TruncateString(target.displayName, 16), + FontSize = 11, + Align = TextAnchor.MiddleCenter, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.05 0.5", AnchorMax = "0.95 0.9" } + }, cardName); + + // Request button + container.Add(new CuiButton + { + Button = + { + Color = ColorTabActive, + Command = $"tpgui.tpr {target.displayName}" + }, + Text = + { + Text = "REQUEST", + FontSize = 10, + Align = TextAnchor.MiddleCenter, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.1 0.08", AnchorMax = "0.9 0.42" } + }, cardName); + } + + // Empty state + if (pagePlayers.Count == 0) + { + string emptyMsg = string.IsNullOrEmpty(search) + ? "No other players online" + : $"No players found matching '{search}'"; + + container.Add(new CuiLabel + { + Text = + { + Text = emptyMsg, + FontSize = 14, + Align = TextAnchor.MiddleCenter, + Color = ColorTextMuted, + Font = "robotocondensed-regular.ttf" + }, + RectTransform = { AnchorMin = "0 0.3", AnchorMax = "1 0.7" } + }, content + "_Grid"); + } + + // --- Pagination controls --- + container.Add(new CuiPanel + { + Image = { Color = ColorTransparent }, + RectTransform = { AnchorMin = "0 0", AnchorMax = "1 0.07" } + }, content, content + "_Pagination"); + + // Previous button + string prevColor = currentPage > 0 ? ColorTabInactive : "0.15 0.15 0.15 0.5"; + container.Add(new CuiButton + { + Button = + { + Color = prevColor, + Command = currentPage > 0 ? "tpgui.page prev" : "" + }, + Text = + { + Text = "◀ PREV", + FontSize = 11, + Align = TextAnchor.MiddleCenter, + Color = currentPage > 0 ? ColorText : ColorTextMuted, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.3 0", AnchorMax = "0.42 1" } + }, content + "_Pagination"); + + // Page indicator + container.Add(new CuiLabel + { + Text = + { + Text = $"Page {currentPage + 1} / {totalPages}", + FontSize = 11, + Align = TextAnchor.MiddleCenter, + Color = ColorTextMuted, + Font = "robotocondensed-regular.ttf" + }, + RectTransform = { AnchorMin = "0.43 0", AnchorMax = "0.57 1" } + }, content + "_Pagination"); + + // Next button + string nextColor = currentPage < totalPages - 1 ? ColorTabInactive : "0.15 0.15 0.15 0.5"; + container.Add(new CuiButton + { + Button = + { + Color = nextColor, + Command = currentPage < totalPages - 1 ? "tpgui.page next" : "" + }, + Text = + { + Text = "NEXT ▶", + FontSize = 11, + Align = TextAnchor.MiddleCenter, + Color = currentPage < totalPages - 1 ? ColorText : ColorTextMuted, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.58 0", AnchorMax = "0.7 1" } + }, content + "_Pagination"); + } + + #endregion + + #region Homes Tab + + private void ShowHomesTab(ref CuiElementContainer container, BasePlayer player) + { + string body = MainPanel + "_Body"; + + // Content area + container.Add(new CuiPanel + { + Image = { Color = ColorTransparent }, + RectTransform = { AnchorMin = "0.02 0.02", AnchorMax = "0.98 0.86" } + }, body, body + "_HomesContent"); + + string content = body + "_HomesContent"; + + // Header row + container.Add(new CuiPanel + { + Image = { Color = ColorTransparent }, + RectTransform = { AnchorMin = "0 0.9", AnchorMax = "1 0.98" } + }, content, content + "_HomeHeader"); + + container.Add(new CuiLabel + { + Text = + { + Text = "YOUR HOMES", + FontSize = 14, + Align = TextAnchor.MiddleLeft, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0 0", AnchorMax = "0.5 1" } + }, content + "_HomeHeader"); + + // Set Home button + container.Add(new CuiButton + { + Button = + { + Color = ColorAccept, + Command = "tpgui.sethome" + }, + Text = + { + Text = "+ SET HOME", + FontSize = 11, + Align = TextAnchor.MiddleCenter, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.78 0.1", AnchorMax = "0.98 0.9" } + }, content + "_HomeHeader"); + + // Divider + container.Add(new CuiPanel + { + Image = { Color = ColorDivider }, + RectTransform = { AnchorMin = "0 0.885", AnchorMax = "1 0.888" } + }, content); + + // Get homes from NTeleportation + var homes = GetPlayerHomes(player); + + if (homes == null || homes.Count == 0) + { + container.Add(new CuiLabel + { + Text = + { + Text = "You have no homes set.\nUse the '+ SET HOME' button to save your current location.", + FontSize = 13, + Align = TextAnchor.MiddleCenter, + Color = ColorTextMuted, + Font = "robotocondensed-regular.ttf" + }, + RectTransform = { AnchorMin = "0 0.3", AnchorMax = "1 0.7" } + }, content); + return; + } + + // Scrollable home list area + container.Add(new CuiPanel + { + Image = { Color = ColorTransparent }, + RectTransform = { AnchorMin = "0 0", AnchorMax = "1 0.87" } + }, content, content + "_HomeList"); + + float rowHeight = 0.08f; + float rowGap = 0.01f; + int index = 0; + + foreach (var home in homes) + { + if (index >= 10) break; // Max 10 visible homes + + float yMax = 1f - index * (rowHeight + rowGap); + float yMin = yMax - rowHeight; + string rowName = content + $"_HomeRow_{index}"; + + // Row background + container.Add(new CuiPanel + { + Image = { Color = ColorCardBg }, + RectTransform = { AnchorMin = $"0 {yMin}", AnchorMax = $"1 {yMax}" } + }, content + "_HomeList", rowName); + + // Home name + container.Add(new CuiLabel + { + Text = + { + Text = home.Key, + FontSize = 13, + Align = TextAnchor.MiddleLeft, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.03 0", AnchorMax = "0.4 1" } + }, rowName); + + // Coordinates + container.Add(new CuiLabel + { + Text = + { + Text = $"({home.Value.x:F0}, {home.Value.y:F0}, {home.Value.z:F0})", + FontSize = 10, + Align = TextAnchor.MiddleLeft, + Color = ColorTextMuted, + Font = "robotocondensed-regular.ttf" + }, + RectTransform = { AnchorMin = "0.4 0", AnchorMax = "0.65 1" } + }, rowName); + + // Teleport button + container.Add(new CuiButton + { + Button = + { + Color = ColorTabActive, + Command = $"tpgui.home {home.Key}" + }, + Text = + { + Text = "TELEPORT", + FontSize = 10, + Align = TextAnchor.MiddleCenter, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.68 0.15", AnchorMax = "0.83 0.85" } + }, rowName); + + // Delete button + container.Add(new CuiButton + { + Button = + { + Color = ColorDeny, + Command = $"tpgui.removehome {home.Key}" + }, + Text = + { + Text = "DELETE", + FontSize = 10, + Align = TextAnchor.MiddleCenter, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.85 0.15", AnchorMax = "0.98 0.85" } + }, rowName); + + index++; + } + } + + #endregion + + #region Warps Tab + + private void ShowWarpsTab(ref CuiElementContainer container, BasePlayer player) + { + string body = MainPanel + "_Body"; + + // Content area + container.Add(new CuiPanel + { + Image = { Color = ColorTransparent }, + RectTransform = { AnchorMin = "0.02 0.02", AnchorMax = "0.98 0.86" } + }, body, body + "_WarpsContent"); + + string content = body + "_WarpsContent"; + + // Header + container.Add(new CuiLabel + { + Text = + { + Text = "SERVER WARPS", + FontSize = 14, + Align = TextAnchor.MiddleLeft, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0 0.9", AnchorMax = "0.5 0.98" } + }, content); + + // Divider + container.Add(new CuiPanel + { + Image = { Color = ColorDivider }, + RectTransform = { AnchorMin = "0 0.885", AnchorMax = "1 0.888" } + }, content); + + // Get warps from NTeleportation + var warps = GetWarps(); + + if (warps == null || warps.Count == 0) + { + container.Add(new CuiLabel + { + Text = + { + Text = "No warps available on this server.", + FontSize = 13, + Align = TextAnchor.MiddleCenter, + Color = ColorTextMuted, + Font = "robotocondensed-regular.ttf" + }, + RectTransform = { AnchorMin = "0 0.3", AnchorMax = "1 0.7" } + }, content); + return; + } + + // Warp list area + container.Add(new CuiPanel + { + Image = { Color = ColorTransparent }, + RectTransform = { AnchorMin = "0 0", AnchorMax = "1 0.87" } + }, content, content + "_WarpList"); + + float rowHeight = 0.08f; + float rowGap = 0.01f; + int index = 0; + + foreach (var warp in warps) + { + if (index >= 10) break; // Max 10 visible warps + + float yMax = 1f - index * (rowHeight + rowGap); + float yMin = yMax - rowHeight; + string rowName = content + $"_WarpRow_{index}"; + + // Row background + container.Add(new CuiPanel + { + Image = { Color = ColorCardBg }, + RectTransform = { AnchorMin = $"0 {yMin}", AnchorMax = $"1 {yMax}" } + }, content + "_WarpList", rowName); + + // Warp name + container.Add(new CuiLabel + { + Text = + { + Text = warp.Key, + FontSize = 13, + Align = TextAnchor.MiddleLeft, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.03 0", AnchorMax = "0.5 1" } + }, rowName); + + // Coordinates + container.Add(new CuiLabel + { + Text = + { + Text = $"({warp.Value.x:F0}, {warp.Value.y:F0}, {warp.Value.z:F0})", + FontSize = 10, + Align = TextAnchor.MiddleLeft, + Color = ColorTextMuted, + Font = "robotocondensed-regular.ttf" + }, + RectTransform = { AnchorMin = "0.5 0", AnchorMax = "0.75 1" } + }, rowName); + + // Teleport button + container.Add(new CuiButton + { + Button = + { + Color = ColorTabActive, + Command = $"tpgui.warp {warp.Key}" + }, + Text = + { + Text = "TELEPORT", + FontSize = 10, + Align = TextAnchor.MiddleCenter, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.82 0.15", AnchorMax = "0.98 0.85" } + }, rowName); + + index++; + } + } + + #endregion + + #region Settings Tab + + private void ShowSettingsTab(ref CuiElementContainer container, BasePlayer player) + { + string body = MainPanel + "_Body"; + + // Content area + container.Add(new CuiPanel + { + Image = { Color = ColorTransparent }, + RectTransform = { AnchorMin = "0.02 0.02", AnchorMax = "0.98 0.86" } + }, body, body + "_SettingsContent"); + + string content = body + "_SettingsContent"; + + // Header + container.Add(new CuiLabel + { + Text = + { + Text = "TELEPORT SETTINGS", + FontSize = 14, + Align = TextAnchor.MiddleLeft, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0 0.9", AnchorMax = "0.5 0.98" } + }, content); + + // Divider + container.Add(new CuiPanel + { + Image = { Color = ColorDivider }, + RectTransform = { AnchorMin = "0 0.885", AnchorMax = "1 0.888" } + }, content); + + // Player info card + container.Add(new CuiPanel + { + Image = { Color = ColorCardBg }, + RectTransform = { AnchorMin = "0 0.6", AnchorMax = "1 0.87" } + }, content, content + "_InfoCard"); + + // Player name + container.Add(new CuiLabel + { + Text = + { + Text = player.displayName, + FontSize = 16, + Align = TextAnchor.MiddleLeft, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.03 0.55", AnchorMax = "0.7 0.95" } + }, content + "_InfoCard"); + + // Steam ID + container.Add(new CuiLabel + { + Text = + { + Text = $"Steam ID: {player.UserIDString}", + FontSize = 10, + Align = TextAnchor.MiddleLeft, + Color = ColorTextMuted, + Font = "robotocondensed-regular.ttf" + }, + RectTransform = { AnchorMin = "0.03 0.1", AnchorMax = "0.7 0.5" } + }, content + "_InfoCard"); + + // NTeleportation status badge + string ntpStatus = NTeleportation != null ? "CONNECTED" : "NOT FOUND"; + string ntpColor = NTeleportation != null ? ColorAccept : ColorDeny; + + container.Add(new CuiPanel + { + Image = { Color = ntpColor }, + RectTransform = { AnchorMin = "0.75 0.3", AnchorMax = "0.97 0.7" } + }, content + "_InfoCard", content + "_StatusBadge"); + + container.Add(new CuiLabel + { + Text = + { + Text = ntpStatus, + FontSize = 10, + Align = TextAnchor.MiddleCenter, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" } + }, content + "_StatusBadge"); + + // Teleport info section + container.Add(new CuiPanel + { + Image = { Color = ColorCardBg }, + RectTransform = { AnchorMin = "0 0.15", AnchorMax = "0.48 0.57" } + }, content, content + "_TprInfo"); + + container.Add(new CuiLabel + { + Text = + { + Text = "TELEPORT REQUESTS", + FontSize = 12, + Align = TextAnchor.MiddleLeft, + Color = ColorTabActive, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.04 0.78", AnchorMax = "0.96 0.96" } + }, content + "_TprInfo"); + + // TPR info rows + AddSettingRow(ref container, content + "_TprInfo", "Cooldown", GetNTPSetting("TPRCooldown", "300s"), 0.55f); + AddSettingRow(ref container, content + "_TprInfo", "Daily Limit", GetNTPSetting("TPRDailyLimit", "5"), 0.33f); + AddSettingRow(ref container, content + "_TprInfo", "Countdown", GetNTPSetting("TPRCountdown", "15s"), 0.11f); + + // Homes info section + container.Add(new CuiPanel + { + Image = { Color = ColorCardBg }, + RectTransform = { AnchorMin = "0.52 0.15", AnchorMax = "1 0.57" } + }, content, content + "_HomesInfo"); + + container.Add(new CuiLabel + { + Text = + { + Text = "HOME TELEPORTS", + FontSize = 12, + Align = TextAnchor.MiddleLeft, + Color = ColorTabActive, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.04 0.78", AnchorMax = "0.96 0.96" } + }, content + "_HomesInfo"); + + var homes = GetPlayerHomes(player); + int homeCount = homes?.Count ?? 0; + + AddSettingRow(ref container, content + "_HomesInfo", "Homes Set", $"{homeCount}", 0.55f); + AddSettingRow(ref container, content + "_HomesInfo", "Max Homes", GetNTPSetting("HomeMaxHomes", "3"), 0.33f); + AddSettingRow(ref container, content + "_HomesInfo", "Cooldown", GetNTPSetting("HomeCooldown", "300s"), 0.11f); + + // Plugin version footer + container.Add(new CuiLabel + { + Text = + { + Text = "CorrosionTeleportGUI v1.0.0 — corrosionmgmt.com", + FontSize = 9, + Align = TextAnchor.MiddleCenter, + Color = "0.3 0.3 0.3 1", + Font = "robotocondensed-regular.ttf" + }, + RectTransform = { AnchorMin = "0 0", AnchorMax = "1 0.08" } + }, content); + } + + private void AddSettingRow(ref CuiElementContainer container, string parent, string label, string value, float yCenter) + { + float rowHeight = 0.16f; + float yMin = yCenter - rowHeight / 2f; + float yMax = yCenter + rowHeight / 2f; + + container.Add(new CuiLabel + { + Text = + { + Text = label, + FontSize = 11, + Align = TextAnchor.MiddleLeft, + Color = ColorSettingLabel, + Font = "robotocondensed-regular.ttf" + }, + RectTransform = { AnchorMin = $"0.04 {yMin}", AnchorMax = $"0.55 {yMax}" } + }, parent); + + container.Add(new CuiLabel + { + Text = + { + Text = value, + FontSize = 11, + Align = TextAnchor.MiddleRight, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = $"0.55 {yMin}", AnchorMax = $"0.96 {yMax}" } + }, parent); + } + + #endregion + + #region TPR Popup + + private void ShowTPRPopup(BasePlayer target, string requesterName) + { + if (target == null) return; + + DestroyPopupUI(target); + + var container = new CuiElementContainer(); + + // Popup overlay (smaller, positioned at top-center) + container.Add(new CuiPanel + { + Image = { Color = "0.05 0.05 0.05 0.92" }, + RectTransform = { AnchorMin = "0.3 0.75", AnchorMax = "0.7 0.95" }, + CursorEnabled = true + }, "Overlay", PopupPanel); + + // Inner panel + container.Add(new CuiPanel + { + Image = { Color = ColorPanel }, + RectTransform = { AnchorMin = "0.02 0.04", AnchorMax = "0.98 0.96" } + }, PopupPanel, PopupPanel + "_Inner"); + + string inner = PopupPanel + "_Inner"; + + // Accent bar at top + container.Add(new CuiPanel + { + Image = { Color = ColorTabActive }, + RectTransform = { AnchorMin = "0 0.92", AnchorMax = "1 1" } + }, inner); + + // Title + container.Add(new CuiLabel + { + Text = + { + Text = "INCOMING TELEPORT REQUEST", + FontSize = 13, + Align = TextAnchor.MiddleCenter, + Color = ColorTabActive, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0 0.62", AnchorMax = "1 0.88" } + }, inner); + + // Requester name + container.Add(new CuiLabel + { + Text = + { + Text = $"{requesterName} wants to teleport to you", + FontSize = 12, + Align = TextAnchor.MiddleCenter, + Color = ColorText, + Font = "robotocondensed-regular.ttf" + }, + RectTransform = { AnchorMin = "0 0.38", AnchorMax = "1 0.62" } + }, inner); + + // Accept button + container.Add(new CuiButton + { + Button = + { + Color = ColorAccept, + Command = "tpgui.accept" + }, + Text = + { + Text = "ACCEPT", + FontSize = 13, + Align = TextAnchor.MiddleCenter, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.05 0.06", AnchorMax = "0.35 0.34" } + }, inner); + + // Deny button + container.Add(new CuiButton + { + Button = + { + Color = ColorDeny, + Command = "tpgui.deny" + }, + Text = + { + Text = "DENY", + FontSize = 13, + Align = TextAnchor.MiddleCenter, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.4 0.06", AnchorMax = "0.7 0.34" } + }, inner); + + // Close button (X) + container.Add(new CuiButton + { + Button = + { + Color = ColorClose, + Command = "tpgui.popup.close" + }, + Text = + { + Text = "✕", + FontSize = 12, + Align = TextAnchor.MiddleCenter, + Color = ColorText, + Font = "robotocondensed-bold.ttf" + }, + RectTransform = { AnchorMin = "0.78 0.06", AnchorMax = "0.95 0.34" } + }, inner); + + CuiHelper.AddUi(target, container); + + // Auto-dismiss after timeout + if (popupTimers.TryGetValue(target.userID, out var existingTimer)) + { + existingTimer?.Destroy(); + } + + popupTimers[target.userID] = timer.Once(PopupTimeout, () => + { + if (target != null && target.IsConnected) + { + DestroyPopupUI(target); + target.ChatMessage("[Corrosion TP] Teleport request timed out."); + } + popupTimers.Remove(target.userID); + }); + } + + #endregion + + #region Helpers + + private void DestroyUI(BasePlayer player) + { + if (player == null) return; + CuiHelper.DestroyUi(player, MainPanel); + } + + private void DestroyPopupUI(BasePlayer player) + { + if (player == null) return; + CuiHelper.DestroyUi(player, PopupPanel); + + if (popupTimers.TryGetValue(player.userID, out var popupTimer)) + { + popupTimer?.Destroy(); + popupTimers.Remove(player.userID); + } + } + + private void RefreshPlayerCache() + { + cachedPlayers = BasePlayer.activePlayerList?.ToList() ?? new List(); + } + + private int GetPlayerPage(BasePlayer player) + { + int page; + if (playerPages.TryGetValue(player.userID, out page)) + return page; + return 0; + } + + private string GetPlayerSearch(BasePlayer player) + { + string search; + if (playerSearches.TryGetValue(player.userID, out search)) + return search; + return string.Empty; + } + + private string GetPlayerTab(BasePlayer player) + { + string tab; + if (playerTabs.TryGetValue(player.userID, out tab)) + return tab; + return "teleport"; + } + + /// + /// Retrieves the player's homes from NTeleportation via its API. + /// Returns an empty dictionary if NTeleportation is not loaded or data is unavailable. + /// + private Dictionary GetPlayerHomes(BasePlayer player) + { + if (NTeleportation == null || player == null) + return new Dictionary(); + + try + { + var result = NTeleportation.Call("API_GetHomes", player); + if (result is Dictionary homes) + return homes; + + // Some versions may return a different type — attempt generic dictionary conversion + if (result != null) + { + var dict = new Dictionary(); + var objDict = result as Dictionary; + if (objDict != null) + { + foreach (var kvp in objDict) + { + if (kvp.Value is Vector3 vec) + dict[kvp.Key] = vec; + } + return dict; + } + } + } + catch (Exception ex) + { + PrintError($"Failed to get homes for {player.displayName}: {ex.Message}"); + } + + return new Dictionary(); + } + + /// + /// Retrieves server warps from NTeleportation data files. + /// Returns an empty dictionary if unavailable. + /// + private Dictionary GetWarps() + { + if (NTeleportation == null) + return new Dictionary(); + + try + { + var result = NTeleportation.Call("API_GetWarps"); + if (result is Dictionary warps) + return warps; + + // Attempt to read from NTeleportation data file directly as fallback + var dataFile = Interface.Oxide.DataFileSystem.ReadObject>("NTeleportation"); + if (dataFile != null && dataFile.ContainsKey("Warps")) + { + var warpsObj = dataFile["Warps"]; + if (warpsObj is Dictionary warpsDict) + { + var parsed = new Dictionary(); + foreach (var kvp in warpsDict) + { + try + { + if (kvp.Value is Dictionary posData) + { + float x = Convert.ToSingle(posData.ContainsKey("x") ? posData["x"] : 0); + float y = Convert.ToSingle(posData.ContainsKey("y") ? posData["y"] : 0); + float z = Convert.ToSingle(posData.ContainsKey("z") ? posData["z"] : 0); + parsed[kvp.Key] = new Vector3(x, y, z); + } + } + catch + { + // Skip malformed warp entries + } + } + return parsed; + } + } + } + catch (Exception ex) + { + PrintError($"Failed to get warps: {ex.Message}"); + } + + return new Dictionary(); + } + + /// + /// Gets NTeleportation configuration values for display in settings. + /// Returns the default value if NTeleportation is not available or the setting can't be read. + /// + private string GetNTPSetting(string key, string defaultValue) + { + if (NTeleportation == null) + return defaultValue; + + try + { + var result = NTeleportation.Call("API_GetConfig", key); + if (result != null) + return result.ToString(); + } + catch + { + // Silently fall back to default — this is a cosmetic read + } + + return defaultValue; + } + + /// + /// Truncates a string to the specified max length, appending "..." if truncated. + /// + private string TruncateString(string input, int maxLength) + { + if (string.IsNullOrEmpty(input) || input.Length <= maxLength) + return input; + return input.Substring(0, maxLength - 3) + "..."; + } + + #endregion + } +}