mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-02 15:53:51 +00:00
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:
parent
ce30cedf4a
commit
9906269671
8 changed files with 203 additions and 17 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -728,7 +728,8 @@ network::Packet ClassicPacketParsers::buildSendMail(uint64_t mailboxGuid,
|
|||
const std::string& recipient,
|
||||
const std::string& subject,
|
||||
const std::string& body,
|
||||
uint32_t money, uint32_t cod) {
|
||||
uint32_t money, uint32_t cod,
|
||||
const std::vector<uint64_t>& itemGuids) {
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_SEND_MAIL));
|
||||
packet.writeUInt64(mailboxGuid);
|
||||
packet.writeString(recipient);
|
||||
|
|
@ -736,7 +737,9 @@ network::Packet ClassicPacketParsers::buildSendMail(uint64_t mailboxGuid,
|
|||
packet.writeString(body);
|
||||
packet.writeUInt32(0); // stationery
|
||||
packet.writeUInt32(0); // unknown
|
||||
packet.writeUInt64(0); // item GUID (0 = no attachment, single item only in Vanilla)
|
||||
// Vanilla supports only one item attachment (single uint64 GUID)
|
||||
uint64_t singleItemGuid = itemGuids.empty() ? 0 : itemGuids[0];
|
||||
packet.writeUInt64(singleItemGuid);
|
||||
packet.writeUInt32(money);
|
||||
packet.writeUInt32(cod);
|
||||
packet.writeUInt64(0); // unk3 (clients > 1.9.4)
|
||||
|
|
|
|||
|
|
@ -3939,7 +3939,8 @@ network::Packet GetMailListPacket::build(uint64_t mailboxGuid) {
|
|||
|
||||
network::Packet SendMailPacket::build(uint64_t mailboxGuid, const std::string& recipient,
|
||||
const std::string& subject, const std::string& body,
|
||||
uint32_t money, uint32_t cod) {
|
||||
uint32_t money, uint32_t cod,
|
||||
const std::vector<uint64_t>& itemGuids) {
|
||||
// WotLK 3.3.5a format
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_SEND_MAIL));
|
||||
packet.writeUInt64(mailboxGuid);
|
||||
|
|
@ -3948,7 +3949,12 @@ network::Packet SendMailPacket::build(uint64_t mailboxGuid, const std::string& r
|
|||
packet.writeString(body);
|
||||
packet.writeUInt32(0); // stationery
|
||||
packet.writeUInt32(0); // unknown
|
||||
packet.writeUInt8(0); // attachment count (0 = no attachments)
|
||||
uint8_t attachCount = static_cast<uint8_t>(itemGuids.size());
|
||||
packet.writeUInt8(attachCount);
|
||||
for (uint8_t i = 0; i < attachCount; ++i) {
|
||||
packet.writeUInt8(i); // attachment slot index
|
||||
packet.writeUInt64(itemGuids[i]);
|
||||
}
|
||||
packet.writeUInt32(money);
|
||||
packet.writeUInt32(cod);
|
||||
return packet;
|
||||
|
|
|
|||
|
|
@ -7384,8 +7384,8 @@ void GameScreen::renderMailComposeWindow(game::GameHandler& gameHandler) {
|
|||
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
||||
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 175, screenH / 2 - 200), ImGuiCond_Appearing);
|
||||
ImGui::SetNextWindowSize(ImVec2(380, 400), ImGuiCond_Appearing);
|
||||
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 190, screenH / 2 - 250), ImGuiCond_Appearing);
|
||||
ImGui::SetNextWindowSize(ImVec2(400, 500), ImGuiCond_Appearing);
|
||||
|
||||
bool open = true;
|
||||
if (ImGui::Begin("Send Mail", &open)) {
|
||||
|
|
@ -7401,8 +7401,56 @@ void GameScreen::renderMailComposeWindow(game::GameHandler& gameHandler) {
|
|||
|
||||
ImGui::Text("Body:");
|
||||
ImGui::InputTextMultiline("##MailBody", mailBodyBuffer_, sizeof(mailBodyBuffer_),
|
||||
ImVec2(-1, 150));
|
||||
ImVec2(-1, 120));
|
||||
|
||||
// Attachments section
|
||||
int attachCount = gameHandler.getMailAttachmentCount();
|
||||
ImGui::Text("Attachments (%d/12):", attachCount);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "Right-click items in bags to attach");
|
||||
|
||||
const auto& attachments = gameHandler.getMailAttachments();
|
||||
// Show attachment slots in a grid (6 per row)
|
||||
for (int i = 0; i < game::GameHandler::MAIL_MAX_ATTACHMENTS; ++i) {
|
||||
if (i % 6 != 0) ImGui::SameLine();
|
||||
ImGui::PushID(i + 5000);
|
||||
const auto& att = attachments[i];
|
||||
if (att.occupied()) {
|
||||
// Show item with quality color border
|
||||
ImVec4 qualColor = ui::InventoryScreen::getQualityColor(att.item.quality);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(qualColor.x * 0.3f, qualColor.y * 0.3f, qualColor.z * 0.3f, 0.8f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(qualColor.x * 0.5f, qualColor.y * 0.5f, qualColor.z * 0.5f, 0.9f));
|
||||
|
||||
// Try to show icon
|
||||
VkDescriptorSet icon = inventoryScreen.getItemIcon(att.item.displayInfoId);
|
||||
bool clicked = false;
|
||||
if (icon) {
|
||||
clicked = ImGui::ImageButton("##att", (ImTextureID)icon, ImVec2(30, 30));
|
||||
} else {
|
||||
// Truncate name to fit
|
||||
std::string label = att.item.name.substr(0, 4);
|
||||
clicked = ImGui::Button(label.c_str(), ImVec2(36, 36));
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
|
||||
if (clicked) {
|
||||
gameHandler.detachMailAttachment(i);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::TextColored(qualColor, "%s", att.item.name.c_str());
|
||||
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Click to remove");
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
} else {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.15f, 0.15f, 0.5f));
|
||||
ImGui::Button("##empty", ImVec2(36, 36));
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("Money:");
|
||||
ImGui::SameLine(60);
|
||||
ImGui::SetNextItemWidth(60);
|
||||
|
|
@ -7429,7 +7477,8 @@ void GameScreen::renderMailComposeWindow(game::GameHandler& gameHandler) {
|
|||
static_cast<uint32_t>(mailComposeMoney_[1]) * 100 +
|
||||
static_cast<uint32_t>(mailComposeMoney_[2]);
|
||||
|
||||
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "Sending cost: 30c");
|
||||
uint32_t sendCost = attachCount > 0 ? static_cast<uint32_t>(30 * attachCount) : 30u;
|
||||
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "Sending cost: %uc", sendCost);
|
||||
|
||||
ImGui::Spacing();
|
||||
bool canSend = (strlen(mailRecipientBuffer_) > 0);
|
||||
|
|
|
|||
|
|
@ -1428,7 +1428,11 @@ void InventoryScreen::renderItemSlot(game::Inventory& inventory, const game::Ite
|
|||
" bagIndex=", bagIndex, " bagSlotIndex=", bagSlotIndex,
|
||||
" vendorMode=", vendorMode_,
|
||||
" bankOpen=", gameHandler_->isBankOpen());
|
||||
if (gameHandler_->isBankOpen() && kind == SlotKind::BACKPACK && backpackIndex >= 0) {
|
||||
if (gameHandler_->isMailComposeOpen() && kind == SlotKind::BACKPACK && backpackIndex >= 0) {
|
||||
gameHandler_->attachItemFromBackpack(backpackIndex);
|
||||
} else if (gameHandler_->isMailComposeOpen() && kind == SlotKind::BACKPACK && isBagSlot) {
|
||||
gameHandler_->attachItemFromBag(bagIndex, bagSlotIndex);
|
||||
} else if (gameHandler_->isBankOpen() && kind == SlotKind::BACKPACK && backpackIndex >= 0) {
|
||||
gameHandler_->depositItem(0xFF, static_cast<uint8_t>(23 + backpackIndex));
|
||||
} else if (gameHandler_->isBankOpen() && kind == SlotKind::BACKPACK && isBagSlot) {
|
||||
gameHandler_->depositItem(static_cast<uint8_t>(19 + bagIndex), static_cast<uint8_t>(bagSlotIndex));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue