Skip to content

Reflect Damage bug and broken chain loop #653

@iloveShaders

Description

@iloveShaders

Bug Report: Monster Secondary Reflect Uses Wrong Damage Value

Project: CrystalServer
File: src/game/game.cpp
Function: Game::combatBlockHit
Severity: Medium


Summary

When a monster has a reflect defined on a damage type that arrives as the secondary slot of a combined attack (e.g. a physical + ice weapon hit), the reflect formula reads damage.primary.value instead of damage.secondary.value. This causes the reflected amount to be calculated from the wrong source, producing either zero damage or a completely incorrect very high value depending on how the primary slot was absorbed.

Additionally, in the same code path: the reflected combat type parameter is set from damage.primary.type instead of damage.secondary.type, and the else branch (triggered when both primary and secondary reflect fire on the same hit) erroneously overwrites damageReflected.primary.value instead of writing to damageReflected.secondary.value.


Affected Monsters

Any monster with monster.reflects defined. Confirmed affected in the current data:

  • Burster Spectre (COMBAT_ICEDAMAGE, 133%)
  • Gazer Spectre (COMBAT_FIREDAMAGE, 133%)
  • Ripper Spectre (COMBAT_EARTHDAMAGE, 133%)
  • Crazed Summer Rearguard / Vanguard (COMBAT_FIREDAMAGE, 70%)
  • Crazed Winter Rearguard / Vanguard (COMBAT_ICEDAMAGE, 70%)
  • Soul-Broken Harbinger (COMBAT_ICEDAMAGE, 70%)
  • Spiky Carnivor / Menacing Carnivor (COMBAT_PHYSICALDAMAGE)
  • Faceless Bane (COMBAT_DEATHDAMAGE, 90%)
  • Leiden, Sand Vortex, The Armored Voidborn, Zarcorix Of Yalahar, The Source of Corruption, The Scourge of Oblivion, Force Field (all-element reflects)

Steps to Reproduce

  1. Find any monster with an elemental reflect, e.g. Burster Spectre (COMBAT_ICEDAMAGE 133%).
  2. Equip a weapon with an ice imbuement (produces a physical primary + ice secondary hit).
  3. Attack the Burster Spectre.
  4. Observe: ridiculous reflect damage is received*, despite the monster's resistances.

Compare: attacking with a pure ice spell (where ice is the primary slot) correctly triggers the reflect.


Root Cause

In Game::combatBlockHit (game.cpp), the secondary reflect block contains the following line:

// Line 7149 — BUG: reads primary value instead of secondary
int32_t reflectPercent = std::ceil(damage.primary.value * secondaryReflectPercent / 100.);

Three bugs exist in this block in total:

Line Bug Effect
7149 damage.primary.value used instead of damage.secondary.value Reflect calculates wrong amount (often 0)
~7093 damageReflectedParams.combatType set to damage.primary.type Wrong combat type passed for secondary-only reflect
~7163 else branch writes result into damageReflected.primary.value Overwrites already-set primary reflect when both slots trigger

Fix

// BEFORE
if (!damage.extension && attacker && target->getMonster()) {
    ...
    int32_t reflectPercent = std::ceil(damage.primary.value * secondaryReflectPercent / 100.);
    ...
    damageReflectedParams.combatType = damage.primary.type;
    ...
    // else branch:
    damageReflected.primary.value = std::ceil(damage.secondary.value * ...) + std::max(...);
}

// AFTER
if (!damage.extension && attacker) {
    ...
    int32_t reflectPercent = std::ceil(damage.secondary.value * secondaryReflectPercent / 100.);
    ...
    damageReflectedParams.combatType = damage.secondary.type;
    ...
    // else branch:
    int32_t reflectFlat = -static_cast<int32_t>(secondaryReflectFlat);
    int32_t reflectPercent = std::ceil(damage.secondary.value * secondaryReflectPercent / 100.);
    int32_t reflectLimit = std::ceil(attacker->getMaxHealth() * 0.01);
    damageReflected.secondary.type = damage.secondary.type;
    damageReflected.secondary.value = std::max(-reflectLimit, reflectFlat + reflectPercent);
}

The target->getMonster() guard is also removed so that secondary reflect behaves consistently with primary reflect, which has no such restriction.


Additional Note

The extension = true flag is correctly set on reflected damage in C++, preventing chain reactions (e.g. Charm Parry reflecting the reflected hit back into the monster).

I found the bug and described the issue on the Discord, Jattar Tajjar was faster and made a proper fix
reflection-fix.patch
msg link: https://discord.com/channels/1310943869923495988/1310954955066048633/1482333948926365789

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions