fix(throwing): apply overthrow -47% as post-bonus damage via ModifyDamage hook
Adds a ModifyDamage(attacker, defender, damage) virtual on BaseWeapon, mirroring the existing ModifyHitChance hook, invoked once in OnHit immediately after the percentage-bonus pool scale. BaseThrown overrides it to cut 47% of the final post-bonus damage at the outermost admissible tile (dist == MaxRange), replacing the old dead ComputeDamage override (the swing gate already guarantees the attacker is within MaxRange, so the prior >= comparison never fired). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9ea79dc634
commit
5c1196cdae
3 changed files with 71 additions and 7 deletions
|
|
@ -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
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>At the outermost admissible tile (dist == MaxRange) a throw loses 47% damage.</summary>
|
||||
[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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inside max range, damage is unchanged.</summary>
|
||||
[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);
|
||||
|
|
|
|||
|
|
@ -1374,6 +1374,12 @@ public abstract partial class BaseWeapon
|
|||
/// </summary>
|
||||
protected virtual double ModifyHitChance(Mobile attacker, Mobile defender, double chance) => chance;
|
||||
|
||||
/// <summary>
|
||||
/// Allows subclasses to modify the final post-bonus damage (e.g. range-based penalties)
|
||||
/// before defender mitigation is applied. Mirrors <see cref="ModifyHitChance" />.
|
||||
/// </summary>
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue