feat: Adds Mobile.Murderer virtual property, consolidates kill-threshold checks (#2355)

## Summary

- Adds `public virtual bool Murderer => Kills >= 5` property to `Mobile`, replacing ~30 scattered `Kills >= 5` / `Kills < 5` magic-number checks across the codebase
- `BaseCreature` overrides `Murderer` to also return `true` when `AlwaysMurderer` is set
- Updates `Corpse` serialization to v15, storing `_murderer` as a `bool` field (migrated from `int Kills >= 5` in earlier versions)
This commit is contained in:
Jack 2026-03-07 05:36:41 +13:00 • committed by GitHub
parent 192f092ee7
commit 6745cf2075
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 200 additions and 43 deletions

View file

@ -64,7 +64,7 @@ public enum CorpseFlag
SelfLooted = 0x00000080
}
[SerializationGenerator(14, false)]
[SerializationGenerator(15, false)]
public partial class Corpse : Container, ICarvable
{
public static readonly TimeSpan MonsterLootRightSacrifice = TimeSpan.FromMinutes(2.0);
@ -126,7 +126,7 @@ public partial class Corpse : Container, ICarvable
[SerializableField(12)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private int _kills;
private bool _murderer;
[SerializableField(13, setter: "private")]
[SerializedCommandProperty(AccessLevel.GameMaster)]
@ -170,7 +170,7 @@ public partial class Corpse : Container, ICarvable
_accessLevel = owner.AccessLevel;
_guild = owner.Guild as Guild;
_kills = owner.Kills;
_murderer = owner.Murderer;
SetFlag(CorpseFlag.Criminal, owner.Criminal);
if (hair?.ItemId > 0)
@ -254,6 +254,28 @@ public partial class Corpse : Container, ICarvable
DevourCorpse();
}
// Replaced int Kills snapshot with bool Murderer snapshot
private void MigrateFrom(V14Content content)
{
_restoreEquip = content.RestoreEquip;
_flags = content.Flags;
_timeOfDeath = content.TimeOfDeath;
_restoreTable = content.RestoreTable;
_decayTimer = new InternalTimer(this, content.DecayTimerDelay);
_decayTimer.Start();
_looters = content.Looters;
_killer = content.Killer;
_aggressors = content.Aggressors;
_owner = content.Owner;
_corpseName = content.CorpseName;
_accessLevel = content.AccessLevel;
_guild = content.Guild;
_murderer = content.Kills >= 5;
_equipItems = content.EquipItems;
_hair = content.Hair;
_facialHair = content.FacialHair;
}
// Added corpse hair and corpse facial hair
private void MigrateFrom(V13Content content)
{
@ -270,7 +292,7 @@ public partial class Corpse : Container, ICarvable
_corpseName = content.CorpseName;
_accessLevel = content.AccessLevel;
_guild = content.Guild;
_kills = content.Kills;
_murderer = content.Kills >= 5;
_equipItems = content.EquipItems;
}
@ -631,7 +653,7 @@ public partial class Corpse : Container, ICarvable
_accessLevel = (AccessLevel)reader.ReadInt();
reader.ReadInt(); // guild reserve
_kills = reader.ReadInt();
_murderer = reader.ReadInt() >= 5;
_equipItems = reader.ReadEntityList<Item>();
}

View file

@ -64,7 +64,7 @@ public partial class Moonstone : Item
{
mobile.SendLocalizedMessage(1049543); // You decide against traveling to Felucca while you are still young.
}
else if (from.Kills >= 5)
else if (from.Murderer)
{
// The magic of the stone cannot be evoked by someone with blood on their hands.
from.SendLocalizedMessage(1005402);

View file

@ -25,7 +25,7 @@ public partial class MoonstoneGate : Moongate
public override void CheckGate(Mobile m, int range)
{
if (m.Kills >= 5)
if (m.Murderer)
{
return;
}
@ -41,7 +41,7 @@ public partial class MoonstoneGate : Moongate
public override void UseGate(Mobile m)
{
if (m.Kills >= 5)
if (m.Murderer)
{
return;
}

View file

@ -339,7 +339,7 @@ public class MoongateGump : Gump
{
checkLists = PMList.SigilLists;
}
else if (mobile.Kills >= 5)
else if (mobile.Murderer)
{
checkLists = PMList.RedLists;
}
@ -474,7 +474,7 @@ public class MoongateGump : Gump
{
_mobile.SendLocalizedMessage(1019002); // You are too far away to use the gate.
}
else if (_mobile.Player && _mobile.Kills >= 5 && list.Map != Map.Felucca)
else if (_mobile.Player && _mobile.Murderer && list.Map != Map.Felucca)
{
_mobile.SendLocalizedMessage(1019004); // You are not allowed to travel there.
}

View file

@ -96,7 +96,7 @@ public partial class Moongate : Item
{
mobile.SendLocalizedMessage(1049543); // You decide against traveling to Felucca while you are still young.
}
else if (m.Kills >= 5 && TargetMap != Map.Felucca ||
else if (m.Murderer && TargetMap != Map.Felucca ||
TargetMap == Map.Tokuno && (flags & ClientFlags.Tokuno) == 0 ||
TargetMap == Map.Malas && (flags & ClientFlags.Malas) == 0 ||
TargetMap == Map.Ilshenar && (flags & ClientFlags.Ilshenar) == 0 ||

View file

@ -226,7 +226,7 @@ public partial class BraceletOfBinding : BaseBracelet, TranslocationItem
return false;
}
if (from.Kills >= 5 && boundRoot.Map != Map.Felucca)
if (from.Murderer && boundRoot.Map != Map.Felucca)
{
from.SendLocalizedMessage(1019004); // You are not allowed to travel there.
return false;