Add mail item attachment support for sending

- CMSG_SEND_MAIL now includes item GUIDs (up to 12 per WotLK)
- Right-click items in bags to attach when mail compose is open
- Compose window shows 12-slot attachment grid with item icons
- Click attached items to remove them
- Classic/Vanilla falls back to single item GUID format
This commit is contained in:
Kelsi 2026-02-25 14:11:09 -08:00
parent ce30cedf4a
commit 9906269671
8 changed files with 203 additions and 17 deletions

View file

@ -12851,10 +12851,114 @@ void GameHandler::sendMail(const std::string& recipient, const std::string& subj
LOG_WARNING("sendMail: mailboxGuid_ is 0 (mailbox closed?)");
return;
}
auto packet = packetParsers_->buildSendMail(mailboxGuid_, recipient, subject, body, money, cod);
// Collect attached item GUIDs
std::vector<uint64_t> itemGuids;
for (const auto& att : mailAttachments_) {
if (att.occupied()) {
itemGuids.push_back(att.itemGuid);
}
}
auto packet = packetParsers_->buildSendMail(mailboxGuid_, recipient, subject, body, money, cod, itemGuids);
LOG_INFO("sendMail: to='", recipient, "' subject='", subject, "' money=", money,
" mailboxGuid=", mailboxGuid_);
" attachments=", itemGuids.size(), " mailboxGuid=", mailboxGuid_);
socket->send(packet);
clearMailAttachments();
}
bool GameHandler::attachItemFromBackpack(int backpackIndex) {
if (backpackIndex < 0 || backpackIndex >= inventory.getBackpackSize()) return false;
const auto& slot = inventory.getBackpackSlot(backpackIndex);
if (slot.empty()) return false;
uint64_t itemGuid = backpackSlotGuids_[backpackIndex];
if (itemGuid == 0) {
itemGuid = resolveOnlineItemGuid(slot.item.itemId);
}
if (itemGuid == 0) {
addSystemChatMessage("Cannot attach: item not found.");
return false;
}
// Check not already attached
for (const auto& att : mailAttachments_) {
if (att.occupied() && att.itemGuid == itemGuid) return false;
}
// Find free attachment slot
for (int i = 0; i < MAIL_MAX_ATTACHMENTS; ++i) {
if (!mailAttachments_[i].occupied()) {
mailAttachments_[i].itemGuid = itemGuid;
mailAttachments_[i].item = slot.item;
mailAttachments_[i].srcBag = 0xFF;
mailAttachments_[i].srcSlot = static_cast<uint8_t>(23 + backpackIndex);
LOG_INFO("Mail attach: slot=", i, " item='", slot.item.name, "' guid=0x",
std::hex, itemGuid, std::dec, " from backpack[", backpackIndex, "]");
return true;
}
}
addSystemChatMessage("Cannot attach: all attachment slots full.");
return false;
}
bool GameHandler::attachItemFromBag(int bagIndex, int slotIndex) {
if (bagIndex < 0 || bagIndex >= inventory.NUM_BAG_SLOTS) return false;
if (slotIndex < 0 || slotIndex >= inventory.getBagSize(bagIndex)) return false;
const auto& slot = inventory.getBagSlot(bagIndex, slotIndex);
if (slot.empty()) return false;
uint64_t itemGuid = 0;
uint64_t bagGuid = equipSlotGuids_[19 + bagIndex];
if (bagGuid != 0) {
auto it = containerContents_.find(bagGuid);
if (it != containerContents_.end() && slotIndex < static_cast<int>(it->second.numSlots)) {
itemGuid = it->second.slotGuids[slotIndex];
}
}
if (itemGuid == 0) {
itemGuid = resolveOnlineItemGuid(slot.item.itemId);
}
if (itemGuid == 0) {
addSystemChatMessage("Cannot attach: item not found.");
return false;
}
for (const auto& att : mailAttachments_) {
if (att.occupied() && att.itemGuid == itemGuid) return false;
}
for (int i = 0; i < MAIL_MAX_ATTACHMENTS; ++i) {
if (!mailAttachments_[i].occupied()) {
mailAttachments_[i].itemGuid = itemGuid;
mailAttachments_[i].item = slot.item;
mailAttachments_[i].srcBag = static_cast<uint8_t>(19 + bagIndex);
mailAttachments_[i].srcSlot = static_cast<uint8_t>(slotIndex);
LOG_INFO("Mail attach: slot=", i, " item='", slot.item.name, "' guid=0x",
std::hex, itemGuid, std::dec, " from bag[", bagIndex, "][", slotIndex, "]");
return true;
}
}
addSystemChatMessage("Cannot attach: all attachment slots full.");
return false;
}
bool GameHandler::detachMailAttachment(int attachIndex) {
if (attachIndex < 0 || attachIndex >= MAIL_MAX_ATTACHMENTS) return false;
if (!mailAttachments_[attachIndex].occupied()) return false;
LOG_INFO("Mail detach: slot=", attachIndex, " item='", mailAttachments_[attachIndex].item.name, "'");
mailAttachments_[attachIndex] = MailAttachSlot{};
return true;
}
void GameHandler::clearMailAttachments() {
for (auto& att : mailAttachments_) att = MailAttachSlot{};
}
int GameHandler::getMailAttachmentCount() const {
int count = 0;
for (const auto& att : mailAttachments_) {
if (att.occupied()) ++count;
}
return count;
}
void GameHandler::mailTakeMoney(uint32_t mailId) {