Death Notes doesn't report minicopter or attack helicopter crash deathsSolved

This thread represents both a known issue and the solution. I noticed that the plugin wouldn't correctly report minicoter or attack heli deaths. I have updated the code to correctly report this. Unfortunately scrap heli deaths do not appear to be something easily reportable since Rust reports those deaths as suicides to both the scrap heli and the passenger (?!). In any case, below is the update plugin code to handle this. I also did some cleanup on my previous patrol heli and bradley tagging and death notes code.

// #define DEBUG

using Newtonsoft.Json;
using Oxide.Core;
using Rust;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;

namespace Oxide.Plugins
{
	using EnemyPrefabs = DeathNotes.RemoteConfiguration<Dictionary<string, string>>;
	using WeaponPrefabs = DeathNotes.RemoteConfiguration<Dictionary<string, string>>;
	using CombatEntityTypes = DeathNotes.RemoteConfiguration<Dictionary<string, DeathNotes.CombatEntityType>>;

	[Info("Death Notes", "Terceran/LaserHydra/Mevent", "6.4.0")]
	[Description("Broadcasts deaths to chat along with detailed information")]
	class DeathNotes : RustPlugin
	{
		#region Fields

		private const string WildcardCharacter = "*";
		private const string CanSeePermission = "deathnotes.cansee";
        //Added by Terceran on 20241230
        private const string SuppressPermission = "deathnotes.suppress";

		private static DeathNotes _instance;

		private PluginConfiguration _configuration;

		private readonly EnemyPrefabs _enemyPrefabs = new EnemyPrefabs("EnemyPrefabs");
		private readonly WeaponPrefabs _weaponPrefabs = new WeaponPrefabs("WeaponPrefabs");
		private readonly CombatEntityTypes _combatEntityTypes = new CombatEntityTypes("CombatEntityTypes");

		private readonly Regex _colorTagRegex =
			new Regex(@"<color=.{0,7}>", RegexOptions.Compiled | RegexOptions.IgnoreCase);

		private readonly Regex _sizeTagRegex =
			new Regex(@"<size=\d*>", RegexOptions.Compiled | RegexOptions.IgnoreCase);

		private readonly List<string> _richTextLiterals = new List<string>
		{
			"</color>", "</size>", "<b>", "</b>", "<i>", "</i>"
		};

		private readonly Dictionary<ulong, AttackInfo> _previousAttack = new Dictionary<ulong, AttackInfo>();
        
        //These were added by Terceran on 1/9/2025
        private readonly Dictionary<ulong, HitInfo> patrolHeliTagTracker = new Dictionary<ulong, HitInfo>();
        private readonly Dictionary<ulong, HitInfo> bradleyTagTracker = new Dictionary<ulong, HitInfo>();

		private readonly Func<PluginConfiguration.DeathMessage, DeathData, bool>[] _messageMatchingStages =
		{
			(m, d) => MatchesCombatEntityType(d.KillerEntityType, m.KillerType) &&
			          MatchesCombatEntityType(d.VictimEntityType, m.VictimType) &&
			          MatchesDamageType(d.DamageType, m.DamageType),

			(m, d) => MatchesCombatEntityType(d.KillerEntityType, m.KillerType) &&
			          MatchesCombatEntityType(d.VictimEntityType, m.VictimType) &&
			          m.DamageType == WildcardCharacter,

			(m, d) => MatchesCombatEntityType(d.KillerEntityType, m.KillerType) &&
			          m.VictimType == WildcardCharacter &&
			          MatchesDamageType(d.DamageType, m.DamageType),

			(m, d) => m.KillerType == WildcardCharacter &&
			          MatchesCombatEntityType(d.VictimEntityType, m.VictimType) &&
			          MatchesDamageType(d.DamageType, m.DamageType),

			(m, d) => MatchesCombatEntityType(d.KillerEntityType, m.KillerType) &&
			          m.VictimType == WildcardCharacter &&
			          m.DamageType == WildcardCharacter,

			(m, d) => m.KillerType == WildcardCharacter &&
			          MatchesCombatEntityType(d.VictimEntityType, m.VictimType) &&
			          m.DamageType == WildcardCharacter,

			(m, d) => m.KillerType == WildcardCharacter &&
			          m.VictimType == WildcardCharacter &&
			          MatchesDamageType(d.DamageType, m.DamageType),

			(m, d) => m.KillerType == WildcardCharacter &&
			          m.VictimType == WildcardCharacter &&
			          m.DamageType == WildcardCharacter
		};

		#endregion

		#region Hooks

		private void Init()
		{
			_instance = this;

			permission.RegisterPermission(CanSeePermission, this);
            
            //Added by Terceran on 20241230
            permission.RegisterPermission(SuppressPermission, this);

			_configuration = Config.ReadObject<PluginConfiguration>();
			_configuration.LoadDefaults();
			Config.WriteObject(_configuration);

			_enemyPrefabs.Load();
			_weaponPrefabs.Load();
			_combatEntityTypes.Load();
		}

		private void Unload()
		{
			_instance = null;
		}

		private void OnEntityTakeDamage(BasePlayer victimEntity, HitInfo hitInfo)
		{

			if (victimEntity == null || hitInfo == null)
            	return;

			// Don't track bleeding
			if (victimEntity.lastDamage == DamageType.Bleeding)
				return;
                
            var userId = victimEntity.ToPlayer().userID;

			_previousAttack[userId] = new AttackInfo
			{
				HitInfo = hitInfo,
				Attacker = victimEntity.lastAttacker ?? hitInfo?.Initiator,
				DamageType = victimEntity.lastDamage
			};
		}
        
        //This method tracks Bradley APC damage and reports whoever tagged it.
        //Added by Terceran on 9/2/2024
        private void OnEntityTakeDamage(BradleyAPC victimEntity, HitInfo hitInfo)
        {

			if (victimEntity == null || hitInfo == null)
            	return;

			ulong netID = victimEntity.net.ID.Value;
			bool justTagged = (victimEntity.lastAttackedTime == float.NegativeInfinity);
			if (!bradleyTagTracker.ContainsKey(netID))
				bradleyTagTracker.Add(netID,new HitInfo());

           	if (hitInfo.WeaponPrefab != null && bradleyTagTracker[netID].WeaponPrefab != hitInfo.WeaponPrefab)
           		bradleyTagTracker[netID].WeaponPrefab = hitInfo.WeaponPrefab;

			if (hitInfo.Weapon != null && bradleyTagTracker[netID].Weapon != hitInfo.Weapon)
            	bradleyTagTracker[netID].Weapon = hitInfo.Weapon;
            
            if (bradleyTagTracker[netID].Initiator == null && hitInfo.Initiator != null)
            	bradleyTagTracker[netID].Initiator = hitInfo.Initiator;
            
            if (!justTagged)
            	return;

            if (hitInfo.Initiator != null)
            {

                var data = new DeathData
                {
                	VictimEntity = victimEntity,
                    KillerEntity = victimEntity.lastAttacker ?? hitInfo?.Initiator,
                    VictimEntityType = GetCombatEntityType(victimEntity),
                    KillerEntityType = GetCombatEntityType(victimEntity.lastAttacker),
                    DamageType = victimEntity.lastDamage,
                    HitInfo = bradleyTagTracker[netID]
                };

                if (hitInfo.Initiator?.ToPlayer()?.displayName != null)
                {
                	bradleyTagTracker[netID].Initiator = hitInfo.Initiator;

                    string weaponName = GetCustomizedWeaponName(data);
                    var tagPlayer = hitInfo.Initiator?.ToPlayer();
                    string message = "<color=#C4FF00>" + tagPlayer?.displayName +
                    				 "</color> has tagged the <color=#C4FF00>Bradley APC</color> with their <color=#C4FF00>" +
                      				 weaponName + "</color>.";

					//Added by Terceran on 20241231
					if (_configuration.ShowBradleyTags && !permission.UserHasPermission(tagPlayer?.UserIDString, SuppressPermission))
                    {
                    	object hookResult = Interface.Call("OnDeathNotice", data.ToDictionary(), message);

						if (hookResult?.Equals(false) ?? false)
                    		return;

                        if (_configuration.ShowInChat && _configuration.ShowBradleyTags)
                        {
                        	foreach (var player in BasePlayer.activePlayerList)
                            {
                                if (_configuration.RequirePermission &&
                                    !permission.UserHasPermission(player.UserIDString, CanSeePermission))
                                    continue;

                                if (_configuration.MessageRadius != -1 &&
                                    player.Distance(data.VictimEntity) > _configuration.MessageRadius)
                                    continue;

                                Player.Reply(
                                    player,
                                    _configuration.ChatFormat.Replace("{message}", message),
                                    ulong.Parse(_configuration.ChatIcon)
                                );
                            }
                        }

                        if (_configuration.ShowInConsole)
                            Puts(StripRichText(message));
                        
                    }
                        
                }
               
            }

		}


        //This method tracks Patrol Helicopter (Karen) damage and reports whoever tagged it.
        //Added by Terceran on 9/1/2024, updated 1/8/2025
		private void OnPatrolHelicopterTakeDamage(PatrolHelicopter victimEntity, HitInfo hitInfo)
        {
			if (victimEntity == null || hitInfo == null) return;

			ulong netID = victimEntity.net.ID.Value;
			bool justTagged = (victimEntity.lastAttackedTime == float.NegativeInfinity);
			if (!patrolHeliTagTracker.ContainsKey(netID))
				patrolHeliTagTracker.Add(netID,new HitInfo());
    
			if (hitInfo.WeaponPrefab != null && patrolHeliTagTracker[netID].WeaponPrefab != hitInfo.WeaponPrefab)
            	patrolHeliTagTracker[netID].WeaponPrefab = hitInfo.WeaponPrefab;
            
            if (hitInfo.Weapon != null && patrolHeliTagTracker[netID].Weapon != hitInfo.Weapon)
            	patrolHeliTagTracker[netID].Weapon = hitInfo.Weapon;

            if (patrolHeliTagTracker[netID].Initiator == null && hitInfo.Initiator != null)
            	patrolHeliTagTracker[netID].Initiator = hitInfo.Initiator;

            if (!justTagged)
            	return;

			if (hitInfo.Initiator != null)
            {
            
				var data = new DeathData
				{
					VictimEntity = victimEntity,
					KillerEntity = victimEntity.lastAttacker ?? hitInfo?.Initiator,
					VictimEntityType = GetCombatEntityType(victimEntity),
					KillerEntityType = GetCombatEntityType(victimEntity.lastAttacker),
					DamageType = victimEntity.lastDamage,
                    HitInfo = patrolHeliTagTracker[netID]
				};

				//Comment out the entire below if statement if you do not want to report Patrol Helicopter tagging.
				if (hitInfo.Initiator?.ToPlayer().displayName != null)
                {
                	patrolHeliTagTracker[netID].Initiator = hitInfo.Initiator;
                    
                	string weaponName = GetCustomizedWeaponName(data);
                    var tagPlayer = hitInfo.Initiator?.ToPlayer();
					string message = "<color=#C4FF00>" + tagPlayer?.displayName +
                    				 "</color> has tagged the <color=#C4FF00>Patrol Helicopter (Karen)</color> with their <color=#C4FF00>" +
                                     weaponName + "</color>. She's going to complain to the manager!";

					//Added by Terceran on 20241231
					if (_configuration.ShowPatrolHeliTags && !permission.UserHasPermission(tagPlayer?.UserIDString, SuppressPermission))
                    {

                        object hookResult = Interface.Call("OnDeathNotice", data.ToDictionary(), message);

                        if (hookResult?.Equals(false) ?? false)
                            return;
     
                        if (_configuration.ShowInChat)
                        {

                        	foreach (var player in BasePlayer.activePlayerList)
                            {
                            	if (_configuration.RequirePermission &&
                                    !permission.UserHasPermission(player.UserIDString, CanSeePermission))
                                	continue;

                                if (_configuration.MessageRadius != -1 &&
                                    player.Distance(data.VictimEntity) > _configuration.MessageRadius)
                                    continue;

                                Player.Reply(
                                    player,
                                    _configuration.ChatFormat.Replace("{message}", message),
                                    ulong.Parse(_configuration.ChatIcon)
                                );
                            }

                            if (_configuration.ShowInConsole)
                                Puts(StripRichText(message));

                        }

                     }
                 }
			}
        
        }

        //This method tracks when the patrol heli and Brad is killed or despawns naturally, and cleans them from the tag trackers.
        //Added by Terceran on 1/8/2025
		private void OnEntityKill(BaseNetworkable entity)
        {
        	if (entity is PatrolHelicopter) {
                PatrolHelicopter karen = (PatrolHelicopter)entity;
                patrolHeliTagTracker.Remove(karen.net.ID.Value);
            }
            else if (entity is BradleyAPC) {
            	Puts("TERC: BradleyAPC entity has been killed!");
                Puts($"TERC: bradleyTagTracker count before: {bradleyTagTracker.Count}");
                BradleyAPC brad = (BradleyAPC)entity;
                bradleyTagTracker.Remove(brad.net.ID.Value);
                Puts($"TERC: bradleyTagTracker count after: {bradleyTagTracker.Count}");
            }
        }

	    private void OnEntityDeath(BaseCombatEntity victimEntity, HitInfo hitInfo)
	    {

			// Ignore - there is no victim for some reason
			if (victimEntity == null)
				return;

			// Try to avoid error when entity was destroyed
			if (victimEntity.gameObject == null)
				return;

			ulong netID = victimEntity.net.ID.Value;


			var data = new DeathData
			{
				VictimEntity = victimEntity,
				KillerEntity = victimEntity.lastAttacker ?? hitInfo?.Initiator,
				VictimEntityType = GetCombatEntityType(victimEntity),
				KillerEntityType = GetCombatEntityType(victimEntity.lastAttacker),
				DamageType = victimEntity.lastDamage,
				HitInfo = hitInfo
			};

			//Added by Terceran on 9/1/2024
			if (victimEntity.GetType().Name == "PatrolHelicopter")
            	data.HitInfo = patrolHeliTagTracker[netID];


			// Handle inconsistencies/exceptions
			HandleInconsistencies(ref data);

#if DEBUG
			LogDebug("[DEATHNOTES DEBUG]");
			LogDebug(
				$"VictimEntity: {data.VictimEntity?.GetType().Name ?? "NULL"} / {data.VictimEntity?.ShortPrefabName ?? "NULL"} / {data.VictimEntity?.PrefabName ?? "NULL"}");
			LogDebug(
				$"KillerEntity: {data.KillerEntity?.GetType().Name ?? "NULL"} / {data.KillerEntity?.ShortPrefabName ?? "NULL"} / {data.KillerEntity?.PrefabName ?? "NULL"}");
			LogDebug($"VictimEntityType: {data.VictimEntityType}");
			LogDebug($"KillerEntityType: {data.KillerEntityType}");
			LogDebug($"DamageType: {data.DamageType}");
			LogDebug($"Bodypart: {GetCustomizedBodypartName(data.HitInfo)}");
			LogDebug($"Weapon: {hitInfo?.WeaponPrefab?.ShortPrefabName ?? "NULL"}");
#endif

			// Change entity type for dwellers
			RepairEntityTypes(ref data);

			// Ignore deaths of other entities
			if (data.KillerEntityType == CombatEntityType.Other || data.VictimEntityType == CombatEntityType.Other)
				return;

			// Ignore deaths which don't involve players or the helicopter which usually does not track a player as killer
			if (data.VictimEntityType != CombatEntityType.Player && data.KillerEntityType != CombatEntityType.Player &&
			    data.VictimEntityType != CombatEntityType.Helicopter)
				return;

			// Populate the variables in the message
			string message = PopulateMessageVariables(
				// Find the best matching death message for this death
				GetDeathMessage(data),
				data
			);

			if (message == null)
				return;

			object hookResult = Interface.Call("OnDeathNotice", data.ToDictionary(), message);

			if (hookResult?.Equals(false) ?? false)
				return;

			//Added by Terceran on 20241230
			if ((data.KillerEntityType == CombatEntityType.Player && !permission.UserHasPermission(data.KillerEntity?.ToPlayer()?.UserIDString, SuppressPermission)) ||
            	(data.VictimEntityType == CombatEntityType.Player && !permission.UserHasPermission(data.VictimEntity?.ToPlayer()?.UserIDString, SuppressPermission)))
            {

				if (_configuration.ShowInChat)
				{

					foreach (var player in BasePlayer.activePlayerList)
					{

						if (_configuration.RequirePermission &&
					    	!permission.UserHasPermission(player.UserIDString, CanSeePermission))
							continue;

						if (_configuration.MessageRadius != -1 &&
					    	player.Distance(data.VictimEntity) > _configuration.MessageRadius)
							continue;

						Player.Reply(
							player,
							_configuration.ChatFormat.Replace("{message}", message),
							ulong.Parse(_configuration.ChatIcon)
						);

					}
				}

				if (_configuration.ShowInConsole)
					Puts(StripRichText(message));
			}
                
            //Added by Terceran on 9/2/2024, modified on 1/9/2025
			if (victimEntity.GetType().Name == "BradleyAPC")
            {
                bradleyTagTracker.Remove(netID);
            }
            else if (victimEntity.GetType().Name == "PatrolHelicopter")
            {
				patrolHeliTagTracker.Remove(netID);
            }

		}

		private void RepairEntityTypes(ref DeathData data)
		{
			if (data.VictimEntity != null)
			{
				string victimPrefabName = data.VictimEntity.ShortPrefabName.ToLower();
				if (victimPrefabName.Contains("underwaterdweller"))
				{
					data.VictimEntityType = CombatEntityType.UnderwaterDweller;
				}

				if (victimPrefabName.Contains("tunneldweller"))
				{
					data.VictimEntityType = CombatEntityType.TunnelDweller;
				}
			}

			if (data.KillerEntity != null)
			{
				string killerPrefabName = data.KillerEntity.ShortPrefabName.ToLower();

				if (killerPrefabName.Contains("underwaterdweller"))
				{
					data.KillerEntityType = CombatEntityType.UnderwaterDweller;
				}

				if (killerPrefabName.Contains("tunneldweller"))
				{
					data.KillerEntityType = CombatEntityType.TunnelDweller;
				}
			}
		}

		private void OnFlameThrowerBurn(FlameThrower flameThrower, BaseEntity baseEntity)
		{
			if (flameThrower == null || baseEntity == null) return;

			var flame = baseEntity.gameObject.AddComponent<Flame>();
			flame.Source = Flame.FlameSource.Flamethrower;
			flame.SourceEntity = flameThrower;
			flame.Initiator = flameThrower.GetOwnerPlayer();
		}

		private void OnFlameExplosion(FlameExplosive explosive, BaseEntity baseEntity)
		{
			if (explosive == null || baseEntity == null) return;

			var flame = baseEntity.gameObject.AddComponent<Flame>();
			flame.Source = Flame.FlameSource.IncendiaryProjectile;
			flame.SourceEntity = explosive;
			flame.Initiator = explosive.creatorEntity;
		}

		private void OnFireBallSpread(FireBall fireBall, BaseEntity newFire)
		{
			if (fireBall == null) return;

			var flame = fireBall.GetComponent<Flame>();
			if (flame == null) return;

			var newFlame = newFire.gameObject.AddComponent<Flame>();
			newFlame.Source = flame.Source;
			newFlame.SourceEntity = flame.SourceEntity;
			newFlame.Initiator = flame.Initiator;
		}

		private void OnFireBallDamage(FireBall fireBall, BaseCombatEntity target, HitInfo hitInfo) =>
			hitInfo.Initiator = fireBall;

		#endregion

		#region Death Messages

		private string GetDeathMessage(DeathData data)
		{
			foreach (var matchingStage in _messageMatchingStages)
			{
				var match = _configuration.Translations.Messages.Find(m => matchingStage.Invoke(m, data));

				if (match != null)
					return match.Messages.GetRandom((uint) DateTime.UtcNow.Millisecond);
			}

			return null;
		}

		private string PopulateMessageVariables(string message, DeathData data)
		{
			if (string.IsNullOrEmpty(message))
				return null;

			var replacements = new Dictionary<string, string>
			{
				["victim"] = GetCustomizedEntityName(data.VictimEntity, data.VictimEntityType)
			};

			if (data.KillerEntityType != CombatEntityType.None)
			{
				replacements.Add("killer", GetCustomizedEntityName(data.KillerEntity, data.KillerEntityType));
				replacements.Add("bodypart", GetCustomizedBodypartName(data.HitInfo));

				if (data.KillerEntity != null)
				{
					var distance = data.KillerEntity.Distance(data.VictimEntity);
					replacements.Add("distance", GetDistance(distance, _configuration.UseMetricDistance));
				}

				if (data.KillerEntityType == CombatEntityType.Player)
				{
					replacements.Add("hp", data.KillerEntity.Health().ToString("#0.#"));
					replacements.Add("weapon", GetCustomizedWeaponName(data));
					replacements.Add("attachments", string.Join(", ", GetCustomizedAttachmentNames(data.HitInfo)));
				}
				else if (data.KillerEntityType == CombatEntityType.Turret
				         || data.KillerEntityType == CombatEntityType.Lock
				         || data.KillerEntityType == CombatEntityType.Trap)
				{
					replacements.Add("owner",
						covalence.Players.FindPlayerById(data.KillerEntity.OwnerID.ToString())?.Name ?? "unknown owner"
					); // TODO: Work on the potential unknown owner case
				}
			}

			message = InsertPlaceholderValues(message, replacements);

			replacements = null;
			return message;
		}

		private struct DeathData
		{
			public CombatEntityType VictimEntityType { get; set; }
			[JsonIgnore] public BaseCombatEntity VictimEntity { get; set; }

			public CombatEntityType KillerEntityType { get; set; }
			[JsonIgnore] public BaseEntity KillerEntity { get; set; }

			public DamageType DamageType { get; set; }
			[JsonIgnore] public HitInfo HitInfo { get; set; }

			public Dictionary<string, object> ToDictionary() => new Dictionary<string, object>
			{
				["VictimEntityType"] = VictimEntityType,
				["VictimEntity"] = VictimEntity,
				["KillerEntityType"] = KillerEntityType,
				["KillerEntity"] = KillerEntity,
				["DamageType"] = DamageType,
				["HitInfo"] = HitInfo
			};
		}

		#endregion

		#region Entity Identification

		private CombatEntityType GetCombatEntityType(BaseEntity entity)
		{
			if (entity == null)
				return CombatEntityType.None;

			if (_combatEntityTypes.Contents != null)
			{
				if (_combatEntityTypes.Contents.ContainsKey(entity.GetType().Name))
					return _combatEntityTypes.Contents[entity.GetType().Name];

				if (_combatEntityTypes.Contents.ContainsKey(entity.ShortPrefabName))
					return _combatEntityTypes.Contents[entity.ShortPrefabName];
			}

//Terceran TEST
//Puts($"TERC TEST1: {entity.GetType().Name}");

			//Added by Terceran on 9/1/2024
			if (entity.GetType().Name.Equals("PatrolHelicopter"))
				return CombatEntityType.Helicopter;

			//Added by Terceran on 10/7/2024
			if (entity.GetType().Name.Equals("SimpleShark"))
            	return CombatEntityType.Animal;

			//Added by Terceran on 11/7/2024
			if (entity.GetType().Name.Equals("Wolf2"))
            	return CombatEntityType.Animal;

			if (entity is BaseOven)
				return CombatEntityType.HeatSource;

			if (entity is SimpleBuildingBlock)
				return CombatEntityType.ExternalWall;

			if (entity is BaseAnimalNPC)
				return CombatEntityType.Animal;

			if (entity is BaseTrap)
				return CombatEntityType.Trap;

			if (entity is Barricade)
				return CombatEntityType.Barricade;

			if (entity is IOEntity)
				return CombatEntityType.Trap;

			if (entity is ScientistNPC)
				return CombatEntityType.Scientist;

			//Added by Terceran on 20241218
			if (entity.GetType().Name.Equals("GingerbreadNPC"))
				return CombatEntityType.GingerbreadNPC;

			if (entity.GetType().Name.Equals("ZombieNPC"))
				return CombatEntityType.ZombieNPC;
            
            //Added by Terceran on 20250107
            if (entity.GetType().Name.Equals("Minicopter"))
				return CombatEntityType.Minicopter;
            
            //Added by Terceran on 20250107
            if (entity.GetType().Name.Equals("ScrapTransportHelicopter"))
				return CombatEntityType.ScrapTransportHelicopter;
            
            //Added by Terceran on 20250107
            if (entity.GetType().Name.Equals("AttackHelicopter"))
				return CombatEntityType.AttackHelicopter;
            

			return CombatEntityType.Other;
		}

		private string GetCustomizedEntityName(BaseEntity entity, CombatEntityType combatEntityType)
		{
			var name = GetEntityName(entity, combatEntityType);

			if (string.IsNullOrEmpty(name))
				return null;

			// Don't load player names into config
			if (combatEntityType == CombatEntityType.Player)
				return name;

			if (!_configuration.Translations.Names.ContainsKey(name))
			{
				_configuration.Translations.Names.Add(name, name);
				Config.WriteObject(_configuration);
			}

			return _configuration.Translations.Names[name];
		}

		private string GetEntityName(BaseEntity entity, CombatEntityType combatEntityType)
		{
        
			// Entity may be null for helicopter or bradley, see HandleExceptions(...)
			if (entity == null &&
			    combatEntityType != CombatEntityType.Helicopter &&
			    combatEntityType != CombatEntityType.Bradley)
				return null;

			switch (combatEntityType)
			{
				case CombatEntityType.Player:
					return StripRichText(entity.ToPlayer().displayName);

				//Added CombatEnttiyType.GingerbreadNPC: case statement and changed Scarecrow to ScarecrowNPC by Terceran on 20241218
				case CombatEntityType.Murderer:
				case CombatEntityType.ScarecrowNPC:
				case CombatEntityType.Scientist:
				case CombatEntityType.ZombieNPC:
                case CombatEntityType.GingerbreadNPC:
					var name = entity.ToPlayer()?.displayName;

					if (!string.IsNullOrEmpty(name) && name != entity.ToPlayer()?.userID.ToString())
					{
						return name;
					}

					if (!_enemyPrefabs.Contents.ContainsKey(entity.ShortPrefabName))
					{
						return combatEntityType.ToString();
					}

					break;

				case CombatEntityType.TunnelDweller:
					return "Tunnel Dweller";

				case CombatEntityType.UnderwaterDweller:
					return "Underwater Dweller";

				case CombatEntityType.Helicopter:
					return "Patrol Helicopter (Karen)";

				case CombatEntityType.Bradley:
					return "Bradley APC";

				case CombatEntityType.Sentry:
					return "Sentry";
                
				case CombatEntityType.Fire:
					return entity.creatorEntity?.ToPlayer()?.displayName ?? "Fire";
                    
                case CombatEntityType.Minicopter:
                	return "Minicopter";
                
                //Terceran: NOTE: Scrappy reporting can't work because a death results in two death notices. One is where the Scrappy is both the killer and the victim,
                //and the other is where it shows the player as having committed suicide, even though they did not.
                case CombatEntityType.ScrapTransportHelicopter:
                    return "ScrapTransportHelicopter";
                
                case CombatEntityType.AttackHelicopter:
                	return "AttackHelicopter";
			}

			if (_enemyPrefabs.Contents.ContainsKey(entity.ShortPrefabName))
				return _enemyPrefabs.Contents[entity.ShortPrefabName];

			return HumanizePascalCase(entity.GetType().Name);
		}

		internal enum CombatEntityType
		{
			Helicopter = 0,
			Bradley = 1,
			Animal = 2,
			Murderer = 3,
			Scientist = 4,
			Player = 5,
			Trap = 6,
			Turret = 7,
			Barricade = 8,
			ExternalWall = 9,
			HeatSource = 10,
			Fire = 11,
			Lock = 12,
			Sentry = 13,
			Other = 14,
			None = 15,
            //Changed by Terceran on 20241218
			ScarecrowNPC = 16,
			TunnelDweller = 17,
			UnderwaterDweller = 18,
			ZombieNPC = 19,
            //Added by Terceran on 20241218
            GingerbreadNPC = 20,
            
            //Added by Terceran on 20250107
            Minicopter = 21,
            ScrapTransportHelicopter = 22,
            AttackHelicopter = 23
		}

		#endregion

		#region Workarounds and Inconsistency Handling

		private void HandleInconsistencies(ref DeathData data)
		{
			// Deaths of other entity types are not of interest and might cause errors
			if (data.VictimEntityType == CombatEntityType.Other)
				return;

			if (data.KillerEntity is FireBall)
				data.DamageType = DamageType.Heat;

			// If the killer entity is null, but a weapon is given, we might be able to fall back to the parent entity of that weapon
			// Notably for the auto turret after the changes it has had
			if (data.KillerEntity == null && data.HitInfo?.Weapon != null)
			{
				data.KillerEntity = data.HitInfo.Weapon.GetParentEntity();
				data.KillerEntityType = GetCombatEntityType(data.KillerEntity);
			}

			// Get previous attacker when bleeding out
			if (data.VictimEntityType == CombatEntityType.Player &&
			    (data.DamageType == DamageType.Bleeding || data.HitInfo == null))
			{
				var userId = data.VictimEntity.ToPlayer().userID;

				if (_previousAttack.ContainsKey(userId))
				{
					var attack = _previousAttack[userId];
					data.KillerEntity = attack.Attacker;
					data.KillerEntityType = GetCombatEntityType(data.KillerEntity);

					// Restore previous hitInfo for weapon determination
					if (attack.HitInfo != null)
						data.HitInfo = attack.HitInfo;

					// Use previous damagetype if this is a selfinflicted death,
					// so falling to death etc. is also shown when wounded and bleeding out
					if (data.KillerEntity == null || data.KillerEntity == data.VictimEntity)
						data.DamageType = attack.DamageType;
					else
						data.DamageType = DamageType.Bleeding;
				}
			}

			if (data.KillerEntityType != CombatEntityType.None && data.KillerEntity != null)
			{
				// Workaround for deaths caused by flamethrower or rocket fire 
				var flame = data.KillerEntity.gameObject.GetComponent<Flame>();
				if (flame != null && flame.Initiator != null)
				{
					data.KillerEntity = flame.Initiator;
					data.KillerEntityType = CombatEntityType.Player;
					return;
				}
			}

			// Bradley kill with main cannon
			if (data.HitInfo?.WeaponPrefab?.ShortPrefabName == "maincannonshell")
			{
				data.KillerEntityType = CombatEntityType.Bradley;
				return;
			}

			if (data.HitInfo?.WeaponPrefab?.ShortPrefabName?.StartsWith("rocket_heli") ?? false)
			{
				data.KillerEntityType = CombatEntityType.Helicopter;
				return;
			}

			// Vehicle Kills
			if (data.KillerEntityType == CombatEntityType.Player
			    && data.DamageType == DamageType.Generic
			    && data.KillerEntity.ToPlayer().isMounted)
			{
				data.DamageType = DamageType.Collision;
				return;
			}
		}

		private struct AttackInfo
		{
			public HitInfo HitInfo { get; set; }
			public DamageType DamageType { get; set; }
			public BaseEntity Attacker { get; set; }
		}

		private class Flame : MonoBehaviour
		{
			public FlameSource Source { get; set; }
			public BaseEntity SourceEntity { get; set; }
			public BaseEntity Initiator { get; set; }

			public enum FlameSource
			{
				Flamethrower,
				IncendiaryProjectile
			}
		}

		#endregion

		#region Weapons

		private string GetCustomizedWeaponName(DeathData deathData)
		{
			var name = GetWeaponName(deathData);

			if (string.IsNullOrEmpty(name))
				return null;

			if (!_configuration.Translations.Weapons.ContainsKey(name))
			{
				_configuration.Translations.Weapons.Add(name, name);
				Config.WriteObject(_configuration);
			}

			return _configuration.Translations.Weapons[name];
		}

		private string GetWeaponName(DeathData deathData)
		{
			if (deathData.HitInfo == null)
				return null;

			Item item = deathData.HitInfo.Weapon?.GetItem();
			/*var parentEntity = hitInfo.Weapon?.GetParentEntity();
			Item item = null;

			if (parentEntity is BasePlayer)
			{
			    (parentEntity as BasePlayer).inventory.FindItemUID(hitInfo.Weapon.ownerItemUID);
			}
			else if (parentEntity is ContainerIOEntity)
			{
			    (parentEntity as ContainerIOEntity).inventory.FindItemByUID(hitInfo.Weapon.ownerItemUID);
			}*/

			if (item != null)
				return item.info.displayName.english;

			var prefab = deathData.HitInfo.Initiator?.GetComponent<Flame>()?.SourceEntity?.ShortPrefabName ??
			             deathData.HitInfo.WeaponPrefab?.ShortPrefabName;

			if (prefab != null)
			{
				if (_weaponPrefabs.Contents.ContainsKey(prefab))
					return _weaponPrefabs.Contents[prefab];

				return prefab;
			}

			// Vehicles are the only thing we classify as a weapon, while not being classified as such by the game.
			// TODO: Having this here kinda sucks, make this better.
			if (deathData.DamageType == DamageType.Collision)
			{
				return "Vehicle";
			}

			return null;
		}

		private string[] GetCustomizedAttachmentNames(HitInfo info)
		{
			var items = info?.Weapon?.GetItem()?.contents?.itemList;

			if (items == null)
			{
				return Array.Empty<string>();
			}

			return items.Select(i => GetCustomizedAttachmentName(i.info.displayName.english)).ToArray();
		}

		private string GetCustomizedAttachmentName(string name)
		{
			if (!_configuration.Translations.Attachments.ContainsKey(name))
			{
				_configuration.Translations.Attachments.Add(name, name);
				Config.WriteObject(_configuration);
			}

			return _configuration.Translations.Attachments[name];
		}

		#endregion

		#region Bodyparts

		private string GetCustomizedBodypartName(HitInfo hitInfo)
		{
			var name = GetBodypartName(hitInfo);

			if (string.IsNullOrEmpty(name))
				return null;

			if (!_configuration.Translations.Bodyparts.ContainsKey(name))
			{
				_configuration.Translations.Bodyparts.Add(name, name);
				Config.WriteObject(_configuration);
			}

			return _configuration.Translations.Bodyparts[name];
		}

		private string GetBodypartName(HitInfo hitInfo)
		{
			var hitArea = hitInfo?.boneArea ?? (HitArea) (-1);
			return (int) hitArea == -1 ? "Body" : hitArea.ToString();
		}

		#endregion

		#region Helper

#if DEBUG
		private static void LogDebug(string text)
		{
			if (BasePlayer.activePlayerList.Count >= 1)
			{
				BasePlayer.activePlayerList[0].ConsoleMessage($"<color=orange>{text}</color>");
			}
		}
#endif

		private static string GetDistance(float meters, bool useMetric)
		{
			double value = Math.Round(useMetric ? meters : meters * 3.28f, 1);
			string unit = value == 1
				? _instance.lang.GetMessage("Distance Unit Singular", _instance)
				: _instance.lang.GetMessage("Distance Unit Plural", _instance);

			return $"{value} {unit}";
		}

		private static string ApplyVariableFormat(string text, string variableName)
		{
			if (_instance._configuration.VariableFormats.ContainsKey(variableName))
			{
				var format = _instance._configuration.VariableFormats[variableName];
				text = format.Replace("{value}", text);
			}

			return text;
		}

		private static string InsertPlaceholderValues(string text, Dictionary<string, string> values)
		{
			foreach (var kvp in values)
			{
				string value = ApplyVariableFormat(kvp.Value, kvp.Key);
				if (string.IsNullOrEmpty(kvp.Value))
				{
					text = text.Replace($"{{{kvp.Key}}}", string.Empty);
				}
				else if (_instance._configuration.VariableColors.ContainsKey(kvp.Key))
				{
					var color = _instance._configuration.VariableColors[kvp.Key];
					text = text.Replace($"{{{kvp.Key}}}", $"<color={color}>{value}</color>");
					color = null;
				}
				else
				{
					text = text.Replace($"{{{kvp.Key}}}", value);
				}
			}

			return text;
		}

		private static string HumanizePascalCase(string text)
		{
			if (string.IsNullOrEmpty(text))
				return string.Empty;

			var sb = new StringBuilder();

			foreach (char c in text)
			{
				if (char.IsUpper(c) && sb.Length != 0 && !char.IsUpper(sb[sb.Length - 1]))
					sb.Append(" ");

				sb.Append(c);
			}

			return sb.ToString();
		}

		private string StripRichText(string text)
		{
			if (string.IsNullOrEmpty(text))
				return string.Empty;

			text = _colorTagRegex.Replace(text, string.Empty);
			text = _sizeTagRegex.Replace(text, string.Empty);

			foreach (var richTextLiteral in _richTextLiterals)
				text = text.Replace(richTextLiteral, string.Empty);

			return text;
		}

		private static bool MatchesCombatEntityType(CombatEntityType combatEntityType, string text)
		{
			if (combatEntityType == CombatEntityType.None && text == "-")
				return true;

			return combatEntityType.ToString().Equals(text);
		}

		private static bool MatchesDamageType(DamageType damageType, string text)
		{
			return damageType.ToString().Equals(text);
		}

		#endregion

		#region Configuration

		protected override void LoadDefaultMessages()
		{
			lang.RegisterMessages(new Dictionary<string, string>
			{
				["Distance Unit Singular"] = "meter",
				["Distance Unit Plural"] = "meters"
			}, this);
		}

		protected override void LoadDefaultConfig() => PrintWarning("Generating new configuration file...");

		private sealed class PluginConfiguration
		{
			[JsonProperty("Translations")] public Translation Translations = new Translation();

			[JsonProperty("Variable Formats")] public Dictionary<string, string> VariableFormats =
				new Dictionary<string, string>
				{
					["attachments"] = " ({value})"
				};

			[JsonProperty("Variable Colors")] public Dictionary<string, string> VariableColors =
				new Dictionary<string, string>
				{
					["killer"] = "#C4FF00",
					["victim"] = "#C4FF00",
					["weapon"] = "#C4FF00",
					["attachments"] = "#C4FF00",
					["distance"] = "#C4FF00",
					["owner"] = "#C4FF00"
				};

			[JsonProperty("Chat Format")]
			public string ChatFormat = "<color=#838383>[<color=#80D000>DeathNotes</color>] {message}</color>";

			[JsonProperty("Chat Icon (SteamID)")] public string ChatIcon = "76561############";

			[JsonProperty("Show Kills in Console")]
			public bool ShowInConsole = true;

			[JsonProperty("Show Kills in Chat")] public bool ShowInChat = true;
            
            //Added by Terceran on 20241231
            [JsonProperty("Show Patrol Heli Tags")] public bool ShowPatrolHeliTags = true;
            [JsonProperty("Show Bradley APC Tags")] public bool ShowBradleyTags = true;

			[JsonProperty("Message Broadcast Radius (in meters)")]
			public int MessageRadius = -1;

			[JsonProperty("Use Metric Distance")] public bool UseMetricDistance = true;

			[JsonProperty("Require Permission (deathnotes.cansee)")]
			public bool RequirePermission = false;

			public void LoadDefaults()
			{
				if (Translations.Messages == null)
				{
					var defaults = new RemoteConfiguration<List<DeathMessage>>("DefaultMessages");
					defaults.Load(success =>
					{
						if (success)
						{
							Translations.Messages = defaults.Contents;
							_instance.Config.WriteObject(this);
						}
					});
				}
			}

			public class DeathMessage
			{
				public string KillerType { get; set; }
				public string VictimType { get; set; }
				public string DamageType { get; set; }

				public string[] Messages { get; set; }

				protected bool Equals(DeathMessage other) => string.Equals(KillerType, other.KillerType) &&
				                                             string.Equals(VictimType, other.VictimType) &&
				                                             string.Equals(DamageType, other.DamageType);
			}

			public class Translation
			{
				[JsonProperty("Death Messages")] public List<DeathMessage> Messages;

				[JsonProperty("Names")] public Dictionary<string, string> Names = new Dictionary<string, string>();

				[JsonProperty("Bodyparts")]
				public Dictionary<string, string> Bodyparts = new Dictionary<string, string>();

				[JsonProperty("Weapons")] public Dictionary<string, string> Weapons = new Dictionary<string, string>();

				[JsonProperty("Attachments")]
				public Dictionary<string, string> Attachments = new Dictionary<string, string>();
			}
		}

		internal sealed class RemoteConfiguration<T>
		{
			private const string Host = "http://files.laserhydra.com/config/DeathNotes/v6.3.6/";

			private readonly string _file;

			public RemoteConfiguration(string file)
			{
				_file = file;
			}

			public T Contents { get; private set; }
			private string ExactUrl => Host + _file;

			public void Load(Action<bool> callback = null)
			{
				_instance.webrequest.Enqueue(ExactUrl, string.Empty, (code, response) =>
				{
					try
					{
						if (!IsSuccessStatusCode(code))
							throw new Exception($"Status code indicates failure. Code: {code}");

						Contents = JsonConvert.DeserializeObject<T>(response);
						callback?.Invoke(true);

						Interface.Oxide.DataFileSystem.WriteObject($"{nameof(DeathNotes)}/{_file}", Contents);
					}
					catch (Exception ex)
					{
						if (Interface.Oxide.DataFileSystem.ExistsDatafile($"{nameof(DeathNotes)}/{_file}"))
						{
							Contents = Interface.Oxide.DataFileSystem.ReadObject<T>($"{nameof(DeathNotes)}/{_file}");

							_instance.PrintWarning(
								$"Could not load remote config '{_file}'. The plugin will be using the previously downloaded file.");

							callback?.Invoke(true);
						}
						else
						{
							_instance.PrintError(
								$"Could not load remote config '{_file}'. The plugin will not work properly. Please check whether you can access {ExactUrl} via your browser. If you can, please check the FAQ on how to solve this.");
							_instance.PrintError($"[Code {code}] {ex.GetType().Name}: {ex.Message}");
							_instance.PrintError($"Response: {response}");

							callback?.Invoke(false);
						}
					}
				}, _instance);
			}

			private bool IsSuccessStatusCode(int code) => code >= 200 && code < 300;
		}

		#endregion
	}
}​

Here is my sample config file:
{
  "Translations": {
    "Death Messages": [
      {
        "KillerType": "Player",
        "VictimType": "Player",
        "DamageType": "Bullet",
        "Messages": [
          "{killer} shot {victim} using their {weapon} over a distance of {distance}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "Player",
        "DamageType": "Arrow",
        "Messages": [
          "{victim} was shot by {killer} with their {weapon} over a distance of {distance}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "Player",
        "DamageType": "Heat",
        "Messages": [
          "{killer} inflamed {victim} with their {weapon}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "{killer} killed {victim} using their {weapon}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "Player",
        "DamageType": "Slash",
        "Messages": [
          "{killer} slashed {victim} into pieces with their {weapon}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "Animal",
        "DamageType": "*",
        "Messages": [
          "{killer} killed a {victim} using their {weapon}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "SimpleShark",
        "DamageType": "*",
        "Messages": [
          "{killer} killed a {victim} using their {weapon}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "Animal",
        "DamageType": "Bullet",
        "Messages": [
          "{killer} shot a {victim} using their {weapon} over a distance of {distance}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "Animal",
        "DamageType": "Arrow",
        "Messages": [
          "{killer} shot a {victim} using their {weapon} over a distance of {distance}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "ScarecrowNPC",
        "DamageType": "*",
        "Messages": [
          "{killer} fought off a spooky {victim} with their {weapon}."
        ]
      },
      {
        "KillerType": "ScarecrowNPC",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "{killer} scared {victim} to death!"
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "ScarecrowNPC",
        "DamageType": "Bullet",
        "Messages": [
          "{killer} did some scaring of their own against a {victim} with their {weapon} over a distance of {distance}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "GingerbreadNPC",
        "DamageType": "*",
        "Messages": [
          "{killer} fought off a deliciously deadly {victim} with their {weapon}."
        ]
      },
      {
        "KillerType": "GingerbreadNPC",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "Death was sweet for {victim}, who was killed by a {killer}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "GingerbreadNPC",
        "DamageType": "Bullet",
        "Messages": [
          "{killer} took a bite out of a deliciously deadly {victim} with their {weapon} over a distance of {distance}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "Scientist",
        "DamageType": "*",
        "Messages": [
          "{killer} did not want to be a part of the {victim}'s experiments, and did some science of their own with their {weapon}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "Scientist",
        "DamageType": "Bullet",
        "Messages": [
          "{killer} did some research of their own against a {victim} with their {weapon} over a distance of {distance}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "Scientist",
        "DamageType": "Arrow",
        "Messages": [
          "{killer} did some research of their own against a {victim} with their {weapon} over a distance of {distance}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "Bradley",
        "DamageType": "*",
        "Messages": [
          "{killer} blew up the {victim} with their {weapon}."
        ]
      },
      {
        "KillerType": "*",
        "VictimType": "Helicopter",
        "DamageType": "*",
        "Messages": [
          "The {victim} was destroyed. Good riddance."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "Helicopter",
        "DamageType": "*",
        "Messages": [
          "The world is a safer place now that {killer} shot down the {victim} with their {weapon}. Good riddance."
        ]
      },
      {
        "KillerType": "Animal",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "{victim} couldn't run away from the {killer}."
        ]
      },
      {
        "KillerType": "SimpleShark",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "Baby Shark, doo-doo, doo-doo, doo-doo ate {victim}."
        ]
      },
      {
        "KillerType": "Bradley",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "{victim} was blasted by the {killer}."
        ]
      },
      {
        "KillerType": "Helicopter",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "{victim} had no chance against the {killer}."
        ]
      },
      {
        "KillerType": "Trap",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "{victim} was reckless and ran into a {killer}."
        ]
      },
      {
        "KillerType": "Barricade",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "{victim} was impaled by a {killer}."
        ]
      },
      {
        "KillerType": "Turret",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "{owner}'s {killer} did its job, killing intruder {victim}."
        ]
      },
      {
        "KillerType": "Murderer",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "A {killer} haunted down {victim}."
        ]
      },
      {
        "KillerType": "TunnelDweller",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "{victim} was taken out by a {killer}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "TunnelDweller",
        "DamageType": "*",
        "Messages": [
          "{killer} took out a {victim} with their {weapon}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "TunnelDweller",
        "DamageType": "Bullet",
        "Messages": [
          "{killer} took out a {victim} with their {weapon} over a distance of {distance}."
        ]
      },
      {
        "KillerType": "UnderwaterDweller",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "{victim} was taken out by an {killer} with their {weapon}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "UnderwaterDweller",
        "DamageType": "*",
        "Messages": [
          "{killer} took out an {victim} with their {weapon}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "UnderwaterDweller",
        "DamageType": "Bullet",
        "Messages": [
          "{killer} took out a {victim} with their {weapon} over a distance of {distance}."
        ]
      },
      {
        "KillerType": "Scientist",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "A {killer} shot down {victim}."
        ]
      },
      {
        "KillerType": "Sentry",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "{victim} broke the rules in a safezone and was killed by a {killer}."
        ]
      },
      {
        "KillerType": "-",
        "VictimType": "Player",
        "DamageType": "Fall",
        "Messages": [
          "{victim} fell to their death. Splat!"
        ]
      },
      {
        "KillerType": "HeatSource",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "{victim} was grilled on a {killer}."
        ]
      },
      {
        "KillerType": "Lock",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "{victim} was electrocuted by {owner}'s {killer}."
        ]
      },
      {
        "KillerType": "*",
        "VictimType": "Player",
        "DamageType": "Heat",
        "Messages": [
          "{victim} burned to death."
        ]
      },
      {
        "KillerType": "*",
        "VictimType": "Player",
        "DamageType": "Radiation",
        "Messages": [
          "{victim} became just a bit too radioactive."
        ]
      },
      {
        "KillerType": "*",
        "VictimType": "Player",
        "DamageType": "Cold",
        "Messages": [
          "{victim} turned into an ice statue."
        ]
      },
      {
        "KillerType": "*",
        "VictimType": "Player",
        "DamageType": "Drowned",
        "Messages": [
          "As {victim} just found out, breathing underwater is rather difficult."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "Player",
        "DamageType": "Bleeding",
        "Messages": [
          "{victim} bled out after being attacked by {killer}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "Animal",
        "DamageType": "Collision",
        "Messages": [
          "{killer} ran over a {victim} with their {weapon}."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "Player",
        "DamageType": "Collision",
        "Messages": [
          "{killer} ran over {victim} with their {weapon}."
        ]
      },
      {
        "KillerType": "Minicopter",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "{victim} tragically perished in a {killer} crash."
        ]
      },
      {
        "KillerType": "ScrapTransportHelicopter",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "{victim} tragically perished in a {killer} crash."
        ]
      },
      {
        "KillerType": "AttackHelicopter",
        "VictimType": "Player",
        "DamageType": "*",
        "Messages": [
          "{victim} tragically perished in a {killer} crash."
        ]
      },
      {
        "KillerType": "Player",
        "VictimType": "Player",
        "DamageType": "Suicide",
        "Messages": [
          "{victim} had enough of life."
        ]
      }
    ],
    "Names": {
      "Chicken": "Chicken",
      "Boar": "Boar",
      "Stag": "Stag",
      "Bear": "Bear",
      "Wolf": "Wolf",
      "Simple Shark": "Shark",
      "Scientist": "Scientist",
      "Helicopter": "Helicopter (Karen)",
      "Bradley APC": "Bradley APC",
      "Polarbear": "Polar Bear",
      "Code Lock": "Code Lock",
      "Fire": "Fire",
      "Sentry": "Sentry",
      "Sam Site": "Sam Site",
      "ScarecrowNPC": "Scarecrow",
      "Tunnel Dweller": "Tunnel Dweller",
      "Barbed Wooden Barricade": "Barbed Wooden Barricade",
      "Barricade": "Barricade",
      "Auto Turret": "Auto Turret",
      "Patrol Helicopter (Karen)": "Patrol Helicopter (Karen)",
      "GRAF": "GRAF",
      "Underwater Dweller": "Underwater Dweller",
      "Campfire": "Campfire",
      "Wolf2": "Wolf",
      "Horse": "Horse",
      "Landmine": "Landmine",
      "Tesla Coil": "Tesla Coil",
      "Gun Trap": "Shotgun Trap",
      "Flame Turret": "Flame Turret",
      "GingerbreadNPC": "Gingerbread Man",
      "ScrapTransportHelicopter": "Scrap Transport Helicopter",
      "AttackHelicopter": "Attack Helicopter",
      "Minicopter": "Minicopter"
    },
    "Bodyparts": {
      "Body": "Body",
      "Leg": "Leg",
      "Chest": "Chest",
      "Stomach": "Stomach",
      "Head": "Head",
      "Arm": "Arm",
      "Hand": "Hand",
      "Foot": "Foot"
    },
    "Weapons": {
      "Salvaged Sword": "Salvaged Sword",
      "Revolver": "Revolver",
      "Salvaged Cleaver": "Salvaged Cleaver",
      "Mace": "Mace",
      "Assault Rifle": "Assault Rifle",
      "Double Barrel Shotgun": "Double Barrel Shotgun",
      "Bolt Action Rifle": "Bolt Action Rifle",
      "M92 Pistol": "M92 Pistol",
      "Custom SMG": "Custom SMG",
      "LR-300 Assault Rifle": "LR-300 Assault Rifle",
      "Timed Explosive Charge": "Timed Explosive Charge (C4)",
      "Pump Shotgun": "Pump Shotgun",
      "Prototype 17": "Prototype 17",
      "Hunting Bow": "Hunting Bow",
      "Thompson": "Thompson",
      "M39 Rifle": "M39 Rifle",
      "Hatchet": "Hatchet",
      "hatchet.entity": "Hatchet",
      "M4 Shotgun": "M4 Shotgun",
      "HMLMG": "HMLMG",
      "l96.entity": "L96 Rifle",
      "L96 Rifle": "L96 Rifle",
      "m249.entity": "M249",
      "M249": "M249",
      "Pickaxe": "Pickaxe",
      "Bone Knife": "Bone Knife",
      "Semi-Automatic Pistol": "Semi-Automatic Pistol",
      "Stone Hatchet": "Stone Hatchet",
      "Rock": "Rock",
      "Skinning Knife": "Skinning Knife",
      "Crossbow": "Crossbow",
      "Combat Knife": "Combat Knife",
      "Semi-Automatic Rifle": "Semi-Automatic Rifle",
      "MP5A4": "MP5A4",
      "Jackhammer": "Jackhammer",
      "SKS": "SKS",
      "militaryflamethrower.entity": "Military Flamethrower",
      "High Velocity Rocket": "High Velocity Rocket",
      "Spas-12 Shotgun": "Spas-12 Shotgun",
      "knife.combat.entity": "Combat Knife",
      "2module_car_spawned.entity": "2-module Vehicle",
      "car_chassis_2module.entity": "2-module Vehicle",
      "car_2mod_02": "2-module Vehicle",
      "3module_car_spawned.entity": "3-module Vehicle",
      "car_chassis_3module.entity": "3-module Vehicle",
      "car_3mod_03": "3-module Vehicle",
      "4module_car_spawned.entity": "4-module Vehicle",
      "car_chassis_4module.entity": "4-module Vehicle",
      "car_4mod_04": "4-module Vehicle",
      "motorbike": "Motorbike",
      "motorbike_sidecar": "Motorbike with Sidecar",
      "pedalbike": "Bicycle",
      "pedaltrike": "Tricycle",
      "snowmobile": "Snowmobile",
      "F1 Grenade": "F1 Grenade",
      "Satchel Charge": "Satchel Charge",
      "Wooden Spear": "Wooden Spear",
      "Stone Spear": "Stone Spear",
      "Compound Bow": "Compound Bow",
      "Machete": "Machete",
      "Pitchfork": "Pitchfork",
      "Stone Pickaxe": "Stone Pickaxe",
      "Legacy bow": "Legacy bow",
      "Rocket": "Rocket",
      "Incendiary Rocket": "Incendiary Rocket",
      "Salvaged Axe": "Salvaged Axe",
      "Torch": "Torch",
      "pickaxe.entity": "Pickaxe",
      "Salvaged Hammer": "Salvaged Hammer",
      "40mm_grenade_he": "40mm HE Grenade",
      "Minigun": "Minigun",
      "Assault Rifle - ICE": "Assault Rifle - ICE",
      "Python Revolver": "Python Revolver",
      "Nailgun": "Nailgun",
      "Salvaged Icepick": "Salvaged Icepick",
      "Longsword": "Longsword",
      "Waterpipe Shotgun": "Waterpipe Shotgun",
      "Chainsaw": "Chainsaw",
      "workcart.entity": "Work Cart",
      "locomotive.entity": "Locomotive",
      "workcart_aboveground.entity": "Above Ground Work Cart",
      "Paddle": "Paddle",
      "workcart_aboveground2.entity": "Above Ground Work Cart",
      "hammer_salvaged.entity": "Salvaged Hammer",
      "Multiple Grenade Launcher": "Multiple Grenade Launcher",
      "minigun.entity": "Minigun",
      "rocket_mlrs": "MLRS Rocket",
      "Eoka Pistol": "Eoka Pistol",
      "High Caliber Revolver": "High Caliber Revolver",
      "Abyss Assault Rifle": "Abyss Assault Rifle",
      "Blunderbuss": "Blunderbuss",
      "Sickle": "Sickle",
      "Butcher Knife": "Butcher Knife",
      "pitchfork.entity": "Pitchfork",
      "Snowball Gun": "Snowball Gun",
      "vampirestake.entity": "Vampire Stake",
      "Baseball Bat": "Baseball Bat",
      "Handmade SMG": "Handmade SMG",
      "Beancan Grenade": "Beancan Grenade",
      "Candy Cane Club": "Candy Cane Club",
      "Snowball": "Snowball",
      "Flashlight": "Flashlight"
    },
    "Attachments": {
      "Weapon flashlight": "Weapon Flashlight",
      "Extended Magazine": "Extended Magazine",
      "16x Zoom Scope": "16x Zoom Scope",
      "Weapon Lasersight": "Weapon Lasersight",
      "Silencer": "Silencer",
      "8x Zoom Scope": "8x Zoom Scope",
      "Holosight": "Holosight",
      "Muzzle Boost": "Muzzle Boost",
      "Muzzle Brake": "Muzzle Brake",
      "Simple Handmade Sight": "Simple Handmade Sight",
      "Gas Compression Overdrive": "Gas Compression Overdrive",
      "Burst Module": "Burst Module",
      "Variable Zoom Scope": "Variable Zoom Scope"
    }
  },
  "Variable Formats": {
    "attachments": " ({value})"
  },
  "Variable Colors": {
    "killer": "#C4FF00",
    "victim": "#C4FF00",
    "weapon": "#C4FF00",
    "attachments": "#C4FF00",
    "distance": "#C4FF00",
    "owner": "#C4FF00"
  },
  "Chat Format": "<color=#838383>[<color=#80D000>DeathNotes</color>] {message}</color>",
  "Chat Icon (SteamID)": "76561############",
  "Show Kills in Console": true,
  "Show Kills in Chat": true,
  "Show Patrol Heli Tags": true,
  "Show Bradley APC Tags": true,
  "Message Broadcast Radius (in meters)": -1,
  "Use Metric Distance": true,
  "Require Permission (deathnotes.cansee)": false
}​

Error while compiling DeathNotes: Unexpected character '​' | Line: 1270, Pos: 2

The code above is exactly 1270 lines long, so that means you're likely missing something when you copied/pasted. If you're still having trouble, message me your Email address and I'll send you my files. Thanks!

6qTbv7hGBYxfSGN.jpg Terceran

The code above is exactly 1270 lines long, so that means you're likely missing something when you copied/pasted. If you're still having trouble, message me your Email address and I'll send you my files. Thanks!

Thank you, my English is not very good
[email protected]

I just sent it to your Email, enjoy.