feat: add countdown timer to loot roll popup

Show a color-coded progress bar (green→yellow→pulsing red) in the loot
roll window indicating time remaining to make a roll decision. The
countdown duration is read from SMSG_LOOT_START_ROLL (or defaults to
60s for the SMSG_LOOT_ROLL path). Remaining seconds are displayed on
the bar itself.
This commit is contained in:
Kelsi 2026-03-12 04:57:36 -07:00
parent b34bf39746
commit b682e8c686
3 changed files with 35 additions and 1 deletions

View file

@ -1129,6 +1129,8 @@ public:
uint32_t itemId = 0;
std::string itemName;
uint8_t itemQuality = 0;
uint32_t rollCountdownMs = 60000; // Duration of roll window in ms
std::chrono::steady_clock::time_point rollStartedAt{};
};
bool hasPendingLootRoll() const { return pendingLootRollActive_; }
const LootRollEntry& getPendingLootRoll() const { return pendingLootRoll_; }

View file

@ -2028,7 +2028,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
/*uint32_t randSuffix =*/ packet.readUInt32();
/*uint32_t randProp =*/ packet.readUInt32();
}
/*uint32_t countdown =*/ packet.readUInt32();
uint32_t countdown = packet.readUInt32();
/*uint8_t voteMask =*/ packet.readUInt8();
// Trigger the roll popup for local player
pendingLootRollActive_ = true;
@ -2038,6 +2038,8 @@ void GameHandler::handlePacket(network::Packet& packet) {
auto* info = getItemInfo(itemId);
pendingLootRoll_.itemName = info ? info->name : std::to_string(itemId);
pendingLootRoll_.itemQuality = info ? static_cast<uint8_t>(info->quality) : 0;
pendingLootRoll_.rollCountdownMs = (countdown > 0 && countdown <= 120000) ? countdown : 60000;
pendingLootRoll_.rollStartedAt = std::chrono::steady_clock::now();
LOG_INFO("SMSG_LOOT_START_ROLL: item=", itemId, " (", pendingLootRoll_.itemName,
") slot=", slot);
break;
@ -19931,6 +19933,8 @@ void GameHandler::handleLootRoll(network::Packet& packet) {
auto* info = getItemInfo(itemId);
pendingLootRoll_.itemName = info ? info->name : std::to_string(itemId);
pendingLootRoll_.itemQuality = info ? static_cast<uint8_t>(info->quality) : 0;
pendingLootRoll_.rollCountdownMs = 60000;
pendingLootRoll_.rollStartedAt = std::chrono::steady_clock::now();
LOG_INFO("SMSG_LOOT_ROLL: need/greed prompt for item=", itemId,
" (", pendingLootRoll_.itemName, ") slot=", slot);
return;

View file

@ -7657,6 +7657,34 @@ void GameScreen::renderLootRollPopup(game::GameHandler& gameHandler) {
uint8_t q = roll.itemQuality;
ImVec4 col = (q < 6) ? kQualityColors[q] : kQualityColors[1];
// Countdown bar
{
auto now = std::chrono::steady_clock::now();
float elapsedMs = static_cast<float>(
std::chrono::duration_cast<std::chrono::milliseconds>(now - roll.rollStartedAt).count());
float totalMs = static_cast<float>(roll.rollCountdownMs > 0 ? roll.rollCountdownMs : 60000);
float fraction = 1.0f - std::min(elapsedMs / totalMs, 1.0f);
float remainSec = (totalMs - elapsedMs) / 1000.0f;
if (remainSec < 0.0f) remainSec = 0.0f;
// Color: green → yellow → red
ImVec4 barColor;
if (fraction > 0.5f)
barColor = ImVec4(0.2f + (1.0f - fraction) * 1.4f, 0.85f, 0.2f, 1.0f);
else if (fraction > 0.2f)
barColor = ImVec4(1.0f, fraction * 1.7f, 0.1f, 1.0f);
else {
float pulse = 0.7f + 0.3f * std::sin(static_cast<float>(ImGui::GetTime()) * 6.0f);
barColor = ImVec4(pulse, 0.1f * pulse, 0.1f * pulse, 1.0f);
}
char timeBuf[16];
std::snprintf(timeBuf, sizeof(timeBuf), "%.0fs", remainSec);
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, barColor);
ImGui::ProgressBar(fraction, ImVec2(-1, 12), timeBuf);
ImGui::PopStyleColor();
}
ImGui::Text("An item is up for rolls:");
// Show item icon if available