diff --git a/Projects/UOContent.Tests/Tests/Items/Weapons/Throwing/ThrowingTests.cs b/Projects/UOContent.Tests/Tests/Items/Weapons/Throwing/ThrowingTests.cs index efb080749..1258d6857 100644 --- a/Projects/UOContent.Tests/Tests/Items/Weapons/Throwing/ThrowingTests.cs +++ b/Projects/UOContent.Tests/Tests/Items/Weapons/Throwing/ThrowingTests.cs @@ -19,6 +19,9 @@ public class ThrowingTests public double TestModifyHitChance(Mobile attacker, Mobile defender, double chance) => ModifyHitChance(attacker, defender, chance); + + public int TestModifyDamage(Mobile attacker, Mobile defender, int damage) => + ModifyDamage(attacker, defender, damage); } // Hit chance modifiers @@ -291,6 +294,62 @@ public class ThrowingTests } } + /// At the outermost admissible tile (dist == MaxRange) a throw loses 47% damage. + [Fact] + public void ModifyDamage_AtMaxRange_Reduces47Percent() + { + var map = Map.Felucca; + var attacker = CreateMobile(map, new Point3D(5900, 500, 0)); + var weapon = new TestThrown(); // MinThrowRange 4 -> MaxThrowRange 7 + try + { + attacker.RawStr = 140; // MaxRange == MaxThrowRange == 7 + attacker.AddItem(weapon); + var defender = CreateMobile(map, new Point3D(5907, 500, 0)); // distance 7 == MaxRange + try + { + Assert.Equal(53, weapon.TestModifyDamage(attacker, defender, 100)); + } + finally + { + defender.Delete(); + } + } + finally + { + weapon.Delete(); + attacker.Delete(); + } + } + + /// Inside max range, damage is unchanged. + [Fact] + public void ModifyDamage_WithinRange_NoChange() + { + var map = Map.Felucca; + var attacker = CreateMobile(map, new Point3D(5920, 500, 0)); + var weapon = new TestThrown(); + try + { + attacker.RawStr = 140; // MaxRange 7 + attacker.AddItem(weapon); + var defender = CreateMobile(map, new Point3D(5925, 500, 0)); // distance 5 < 7 + try + { + Assert.Equal(100, weapon.TestModifyDamage(attacker, defender, 100)); + } + finally + { + defender.Delete(); + } + } + finally + { + weapon.Delete(); + attacker.Delete(); + } + } + private static PlayerMobile CreateMobile(Map map, Point3D location) { var mobile = new PlayerMobile(World.NewMobile); diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.cs index 3611d55cb..366a47f9f 100644 --- a/Projects/UOContent/Items/Weapons/BaseWeapon.cs +++ b/Projects/UOContent/Items/Weapons/BaseWeapon.cs @@ -1374,6 +1374,12 @@ public abstract partial class BaseWeapon /// protected virtual double ModifyHitChance(Mobile attacker, Mobile defender, double chance) => chance; + /// + /// Allows subclasses to modify the final post-bonus damage (e.g. range-based penalties) + /// before defender mitigation is applied. Mirrors . + /// + protected virtual int ModifyDamage(Mobile attacker, Mobile defender, int damage) => damage; + public virtual TimeSpan GetDelay(Mobile m) { double speed = Speed; @@ -1883,6 +1889,7 @@ public abstract partial class BaseWeapon percentageBonus = Math.Min(percentageBonus, 300); damage = AOS.Scale(damage, 100 + percentageBonus); + damage = ModifyDamage(attacker, defender, damage); var defLoc = new WorldLocation(defender); var bcAtt = attacker as BaseCreature; diff --git a/Projects/UOContent/Items/Weapons/Throwing/BaseThrown.cs b/Projects/UOContent/Items/Weapons/Throwing/BaseThrown.cs index 2b765a740..2309503c3 100644 --- a/Projects/UOContent/Items/Weapons/Throwing/BaseThrown.cs +++ b/Projects/UOContent/Items/Weapons/Throwing/BaseThrown.cs @@ -86,15 +86,13 @@ public abstract partial class BaseThrown : BaseRanged return chance; } - // Overthrow penalty: -47% damage when target is beyond the attacker's current max range. - // MaxRange returns DefMaxRange (STR-scaled), so this reflects the dynamic per-attack value. - public override int ComputeDamage(Mobile attacker, Mobile defender) + // Overthrow: a throw that reaches the edge of its (STR-scaled) range lands with 47% less + // damage, applied on top of all offensive bonuses. MaxRange is the dynamic DefMaxRange. + protected override int ModifyDamage(Mobile attacker, Mobile defender, int damage) { - var damage = base.ComputeDamage(attacker, defender); - - if (!attacker.InRange(defender.Location, MaxRange)) + if (!attacker.InRange(defender.Location, MaxRange - 1)) { - damage = (int)(damage * 0.53); + damage = damage * 53 / 100; } return damage;