fix(throwing): clamp DefMaxRange to throw band and guard divisor

Implement Math.Clamp to ensure DefMaxRange stays within [MinThrowRange,
MaxThrowRange], and guard against division by zero if the divisor
becomes zero or negative. Adds four tests: AtStrReq, At140Str,
AboveMaxStr (capping), BelowStrReq (flooring).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kamron Batman 2026-07-02 20:31:34 -07:00
parent fee005469b
commit 942c7935b2
2 changed files with 87 additions and 5 deletions

View file

@ -350,6 +350,80 @@ public class ThrowingTests
}
}
// DefMaxRange STR scaling (uses SoulGlaive: StrReq 60, Min 8, Max 11)
[Fact]
public void DefMaxRange_AtStrReq_EqualsMinThrowRange()
{
var weapon = new SoulGlaive();
var attacker = CreateMobile(Map.Felucca, new Point3D(5800, 500, 0));
try
{
attacker.RawStr = 60; // == AosStrengthReq
attacker.AddItem(weapon);
Assert.Equal(weapon.MinThrowRange, weapon.DefMaxRange); // 8
}
finally
{
weapon.Delete();
attacker.Delete();
}
}
[Fact]
public void DefMaxRange_At140Str_EqualsMaxThrowRange()
{
var weapon = new SoulGlaive();
var attacker = CreateMobile(Map.Felucca, new Point3D(5810, 500, 0));
try
{
attacker.RawStr = 140;
attacker.AddItem(weapon);
Assert.Equal(weapon.MaxThrowRange, weapon.DefMaxRange); // 11
}
finally
{
weapon.Delete();
attacker.Delete();
}
}
[Fact]
public void DefMaxRange_AboveMaxStr_IsCappedAtMaxThrowRange()
{
var weapon = new SoulGlaive();
var attacker = CreateMobile(Map.Felucca, new Point3D(5820, 500, 0));
try
{
attacker.RawStr = 200; // uncapped formula would give 13
attacker.AddItem(weapon);
Assert.Equal(weapon.MaxThrowRange, weapon.DefMaxRange); // 11, not 13
}
finally
{
weapon.Delete();
attacker.Delete();
}
}
[Fact]
public void DefMaxRange_BelowStrReq_FlooredAtMinThrowRange()
{
var weapon = new SoulGlaive();
var attacker = CreateMobile(Map.Felucca, new Point3D(5830, 500, 0));
try
{
attacker.RawStr = 10; // below StrReq 60
attacker.AddItem(weapon);
Assert.Equal(weapon.MinThrowRange, weapon.DefMaxRange); // 8, not 6
}
finally
{
weapon.Delete();
attacker.Delete();
}
}
private static PlayerMobile CreateMobile(Map map, Point3D location)
{
var mobile = new PlayerMobile(World.NewMobile);

View file

@ -14,17 +14,25 @@ public abstract partial class BaseThrown : BaseRanged
public virtual int MaxThrowRange => MinThrowRange + 3;
// Dynamic max range scaled by attacker Strength.
// Dynamic max range scaled by attacker Strength, clamped to the weapon's throw band.
// At StrReq the effective range equals MinThrowRange; at 140 Str it reaches MaxThrowRange.
public override int DefMaxRange
{
get
{
var baseRange = MaxThrowRange;
if (Parent is not Mobile attacker)
{
return MaxThrowRange;
}
return Parent is Mobile attacker
? baseRange - 3 + (attacker.Str - AosStrengthReq) / ((140 - AosStrengthReq) / 3)
: baseRange;
var divisor = (140 - AosStrengthReq) / 3;
if (divisor <= 0)
{
return MaxThrowRange;
}
var scaled = MaxThrowRange - 3 + (attacker.Str - AosStrengthReq) / divisor;
return Math.Clamp(scaled, MinThrowRange, MaxThrowRange);
}
}