From d1580614e0d30aeb61b9e2e06c5184bd0fd32dda Mon Sep 17 00:00:00 2001 From: Crome696 Date: Wed, 8 Jul 2026 19:32:11 +0200 Subject: [PATCH] fix(items): harden Battle Lust cleanup --- .../Items/Weapons/BattleLustPropertyTests.cs | 149 ++++++++++++++++++ .../UOContent/Items/Weapons/BaseWeapon.cs | 15 ++ .../UOContent/Items/Weapons/BattleLust.cs | 8 +- Projects/UOContent/Misc/AOS.cs | 15 +- 4 files changed, 184 insertions(+), 3 deletions(-) diff --git a/Projects/UOContent.Tests/Tests/Items/Weapons/BattleLustPropertyTests.cs b/Projects/UOContent.Tests/Tests/Items/Weapons/BattleLustPropertyTests.cs index 1ed7e5596..bd49f3bcb 100644 --- a/Projects/UOContent.Tests/Tests/Items/Weapons/BattleLustPropertyTests.cs +++ b/Projects/UOContent.Tests/Tests/Items/Weapons/BattleLustPropertyTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Server; using Server.Items; +using Server.Mobiles; using Server.Text; using Xunit; @@ -99,6 +100,7 @@ public class BattleLustPropertyTests var wielder = CreateMobile(hitsMax: 5000, hits: 5000); var source = CreateMobile(location: new Point3D(6201, 500, 0)); var deletedSource = CreateMobile(location: new Point3D(6202, 500, 0)); + var deadSource = CreateDeadPlayerMobile(new Point3D(6203, 500, 0)); var weapon = EquipBattleLustWeapon(wielder); try @@ -122,6 +124,10 @@ public class BattleLustPropertyTests ApplyDamage(wielder, deletedSource, BattleLust.DamageThreshold); Assert.Equal(0, BattleLust.GetPoints(wielder)); + Core._now = Core._now.AddSeconds(2); + ApplyDamage(wielder, deadSource, BattleLust.DamageThreshold); + Assert.Equal(0, BattleLust.GetPoints(wielder)); + Core._now = Core._now.AddSeconds(2); ApplyDamage(wielder, source, BattleLust.DamageThreshold); Assert.Equal(1, BattleLust.GetPoints(wielder)); @@ -135,6 +141,38 @@ public class BattleLustPropertyTests wielder.Delete(); source.Delete(); deletedSource.Delete(); + deadSource.Delete(); + } + } + + [Fact] + public void DamageTaken_DoesNotGainWhenMobileDamageAppliesNoHitPointLoss() + { + var previousExpansion = Core.Expansion; + var previousNow = Core._now; + var wielder = CreateMobile(hitsMax: 5000, hits: 5000); + var source = CreateMobile(location: new Point3D(6201, 500, 0)); + var weapon = EquipBattleLustWeapon(wielder); + + try + { + Core.Expansion = Expansion.SA; + Core._now = TestNow; + wielder.Blessed = true; + + ApplyDamage(wielder, source, BattleLust.DamageThreshold); + + Assert.Equal(5000, wielder.Hits); + Assert.Equal(0, BattleLust.GetPoints(wielder)); + } + finally + { + Core.Expansion = previousExpansion; + Core._now = previousNow; + BattleLust.Clear(wielder); + weapon.Delete(); + wielder.Delete(); + source.Delete(); } } @@ -257,6 +295,105 @@ public class BattleLustPropertyTests } } + [Fact] + public void RuntimeContext_ClearsOnWeaponLossEvenIfReequippedBeforeLazyCleanup() + { + var previousExpansion = Core.Expansion; + var previousNow = Core._now; + var wielder = CreateMobile(); + var source = CreateMobile(location: new Point3D(6201, 500, 0)); + var weapon = EquipBattleLustWeapon(wielder); + + try + { + Core.Expansion = Expansion.SA; + Core._now = TestNow; + + ApplyDamage(wielder, source, BattleLust.DamageThreshold); + Assert.Equal(1, BattleLust.GetPoints(wielder)); + + wielder.RemoveItem(weapon); + wielder.AddItem(weapon); + + Assert.Equal(0, BattleLust.GetPoints(wielder)); + } + finally + { + Core.Expansion = previousExpansion; + Core._now = previousNow; + BattleLust.Clear(wielder); + weapon.Delete(); + wielder.Delete(); + source.Delete(); + } + } + + [Fact] + public void RuntimeContext_ClearsWhenBattleLustPropertyIsRemovedEvenIfRestoredBeforeLazyCleanup() + { + var previousExpansion = Core.Expansion; + var previousNow = Core._now; + var wielder = CreateMobile(); + var source = CreateMobile(location: new Point3D(6201, 500, 0)); + var weapon = EquipBattleLustWeapon(wielder); + + try + { + Core.Expansion = Expansion.SA; + Core._now = TestNow; + + ApplyDamage(wielder, source, BattleLust.DamageThreshold); + Assert.Equal(1, BattleLust.GetPoints(wielder)); + + weapon.WeaponAttributes.BattleLust = 0; + weapon.WeaponAttributes.BattleLust = 1; + + Assert.Equal(0, BattleLust.GetPoints(wielder)); + } + finally + { + Core.Expansion = previousExpansion; + Core._now = previousNow; + BattleLust.Clear(wielder); + weapon.Delete(); + wielder.Delete(); + source.Delete(); + } + } + + [Fact] + public void RuntimeContext_ClearsOnInvalidMapEvenIfOwnerReturnsBeforeDecayTick() + { + var previousExpansion = Core.Expansion; + var previousNow = Core._now; + var wielder = CreateMobile(); + var source = CreateMobile(location: new Point3D(6201, 500, 0)); + var weapon = EquipBattleLustWeapon(wielder); + + try + { + Core.Expansion = Expansion.SA; + Core._now = TestNow; + + ApplyDamage(wielder, source, BattleLust.DamageThreshold); + Assert.Equal(1, BattleLust.GetPoints(wielder)); + + wielder.MoveToWorld(Point3D.Zero, Map.Internal); + wielder.MoveToWorld(new Point3D(6200, 500, 0), Map.Felucca); + + Assert.Equal(0, BattleLust.GetPoints(wielder)); + } + finally + { + Core.Expansion = previousExpansion; + Core._now = previousNow; + BattleLust.Clear(wielder); + weapon.Delete(); + wielder.Delete(); + source.Delete(); + } + } + [Fact] public void GetDamageBonus_UsesAggressedCountAndPropertySpecificPvpPvmCaps() { @@ -400,6 +537,18 @@ public class BattleLustPropertyTests return mobile; } + private static PlayerMobile CreateDeadPlayerMobile(Point3D location) + { + var mobile = new PlayerMobile(World.NewMobile); + mobile.DefaultMobileInit(); + mobile.Player = true; + mobile.Hits = mobile.HitsMax; + mobile.MoveToWorld(location, Map.Felucca); + mobile.Body = mobile.Race.GhostBody(mobile); + Assert.False(mobile.Alive); + return mobile; + } + private static void InitializeHits(Mobile mobile, int hitsMax, int hits) { mobile.RawStr = Math.Max(1, (hitsMax - 50) * 2); diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.cs index 12722d997..8087b0a2a 100644 --- a/Projects/UOContent/Items/Weapons/BaseWeapon.cs +++ b/Projects/UOContent/Items/Weapons/BaseWeapon.cs @@ -1140,6 +1140,11 @@ public abstract partial class BaseWeapon return; } + if (WeaponAttributes.BattleLust != 0) + { + BattleLust.Clear(m); + } + var serial = Serial; m.RemoveStatMod($"{serial}Str"); @@ -1176,6 +1181,16 @@ public abstract partial class BaseWeapon m.Delta(MobileDelta.WeaponDamage); } + public override void OnMapChange() + { + base.OnMapChange(); + + if ((Map == null || Map == Map.Internal) && Parent is Mobile m && WeaponAttributes.BattleLust != 0) + { + BattleLust.Clear(m); + } + } + public virtual SkillName GetUsedSkill(Mobile m, bool checkSkillAttrs) { SkillName sk; diff --git a/Projects/UOContent/Items/Weapons/BattleLust.cs b/Projects/UOContent/Items/Weapons/BattleLust.cs index f6f34042c..9555851ec 100644 --- a/Projects/UOContent/Items/Weapons/BattleLust.cs +++ b/Projects/UOContent/Items/Weapons/BattleLust.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using ModernUO.CodeGeneratedEvents; +using Server.Mobiles; namespace Server.Items; @@ -86,7 +88,11 @@ public static class BattleLust return context.Points; } - internal static void Clear(Mobile mobile) + [OnEvent(nameof(PlayerMobile.PlayerDeathEvent))] + [OnEvent(nameof(PlayerMobile.PlayerDeletedEvent))] + [OnEvent(nameof(BaseCreature.CreatureDeathEvent))] + [OnEvent(nameof(BaseCreature.CreatureDeletedEvent))] + public static void Clear(Mobile mobile) { if (mobile != null && _contexts.Remove(mobile, out var context)) { diff --git a/Projects/UOContent/Misc/AOS.cs b/Projects/UOContent/Misc/AOS.cs index ceb027143..9a0e524af 100644 --- a/Projects/UOContent/Misc/AOS.cs +++ b/Projects/UOContent/Misc/AOS.cs @@ -236,8 +236,9 @@ namespace Server SpellHelper.DoLeech(totalDamage, from, m); } + var oldHits = m.Hits; m.Damage(totalDamage, from); - BattleLust.OnDamageTaken(m, from, totalDamage); + BattleLust.OnDamageTaken(m, from, Math.Max(0, oldHits - m.Hits)); return totalDamage; } @@ -987,7 +988,17 @@ namespace Server public int BattleLust { get => this[AosWeaponAttribute.BattleLust]; - set => this[AosWeaponAttribute.BattleLust] = value; + set + { + var hadBattleLust = BattleLust != 0; + + this[AosWeaponAttribute.BattleLust] = value; + + if (hadBattleLust && value == 0 && Owner is BaseWeapon { Parent: Mobile m }) + { + Server.Items.BattleLust.Clear(m); + } + } } public static int GetValue(Mobile m, AosWeaponAttribute attribute)