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:
parent
fee005469b
commit
942c7935b2
2 changed files with 87 additions and 5 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue