From ff84f140df78100bff3367e5415ef90b966f042f Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Thu, 2 Jul 2026 20:44:22 -0700 Subject: [PATCH] fix(throwing): use RawDex for close-quarters mitigation (match ServUO/OSI) Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Items/Weapons/Throwing/ThrowingTests.cs | 30 +++++++++++++++++++ .../Items/Weapons/Throwing/BaseThrown.cs | 6 ++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Projects/UOContent.Tests/Tests/Items/Weapons/Throwing/ThrowingTests.cs b/Projects/UOContent.Tests/Tests/Items/Weapons/Throwing/ThrowingTests.cs index 1258d6857..da896ba8b 100644 --- a/Projects/UOContent.Tests/Tests/Items/Weapons/Throwing/ThrowingTests.cs +++ b/Projects/UOContent.Tests/Tests/Items/Weapons/Throwing/ThrowingTests.cs @@ -350,6 +350,36 @@ public class ThrowingTests } } + /// Close-quarters mitigation uses RawDex, so a Dex debuff does not change the penalty. + [Fact] + public void ModifyHitChance_CloseQuarters_UsesRawDex_IgnoresStatMods() + { + var map = Map.Felucca; + var attacker = CreateMobile(map, new Point3D(5940, 500, 0)); + var defender = CreateMobile(map, new Point3D(5941, 500, 0)); // distance 1 + var weapon = new TestThrown(); + try + { + attacker.Race = Race.Elf; // avoid Human JOAT skill floor + attacker.Skills.Throwing.BaseFixedPoint = 0; + attacker.RawDex = 40; + // RawDex-based mitigation = (0 + 40)/20 = 2.0 -> penalty = (12 - 2)/100 = 0.10 + var baseline = weapon.TestModifyHitChance(attacker, defender, 0.8); // 0.70 + + attacker.AddStatMod(new StatMod(StatType.Dex, "curse", -30, TimeSpan.Zero)); // effective Dex 10 + var withMod = weapon.TestModifyHitChance(attacker, defender, 0.8); + + Assert.Equal(0.70, baseline, 10); + Assert.Equal(baseline, withMod, 10); // unchanged because RawDex is used + } + finally + { + attacker.Delete(); + defender.Delete(); + weapon.Delete(); + } + } + private static PlayerMobile CreateMobile(Map map, Point3D location) { var mobile = new PlayerMobile(World.NewMobile); diff --git a/Projects/UOContent/Items/Weapons/Throwing/BaseThrown.cs b/Projects/UOContent/Items/Weapons/Throwing/BaseThrown.cs index 2309503c3..da2ea4f38 100644 --- a/Projects/UOContent/Items/Weapons/Throwing/BaseThrown.cs +++ b/Projects/UOContent/Items/Weapons/Throwing/BaseThrown.cs @@ -61,10 +61,10 @@ public abstract partial class BaseThrown : BaseRanged if (distance <= 1) { - // Close-quarters penalty: up to -12%, mitigated by (Throwing + Dex) / 20. - // At 240 combined (120 skill + 120 dex) the penalty is fully mitigated. + // Close-quarters penalty: up to -12%, mitigated by (Throwing + RawDex) / 20. + // At 240 combined (120 skill + 120 RawDex) the penalty is fully mitigated. var throwSkill = attacker.Skills[SkillName.Throwing].Value; - var mitigation = Math.Min(12.0, (throwSkill + attacker.Dex) / 20.0); + var mitigation = Math.Min(12.0, (throwSkill + attacker.RawDex) / 20.0); chance -= (12.0 - mitigation) / 100.0; } else if (distance < MinThrowRange)