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 } }