How to make a Rust team info command plugin (Oxide/Carbon)
Adds a /team chat command that lists every member of the caller's Rust team with online status, health and map grid for online members. It is a command-only plugin and uses no Oxide hooks beyond Init.
Rust's built-in team UI shows a roster, but admins and players who work from chat or RCON often want the data as plain text: who is online, how much health they have and which map grid they are standing in. The vanilla game exposes all of that through RelationshipManager, but nothing prints it as a compact list. This plugin registers a single /team chat command that finds the caller's team with RelationshipManager.ServerInstance.FindTeam, walks the team's members, and formats each entry with a status, a health value and a grid reference. Online members are resolved with BasePlayer.FindByID, and the grid is calculated from the player position against World.Size, so it works on any map size. Teamless players, dead players and sleeping bodies are handled without errors, and the output length is bounded by MaxMembersPerMessage so large teams do not flood chat. Everything a player sees comes from the language file.
The plugin code
using System.Linq;
using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Team Info Command", "RustPluginGenerator", "1.0.0")]
[Description("Adds a /team chat command that lists team members with online status, health and map grid.")]
public class TeamInfoCommand : RustPlugin
{
private const string PermUse = "teaminfocommand.use";
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Grid cell size (meters)")]
public int GridCellSize { get; set; } = 150;
[JsonProperty("Show health for online members")]
public bool ShowHealth { get; set; } = true;
[JsonProperty("Show map grid for online members")]
public bool ShowGrid { get; set; } = true;
[JsonProperty("Maximum members listed per message")]
public int MaxMembersPerMessage { get; set; } = 10;
}
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>
{
["Header"] = "Team roster ({0} members, leader: {1})",
["Leader"] = "[Leader]",
["Member"] = "[Member]",
["Online"] = "Online",
["Sleeping"] = "Sleeping",
["Dead"] = "Dead",
["Offline"] = "Offline",
["Line"] = "{0} {1} - {2}",
["LineHealth"] = "{0} {1} - {2} {3}/{4} - Grid {5}",
["AndMore"] = "... and {0} more member(s).",
["NoTeam"] = "You are not in a team."
}, 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);
}
[ChatCommand("team")]
private void CmdTeam(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermUse))
{
SendReply(player, Msg("NoTeam", player.UserIDString));
return;
}
ulong teamId = player.currentTeam;
if (teamId == 0)
{
SendReply(player, Msg("NoTeam", player.UserIDString));
return;
}
var team = RelationshipManager.ServerInstance.FindTeam(teamId);
if (team == null || team.members == null || team.members.Count == 0)
{
SendReply(player, Msg("NoTeam", player.UserIDString));
return;
}
string leaderName = ResolveName(team.teamLeader);
SendReply(player, Msg("Header", player.UserIDString, team.members.Count, leaderName));
int limit = Mathf.Clamp(config.MaxMembersPerMessage, 1, 50);
int printed = 0;
foreach (ulong memberId in team.members)
{
if (printed >= limit) break;
BasePlayer member = BasePlayer.FindByID(memberId);
string name = ResolveName(memberId, member);
string rank = memberId == team.teamLeader ? Msg("Leader", player.UserIDString) : Msg("Member", player.UserIDString);
if (member == null)
{
SendReply(player, Msg("Line", player.UserIDString, rank, name, Msg("Offline", player.UserIDString)));
printed++;
continue;
}
if (member.IsDead())
{
SendReply(player, Msg("Line", player.UserIDString, rank, name, Msg("Dead", player.UserIDString)));
printed++;
continue;
}
if (!member.IsConnected || member.IsSleeping())
{
SendReply(player, Msg("Line", player.UserIDString, rank, name, Msg("Sleeping", player.UserIDString)));
printed++;
continue;
}
string status;
if (config.ShowHealth)
{
status = Msg("Online", player.UserIDString);
int health = Mathf.CeilToInt(member.health);
int maxHealth = Mathf.CeilToInt(member.MaxHealth());
string grid = config.ShowGrid ? GridReference(member.transform.position) : "-";
SendReply(player, Msg("LineHealth", player.UserIDString, rank, name, status, health, maxHealth, grid));
}
else
{
string grid = config.ShowGrid ? GridReference(member.transform.position) : "-";
status = config.ShowGrid
? string.Format("{0} (Grid {1})", Msg("Online", player.UserIDString), grid)
: Msg("Online", player.UserIDString);
SendReply(player, Msg("Line", player.UserIDString, rank, name, status));
}
printed++;
}
int remaining = team.members.Count - printed;
if (remaining > 0)
{
SendReply(player, Msg("AndMore", player.UserIDString, remaining));
}
}
private string ResolveName(ulong userId, BasePlayer known = null)
{
BasePlayer found = known ?? BasePlayer.FindByID(userId);
if (found != null && !string.IsNullOrEmpty(found.displayName))
return found.displayName;
return userId.ToString();
}
private string GridReference(Vector3 position)
{
float cell = Mathf.Max(1f, config.GridCellSize);
float worldSize = World.Size;
if (worldSize <= 0f) return "-";
int cells = Mathf.Max(1, Mathf.CeilToInt(worldSize / cell));
int x = Mathf.Clamp(Mathf.FloorToInt((position.x + worldSize * 0.5f) / cell), 0, cells - 1);
int z = Mathf.Clamp(Mathf.FloorToInt((worldSize * 0.5f - position.z) / cell), 0, cells - 1);
var sb = new StringBuilder();
int letterIndex = x;
do
{
sb.Insert(0, (char)('A' + letterIndex % 26));
letterIndex = letterIndex / 26 - 1;
} while (letterIndex >= 0);
sb.Append(z);
return sb.ToString();
}
}
}Config file
Created automatically on first load at oxide/config/TeamInfoCommand.json.
{
"Grid cell size (meters)": 150,
"Show health for online members": true,
"Show map grid for online members": true,
"Maximum members listed per message": 10
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/team | Shows your team roster with online status, health and map grid. | teaminfocommand.use |
| Permission | Grants |
|---|---|
teaminfocommand.use | Allows the player to run /team. Grant it to the default group or to a VIP group. |
How the code works
Register the command and permission
Initregistersteaminfocommand.usewithpermission.RegisterPermission, and the[ChatCommand("team")]attribute wiresCmdTeamto the chat command. The method signature is(BasePlayer player, string command, string[] args); args are ignored because the plugin takes no arguments. The handler checkspermission.UserHasPermission(player.UserIDString, PermUse)first and sends theNoTeammessage when the check fails, so a server that does not want the command simply does not grant the permission. All replies go through theMsghelper.Find the caller's team
The player's
currentTeamfield is a ulong that is 0 when the player is not in a team. When it is 0 the handler replies withNoTeamand returns. Otherwise it callsRelationshipManager.ServerInstance.FindTeam(teamId), then checks that the returned team is not null and that itsmemberscollection is present and non-empty, with the sameNoTeamreply for each failure.team.membersis iterated in aforeach, andteam.teamLeaderis used to mark the leader in each line.Resolve each member and their status
Each member ID is passed to
BasePlayer.FindByID. A null result means the member's object is gone, soResolveNameprints the user ID string and the line is labelledOffline. For a valid player the checks run in order:IsDead()gives theDeadlabel, then!IsConnected || IsSleeping()givesSleeping, and anything else is treated as online.ResolveNameusesdisplayNamewhen it is not empty and falls back to the ID string.team.teamLeaderdecides whether the line is taggedLeaderorMember.Report health and map grid
When
ShowHealthis on, online members get theLineHealthmessage withMathf.CeilToInt(member.health)andMathf.CeilToInt(member.MaxHealth()). WhenShowGridis on,GridReferencetakesmember.transform.position, divides the world byWorld.SizeandGridCellSize, and builds a letter/number pair such asH12, with Z counted from the north edge. IfShowHealthis off the plugin sendsLineinstead and appends the grid to theOnlinestatus, or omits it whenShowGridis off. Sleeping and offline members never get a health or grid value.Bound the output length
Chat lines are capped by the server, and a large team will overflow a single message.
MaxMembersPerMessageis clamped between 1 and 50 and stops theforeachloop once that many lines are printed. The remaining count is computed as the member count minus the printed lines and sent as theAndMoremessage, so the leader always knows the real team size. The header line reports the member count from the team object and the resolved name ofteam.teamLeader.Config and messages
The config class holds
GridCellSize(default 150),ShowHealth(default true),ShowGrid(default true) andMaxMembersPerMessage(default 10), each with a[JsonProperty]name.LoadDefaultConfigwrites the defaults, andLoadConfigfalls back to them with a warning if the file fails to parse, callingSaveConfigafterwards. Every player-facing string —Header,Leader,Member,Online,Sleeping,Dead,Offline,Line,LineHealth,AndMoreandNoTeam— is registered inLoadDefaultMessagesso admins can translate or restyle the output.
Install it on your server
Save TeamInfoCommand.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/TeamInfoCommand.json(Carbon:carbon/configs/TeamInfoCommand.json). After editing, reload it:oxide.reload TeamInfoCommand c.reload TeamInfoCommand # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip:oxide.grant group default teaminfocommand.use c.grant group default teaminfocommand.use # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Add position distance
Append the distance in meters from the caller to each online member.
Generate this versionConsole and admin variant
Add a console command so admins can inspect any player's team by name or ID.
Generate this versionTeam broadcast shortcut
Let members ping the whole team with a one-line message.
Generate this version
FAQ
How do I install this on Oxide or Carbon?
Drop TeamInfoCommand.cs into oxide/plugins (Carbon reads the same folder). Oxide compiles it on the next reload or server restart, and the default config appears in oxide/config/TeamInfoCommand.json. Grant teaminfocommand.use to default with oxide.grant group default teaminfocommand.use.
Why does /team say I have no team?
The player's currentTeam value is 0, meaning they are not in a Rust team, or FindTeam returned nothing usable. Teams are created with the in-game team UI or another plugin; this command only reads existing teams. If your server uses a clan plugin with its own roster, this command will not see it.
Why do offline members show no grid?
Grid and health are only printed for players who are awake and connected. Sleeping bodies are reported as Sleeping and disconnected members as Offline, because exposing their position would reveal where bases and stashes are. Set ShowHealth and ShowGrid to false if you want an even more compact list.
The grid looks wrong on a custom map size. What should I change?
Grid letters and numbers are derived from World.Size divided by GridCellSize. The vanilla map uses 150 meter cells, so keep the default 150 unless your map image was generated with a different cell size. After editing the config run oxide.reload TeamInfoCommand to apply it.
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 teams & clans plugins
- Simple clansAdds a lightweight clan system with /clan create, invite, join, leave and info, stores clans in a data file, prefixes members' chat with their tag…OnServerInitializedOnPlayerChatOnEntityTakeDamage
- Team auto authorizationAutomatically authorizes a player on every tool cupboard, code lock and auto turret owned by their team when they join a team, and removes that…OnTeamAcceptInviteOnTeamLeaveOnTeamKick
- Team death alertsSends a chat alert to every online teammate when a player dies, including the killer name and the map grid of the death.OnPlayerDeath
Last updated Oct 26, 2026.
Customize team info command
Customize