How to make a Rust team death alerts plugin (killer and grid)
Sends a chat alert to every online teammate when a player dies, including the killer name and the map grid of the death. Uses OnPlayerDeath and RelationshipManager team lookup.
When a teammate dies while you are across the map, the vanilla game gives you almost nothing: no killer name, no location, just a status change in the team list. Admins often want a proper alert so groups actually know what happened and can react.
This plugin hooks OnPlayerDeath, resolves the victim's team through RelationshipManager.ServerInstance.FindTeam, and sends a formatted chat line to each member who is online and not the victim. The killer is read from info.Initiator: a player gets their display name, common NPCs get a readable label, and other entities fall back to their short prefab name. The grid is derived from World.Size, the configured cell size and transform.position with a small helper, so no external map plugin is needed.
Everything is configurable: alert format, whether to include the grid, whether to skip suicide or environment deaths, and a cooldown so a team is not spammed when several members die at once during a raid.
The plugin code
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Team Death Alerts", "RustPluginGenerator", "1.0.0")]
[Description("Sends online teammates a chat alert with the killer name and map grid when a player dies.")]
public class TeamDeathAlerts : RustPlugin
{
private const string PermUse = "teamdeathalerts.use";
private const string PermAdmin = "teamdeathalerts.admin";
private PluginConfig config;
private readonly Dictionary<ulong, float> lastAlert = new Dictionary<ulong, float>();
private class PluginConfig
{
[JsonProperty("Alert format (body text)")]
public string AlertFormat { get; set; } = "<color=#ff6b6b>{victim}</color> was killed by <color=#ffd166>{killer}</color> at grid <color=#8ecae6>{grid}</color> ({damage}).";
[JsonProperty("Show grid in alerts")]
public bool ShowGrid { get; set; } = true;
[JsonProperty("Grid cell size (metres)")]
public float GridSize { get; set; } = 146f;
[JsonProperty("Ignore suicide deaths")]
public bool IgnoreSuicide { get; set; } = true;
[JsonProperty("Ignore deaths with no attacker")]
public bool IgnoreEnvironment { get; set; } = false;
[JsonProperty("Alert cooldown per team (seconds)")]
public float AlertCooldown { get; set; } = 3f;
}
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>
{
["Alert"] = "{0}",
["UnknownKiller"] = "an unknown source",
["DamageFormat"] = "{0} damage",
["TestPrefix"] = "[TeamDeathAlerts] Sample alert:",
["StatusHeader"] = "TeamDeathAlerts status:",
["NoPermission"] = "You do not have permission to use this command."
}, this);
}
private void Init()
{
permission.RegisterPermission(PermUse, this);
permission.RegisterPermission(PermAdmin, this);
}
private string Msg(string key, string userId = null, params object[] args)
=> string.Format(lang.GetMessage(key, this, userId), args);
private object OnPlayerDeath(BasePlayer player, HitInfo info)
{
if (player == null || player.IsNpc) return null;
if (player.currentTeam == 0) return null;
var damageType = info?.damageTypes != null
? info.damageTypes.GetMajorityDamageType()
: Rust.DamageType.Generic;
if (config.IgnoreSuicide && damageType == Rust.DamageType.Suicide) return null;
BaseEntity attacker = info?.Initiator;
if (config.IgnoreEnvironment && attacker == null) return null;
var team = RelationshipManager.ServerInstance.FindTeam(player.currentTeam);
if (team == null || team.members == null || team.members.Count < 2) return null;
float now = Time.realtimeSinceStartup;
float last;
if (config.AlertCooldown > 0f && lastAlert.TryGetValue(player.currentTeam, out last) && now - last < config.AlertCooldown)
return null;
lastAlert[player.currentTeam] = now;
string killer = ResolveKillerName(attacker);
string grid = config.ShowGrid ? GetGrid(player.transform.position) : "";
string damage = Msg("DamageFormat", null, PrettyDamage(damageType));
string body = config.AlertFormat
.Replace("{victim}", player.displayName)
.Replace("{killer}", killer)
.Replace("{grid}", grid)
.Replace("{damage}", damage);
foreach (ulong memberId in team.members)
{
if (memberId == player.userID.Get()) continue;
BasePlayer member = BasePlayer.FindByID(memberId);
if (member == null || !member.IsConnected || member.IsSleeping()) continue;
member.ChatMessage(Msg("Alert", member.UserIDString, body));
}
return null;
}
private string ResolveKillerName(BaseEntity attacker)
{
if (attacker == null) return Msg("UnknownKiller");
var attackerPlayer = attacker as BasePlayer;
if (attackerPlayer != null)
return attackerPlayer.displayName;
var scientist = attacker as ScientistNPC;
if (scientist != null)
{
if (scientist.ShortPrefabName != null && scientist.ShortPrefabName.Contains("scarecrow"))
return "Scarecrow";
return "Scientist";
}
if (attacker is BaseAnimalNPC) return "Animal";
if (attacker is PatrolHelicopter) return "Patrol Helicopter";
if (attacker is BradleyAPC) return "Bradley APC";
return attacker.ShortPrefabName ?? Msg("UnknownKiller");
}
private string PrettyDamage(Rust.DamageType type)
{
switch (type)
{
case Rust.DamageType.Bullet: return "bullet";
case Rust.DamageType.Explosion: return "explosion";
case Rust.DamageType.Slash: return "melee";
case Rust.DamageType.Stab: return "melee";
case Rust.DamageType.Blunt: return "melee";
case Rust.DamageType.Heat: return "fire";
case Rust.DamageType.Fall: return "fall";
case Rust.DamageType.Suicide: return "suicide";
case Rust.DamageType.Decay: return "decay";
case Rust.DamageType.Bite: return "animal";
default: return type.ToString().ToLowerInvariant();
}
}
private string GetGrid(Vector3 pos)
{
float cell = config.GridSize <= 0f ? 146f : config.GridSize;
int column = Mathf.FloorToInt((pos.x + World.Size / 2f) / cell);
int row = Mathf.FloorToInt((pos.z + World.Size / 2f) / cell);
char letter = (char)('A' + Mathf.Clamp(column, 0, 25));
return string.Concat(letter, row.ToString());
}
[ChatCommand("tda")]
private void CmdTda(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermUse))
{
player.ChatMessage(Msg("NoPermission", player.UserIDString));
return;
}
if (args == null || args.Length == 0 || args[0].ToLowerInvariant() != "test")
{
player.ChatMessage("/tda test");
return;
}
string body = config.AlertFormat
.Replace("{victim}", player.displayName)
.Replace("{killer}", "Example Killer")
.Replace("{grid}", config.ShowGrid ? GetGrid(player.transform.position) : "")
.Replace("{damage}", Msg("DamageFormat", null, "bullet"));
player.ChatMessage(Msg("TestPrefix", player.UserIDString));
player.ChatMessage(Msg("Alert", player.UserIDString, body));
}
[ConsoleCommand("teamdeathalerts.status")]
private void CcmdStatus(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player != null && !permission.UserHasPermission(player.UserIDString, PermAdmin))
{
player.ChatMessage(Msg("NoPermission", player.UserIDString));
return;
}
string text = Msg("StatusHeader") +
"\nAlert format: " + config.AlertFormat +
"\nShow grid: " + config.ShowGrid +
"\nGrid size: " + config.GridSize +
"\nIgnore suicide: " + config.IgnoreSuicide +
"\nIgnore environment: " + config.IgnoreEnvironment +
"\nCooldown: " + config.AlertCooldown;
if (player != null) player.ChatMessage(text);
else arg.ReplyWith(text);
}
}
}Hooks used: OnPlayerDeath
Config file
Created automatically on first load at oxide/config/TeamDeathAlerts.json.
{
"Alert format (body text)": "<color=#ff6b6b>{victim}</color> was killed by <color=#ffd166>{killer}</color> at grid <color=#8ecae6>{grid}</color> ({damage}).",
"Show grid in alerts": true,
"Grid cell size (metres)": 146,
"Ignore suicide deaths": true,
"Ignore deaths with no attacker": false,
"Alert cooldown per team (seconds)": 3
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/tda test | Sends you a sample death alert to preview the configured format. | teamdeathalerts.use |
teamdeathalerts.status | Prints the current config values to the caller or the server console. | teamdeathalerts.admin |
| Permission | Grants |
|---|---|
teamdeathalerts.use | Allows the player to run /tda test to preview an alert. |
teamdeathalerts.admin | Allows running the teamdeathalerts.status console command from the server console, RCON or in game. |
How the code works
Reading the hook and filtering deaths
OnPlayerDeath(BasePlayer player, HitInfo info)fires for every death. The method returns early for null players, NPCs and players without a team (currentTeam == 0). It readsinfo.damageTypes.GetMajorityDamageType()and skips the death whenIgnoreSuicideis true and the type isRust.DamageType.Suicide. WhenIgnoreEnvironmentis on it also skips deaths whereinfo.Initiatoris null. Returning null keeps vanilla behavior because this hook is only used for observation.Resolving the team and its online members
The plugin calls
RelationshipManager.ServerInstance.FindTeam(player.currentTeam)and bails out if the team is null or has fewer than two members. It then iteratesteam.members, resolving each ulong id withBasePlayer.FindByID, which returns null for offline players.IsConnectedis checked andIsSleeping()is skipped, and the dead player is excluded by comparingmemberIdwithplayer.userID.Get()so nobody is messaged about their own death.Naming the killer
ResolveKillerNametakesinfo.Initiator. ABasePlayerattacker contributesdisplayName. AScientistNPCis labeled Scarecrow or Scientist depending on whetherShortPrefabNamecontainsscarecrow, aBaseAnimalNPCis Animal, andPatrolHelicopterandBradleyAPCget their own labels. Anything else falls back toShortPrefabName, and a null attacker returns theUnknownKillerlang entry. The majority damage type is converted to a short word byPrettyDamageand placed in the{damage}placeholder.Computing the grid
Rust maps are square and centered, so the column is
Mathf.FloorToInt((pos.x + World.Size / 2f) / cell)and the row the same on z, wherecellcomes from theGridSizeconfig key and defaults to 146. The column is clamped to 0-25 and turned into a letter with(char)('A' + column). WhenShowGridis false the{grid}placeholder is replaced with an empty string instead.Formatting and sending the alert
The
AlertFormatstring from the config holds placeholders{victim},{killer},{grid}and{damage}. The plugin builds the body once, then sends it to each qualifying teammate withmember.ChatMessageusing theAlertlang key, which is a{0}wrapper around the body. Wrapper, prefix and unknown-killer strings come fromLoadDefaultMessagesinoxide/lang/en/TeamDeathAlerts.json; the body itself is edited in the config.Cooldown and commands
A
Dictionary<ulong, float>keyed by team id stores the last alert time fromTime.realtimeSinceStartup. WhenAlertCooldownis above zero and the new death is inside the window, the alert is suppressed so a wipe does not flood the chat./tda testis checked againstteamdeathalerts.useand renders the format with sample values;teamdeathalerts.statusis checked againstteamdeathalerts.adminand prints each config value.
Install it on your server
Save TeamDeathAlerts.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/TeamDeathAlerts.json(Carbon:carbon/configs/TeamDeathAlerts.json). After editing, reload it:oxide.reload TeamDeathAlerts c.reload TeamDeathAlerts # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip:oxide.grant group default teamdeathalerts.use c.grant group default teamdeathalerts.use # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Team chat only
Sends the alert to the team's chat channel instead of a direct message to each member.
Generate this versionPosition on the map
Replaces the grid text with a map marker on the death location.
Generate this versionKill feed to all players
Broadcasts every PvP kill to the whole server, not just the victim's team.
Generate this version
FAQ
Does this alert players who are offline or sleeping?
No. BasePlayer.FindByID only returns a player object for connected clients, and the plugin additionally checks IsConnected and skips sleeping players. Offline teammates see nothing; a data-file queue could be added if you want them notified after reconnect.
Are NPC and animal kills reported?
Yes by default. When info.Initiator is a scientist, animal, helicopter or Bradley the alert uses the matching label from ResolveKillerName. Set IgnoreEnvironment to true to skip deaths where info.Initiator is null, and IgnoreSuicide to hide suicide deaths.
Why do I get no alert on some deaths?
The most common causes are AlertCooldown suppressing a second death in the same team within the configured window, the victim having no team (currentTeam is 0), or a plugin such as a PvE damage blocker preventing the kill from reaching OnPlayerDeath.
How do I install it?
Drop TeamDeathAlerts.cs into oxide/plugins, let Oxide compile it, then edit oxide/config/TeamDeathAlerts.json and run oxide.reload TeamDeathAlerts. Carbon loads the same file from its plugins folder and supports the same config and lang layout.
How do I change the wording?
Edit oxide/lang/en/TeamDeathAlerts.json for the wrapper line and AlertFormat in the config for the body. Placeholders are plain replacements, so any mix of {victim}, {killer}, {grid} and {damage} works, including repeating a placeholder.
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 info commandAdds a /team chat command that lists every member of the caller's Rust team with online status, health and map grid for online members.
Last updated Oct 26, 2026.
Customize team death alerts
Customize