tbc: fix SMSG_CAST_RESULT — no castCount prefix in TBC 2.4.3

TBC 2.4.3 SMSG_CAST_RESULT sends spellId(u32) + result(u8) = 5 bytes.
WotLK 3.3.5a added a castCount(u8) prefix making it 6 bytes.  Without
this fix the WotLK parser was reading spellId[0] as castCount, then the
remaining 3 spellId bytes plus result byte as spellId (wrong), and then
whatever follows as result — producing incorrect failure messages and
potentially not clearing the cast bar on TBC.

Add TbcPacketParsers::parseCastResult override and a virtual base method,
then route SMSG_CAST_RESULT through virtual dispatch in the game handler.
This commit is contained in:
Kelsi 2026-03-09 21:46:18 -07:00
parent 1b2c7f595e
commit 921c83df2e
3 changed files with 40 additions and 10 deletions

View file

@ -110,6 +110,19 @@ public:
return CastFailedParser::parse(packet, data); return CastFailedParser::parse(packet, data);
} }
/** Parse SMSG_CAST_RESULT header (spellId + result), expansion-aware.
* WotLK: castCount(u8) + spellId(u32) + result(u8)
* TBC/Classic: spellId(u32) + result(u8) (no castCount prefix)
*/
virtual bool parseCastResult(network::Packet& packet, uint32_t& spellId, uint8_t& result) {
// WotLK default: skip castCount, read spellId + result
if (packet.getSize() - packet.getReadPos() < 6) return false;
packet.readUInt8(); // castCount
spellId = packet.readUInt32();
result = packet.readUInt8();
return true;
}
/** Parse SMSG_AURA_UPDATE / SMSG_AURA_UPDATE_ALL */ /** Parse SMSG_AURA_UPDATE / SMSG_AURA_UPDATE_ALL */
virtual bool parseAuraUpdate(network::Packet& packet, AuraUpdateData& data, bool isAll = false) { virtual bool parseAuraUpdate(network::Packet& packet, AuraUpdateData& data, bool isAll = false) {
return AuraUpdateParser::parse(packet, data, isAll); return AuraUpdateParser::parse(packet, data, isAll);
@ -307,6 +320,8 @@ public:
bool parseMonsterMove(network::Packet& packet, MonsterMoveData& data) override; bool parseMonsterMove(network::Packet& packet, MonsterMoveData& data) override;
// TBC 2.4.3 SMSG_GOSSIP_MESSAGE quests lack questFlags(u32)+isRepeatable(u8) (WotLK added them) // TBC 2.4.3 SMSG_GOSSIP_MESSAGE quests lack questFlags(u32)+isRepeatable(u8) (WotLK added them)
bool parseGossipMessage(network::Packet& packet, GossipMessageData& data) override; bool parseGossipMessage(network::Packet& packet, GossipMessageData& data) override;
// TBC 2.4.3 SMSG_CAST_RESULT: spellId(u32) + result(u8) — WotLK added castCount(u8) prefix
bool parseCastResult(network::Packet& packet, uint32_t& spellId, uint8_t& result) override;
// TBC 2.4.3 SMSG_MAIL_LIST_RESULT: uint8 count (not uint32+uint8), no body field, // TBC 2.4.3 SMSG_MAIL_LIST_RESULT: uint8 count (not uint32+uint8), no body field,
// attachment uses uint64 itemGuid (not uint32), enchants are 7×u32 id-only (not 7×{id+dur+charges}) // attachment uses uint64 itemGuid (not uint32), enchants are 7×u32 id-only (not 7×{id+dur+charges})
bool parseMailList(network::Packet& packet, std::vector<MailMessage>& inbox) override; bool parseMailList(network::Packet& packet, std::vector<MailMessage>& inbox) override;

View file

@ -1742,28 +1742,28 @@ void GameHandler::handlePacket(network::Packet& packet) {
} }
// ---- Cast result (WotLK extended cast failed) ---- // ---- Cast result (WotLK extended cast failed) ----
case Opcode::SMSG_CAST_RESULT: case Opcode::SMSG_CAST_RESULT: {
// WotLK: uint8 castCount + uint32 spellId + uint8 result [+ optional extra] // WotLK: castCount(u8) + spellId(u32) + result(u8)
// TBC/Classic: spellId(u32) + result(u8) (no castCount prefix)
// If result == 0, the spell successfully began; otherwise treat like SMSG_CAST_FAILED. // If result == 0, the spell successfully began; otherwise treat like SMSG_CAST_FAILED.
if (packet.getSize() - packet.getReadPos() >= 6) { uint32_t castResultSpellId = 0;
/*uint8_t castCount =*/ packet.readUInt8(); uint8_t castResult = 0;
/*uint32_t spellId =*/ packet.readUInt32(); if (packetParsers_->parseCastResult(packet, castResultSpellId, castResult)) {
uint8_t result = packet.readUInt8(); if (castResult != 0) {
if (result != 0) {
// Failure — clear cast bar and show message
casting = false; casting = false;
currentCastSpellId = 0; currentCastSpellId = 0;
castTimeRemaining = 0.0f; castTimeRemaining = 0.0f;
const char* reason = getSpellCastResultString(result, -1); const char* reason = getSpellCastResultString(castResult, -1);
MessageChatData msg; MessageChatData msg;
msg.type = ChatType::SYSTEM; msg.type = ChatType::SYSTEM;
msg.language = ChatLanguage::UNIVERSAL; msg.language = ChatLanguage::UNIVERSAL;
msg.message = reason ? reason msg.message = reason ? reason
: ("Spell cast failed (error " + std::to_string(result) + ")"); : ("Spell cast failed (error " + std::to_string(castResult) + ")");
addLocalChatMessage(msg); addLocalChatMessage(msg);
} }
} }
break; break;
}
// ---- Spell failed on another unit ---- // ---- Spell failed on another unit ----
case Opcode::SMSG_SPELL_FAILED_OTHER: case Opcode::SMSG_SPELL_FAILED_OTHER:

View file

@ -977,6 +977,21 @@ bool TbcPacketParsers::parseMailList(network::Packet& packet, std::vector<MailMe
return !inbox.empty(); return !inbox.empty();
} }
// ============================================================================
// TbcPacketParsers::parseCastResult — TBC 2.4.3 SMSG_CAST_RESULT
//
// TBC format: spellId(u32) + result(u8) = 5 bytes
// WotLK adds a castCount(u8) prefix making it 6 bytes.
// Without this override, WotLK parser reads spellId[0] as castCount,
// then the remaining 4 bytes as spellId (off by one), producing wrong result.
// ============================================================================
bool TbcPacketParsers::parseCastResult(network::Packet& packet, uint32_t& spellId, uint8_t& result) {
if (packet.getSize() - packet.getReadPos() < 5) return false;
spellId = packet.readUInt32(); // No castCount prefix in TBC
result = packet.readUInt8();
return true;
}
// ============================================================================ // ============================================================================
// TbcPacketParsers::parseAttackerStateUpdate — TBC 2.4.3 SMSG_ATTACKERSTATEUPDATE // TbcPacketParsers::parseAttackerStateUpdate — TBC 2.4.3 SMSG_ATTACKERSTATEUPDATE
// //