fix(destroyable): damage reductions add up, as the client

The damage reduction behavior set the reduction to its amount and back to 0
when it ended, so a second reduction replaced the first and ending either
removed both. The client's behavior sends AddDamageReduction
(DamageReductionBehavior::Cast) and the destroyable adds it to the current
reduction; removing takes off that amount, never below 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-10-02 09:11:20 -05:00
parent 3236d9f08a
commit cf1e8a54c6
4 changed files with 28 additions and 2 deletions

View File

@@ -22,7 +22,8 @@ void DamageReductionBehavior::Handle(BehaviorContext* context, RakNet::BitStream
return;
}
destroyable->SetDamageReduction(m_ReductionAmount);
// DamageReductionBehavior::Cast (0x004fdbb0) sends AddDamageReduction: it adds to any other reduction
destroyable->AddDamageReduction(m_ReductionAmount);
context->RegisterTimerBehavior(this, branch, target->GetObjectID());
}
@@ -46,7 +47,7 @@ void DamageReductionBehavior::Timer(BehaviorContext* context, BehaviorBranchCont
return;
}
destroyable->SetDamageReduction(0);
destroyable->RemoveDamageReduction(m_ReductionAmount);
}
void DamageReductionBehavior::Load() {

View File

@@ -444,6 +444,14 @@ void DestroyableComponent::SetDamageReduction(int32_t value) {
m_DamageReduction = value;
}
void DestroyableComponent::AddDamageReduction(const int32_t value) {
SetDamageReduction(std::max(m_DamageReduction + value, 0));
}
void DestroyableComponent::RemoveDamageReduction(const int32_t value) {
SetDamageReduction(std::max(m_DamageReduction - value, 0));
}
void DestroyableComponent::SetIsImmune(bool value) {
m_DirtyHealth = true;
m_ImmuneToBasicAttackCount = value ? 1 : 0;

View File

@@ -175,6 +175,11 @@ public:
*/
void SetDamageReduction(int32_t value);
// The client's AddDamageReduction / RemoveDamageReduction: reductions from several buffs add up, and removing one
// takes off only its own amount (never below 0)
void AddDamageReduction(int32_t value);
void RemoveDamageReduction(int32_t value);
/**
* Returns the current damage reduction value
* @return the current damage reduction value

View File

@@ -706,3 +706,15 @@ TEST_F(DestroyableTest, DeadSmashableIsNotStunned) {
const auto sent = PacketTestUtils::Capture([&] { destroyableComponent->Smash(LWOOBJID_EMPTY); });
EXPECT_TRUE(GameMessageTestUtils::SentGameMessages<GameMessages::SetStunned>(sent).empty());
}
// The client's AddDamageReduction / RemoveDamageReduction: two buffs add up, ending one leaves the other's
TEST_F(DestroyableTest, DamageReductionsAddUp) {
destroyableComponent->SetDamageReduction(0);
destroyableComponent->AddDamageReduction(2);
destroyableComponent->AddDamageReduction(3);
EXPECT_EQ(destroyableComponent->GetDamageReduction(), 5);
destroyableComponent->RemoveDamageReduction(2);
EXPECT_EQ(destroyableComponent->GetDamageReduction(), 3);
destroyableComponent->RemoveDamageReduction(10);
EXPECT_EQ(destroyableComponent->GetDamageReduction(), 0);
}