mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-03 08:03:50 +00:00
feat: show quality-coloured loot toast when items are received
SMSG_ITEM_PUSH_RESULT now fires a new ItemLootCallback that game_screen.cpp uses to push a compact slide-in toast at the bottom-left of the screen. Each toast: - Shows a quality-tinted left accent bar (grey/white/green/blue/ purple/orange matching WoW quality colours) - Displays "Loot: <item name>" with the name in quality colour - Appends " x<N>" for stacked pickups - Coalesces repeated pickups of the same item (adds count, resets timer) - Stacks up to 5 entries, 3 s lifetime with 0.15 s slide-in and 0.7 s fade-out
This commit is contained in:
parent
129fa84fe3
commit
42d66bc876
4 changed files with 132 additions and 0 deletions
|
|
@ -1494,6 +1494,10 @@ public:
|
||||||
using PvpHonorCallback = std::function<void(uint32_t honorAmount, uint64_t victimGuid, uint32_t victimRank)>;
|
using PvpHonorCallback = std::function<void(uint32_t honorAmount, uint64_t victimGuid, uint32_t victimRank)>;
|
||||||
void setPvpHonorCallback(PvpHonorCallback cb) { pvpHonorCallback_ = std::move(cb); }
|
void setPvpHonorCallback(PvpHonorCallback cb) { pvpHonorCallback_ = std::move(cb); }
|
||||||
|
|
||||||
|
// Item looted / received callback (SMSG_ITEM_PUSH_RESULT when showInChat is set)
|
||||||
|
using ItemLootCallback = std::function<void(uint32_t itemId, uint32_t count, uint32_t quality, const std::string& name)>;
|
||||||
|
void setItemLootCallback(ItemLootCallback cb) { itemLootCallback_ = std::move(cb); }
|
||||||
|
|
||||||
// Quest turn-in completion callback
|
// Quest turn-in completion callback
|
||||||
using QuestCompleteCallback = std::function<void(uint32_t questId, const std::string& questTitle)>;
|
using QuestCompleteCallback = std::function<void(uint32_t questId, const std::string& questTitle)>;
|
||||||
void setQuestCompleteCallback(QuestCompleteCallback cb) { questCompleteCallback_ = std::move(cb); }
|
void setQuestCompleteCallback(QuestCompleteCallback cb) { questCompleteCallback_ = std::move(cb); }
|
||||||
|
|
@ -2838,6 +2842,9 @@ private:
|
||||||
// ---- PvP honor credit callback ----
|
// ---- PvP honor credit callback ----
|
||||||
PvpHonorCallback pvpHonorCallback_;
|
PvpHonorCallback pvpHonorCallback_;
|
||||||
|
|
||||||
|
// ---- Item loot callback ----
|
||||||
|
ItemLootCallback itemLootCallback_;
|
||||||
|
|
||||||
// ---- Quest completion callback ----
|
// ---- Quest completion callback ----
|
||||||
QuestCompleteCallback questCompleteCallback_;
|
QuestCompleteCallback questCompleteCallback_;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -574,6 +574,19 @@ private:
|
||||||
bool pvpHonorCallbackSet_ = false;
|
bool pvpHonorCallbackSet_ = false;
|
||||||
void renderPvpHonorToasts();
|
void renderPvpHonorToasts();
|
||||||
|
|
||||||
|
// Item loot toast — quality-coloured popup when an item is received
|
||||||
|
struct ItemLootToastEntry {
|
||||||
|
uint32_t itemId = 0;
|
||||||
|
uint32_t count = 0;
|
||||||
|
uint32_t quality = 1; // 0=grey,1=white,2=green,3=blue,4=purple,5=orange
|
||||||
|
std::string name;
|
||||||
|
float age = 0.0f;
|
||||||
|
};
|
||||||
|
static constexpr float ITEM_LOOT_TOAST_DURATION = 3.0f;
|
||||||
|
std::vector<ItemLootToastEntry> itemLootToasts_;
|
||||||
|
bool itemLootCallbackSet_ = false;
|
||||||
|
void renderItemLootToasts();
|
||||||
|
|
||||||
// Zone discovery text ("Entering: <ZoneName>")
|
// Zone discovery text ("Entering: <ZoneName>")
|
||||||
static constexpr float ZONE_TEXT_DURATION = 5.0f;
|
static constexpr float ZONE_TEXT_DURATION = 5.0f;
|
||||||
float zoneTextTimer_ = 0.0f;
|
float zoneTextTimer_ = 0.0f;
|
||||||
|
|
|
||||||
|
|
@ -1701,12 +1701,17 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
queryItemInfo(itemId, 0);
|
queryItemInfo(itemId, 0);
|
||||||
if (showInChat) {
|
if (showInChat) {
|
||||||
std::string itemName = "item #" + std::to_string(itemId);
|
std::string itemName = "item #" + std::to_string(itemId);
|
||||||
|
uint32_t quality = 1; // white default
|
||||||
if (const ItemQueryResponseData* info = getItemInfo(itemId)) {
|
if (const ItemQueryResponseData* info = getItemInfo(itemId)) {
|
||||||
if (!info->name.empty()) itemName = info->name;
|
if (!info->name.empty()) itemName = info->name;
|
||||||
|
quality = info->quality;
|
||||||
}
|
}
|
||||||
std::string msg = "Received: " + itemName;
|
std::string msg = "Received: " + itemName;
|
||||||
if (count > 1) msg += " x" + std::to_string(count);
|
if (count > 1) msg += " x" + std::to_string(count);
|
||||||
addSystemChatMessage(msg);
|
addSystemChatMessage(msg);
|
||||||
|
if (itemLootCallback_) {
|
||||||
|
itemLootCallback_(itemId, count, quality, itemName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
LOG_INFO("Item push: itemId=", itemId, " count=", count,
|
LOG_INFO("Item push: itemId=", itemId, " count=", count,
|
||||||
" showInChat=", static_cast<int>(showInChat));
|
" showInChat=", static_cast<int>(showInChat));
|
||||||
|
|
|
||||||
|
|
@ -359,6 +359,25 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
||||||
pvpHonorCallbackSet_ = true;
|
pvpHonorCallbackSet_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up item loot toast callback (once)
|
||||||
|
if (!itemLootCallbackSet_) {
|
||||||
|
gameHandler.setItemLootCallback([this](uint32_t itemId, uint32_t count,
|
||||||
|
uint32_t quality, const std::string& name) {
|
||||||
|
// Coalesce: if same item already in queue, bump count and reset age
|
||||||
|
for (auto& t : itemLootToasts_) {
|
||||||
|
if (t.itemId == itemId) {
|
||||||
|
t.count += count;
|
||||||
|
t.age = 0.0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (itemLootToasts_.size() >= 5)
|
||||||
|
itemLootToasts_.erase(itemLootToasts_.begin());
|
||||||
|
itemLootToasts_.push_back({itemId, count, quality, name, 0.0f});
|
||||||
|
});
|
||||||
|
itemLootCallbackSet_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Set up UI error frame callback (once)
|
// Set up UI error frame callback (once)
|
||||||
if (!uiErrorCallbackSet_) {
|
if (!uiErrorCallbackSet_) {
|
||||||
gameHandler.setUIErrorCallback([this](const std::string& msg) {
|
gameHandler.setUIErrorCallback([this](const std::string& msg) {
|
||||||
|
|
@ -692,6 +711,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
||||||
renderQuestProgressToasts();
|
renderQuestProgressToasts();
|
||||||
renderPlayerLevelUpToasts(gameHandler);
|
renderPlayerLevelUpToasts(gameHandler);
|
||||||
renderPvpHonorToasts();
|
renderPvpHonorToasts();
|
||||||
|
renderItemLootToasts();
|
||||||
renderZoneText();
|
renderZoneText();
|
||||||
|
|
||||||
// World map (M key toggle handled inside)
|
// World map (M key toggle handled inside)
|
||||||
|
|
@ -18226,6 +18246,93 @@ void GameScreen::renderQuestProgressToasts() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Item loot toasts — quality-coloured strip at bottom-left when item received
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void GameScreen::renderItemLootToasts() {
|
||||||
|
if (itemLootToasts_.empty()) return;
|
||||||
|
|
||||||
|
float dt = ImGui::GetIO().DeltaTime;
|
||||||
|
for (auto& t : itemLootToasts_) t.age += dt;
|
||||||
|
itemLootToasts_.erase(
|
||||||
|
std::remove_if(itemLootToasts_.begin(), itemLootToasts_.end(),
|
||||||
|
[](const ItemLootToastEntry& t) { return t.age >= ITEM_LOOT_TOAST_DURATION; }),
|
||||||
|
itemLootToasts_.end());
|
||||||
|
if (itemLootToasts_.empty()) return;
|
||||||
|
|
||||||
|
ImVec2 displaySize = ImGui::GetIO().DisplaySize;
|
||||||
|
float screenH = displaySize.y > 0.0f ? displaySize.y : 720.0f;
|
||||||
|
|
||||||
|
// Quality colours (matching WoW convention)
|
||||||
|
static const ImU32 kQualityColors[] = {
|
||||||
|
IM_COL32(157, 157, 157, 255), // 0 grey (poor)
|
||||||
|
IM_COL32(255, 255, 255, 255), // 1 white (common)
|
||||||
|
IM_COL32( 30, 255, 30, 255), // 2 green (uncommon)
|
||||||
|
IM_COL32( 0, 112, 221, 255), // 3 blue (rare)
|
||||||
|
IM_COL32(163, 53, 238, 255), // 4 purple (epic)
|
||||||
|
IM_COL32(255, 128, 0, 255), // 5 orange (legendary)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stack at bottom-left above action bars; each item is 24 px tall
|
||||||
|
constexpr float TOAST_W = 260.0f;
|
||||||
|
constexpr float TOAST_H = 24.0f;
|
||||||
|
constexpr float TOAST_GAP = 2.0f;
|
||||||
|
constexpr float TOAST_X = 14.0f;
|
||||||
|
float baseY = screenH * 0.68f; // slightly above the whisper toasts
|
||||||
|
|
||||||
|
ImDrawList* bgDL = ImGui::GetBackgroundDrawList();
|
||||||
|
const int count = static_cast<int>(itemLootToasts_.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
const auto& toast = itemLootToasts_[i];
|
||||||
|
|
||||||
|
float remaining = ITEM_LOOT_TOAST_DURATION - toast.age;
|
||||||
|
float alpha;
|
||||||
|
if (toast.age < 0.15f)
|
||||||
|
alpha = toast.age / 0.15f;
|
||||||
|
else if (remaining < 0.7f)
|
||||||
|
alpha = remaining / 0.7f;
|
||||||
|
else
|
||||||
|
alpha = 1.0f;
|
||||||
|
alpha = std::clamp(alpha, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
// Slide-in from left
|
||||||
|
float slideX = (toast.age < 0.15f) ? (TOAST_W * (1.0f - toast.age / 0.15f)) : 0.0f;
|
||||||
|
float tx = TOAST_X - slideX;
|
||||||
|
float ty = baseY - (count - i) * (TOAST_H + TOAST_GAP);
|
||||||
|
|
||||||
|
uint8_t bgA = static_cast<uint8_t>(180 * alpha);
|
||||||
|
uint8_t fgA = static_cast<uint8_t>(255 * alpha);
|
||||||
|
|
||||||
|
// Background: very dark with quality-tinted left border accent
|
||||||
|
bgDL->AddRectFilled(ImVec2(tx, ty), ImVec2(tx + TOAST_W, ty + TOAST_H),
|
||||||
|
IM_COL32(12, 12, 12, bgA), 3.0f);
|
||||||
|
|
||||||
|
// Quality colour accent bar on left edge (3px wide)
|
||||||
|
ImU32 qualCol = kQualityColors[std::min(static_cast<uint32_t>(5u), toast.quality)];
|
||||||
|
ImU32 qualColA = (qualCol & 0x00FFFFFFu) | (static_cast<uint32_t>(fgA) << 24u);
|
||||||
|
bgDL->AddRectFilled(ImVec2(tx, ty), ImVec2(tx + 3.0f, ty + TOAST_H), qualColA, 3.0f);
|
||||||
|
|
||||||
|
// "Loot:" label in dim white
|
||||||
|
bgDL->AddText(ImVec2(tx + 7.0f, ty + 5.0f),
|
||||||
|
IM_COL32(160, 160, 160, static_cast<uint8_t>(200 * alpha)), "Loot:");
|
||||||
|
|
||||||
|
// Item name in quality colour
|
||||||
|
std::string displayName = toast.name.empty() ? ("Item #" + std::to_string(toast.itemId)) : toast.name;
|
||||||
|
if (displayName.size() > 26) { displayName.resize(23); displayName += "..."; }
|
||||||
|
bgDL->AddText(ImVec2(tx + 42.0f, ty + 5.0f), qualColA, displayName.c_str());
|
||||||
|
|
||||||
|
// Count (if > 1)
|
||||||
|
if (toast.count > 1) {
|
||||||
|
char countBuf[12];
|
||||||
|
snprintf(countBuf, sizeof(countBuf), "x%u", toast.count);
|
||||||
|
bgDL->AddText(ImVec2(tx + TOAST_W - 34.0f, ty + 5.0f),
|
||||||
|
IM_COL32(200, 200, 200, static_cast<uint8_t>(200 * alpha)), countBuf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// PvP honor credit toasts — shown at screen top-right on honorable kill
|
// PvP honor credit toasts — shown at screen top-right on honorable kill
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue