mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-06 00:53:52 +00:00
Resolve emote text from DBC for other players' text emotes
Load third-person emote text templates (othersTarget/othersNoTarget) from EmotesText.dbc fields 3 and 7 alongside existing sender text. Build reverse lookup map from dbcId to EmoteInfo for incoming SMSG_TEXT_EMOTE resolution. Other players now show proper emote descriptions like "Player dances with Target" instead of generic "Player performs an emote" text.
This commit is contained in:
parent
a90c130d6e
commit
3acb42b363
5 changed files with 118 additions and 38 deletions
|
|
@ -239,6 +239,10 @@ public:
|
||||||
using ChatBubbleCallback = std::function<void(uint64_t, const std::string&, bool)>;
|
using ChatBubbleCallback = std::function<void(uint64_t, const std::string&, bool)>;
|
||||||
void setChatBubbleCallback(ChatBubbleCallback cb) { chatBubbleCallback_ = std::move(cb); }
|
void setChatBubbleCallback(ChatBubbleCallback cb) { chatBubbleCallback_ = std::move(cb); }
|
||||||
|
|
||||||
|
// Emote animation callback: (entityGuid, animationId)
|
||||||
|
using EmoteAnimCallback = std::function<void(uint64_t, uint32_t)>;
|
||||||
|
void setEmoteAnimCallback(EmoteAnimCallback cb) { emoteAnimCallback_ = std::move(cb); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get chat history (recent messages)
|
* Get chat history (recent messages)
|
||||||
* @param maxMessages Maximum number of messages to return (0 = all)
|
* @param maxMessages Maximum number of messages to return (0 = all)
|
||||||
|
|
@ -1049,6 +1053,7 @@ private:
|
||||||
size_t maxChatHistory = 100; // Maximum chat messages to keep
|
size_t maxChatHistory = 100; // Maximum chat messages to keep
|
||||||
std::vector<std::string> joinedChannels_; // Active channel memberships
|
std::vector<std::string> joinedChannels_; // Active channel memberships
|
||||||
ChatBubbleCallback chatBubbleCallback_;
|
ChatBubbleCallback chatBubbleCallback_;
|
||||||
|
EmoteAnimCallback emoteAnimCallback_;
|
||||||
|
|
||||||
// Targeting
|
// Targeting
|
||||||
uint64_t targetGuid = 0;
|
uint64_t targetGuid = 0;
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,8 @@ public:
|
||||||
bool isEmoteActive() const { return emoteActive; }
|
bool isEmoteActive() const { return emoteActive; }
|
||||||
static std::string getEmoteText(const std::string& emoteName, const std::string* targetName = nullptr);
|
static std::string getEmoteText(const std::string& emoteName, const std::string* targetName = nullptr);
|
||||||
static uint32_t getEmoteDbcId(const std::string& emoteName);
|
static uint32_t getEmoteDbcId(const std::string& emoteName);
|
||||||
|
static std::string getEmoteTextByDbcId(uint32_t dbcId, const std::string& senderName, const std::string* targetName = nullptr);
|
||||||
|
static uint32_t getEmoteAnimByDbcId(uint32_t dbcId);
|
||||||
|
|
||||||
// Targeting support
|
// Targeting support
|
||||||
void setTargetPosition(const glm::vec3* pos);
|
void setTargetPosition(const glm::vec3* pos);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include "game/warden_module.hpp"
|
#include "game/warden_module.hpp"
|
||||||
#include "game/opcodes.hpp"
|
#include "game/opcodes.hpp"
|
||||||
#include "game/update_field_table.hpp"
|
#include "game/update_field_table.hpp"
|
||||||
|
#include "rendering/renderer.hpp"
|
||||||
#include "pipeline/dbc_layout.hpp"
|
#include "pipeline/dbc_layout.hpp"
|
||||||
#include "network/world_socket.hpp"
|
#include "network/world_socket.hpp"
|
||||||
#include "network/packet.hpp"
|
#include "network/packet.hpp"
|
||||||
|
|
@ -4021,23 +4022,35 @@ void GameHandler::handleTextEmote(network::Packet& packet) {
|
||||||
queryPlayerName(data.senderGuid);
|
queryPlayerName(data.senderGuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build emote message text (server sends textEmoteId, we look up the text)
|
// Resolve emote text from DBC using third-person "others see" templates
|
||||||
// For now, just display a generic emote message
|
const std::string* targetPtr = data.targetName.empty() ? nullptr : &data.targetName;
|
||||||
|
std::string emoteText = rendering::Renderer::getEmoteTextByDbcId(data.textEmoteId, senderName, targetPtr);
|
||||||
|
if (emoteText.empty()) {
|
||||||
|
// Fallback if DBC lookup fails
|
||||||
|
emoteText = data.targetName.empty()
|
||||||
|
? senderName + " performs an emote."
|
||||||
|
: senderName + " performs an emote at " + data.targetName + ".";
|
||||||
|
}
|
||||||
|
|
||||||
MessageChatData chatMsg;
|
MessageChatData chatMsg;
|
||||||
chatMsg.type = ChatType::TEXT_EMOTE;
|
chatMsg.type = ChatType::TEXT_EMOTE;
|
||||||
chatMsg.language = ChatLanguage::COMMON;
|
chatMsg.language = ChatLanguage::COMMON;
|
||||||
chatMsg.senderGuid = data.senderGuid;
|
chatMsg.senderGuid = data.senderGuid;
|
||||||
chatMsg.senderName = senderName;
|
chatMsg.senderName = senderName;
|
||||||
chatMsg.message = data.targetName.empty()
|
chatMsg.message = emoteText;
|
||||||
? senderName + " performs an emote."
|
|
||||||
: senderName + " performs an emote at " + data.targetName + ".";
|
|
||||||
|
|
||||||
chatHistory.push_back(chatMsg);
|
chatHistory.push_back(chatMsg);
|
||||||
if (chatHistory.size() > maxChatHistory) {
|
if (chatHistory.size() > maxChatHistory) {
|
||||||
chatHistory.erase(chatHistory.begin());
|
chatHistory.erase(chatHistory.begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_INFO("TEXT_EMOTE from ", senderName, " (emoteId=", data.textEmoteId, ")");
|
// Trigger emote animation on sender's entity via callback
|
||||||
|
uint32_t animId = rendering::Renderer::getEmoteAnimByDbcId(data.textEmoteId);
|
||||||
|
if (animId != 0 && emoteAnimCallback_) {
|
||||||
|
emoteAnimCallback_(data.senderGuid, animId);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("TEXT_EMOTE from ", senderName, " (emoteId=", data.textEmoteId, ", anim=", animId, ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::joinChannel(const std::string& channelName, const std::string& password) {
|
void GameHandler::joinChannel(const std::string& channelName, const std::string& password) {
|
||||||
|
|
|
||||||
|
|
@ -105,8 +105,11 @@ void DBCLayout::loadWotlkDefaults() {
|
||||||
layouts_["Emotes"] = {{{ "ID", 0 }, { "AnimID", 2 }}};
|
layouts_["Emotes"] = {{{ "ID", 0 }, { "AnimID", 2 }}};
|
||||||
|
|
||||||
// EmotesText.dbc
|
// EmotesText.dbc
|
||||||
|
// Fields 3-18 are 16 EmotesTextData refs: [others+target, target+target, sender+target, ?,
|
||||||
|
// others+notarget, ?, sender+notarget, ?, female variants...]
|
||||||
layouts_["EmotesText"] = {{{ "Command", 1 }, { "EmoteRef", 2 },
|
layouts_["EmotesText"] = {{{ "Command", 1 }, { "EmoteRef", 2 },
|
||||||
{ "SenderTargetTextID", 5 }, { "SenderNoTargetTextID", 9 }}};
|
{ "OthersTargetTextID", 3 }, { "SenderTargetTextID", 5 },
|
||||||
|
{ "OthersNoTargetTextID", 7 }, { "SenderNoTargetTextID", 9 }}};
|
||||||
|
|
||||||
// EmotesTextData.dbc
|
// EmotesTextData.dbc
|
||||||
layouts_["EmotesTextData"] = {{{ "ID", 0 }, { "Text", 1 }}};
|
layouts_["EmotesTextData"] = {{{ "ID", 0 }, { "Text", 1 }}};
|
||||||
|
|
|
||||||
|
|
@ -67,12 +67,15 @@ struct EmoteInfo {
|
||||||
uint32_t animId = 0;
|
uint32_t animId = 0;
|
||||||
uint32_t dbcId = 0; // EmotesText.dbc record ID (for CMSG_TEXT_EMOTE)
|
uint32_t dbcId = 0; // EmotesText.dbc record ID (for CMSG_TEXT_EMOTE)
|
||||||
bool loop = false;
|
bool loop = false;
|
||||||
std::string textNoTarget;
|
std::string textNoTarget; // sender sees, no target: "You dance."
|
||||||
std::string textTarget;
|
std::string textTarget; // sender sees, with target: "You dance with %s."
|
||||||
|
std::string othersNoTarget; // others see, no target: "%s dances."
|
||||||
|
std::string othersTarget; // others see, with target: "%s dances with %s."
|
||||||
std::string command;
|
std::string command;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::unordered_map<std::string, EmoteInfo> EMOTE_TABLE;
|
static std::unordered_map<std::string, EmoteInfo> EMOTE_TABLE;
|
||||||
|
static std::unordered_map<uint32_t, const EmoteInfo*> EMOTE_BY_DBCID; // reverse lookup: dbcId → EmoteInfo*
|
||||||
static bool emoteTableLoaded = false;
|
static bool emoteTableLoaded = false;
|
||||||
|
|
||||||
static std::vector<std::string> parseEmoteCommands(const std::string& raw) {
|
static std::vector<std::string> parseEmoteCommands(const std::string& raw) {
|
||||||
|
|
@ -101,26 +104,27 @@ static bool isLoopingEmote(const std::string& command) {
|
||||||
static void loadFallbackEmotes() {
|
static void loadFallbackEmotes() {
|
||||||
if (!EMOTE_TABLE.empty()) return;
|
if (!EMOTE_TABLE.empty()) return;
|
||||||
EMOTE_TABLE = {
|
EMOTE_TABLE = {
|
||||||
{"wave", {67, 0, false, "You wave.", "You wave at %s.", "wave"}},
|
{"wave", {67, 0, false, "You wave.", "You wave at %s.", "%s waves.", "%s waves at %s.", "wave"}},
|
||||||
{"bow", {66, 0, false, "You bow down graciously.", "You bow down before %s.", "bow"}},
|
{"bow", {66, 0, false, "You bow down graciously.", "You bow down before %s.", "%s bows down graciously.", "%s bows down before %s.", "bow"}},
|
||||||
{"laugh", {70, 0, false, "You laugh.", "You laugh at %s.", "laugh"}},
|
{"laugh", {70, 0, false, "You laugh.", "You laugh at %s.", "%s laughs.", "%s laughs at %s.", "laugh"}},
|
||||||
{"point", {84, 0, false, "You point over yonder.", "You point at %s.", "point"}},
|
{"point", {84, 0, false, "You point over yonder.", "You point at %s.", "%s points over yonder.", "%s points at %s.", "point"}},
|
||||||
{"cheer", {68, 0, false, "You cheer!", "You cheer at %s.", "cheer"}},
|
{"cheer", {68, 0, false, "You cheer!", "You cheer at %s.", "%s cheers!", "%s cheers at %s.", "cheer"}},
|
||||||
{"dance", {69, 0, true, "You burst into dance.", "You dance with %s.", "dance"}},
|
{"dance", {69, 0, true, "You burst into dance.", "You dance with %s.", "%s bursts into dance.", "%s dances with %s.", "dance"}},
|
||||||
{"kneel", {75, 0, false, "You kneel down.", "You kneel before %s.", "kneel"}},
|
{"kneel", {75, 0, false, "You kneel down.", "You kneel before %s.", "%s kneels down.", "%s kneels before %s.", "kneel"}},
|
||||||
{"applaud", {80, 0, false, "You applaud. Bravo!", "You applaud at %s. Bravo!", "applaud"}},
|
{"applaud", {80, 0, false, "You applaud. Bravo!", "You applaud at %s. Bravo!", "%s applauds. Bravo!", "%s applauds at %s. Bravo!", "applaud"}},
|
||||||
{"shout", {81, 0, false, "You shout.", "You shout at %s.", "shout"}},
|
{"shout", {81, 0, false, "You shout.", "You shout at %s.", "%s shouts.", "%s shouts at %s.", "shout"}},
|
||||||
{"chicken", {78, 0, false, "With arms flapping, you strut around. Cluck, Cluck, Chicken!",
|
{"chicken", {78, 0, false, "With arms flapping, you strut around. Cluck, Cluck, Chicken!",
|
||||||
"With arms flapping, you strut around %s. Cluck, Cluck, Chicken!", "chicken"}},
|
"With arms flapping, you strut around %s. Cluck, Cluck, Chicken!",
|
||||||
{"cry", {77, 0, false, "You cry.", "You cry on %s's shoulder.", "cry"}},
|
"%s struts around. Cluck, Cluck, Chicken!", "%s struts around %s. Cluck, Cluck, Chicken!", "chicken"}},
|
||||||
{"kiss", {76, 0, false, "You blow a kiss into the wind.", "You blow a kiss to %s.", "kiss"}},
|
{"cry", {77, 0, false, "You cry.", "You cry on %s's shoulder.", "%s cries.", "%s cries on %s's shoulder.", "cry"}},
|
||||||
{"roar", {74, 0, false, "You roar with bestial vigor. So fierce!", "You roar with bestial vigor at %s. So fierce!", "roar"}},
|
{"kiss", {76, 0, false, "You blow a kiss into the wind.", "You blow a kiss to %s.", "%s blows a kiss into the wind.", "%s blows a kiss to %s.", "kiss"}},
|
||||||
{"salute", {113, 0, false, "You salute.", "You salute %s with respect.", "salute"}},
|
{"roar", {74, 0, false, "You roar with bestial vigor. So fierce!", "You roar with bestial vigor at %s. So fierce!", "%s roars with bestial vigor. So fierce!", "%s roars with bestial vigor at %s. So fierce!", "roar"}},
|
||||||
{"rude", {73, 0, false, "You make a rude gesture.", "You make a rude gesture at %s.", "rude"}},
|
{"salute", {113, 0, false, "You salute.", "You salute %s with respect.", "%s salutes.", "%s salutes %s with respect.", "salute"}},
|
||||||
{"flex", {82, 0, false, "You flex your muscles. Oooooh so strong!", "You flex at %s. Oooooh so strong!", "flex"}},
|
{"rude", {73, 0, false, "You make a rude gesture.", "You make a rude gesture at %s.", "%s makes a rude gesture.", "%s makes a rude gesture at %s.", "rude"}},
|
||||||
{"shy", {83, 0, false, "You smile shyly.", "You smile shyly at %s.", "shy"}},
|
{"flex", {82, 0, false, "You flex your muscles. Oooooh so strong!", "You flex at %s. Oooooh so strong!", "%s flexes. Oooooh so strong!", "%s flexes at %s. Oooooh so strong!", "flex"}},
|
||||||
{"beg", {79, 0, false, "You beg everyone around you. How pathetic.", "You beg %s. How pathetic.", "beg"}},
|
{"shy", {83, 0, false, "You smile shyly.", "You smile shyly at %s.", "%s smiles shyly.", "%s smiles shyly at %s.", "shy"}},
|
||||||
{"eat", {61, 0, false, "You begin to eat.", "You begin to eat in front of %s.", "eat"}},
|
{"beg", {79, 0, false, "You beg everyone around you. How pathetic.", "You beg %s. How pathetic.", "%s begs everyone around. How pathetic.", "%s begs %s. How pathetic.", "beg"}},
|
||||||
|
{"eat", {61, 0, false, "You begin to eat.", "You begin to eat in front of %s.", "%s begins to eat.", "%s begins to eat in front of %s.", "eat"}},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,17 +201,16 @@ static void loadEmotesFromDbc() {
|
||||||
animId = emoteRef; // fallback if EmotesText stores animation id directly
|
animId = emoteRef; // fallback if EmotesText stores animation id directly
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t senderTargetTextId = emotesTextDbc->getUInt32(r, etL ? (*etL)["SenderTargetTextID"] : 5); // unisex, target, sender
|
uint32_t senderTargetTextId = emotesTextDbc->getUInt32(r, etL ? (*etL)["SenderTargetTextID"] : 5);
|
||||||
uint32_t senderNoTargetTextId = emotesTextDbc->getUInt32(r, etL ? (*etL)["SenderNoTargetTextID"] : 9); // unisex, no target, sender
|
uint32_t senderNoTargetTextId = emotesTextDbc->getUInt32(r, etL ? (*etL)["SenderNoTargetTextID"] : 9);
|
||||||
|
uint32_t othersTargetTextId = emotesTextDbc->getUInt32(r, etL ? (*etL)["OthersTargetTextID"] : 3);
|
||||||
|
uint32_t othersNoTargetTextId = emotesTextDbc->getUInt32(r, etL ? (*etL)["OthersNoTargetTextID"] : 7);
|
||||||
|
|
||||||
std::string textTarget;
|
std::string textTarget, textNoTarget, oTarget, oNoTarget;
|
||||||
std::string textNoTarget;
|
if (auto it = textData.find(senderTargetTextId); it != textData.end()) textTarget = it->second;
|
||||||
if (auto it = textData.find(senderTargetTextId); it != textData.end()) {
|
if (auto it = textData.find(senderNoTargetTextId); it != textData.end()) textNoTarget = it->second;
|
||||||
textTarget = it->second;
|
if (auto it = textData.find(othersTargetTextId); it != textData.end()) oTarget = it->second;
|
||||||
}
|
if (auto it = textData.find(othersNoTargetTextId); it != textData.end()) oNoTarget = it->second;
|
||||||
if (auto it = textData.find(senderNoTargetTextId); it != textData.end()) {
|
|
||||||
textNoTarget = it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const std::string& cmd : parseEmoteCommands(cmdRaw)) {
|
for (const std::string& cmd : parseEmoteCommands(cmdRaw)) {
|
||||||
if (cmd.empty()) continue;
|
if (cmd.empty()) continue;
|
||||||
|
|
@ -217,6 +220,8 @@ static void loadEmotesFromDbc() {
|
||||||
info.loop = isLoopingEmote(cmd);
|
info.loop = isLoopingEmote(cmd);
|
||||||
info.textNoTarget = textNoTarget;
|
info.textNoTarget = textNoTarget;
|
||||||
info.textTarget = textTarget;
|
info.textTarget = textTarget;
|
||||||
|
info.othersNoTarget = oNoTarget;
|
||||||
|
info.othersTarget = oTarget;
|
||||||
info.command = cmd;
|
info.command = cmd;
|
||||||
EMOTE_TABLE.emplace(cmd, std::move(info));
|
EMOTE_TABLE.emplace(cmd, std::move(info));
|
||||||
}
|
}
|
||||||
|
|
@ -228,6 +233,14 @@ static void loadEmotesFromDbc() {
|
||||||
} else {
|
} else {
|
||||||
LOG_INFO("Emotes: loaded ", EMOTE_TABLE.size(), " commands from DBC");
|
LOG_INFO("Emotes: loaded ", EMOTE_TABLE.size(), " commands from DBC");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build reverse lookup by dbcId (only first command per emote needed)
|
||||||
|
EMOTE_BY_DBCID.clear();
|
||||||
|
for (auto& [cmd, info] : EMOTE_TABLE) {
|
||||||
|
if (info.dbcId != 0) {
|
||||||
|
EMOTE_BY_DBCID.emplace(info.dbcId, &info);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Renderer::Renderer() = default;
|
Renderer::Renderer() = default;
|
||||||
|
|
@ -1581,6 +1594,50 @@ uint32_t Renderer::getEmoteDbcId(const std::string& emoteName) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Renderer::getEmoteTextByDbcId(uint32_t dbcId, const std::string& senderName,
|
||||||
|
const std::string* targetName) {
|
||||||
|
loadEmotesFromDbc();
|
||||||
|
auto it = EMOTE_BY_DBCID.find(dbcId);
|
||||||
|
if (it == EMOTE_BY_DBCID.end()) return "";
|
||||||
|
|
||||||
|
const EmoteInfo& info = *it->second;
|
||||||
|
|
||||||
|
// Use "others see" text templates: "%s dances." / "%s dances with %s."
|
||||||
|
if (targetName && !targetName->empty()) {
|
||||||
|
if (!info.othersTarget.empty()) {
|
||||||
|
// Replace first %s with sender, second %s with target
|
||||||
|
std::string out;
|
||||||
|
out.reserve(info.othersTarget.size() + senderName.size() + targetName->size());
|
||||||
|
bool firstReplaced = false;
|
||||||
|
for (size_t i = 0; i < info.othersTarget.size(); ++i) {
|
||||||
|
if (info.othersTarget[i] == '%' && i + 1 < info.othersTarget.size() && info.othersTarget[i + 1] == 's') {
|
||||||
|
out += firstReplaced ? *targetName : senderName;
|
||||||
|
firstReplaced = true;
|
||||||
|
++i;
|
||||||
|
} else {
|
||||||
|
out.push_back(info.othersTarget[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
return senderName + " " + info.command + "s at " + *targetName + ".";
|
||||||
|
} else {
|
||||||
|
if (!info.othersNoTarget.empty()) {
|
||||||
|
return replacePlaceholders(info.othersNoTarget, &senderName);
|
||||||
|
}
|
||||||
|
return senderName + " " + info.command + "s.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Renderer::getEmoteAnimByDbcId(uint32_t dbcId) {
|
||||||
|
loadEmotesFromDbc();
|
||||||
|
auto it = EMOTE_BY_DBCID.find(dbcId);
|
||||||
|
if (it != EMOTE_BY_DBCID.end()) {
|
||||||
|
return it->second->animId;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void Renderer::setTargetPosition(const glm::vec3* pos) {
|
void Renderer::setTargetPosition(const glm::vec3* pos) {
|
||||||
targetPosition = pos;
|
targetPosition = pos;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue