From ae627193f84bb3fef9f34097e86530b2209be582 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Fri, 20 Mar 2026 14:27:46 -0700 Subject: [PATCH] feat: fire combat, spell, and cooldown events for Lua addon system Add PLAYER_REGEN_DISABLED/ENABLED (combat enter/leave) via per-frame edge detection, LEARNED_SPELL_IN_TAB/SPELLS_CHANGED on spell learn/remove, SPELL_UPDATE_COOLDOWN/ACTIONBAR_UPDATE_COOLDOWN on cooldown finish, and PLAYER_XP_UPDATE on XP field changes. Total addon events now at 34. --- include/game/game_handler.hpp | 1 + src/game/game_handler.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index c455a95d..1c2907fd 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -2797,6 +2797,7 @@ private: float autoAttackResendTimer_ = 0.0f; // Re-send CMSG_ATTACKSWING every ~1s while attacking float autoAttackFacingSyncTimer_ = 0.0f; // Periodic facing sync while meleeing std::unordered_set hostileAttackers_; + bool wasCombat_ = false; // Previous frame combat state for PLAYER_REGEN edge detection std::vector combatText; static constexpr size_t MAX_COMBAT_LOG = 500; struct RecentSpellstealLogEntry { diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index e23e8fc7..5e7af734 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -922,6 +922,17 @@ void GameHandler::update(float deltaTime) { clearTarget(); } + // Detect combat state transitions → fire PLAYER_REGEN_DISABLED / PLAYER_REGEN_ENABLED + { + bool combatNow = isInCombat(); + if (combatNow != wasCombat_) { + wasCombat_ = combatNow; + if (addonEventCallback_) { + addonEventCallback_(combatNow ? "PLAYER_REGEN_DISABLED" : "PLAYER_REGEN_ENABLED", {}); + } + } + } + if (auctionSearchDelayTimer_ > 0.0f) { auctionSearchDelayTimer_ -= deltaTime; if (auctionSearchDelayTimer_ < 0.0f) auctionSearchDelayTimer_ = 0.0f; @@ -12453,6 +12464,8 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem if (key == ufPlayerXp) { playerXp_ = val; LOG_DEBUG("XP updated: ", val); + if (addonEventCallback_) + addonEventCallback_("PLAYER_XP_UPDATE", {std::to_string(val)}); } else if (key == ufPlayerNextXp) { playerNextLevelXp_ = val; @@ -19203,6 +19216,10 @@ void GameHandler::handleCooldownEvent(network::Packet& packet) { slot.cooldownRemaining = 0.0f; } } + if (addonEventCallback_) { + addonEventCallback_("SPELL_UPDATE_COOLDOWN", {}); + addonEventCallback_("ACTIONBAR_UPDATE_COOLDOWN", {}); + } } void GameHandler::handleAuraUpdate(network::Packet& packet, bool isAll) { @@ -19293,6 +19310,12 @@ void GameHandler::handleLearnedSpell(network::Packet& packet) { } } + // Fire LEARNED_SPELL_IN_TAB / SPELLS_CHANGED for Lua addons + if (!alreadyKnown && addonEventCallback_) { + addonEventCallback_("LEARNED_SPELL_IN_TAB", {std::to_string(spellId)}); + addonEventCallback_("SPELLS_CHANGED", {}); + } + // Show chat message for non-talent spells, but only if not already announced by // SMSG_TRAINER_BUY_SUCCEEDED (which pre-inserts into knownSpells). if (!alreadyKnown) { @@ -19313,6 +19336,7 @@ void GameHandler::handleRemovedSpell(network::Packet& packet) { uint32_t spellId = classicSpellId ? packet.readUInt16() : packet.readUInt32(); knownSpells.erase(spellId); LOG_INFO("Removed spell: ", spellId); + if (addonEventCallback_) addonEventCallback_("SPELLS_CHANGED", {}); const std::string& name = getSpellName(spellId); if (!name.empty())