From 28fdd8bb98cf72d4d598de1e5566a72e21d19be0 Mon Sep 17 00:00:00 2001 From: Corey Ryan Dean Date: Wed, 10 Jun 2026 18:38:57 -0500 Subject: [PATCH] fix(combat): armour durability wear targeted the attacker instead of the defender The 'Damage armour' block in ActorAttack (GameServer.bb) was a copy-paste of the weapon-wear block directly above it, which correctly targets A1 (the attacker's weapon wears). The copy kept A1 for armour wear too, so each swing wore down the ATTACKER's equipped armour and echoed the P_ItemHealth durability update to the attacker -- even though the armour that absorbed the blow belongs to A2 (the defender; the damage formulas read GetArmourLevel(A2\Inventory)). Re-target the armour-wear block only: A2's Shield..Feet slots now wear, and the P_ItemHealth echo goes to A2\RNID under the same RNID > 0 NPC guard the block already used. Deliberate test flip: CombatDamageFormulaTest.bb's testArmourWearHitsAttackerNotDefender pinned the shipped bug (FLAG-FOR-HUMAN, PR #572). Renamed to testArmourWearHitsDefender and flipped its assertions to the corrected behavior -- the defender's chest piece loses durability over a 300-swing volley while the attacker's stays at exactly 100. The attacker's weapon-wear pin and every other pin are untouched. Verified: test.bat CombatDamageFormula PASS; full suite 58/58 green; full compile.bat (engine + tools) exit 0. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/Modules/GameServer.bb | 18 +++++++++------ src/Tests/Modules/CombatDamageFormulaTest.bb | 24 ++++++++++++-------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/Modules/GameServer.bb b/src/Modules/GameServer.bb index 96a64df4..ef26a239 100644 --- a/src/Modules/GameServer.bb +++ b/src/Modules/GameServer.bb @@ -548,16 +548,20 @@ Function ActorAttack(A1.ActorInstance, A2.ActorInstance) EndIf EndIf - ; Damage armour + ; Damage armour. The DEFENDER's (A2's) equipped armour absorbed the + ; blow (the damage formulas read GetArmourLevel(A2\Inventory)), so it + ; is A2's armour that wears -- this block was historically a + ; copy-paste of the weapon-wear block above, which left the wear + ; target as A1 (the attacker). If ArmourDamage = True For i = SlotI_Shield To SlotI_Feet - If A1\Inventory\Items[i] <> Null - If A1\Inventory\Items[i]\ItemHealth > 0 + If A2\Inventory\Items[i] <> Null + If A2\Inventory\Items[i]\ItemHealth > 0 If Rand(1, 5) = 1 - A1\Inventory\Items[i]\ItemHealth = A1\Inventory\Items[i]\ItemHealth - 1 - If A1\RNID > 0 - Pa$ = RCE_StrFromInt$(i, 1) + RCE_StrFromInt$(A1\Inventory\Items[i]\ItemHealth, 2) - RCE_Send(Host, A1\RNID, P_ItemHealth, Pa$, True) + A2\Inventory\Items[i]\ItemHealth = A2\Inventory\Items[i]\ItemHealth - 1 + If A2\RNID > 0 + Pa$ = RCE_StrFromInt$(i, 1) + RCE_StrFromInt$(A2\Inventory\Items[i]\ItemHealth, 2) + RCE_Send(Host, A2\RNID, P_ItemHealth, Pa$, True) EndIf EndIf EndIf diff --git a/src/Tests/Modules/CombatDamageFormulaTest.bb b/src/Tests/Modules/CombatDamageFormulaTest.bb index a191476f..680f5e29 100644 --- a/src/Tests/Modules/CombatDamageFormulaTest.bb +++ b/src/Tests/Modules/CombatDamageFormulaTest.bb @@ -829,13 +829,17 @@ End Test ; ActorAttack -- equipment wear ; ============================================================================ -; FLAG-FOR-HUMAN: the "Damage armour" block in ActorAttack wears down the -; ATTACKER's (A1's) equipped armour, not the defender's (A2's), even -; though A2's armour is what absorbed the blow. Pinned as shipped: after -; 300 swings with ArmourDamage on, the defender's chest piece is still at -; exactly 100 health while the attacker's has lost some (1-in-5 chance -; per swing; P(zero losses in 300) ~ 1e-29). -Test testArmourWearHitsAttackerNotDefender() +; The "Damage armour" block in ActorAttack wears down the DEFENDER's +; (A2's) equipped armour -- the armour that absorbed the blow. This test +; originally pinned (FLAG-FOR-HUMAN) the opposite, shipped-buggy behavior: +; the block was a copy-paste of the weapon-wear block above it and wore +; the ATTACKER's (A1's) armour instead. That bug is now fixed in +; GameServer.bb (A1 -> A2 in the armour-wear block only), and this test +; was deliberately flipped to assert the corrected behavior: after 300 +; swings with ArmourDamage on, the defender's chest piece has lost some +; health (1-in-5 chance per swing; P(zero losses in 300) ~ 1e-29) while +; the attacker's is still at exactly 100. +Test testArmourWearHitsDefender() ResetWorld() SeedRnd(4001) CombatFormula = 2 @@ -849,9 +853,9 @@ Test testArmourWearHitsAttackerNotDefender() For i = 1 To 300 ActorAttack(A1, A2) Next - Assert(DefenderChest\ItemHealth = 100) ; FLAG-FOR-HUMAN - Assert(AttackerChest\ItemHealth < 100) ; FLAG-FOR-HUMAN - Assert(AttackerChest\ItemHealth > 0) + Assert(DefenderChest\ItemHealth < 100) + Assert(DefenderChest\ItemHealth > 0) + Assert(AttackerChest\ItemHealth = 100) ResetWorld() End Test