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;