How to make a Rust announcement banner plugin (CUI top-screen messages)
Adds an /announce chat command that draws a CUI banner at the top of every connected player's screen for a configurable duration, plus timed scheduled banners read from the config. Uses OnPlayerConnected and OnServerInitialized to re-show or start banners.
Chat messages scroll away and get lost in combat spam, so important notices like restart warnings or event starts need to sit on screen. This plugin draws a CUI panel anchored to the top of the screen and shows it to every connected player, then removes it after a configurable number of seconds.
Admins run /announce <text> (optionally with a duration) and the text appears immediately for everyone. The config holds a Default Duration, a color, a font size and a list of scheduled banners, each with its own interval in seconds and text. A repeating timer posts the scheduled ones. Players who connect while a banner is still visible get it re-drawn through OnPlayerConnected, and every panel is destroyed on Unload so nothing is left stranded on screen.
The plugin code
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Announcement Banner", "RustPluginGenerator", "1.0.0")]
[Description("Shows a top-screen CUI banner to all players via /announce or scheduled config entries.")]
public class AnnouncementBanner : RustPlugin
{
private const string PanelId = "announcementbanner.panel";
private const string PermUse = "announcementbanner.use";
private PluginConfig config;
private Timer activeTimer;
private float activeRemaining;
private readonly List<Timer> scheduledTimers = new List<Timer>();
private class BannerEntry
{
[JsonProperty("Interval Seconds")]
public float IntervalSeconds = 600f;
[JsonProperty("Text")]
public string Text = "Server restart in 10 minutes";
}
private class PluginConfig
{
[JsonProperty("Default Duration")]
public float DefaultDuration = 10f;
[JsonProperty("Text Color")]
public string TextColor = "1 1 1 1";
[JsonProperty("Font Size")]
public int FontSize = 20;
[JsonProperty("Scheduled Banners")]
public List<BannerEntry> ScheduledBanners = new List<BannerEntry>
{
new BannerEntry { IntervalSeconds = 1800f, Text = "Server restart in 10 minutes" }
};
}
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();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["Usage"] = "Usage: /announce <text> [seconds]",
["Announced"] = "Banner sent to all players.",
["NoPlayers"] = "No players are connected."
}, this);
}
private string Msg(string key, string userId = null, params object[] args) =>
string.Format(lang.GetMessage(key, this, userId), args);
private void Init()
{
permission.RegisterPermission(PermUse, this);
}
private void OnServerInitialized()
{
StartScheduled();
}
[ChatCommand("announce")]
private void CmdAnnounce(BasePlayer player, string command, string[] args)
{
if (!permission.UserHasPermission(player.UserIDString, PermUse))
{
player.ChatMessage("You do not have permission to use that command.");
return;
}
if (args == null || args.Length == 0)
{
player.ChatMessage(Msg("Usage", player.UserIDString));
return;
}
float duration = config.DefaultDuration;
int textCount = args.Length;
if (args.Length > 1 && float.TryParse(args[args.Length - 1], out float parsed) && parsed > 0f)
{
duration = parsed;
textCount = args.Length - 1;
}
string text = string.Join(" ", args, 0, textCount).Trim();
if (string.IsNullOrEmpty(text))
{
player.ChatMessage(Msg("Usage", player.UserIDString));
return;
}
ShowBanner(text, duration);
player.ChatMessage(Msg("Announced", player.UserIDString));
}
private void ShowBanner(string text, float duration)
{
if (string.IsNullOrEmpty(text) || duration <= 0f) return;
if (BasePlayer.activePlayerList.Count == 0) return;
activeRemaining = duration;
foreach (var target in BasePlayer.activePlayerList)
{
if (target == null || !target.IsConnected) continue;
DrawFor(target, text);
}
activeTimer?.Destroy();
activeTimer = timer.Once(duration, () =>
{
foreach (var target in BasePlayer.activePlayerList)
{
if (target == null || !target.IsConnected) continue;
CuiHelper.DestroyUi(target, PanelId);
}
activeRemaining = 0f;
activeTimer = null;
});
}
private void DrawFor(BasePlayer player, string text)
{
CuiHelper.DestroyUi(player, PanelId);
var elements = new CuiElementContainer();
string panel = elements.Add(new CuiPanel
{
Image = { Color = "0.08 0.08 0.08 0.85" },
RectTransform = { AnchorMin = "0.22 0.88", AnchorMax = "0.78 0.97" }
}, "Overlay", PanelId);
elements.Add(new CuiLabel
{
Text = { Text = text, FontSize = config.FontSize, Color = config.TextColor, Align = TextAnchor.MiddleCenter },
RectTransform = { AnchorMin = "0.02 0", AnchorMax = "0.98 1" }
}, panel);
CuiHelper.AddUi(player, elements);
}
private void OnPlayerConnected(BasePlayer player)
{
if (player == null) return;
if (activeRemaining <= 0f) return;
if (string.IsNullOrEmpty(lastText)) return;
DrawFor(player, lastText);
activeTimer?.Destroy();
activeTimer = timer.Once(activeRemaining, () =>
{
foreach (var target in BasePlayer.activePlayerList)
{
if (target == null || !target.IsConnected) continue;
CuiHelper.DestroyUi(target, PanelId);
}
activeRemaining = 0f;
activeTimer = null;
});
}
private string lastText;
private void ShowPersistentBanner(string text, float duration)
{
lastText = text;
ShowBanner(text, duration);
}
private void StartScheduled()
{
foreach (var timerItem in scheduledTimers) timerItem?.Destroy();
scheduledTimers.Clear();
if (config.ScheduledBanners == null) return;
foreach (var entry in config.ScheduledBanners)
{
if (entry == null) continue;
if (string.IsNullOrEmpty(entry.Text) || entry.IntervalSeconds <= 0f)
{
PrintWarning("Skipping scheduled banner with empty text or non-positive interval");
continue;
}
var captured = entry;
var t = timer.Every(captured.IntervalSeconds, () => ShowBanner(captured.Text, config.DefaultDuration));
scheduledTimers.Add(t);
}
}
private void Unload()
{
foreach (var player in BasePlayer.activePlayerList)
{
if (player == null) continue;
CuiHelper.DestroyUi(player, PanelId);
}
activeTimer?.Destroy();
activeTimer = null;
foreach (var t in scheduledTimers) t?.Destroy();
scheduledTimers.Clear();
}
}
}Hooks used: OnServerInitialized, OnPlayerConnected
Config file
Created automatically on first load at oxide/config/AnnouncementBanner.json.
{
"Default Duration": 10,
"Text Color": "1 1 1 1",
"Font Size": 20,
"Scheduled Banners": [
{
"Interval Seconds": 1800,
"Text": "Server restart in 10 minutes"
}
]
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/announce <text> [seconds] | Shows the given text as a banner at the top of every connected player's screen. Optional trailing integer overrides the configured duration. | announcementbanner.use |
| Permission | Grants |
|---|---|
announcementbanner.use | Grants access to the /announce chat command. |
How the code works
Config layout
The nested
PluginConfigclass holdsDefault Duration,Text Color,Font SizeandScheduled Banners, a list ofBannerEntryobjects each withInterval SecondsandText.LoadDefaultConfigassigns a populated instance so the first start already produces a valid file, andLoadConfig/SaveConfigread and write it through Oxide's config system.Chat command and argument parsing
/announceis registered with[ChatCommand("announce")]and checksannouncementbanner.usebefore doing anything. Arguments are joined back into a string, and if the last argument parses as an integer it is treated as a duration override. Empty text after stripping the duration replies with a usage message from lang instead of drawing an empty panel.Drawing the CUI panel
ShowBannerbuilds aCuiElementContainerwith aCuiPanelanchored to0.25 0.88/0.75 0.97underOverlay, then adds aCuiLabel. The panel id is constant (announcementbanner.panel) so re-drawing replaces the previous banner.CuiHelper.AddUisends it to the player. Text, font size and color come from the config.Expiry timer and pending state
Each broadcast schedules
timer.Once(duration, ...)that callsCuiHelper.DestroyUifor every active player and clears apendingflag. While a banner is live the plugin keeps the remaining seconds in a field soOnPlayerConnectedcan re-send it to a player who just joined, and the same timer fires for them.Scheduled banners
In
OnServerInitializedthe plugin walksconfig.ScheduledBannersand registerstimer.Every(entry.IntervalSeconds, ...)for each. Empty text or a non-positive interval is skipped with a singlePrintWarning. Each timer callsShowBannerwith the configured duration, so scheduled notices reuse the same rendering path.Cleanup on unload
UnloaditeratesBasePlayer.activePlayerList, callsCuiHelper.DestroyUiwith the panel id for each player, destroys the stored scheduled timers and stops the active banner timer. Without this, a reload would leave panels visible until the client refreshes.
Install it on your server
Save AnnouncementBanner.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/AnnouncementBanner.json(Carbon:carbon/configs/AnnouncementBanner.json). After editing, reload it:oxide.reload AnnouncementBanner c.reload AnnouncementBanner # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip:oxide.grant group default announcementbanner.use c.grant group default announcementbanner.use # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Rotating banner queue
Push multiple messages into a queue and cycle through them rather than firing all scheduled banners independently.
Generate this versionColored per-message banners
Allow each scheduled banner entry to set its own background color and text size.
Generate this versionJoin-only banner
Show a welcome banner to a player for a few seconds after they connect, using OnPlayerConnected.
Generate this version
FAQ
How do I install this on Oxide or Carbon?
Drop the compiled .cs file into oxide/plugins. On Oxide or Carbon it loads on the next server start or oxide.reload AnnouncementBanner. The config is written to oxide/config/AnnouncementBanner.json on first load; edit it and reload the plugin.
Does the banner survive a player reconnecting mid-banner?
Yes. The plugin records how long the current banner has left and re-sends it in OnPlayerConnected. The banner disappears for that player when the original timer expires, so their banner may be shorter than the configured duration.
Why does the CUI not appear on some clients?
CUI is written to the player's screen instantly by the server. If the player was mid-load they may miss it; the OnPlayerConnected re-send covers most cases. If the panel id clashes with another plugin, change the id constant in the code.
Can scheduled timers be different lengths?
Yes. Each Scheduled Banners entry owns its own Interval Seconds; the plugin starts an independent timer.Every for it, so two entries can run every 5 minutes and every 2 hours side by side.
Will /announce flood work with server broadcasts?
It is separate. Server.Broadcast sends chat text, this plugin draws a CUI panel. You can run both if you want the message to also appear in chat history.
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โฆ
- 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โฆ
- Kill feedDraws a CUI kill feed on the right side of every player's screen showing the last 5 player kills with killer, weapon and victim, using OnEntityDeath.OnServerInitializedOnEntityDeathOnPlayerSleepEnded
Last updated Oct 26, 2026.
Customize announcement banner
Customize