How to make a Rust server HUD plugin (CUI)
Builds a small persistent CUI panel with the server name, online player count and in-game clock, updated from an Oxide timer for every connected player. Visibility is saved per Steam ID with OnPlayerConnected and OnPlayerDisconnected plus the /hud toggle.
A server HUD is the quickest way to show your branding and live information without spamming chat. The catch is that CUI panels do not persist: every respawn or reconnect wipes your layer, and the in-game clock drifts if you refresh it rarely. This plugin builds one small CuiElementContainer and re-sends it to each awake player on a timer, so the panel stays visible and the time stays accurate.
Server name comes from ConVar.Server.hostname, the player number from BasePlayer.activePlayerList.Count, and the clock from TOD_Sky.Instance.Cycle.Hour. Visibility is stored per Steam ID in oxide/data, so a player who hides the HUD keeps it hidden after reconnecting. OnPlayerConnected pushes the panel on join, OnPlayerDisconnected writes the data file, and /hud toggles it. The timer reference is destroyed in Unload so a reload does not stack updates.
The plugin code
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Server HUD", "RustPluginGenerator", "1.0.0")]
[Description("Shows a persistent CUI panel with server name, online count and in-game time.")]
public class ServerHud : RustPlugin
{
private const string PanelName = "ServerHud.Panel";
private PluginConfig config;
private StoredData stored;
private Timer refreshTimer;
private class PluginConfig
{
[JsonProperty("Refresh Interval (seconds)")]
public float RefreshInterval = 3f;
[JsonProperty("Show Clock")]
public bool ShowClock = true;
[JsonProperty("Show Player Count")]
public bool ShowPlayerCount = true;
[JsonProperty("Anchor Min")]
public string AnchorMin = "0.01 0.94";
[JsonProperty("Anchor Max")]
public string AnchorMax = "0.16 0.99";
[JsonProperty("Panel Color")]
public string PanelColor = "0.05 0.05 0.05 0.75";
[JsonProperty("Text Color")]
public string TextColor = "1 1 1 1";
[JsonProperty("Font Size")]
public int FontSize = 12;
[JsonProperty("Hidden Steam IDs")]
public List<ulong> Hidden = new List<ulong>();
}
private class StoredData
{
[JsonProperty("Hidden Steam IDs")]
public HashSet<ulong> Hidden = new HashSet<ulong>();
}
protected override void LoadDefaultConfig() => config = new PluginConfig();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<PluginConfig>();
if (config == null) throw new Exception();
}
catch
{
PrintWarning("Configuration file is invalid, using defaults");
LoadDefaultConfig();
}
if (config.Hidden == null)
{
config.Hidden = new List<ulong>();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["HudEnabled"] = "HUD enabled.",
["HudDisabled"] = "HUD disabled."
}, this);
}
private string Msg(string key, string userId = null, params object[] args) => string.Format(lang.GetMessage(key, this, userId), args);
private void OnServerInitialized()
{
LoadData();
float interval = config.RefreshInterval;
if (interval < 1f) interval = 1f;
refreshTimer = timer.Every(interval, RefreshAll);
}
private void Unload()
{
if (refreshTimer != null)
{
refreshTimer.Destroy();
refreshTimer = null;
}
SaveData();
foreach (var player in BasePlayer.activePlayerList)
{
if (player != null)
{
CuiHelper.DestroyUi(player, PanelName);
}
}
}
private void LoadData()
{
try
{
stored = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);
}
catch
{
stored = null;
}
if (stored == null)
{
stored = new StoredData();
}
if (stored.Hidden == null)
{
stored.Hidden = new HashSet<ulong>();
}
}
private void SaveData()
{
if (stored != null)
{
Interface.Oxide.DataFileSystem.WriteObject(Name, stored);
}
}
private void RefreshAll()
{
foreach (var player in BasePlayer.activePlayerList)
{
if (player == null || player.IsNpc || !player.IsConnected)
{
continue;
}
if (stored.Hidden.Contains(player.userID.Get()))
{
continue;
}
ShowHud(player);
}
}
private void ShowHud(BasePlayer player)
{
if (player == null || !player.IsConnected)
{
return;
}
string text = config.ShowPlayerCount
? $"{BasePlayer.activePlayerList.Count} online"
: null;
if (config.ShowClock)
{
string clock = GetGameTime();
text = string.IsNullOrEmpty(text) ? clock : text + " | " + clock;
}
if (string.IsNullOrEmpty(text))
{
text = " ";
}
var container = new CuiElementContainer();
string panel = container.Add(new CuiPanel
{
Image = { Color = config.PanelColor },
RectTransform = { AnchorMin = config.AnchorMin, AnchorMax = config.AnchorMax },
CursorEnabled = false
}, "Overlay", PanelName);
container.Add(new CuiLabel
{
Text =
{
Text = ConVar.Server.hostname ?? "Rust Server",
FontSize = config.FontSize,
Align = TextAnchor.MiddleCenter,
Color = config.TextColor
},
RectTransform = { AnchorMin = "0 0.5", AnchorMax = "1 1" }
}, panel);
container.Add(new CuiLabel
{
Text =
{
Text = text,
FontSize = config.FontSize,
Align = TextAnchor.MiddleCenter,
Color = config.TextColor
},
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 0.5" }
}, panel);
CuiHelper.DestroyUi(player, PanelName);
CuiHelper.AddUi(player, container);
}
private string GetGameTime()
{
var sky = TOD_Sky.Instance;
if (sky == null)
{
return "--:--";
}
float hour = sky.Cycle.Hour;
int h = Mathf.FloorToInt(hour);
int m = Mathf.FloorToInt((hour - h) * 60f);
return string.Format("{0:00}:{1:00}", h, m);
}
private void OnPlayerConnected(BasePlayer player)
{
if (player == null || player.IsNpc)
{
return;
}
if (stored.Hidden.Contains(player.userID.Get()))
{
return;
}
timer.Once(1f, () =>
{
if (player != null && player.IsConnected && !stored.Hidden.Contains(player.userID.Get()))
{
ShowHud(player);
}
});
}
private void OnPlayerDisconnected(BasePlayer player, string reason)
{
SaveData();
}
[ChatCommand("hud")]
private void CmdHud(BasePlayer player, string command, string[] args)
{
if (player == null)
{
return;
}
ulong id = player.userID.Get();
if (stored.Hidden.Contains(id))
{
stored.Hidden.Remove(id);
ShowHud(player);
SendReply(player, Msg("HudEnabled", player.UserIDString));
}
else
{
stored.Hidden.Add(id);
CuiHelper.DestroyUi(player, PanelName);
SendReply(player, Msg("HudDisabled", player.UserIDString));
}
SaveData();
}
}
}Hooks used: OnServerInitialized, OnPlayerConnected, OnPlayerDisconnected
Config file
Created automatically on first load at oxide/config/ServerHud.json.
{
"Refresh Interval (seconds)": 3,
"Show Clock": true,
"Show Player Count": true,
"Anchor Min": "0.01 0.94",
"Anchor Max": "0.16 0.99",
"Panel Color": "0.05 0.05 0.05 0.75",
"Text Color": "1 1 1 1",
"Font Size": 12,
"Hidden Steam IDs": []
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/hud | Toggles the HUD panel on or off for the calling player. The choice is saved and restored on reconnect. | Everyone |
How the code works
Config and stored visibility
The nested
PluginConfigholdsRefresh Interval (seconds),Show Clock,Show Player Count, the anchor stringsAnchor Min/Anchor Max,Panel Color,Text ColorandFont Size, all read throughConfig.ReadObjectafterLoadConfig. Player preferences live in a separateStoredDataclass with aHashSet<ulong>namedHidden, loaded inLoadDatathroughInterface.Oxide.DataFileSystem.ReadObject<StoredData>(Name).LoadDataalso recreates both objects when the file is missing or empty.Building the CuiElementContainer
The method
ShowHud(BasePlayer player)creates aCuiElementContainer, adds a parentCuiPanelwithCursorEnabledfalse and the configured anchors, then twoCuiLabels: the hostname on the upper half, and a combined line on the lower half built from the online count and the clock. BeforeCuiHelper.AddUithe method callsCuiHelper.DestroyUi(player, PanelName), so repeated sends replace the old panel instead of stacking rectangles. The constantPanelNameisServerHud.Panel.Timer refresh loop
In
OnServerInitialized, the storedTimerfieldrefreshTimeris assigned fromtimer.Every(interval, RefreshAll), whereintervalisconfig.RefreshIntervalclamped to a minimum of 1 second.RefreshAllloopsBasePlayer.activePlayerList, skips null, NPC and disconnected entries, skips Steam IDs present instored.Hidden, and callsShowHud. A short interval makes the clock tick; a longer one cuts RPC volume.Reading the in-game clock
GetGameTimereadsTOD_Sky.Instance.Cycle.Hourand returns--:--whenTOD_Sky.Instanceis null. OtherwiseMathf.FloorToIntgives the hour and the fractional part multiplied by 60 and floored gives the minutes, formatted withstring.Format("{0:00}:{1:00}", h, m). The panel shows that string only whenShow Clockis true; the online count line is controlled separately byShow Player Count.Connecting and disconnecting
OnPlayerConnectedreturns early for NPCs and for players already present instored.Hidden. Otherwise it schedulestimer.Once(1f, ...)so the client is ready before the first draw; the callback re-checks that the player is still connected and still visible.OnPlayerDisconnectedonly callsSaveData, which writes the currentstoredobject to the plugin's data file so a toggle made just before logout survives.The /hud toggle and cleanup
[ChatCommand("hud")] CmdHudchecks membership instored.Hidden: if present it removes the ID, callsShowHudand repliesHudEnabled; otherwise it adds the ID, callsCuiHelper.DestroyUi(player, PanelName)and repliesHudDisabled. Both branches finish withSaveData.UnloaddestroysrefreshTimer, saves the data file and destroys the panel for every player inBasePlayer.activePlayerListso no stale layer survives a reload.
Install it on your server
Save ServerHud.cs
Keep the file name exactly as the class name. Oxide matches them.Upload it to the plugins folder
Oxide:oxide/plugins/. Carbon:carbon/plugins/. The server compiles and loads it within a few seconds, no restart needed.Edit the config if you want
On first load the default config is written tooxide/config/ServerHud.json(Carbon:carbon/configs/ServerHud.json). After editing, reload it:oxide.reload ServerHud c.reload ServerHud # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Corner minimap-style panel
Anchor the HUD to the top-left with a compass heading derived from the player's eyes forward vector.
Generate this versionWipe countdown line
Add a third line showing days and hours until the next scheduled wipe from config.
Generate this versionServer event alerts
Push a temporary line into the HUD when a patrol helicopter or airdrop spawns.
Generate this version
FAQ
Why does the HUD disappear after a respawn or map change?
Rust clients rebuild their CUI layers on respawn and world load, and only your plugin can restore them. Re-sending the panel from the refresh timer handles this automatically. If you refresh only on connect, the panel vanishes after a death and never returns until the player reconnects.
How much network traffic does a CUI timer add?
Each CuiHelper.AddUi call sends one RPC per player. On a busy server, a 1-second refresh for 200 players is 200 small RPCs every second on top of normal game traffic. A 3 to 5 second interval is a good compromise: the clock still looks live and the cost stays low.
Can I restrict /hud to a group?
Yes. Add private const string PermUse = "serverhud.use"; and register it in private void Init(). Then check if (!permission.UserHasPermission(player.UserIDString, PermUse)) at the top of CmdHud and return with a lang message. Grant it with oxide.grant group default serverhud.use.
Does this work on Carbon?
Yes. Carbon loads Oxide plugins without changes, and CuiHelper, lang, Interface.Oxide.DataFileSystem and the standard hooks behave the same. Copy the .cs file into oxide/plugins (or Carbon's plugin folder), let it compile, then edit the generated config under oxide/config.
How do I turn the clock off?
Set Show Clock to false in the config and reload the plugin. The panel then shows only the server name and the online count. The player count line is controlled separately by Show Player Count, so you can show any combination of the three fields.
Customize it for your server
Change the reward, add a cooldown, restrict it to VIPs. Start from this prompt and edit anything.
Edit the prompt on the next screen. 2 free plugins a month, no card.
More ui plugins
- Admin menuBuilds a CUI admin panel that opens with /am and renders one button per action defined in the config, each button running a console command throughโฆ
- Announcement bannerAdds an /announce chat command that draws a CUI banner at the top of every connected player's screen for a configurable duration, plus timedโฆOnServerInitializedOnPlayerConnected
- Command shortcut menuAdds a /menu chat command that opens a CUI grid of configurable buttons; each button closes the panel and runs its configured chat command for thatโฆ
Last updated Oct 26, 2026.
Customize custom hud
Customize