I noticed that there was no posted solution to the missing Patrol Helicopter death notes issue, so I decided to research it. You will all be happy to know that I have devised a solution. I'll recap the issues at the bottom of this thread, but first, let me share the necessary code fixes to make it work. Please note that I also included a new feature that I wanted, which is to announce who tags the Patrol Helicopter. You can omit that code if you wish. I also did the same for the Bradley APC, but did not include here since it was outside the scope of the issue.
First, add the 3 lines below toward the top of your Death Notes plugin. I added the two surrounding lines for context (don't copy them):
private readonly Dictionary<ulong, AttackInfo> _previousAttack = new Dictionary<ulong, AttackInfo>();
//These were added by Terceran
private readonly HitInfo patrolHeliHitInfo = new HitInfo();
private uint patrolHeliTaggedEpoch = 0;
private readonly Func<PluginConfiguration.DeathMessage, DeathData, bool>[] _messageMatchingStages =
Next, add the 3 lines of code below to your GetCombatEntityType method.
private CombatEntityType GetCombatEntityType(BaseEntity entity)
{
if (entity == null)
return CombatEntityType.None;
if (_combatEntityTypes.Contents != null)
{
if (_combatEntityTypes.Contents.ContainsKey(entity.GetType().Name))
return _combatEntityTypes.Contents[entity.GetType().Name];
if (_combatEntityTypes.Contents.ContainsKey(entity.ShortPrefabName))
return _combatEntityTypes.Contents[entity.ShortPrefabName];
}
//Added by Terceran
if (entity.GetType().Name.Equals("PatrolHelicopter"))
return CombatEntityType.Helicopter;
if (entity is BaseOven)
return CombatEntityType.HeatSource;
Add this entire new method to your code (I placed mine immediately before OnEntityDeath):
//This method tracks Patrol Helicopter (Karen) damage and reports whoever tagged it.
//Added by Terceran
private void OnPatrolHelicopterTakeDamage(PatrolHelicopter victimEntity, HitInfo hitInfo) {
if (victimEntity == null || hitInfo == null) return;
if (hitInfo.WeaponPrefab != null && patrolHeliHitInfo.WeaponPrefab != hitInfo.WeaponPrefab)
patrolHeliHitInfo.WeaponPrefab = hitInfo.WeaponPrefab;
if (hitInfo.Weapon != null && patrolHeliHitInfo.Weapon != hitInfo.Weapon)
patrolHeliHitInfo.Weapon = hitInfo.Weapon;
if (patrolHeliHitInfo.Initiator == null && hitInfo.Initiator != null)
patrolHeliHitInfo.Initiator = hitInfo.Initiator;
TimeSpan timeSpan = DateTime.UtcNow - new DateTime(1970,1,1);
uint nowEpoch = (uint)timeSpan.TotalSeconds;
//The Patrol Helicopter (Karen) hasn't been tagged in a couple of minutes.
if (nowEpoch - patrolHeliTaggedEpoch >= 120)
patrolHeliTaggedEpoch = 0;
else
return;
if (hitInfo.Initiator != null)
{
patrolHeliTaggedEpoch = nowEpoch;
var data = new DeathData
{
VictimEntity = victimEntity,
KillerEntity = victimEntity.lastAttacker ?? hitInfo?.Initiator,
VictimEntityType = GetCombatEntityType(victimEntity),
KillerEntityType = GetCombatEntityType(victimEntity.lastAttacker),
DamageType = victimEntity.lastDamage,
HitInfo = patrolHeliHitInfo
};
//Comment out the below if statement if you do not want to report Patrol Helicopter tagging.
if (hitInfo.Initiator?.ToPlayer().displayName != null)
{
patrolHeliHitInfo.Initiator = hitInfo.Initiator;
string weaponName = GetCustomizedWeaponName(data);
string message = "<color=#C4FF00>" + hitInfo.Initiator?.ToPlayer().displayName +
"</color> has tagged the <color=#C4FF00>Patrol Helicopter (Karen)</color> with their <color=#C4FF00>" +
weaponName + "</color>. She's going to complain to the manager!";
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));
}
}
}
Next, add the two code blocks below to your OnEntityDeath method:
private void OnEntityDeath(BaseCombatEntity victimEntity, HitInfo hitInfo)
{
// Ignore - there is no victim for some reason
if (victimEntity == null)
return;
// Try to avoid error when entity was destroyed
if (victimEntity.gameObject == null)
return;
var data = new DeathData
{
VictimEntity = victimEntity,
KillerEntity = victimEntity.lastAttacker ?? hitInfo?.Initiator,
VictimEntityType = GetCombatEntityType(victimEntity),
KillerEntityType = GetCombatEntityType(victimEntity.lastAttacker),
DamageType = victimEntity.lastDamage,
HitInfo = hitInfo
};
//Pass along our previously collected damage information.
//Added by Terceran
if (victimEntity.GetType().Name == "PatrolHelicopter")
data.HitInfo = patrolHeliHitInfo;
// Handle inconsistencies/exceptions
HandleInconsistencies(ref data);
#if DEBUG
LogDebug("[DEATHNOTES DEBUG]");
LogDebug(
$"VictimEntity: {data.VictimEntity?.GetType().Name ?? "NULL"} / {data.VictimEntity?.ShortPrefabName ?? "NULL"} / {data.VictimEntity?.PrefabName ?? "NULL"}");
LogDebug(
$"KillerEntity: {data.KillerEntity?.GetType().Name ?? "NULL"} / {data.KillerEntity?.ShortPrefabName ?? "NULL"} / {data.KillerEntity?.PrefabName ?? "NULL"}");
LogDebug($"VictimEntityType: {data.VictimEntityType}");
LogDebug($"KillerEntityType: {data.KillerEntityType}");
LogDebug($"DamageType: {data.DamageType}");
LogDebug($"Bodypart: {GetCustomizedBodypartName(data.HitInfo)}");
LogDebug($"Weapon: {hitInfo?.WeaponPrefab?.ShortPrefabName ?? "NULL"}");
#endif
// Change entity type for dwellers
RepairEntityTypes(ref data);
// Ignore deaths of other entities
if (data.KillerEntityType == CombatEntityType.Other || data.VictimEntityType == CombatEntityType.Other)
return;
// Ignore deaths which don't involve players or the helicopter which usually does not track a player as killer
if (data.VictimEntityType != CombatEntityType.Player && data.KillerEntityType != CombatEntityType.Player &&
data.VictimEntityType != CombatEntityType.Helicopter)
return;
// Populate the variables in the message
string message = PopulateMessageVariables(
// Find the best matching death message for this death
GetDeathMessage(data),
data
);
if (message == null)
return;
object hookResult = Interface.Call("OnDeathNotice", data.ToDictionary(), message);
if (hookResult?.Equals(false) ?? false)
return;
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));
//The Patrol Helicopter has been destroyed, time to clean up after ourselves.
//Added by Terceran
if (victimEntity.GetType().Name == "PatrolHelicopter")
{
patrolHeliHitInfo.Initiator = null;
patrolHeliHitInfo.WeaponPrefab = null;
patrolHeliHitInfo.Weapon = null;
patrolHeliTaggedEpoch = 0;
}
}
That's it! To recap the issue, the Patrol Helicopter was erroneously omitted from the GetCombatEntityType method, so the Patrol Helicopter was never being classified correctly. Next, even once that was corrected, there is yet another issue where the information regarding the damage done to the Patrol Helicopter was missing. This included who was attacking it, with what weapon, etc. So, we needed to track that information as damage was being inflicted to the Patrol Helicopter and supply it to Death Notes once the Patrol Helicopter was destroyed. I certainly hope this analysis and fix is useful to everyone.
P.S. - I apologize for the poor code formatting. I'm apparently better at fixing bugs than I am at posting code.
Merged postI made a few small corrections to the OnPatrolHelicopterTakeDamage method, please use this version instead of the one I previously posted:
//This method tracks Patrol Helicopter (Karen) damage and reports whoever tagged it.
//Added by Terceran
private void OnPatrolHelicopterTakeDamage(PatrolHelicopter victimEntity, HitInfo hitInfo) {
if (victimEntity == null || hitInfo == null) return;
if (hitInfo.WeaponPrefab != null && patrolHeliHitInfo.WeaponPrefab != hitInfo.WeaponPrefab)
patrolHeliHitInfo.WeaponPrefab = hitInfo.WeaponPrefab;
if (hitInfo.Weapon != null && patrolHeliHitInfo.Weapon != hitInfo.Weapon)
patrolHeliHitInfo.Weapon = hitInfo.Weapon;
if (patrolHeliHitInfo.Initiator == null && hitInfo.Initiator != null)
patrolHeliHitInfo.Initiator = hitInfo.Initiator;
TimeSpan timeSpan = DateTime.UtcNow - new DateTime(1970,1,1);
uint nowEpoch = (uint)timeSpan.TotalSeconds;
//The Patrol Helicopter (Karen) has been hit within a couple of minutes.
if (nowEpoch - patrolHeliTaggedEpoch < 120)
{
patrolHeliTaggedEpoch = nowEpoch;
return;
}
patrolHeliTaggedEpoch = nowEpoch;
if (hitInfo.Initiator != null)
{
var data = new DeathData
{
VictimEntity = victimEntity,
KillerEntity = victimEntity.lastAttacker ?? hitInfo?.Initiator,
VictimEntityType = GetCombatEntityType(victimEntity),
KillerEntityType = GetCombatEntityType(victimEntity.lastAttacker),
DamageType = victimEntity.lastDamage,
HitInfo = patrolHeliHitInfo
};
//Comment out the below if statement if you do not want to report Patrol Helicopter tagging.
if (hitInfo.Initiator?.ToPlayer().displayName != null)
{
patrolHeliHitInfo.Initiator = hitInfo.Initiator;
string weaponName = GetCustomizedWeaponName(data);
string message = "<color=#C4FF00>" + hitInfo.Initiator?.ToPlayer().displayName +
"</color> has tagged the <color=#C4FF00>Patrol Helicopter (Karen)</color> with their <color=#C4FF00>" +
weaponName + "</color>. She's going to complain to the manager!";
object hookResult = Interface.Call("OnDeathNotice", data.ToDictionary(), message);
if (hookResult?.Equals(false) ?? false)
return;
if (_configuration.ShowInChat)
{
foreach (var player in BasePlayer.activePlayerList)
{
if (_configuration.RequirePermission &&
!permission.UserHasPermission(player.UserIDString, CanSeePermission))
continue;
if (_configuration.MessageRadius != -1 &&
player.Distance(data.VictimEntity) > _configuration.MessageRadius)
continue;
Player.Reply(
player,
_configuration.ChatFormat.Replace("{message}", message),
ulong.Parse(_configuration.ChatIcon)
);
}
}
if (_configuration.ShowInConsole)
Puts(StripRichText(message));
}
}
}