A scoreboard sidebar
The sidebar is the right-hand scoreboard. In vanilla you drive it with a scoreboard objective and /scoreboard objectives setdisplay sidebar. The API hides that: a Sidebar is a plugin-owned objective sent only to its viewers, shown per viewer, and removed automatically when your plugin unloads. Different players can see different lines at the same time.
The API
Section titled “The API”Sidebars is a property of your plugin: Sidebars.Create(...) makes a sidebar.
| Member | Meaning |
|---|---|
Sidebars.Create(Component title) |
creates a sidebar nobody sees yet |
sidebar.Title |
get/set the title |
sidebar.SetLine(int index, Component? text) |
sets or replaces a line; null removes it |
sidebar.SetLines(IReadOnlyList<Component> lines) |
replaces all lines at once |
sidebar.Clear() |
removes every line |
sidebar.Show(Player) / sidebar.Hide(Player) |
per-viewer visibility |
sidebar.IsShownTo(Player) / sidebar.Viewers |
who sees it |
sidebar.Remove() |
hides it from everybody and releases it |
Text is a Component: a plain string converts to literal text, so a player name can never inject formatting. For colours use Component.Legacy("&aGreen") or Component.Parse("<green>Green"), or build one with Component.Text("Green").Color(TextColor.Green).
A Sidebar has one set of lines, shown to every player you Show it to. Use one shared sidebar for global information and one sidebar per player for personal information.
A shared sidebar
Section titled “A shared sidebar”Shows the player count and the server tick, refreshed once a second.
using Mc.Scripting;using Mc.Scripting.Text;
namespace StatsBoard;
public sealed class PluginMain : Plugin{ private Sidebar _board = null!;
protected override void OnEnable() { _board = Sidebars.Create(Component.Text("My Server").Color(TextColor.Gold).Bold());
foreach (Player player in Server.Players) _board.Show(player); Events.On((ref PlayerJoinEvent e) => _board.Show(e.Player));
Scheduler.RunEvery(20, () => { _board.SetLine(2, Component.Legacy($"&7Online: &f{Server.PlayerCount}")); _board.SetLine(1, Component.Legacy($"&7Tick: &f{Server.CurrentTick}")); }); }}One sidebar per player
Section titled “One sidebar per player”For text that differs per viewer, keep one Sidebar per player. The name is written as literal text, not as a legacy format string, so it cannot inject colour codes.
using Mc.Scripting;using Mc.Scripting.Text;
namespace PersonalBoards;
public sealed class PluginMain : Plugin{ private readonly Dictionary<Uuid, Sidebar> _boards = new();
protected override void OnEnable() { Events.On((ref PlayerJoinEvent e) => { Sidebar board = Sidebars.Create(Component.Text(e.Username.ToString()).Color(TextColor.Aqua)); board.Show(e.Player); _boards[e.Uuid] = board; });
Events.On((ref PlayerQuitEvent e) => { if (_boards.Remove(e.Uuid, out Sidebar? board)) board.Remove(); });
Scheduler.RunEvery(20, () => { foreach (Player p in Server.Players) { if (!_boards.TryGetValue(p.Uuid, out Sidebar? board)) continue; board.SetLine(2, Component.Legacy($"&7Health: &c{p.Health:0.#}")); board.SetLine(1, Component.Legacy($"&7XP level: &a{p.ExperienceLevel}")); } }); }}- Lifetime: a sidebar is owned by your plugin; on unload or hot reload it is hidden from every viewer, so a reloaded plugin starts from a clean scoreboard and re-creates its sidebars in
OnEnable. - Performance:
SetLinesends packets to the viewers of that sidebar, and it does nothing when the text is unchanged. Setting the same text every tick costs only a comparison. Spread large numbers of per-player sidebars over several ticks if needed. - Limits: a sidebar shows at most
Sidebar.MaxLines(15) lines. An index outside 0 to 14 throwsArgumentOutOfRangeException. - Layering with the server sidebar: a plugin sidebar shown to a player covers the server’s own sidebar on that player’s screen. Hiding it shows the next plugin sidebar, or the server’s one.
- Server-wide scores:
Scoreboard.CreateObjective(...)makes a server objective that every player sees and that is saved with the world, andScoreboard.SetDisplay(DisplaySlot.Sidebar, objective)puts it in the sidebar slot. Use those for shared scores that/scoreboardalso edits, andSidebarfor per-player content. - Players who leave: a handle goes stale when the player leaves (
IsOnlineis false). Dropping the dictionary entry and removing the sidebar onPlayerQuitEventis enough.