feat: extend SMSG_SPELLLOGEXECUTE to parse power drain, health leech, interrupt cast, and feed pet effects

- Effect 10 (POWER_DRAIN): show PERIODIC_DAMAGE text on victim, ENERGIZE on caster;
  handles Drain Mana, Viper Sting, Fel Drain, etc.
- Effect 11 (HEALTH_LEECH): show SPELL_DAMAGE on victim, HEAL on caster;
  handles Drain Life, Death Coil, etc.
- Effect 24/114 (CREATE_ITEM/CREATE_ITEM2): existing profession crafting feedback
  extended to also cover CREATE_ITEM2 (engineering/enchanting recipes using alt effect)
- Effect 26 (INTERRUPT_CAST): clear the interrupted unit's cast bar from unitCastStates_
  so the cast bar dismisses immediately rather than waiting for the next update packet
- Effect 49 (FEED_PET): show "You feed your pet <item>." message for hunter pet feeding

All effects are expansion-aware: TBC/Classic use full uint64 GUIDs, WotLK uses packed GUIDs.
This commit is contained in:
Kelsi 2026-03-12 23:09:04 -07:00
parent e029e8649f
commit 0089b3a160

View file

@ -6066,7 +6066,12 @@ void GameHandler::handlePacket(network::Packet& packet) {
// WotLK: packed_guid caster + uint32 spellId + uint32 effectCount
// TBC/Classic: uint64 caster + uint32 spellId + uint32 effectCount
// Per-effect: uint8 effectType + uint32 effectLogCount + effect-specific data
// Effect 24 = SPELL_EFFECT_CREATE_ITEM: uint32 itemEntry per entry
// Effect 10 = POWER_DRAIN: packed_guid target + uint32 amount + uint32 powerType + float multiplier
// Effect 11 = HEALTH_LEECH: packed_guid target + uint32 amount + float multiplier
// Effect 24 = CREATE_ITEM: uint32 itemEntry
// Effect 26 = INTERRUPT_CAST: packed_guid target + uint32 interrupted_spell_id
// Effect 49 = FEED_PET: uint32 itemEntry
// Effect 114= CREATE_ITEM2: uint32 itemEntry (same layout as CREATE_ITEM)
const bool exeTbcLike = isClassicLikeExpansion() || isActiveExpansion("tbc");
if (packet.getSize() - packet.getReadPos() < (exeTbcLike ? 8u : 1u)) {
packet.setReadPos(packet.getSize()); break;
@ -6086,8 +6091,46 @@ void GameHandler::handlePacket(network::Packet& packet) {
uint8_t effectType = packet.readUInt8();
uint32_t effectLogCount = packet.readUInt32();
effectLogCount = std::min(effectLogCount, 64u); // sanity
if (effectType == 24) {
// SPELL_EFFECT_CREATE_ITEM: uint32 itemEntry per log entry
if (effectType == 10) {
// SPELL_EFFECT_POWER_DRAIN: packed_guid target + uint32 amount + uint32 powerType + float multiplier
for (uint32_t li = 0; li < effectLogCount; ++li) {
if (packet.getSize() - packet.getReadPos() < 1) break;
uint64_t drainTarget = exeTbcLike
? (packet.getSize() - packet.getReadPos() >= 8 ? packet.readUInt64() : 0)
: UpdateObjectParser::readPackedGuid(packet);
if (packet.getSize() - packet.getReadPos() < 12) { packet.setReadPos(packet.getSize()); break; }
uint32_t drainAmount = packet.readUInt32();
uint32_t drainPower = packet.readUInt32(); // 0=mana,1=rage,3=energy,6=runic
/*float drainMult =*/ packet.readFloat();
if (drainAmount > 0) {
if (drainTarget == playerGuid)
addCombatText(CombatTextEntry::PERIODIC_DAMAGE, static_cast<int32_t>(drainAmount), exeSpellId, false);
else if (isPlayerCaster)
addCombatText(CombatTextEntry::ENERGIZE, static_cast<int32_t>(drainAmount), exeSpellId, true);
}
LOG_DEBUG("SMSG_SPELLLOGEXECUTE POWER_DRAIN: spell=", exeSpellId,
" power=", drainPower, " amount=", drainAmount);
}
} else if (effectType == 11) {
// SPELL_EFFECT_HEALTH_LEECH: packed_guid target + uint32 amount + float multiplier
for (uint32_t li = 0; li < effectLogCount; ++li) {
if (packet.getSize() - packet.getReadPos() < 1) break;
uint64_t leechTarget = exeTbcLike
? (packet.getSize() - packet.getReadPos() >= 8 ? packet.readUInt64() : 0)
: UpdateObjectParser::readPackedGuid(packet);
if (packet.getSize() - packet.getReadPos() < 8) { packet.setReadPos(packet.getSize()); break; }
uint32_t leechAmount = packet.readUInt32();
/*float leechMult =*/ packet.readFloat();
if (leechAmount > 0) {
if (leechTarget == playerGuid)
addCombatText(CombatTextEntry::SPELL_DAMAGE, static_cast<int32_t>(leechAmount), exeSpellId, false);
else if (isPlayerCaster)
addCombatText(CombatTextEntry::HEAL, static_cast<int32_t>(leechAmount), exeSpellId, true);
}
LOG_DEBUG("SMSG_SPELLLOGEXECUTE HEALTH_LEECH: spell=", exeSpellId, " amount=", leechAmount);
}
} else if (effectType == 24 || effectType == 114) {
// SPELL_EFFECT_CREATE_ITEM / CREATE_ITEM2: uint32 itemEntry per log entry
for (uint32_t li = 0; li < effectLogCount; ++li) {
if (packet.getSize() - packet.getReadPos() < 4) break;
uint32_t itemEntry = packet.readUInt32();
@ -6108,11 +6151,36 @@ void GameHandler::handlePacket(network::Packet& packet) {
" item=", itemEntry, " name=", itemName);
}
}
} else if (effectType == 26) {
// SPELL_EFFECT_INTERRUPT_CAST: packed_guid target + uint32 interrupted_spell_id
for (uint32_t li = 0; li < effectLogCount; ++li) {
if (packet.getSize() - packet.getReadPos() < 1) break;
uint64_t icTarget = exeTbcLike
? (packet.getSize() - packet.getReadPos() >= 8 ? packet.readUInt64() : 0)
: UpdateObjectParser::readPackedGuid(packet);
if (packet.getSize() - packet.getReadPos() < 4) { packet.setReadPos(packet.getSize()); break; }
uint32_t icSpellId = packet.readUInt32();
// Clear the interrupted unit's cast bar immediately
unitCastStates_.erase(icTarget);
LOG_DEBUG("SMSG_SPELLLOGEXECUTE INTERRUPT_CAST: spell=", exeSpellId,
" interrupted=", icSpellId, " target=0x", std::hex, icTarget, std::dec);
}
} else if (effectType == 49) {
// SPELL_EFFECT_FEED_PET: uint32 itemEntry per log entry
for (uint32_t li = 0; li < effectLogCount; ++li) {
if (packet.getSize() - packet.getReadPos() < 4) break;
uint32_t feedItem = packet.readUInt32();
if (isPlayerCaster && feedItem != 0) {
ensureItemInfo(feedItem);
const ItemQueryResponseData* info = getItemInfo(feedItem);
std::string itemName = info && !info->name.empty()
? info->name : ("item #" + std::to_string(feedItem));
addSystemChatMessage("You feed your pet " + itemName + ".");
LOG_DEBUG("SMSG_SPELLLOGEXECUTE FEED_PET: item=", feedItem, " name=", itemName);
}
}
} else {
// Other effect types: consume their data safely
// Most effects have no trailing per-entry data beyond the count header,
// or use variable-length sub-records we cannot safely skip.
// Stop parsing at first unknown effect to avoid misalignment.
// Unknown effect type — stop parsing to avoid misalignment
packet.setReadPos(packet.getSize());
break;
}