mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-25 13:03:50 +00:00
Add UIErrorsFrame: center-bottom spell error overlay with fade-out
This commit is contained in:
parent
955b22841e
commit
25e2c60603
4 changed files with 92 additions and 2 deletions
|
|
@ -1269,6 +1269,11 @@ public:
|
|||
using PlayPositionalSoundCallback = std::function<void(uint32_t soundId, uint64_t sourceGuid)>;
|
||||
void setPlayPositionalSoundCallback(PlayPositionalSoundCallback cb) { playPositionalSoundCallback_ = std::move(cb); }
|
||||
|
||||
// UI error frame: prominent on-screen error messages (spell can't be cast, etc.)
|
||||
using UIErrorCallback = std::function<void(const std::string& msg)>;
|
||||
void setUIErrorCallback(UIErrorCallback cb) { uiErrorCallback_ = std::move(cb); }
|
||||
void addUIError(const std::string& msg) { if (uiErrorCallback_) uiErrorCallback_(msg); }
|
||||
|
||||
// Mount state
|
||||
using MountCallback = std::function<void(uint32_t mountDisplayId)>; // 0 = dismount
|
||||
void setMountCallback(MountCallback cb) { mountCallback_ = std::move(cb); }
|
||||
|
|
@ -2548,6 +2553,9 @@ private:
|
|||
PlayMusicCallback playMusicCallback_;
|
||||
PlaySoundCallback playSoundCallback_;
|
||||
PlayPositionalSoundCallback playPositionalSoundCallback_;
|
||||
|
||||
// ---- UI error frame callback ----
|
||||
UIErrorCallback uiErrorCallback_;
|
||||
};
|
||||
|
||||
} // namespace game
|
||||
|
|
|
|||
|
|
@ -76,6 +76,12 @@ private:
|
|||
float damageFlashAlpha_ = 0.0f; // Screen edge flash intensity (fades to 0)
|
||||
float levelUpFlashAlpha_ = 0.0f; // Golden level-up burst effect (fades to 0)
|
||||
uint32_t levelUpDisplayLevel_ = 0; // Level shown in level-up text
|
||||
|
||||
// UIErrorsFrame: WoW-style center-bottom error messages (spell fails, out of range, etc.)
|
||||
struct UIErrorEntry { std::string text; float age = 0.0f; };
|
||||
std::vector<UIErrorEntry> uiErrors_;
|
||||
bool uiErrorCallbackSet_ = false;
|
||||
static constexpr float kUIErrorLifetime = 2.5f;
|
||||
bool showPlayerInfo = false;
|
||||
bool showSocialFrame_ = false; // O key toggles social/friends list
|
||||
bool showGuildRoster_ = false;
|
||||
|
|
@ -256,6 +262,7 @@ private:
|
|||
void renderCombatText(game::GameHandler& gameHandler);
|
||||
void renderPartyFrames(game::GameHandler& gameHandler);
|
||||
void renderBossFrames(game::GameHandler& gameHandler);
|
||||
void renderUIErrors(game::GameHandler& gameHandler, float deltaTime);
|
||||
void renderGroupInvitePopup(game::GameHandler& gameHandler);
|
||||
void renderDuelRequestPopup(game::GameHandler& gameHandler);
|
||||
void renderLootRollPopup(game::GameHandler& gameHandler);
|
||||
|
|
|
|||
|
|
@ -1124,6 +1124,7 @@ void GameHandler::update(float deltaTime) {
|
|||
autoAttackOutOfRangeTime_ += deltaTime;
|
||||
if (autoAttackRangeWarnCooldown_ <= 0.0f) {
|
||||
addSystemChatMessage("Target is too far away.");
|
||||
addUIError("Target is too far away.");
|
||||
autoAttackRangeWarnCooldown_ = 1.25f;
|
||||
}
|
||||
// Stop chasing stale swings when the target remains out of range.
|
||||
|
|
@ -1959,11 +1960,13 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
playerPowerType = static_cast<int>(pu->getPowerType());
|
||||
}
|
||||
const char* reason = getSpellCastResultString(castResult, playerPowerType);
|
||||
std::string errMsg = reason ? reason
|
||||
: ("Spell cast failed (error " + std::to_string(castResult) + ")");
|
||||
addUIError(errMsg);
|
||||
MessageChatData msg;
|
||||
msg.type = ChatType::SYSTEM;
|
||||
msg.language = ChatLanguage::UNIVERSAL;
|
||||
msg.message = reason ? reason
|
||||
: ("Spell cast failed (error " + std::to_string(castResult) + ")");
|
||||
msg.message = errMsg;
|
||||
addLocalChatMessage(msg);
|
||||
}
|
||||
}
|
||||
|
|
@ -2274,6 +2277,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
LOG_INFO("SMSG_ENABLE_BARBER_SHOP: barber shop available");
|
||||
break;
|
||||
case Opcode::SMSG_FEIGN_DEATH_RESISTED:
|
||||
addUIError("Your Feign Death was resisted.");
|
||||
addSystemChatMessage("Your Feign Death attempt was resisted.");
|
||||
LOG_DEBUG("SMSG_FEIGN_DEATH_RESISTED");
|
||||
break;
|
||||
|
|
@ -3173,6 +3177,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
handleDuelWinner(packet);
|
||||
break;
|
||||
case Opcode::SMSG_DUEL_OUTOFBOUNDS:
|
||||
addUIError("You are out of the duel area!");
|
||||
addSystemChatMessage("You are out of the duel area!");
|
||||
break;
|
||||
case Opcode::SMSG_DUEL_INBOUNDS:
|
||||
|
|
|
|||
|
|
@ -227,6 +227,15 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
achievementCallbackSet_ = true;
|
||||
}
|
||||
|
||||
// Set up UI error frame callback (once)
|
||||
if (!uiErrorCallbackSet_) {
|
||||
gameHandler.setUIErrorCallback([this](const std::string& msg) {
|
||||
uiErrors_.push_back({msg, 0.0f});
|
||||
if (uiErrors_.size() > 5) uiErrors_.erase(uiErrors_.begin());
|
||||
});
|
||||
uiErrorCallbackSet_ = true;
|
||||
}
|
||||
|
||||
// Apply UI transparency setting
|
||||
float prevAlpha = ImGui::GetStyle().Alpha;
|
||||
ImGui::GetStyle().Alpha = uiOpacity_;
|
||||
|
|
@ -443,6 +452,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
renderNameplates(gameHandler); // player names always shown; NPC plates gated by showNameplates_
|
||||
renderBattlegroundScore(gameHandler);
|
||||
renderCombatText(gameHandler);
|
||||
renderUIErrors(gameHandler, ImGui::GetIO().DeltaTime);
|
||||
if (showRaidFrames_) {
|
||||
renderPartyFrames(gameHandler);
|
||||
}
|
||||
|
|
@ -6515,6 +6525,66 @@ void GameScreen::renderPartyFrames(game::GameHandler& gameHandler) {
|
|||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// UI Error Frame (WoW-style center-bottom error overlay)
|
||||
// ============================================================
|
||||
|
||||
void GameScreen::renderUIErrors(game::GameHandler& /*gameHandler*/, float deltaTime) {
|
||||
// Age out old entries
|
||||
for (auto& e : uiErrors_) e.age += deltaTime;
|
||||
uiErrors_.erase(
|
||||
std::remove_if(uiErrors_.begin(), uiErrors_.end(),
|
||||
[](const UIErrorEntry& e) { return e.age >= kUIErrorLifetime; }),
|
||||
uiErrors_.end());
|
||||
|
||||
if (uiErrors_.empty()) return;
|
||||
|
||||
auto* window = core::Application::getInstance().getWindow();
|
||||
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
||||
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
||||
|
||||
// Fixed invisible overlay
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImGui::SetNextWindowSize(ImVec2(screenW, screenH));
|
||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoNav |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar;
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
if (ImGui::Begin("##UIErrors", nullptr, flags)) {
|
||||
// Render messages stacked above the action bar (~200px from bottom)
|
||||
// The newest message is on top; older ones fade below it.
|
||||
const float baseY = screenH - 200.0f;
|
||||
const float lineH = 20.0f;
|
||||
const int count = static_cast<int>(uiErrors_.size());
|
||||
|
||||
ImDrawList* draw = ImGui::GetWindowDrawList();
|
||||
for (int i = count - 1; i >= 0; --i) {
|
||||
const auto& e = uiErrors_[i];
|
||||
float alpha = 1.0f - (e.age / kUIErrorLifetime);
|
||||
alpha = std::max(0.0f, std::min(1.0f, alpha));
|
||||
|
||||
// Fade fast in the last 0.5 s
|
||||
if (e.age > kUIErrorLifetime - 0.5f)
|
||||
alpha *= (kUIErrorLifetime - e.age) / 0.5f;
|
||||
|
||||
uint8_t a8 = static_cast<uint8_t>(alpha * 255.0f);
|
||||
ImU32 textCol = IM_COL32(255, 50, 50, a8);
|
||||
ImU32 shadowCol= IM_COL32( 0, 0, 0, static_cast<uint8_t>(alpha * 180));
|
||||
|
||||
const char* txt = e.text.c_str();
|
||||
ImVec2 sz = ImGui::CalcTextSize(txt);
|
||||
float x = std::round((screenW - sz.x) * 0.5f);
|
||||
float y = std::round(baseY - (count - 1 - i) * lineH);
|
||||
|
||||
// Drop shadow
|
||||
draw->AddText(ImVec2(x + 1, y + 1), shadowCol, txt);
|
||||
draw->AddText(ImVec2(x, y), textCol, txt);
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Boss Encounter Frames
|
||||
// ============================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue