-
Notifications
You must be signed in to change notification settings - Fork 132
Reflect Damage bug and broken chain loop #653
Description
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
- Find any monster with an elemental reflect, e.g. Burster Spectre (
COMBAT_ICEDAMAGE133%). - Equip a weapon with an ice imbuement (produces a physical primary + ice secondary hit).
- Attack the Burster Spectre.
- 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