fixin critical bugs, non critical bugs, sendmail implementation

This commit is contained in:
Paul 2026-03-28 11:35:10 +03:00
parent b2710258dc
commit 888a78d775
15 changed files with 116 additions and 119 deletions

View file

@ -2660,8 +2660,7 @@ void GameHandler::registerOpcodeHandlers() {
// Clear cached talent data so the talent screen reflects the reset.
dispatchTable_[Opcode::SMSG_TALENTS_INVOLUNTARILY_RESET] = [this](network::Packet& packet) {
// Clear cached talent data so the talent screen reflects the reset.
learnedTalents_[0].clear();
learnedTalents_[1].clear();
if (spellHandler_) spellHandler_->resetTalentState();
addUIError("Your talents have been reset by the server.");
addSystemChatMessage("Your talents have been reset by the server.");
packet.skipAll();
@ -4917,14 +4916,7 @@ void GameHandler::handleLoginVerifyWorld(network::Packet& packet) {
// Reset talent initialization so the first SMSG_TALENTS_INFO after login
// correctly sets the active spec (static locals don't reset across logins).
talentsInitialized_ = false;
learnedTalents_[0].clear();
learnedTalents_[1].clear();
learnedGlyphs_[0].fill(0);
learnedGlyphs_[1].fill(0);
unspentTalentPoints_[0] = 0;
unspentTalentPoints_[1] = 0;
activeTalentSpec_ = 0;
if (spellHandler_) spellHandler_->resetTalentState();
// Auto-join default chat channels only on first world entry.
autoJoinDefaultChannels();
@ -5069,6 +5061,12 @@ void GameHandler::sendRequestVehicleExit() {
vehicleId_ = 0; // Optimistically clear; server will confirm via SMSG_PLAYER_VEHICLE_DATA(0)
}
const std::vector<GameHandler::EquipmentSetInfo>& GameHandler::getEquipmentSets() const {
if (inventoryHandler_) return inventoryHandler_->getEquipmentSets();
static const std::vector<EquipmentSetInfo> empty;
return empty;
}
bool GameHandler::supportsEquipmentSets() const {
return inventoryHandler_ && inventoryHandler_->supportsEquipmentSets();
}

View file

@ -698,7 +698,7 @@ void InventoryHandler::handleLootResponse(network::Packet& packet) {
const bool wotlkLoot = isActiveExpansion("wotlk");
if (!LootResponseParser::parse(packet, currentLoot_, wotlkLoot)) return;
const bool hasLoot = !currentLoot_.items.empty() || currentLoot_.gold > 0;
if (!hasLoot && owner_.casting && owner_.currentCastSpellId != 0 && lastInteractedGoGuid_ != 0) {
if (!hasLoot && owner_.isCasting() && owner_.getCurrentCastSpellId() != 0 && lastInteractedGoGuid_ != 0) {
LOG_DEBUG("Ignoring empty SMSG_LOOT_RESPONSE during gather cast");
return;
}
@ -1500,14 +1500,30 @@ void InventoryHandler::refreshMailList() {
void InventoryHandler::sendMail(const std::string& recipient, const std::string& subject,
const std::string& body, uint64_t money, uint64_t cod) {
if (owner_.state != WorldState::IN_WORLD || !owner_.socket || mailboxGuid_ == 0) return;
std::vector<uint64_t> itemGuids;
for (const auto& a : mailAttachments_) {
if (a.occupied()) itemGuids.push_back(a.itemGuid);
if (owner_.state != WorldState::IN_WORLD) {
LOG_WARNING("sendMail: not in world");
return;
}
auto packet = SendMailPacket::build(mailboxGuid_, recipient, subject, body, money, cod,
itemGuids);
if (!owner_.socket) {
LOG_WARNING("sendMail: no socket");
return;
}
if (mailboxGuid_ == 0) {
LOG_WARNING("sendMail: mailboxGuid_ is 0 (mailbox closed?)");
return;
}
// Collect attached item GUIDs
std::vector<uint64_t> itemGuids;
for (const auto& att : mailAttachments_) {
if (att.occupied()) {
itemGuids.push_back(att.itemGuid);
}
}
auto packet = owner_.packetParsers_->buildSendMail(mailboxGuid_, recipient, subject, body, money, cod, itemGuids);
LOG_INFO("sendMail: to='", recipient, "' subject='", subject, "' money=", money,
" attachments=", itemGuids.size(), " mailboxGuid=", mailboxGuid_);
owner_.socket->send(packet);
clearMailAttachments();
}
bool InventoryHandler::attachItemFromBackpack(int backpackIndex) {

View file

@ -431,7 +431,7 @@ void MovementHandler::sendMovement(Opcode opcode) {
const bool wasMoving = (movementInfo.flags & kMoveMask) != 0;
// Cancel any timed (non-channeled) cast the moment the player starts moving.
if (owner_.casting && !owner_.castIsChannel) {
if (owner_.isCasting() && !owner_.isChanneling()) {
const bool isPositionalMove =
opcode == Opcode::MSG_MOVE_START_FORWARD ||
opcode == Opcode::MSG_MOVE_START_BACKWARD ||
@ -798,7 +798,7 @@ void MovementHandler::dismount() {
owner_.socket->send(pkt);
LOG_INFO("Sent CMSG_CANCEL_AURA (mount spell ", savedMountAura, ") — Classic fallback");
} else {
for (const auto& a : owner_.playerAuras) {
for (const auto& a : owner_.getPlayerAuras()) {
if (!a.isEmpty() && a.maxDurationMs < 0 && a.casterGuid == owner_.playerGuid) {
auto pkt = CancelAuraPacket::build(a.spellId);
owner_.socket->send(pkt);
@ -1808,6 +1808,9 @@ void MovementHandler::handleTeleportAck(network::Packet& packet) {
movementInfo.orientation = core::coords::serverToCanonicalYaw(orientation);
movementInfo.flags = 0;
// Clear cast bar on teleport — SpellHandler owns the casting_ flag
if (owner_.spellHandler_) owner_.spellHandler_->resetCastState();
if (owner_.socket) {
network::Packet ack(wireOpcode(Opcode::MSG_MOVE_TELEPORT_ACK));
const bool legacyGuidAck =
@ -1869,10 +1872,7 @@ void MovementHandler::handleNewWorld(network::Packet& packet) {
owner_.clearHostileAttackers();
owner_.stopAutoAttack();
owner_.tabCycleStale = true;
owner_.casting = false;
owner_.castIsChannel = false;
owner_.currentCastSpellId = 0;
owner_.castTimeRemaining = 0.0f;
owner_.resetCastState();
owner_.craftQueueSpellId_ = 0;
owner_.craftQueueRemaining_ = 0;
owner_.queuedSpellId_ = 0;
@ -1941,12 +1941,7 @@ void MovementHandler::handleNewWorld(network::Packet& packet) {
owner_.areaTriggerCheckTimer_ = -5.0f;
owner_.areaTriggerSuppressFirst_ = true;
owner_.stopAutoAttack();
owner_.casting = false;
owner_.castIsChannel = false;
owner_.currentCastSpellId = 0;
owner_.pendingGameObjectInteractGuid_ = 0;
owner_.lastInteractedGoGuid_ = 0;
owner_.castTimeRemaining = 0.0f;
owner_.resetCastState();
owner_.craftQueueSpellId_ = 0;
owner_.craftQueueRemaining_ = 0;
owner_.queuedSpellId_ = 0;

View file

@ -1221,7 +1221,6 @@ void SocialHandler::handleGroupDecline(network::Packet& packet) {
void SocialHandler::handleGroupList(network::Packet& packet) {
const bool hasRoles = isActiveExpansion("wotlk");
const uint32_t prevCount = partyData.memberCount;
const uint8_t prevLootMethod = partyData.lootMethod;
const bool wasInGroup = !partyData.isEmpty();
partyData = GroupListData{};

View file

@ -136,7 +136,7 @@ void SpellHandler::registerOpcodes(DispatchTable& table) {
table[Opcode::SMSG_ACHIEVEMENT_EARNED] = [this](network::Packet& packet) {
handleAchievementEarned(packet);
};
table[Opcode::SMSG_EQUIPMENT_SET_LIST] = [this](network::Packet& packet) { handleEquipmentSetList(packet); };
// SMSG_EQUIPMENT_SET_LIST — owned by InventoryHandler::registerOpcodes
// ---- Cast result / spell visuals / cooldowns / modifiers ----
table[Opcode::SMSG_CAST_RESULT] = [this](network::Packet& p) { handleCastResult(p); };
@ -1423,43 +1423,7 @@ void SpellHandler::handleAchievementEarned(network::Packet& packet) {
owner_.addonEventCallback_("ACHIEVEMENT_EARNED", {std::to_string(achievementId)});
}
void SpellHandler::handleEquipmentSetList(network::Packet& packet) {
if (packet.getSize() - packet.getReadPos() < 4) return;
uint32_t count = packet.readUInt32();
if (count > 10) {
LOG_WARNING("SMSG_EQUIPMENT_SET_LIST: unexpected count ", count, ", ignoring");
packet.setReadPos(packet.getSize());
return;
}
equipmentSets_.clear();
equipmentSets_.reserve(count);
for (uint32_t i = 0; i < count; ++i) {
if (packet.getSize() - packet.getReadPos() < 16) break;
EquipmentSet es;
es.setGuid = packet.readUInt64();
es.setId = packet.readUInt32();
es.name = packet.readString();
es.iconName = packet.readString();
es.ignoreSlotMask = packet.readUInt32();
for (int slot = 0; slot < 19; ++slot) {
if (packet.getSize() - packet.getReadPos() < 8) break;
es.itemGuids[slot] = packet.readUInt64();
}
equipmentSets_.push_back(std::move(es));
}
// Populate public-facing info
equipmentSetInfo_.clear();
equipmentSetInfo_.reserve(equipmentSets_.size());
for (const auto& es : equipmentSets_) {
EquipmentSetInfo info;
info.setGuid = es.setGuid;
info.setId = es.setId;
info.name = es.name;
info.iconName = es.iconName;
equipmentSetInfo_.push_back(std::move(info));
}
LOG_INFO("SMSG_EQUIPMENT_SET_LIST: ", equipmentSets_.size(), " equipment sets received");
}
// SMSG_EQUIPMENT_SET_LIST — moved to InventoryHandler
// ============================================================
// Pet spell methods (moved from GameHandler)
@ -1645,6 +1609,17 @@ void SpellHandler::resetCastState() {
owner_.lastInteractedGoGuid_ = 0;
}
void SpellHandler::resetTalentState() {
talentsInitialized_ = false;
learnedTalents_[0].clear();
learnedTalents_[1].clear();
learnedGlyphs_[0].fill(0);
learnedGlyphs_[1].fill(0);
unspentTalentPoints_[0] = 0;
unspentTalentPoints_[1] = 0;
activeTalentSpec_ = 0;
}
void SpellHandler::clearUnitCaches() {
unitCastStates_.clear();
unitAurasCache_.clear();