Skip to content
RustPluginGenerator

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

TeamInfoCommand.cs
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.

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

Chat and console commands
CommandWhat it doesPermission
/teamShows your team roster with online status, health and map grid.teaminfocommand.use
Permissions
PermissionGrants
teaminfocommand.useAllows the player to run /team. Grant it to the default group or to a VIP group.

How the code works

  1. Register the command and permission

    Init registers teaminfocommand.use with permission.RegisterPermission, and the [ChatCommand("team")] attribute wires CmdTeam to the chat command. The method signature is (BasePlayer player, string command, string[] args); args are ignored because the plugin takes no arguments. The handler checks permission.UserHasPermission(player.UserIDString, PermUse) first and sends the NoTeam message when the check fails, so a server that does not want the command simply does not grant the permission. All replies go through the Msg helper.

  2. Find the caller's team

    The player's currentTeam field is a ulong that is 0 when the player is not in a team. When it is 0 the handler replies with NoTeam and returns. Otherwise it calls RelationshipManager.ServerInstance.FindTeam(teamId), then checks that the returned team is not null and that its members collection is present and non-empty, with the same NoTeam reply for each failure. team.members is iterated in a foreach, and team.teamLeader is used to mark the leader in each line.

  3. Resolve each member and their status

    Each member ID is passed to BasePlayer.FindByID. A null result means the member's object is gone, so ResolveName prints the user ID string and the line is labelled Offline. For a valid player the checks run in order: IsDead() gives the Dead label, then !IsConnected || IsSleeping() gives Sleeping, and anything else is treated as online. ResolveName uses displayName when it is not empty and falls back to the ID string. team.teamLeader decides whether the line is tagged Leader or Member.

  4. Report health and map grid

    When ShowHealth is on, online members get the LineHealth message with Mathf.CeilToInt(member.health) and Mathf.CeilToInt(member.MaxHealth()). When ShowGrid is on, GridReference takes member.transform.position, divides the world by World.Size and GridCellSize, and builds a letter/number pair such as H12, with Z counted from the north edge. If ShowHealth is off the plugin sends Line instead and appends the grid to the Online status, or omits it when ShowGrid is off. Sleeping and offline members never get a health or grid value.

  5. Bound the output length

    Chat lines are capped by the server, and a large team will overflow a single message. MaxMembersPerMessage is clamped between 1 and 50 and stops the foreach loop once that many lines are printed. The remaining count is computed as the member count minus the printed lines and sent as the AndMore message, so the leader always knows the real team size. The header line reports the member count from the team object and the resolved name of team.teamLeader.

  6. Config and messages

    The config class holds GridCellSize (default 150), ShowHealth (default true), ShowGrid (default true) and MaxMembersPerMessage (default 10), each with a [JsonProperty] name. LoadDefaultConfig writes the defaults, and LoadConfig falls back to them with a warning if the file fails to parse, calling SaveConfig afterwards. Every player-facing string — Header, Leader, Member, Online, Sleeping, Dead, Offline, Line, LineHealth, AndMore and NoTeam — is registered in LoadDefaultMessages so admins can translate or restyle the output.

Install it on your server

  1. Save TeamInfoCommand.cs

    Keep the file name exactly as the class name. Oxide matches them.
  2. 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.
  3. Edit the config if you want

    On first load the default config is written to oxide/config/TeamInfoCommand.json (Carbon: carbon/configs/TeamInfoCommand.json). After editing, reload it:oxide.reload TeamInfoCommand c.reload TeamInfoCommand # Carbon
  4. Grant the permission

    Give it to every player, or swap default for a group such as vip: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 version
  • Console and admin variant

    Add a console command so admins can inspect any player's team by name or ID.

    Generate this version
  • Team 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.

Prompt: Create a Rust Oxide plugin TeamInfoCommand with a permission-gated /team chat command that lists team members, marking leader, online, sleeping, dead and offline states, plus health and map grid for online members, with grid cell size, health, grid and max members in config.
Customize for my server

Edit the prompt on the next screen. 2 free plugins a month, no card.

Last updated Oct 26, 2026.

Customize team info command

Customize