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

FREE DeathNotes : 6.5.5 6.5.5

Нет прав для скачивания
C#:
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using Rust;
using Rust.Ai.Gen2;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;

namespace Oxide.Plugins
{
	[Info("Death Notes", "Terceran/Mr. Blue", "6.5.5")]
	[Description("Broadcasts detailed death information")]
	class DeathNotes : RustPlugin
	{
		[PluginReference]
		private Plugin UINotify;
		
		[PluginReference]
		private Plugin Notify;
		
		#region Fields

		private const string WildcardCharacter = "*";
		private const string CanSeePermission = "deathnotes.cansee";
		private const string CantSeePermission = "deathnotes.cantsee";
        private const string SuppressPermission = "deathnotes.suppress";
        private const string TeamOnlyPermission = "deathnotes.seeteamonly";

		private static DeathNotes _instance;

		private PluginConfiguration _configuration;

		private readonly Dictionary<string, string> _enemyPrefabs = new()
		{
			["spikes.floor"] = "Wooden Floor Spike Cluster",
			["spikes_static"] = "Wooden Floor Spike Cluster",
			["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 void LogOutput(string text)
        {
	        //Puts(text);
	        string logText = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") + " : " + text;
	        LogToFile("DeathNotes-log", logText, this);
        }
        
		private readonly Func<PluginConfiguration.DeathMessage, DeathData, bool>[] _messageMatchingStages =
		{
			(m, d) => m.Enabled == true && MatchesCombatEntityType(d.KillerEntityType, m.KillerType) &&
			          MatchesCombatEntityType(d.VictimEntityType, m.VictimType) &&
			          MatchesDamageType(d.DamageType, m.DamageType),

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

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

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

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

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

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

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

		#endregion

		#region Hooks

		private void Init()
		{
			_instance = this;

			permission.RegisterPermission(CanSeePermission, this);
            permission.RegisterPermission(CantSeePermission, this);
            permission.RegisterPermission(SuppressPermission, this);
            permission.RegisterPermission(TeamOnlyPermission, this);

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

            string tmpPerm;
            foreach (string perm in _configuration.PatrolHeliDisplayPerms)
            {
                tmpPerm = perm.Trim().ToLower();
                if (!perm.StartsWith("deathnotes."))
                    continue;
                permission.RegisterPermission(perm, this);
            }

            foreach (string perm in _configuration.BradleyDisplayPerms)
            {
                tmpPerm = perm.Trim().ToLower();
                if (!perm.StartsWith("deathnotes."))
                    continue;
                permission.RegisterPermission(perm, this);
            }

            foreach (var msg in _configuration.Translations.Messages)
			{
                foreach (string perm in msg.DisplayPerms)
                {
                    tmpPerm = perm.Trim().ToLower();
                    if (!perm.StartsWith("deathnotes."))
                        continue;
                    permission.RegisterPermission(perm, this);
                }
            }

			LogOutput("Initializing DeathNotes plugin");
		}

		private void OnPluginLoaded(Plugin plugin)
		{
			if (plugin.Name == "Notify")
				Notify = plugin;
			else if (plugin.Name == "UINotify")
				UINotify = plugin;
		}

		private void OnPluginUnloaded(Plugin plugin)
		{
			if (plugin.Name == "Notify")
				Notify = null;
			else if (plugin.Name == "UINotify")
				UINotify = null;
		}
		
		private void Unload()
		{
			LogOutput("Unloading DeathNotes plugin");
			_instance = null;
		}

		private void OnEntityTakeDamage(BasePlayer victimEntity, HitInfo hitInfo)
		{
			if (victimEntity == null || hitInfo == null)
            	return;

            BaseEntity attacker = victimEntity.lastAttacker ?? hitInfo?.Initiator;

            var userId = victimEntity.ToPlayer().userID;

            AttackInfo currentAttack;
            if (_previousAttack.TryGetValue(userId, out currentAttack))
            {
                if (attacker != null)
                    currentAttack.Attacker = attacker;
                
                if (hitInfo != null)
                    currentAttack.HitInfo = hitInfo;

                if (victimEntity.lastDamage != null)
                    currentAttack.DamageType = victimEntity.lastDamage;
            }
            else
            {
                _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 ? !ShowPatrolHeliTags() : !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;

			//Prints to console
			if (isPatrolHeli ? _configuration.ShowPatrolHeliTagsInConsole : _configuration.ShowBradleyTagsInConsole)
				Puts(StripRichText(message));
				
			//Prints to chat, the Notify plugin, and/or the UINotify plugin
			foreach (var player in BasePlayer.activePlayerList)
			{
				if ((_configuration.RequirePermission &&
				     !permission.UserHasPermission(player.UserIDString, CanSeePermission)) ||
				    permission.UserHasPermission(player.UserIDString, CantSeePermission) ||
                    (permission.UserHasPermission(player.UserIDString, TeamOnlyPermission) && !ArePlayersOnSameTeam(player, data)))
					continue;

                if (isPatrolHeli ? !PlayersPassesPermsCheck(player.UserIDString, _configuration.PatrolHeliDisplayPerms) : !PlayersPassesPermsCheck(player.UserIDString, _configuration.BradleyDisplayPerms))
                        continue;

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

				if (isPatrolHeli ? _configuration.ShowPatrolHeliTagsInChat : _configuration.ShowBradleyTagsInChat)
				{
					Player.Reply(
						player,
						_configuration.ChatFormat.Replace("{message}", message),
						ulong.Parse(_configuration.ChatIcon)
					);
				}
					
				if ((isPatrolHeli ? _configuration.ShowPatrolHeliTagsInNotify : _configuration.ShowBradleyTagsInNotify) && Notify != null)
					Notify.Call("SendNotify", player, _configuration.NotifyMessageType, _configuration.ChatFormat.Replace("{message}", message));
					
				if ((isPatrolHeli ? _configuration.ShowPatrolHeliTagsInUINotify : _configuration.ShowBradleyTagsInUINotify) && UINotify != null)
					UINotify.Call("SendNotify", player, _configuration.UINotifyMessageType, _configuration.ChatFormat.Replace("{message}", 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.
			// Try to avoid error when entity was destroyed
			//Note: If someone is wounded and dies, there will be null hitInfo
			if ((victimEntity == null) || (victimEntity.gameObject == null))
				return;

			//Snakes produce two death messages, one when it is killed and one when it is skinned.
			//The below is to prevent a death note related to skinning.
			if ((victimEntity is SnakeHazard) && (((SnakeHazard)victimEntity).IsCorpse))
				return;
			
			//Some corpses produce two death messages, one when it is killed and one when it is skinned.
			//The below is to prevent a death note related to skinning.
			if (victimEntity is BaseCorpse)
				return;
			
			if (hitInfo == null)
			{
				hitInfo = new HitInfo();
				hitInfo.Initiator = null;
			}

			//Attempts to get the NetID. This was occasionally resulting in a NullReferenceException, so we're being careful.
			ulong netID = 0;
			try
			{
				netID = victimEntity.net.ID.Value;
			}
			catch (Exception e)
			{
				LogOutput($"Exception in OnEntityDeath while retrieving victimEntity.net: {e.ToString()}");
				netID = 0;
			}
			
			if ((victimEntity is PatrolHelicopter || victimEntity is BradleyAPC) && netID == 0)
				return;

			DeathData 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 (data.VictimEntityType == CombatEntityType.Player)
                _previousAttack.Remove(data.VictimEntity.ToPlayer().userID);
            
			if (_configuration.DebugMode)
			{
				LogOutput("[DEATHNOTES DEBUG]");
				LogOutput(
					$"\tKillerEntity: {data.KillerEntity?.GetType().Name ?? "NULL"} / {data.KillerEntity?.ShortPrefabName ?? "NULL"} / {data.KillerEntity?.PrefabName ?? "NULL"}");
				if (data.KillerEntity != null)
					LogOutput(
						$"\tKiller's Owner: {covalence.Players.FindPlayerById(data.KillerEntity?.OwnerID.ToString())?.Name}");
				LogOutput(
					$"\tVictimEntity: {data.VictimEntity?.GetType().Name ?? "NULL"} / {data.VictimEntity?.ShortPrefabName ?? "NULL"} / {data.VictimEntity?.PrefabName ?? "NULL"}");
				LogOutput($"\tInitiator: {data.HitInfo?.Initiator?.ShortPrefabName}");
				LogOutput($"\tLastAttacker: {data.VictimEntity?.lastAttacker?.ShortPrefabName}");
				LogOutput($"\tKillerEntityType: {data.KillerEntityType}");
				LogOutput($"\tVictimEntityType: {data.VictimEntityType}");
				LogOutput($"\tDamageType: {data.DamageType}");
				LogOutput($"\tBodypart: {GetCustomizedBodypartName(data.HitInfo)}");
				LogOutput($"\tWeapon: {hitInfo?.WeaponPrefab?.ShortPrefabName ?? "NULL"}");
                if (data.HitInfo != null)
                    LogOutput($"\tHitPositionWorld: {GetPositionString(data.HitInfo.HitPositionWorld)}");
			}
			

			// 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;
			
            PluginConfiguration.DeathMessage deathMessage = GetDeathMessage(data);

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

			if ((message == null) || (deathMessage == null))
				return;
	
			object hookResult = false;
			try
			{ 
				hookResult = Interface.Call("OnDeathNotice", data.ToDictionary(), message);
				if (hookResult?.Equals(false) ?? false)
					return;
			}
			catch (Exception e)
			{
				LogOutput($"Exception in OnEntityDeath while calling OnDeathNotice hook: {e.ToString()}");
				return;
			}
			
			if (
					(deathMessage.ShowKillsInChat == true || deathMessage.ShowKillsInConsole == true || deathMessage.ShowKillsInNotify == true || deathMessage.ShowKillsInUINotify == true) &&
					(data.KillerEntityType != CombatEntityType.Player || !permission.UserHasPermission(data.KillerEntity?.ToPlayer()?.UserIDString, SuppressPermission)) &&
					(data.VictimEntityType != CombatEntityType.Player || !permission.UserHasPermission(data.VictimEntity?.ToPlayer()?.UserIDString, SuppressPermission))
			    )
			{

				//Prints to console
				if (deathMessage.ShowKillsInConsole == true)
					Puts(StripRichText(message));
				
				//Prints to chat, the Notify plugin, and/or the UINotify plugin
				foreach (var player in BasePlayer.activePlayerList)
				{
					if ((_configuration.RequirePermission &&
						!permission.UserHasPermission(player.UserIDString, CanSeePermission)) ||
					    permission.UserHasPermission(player.UserIDString, CantSeePermission) ||
                        (permission.UserHasPermission(player.UserIDString, TeamOnlyPermission) && !ArePlayersOnSameTeam(player, data)))
						continue;
                    
                    if (!PlayersPassesPermsCheck(player.UserIDString, deathMessage.DisplayPerms))
                        continue;

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

					if (deathMessage.ShowKillsInChat == true)
					{
						Player.Reply(
							player,
							_configuration.ChatFormat.Replace("{message}", message),
							ulong.Parse(_configuration.ChatIcon)
						);
					}
					
					if ((deathMessage.ShowKillsInNotify == true) && Notify != null)
						Notify.Call("SendNotify", player, _configuration.NotifyMessageType, _configuration.ChatFormat.Replace("{message}", message));

					if ((deathMessage.ShowKillsInUINotify == true) && UINotify != null)
						UINotify.Call("SendNotify", player, _configuration.UINotifyMessageType, _configuration.ChatFormat.Replace("{message}", message));
				}
			}
		}

		private void RepairEntityTypes(ref DeathData data)
		{
			if (data.VictimEntity != null)
			{
				string victimPrefabName = data.VictimEntity.ShortPrefabName.ToLower();
				if (victimPrefabName.Contains("corpse"))
				{
					data.VictimEntityType = CombatEntityType.Other;
				}
				else 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;
				}
				else if (victimPrefabName.Contains("shark"))
				{
					data.VictimEntityType = CombatEntityType.Shark;
				}
			}

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

				if (killerPrefabName.Contains("corpse"))
				{
					data.KillerEntityType = CombatEntityType.Other;
				}
				else if (killerPrefabName.Contains("underwaterdweller"))
				{
					data.KillerEntityType = CombatEntityType.UnderwaterDweller;
				}
				else if (killerPrefabName.StartsWith("excavator"))
				{
					data.KillerEntityType = CombatEntityType.ExcavatorArm;
				}
				else if (killerPrefabName.StartsWith("carshredder"))
				{
					data.KillerEntityType = CombatEntityType.CarShredder;
				}
				else if (killerPrefabName.Contains("tunneldweller"))
				{
					data.KillerEntityType = CombatEntityType.TunnelDweller;
				}
				else if (killerPrefabName.Contains("bradleyapc"))
				{
					data.KillerEntityType = CombatEntityType.Bradley;
				}
				else if (killerPrefabName.Contains("shark"))
				{
					data.KillerEntityType = CombatEntityType.Shark;
				}
				else if (killerPrefabName.Contains("cactus"))
				{
					data.KillerEntityType = CombatEntityType.Cactus;
				}
				else if (killerPrefabName.Contains("locomotive"))
				{
					data.KillerEntityType = CombatEntityType.Train;
				}
				else if (killerPrefabName.Contains("workcart"))
				{
					data.KillerEntityType = CombatEntityType.Train;
				}
				else if (killerPrefabName.Contains("train"))
				{
					data.KillerEntityType = CombatEntityType.Train;
				}

			}
		}

		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)
        {
            if (hitInfo.Initiator == null)
			    hitInfo.Initiator = fireBall;
        }

		#endregion

		#region Death Messages

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

				if (match != null)
					return match;
			}

			return null;
		}

		private string GetDeathMessageString(PluginConfiguration.DeathMessage deathMessage)
		{
            if (deathMessage != null)
                return deathMessage.Messages.GetRandom((uint) DateTime.UtcNow.Millisecond);
            else 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),
                ["position"] = GetPositionString(data.HitInfo.HitPositionWorld),
                ["mapgrid"] = MapHelper.GridToString(MapHelper.PositionToGrid(data.HitInfo.HitPositionWorld))
			};

			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) || (data.KillerEntityType == CombatEntityType.CustomNPC) || (data.KillerEntityType == CombatEntityType.Drone))
				{
					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
						 || data.KillerEntityType == CombatEntityType.SAM)
				{
					replacements.Add("owner",
						covalence.Players.FindPlayerById(data.KillerEntity.OwnerID.ToString())?.Name ?? "Unknown Owner"
					);
				}
			}

			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;
			
            //For a plugin that defined a CustomPet entity name, we redefine it as a CustonNPC.
			if (entityTypeName.StartsWith("CustomPet"))
				return CombatEntityType.CustomNPC;

 			switch (entity)
			{
				case ScientistNPC:
                case ScientistNPC2:
				{
					if (entity.ShortPrefabName.EndsWith("heavy"))
						return CombatEntityType.HeavyScientist;
					return CombatEntityType.Scientist;
				}

				case Zombie:
					return CombatEntityType.ZombieNPC;
				
				case PatrolHelicopter:
					return CombatEntityType.Helicopter;
				
				case BaseAnimalNPC:
				case Wolf2:
				case Panther:
				case Tiger:
				case Crocodile:
				case SnakeHazard:
				case RidableHorse:
				case FarmableAnimal:
					return CombatEntityType.Animal;
				
				case SamSite:
					return CombatEntityType.SAM;
				
				case BaseOven:
					return CombatEntityType.HeatSource;
				
				case SimpleBuildingBlock:
					return CombatEntityType.ExternalWall;
				
				case Barricade:
					return CombatEntityType.Barricade;
				
				case BaseTrap:
				case IOEntity:
				case GunTrap:
					return CombatEntityType.Trap;

				case GingerbreadNPC:
					return CombatEntityType.GingerbreadNPC;

				case ScarecrowNPC:
					return CombatEntityType.ScarecrowNPC;
				
				case Minicopter:
					return CombatEntityType.Minicopter;
				
				case ScrapTransportHelicopter:
					return CombatEntityType.ScrapTransportHelicopter;
				
				case AttackHelicopter:
					return CombatEntityType.AttackHelicopter;
				
				case BeeSwarmAI:
					return CombatEntityType.BeeSwarm;
				
				case BaseFishNPC:
					if (entity.ShortPrefabName.ToLower().Contains("shark"))
						return CombatEntityType.Shark;
					else return CombatEntityType.Animal;
					
				case SimpleShark:
					return CombatEntityType.Shark;

				case NPCShopKeeper:
					return CombatEntityType.NPCShopKeeper;

				case ExcavatorArm:
					return CombatEntityType.ExcavatorArm;

				case LargeShredder:
					return CombatEntityType.CarShredder;

				case Drone:
					return CombatEntityType.Drone;

				case ResourceEntity:
				{
					if (entity.ShortPrefabName.EndsWith("cactus"))
						return CombatEntityType.Cactus;
					else return CombatEntityType.Other;
				}

				case TrainEngine:
				case TrainCar:
					return CombatEntityType.Train;

				case MagnetCrane:
					return CombatEntityType.MagnetCrane;

				case ElevatorLift:
					return CombatEntityType.Elevator;
				
				case HotAirBalloon:
					return CombatEntityType.HotAirBalloon;

				case BatteringRam:
					return CombatEntityType.BatteringRam;

                case Tugboat:
                    return CombatEntityType.Tugboat;

                case CargoShip:
                    return CombatEntityType.CargoShip;

                case DigitSendCodeLock:
                    return CombatEntityType.Lock;

				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:
				case CombatEntityType.NPCShopKeeper:
					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. There's no apparent way to tie
				//the two together.
				case CombatEntityType.ScrapTransportHelicopter:
					return "Scrap Transport Helicopter";
				
				case CombatEntityType.AttackHelicopter:
					return "AttackHelicopter";
				
				case CombatEntityType.BeeSwarm:
					return "BeeSwarm";
				
				case CombatEntityType.Shark:
					return "Shark";

				case CombatEntityType.ExcavatorArm:
					return "Giant Excavator Arm";

				case CombatEntityType.CarShredder:
					return "Car Shredder";

				case CombatEntityType.Drone:
					return "Drone";

				case CombatEntityType.Cactus:
					return "Cactus";

				case CombatEntityType.Train:
					return "Train";

				case CombatEntityType.MagnetCrane:
					return "Magnet Crane";

				case CombatEntityType.Elevator:
					return "Elevator";

				case CombatEntityType.HotAirBalloon:
					return "Hot Air Balloon";

				case CombatEntityType.BatteringRam:
					return "Battering Ram";

                case CombatEntityType.Tugboat:
                    return "Tugboat";

                case CombatEntityType.CargoShip:
                    return "CargoShip";

                case CombatEntityType.CustomNPC:
                    if (entity != null && entity is BasePlayer)
                        return entity.ToPlayer().displayName;
                    else return "CustomNPC";
			}

			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,
			SAM = 26,
			Shark = 27,
			NPCShopKeeper = 28,
			ExcavatorArm = 29,
			Drone = 30,
			CarShredder = 31,
			Cactus = 32,
			Train = 33,
			MagnetCrane = 34,
			Elevator = 35,
			HotAirBalloon = 36,
			BatteringRam = 37,
            Tugboat = 38,
            CargoShip = 39,
            CustomNPC = 40
		}

		#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 (data.KillerEntity.ShortPrefabName.StartsWith("flameturret"))
					data.KillerEntityType = CombatEntityType.Turret;
			}

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

			//If the killer is a SAM Site
			if (data.KillerEntity == null && data.HitInfo != null && data.HitInfo.Initiator != null &&
			    data.HitInfo.Initiator.ShortPrefabName.StartsWith("sam_site_turret_deployed"))
			{
				data.KillerEntity = data.VictimEntity.lastAttacker ?? data.HitInfo.Initiator;
				// data.KillerEntity = data.HitInfo.Initiator;
				data.KillerEntityType = CombatEntityType.SAM;
			}

			// 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 self inflicted 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.KillerEntityType == CombatEntityType.Train &&
				 data.DamageType == DamageType.Generic &&
				 data.VictimEntityType == CombatEntityType.Player))
			{
				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;

			string prefab = null;
            try {
                prefab = deathData.HitInfo.Initiator?.GetComponent<Flame>()?.SourceEntity?.ShortPrefabName ??
						 deathData.HitInfo.WeaponPrefab?.ShortPrefabName;
            }
            catch (Exception e)
            {
                LogOutput($"Exception in GetWeaponName: {e.ToString()}");
                prefab = null;
            }

			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.
			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

        public bool ShowPatrolHeliTags()
        {
            return (_configuration.ShowPatrolHeliTagsInConsole || _configuration.ShowPatrolHeliTagsInChat || _configuration.ShowPatrolHeliTagsInNotify || _configuration.ShowPatrolHeliTagsInUINotify);
        }


        public bool ShowBradleyTags()
        {
            return (_configuration.ShowBradleyTagsInConsole || _configuration.ShowBradleyTagsInChat || _configuration.ShowBradleyTagsInNotify || _configuration.ShowBradleyTagsInUINotify);
        }


		[ChatCommand("dn")]
        private void CmdDeathNotes(BasePlayer player, string command, string[] args)
        {
            if (_configuration.CanPlayerUseDNCommand == false)
                return;

            if (args.Length <= 0)
            {
                bool isHidden = permission.UserHasPermission(player.UserIDString, CantSeePermission);
                if (isHidden)
                {
                    permission.RevokeUserPermission(player.UserIDString, CantSeePermission);
                    Player.Message(player, $"<color=#80D000>Death Notes</color> have been <color=#00FF00>ENABLED</color>");
                }
                else
                {
                    permission.GrantUserPermission(player.UserIDString, CantSeePermission, this);
                    Player.Message(player, $"<color=#80D000>Death Notes</color> have been <color=#FF0000>DISABLED</color>");
                }
            }
            else
            {
                
                string arg = args[0].ToLower();
                if (args[0] == "teamonly")
                {
                    bool isTeamOnly = permission.UserHasPermission(player.UserIDString, TeamOnlyPermission);
                    if (isTeamOnly)
                    {
                        permission.RevokeUserPermission(player.UserIDString, TeamOnlyPermission);
                        Player.Message(player, $"<color=#80D000>Death Notes Team Only</color> view has been <color=#FF0000>DISABLED</color>");
                    }
                    else {
                        permission.GrantUserPermission(player.UserIDString, TeamOnlyPermission, this);
                        Player.Message(player, $"<color=#80D000>Death Notes Team Only</color> view has been <color=#00FF00>ENABLED</color>");
                    }
                }                
            }
        }

		//Useful for debugging
		private static void LogDebug(string text)
		{
			if (_instance._configuration.DebugMode)
			{
				if (BasePlayer.activePlayerList.Count >= 1)
				{
					BasePlayer.activePlayerList[0].ConsoleMessage($"<color=orange>{text}</color>");
				}
			}
		}


        //Checks to see if the player has the specified Death Message permissions 
        private bool PlayersPassesPermsCheck(string playerIDString, HashSet<string> displayPerms)
        {
            if (displayPerms == null || displayPerms.Count <= 0)
                return true;

            string tmpPerm;
            foreach (string perm in displayPerms)
            {
                tmpPerm = perm.Trim().ToLower();
                if (!tmpPerm.StartsWith("deathnotes.") || tmpPerm.Equals("deathnotes.cansee") || tmpPerm.Equals("deathnotes.cantsee") || tmpPerm.Equals("deathnotes.suppress") || tmpPerm.Equals("deathnotes.seeteamonly"))
                    continue;
                
                if (permission.UserHasPermission(playerIDString, tmpPerm))
                    return true;
            }
            return false;
        }


        //Indicates if either the attacker or the victim in the DeathData parameter are on the same team as the supplied player
        private bool ArePlayersOnSameTeam(BasePlayer player, DeathData data)
        {
            if (player == null)
                return false;

            if ((data.KillerEntity is BasePlayer killer && killer != null) && (killer.userID == player.userID || (killer.currentTeam != 0 && player.currentTeam == killer.currentTeam)))
                return true;

            if ((data.VictimEntity is BasePlayer victim && victim != null) && ((victim.userID == player.userID) || (victim.currentTeam != 0 && player.currentTeam == victim.currentTeam)))
                return true;

            return false;
        }

		//Primarily for Rustcord interoperability, this indicates whether or not the death was PVP caused, or not.
		private bool IsDeathPVP(int inVictimType, int inKillerType)
        {
			CombatEntityType victimType = (CombatEntityType)inVictimType;
			CombatEntityType killerType = (CombatEntityType)inKillerType;
			return ((victimType == CombatEntityType.Player && (killerType == CombatEntityType.Player || 
										 killerType == CombatEntityType.Trap || killerType == CombatEntityType.Turret ||
										 killerType == CombatEntityType.Barricade ||
										 killerType == CombatEntityType.ExternalWall ||
										 killerType == CombatEntityType.HeatSource ||
 									     killerType == CombatEntityType.Cactus ||
 									     killerType == CombatEntityType.Fire || killerType == CombatEntityType.Lock ||
                                         killerType == CombatEntityType.Other || killerType == CombatEntityType.None ||
										 killerType =
// ... [Code preview truncated due to length. Full source in attached file] ...

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