mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-28 09:33:52 +00:00
fix: mail money uint64, other-player cape textures, zone toast dedup, TCP_NODELAY
Mail: change money/COD fields from uint32 to uint64 in CMSG_SEND_MAIL and SMSG_MAIL_LIST_RESULT for WotLK 3.3.5a. Classic keeps uint32 on the wire. Fixes money truncation and packet misalignment causing mail failures. Other-player capes: add cape texture loading to setOnlinePlayerEquipment(). The cape geoset was enabled but no texture was loaded, leaving capes blank. Now mirrors the local-player path: looks up ItemDisplayInfo.dbc, finds cape texture candidates, applies via setGroupTextureOverride/setTextureSlotOverride. Zone toasts: suppress duplicate zone toast when the zone text overlay is already showing the same zone name. Fixes double "Entering: Stormwind City". Network: enable TCP_NODELAY on both auth and world sockets after connect(), disabling Nagle's algorithm to eliminate up to 200ms buffering delay on small packets (movement, spell casts, chat). Rendering: track material and bone descriptor sets in M2 renderer to skip redundant vkCmdBindDescriptorSets calls between batches sharing same textures.
This commit is contained in:
parent
6b1c728377
commit
50a3eb7f07
12 changed files with 141 additions and 28 deletions
|
|
@ -2172,7 +2172,7 @@ public:
|
|||
bool hasNewMail() const { return hasNewMail_; }
|
||||
void closeMailbox();
|
||||
void sendMail(const std::string& recipient, const std::string& subject,
|
||||
const std::string& body, uint32_t money, uint32_t cod = 0);
|
||||
const std::string& body, uint64_t money, uint64_t cod = 0);
|
||||
|
||||
// Mail attachments (max 12 per WotLK)
|
||||
static constexpr int MAIL_MAX_ATTACHMENTS = 12;
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ public:
|
|||
/** Build CMSG_SEND_MAIL */
|
||||
virtual network::Packet buildSendMail(uint64_t mailboxGuid, const std::string& recipient,
|
||||
const std::string& subject, const std::string& body,
|
||||
uint32_t money, uint32_t cod,
|
||||
uint64_t money, uint64_t cod,
|
||||
const std::vector<uint64_t>& itemGuids = {}) {
|
||||
return SendMailPacket::build(mailboxGuid, recipient, subject, body, money, cod, itemGuids);
|
||||
}
|
||||
|
|
@ -420,7 +420,7 @@ public:
|
|||
network::Packet buildLeaveChannel(const std::string& channelName) override;
|
||||
network::Packet buildSendMail(uint64_t mailboxGuid, const std::string& recipient,
|
||||
const std::string& subject, const std::string& body,
|
||||
uint32_t money, uint32_t cod,
|
||||
uint64_t money, uint64_t cod,
|
||||
const std::vector<uint64_t>& itemGuids = {}) override;
|
||||
bool parseMailList(network::Packet& packet, std::vector<MailMessage>& inbox) override;
|
||||
network::Packet buildMailTakeItem(uint64_t mailboxGuid, uint32_t mailId, uint32_t itemGuidLow) override;
|
||||
|
|
|
|||
|
|
@ -2497,8 +2497,8 @@ struct MailMessage {
|
|||
std::string subject;
|
||||
std::string body;
|
||||
uint32_t stationeryId = 0;
|
||||
uint32_t money = 0;
|
||||
uint32_t cod = 0; // Cash on delivery
|
||||
uint64_t money = 0;
|
||||
uint64_t cod = 0; // Cash on delivery
|
||||
uint32_t flags = 0;
|
||||
float expirationTime = 0.0f;
|
||||
uint32_t mailTemplateId = 0;
|
||||
|
|
@ -2517,7 +2517,7 @@ class SendMailPacket {
|
|||
public:
|
||||
static network::Packet build(uint64_t mailboxGuid, const std::string& recipient,
|
||||
const std::string& subject, const std::string& body,
|
||||
uint32_t money, uint32_t cod,
|
||||
uint64_t money, uint64_t cod,
|
||||
const std::vector<uint64_t>& itemGuids = {});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <cerrno>
|
||||
|
|
|
|||
|
|
@ -7447,6 +7447,84 @@ void Application::setOnlinePlayerEquipment(uint64_t guid,
|
|||
|
||||
charRenderer->setActiveGeosets(st.instanceId, geosets);
|
||||
|
||||
// --- Cape texture (group 15 / texture type 2) ---
|
||||
// The geoset above enables the cape mesh, but without a texture it renders blank.
|
||||
if (hasInvType({16})) {
|
||||
// Back/cloak is WoW equipment slot 14 (BACK) in the 19-element array.
|
||||
uint32_t capeDid = displayInfoIds[14];
|
||||
if (capeDid != 0) {
|
||||
int32_t capeRecIdx = displayInfoDbc->findRecordById(capeDid);
|
||||
if (capeRecIdx >= 0) {
|
||||
const uint32_t leftTexField = idiL ? (*idiL)["LeftModelTexture"] : 3u;
|
||||
std::string capeName = displayInfoDbc->getString(
|
||||
static_cast<uint32_t>(capeRecIdx), leftTexField);
|
||||
|
||||
if (!capeName.empty()) {
|
||||
std::replace(capeName.begin(), capeName.end(), '/', '\\');
|
||||
|
||||
auto hasBlpExt = [](const std::string& p) {
|
||||
if (p.size() < 4) return false;
|
||||
std::string ext = p.substr(p.size() - 4);
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return ext == ".blp";
|
||||
};
|
||||
|
||||
const bool hasDir = (capeName.find('\\') != std::string::npos);
|
||||
const bool hasExt = hasBlpExt(capeName);
|
||||
|
||||
std::vector<std::string> capeCandidates;
|
||||
auto addCapeCandidate = [&](const std::string& p) {
|
||||
if (p.empty()) return;
|
||||
if (std::find(capeCandidates.begin(), capeCandidates.end(), p) == capeCandidates.end()) {
|
||||
capeCandidates.push_back(p);
|
||||
}
|
||||
};
|
||||
|
||||
if (hasDir) {
|
||||
addCapeCandidate(capeName);
|
||||
if (!hasExt) addCapeCandidate(capeName + ".blp");
|
||||
} else {
|
||||
std::string baseObj = "Item\\ObjectComponents\\Cape\\" + capeName;
|
||||
std::string baseTex = "Item\\TextureComponents\\Cape\\" + capeName;
|
||||
addCapeCandidate(baseObj);
|
||||
addCapeCandidate(baseTex);
|
||||
if (!hasExt) {
|
||||
addCapeCandidate(baseObj + ".blp");
|
||||
addCapeCandidate(baseTex + ".blp");
|
||||
}
|
||||
addCapeCandidate(baseObj + (st.genderId == 1 ? "_F.blp" : "_M.blp"));
|
||||
addCapeCandidate(baseObj + "_U.blp");
|
||||
addCapeCandidate(baseTex + (st.genderId == 1 ? "_F.blp" : "_M.blp"));
|
||||
addCapeCandidate(baseTex + "_U.blp");
|
||||
}
|
||||
|
||||
const rendering::VkTexture* whiteTex = charRenderer->loadTexture("");
|
||||
rendering::VkTexture* capeTexture = nullptr;
|
||||
for (const auto& candidate : capeCandidates) {
|
||||
rendering::VkTexture* tex = charRenderer->loadTexture(candidate);
|
||||
if (tex && tex != whiteTex) {
|
||||
capeTexture = tex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (capeTexture) {
|
||||
charRenderer->setGroupTextureOverride(st.instanceId, 15, capeTexture);
|
||||
if (const auto* md = charRenderer->getModelData(st.modelId)) {
|
||||
for (size_t ti = 0; ti < md->textures.size(); ti++) {
|
||||
if (md->textures[ti].type == 2) {
|
||||
charRenderer->setTextureSlotOverride(
|
||||
st.instanceId, static_cast<uint16_t>(ti), capeTexture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Textures (skin atlas compositing) ---
|
||||
static constexpr const char* componentDirs[] = {
|
||||
"ArmUpperTexture",
|
||||
|
|
|
|||
|
|
@ -24348,7 +24348,7 @@ void GameHandler::refreshMailList() {
|
|||
}
|
||||
|
||||
void GameHandler::sendMail(const std::string& recipient, const std::string& subject,
|
||||
const std::string& body, uint32_t money, uint32_t cod) {
|
||||
const std::string& body, uint64_t money, uint64_t cod) {
|
||||
if (state != WorldState::IN_WORLD) {
|
||||
LOG_WARNING("sendMail: not in world");
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1512,7 +1512,7 @@ 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,
|
||||
uint64_t money, uint64_t cod,
|
||||
const std::vector<uint64_t>& itemGuids) {
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_SEND_MAIL));
|
||||
packet.writeUInt64(mailboxGuid);
|
||||
|
|
|
|||
|
|
@ -5230,7 +5230,7 @@ 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,
|
||||
uint64_t money, uint64_t cod,
|
||||
const std::vector<uint64_t>& itemGuids) {
|
||||
// WotLK 3.3.5a format
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_SEND_MAIL));
|
||||
|
|
@ -5246,8 +5246,8 @@ network::Packet SendMailPacket::build(uint64_t mailboxGuid, const std::string& r
|
|||
packet.writeUInt8(i); // attachment slot index
|
||||
packet.writeUInt64(itemGuids[i]);
|
||||
}
|
||||
packet.writeUInt32(money);
|
||||
packet.writeUInt32(cod);
|
||||
packet.writeUInt64(money);
|
||||
packet.writeUInt64(cod);
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
|
@ -5321,11 +5321,11 @@ bool PacketParsers::parseMailList(network::Packet& packet, std::vector<MailMessa
|
|||
default: msg.senderEntry = packet.readUInt32(); break;
|
||||
}
|
||||
|
||||
msg.cod = packet.readUInt32();
|
||||
msg.cod = packet.readUInt64();
|
||||
packet.readUInt32(); // item text id
|
||||
packet.readUInt32(); // unknown
|
||||
msg.stationeryId = packet.readUInt32();
|
||||
msg.money = packet.readUInt32();
|
||||
msg.money = packet.readUInt64();
|
||||
msg.flags = packet.readUInt32();
|
||||
msg.expirationTime = packet.readFloat();
|
||||
msg.mailTemplateId = packet.readUInt32();
|
||||
|
|
|
|||
|
|
@ -86,6 +86,11 @@ bool TCPSocket::connect(const std::string& host, uint16_t port) {
|
|||
}
|
||||
}
|
||||
|
||||
// Disable Nagle's algorithm — send small packets immediately.
|
||||
int one = 1;
|
||||
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY,
|
||||
reinterpret_cast<const char*>(&one), sizeof(one));
|
||||
|
||||
connected = true;
|
||||
LOG_INFO("Connected to ", host, ":", port);
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -220,6 +220,11 @@ bool WorldSocket::connect(const std::string& host, uint16_t port) {
|
|||
}
|
||||
}
|
||||
|
||||
// Disable Nagle's algorithm — send small packets immediately.
|
||||
int one = 1;
|
||||
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY,
|
||||
reinterpret_cast<const char*>(&one), sizeof(one));
|
||||
|
||||
connected = true;
|
||||
LOG_INFO("Connected to world server: ", host, ":", port);
|
||||
startAsyncPump();
|
||||
|
|
|
|||
|
|
@ -2290,6 +2290,8 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
|
||||
// State tracking
|
||||
VkPipeline currentPipeline = VK_NULL_HANDLE;
|
||||
VkDescriptorSet currentMaterialSet = VK_NULL_HANDLE;
|
||||
VkDescriptorSet currentBoneSet = VK_NULL_HANDLE;
|
||||
uint32_t frameIndex = vkCtx_->getCurrentFrame();
|
||||
|
||||
// Push constants struct matching m2.vert.glsl push_constant block
|
||||
|
|
@ -2397,10 +2399,11 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
instance.bonesDirty[frameIndex] = false;
|
||||
}
|
||||
|
||||
// Bind bone descriptor set (set 2)
|
||||
if (instance.boneSet[frameIndex]) {
|
||||
// Bind bone descriptor set (set 2) — skip if already bound
|
||||
if (instance.boneSet[frameIndex] && instance.boneSet[frameIndex] != currentBoneSet) {
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipelineLayout_, 2, 1, &instance.boneSet[frameIndex], 0, nullptr);
|
||||
currentBoneSet = instance.boneSet[frameIndex];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2568,8 +2571,11 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
// Bind material descriptor set (set 1) — skip batch if missing
|
||||
// to avoid inheriting a stale descriptor set from a prior renderer
|
||||
if (!batch.materialSet) continue;
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipelineLayout_, 1, 1, &batch.materialSet, 0, nullptr);
|
||||
if (batch.materialSet != currentMaterialSet) {
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipelineLayout_, 1, 1, &batch.materialSet, 0, nullptr);
|
||||
currentMaterialSet = batch.materialSet;
|
||||
}
|
||||
|
||||
// Push constants
|
||||
M2PushConstants pc;
|
||||
|
|
@ -2598,8 +2604,10 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
currentModelId = UINT32_MAX;
|
||||
currentModel = nullptr;
|
||||
currentModelValid = false;
|
||||
// Reset pipeline to opaque so the first transparent bind always sets explicitly
|
||||
// Reset state so the first transparent bind always sets explicitly
|
||||
currentPipeline = opaquePipeline_;
|
||||
currentMaterialSet = VK_NULL_HANDLE;
|
||||
currentBoneSet = VK_NULL_HANDLE;
|
||||
|
||||
for (const auto& entry : sortedVisible_) {
|
||||
if (entry.index >= instances.size()) continue;
|
||||
|
|
@ -2647,9 +2655,10 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
bool needsBones = modelNeedsAnimation && !instance.boneMatrices.empty();
|
||||
if (needsBones && (!instance.boneBuffer[frameIndex] || !instance.boneSet[frameIndex])) continue;
|
||||
bool useBones = needsBones;
|
||||
if (useBones && instance.boneSet[frameIndex]) {
|
||||
if (useBones && instance.boneSet[frameIndex] && instance.boneSet[frameIndex] != currentBoneSet) {
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipelineLayout_, 2, 1, &instance.boneSet[frameIndex], 0, nullptr);
|
||||
currentBoneSet = instance.boneSet[frameIndex];
|
||||
}
|
||||
|
||||
uint16_t desiredLOD = 0;
|
||||
|
|
@ -2740,8 +2749,11 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
}
|
||||
|
||||
if (!batch.materialSet) continue;
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipelineLayout_, 1, 1, &batch.materialSet, 0, nullptr);
|
||||
if (batch.materialSet != currentMaterialSet) {
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipelineLayout_, 1, 1, &batch.materialSet, 0, nullptr);
|
||||
currentMaterialSet = batch.materialSet;
|
||||
}
|
||||
|
||||
M2PushConstants pc;
|
||||
pc.model = instance.modelMatrix;
|
||||
|
|
|
|||
|
|
@ -13081,6 +13081,15 @@ void GameScreen::renderZoneToasts(float deltaTime) {
|
|||
[](const ZoneToastEntry& e) { return e.age >= kZoneToastLifetime; }),
|
||||
zoneToasts_.end());
|
||||
|
||||
// Suppress toasts while the zone text overlay is showing the same zone —
|
||||
// avoids duplicate "Entering: Stormwind City" messages.
|
||||
if (zoneTextTimer_ > 0.0f) {
|
||||
zoneToasts_.erase(
|
||||
std::remove_if(zoneToasts_.begin(), zoneToasts_.end(),
|
||||
[this](const ZoneToastEntry& e) { return e.zoneName == zoneTextName_; }),
|
||||
zoneToasts_.end());
|
||||
}
|
||||
|
||||
if (zoneToasts_.empty()) return;
|
||||
|
||||
auto* window = core::Application::getInstance().getWindow();
|
||||
|
|
@ -21462,11 +21471,14 @@ void GameScreen::renderMailWindow(game::GameHandler& gameHandler) {
|
|||
|
||||
// COD warning
|
||||
if (mail.cod > 0) {
|
||||
uint32_t g = mail.cod / 10000;
|
||||
uint32_t s = (mail.cod / 100) % 100;
|
||||
uint32_t c = mail.cod % 100;
|
||||
uint64_t g = mail.cod / 10000;
|
||||
uint64_t s = (mail.cod / 100) % 100;
|
||||
uint64_t c = mail.cod % 100;
|
||||
ImGui::TextColored(kColorRed,
|
||||
"COD: %ug %us %uc (you pay this to take items)", g, s, c);
|
||||
"COD: %llug %llus %lluc (you pay this to take items)",
|
||||
static_cast<unsigned long long>(g),
|
||||
static_cast<unsigned long long>(s),
|
||||
static_cast<unsigned long long>(c));
|
||||
}
|
||||
|
||||
// Attachments
|
||||
|
|
@ -21693,9 +21705,9 @@ void GameScreen::renderMailComposeWindow(game::GameHandler& gameHandler) {
|
|||
ImGui::SameLine();
|
||||
ImGui::Text("c");
|
||||
|
||||
uint32_t totalMoney = static_cast<uint32_t>(mailComposeMoney_[0]) * 10000 +
|
||||
static_cast<uint32_t>(mailComposeMoney_[1]) * 100 +
|
||||
static_cast<uint32_t>(mailComposeMoney_[2]);
|
||||
uint64_t totalMoney = static_cast<uint64_t>(mailComposeMoney_[0]) * 10000 +
|
||||
static_cast<uint64_t>(mailComposeMoney_[1]) * 100 +
|
||||
static_cast<uint64_t>(mailComposeMoney_[2]);
|
||||
|
||||
uint32_t sendCost = attachCount > 0 ? static_cast<uint32_t>(30 * attachCount) : 30u;
|
||||
ImGui::TextColored(kColorGray, "Sending cost: %uc", sendCost);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue