Иконка ресурса

FREE DeathNotes : 6.4.3 6.4.3

Нет прав для скачивания
C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
// #define DEBUG

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

namespace Oxide.Plugins
{
	[Info("Death Notes", "Terceran/Mr.Blue", "6.4.3")]
	[Description("Broadcasts deaths to chat along with detailed information")]
	class DeathNotes : RustPlugin
	{
		#region Fields

		private const string WildcardCharacter = "*";
		private const string CanSeePermission = "deathnotes.cansee";
        private const string SuppressPermission = "deathnotes.suppress";

		private static DeathNotes _instance;

		private PluginConfiguration _configuration;

		private readonly Dictionary<string, string> _enemyPrefabs = new()
		{
			["spikes.floor"] = "Wooden Floor Spikes",
			["barricade.woodwire"] = "Barbed Wooden Barricade",
			["barricade.metal"] = "Metal Barricade",
			["wall.external.high.wood"] = "High External Wooden Wall",
			["wall.external.high.stone"] = "High External Stone Wall",
			["gates.external.high.stone"] = "High External Stone Gate",
			["campfire"] = "Campfire",
			["skull_fire_pit"] = "Skull Fire Pit",
			["heavyscientist"] = "Heavy Scientist"
		};
		private readonly Dictionary<string, string> _weaponPrefabs = new()
		{
			["rocket_basic"] = "Rocket",
			["rocket_hv"] = "High Velocity Rocket",
			["rocket_fire"] = "Incendiary Rocket",
			["grenade.f1.deployed"] = "F1 Grenade",
			["grenade.beancan.deployed"] = "Beancan Grenade",
			["survey_charge.deployed"] = "Survey Charge",
			["explosive.satchel.deployed"] = "Satchel Charge",
			["explosive.timed.deployed"] = "Timed Explosive Charge",
			["rock.entity"] = "Rock",
			["longsword.entity"] = "Longsword",
			["mace.entity"] = "Mace",
			["spear_stone.entity"] = "Stone Spear",
			["spear_wooden.entity"] = "Wooden Spear",
			["machete.weapon"] = "Machete",
			["knife_bone.entity"] = "Bone Knife",
			["bone_club.entity"] = "Bone Club",
			["salvaged_cleaver.entity"] = "Salvaged Cleaver",
			["salvaged_sword.entity"] = "Salvaged Sword",
			["candy_cane.entity"] = "Candy Cane Club",
			["flamethrower.entity"] = "Flame Thrower",
			["snowball.entity"] = "Snowball",
			["combat.knife.entity"] = "Combat Knife"
		};
		private readonly Dictionary<string, CombatEntityType> _combatEntityTypes = new()
		{
			["GunTrap"]  = CombatEntityType.Trap,
			["FlameTurret"]  = CombatEntityType.Turret,
			["AutoTurret"]  = CombatEntityType.Turret,
			["BaseHelicopter"]  = CombatEntityType.Helicopter,
			["BradleyAPC"]  = CombatEntityType.Bradley,
			["BasePlayer"]  = CombatEntityType.Player,
			["NPCMurderer"]  = CombatEntityType.Murderer,
			["CodeLock"]  = CombatEntityType.Lock,
			["Scientist"]  = CombatEntityType.Scientist,
			["ScientistNPC"]  = CombatEntityType.Scientist,
			["HTNPlayer"]  = CombatEntityType.Scientist,
			["NPCAutoTurret"]  = CombatEntityType.Sentry,
			["FireBall"]  = CombatEntityType.Fire,
			["scarecrow"]  = CombatEntityType.ScarecrowNPC,
			["ScientistNPCNew"]  = CombatEntityType.Scientist,
			["tunneldweller"]  = CombatEntityType.TunnelDweller,
			["underwaterdweller"]  = CombatEntityType.UnderwaterDweller
		};

		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>();

        private Dictionary<ulong, HitInfo> _patrolHeliTagTracker = new Dictionary<ulong, HitInfo>();
        private HashSet<ulong> _bradleyApcTagTracker = new HashSet<ulong>();

		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);
            permission.RegisterPermission(SuppressPermission, this);

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

		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.private void OnEntityTakeDamage(BradleyAPC victimEntity, HitInfo hitInfo) => HandleEntityDamage(victimEntity, hitInfo);
        
        //This method tracks Patrol Helicopter damage and reports whoever tagged it.private void OnPatrolHelicopterTakeDamage(PatrolHelicopter victimEntity, HitInfo hitInfo) => HandleEntityDamage(victimEntity, hitInfo);

		private void HandleEntityDamage( BaseCombatEntity victimEntity, HitInfo hitInfo )
		{
			if (victimEntity == null || hitInfo == null)
				return;

			bool isPatrolHeli = victimEntity is PatrolHelicopter;

			HitInfo storedHitInfo;
			ulong netID = victimEntity.net.ID.Value;

			if (isPatrolHeli)
			{
				bool wasHeliIdFound = _patrolHeliTagTracker.TryGetValue(netID, out storedHitInfo);
				if (!wasHeliIdFound)
				{
					storedHitInfo = new HitInfo();
					_patrolHeliTagTracker.Add(netID, storedHitInfo);
				}

				if (hitInfo.WeaponPrefab != null)
					storedHitInfo.WeaponPrefab = hitInfo.WeaponPrefab;

				if (hitInfo.Weapon != null)
					storedHitInfo.Weapon = hitInfo.Weapon;

				if (wasHeliIdFound || (victimEntity.lastAttackedTime != float.NegativeInfinity))
					return;
				
				if (storedHitInfo.Initiator == null && hitInfo.Initiator != null)
					storedHitInfo.Initiator = hitInfo.Initiator;
			}
			else //If it's a Bradley APC
			{
				if ((victimEntity.lastAttackedTime != float.NegativeInfinity) || (_bradleyApcTagTracker.Contains(netID)))
					return;
				_bradleyApcTagTracker.Add(netID);
				storedHitInfo = hitInfo;
			}

			var attackerPlayer = hitInfo.Initiator?.ToPlayer();
			if( attackerPlayer?.displayName == null )
			{
				return;
			}

			storedHitInfo.Initiator = hitInfo.Initiator;

			if( (isPatrolHeli ? !_configuration.ShowPatrolHeliTags : !_configuration.ShowBradleyTags) || permission.UserHasPermission( attackerPlayer.UserIDString, SuppressPermission ) )
			{
				return;
			}

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

			if (data.KillerEntity != null)
				data.KillerEntityType = CombatEntityType.Player;
					
			string message = PopulateMessageVariables(isPatrolHeli ? _configuration.PatrolHeliTagMessage : _configuration.BradleyTagMessage, data);

			object hookResult = false;
			try
			{
				hookResult = Interface.Call("OnDeathNotice", data.ToDictionary(), message);
			}
			catch (NullReferenceException)
			{
				return;
			}
			
			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 Patrol Helicopters are killed or despawns naturally, and cleans them from the tag trackers.private void OnEntityKill(PatrolHelicopter entity)
		{
			_patrolHeliTagTracker.Remove(entity.net.ID.Value);
		}

		//This method tracks when Bradley APCs are killed or despawns naturally, and cleans them from the tag trackers.private void OnEntityKill(BradleyAPC entity)
		{
			_bradleyApcTagTracker.Remove(entity.net.ID.Value);
		}
		
		private void OnEntityDeath(BaseCombatEntity victimEntity, HitInfo hitInfo)
		{
			//There is no victim information for some reason.//Note: If someone is wounded and dies, there will be null hitInfo
			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
			};
			
			if (victimEntity is PatrolHelicopter && _patrolHeliTagTracker.TryGetValue( netID, out var value ))
				data.HitInfo = value;

			// 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);

			// Need do this before we cancel out of the method, as we need to track all these entities dying.Even if it's not a player killing them.if (victimEntity is PatrolHelicopter)
				_patrolHeliTagTracker.Remove(netID);
			else if (victimEntity is BradleyAPC)
				_bradleyApcTagTracker.Remove(netID);
			
			// 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 = false;
			try
			{ 
				hookResult = Interface.Call("OnDeathNotice", data.ToDictionary(), message);
			}
			catch (NullReferenceException)
			{
				return;
			}

			if (hookResult?.Equals(false) ?? false)
				return;
			
			if ((_configuration.ShowInChat || _configuration.ShowInConsole) &&
				((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));
			}
		}

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

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

				if (killerPrefabName.Contains("underwaterdweller"))
				{
					data.KillerEntityType = CombatEntityType.UnderwaterDweller;
				}
				else if (killerPrefabName.Contains("tunneldweller"))
				{
					data.KillerEntityType = CombatEntityType.TunnelDweller;
				}
				else if (killerPrefabName.Contains("bradleyapc"))
				{
					data.KillerEntityType = CombatEntityType.Bradley;
				}
			}
		}

		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;
			
			string entityTypeName = entity.GetType().Name;
			if (entityTypeName != "ScientistNPC" && _combatEntityTypes.TryGetValue( entityTypeName, out var entityType ))
				return entityType;

			if (_combatEntityTypes.TryGetValue( entity.ShortPrefabName, out var type ))
				return type;

			//For those plugins that do not correctly type ZombieNPCs as Zombies.if (entityTypeName.StartsWith("Zombie"))
				return CombatEntityType.ZombieNPC;
			
 			switch (entity)
			{
				case ScientistNPC:
				{
					if (entity.ShortPrefabName.EndsWith("heavy"))
						return CombatEntityType.HeavyScientist;
					return CombatEntityType.Scientist;
				}

				case Zombie:
					return CombatEntityType.ZombieNPC;
				
				case PatrolHelicopter:
					return CombatEntityType.Helicopter;
				
				case BaseAnimalNPC:
				case SimpleShark:
				case Wolf2:
				case FarmableAnimal:
					return CombatEntityType.Animal;
				
				case BaseOven:
					return CombatEntityType.HeatSource;
				
				case SimpleBuildingBlock:
					return CombatEntityType.ExternalWall;
				
				case Barricade:
					return CombatEntityType.Barricade;
				
				case BaseTrap:
				case IOEntity:
					return CombatEntityType.Trap;

				case GingerbreadNPC:
					return CombatEntityType.GingerbreadNPC;
				
				case Minicopter:
					return CombatEntityType.Minicopter;
				
				case ScrapTransportHelicopter:
					return CombatEntityType.ScrapTransportHelicopter;
				
				case AttackHelicopter:
					return CombatEntityType.AttackHelicopter;
				
				case BeeSwarmAI:
					return CombatEntityType.BeeSwarm;
				
				default:
					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);
				
				case CombatEntityType.Murderer:
				case CombatEntityType.ScarecrowNPC:
				case CombatEntityType.Scientist:
				case CombatEntityType.HeavyScientist:
				case CombatEntityType.ZombieNPC:
				case CombatEntityType.GingerbreadNPC:
					var name = entity.ToPlayer()?.displayName;
					
					if ((combatEntityType == CombatEntityType.HeavyScientist) && (name == "Scientist"))
						name = "Heavy Scientist";
					
					if (!string.IsNullOrEmpty(name) && name != entity.ToPlayer()?.userID.ToString())
					{
						return name;
					}

					if (!_enemyPrefabs.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";

				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";
				
				//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";
				
				case CombatEntityType.BeeSwarm:
					return "BeeSwarm";
			}

			if (_enemyPrefabs.TryGetValue( entity.ShortPrefabName, out string entityName ))
				return entityName;

			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,
			ScarecrowNPC = 16,
			TunnelDweller = 17,
			UnderwaterDweller = 18,
			ZombieNPC = 19,
			GingerbreadNPC = 20,
			HeavyScientist = 21,
			Minicopter = 22,
			ScrapTransportHelicopter = 23,
			AttackHelicopter = 24,
			BeeSwarm = 25
		}

		#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.TryGetValue( prefab, out string weaponName ))
					return weaponName;

				return prefab;
			}
			
			//Added by Terceran 20250215.Ballistas are a special case because they are considered a vehicle and
			//not a weapon.if (deathData.HitInfo.ProjectilePrefab?.name?.StartsWith("ballista") ?? false)
			{
				return "Ballista";
			}

			// 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 = "76561198077847390";

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

			[JsonProperty("Show Kills in Chat")] public bool ShowInChat = true;
			
			[JsonProperty("Show Patrol Heli Tags")] public bool ShowPatrolHeliTags = true;
			[JsonProperty("Show Bradley APC Tags")] public bool ShowBradleyTags = true;
			
			[JsonProperty("Patrol Helicopter Tag Message")]
			public string PatrolHeliTagMessage = "{killer} has tagged the {victim} with their {weapon} over a distance of {distance}.";
		   
			[JsonProperty("Bradley Tag Message")]
			public string BradleyTagMessage = "{killer} has tagged the {victim} with their {weapon} over a distance of {distance}.";

			[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()
			{
				Translations.Names.TryAdd("Chicken", "Chicken");
				Translations.Names.TryAdd("Boar", "Boar");
				Translations.Names.TryAdd("Stag", "Stag");
				Translations.Names.TryAdd("Bear", "Bear");
				Translations.Names.TryAdd("Wolf", "Wolf");
				Translations.Names.TryAdd("Simple Shark", "Shark");
				Translations.Names.TryAdd("Scientist", "Scientist");
				Translations.Names.TryAdd("Heavy Scientist", "Heavy Scientist");
				Translations.Names.TryAdd("HeavyScientist", "Heavy Scientist");
				Translations.Names.TryAdd("scientistnpc_heavy", "Heavy Scientist");
				Translations.Names.TryAdd("Helicopter", "Patrol Helicopter");
				Translations.Names.TryAdd("Bradley APC", "Bradley APC");
				Translations.Names.TryAdd("Polarbear", "Polar Bear");
				Translations.Names.TryAdd("Code Lock", "Code Lock");
				Translations.Names.TryAdd("Fire", "Fire");
				Translations.Names.TryAdd("Sentry", "Sentry");
				Translations.Names.TryAdd("Sam Site", "SAM Site");
				Translations.Names.TryAdd("ScarecrowNPC", "Scarecrow");
				Translations.Names.TryAdd("Tunnel Dweller", "Tunnel Dweller");
				Translations.Names.TryAdd("Barbed Wooden Barricade", "Barbed Wooden Barricade");
				Translations.Names.TryAdd("Barricade", "Barricade");
				Translations.Names.TryAdd("Auto Turret", "Auto Turret");
				Translations.Names.TryAdd("Patrol Helicopter", "Patrol Helicopter");
				Translations.Names.TryAdd("GRAF", "GRAF");
				Translations.Names.TryAdd("Underwater Dweller", "Underwater Dweller");
				Translations.Names.TryAdd("Campfire", "Campfire");
				Translations.Names.TryAdd("Wolf2", "Wolf");
				Translations.Names.TryAdd("Horse", "Horse");
				Translations.Names.TryAdd("Landmine", "Landmine");
				Translations.Names.TryAdd("Tesla Coil", "Tesla Coil");
				Translations.Names.TryAdd("Gun Trap", "Shotgun Trap");
				Translations.Names.TryAdd("Flame Turret", "Flame Turret");
				Translations.Names.TryAdd("GingerbreadNPC", "Gingerbread Man");
				Translations.Names.TryAdd("ScrapTransportHelicopter", "Scrap Transport Helicopter");
				Translations.Names.TryAdd("AttackHelicopter", "Attack Helicopter");
				Translations.Names.TryAdd("Minicopter", "Minicopter");
				Translations.Names.TryAdd("Metal Barricade", "Metal Barricade");
				Translations.Names.TryAdd("ZombieNPC", "Zombie");
				Translations.Names.TryAdd("Bear Trap", "Bear Trap");
				Translations.Names.TryAdd("Custom Bradley", "Custom Bradley");
				Translations.Names.TryAdd("Farmable Animal", "Farm Animal");
				Translations.Names.TryAdd("BeeSwarm", "Bee Swarm");
				Translations.Names.TryAdd("Custom Animal Npc", "Custom Animal NPC");

				Translations.Bodyparts.TryAdd("Body", "Body");
				Translations.Bodyparts.TryAdd("Leg", "Leg");
				Translations.Bodyparts.TryAdd("Chest", "Chest");
				Translations.Bodyparts.TryAdd("Stomach", "Stomach");
				Translations.Bodyparts.TryAdd("Head", "Head");
				Translations.Bodyparts.TryAdd("Arm", "Arm");
				Translations.Bodyparts.TryAdd("Hand", "Hand");
				Translations.Bodyparts.TryAdd("Foot", "Foot");
				
				Translations.Weapons.TryAdd("Salvaged Sword", "Salvaged Sword");
				Translations.Weapons.TryAdd("Revolver", "Revolver");
				Translations.Weapons.TryAdd("Salvaged Cleaver", "Salvaged Cleaver");
				Translations.Weapons.TryAdd("Mace", "Mace");
				Translations.Weapons.TryAdd("Assault Rifle", "Assault Rifle");
				Translations.Weapons.TryAdd("Double Barrel Shotgun", "Double Barrel Shotgun");
				Translations.Weapons.TryAdd("Bolt Action Rifle", "Bolt Action Rifle");
				Translations.Weapons.TryAdd("M92 Pistol", "M92 Pistol");
				Translations.Weapons.TryAdd("Custom SMG", "Custom SMG");
				Translations.Weapons.TryAdd("LR-300 Assault Rifle", "LR-300 Assault Rifle");
				Translations.Weapons.TryAdd("Timed Explosive Charge", "Timed Explosive Charge (C4)");
				Translations.Weapons.TryAdd("Pump Shotgun", "Pump Shotgun");
				Translations.Weapons.TryAdd("Prototype 17", "Prototype 17");
				Translations.Weapons.TryAdd("Hunting Bow", "Hunting Bow");
				Translations.Weapons.TryAdd("Thompson", "Thompson");
				Translations.Weapons.TryAdd("M39 Rifle", "M39 Rifle");
				Translations.Weapons.TryAdd("Hatchet", "Hatchet");
				Translations.Weapons.TryAdd("hatchet.entity", "Hatchet");
				Translations.Weapons.TryAdd("M4 Shotgun", "M4 Shotgun");
				Translations.Weapons.TryAdd("HMLMG", "HMLMG");
				Translations.Weapons.TryAdd("l96.entity", "L96 Rifle");
				Translations.Weapons.TryAdd("L96 Rifle", "L96 Rifle");
				Translations.Weapons.TryAdd("m249.entity", "M249");
				Translations.Weapons.TryAdd("M249", "M249");
				Translations.Weapons.TryAdd("Pickaxe", "Pickaxe");
				Translations.Weapons.TryAdd("Bone Knife", "Bone Knife");
				Translations.Weapons.TryAdd("Semi-Automatic Pistol", "Semi-Automatic Pistol");
				Translations.Weapons.TryAdd("Stone Hatchet", "Stone Hatchet");
				Translations.Weapons.TryAdd("Rock", "Rock");
				Translations.Weapons.TryAdd("Skinning Knife", "Skinning Knife");
				Translations.Weapons.TryAdd("Crossbow", "Crossbow");
				Translations.Weapons.TryAdd("Combat Knife", "Combat Knife");
				Translations.Weapons.TryAdd("Semi-Automatic Rifle", "Semi-Automatic Rifle");
				Translations.Weapons.TryAdd("MP5A4", "MP5A4");
				Translations.Weapons.TryAdd("Jackhammer", "Jackhammer");
				Translations.Weapons.TryAdd("SKS", "SKS");
				Translations.Weapons.TryAdd("militaryflamethrower.entity", "Military Flamethrower");
				Translations.Weapons.TryAdd("High Velocity Rocket", "High Velocity Rocket");
				Translations.Weapons.TryAdd("Spas-12 Shotgun", "Spas-12 Shotgun");
				Translations.Weapons.TryAdd("knife.combat.entity", "Combat Knife");
				Translations.Weapons.TryAdd("2module_car_spawned.entity", "2-module Vehicle");
				Translations.Weapons.TryAdd("car_chassis_2module.entity", "2-module Vehicle");
				Translations.Weapons.TryAdd("car_2mod_01", "2-module Vehicle");
				Translations.Weapons.TryAdd("car_2mod_02", "2-module Vehicle");
				Translations.Weapons.TryAdd("car_2mod_03", "2-module Vehicle");
				Translations.Weapons.TryAdd("car_2mod_04", "2-module Vehicle");
				Translations.Weapons.TryAdd("car_2mod_05", "2-module Vehicle");
				Translations.Weapons.TryAdd("car_2mod_06", "2-module Vehicle");
				Translations.Weapons.TryAdd("car_2mod_07", "2-module Vehicle");
				Translations.Weapons.TryAdd("car_2mod_08", "2-module Vehicle");
				Translations.Weapons.TryAdd("3module_car_spawned.entity", "3-module Vehicle");
				Translations.Weapons.TryAdd("car_chassis_3module.entity", "3-module Vehicle");
				Translations.Weapons.TryAdd("car_3mod_01", "3-module Vehicle");
				Translations.Weapons.TryAdd("car_3mod_02", "3-module Vehicle");
				Translations.Weapons.TryAdd("car_3mod_03", "3-module Vehicle");
				Translations.Weapons.TryAdd("car_3mod_04", "3-module Vehicle");
				Translations.Weapons.TryAdd("car_3mod_05", "3-module Vehicle");
				Translations.Weapons.TryAdd("car_3mod_06", "3-module Vehicle");
				Translations.Weapons.TryAdd("car_3mod_07", "3-module Vehicle");
				Translations.Weapons.TryAdd("car_3mod_08", "3-module Vehicle");
				Translations.Weapons.TryAdd("car_3mod_09", "3-module Vehicle");
				Translations.Weapons.TryAdd("car_3mod_10", "3-module Vehicle");
				Translations.Weapons.TryAdd("car_3mod_11", "3-module Vehicle");
				Translations.Weapons.TryAdd("car_3mod_12", "3-module Vehicle");
				Translations.Weapons.TryAdd("4module_car_spawned.entity", "4-module Vehicle");
				Translations.Weapons.TryAdd("car_chassis_4module.entity", "4-module Vehicle");
				Translations.Weapons.TryAdd("car_4mod_01", "4-module Vehicle");
				Translations.Weapons.TryAdd("car_4mod_02", "4-module Vehicle");
				Translations.Weapons.TryAdd("car_4mod_03", "4-module Vehicle");
				Translations.Weapons.TryAdd("car_4mod_04", "4-module Vehicle");
				Translations.Weapons.TryAdd("car_4mod_05", "4-module Vehicle");
				Translations.Weapons.TryAdd("car_4mod_06", "4-module Vehicle");
				Translations.Weapons.TryAdd("car_4mod_07", "4-module Vehicle");
				Translations.Weapons.TryAdd("car_4mod_08", "4-module Vehicle");
				Translations.Weapons.TryAdd("car_4mod_09", "4-module Vehicle");
				Translations.Weapons.TryAdd("car_4mod_10", "4-module Vehicle");
				Translations.Weapons.TryAdd("car_4mod_11", "4-module Vehicle");
				Translations.Weapons.TryAdd("motorbike", "Motorbike");
				Translations.Weapons.TryAdd("motorbike_sidecar", "Motorbike with Sidecar");
				Translations.Weapons.TryAdd("pedalbike", "Bicycle");
				Translations.Weapons.TryAdd("pedaltrike", "Tricycle");
				Translations.Weapons.TryAdd("snowmobile", "Snowmobile");
				Translations.Weapons.TryAdd("F1 Grenade", "F1 Grenade");
				Translations.Weapons.TryAdd("Satchel Charge", "Satchel Charge");
				Translations.Weapons.TryAdd("Wooden Spear", "Wooden Spear");
				Translations.Weapons.TryAdd("Stone Spear", "Stone Spear");
				Translations.Weapons.TryAdd("Compound Bow", "Compound Bow");
				Translations.Weapons.TryAdd("Machete", "Machete");
				Translations.Weapons.TryAdd("Pitchfork", "Pitchfork");
				Translations.Weapons.TryAdd("Stone Pickaxe", "Stone Pickaxe");
				Translations.Weapons.TryAdd("stone_pickaxe.entity", "Stone Pickaxe");
				Translations.Weapons.TryAdd("Legacy bow", "Legacy Bow");
				Translations.Weapons.TryAdd("Rocket", "Rocket");
				Translations.Weapons.TryAdd("Incendiary Rocket", "Incendiary Rocket");
				Translations.Weapons.TryAdd("Salvaged Axe", "Salvaged Axe");
				Translations.Weapons.TryAdd("Torch", "Torch");
				Translations.Weapons.TryAdd("pickaxe.entity", "Pickaxe");
				Translations.Weapons.TryAdd("Salvaged Hammer", "Salvaged Hammer");
				Translations.Weapons.TryAdd("40mm_grenade_he", "40mm HE Grenade");
				Translations.Weapons.TryAdd("Minigun", "Minigun");
				Translations.Weapons.TryAdd("Assault Rifle - ICE", "Ice Assault Rifle");
				Translations.Weapons.TryAdd("Python Revolver", "Python Revolver");
				Translations.Weapons.TryAdd("Nailgun", "Nailgun");
				Translations.Weapons.TryAdd("Salvaged Icepick", "Salvaged Icepick");
				Translations.Weapons.TryAdd("Longsword", "Longsword");
				Translations.Weapons.TryAdd("Waterpipe Shotgun", "Waterpipe Shotgun");
				Translations.Weapons.TryAdd("Chainsaw", "Chainsaw");
				Translations.Weapons.TryAdd("workcart.entity", "Work Cart");
				Translations.Weapons.TryAdd("locomotive.entity", "Locomotive");
				Translations.Weapons.TryAdd("workcart_aboveground.entity", "Above Ground Work Cart");
				Translations.Weapons.TryAdd("Paddle", "Paddle");
				Translations.Weapons.TryAdd("workcart_aboveground2.entity", "Above Ground Work Cart");
				Translations.Weapons.TryAdd("hammer_salvaged.entity", "Salvaged Hammer");
				Translations.Weapons.TryAdd("Multiple Grenade Launcher", "Multiple Grenade Launcher");
				Translations.Weapons.TryAdd("minigun.entity", "Minigun");
				Translations.Weapons.TryAdd("rocket_mlrs", "MLRS Rocket");
				Translations.Weapons.TryAdd("Eoka Pistol", "Eoka Pistol");
				Translations.Weapons.TryAdd("High Caliber Revolver", "High Caliber Revolver");
				Translations.Weapons.TryAdd("Abyss Assault Rifle", "Abyss Assault Rifle");
				Translations.Weapons.TryAdd("Blunderbuss", "Blunderbuss");
				Translations.Weapons.TryAdd("Sickle", "Sickle");
				Translations.Weapons.TryAdd("Butcher Knife", "Butcher Knife");
				Translations.Weapons.TryAdd("pitchfork.entity", "Pitchfork");
				Translations.Weapons.TryAdd("Snowball Gun", "Snowball Gun");
				Translations.Weapons.TryAdd("vampirestake.entity", "Vampire Stake");
				Translations.Weapons.TryAdd("Baseball Bat", "Baseball Bat");
				Translations.Weapons.TryAdd("Handmade SMG", "Handmade SMG");
				Translations.Weapons.TryAdd("Beancan Grenade", "Beancan Grenade");
				Translations.Weapons.TryAdd("Candy Cane Club", "Candy Cane Club");
				Translations.Weapons.TryAdd("Snowball", "Snowball");
				Translations.Weapons.TryAdd("Flashlight", "Flashlight");
				Translations.Weapons.TryAdd("Speargun", "Speargun");
				Translations.Weapons.TryAdd("double_shotgun.entity", "Double Barrel Shotgun");
				Translations.Weapons.TryAdd("Skull", "Skull");
				Translations.Weapons.TryAdd("Mini Crossbow", "Mini Crossbow");
				Translations.Weapons.TryAdd("Ice Assault Rifle", "Ice Assault Rifle");
				Translations.Weapons.TryAdd("Frontier Hatchet", "Frontier Hatchet");
				Translations.Weapons.TryAdd("Medieval Assault Rifle", "Medieval Assault Rifle");
				Translations.Weapons.TryAdd("boulder_explosive_deployed", "Propane Explosive Bomb");
				Translations.Weapons.TryAdd("Ballista", "Ballista");
				Translations.Weapons.TryAdd("boulder_explosive", "Propane Explosive Bomb");
				Translations.Weapons.TryAdd("boulder_mid", "Scattershot");
				Translations.Weapons.TryAdd("boulder_incendiary", "Firebomb");
				Translations.Weapons.TryAdd("Flame Thrower", "Flame Thrower");
				Translations.Weapons.TryAdd("grenade.molotov.deployed", "Molotov Cocktail");
				Translations.Weapons.TryAdd("Concrete Pickaxe", "Concrete Pickaxe");

// ... [Code preview truncated due to length. Full source in attached file] ...

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху