How to make a Rust death messages plugin (Oxide/Carbon)
Broadcasts a formatted death message to the whole server for every player death, using OnPlayerDeath to read the victim, the attacker, the weapon and the distance and to pick a PvP, NPC, animal or environmental template from config.
The vanilla death screen tells a player how they died, but the rest of the server sees nothing. Admins who want kill feed style chat lines usually end up with hard-coded strings that go stale as soon as a new weapon or animal is added.
This plugin hooks OnPlayerDeath, inspects the HitInfo and the entity that killed the player, and picks one of four template groups from config: PvP, NPC, animal, and environment. PvP messages include the killer name, the victim name, the weapon's display name and the rounded distance between the two players. Animal and NPC messages use the attacker's prefab name translated through the Animal Names and Npc Names tables. Environmental deaths use the damage type from the hit to select a template.
Templates are standard .NET format strings, so admins can reorder fields or drop the distance entirely without touching the code.
The plugin code
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Death Notes", "RustPluginGenerator", "1.0.0")]
[Description("Broadcasts a configurable chat message for every player death")]
public class DeathNotes : RustPlugin
{
private const string PermAdmin = "deathnotes.admin";
private PluginConfig config;
private ulong lastVictim;
private float lastDeathTime;
private class PluginConfig
{
[JsonProperty("Broadcast Prefix")]
public string BroadcastPrefix = "<color=#ce422b>[Death]</color>";
[JsonProperty("Duplicate Window Seconds")]
public float DuplicateWindowSeconds = 0.5f;
[JsonProperty("Unknown Weapon")]
public string UnknownWeapon = "Unknown";
[JsonProperty("PvP Templates")]
public Dictionary<string, string> PvpTemplates = new Dictionary<string, string>
{
["Weapon"] = "{0} killed {1} with {2} at {3}m",
["NoWeapon"] = "{0} killed {1}"
};
[JsonProperty("Animal Templates")]
public Dictionary<string, string> AnimalTemplates = new Dictionary<string, string>
{
["Killed"] = "{1} was eaten by a {0}",
["Named"] = "{1} was killed by a {0}"
};
[JsonProperty("Npc Templates")]
public Dictionary<string, string> NpcTemplates = new Dictionary<string, string>
{
["Killed"] = "{1} was eliminated by {0}"
};
[JsonProperty("Environment Templates")]
public Dictionary<string, string> EnvironmentTemplates = new Dictionary<string, string>
{
["Fall"] = "{0} fell to their death",
["Cold"] = "{0} froze to death",
["Bleeding"] = "{0} bled out",
["Decay"] = "{0} decayed",
["Hunger"] = "{0} starved",
["Thirst"] = "{0} died of thirst",
["Radiation"] = "{0} died of radiation",
["Heat"] = "{0} burned to death",
["Suicide"] = "{0} ended it all",
["Default"] = "{0} died"
};
[JsonProperty("Animal Names")]
public Dictionary<string, string> AnimalNames = new Dictionary<string, string>
{
["bear"] = "Bear",
["polarbear"] = "Polar Bear",
["boar"] = "Boar",
["wolf"] = "Wolf",
["stag"] = "Stag",
["horse"] = "Horse",
["chicken"] = "Chicken",
["snake"] = "Snake",
["shark"] = "Shark",
["crocodile"] = "Crocodile",
["tiger"] = "Tiger",
["panther"] = "Panther"
};
[JsonProperty("Npc Names")]
public Dictionary<string, string> NpcNames = new Dictionary<string, string>
{
["scientistnpc"] = "Scientist",
["scientistnpc_arctic"] = "Arctic Scientist",
["scientistnpc_heavy"] = "Heavy Scientist",
["scientistnpc_junkpile"] = "Junkpile Scientist",
["tunneldweller"] = "Tunnel Dweller",
["underwaterdweller"] = "Underwater Dweller"
};
}
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>
{
["Death Message"] = "{0}",
["Test Header"] = "Test message for template group '{0}':",
["Reloaded"] = "DeathNotes config reloaded with {0} templates."
}, this);
}
private string Msg(string key, string userId = null, params object[] args)
{
return string.Format(lang.GetMessage(key, this, userId), args);
}
private void Init()
{
permission.RegisterPermission(PermAdmin, this);
}
private object OnPlayerDeath(BasePlayer player, HitInfo info)
{
if (player == null || info == null) return null;
if (config.DuplicateWindowSeconds > 0f && player.userID.Get() == lastVictim &&
Time.realtimeSinceStartup - lastDeathTime < config.DuplicateWindowSeconds)
return null;
lastVictim = player.userID.Get();
lastDeathTime = Time.realtimeSinceStartup;
var line = BuildLine(player, info);
if (string.IsNullOrEmpty(line)) return null;
Server.Broadcast(string.Format("{0} {1}", config.BroadcastPrefix, Msg("Death Message", null, line)));
return null;
}
private string BuildLine(BasePlayer victim, HitInfo info)
{
var victimName = victim.displayName;
var attackerPlayer = info.InitiatorPlayer;
if (attackerPlayer != null && attackerPlayer != victim)
{
var weapon = GetWeaponName(info);
var template = string.IsNullOrEmpty(weapon)
? Get(config.PvpTemplates, "NoWeapon", "{0} killed {1}")
: Get(config.PvpTemplates, "Weapon", "{0} killed {1} with {2} at {3}m");
var distance = Mathf.RoundToInt(Vector3.Distance(attackerPlayer.transform.position, victim.transform.position));
return string.Format(template, attackerPlayer.displayName, victimName, weapon, distance);
}
var initiator = info.Initiator;
var animal = initiator as BaseAnimalNPC;
if (animal != null)
{
var name = ReadableName(animal.ShortPrefabName, config.AnimalNames);
var template = Get(config.AnimalTemplates, "Killed", "{1} was killed by a {0}");
return string.Format(template, name, victimName);
}
var npc = initiator as BaseNpc;
if (npc != null)
{
var name = ReadableName(npc.ShortPrefabName, config.NpcNames);
var template = Get(config.NpcTemplates, "Killed", "{1} was eliminated by {0}");
return string.Format(template, name, victimName);
}
var damageType = info.damageTypes.GetMajorityDamageType();
var envKey = EnvironmentKey(damageType);
var envTemplate = Get(config.EnvironmentTemplates, envKey, Get(config.EnvironmentTemplates, "Default", "{0} died"));
return string.Format(envTemplate, victimName);
}
private static string GetWeaponName(HitInfo info)
{
var attack = info.Weapon;
if (attack == null) return null;
var item = attack.GetItem();
if (item?.info == null) return null;
return item.info.displayName.english;
}
private static string ReadableName(string prefab, Dictionary<string, string> table)
{
if (string.IsNullOrEmpty(prefab)) return "Unknown";
var key = prefab.ToLowerInvariant().Replace(".prefab", "");
if (key.StartsWith("npc_")) key = key.Substring(4);
if (table != null && table.TryGetValue(key, out var known)) return known;
return char.ToUpperInvariant(key[0]) + key.Substring(1).Replace('_', ' ');
}
private static string EnvironmentKey(Rust.DamageType type)
{
switch (type)
{
case Rust.DamageType.Fall: return "Fall";
case Rust.DamageType.Cold: return "Cold";
case Rust.DamageType.Bleeding: return "Bleeding";
case Rust.DamageType.Decay: return "Decay";
case Rust.DamageType.Hunger: return "Hunger";
case Rust.DamageType.Thirst: return "Thirst";
case Rust.DamageType.Radiation: return "Radiation";
case Rust.DamageType.Heat: return "Heat";
case Rust.DamageType.Suicide: return "Suicide";
default: return "Default";
}
}
private static string Get(Dictionary<string, string> table, string key, string fallback)
{
if (table != null && table.TryGetValue(key, out var value) && !string.IsNullOrEmpty(value))
return value;
return fallback;
}
[ChatCommand("deathnotes")]
private void CmdDeathNotes(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermAdmin))
{
SendReply(player, "You do not have permission to use this command.");
return;
}
if (args == null || args.Length < 2 || args[0].ToLowerInvariant() != "test")
{
SendReply(player, "Usage: /deathnotes test <pvp|animal|npc|env>");
return;
}
var group = args[1].ToLowerInvariant();
Dictionary<string, string> table;
switch (group)
{
case "animal": table = config.AnimalTemplates; break;
case "npc": table = config.NpcTemplates; break;
case "env": table = config.EnvironmentTemplates; break;
default: group = "pvp"; table = config.PvpTemplates; break;
}
SendReply(player, Msg("Test Header", player.UserIDString, group));
if (table == null) return;
foreach (var pair in table)
SendReply(player, string.Format("{0}: {1}", pair.Key, pair.Value));
}
[ConsoleCommand("deathnotes.reload")]
private void CcmdReload(ConsoleSystem.Arg arg)
{
if (arg == null) return;
var player = arg.Player();
if (player != null && !permission.UserHasPermission(player.UserIDString, PermAdmin))
{
arg.ReplyWith("You do not have permission to use this command.");
return;
}
LoadConfig();
var count = 0;
if (config.PvpTemplates != null) count += config.PvpTemplates.Count;
if (config.AnimalTemplates != null) count += config.AnimalTemplates.Count;
if (config.NpcTemplates != null) count += config.NpcTemplates.Count;
if (config.EnvironmentTemplates != null) count += config.EnvironmentTemplates.Count;
arg.ReplyWith(Msg("Reloaded", null, count));
}
}
}Hooks used: OnPlayerDeath
Config file
Created automatically on first load at oxide/config/DeathNotes.json.
{
"Broadcast Prefix": "<color=#ce422b>[Death]</color>",
"Duplicate Window Seconds": 0.5,
"Unknown Weapon": "Unknown",
"PvP Templates": {
"Weapon": "{0} killed {1} with {2} at {3}m",
"NoWeapon": "{0} killed {1}"
},
"Animal Templates": {
"Killed": "{1} was eaten by a {0}",
"Named": "{1} was killed by a {0}"
},
"Npc Templates": {
"Killed": "{1} was eliminated by {0}"
},
"Environment Templates": {
"Fall": "{0} fell to their death",
"Cold": "{0} froze to death",
"Bleeding": "{0} bled out",
"Decay": "{0} decayed",
"Hunger": "{0} starved",
"Thirst": "{0} died of thirst",
"Radiation": "{0} died of radiation",
"Heat": "{0} burned to death",
"Suicide": "{0} ended it all",
"Default": "{0} died"
},
"Animal Names": {
"bear": "Bear",
"polarbear": "Polar Bear",
"boar": "Boar",
"wolf": "Wolf",
"stag": "Stag",
"horse": "Horse",
"chicken": "Chicken",
"snake": "Snake",
"shark": "Shark",
"crocodile": "Crocodile",
"tiger": "Tiger",
"panther": "Panther"
},
"Npc Names": {
"scientistnpc": "Scientist",
"scientistnpc_arctic": "Arctic Scientist",
"scientistnpc_heavy": "Heavy Scientist",
"scientistnpc_junkpile": "Junkpile Scientist",
"tunneldweller": "Tunnel Dweller",
"underwaterdweller": "Underwater Dweller"
}
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/deathnotes test <pvp|animal|npc|env> | Prints every template in the chosen group to the caller, so admins can preview wording before saving the config. | deathnotes.admin |
deathnotes.reload | Reloads the configuration file from disk and reports how many templates were loaded. | deathnotes.admin |
| Permission | Grants |
|---|---|
deathnotes.admin | Grants access to the deathnotes.reload console command and the /deathnotes test chat command. |
How the code works
Read the victim and attacker from OnPlayerDeath
object OnPlayerDeath(BasePlayer player, HitInfo info)gives you the dying player and the hit that finished them. The attacker isinfo.InitiatorPlayerfor PvP. That value is null when the initiator is not a player, so the plugin falls back toinfo.Initiatorand tests it withas BaseAnimalNPCand thenas BaseNpc. Returnnullfrom the hook so Rust's own death handling runs unchanged.Turn prefab names into readable names
ShortPrefabNameon a bear isbear, on a boarboar, and scientist prefabs carry their variant in the name. TheReadableNamehelper lowercases the prefab, strips a.prefabsuffix, drops a leadingnpc_, looks the key up in theAnimal NamesorNpc Namesconfig dictionary, and otherwise capitalises the key and replaces underscores with spaces. New prefabs therefore still produce a reasonable message.Get the weapon and distance for PvP
GetWeaponNamereadsinfo.Weapon, callsGetItem()on it and returnsitem.info.displayName.english, the same name the player sees in their inventory. If the weapon or item is null the plugin uses theNoWeapontemplate instead of theWeaponone. Distance isVector3.Distancebetween the two players'transform.position, rounded withMathf.RoundToInt, so melee kills work as well as rifle shots.Classify environmental deaths
When no player, animal or NPC caused the death, the plugin calls
info.damageTypes.GetMajorityDamageType()and passes the result toEnvironmentKey, which maps it to a key inEnvironment Templates: Fall, Cold, Bleeding, Decay, Hunger, Thirst, Radiation, Heat or Suicide. Anything unmapped lands on theDefaultentry, so a new damage type never produces an empty line. Only the victim name is passed as a placeholder here.Format and broadcast through lang
Each template is looked up with the
Gethelper, which accepts a fallback string used when the config entry is missing or blank. The plugin formats the template withstring.Format, then wraps the result withMsg("Death Message", null, line), whose language file value defaults to{0}so admins can add a per-player prefix.Server.Broadcastsends the prefixed line to every connected player.Guard against duplicate deaths
A bleed-out tick can overlap a bullet in the same frame and fire
OnPlayerDeathtwice. The plugin stores the victim's user id inlastVictimand theTime.realtimeSinceStartupvalue inlastDeathTime; if the same victim dies again withinDuplicate Window Secondsfrom config (default 0.5) the hook returns early. Set the value to 0 to disable the check.
Install it on your server
Save DeathNotes.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/DeathNotes.json(Carbon:carbon/configs/DeathNotes.json). After editing, reload it:oxide.reload DeathNotes c.reload DeathNotes # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip:oxide.grant group default deathnotes.admin c.grant group default deathnotes.admin # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Per-team kill feed
Only show PvP deaths that involve a team member, with a different colour for team kills.
Generate this versionRange bonus line
Append a second line for long-range kills.
Generate this versionDiscord relay
Forward each broadcast to a webhook outside the plugin.
Generate this version
FAQ
Does this catch suicides and admin-killed players?
Yes. When the attacker is the victim or the initiator is not a player, animal or NPC, the damage type decides the template, and Rust.DamageType.Suicide has its own entry. Kills done from the server console produce no initiator, so they fall into the environment branch and use the Default text unless you map that damage type.
Why does the weapon show as "Unknown" sometimes?
GetWeaponName returns null when info.Weapon is null or has no associated item, for example damage from a fireball or a trap. The plugin then uses the NoWeapon template from PvP Templates, which you can rewrite to mention the cause or leave short.
How do I install and update it on Oxide or Carbon?
Drop DeathNotes.cs into oxide/plugins, wait for compilation, and the default config appears at oxide/config/DeathNotes.json. Carbon loads the same file unchanged. To apply template edits, save the config and run deathnotes.reload from the server console or RCON.
Can I keep the distance out of the message?
Yes. Edit the Weapon template in PvP Templates and remove the {3} placeholder. Format arguments are positional, so leaving extra arguments unused is harmless as long as every placeholder has a matching argument.
Does it spam chat on a busy wipe day?
Every death is broadcast, which on a 200-slot server can mean a line every few seconds. Raise Duplicate Window Seconds to merge rapid repeats from bleed-outs, or shorten the templates so each line takes less room in chat.
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 chat plugins
- Automatic announcementsAutoBroadcast sends a list of chat messages to every online player on a repeating timer, in order or random order, and fills {players} and {time}โฆOnServerInitialized
- Chat loggerRecords every chat message with timestamp, SteamID, name, channel and text into a capped oxide/data file, and adds a /chatlog command that prints aโฆOnServerInitializedOnPlayerChat
- Chat mentionsDetects @name tokens in chat and sends the mentioned online player a private notice plus a notification sound, with a per-sender cooldown.OnPlayerChatOnPlayerDisconnected
Last updated Oct 26, 2026.
Customize death messages
Customize