mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43: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_; }
|
bool hasNewMail() const { return hasNewMail_; }
|
||||||
void closeMailbox();
|
void closeMailbox();
|
||||||
void sendMail(const std::string& recipient, const std::string& subject,
|
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)
|
// Mail attachments (max 12 per WotLK)
|
||||||
static constexpr int MAIL_MAX_ATTACHMENTS = 12;
|
static constexpr int MAIL_MAX_ATTACHMENTS = 12;
|
||||||
|
|
|
||||||
|
|
@ -261,7 +261,7 @@ public:
|
||||||
/** Build CMSG_SEND_MAIL */
|
/** Build CMSG_SEND_MAIL */
|
||||||
virtual network::Packet buildSendMail(uint64_t mailboxGuid, const std::string& recipient,
|
virtual network::Packet buildSendMail(uint64_t mailboxGuid, const std::string& recipient,
|
||||||
const std::string& subject, const std::string& body,
|
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 = {}) {
|
const std::vector<uint64_t>& itemGuids = {}) {
|
||||||
return SendMailPacket::build(mailboxGuid, recipient, subject, body, money, cod, 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 buildLeaveChannel(const std::string& channelName) override;
|
||||||
network::Packet buildSendMail(uint64_t mailboxGuid, const std::string& recipient,
|
network::Packet buildSendMail(uint64_t mailboxGuid, const std::string& recipient,
|
||||||
const std::string& subject, const std::string& body,
|
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;
|
const std::vector<uint64_t>& itemGuids = {}) override;
|
||||||
bool parseMailList(network::Packet& packet, std::vector<MailMessage>& inbox) override;
|
bool parseMailList(network::Packet& packet, std::vector<MailMessage>& inbox) override;
|
||||||
network::Packet buildMailTakeItem(uint64_t mailboxGuid, uint32_t mailId, uint32_t itemGuidLow) 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 subject;
|
||||||
std::string body;
|
std::string body;
|
||||||
uint32_t stationeryId = 0;
|
uint32_t stationeryId = 0;
|
||||||
uint32_t money = 0;
|
uint64_t money = 0;
|
||||||
uint32_t cod = 0; // Cash on delivery
|
uint64_t cod = 0; // Cash on delivery
|
||||||
uint32_t flags = 0;
|
uint32_t flags = 0;
|
||||||
float expirationTime = 0.0f;
|
float expirationTime = 0.0f;
|
||||||
uint32_t mailTemplateId = 0;
|
uint32_t mailTemplateId = 0;
|
||||||
|
|
@ -2517,7 +2517,7 @@ class SendMailPacket {
|
||||||
public:
|
public:
|
||||||
static network::Packet build(uint64_t mailboxGuid, const std::string& recipient,
|
static network::Packet build(uint64_t mailboxGuid, const std::string& recipient,
|
||||||
const std::string& subject, const std::string& body,
|
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 = {});
|
const std::vector<uint64_t>& itemGuids = {});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
|
|
|
||||||
|
|
@ -7447,6 +7447,84 @@ void Application::setOnlinePlayerEquipment(uint64_t guid,
|
||||||
|
|
||||||
charRenderer->setActiveGeosets(st.instanceId, geosets);
|
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) ---
|
// --- Textures (skin atlas compositing) ---
|
||||||
static constexpr const char* componentDirs[] = {
|
static constexpr const char* componentDirs[] = {
|
||||||
"ArmUpperTexture",
|
"ArmUpperTexture",
|
||||||
|
|
|
||||||
|
|
@ -24348,7 +24348,7 @@ void GameHandler::refreshMailList() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::sendMail(const std::string& recipient, const std::string& subject,
|
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) {
|
if (state != WorldState::IN_WORLD) {
|
||||||
LOG_WARNING("sendMail: not in world");
|
LOG_WARNING("sendMail: not in world");
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -1512,7 +1512,7 @@ network::Packet ClassicPacketParsers::buildSendMail(uint64_t mailboxGuid,
|
||||||
const std::string& recipient,
|
const std::string& recipient,
|
||||||
const std::string& subject,
|
const std::string& subject,
|
||||||
const std::string& body,
|
const std::string& body,
|
||||||
uint32_t money, uint32_t cod,
|
uint64_t money, uint64_t cod,
|
||||||
const std::vector<uint64_t>& itemGuids) {
|
const std::vector<uint64_t>& itemGuids) {
|
||||||
network::Packet packet(wireOpcode(Opcode::CMSG_SEND_MAIL));
|
network::Packet packet(wireOpcode(Opcode::CMSG_SEND_MAIL));
|
||||||
packet.writeUInt64(mailboxGuid);
|
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,
|
network::Packet SendMailPacket::build(uint64_t mailboxGuid, const std::string& recipient,
|
||||||
const std::string& subject, const std::string& body,
|
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) {
|
const std::vector<uint64_t>& itemGuids) {
|
||||||
// WotLK 3.3.5a format
|
// WotLK 3.3.5a format
|
||||||
network::Packet packet(wireOpcode(Opcode::CMSG_SEND_MAIL));
|
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.writeUInt8(i); // attachment slot index
|
||||||
packet.writeUInt64(itemGuids[i]);
|
packet.writeUInt64(itemGuids[i]);
|
||||||
}
|
}
|
||||||
packet.writeUInt32(money);
|
packet.writeUInt64(money);
|
||||||
packet.writeUInt32(cod);
|
packet.writeUInt64(cod);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5321,11 +5321,11 @@ bool PacketParsers::parseMailList(network::Packet& packet, std::vector<MailMessa
|
||||||
default: msg.senderEntry = packet.readUInt32(); break;
|
default: msg.senderEntry = packet.readUInt32(); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.cod = packet.readUInt32();
|
msg.cod = packet.readUInt64();
|
||||||
packet.readUInt32(); // item text id
|
packet.readUInt32(); // item text id
|
||||||
packet.readUInt32(); // unknown
|
packet.readUInt32(); // unknown
|
||||||
msg.stationeryId = packet.readUInt32();
|
msg.stationeryId = packet.readUInt32();
|
||||||
msg.money = packet.readUInt32();
|
msg.money = packet.readUInt64();
|
||||||
msg.flags = packet.readUInt32();
|
msg.flags = packet.readUInt32();
|
||||||
msg.expirationTime = packet.readFloat();
|
msg.expirationTime = packet.readFloat();
|
||||||
msg.mailTemplateId = packet.readUInt32();
|
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;
|
connected = true;
|
||||||
LOG_INFO("Connected to ", host, ":", port);
|
LOG_INFO("Connected to ", host, ":", port);
|
||||||
return true;
|
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;
|
connected = true;
|
||||||
LOG_INFO("Connected to world server: ", host, ":", port);
|
LOG_INFO("Connected to world server: ", host, ":", port);
|
||||||
startAsyncPump();
|
startAsyncPump();
|
||||||
|
|
|
||||||
|
|
@ -2290,6 +2290,8 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
||||||
|
|
||||||
// State tracking
|
// State tracking
|
||||||
VkPipeline currentPipeline = VK_NULL_HANDLE;
|
VkPipeline currentPipeline = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorSet currentMaterialSet = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorSet currentBoneSet = VK_NULL_HANDLE;
|
||||||
uint32_t frameIndex = vkCtx_->getCurrentFrame();
|
uint32_t frameIndex = vkCtx_->getCurrentFrame();
|
||||||
|
|
||||||
// Push constants struct matching m2.vert.glsl push_constant block
|
// 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;
|
instance.bonesDirty[frameIndex] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind bone descriptor set (set 2)
|
// Bind bone descriptor set (set 2) — skip if already bound
|
||||||
if (instance.boneSet[frameIndex]) {
|
if (instance.boneSet[frameIndex] && instance.boneSet[frameIndex] != currentBoneSet) {
|
||||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||||
pipelineLayout_, 2, 1, &instance.boneSet[frameIndex], 0, nullptr);
|
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
|
// Bind material descriptor set (set 1) — skip batch if missing
|
||||||
// to avoid inheriting a stale descriptor set from a prior renderer
|
// to avoid inheriting a stale descriptor set from a prior renderer
|
||||||
if (!batch.materialSet) continue;
|
if (!batch.materialSet) continue;
|
||||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
if (batch.materialSet != currentMaterialSet) {
|
||||||
pipelineLayout_, 1, 1, &batch.materialSet, 0, nullptr);
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||||
|
pipelineLayout_, 1, 1, &batch.materialSet, 0, nullptr);
|
||||||
|
currentMaterialSet = batch.materialSet;
|
||||||
|
}
|
||||||
|
|
||||||
// Push constants
|
// Push constants
|
||||||
M2PushConstants pc;
|
M2PushConstants pc;
|
||||||
|
|
@ -2598,8 +2604,10 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
||||||
currentModelId = UINT32_MAX;
|
currentModelId = UINT32_MAX;
|
||||||
currentModel = nullptr;
|
currentModel = nullptr;
|
||||||
currentModelValid = false;
|
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_;
|
currentPipeline = opaquePipeline_;
|
||||||
|
currentMaterialSet = VK_NULL_HANDLE;
|
||||||
|
currentBoneSet = VK_NULL_HANDLE;
|
||||||
|
|
||||||
for (const auto& entry : sortedVisible_) {
|
for (const auto& entry : sortedVisible_) {
|
||||||
if (entry.index >= instances.size()) continue;
|
if (entry.index >= instances.size()) continue;
|
||||||
|
|
@ -2647,9 +2655,10 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
||||||
bool needsBones = modelNeedsAnimation && !instance.boneMatrices.empty();
|
bool needsBones = modelNeedsAnimation && !instance.boneMatrices.empty();
|
||||||
if (needsBones && (!instance.boneBuffer[frameIndex] || !instance.boneSet[frameIndex])) continue;
|
if (needsBones && (!instance.boneBuffer[frameIndex] || !instance.boneSet[frameIndex])) continue;
|
||||||
bool useBones = needsBones;
|
bool useBones = needsBones;
|
||||||
if (useBones && instance.boneSet[frameIndex]) {
|
if (useBones && instance.boneSet[frameIndex] && instance.boneSet[frameIndex] != currentBoneSet) {
|
||||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||||
pipelineLayout_, 2, 1, &instance.boneSet[frameIndex], 0, nullptr);
|
pipelineLayout_, 2, 1, &instance.boneSet[frameIndex], 0, nullptr);
|
||||||
|
currentBoneSet = instance.boneSet[frameIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t desiredLOD = 0;
|
uint16_t desiredLOD = 0;
|
||||||
|
|
@ -2740,8 +2749,11 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!batch.materialSet) continue;
|
if (!batch.materialSet) continue;
|
||||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
if (batch.materialSet != currentMaterialSet) {
|
||||||
pipelineLayout_, 1, 1, &batch.materialSet, 0, nullptr);
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||||
|
pipelineLayout_, 1, 1, &batch.materialSet, 0, nullptr);
|
||||||
|
currentMaterialSet = batch.materialSet;
|
||||||
|
}
|
||||||
|
|
||||||
M2PushConstants pc;
|
M2PushConstants pc;
|
||||||
pc.model = instance.modelMatrix;
|
pc.model = instance.modelMatrix;
|
||||||
|
|
|
||||||
|
|
@ -13081,6 +13081,15 @@ void GameScreen::renderZoneToasts(float deltaTime) {
|
||||||
[](const ZoneToastEntry& e) { return e.age >= kZoneToastLifetime; }),
|
[](const ZoneToastEntry& e) { return e.age >= kZoneToastLifetime; }),
|
||||||
zoneToasts_.end());
|
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;
|
if (zoneToasts_.empty()) return;
|
||||||
|
|
||||||
auto* window = core::Application::getInstance().getWindow();
|
auto* window = core::Application::getInstance().getWindow();
|
||||||
|
|
@ -21462,11 +21471,14 @@ void GameScreen::renderMailWindow(game::GameHandler& gameHandler) {
|
||||||
|
|
||||||
// COD warning
|
// COD warning
|
||||||
if (mail.cod > 0) {
|
if (mail.cod > 0) {
|
||||||
uint32_t g = mail.cod / 10000;
|
uint64_t g = mail.cod / 10000;
|
||||||
uint32_t s = (mail.cod / 100) % 100;
|
uint64_t s = (mail.cod / 100) % 100;
|
||||||
uint32_t c = mail.cod % 100;
|
uint64_t c = mail.cod % 100;
|
||||||
ImGui::TextColored(kColorRed,
|
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
|
// Attachments
|
||||||
|
|
@ -21693,9 +21705,9 @@ void GameScreen::renderMailComposeWindow(game::GameHandler& gameHandler) {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Text("c");
|
ImGui::Text("c");
|
||||||
|
|
||||||
uint32_t totalMoney = static_cast<uint32_t>(mailComposeMoney_[0]) * 10000 +
|
uint64_t totalMoney = static_cast<uint64_t>(mailComposeMoney_[0]) * 10000 +
|
||||||
static_cast<uint32_t>(mailComposeMoney_[1]) * 100 +
|
static_cast<uint64_t>(mailComposeMoney_[1]) * 100 +
|
||||||
static_cast<uint32_t>(mailComposeMoney_[2]);
|
static_cast<uint64_t>(mailComposeMoney_[2]);
|
||||||
|
|
||||||
uint32_t sendCost = attachCount > 0 ? static_cast<uint32_t>(30 * attachCount) : 30u;
|
uint32_t sendCost = attachCount > 0 ? static_cast<uint32_t>(30 * attachCount) : 30u;
|
||||||
ImGui::TextColored(kColorGray, "Sending cost: %uc", sendCost);
|
ImGui::TextColored(kColorGray, "Sending cost: %uc", sendCost);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue