mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +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)>;
|
||||
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)
|
||||
* @param maxMessages Maximum number of messages to return (0 = all)
|
||||
|
|
@ -1049,6 +1053,7 @@ private:
|
|||
size_t maxChatHistory = 100; // Maximum chat messages to keep
|
||||
std::vector<std::string> joinedChannels_; // Active channel memberships
|
||||
ChatBubbleCallback chatBubbleCallback_;
|
||||
EmoteAnimCallback emoteAnimCallback_;
|
||||
|
||||
// Targeting
|
||||
uint64_t targetGuid = 0;
|
||||
|
|
|
|||
|
|
@ -126,6 +126,8 @@ public:
|
|||
bool isEmoteActive() const { return emoteActive; }
|
||||
static std::string getEmoteText(const std::string& emoteName, const std::string* targetName = nullptr);
|
||||
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
|
||||
void setTargetPosition(const glm::vec3* pos);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "game/warden_module.hpp"
|
||||
#include "game/opcodes.hpp"
|
||||
#include "game/update_field_table.hpp"
|
||||
#include "rendering/renderer.hpp"
|
||||
#include "pipeline/dbc_layout.hpp"
|
||||
#include "network/world_socket.hpp"
|
||||
#include "network/packet.hpp"
|
||||
|
|
@ -4021,23 +4022,35 @@ void GameHandler::handleTextEmote(network::Packet& packet) {
|
|||
queryPlayerName(data.senderGuid);
|
||||
}
|
||||
|
||||
// Build emote message text (server sends textEmoteId, we look up the text)
|
||||
// For now, just display a generic emote message
|
||||
// Resolve emote text from DBC using third-person "others see" templates
|
||||
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;
|
||||
chatMsg.type = ChatType::TEXT_EMOTE;
|
||||
chatMsg.language = ChatLanguage::COMMON;
|
||||
chatMsg.senderGuid = data.senderGuid;
|
||||
chatMsg.senderName = senderName;
|
||||
chatMsg.message = data.targetName.empty()
|
||||
? senderName + " performs an emote."
|
||||
: senderName + " performs an emote at " + data.targetName + ".";
|
||||
chatMsg.message = emoteText;
|
||||
|
||||
chatHistory.push_back(chatMsg);
|
||||
if (chatHistory.size() > maxChatHistory) {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -105,8 +105,11 @@ void DBCLayout::loadWotlkDefaults() {
|
|||
layouts_["Emotes"] = {{{ "ID", 0 }, { "AnimID", 2 }}};
|
||||
|
||||
// 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 },
|
||||
{ "SenderTargetTextID", 5 }, { "SenderNoTargetTextID", 9 }}};
|
||||
{ "OthersTargetTextID", 3 }, { "SenderTargetTextID", 5 },
|
||||
{ "OthersNoTargetTextID", 7 }, { "SenderNoTargetTextID", 9 }}};
|
||||
|
||||
// EmotesTextData.dbc
|
||||
layouts_["EmotesTextData"] = {{{ "ID", 0 }, { "Text", 1 }}};
|
||||
|
|
|
|||
|
|
@ -67,12 +67,15 @@ struct EmoteInfo {
|
|||
uint32_t animId = 0;
|
||||
uint32_t dbcId = 0; // EmotesText.dbc record ID (for CMSG_TEXT_EMOTE)
|
||||
bool loop = false;
|
||||
std::string textNoTarget;
|
||||
std::string textTarget;
|
||||
std::string textNoTarget; // sender sees, no target: "You dance."
|
||||
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;
|
||||
};
|
||||
|
||||
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 std::vector<std::string> parseEmoteCommands(const std::string& raw) {
|
||||
|
|
@ -101,26 +104,27 @@ static bool isLoopingEmote(const std::string& command) {
|
|||
static void loadFallbackEmotes() {
|
||||
if (!EMOTE_TABLE.empty()) return;
|
||||
EMOTE_TABLE = {
|
||||
{"wave", {67, 0, false, "You wave.", "You wave at %s.", "wave"}},
|
||||
{"bow", {66, 0, false, "You bow down graciously.", "You bow down before %s.", "bow"}},
|
||||
{"laugh", {70, 0, false, "You laugh.", "You laugh at %s.", "laugh"}},
|
||||
{"point", {84, 0, false, "You point over yonder.", "You point at %s.", "point"}},
|
||||
{"cheer", {68, 0, false, "You cheer!", "You cheer at %s.", "cheer"}},
|
||||
{"dance", {69, 0, true, "You burst into dance.", "You dance with %s.", "dance"}},
|
||||
{"kneel", {75, 0, false, "You kneel down.", "You kneel before %s.", "kneel"}},
|
||||
{"applaud", {80, 0, false, "You applaud. Bravo!", "You applaud at %s. Bravo!", "applaud"}},
|
||||
{"shout", {81, 0, false, "You shout.", "You shout at %s.", "shout"}},
|
||||
{"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.", "%s bows down graciously.", "%s bows down before %s.", "bow"}},
|
||||
{"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.", "%s points over yonder.", "%s points at %s.", "point"}},
|
||||
{"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.", "%s bursts into dance.", "%s dances with %s.", "dance"}},
|
||||
{"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!", "%s applauds. Bravo!", "%s applauds at %s. Bravo!", "applaud"}},
|
||||
{"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!",
|
||||
"With arms flapping, you strut around %s. Cluck, Cluck, Chicken!", "chicken"}},
|
||||
{"cry", {77, 0, false, "You cry.", "You cry on %s's shoulder.", "cry"}},
|
||||
{"kiss", {76, 0, false, "You blow a kiss into the wind.", "You blow a kiss to %s.", "kiss"}},
|
||||
{"roar", {74, 0, false, "You roar with bestial vigor. So fierce!", "You roar with bestial vigor at %s. So fierce!", "roar"}},
|
||||
{"salute", {113, 0, false, "You salute.", "You salute %s with respect.", "salute"}},
|
||||
{"rude", {73, 0, false, "You make a rude gesture.", "You make a rude gesture at %s.", "rude"}},
|
||||
{"flex", {82, 0, false, "You flex your muscles. Oooooh so strong!", "You flex at %s. Oooooh so strong!", "flex"}},
|
||||
{"shy", {83, 0, false, "You smile shyly.", "You smile shyly at %s.", "shy"}},
|
||||
{"beg", {79, 0, false, "You beg everyone around you. How pathetic.", "You beg %s. How pathetic.", "beg"}},
|
||||
{"eat", {61, 0, false, "You begin to eat.", "You begin to eat in front of %s.", "eat"}},
|
||||
"With arms flapping, you strut around %s. Cluck, Cluck, Chicken!",
|
||||
"%s struts around. Cluck, Cluck, Chicken!", "%s struts around %s. Cluck, Cluck, Chicken!", "chicken"}},
|
||||
{"cry", {77, 0, false, "You cry.", "You cry on %s's shoulder.", "%s cries.", "%s cries on %s's shoulder.", "cry"}},
|
||||
{"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"}},
|
||||
{"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"}},
|
||||
{"salute", {113, 0, false, "You salute.", "You salute %s with respect.", "%s salutes.", "%s salutes %s with respect.", "salute"}},
|
||||
{"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"}},
|
||||
{"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"}},
|
||||
{"shy", {83, 0, false, "You smile shyly.", "You smile shyly at %s.", "%s smiles shyly.", "%s smiles shyly at %s.", "shy"}},
|
||||
{"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
|
||||
}
|
||||
|
||||
uint32_t senderTargetTextId = emotesTextDbc->getUInt32(r, etL ? (*etL)["SenderTargetTextID"] : 5); // unisex, target, sender
|
||||
uint32_t senderNoTargetTextId = emotesTextDbc->getUInt32(r, etL ? (*etL)["SenderNoTargetTextID"] : 9); // unisex, no target, sender
|
||||
uint32_t senderTargetTextId = emotesTextDbc->getUInt32(r, etL ? (*etL)["SenderTargetTextID"] : 5);
|
||||
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 textNoTarget;
|
||||
if (auto it = textData.find(senderTargetTextId); it != textData.end()) {
|
||||
textTarget = it->second;
|
||||
}
|
||||
if (auto it = textData.find(senderNoTargetTextId); it != textData.end()) {
|
||||
textNoTarget = it->second;
|
||||
}
|
||||
std::string textTarget, textNoTarget, oTarget, oNoTarget;
|
||||
if (auto it = textData.find(senderTargetTextId); it != textData.end()) textTarget = it->second;
|
||||
if (auto it = textData.find(senderNoTargetTextId); it != textData.end()) textNoTarget = 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;
|
||||
|
||||
for (const std::string& cmd : parseEmoteCommands(cmdRaw)) {
|
||||
if (cmd.empty()) continue;
|
||||
|
|
@ -217,6 +220,8 @@ static void loadEmotesFromDbc() {
|
|||
info.loop = isLoopingEmote(cmd);
|
||||
info.textNoTarget = textNoTarget;
|
||||
info.textTarget = textTarget;
|
||||
info.othersNoTarget = oNoTarget;
|
||||
info.othersTarget = oTarget;
|
||||
info.command = cmd;
|
||||
EMOTE_TABLE.emplace(cmd, std::move(info));
|
||||
}
|
||||
|
|
@ -228,6 +233,14 @@ static void loadEmotesFromDbc() {
|
|||
} else {
|
||||
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;
|
||||
|
|
@ -1581,6 +1594,50 @@ uint32_t Renderer::getEmoteDbcId(const std::string& emoteName) {
|
|||
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) {
|
||||
targetPosition = pos;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue