Files
corrosion-admin-panel/plugin/CorrosionTeleportGUI.cs
Vantz Stockwell 16f378eada
All checks were successful
Test Asgard Runner / test (push) Successful in 2s
feat: Add CorrosionTeleportGUI uMod plugin — in-game teleport CUI
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 <noreply@anthropic.com>
2026-02-22 01:12:34 -05:00

1556 lines
54 KiB
C#

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<ulong, int> playerPages = new Dictionary<ulong, int>();
private Dictionary<ulong, string> playerSearches = new Dictionary<ulong, string>();
private Dictionary<ulong, string> playerTabs = new Dictionary<ulong, string>();
private Dictionary<ulong, Timer> popupTimers = new Dictionary<ulong, Timer>();
private List<BasePlayer> cachedPlayers = new List<BasePlayer>();
#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("<color=#CD6632>[Corrosion TP]</color> No target specified.");
return;
}
DestroyUI(player);
player.SendConsoleCommand("chat.say", "/tpr " + targetName);
player.ChatMessage($"<color=#CD6632>[Corrosion TP]</color> Teleport request sent to <color=#FFFFFF>{targetName}</color>.");
}
[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("<color=#CD6632>[Corrosion TP]</color> 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($"<color=#CD6632>[Corrosion TP]</color> Setting home '<color=#FFFFFF>{homeName}</color>' 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("<color=#CD6632>[Corrosion TP]</color> No home specified.");
return;
}
player.SendConsoleCommand("chat.say", "/removehome " + homeName);
player.ChatMessage($"<color=#CD6632>[Corrosion TP]</color> Removing home '<color=#FFFFFF>{homeName}</color>'...");
// 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("<color=#CD6632>[Corrosion TP]</color> 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
/// <summary>
/// Hook called by NTeleportation when a player receives a teleport request.
/// Shows the accept/deny popup to the target player.
/// </summary>
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 = $"<b>{requesterName}</b> 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("<color=#CD6632>[Corrosion TP]</color> 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<BasePlayer>();
}
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";
}
/// <summary>
/// Retrieves the player's homes from NTeleportation via its API.
/// Returns an empty dictionary if NTeleportation is not loaded or data is unavailable.
/// </summary>
private Dictionary<string, Vector3> GetPlayerHomes(BasePlayer player)
{
if (NTeleportation == null || player == null)
return new Dictionary<string, Vector3>();
try
{
var result = NTeleportation.Call("API_GetHomes", player);
if (result is Dictionary<string, Vector3> homes)
return homes;
// Some versions may return a different type — attempt generic dictionary conversion
if (result != null)
{
var dict = new Dictionary<string, Vector3>();
var objDict = result as Dictionary<string, object>;
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<string, Vector3>();
}
/// <summary>
/// Retrieves server warps from NTeleportation data files.
/// Returns an empty dictionary if unavailable.
/// </summary>
private Dictionary<string, Vector3> GetWarps()
{
if (NTeleportation == null)
return new Dictionary<string, Vector3>();
try
{
var result = NTeleportation.Call("API_GetWarps");
if (result is Dictionary<string, Vector3> warps)
return warps;
// Attempt to read from NTeleportation data file directly as fallback
var dataFile = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, object>>("NTeleportation");
if (dataFile != null && dataFile.ContainsKey("Warps"))
{
var warpsObj = dataFile["Warps"];
if (warpsObj is Dictionary<string, object> warpsDict)
{
var parsed = new Dictionary<string, Vector3>();
foreach (var kvp in warpsDict)
{
try
{
if (kvp.Value is Dictionary<string, object> 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<string, Vector3>();
}
/// <summary>
/// Gets NTeleportation configuration values for display in settings.
/// Returns the default value if NTeleportation is not available or the setting can't be read.
/// </summary>
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;
}
/// <summary>
/// Truncates a string to the specified max length, appending "..." if truncated.
/// </summary>
private string TruncateString(string input, int maxLength)
{
if (string.IsNullOrEmpty(input) || input.Length <= maxLength)
return input;
return input.Substring(0, maxLength - 3) + "...";
}
#endregion
}
}