Skip to content
RustPluginGenerator

How to make a Rust map grid location plugin (chat /grid command)

A /grid chat command that converts a player's world position into a Rust map grid cell (like G12) using the server's world size, with exact coordinates shown only to admins. It uses OnServerInitialized to cache the world size and OnPlayerConnected to announce the player's grid on join.

Vanilla Rust shows a grid reference in the map UI but gives you no chat command to print it, so players who call out coordinates in voice or text often read the grid wrong, and admins handling reports have no quick way to ask where someone is. This plugin adds a /grid chat command that reads player.transform.position, converts it to the map's grid index using ConVar.Server.worldsize, and replies with a letter/number cell such as G12. The number column counts from the south, the letter row from the west, matching the in-game map grid. Admins and players holding the mapgridlocation.coords permission also see the raw X/Z coordinates. The world size is read once in OnServerInitialized, the grid is announced to each player on OnPlayerConnected, and the config lets you change the join announcement, the cell count and whether the grid is shown.

The plugin code

MapGridLocation.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using Newtonsoft.Json;
using Oxide.Core;

namespace Oxide.Plugins
{
    [Info("Map Grid Location", "RustPluginGenerator", "1.0.0")]
    [Description("Adds a /grid command that reports the player's map grid cell and shows exact coordinates to admins.")]
    public class MapGridLocation : RustPlugin
    {
        private const string PermUse = "mapgridlocation.use";
        private const string PermCoords = "mapgridlocation.coords";

        private PluginConfig config;
        private float worldSize;
        private float cellSize;
        private readonly List<Timer> timers = new List<Timer>();

        private class PluginConfig
        {
            [JsonProperty("Announce On Connect")]
            public bool AnnounceOnConnect { get; set; } = true;

            [JsonProperty("Grid Cells")]
            public int GridCells { get; set; } = 30;

            [JsonProperty("Show Grid In Reply")]
            public bool ShowGridInReply { get; set; } = true;
        }

        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>
            {
                ["GridReply"] = "You are in grid {0}.",
                ["GridReplyCoords"] = "You are in grid {0} (X: {1}, Z: {2}, Y: {3}).",
                ["GridUnknown"] = "Could not work out your grid position.",
                ["NoPermission"] = "You do not have permission to use this command.",
                ["Announce"] = "You are currently in grid {0}."
            }, 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);
            permission.RegisterPermission(PermCoords, this);
        }

        private void OnServerInitialized()
        {
            worldSize = ConVar.Server.worldsize;
            RefreshCellSize();
        }

        private void RefreshCellSize()
        {
            int cells = config.GridCells <= 0 ? 30 : config.GridCells;
            if (worldSize <= 0f) worldSize = ConVar.Server.worldsize;
            if (worldSize <= 0f) worldSize = 3000f;
            cellSize = worldSize / cells;
        }

        private string PositionToGrid(Vector3 pos)
        {
            if (cellSize <= 0f) RefreshCellSize();

            int cells = config.GridCells <= 0 ? 30 : config.GridCells;
            float half = worldSize * 0.5f;
            float shiftedX = (pos.x + half) / cellSize;
            float shiftedZ = (pos.z + half) / cellSize;
            int column = Mathf.FloorToInt(shiftedX);
            int row = Mathf.FloorToInt(shiftedZ);

            if (column < 0) column = 0;
            if (column > cells - 1) column = cells - 1;
            if (row < 0) row = 0;
            if (row > cells - 1) row = cells - 1;

            char letter = (char)('A' + row);
            return letter + (column + 1).ToString();
        }

        private string BuildReply(BasePlayer player, string userId, bool withCoords)
        {
            Vector3 pos = player.transform.position;
            string grid = PositionToGrid(pos);
            string coords = string.Format("{0}, {1}, {2}",
                Mathf.RoundToInt(pos.x), Mathf.RoundToInt(pos.z), Mathf.RoundToInt(pos.y));

            if (!config.ShowGridInReply)
            {
                return withCoords
                    ? string.Format("X: {0}, Z: {1}, Y: {2}", Mathf.RoundToInt(pos.x), Mathf.RoundToInt(pos.z), Mathf.RoundToInt(pos.y))
                    : Msg("GridReply", userId, grid);
            }

            if (withCoords)
                return Msg("GridReplyCoords", userId, grid, Mathf.RoundToInt(pos.x), Mathf.RoundToInt(pos.z), Mathf.RoundToInt(pos.y));

            return Msg("GridReply", userId, grid);
        }

        [ChatCommand("grid")]
        private void CmdGrid(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            string userId = player.UserIDString;
            if (!permission.UserHasPermission(userId, PermUse))
            {
                SendReply(player, Msg("NoPermission", userId));
                return;
            }

            bool withCoords = permission.UserHasPermission(userId, PermCoords) || player.IsAdmin;
            SendReply(player, BuildReply(player, userId, withCoords));
        }

        private void OnPlayerConnected(BasePlayer player)
        {
            if (player == null || !config.AnnounceOnConnect) return;
            if (!permission.UserHasPermission(player.UserIDString, PermUse)) return;

            Timer t = timer.Once(3f, () =>
            {
                if (player == null || !player.IsConnected) return;
                SendReply(player, Msg("Announce", player.UserIDString, PositionToGrid(player.transform.position)));
            });
            timers.Add(t);
        }

        private void Unload()
        {
            foreach (Timer t in timers)
            {
                if (t != null) t.Destroy();
            }
            timers.Clear();
        }
    }
}

Hooks used: OnServerInitialized, OnPlayerConnected

Config file

Created automatically on first load at oxide/config/MapGridLocation.json.

MapGridLocation.json
{
  "Announce On Connect": true,
  "Grid Cells": 30,
  "Show Grid In Reply": true
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
/gridReplies with your current map grid cell. Adds exact X/Z coordinates when you have the admin permission.mapgridlocation.use
Permissions
PermissionGrants
mapgridlocation.useGrants access to the /grid chat command and the connect announcement.
mapgridlocation.coordsAdds exact X/Z world coordinates and height to the /grid reply.

How the code works

  1. Cache the world size once

    The grid is drawn from the world origin to the world edge, so the plugin needs ConVar.Server.worldsize. OnServerInitialized copies it into the float worldSize field and calls RefreshCellSize, which divides it by the config Grid Cells value (default 30) to fill the float cellSize field. Doing this once avoids reading the convar on every command call. If Grid Cells is zero or negative, the helper falls back to 30.

  2. Convert a position to a grid cell

    Positions run from -worldSize/2 to +worldSize/2 on both axes. PositionToGrid adds worldSize * 0.5f to X and Z to shift the numbers to 0-based, divides by cellSize, floors with Mathf.FloorToInt, and clamps to the 0..(cells-1) range. The letter comes from the Z row ((char)('A' + row)) so the letter row counts from the south and the number column counts from the east, matching the map. The result is a string like G12.

  3. The /grid chat command

    [ChatCommand("grid")] declares CmdGrid(BasePlayer player, string command, string[] args). It checks permission.UserHasPermission for mapgridlocation.use and answers via BuildReply. That helper reads player.transform.position, builds the cell, and chooses the lang key GridReply or GridReplyCoords. A player with mapgridlocation.coords or the IsAdmin flag gets the coordinates variant. An alternative reply without the grid is built when Show Grid In Reply is false.

  4. Announcing the grid on join

    OnPlayerConnected(BasePlayer player) fires when a player finishes connecting. If Announce On Connect is true and the player has mapgridlocation.use, the plugin schedules a one-shot timer.Once(3f, ...) so the message lands after connection spam. Inside the callback it checks IsConnected again and sends the Announce lang key. The timer is added to the timers list so Unload can destroy it if the plugin is reloaded mid-delay.

  5. Config and language files

    PluginConfig holds Announce On Connect, Grid Cells and Show Grid In Reply, mapped with [JsonProperty] names. LoadConfig reads the object, prints a warning and reloads defaults when parsing fails, then saves. LoadDefaultMessages registers GridReply, GridReplyCoords, GridUnknown, NoPermission and Announce, so an owner can edit translations in oxide/lang/en/MapGridLocation.json without recompiling.

  6. Handling edge cases

    If cellSize is not positive when a command runs, PositionToGrid calls RefreshCellSize, which re-reads ConVar.Server.worldsize and falls back to 3000 when the convar is unset. Column and row values outside the grid are clamped to the nearest valid cell rather than producing an out-of-range letter. Unload walks the timers list, calls Destroy() on each Timer, and clears the list so a reload does not leave a stale callback.

Install it on your server

  1. Save MapGridLocation.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/MapGridLocation.json (Carbon: carbon/configs/MapGridLocation.json). After editing, reload it:oxide.reload MapGridLocation c.reload MapGridLocation # Carbon
  4. Grant the permission

    Give it to every player, or swap default for a group such as vip:oxide.grant group default mapgridlocation.use c.grant group default mapgridlocation.use # Carbon

Variations

Each one opens the generator with a ready prompt you can tweak.

  • Show grid to teammates

    Add a /teamgrid command that prints every online team member's grid for the caller.

    Generate this version
  • Broadcast admin coordinate request

    Let staff query another player's grid by name.

    Generate this version
  • Auto-grid on clan chat

    Prefix chat messages with the sender's grid when the message starts with the grid prefix.

    Generate this version

FAQ

Which axis maps to the letter and which to the number?

On the vanilla Rust map the letters run north-south (Z) and the numbers run east-west (X). The plugin divides player.transform.position.x by cellSize to get the number column and transform.position.z by cellSize to get the letter row, matching the in-game grid so the /grid result lines up with the map overlay.

Does it work on custom-size or custom-cell maps?

Set Grid Cells to the number of divisions your map overlay draws (usually 30). The plugin divides ConVar.Server.worldsize by that number, so a 4000-size map with 30 cells gives cells of roughly 133 units, which matches the Rust map. If another plugin redraws the grid, set Grid Cells to its division count.

Does the plugin store or log positions?

No. Every /grid call computes the cell from the live position and sends a chat reply. Nothing is written to oxide/data and nothing is printed to the server console. If you want a log, add a Puts line in CmdGrid and it will appear in the console output.

How do I install it?

Save the plugin as MapGridLocation.cs, drop it into oxide/plugins/, and run oxide.reload MapGridLocation (or restart the server). On Carbon, place the same file in carbon/plugins/. The default config is created on first load, and you can add the permissions with oxide.grant.

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 named MapGridLocation with a /grid chat command that converts the player's position to a map grid cell using ConVar.Server.worldsize and a configurable 30-cell grid, showing exact coordinates to admins.
Customize for my server

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

Last updated Oct 26, 2026.

Customize map grid location

Customize