From 4c10974553c5e1c4ebba64afb63afa905abc6cad Mon Sep 17 00:00:00 2001 From: Kelsi Date: Fri, 20 Mar 2026 14:15:00 -0700 Subject: [PATCH] feat: add party/raid unit IDs and game events for Lua addon system Extend resolveUnit() to support party1-4, raid1-40, and use resolveUnitGuid for UnitGUID/UnitIsPlayer/UnitBuff/UnitDebuff (including unitAurasCache for party member auras). Fire UNIT_HEALTH, UNIT_POWER, UNIT_AURA, UNIT_SPELLCAST_START, UNIT_SPELLCAST_SUCCEEDED, GROUP_ROSTER_UPDATE, and PARTY_MEMBERS_CHANGED events to Lua addons from the corresponding packet handlers. --- src/addons/lua_engine.cpp | 58 +++++++++++++++++++++++++++------------ src/game/game_handler.cpp | 56 ++++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 18 deletions(-) diff --git a/src/addons/lua_engine.cpp b/src/addons/lua_engine.cpp index 6c40cafe..71ed8986 100644 --- a/src/addons/lua_engine.cpp +++ b/src/addons/lua_engine.cpp @@ -68,20 +68,46 @@ static game::Unit* getPlayerUnit(lua_State* L) { return dynamic_cast(entity.get()); } -// Helper: resolve "player", "target", "focus", "pet" unit IDs to entity +// Helper: resolve WoW unit IDs to GUID +static uint64_t resolveUnitGuid(game::GameHandler* gh, const std::string& uid) { + if (uid == "player") return gh->getPlayerGuid(); + if (uid == "target") return gh->getTargetGuid(); + if (uid == "focus") return gh->getFocusGuid(); + if (uid == "pet") return gh->getPetGuid(); + // party1-party4, raid1-raid40 + if (uid.rfind("party", 0) == 0 && uid.size() > 5) { + int idx = 0; + try { idx = std::stoi(uid.substr(5)); } catch (...) { return 0; } + if (idx < 1 || idx > 4) return 0; + const auto& pd = gh->getPartyData(); + // party members exclude self; index 1-based + int found = 0; + for (const auto& m : pd.members) { + if (m.guid == gh->getPlayerGuid()) continue; + if (++found == idx) return m.guid; + } + return 0; + } + if (uid.rfind("raid", 0) == 0 && uid.size() > 4 && uid[4] != 'p') { + int idx = 0; + try { idx = std::stoi(uid.substr(4)); } catch (...) { return 0; } + if (idx < 1 || idx > 40) return 0; + const auto& pd = gh->getPartyData(); + if (idx <= static_cast(pd.members.size())) + return pd.members[idx - 1].guid; + return 0; + } + return 0; +} + +// Helper: resolve "player", "target", "focus", "pet", "partyN", "raidN" unit IDs to entity static game::Unit* resolveUnit(lua_State* L, const char* unitId) { auto* gh = getGameHandler(L); if (!gh || !unitId) return nullptr; std::string uid(unitId); for (char& c : uid) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = 0; - if (uid == "player") guid = gh->getPlayerGuid(); - else if (uid == "target") guid = gh->getTargetGuid(); - else if (uid == "focus") guid = gh->getFocusGuid(); - else if (uid == "pet") guid = gh->getPetGuid(); - else return nullptr; - + uint64_t guid = resolveUnitGuid(gh, uid); if (guid == 0) return nullptr; auto entity = gh->getEntityManager().getEntity(guid); if (!entity) return nullptr; @@ -250,11 +276,7 @@ static int lua_UnitGUID(lua_State* L) { if (!gh) { lua_pushnil(L); return 1; } std::string uidStr(uid); for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = 0; - if (uidStr == "player") guid = gh->getPlayerGuid(); - else if (uidStr == "target") guid = gh->getTargetGuid(); - else if (uidStr == "focus") guid = gh->getFocusGuid(); - else if (uidStr == "pet") guid = gh->getPetGuid(); + uint64_t guid = resolveUnitGuid(gh, uidStr); if (guid == 0) { lua_pushnil(L); return 1; } char buf[32]; snprintf(buf, sizeof(buf), "0x%016llX", (unsigned long long)guid); @@ -268,10 +290,7 @@ static int lua_UnitIsPlayer(lua_State* L) { if (!gh) { lua_pushboolean(L, 0); return 1; } std::string uidStr(uid); for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = 0; - if (uidStr == "player") guid = gh->getPlayerGuid(); - else if (uidStr == "target") guid = gh->getTargetGuid(); - else if (uidStr == "focus") guid = gh->getFocusGuid(); + uint64_t guid = resolveUnitGuid(gh, uidStr); auto entity = guid ? gh->getEntityManager().getEntity(guid) : nullptr; lua_pushboolean(L, entity && entity->getType() == game::ObjectType::PLAYER); return 1; @@ -346,6 +365,11 @@ static int lua_UnitAura(lua_State* L, bool wantBuff) { const std::vector* auras = nullptr; if (uidStr == "player") auras = &gh->getPlayerAuras(); else if (uidStr == "target") auras = &gh->getTargetAuras(); + else { + // Try party/raid/focus via GUID lookup in unitAurasCache + uint64_t guid = resolveUnitGuid(gh, uidStr); + if (guid != 0) auras = gh->getUnitAuras(guid); + } if (!auras) { lua_pushnil(L); return 1; } // Filter to buffs or debuffs and find the Nth one diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 4180ca76..e23e8fc7 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -12120,6 +12120,8 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem bool displayIdChanged = false; bool npcDeathNotified = false; bool npcRespawnNotified = false; + bool healthChanged = false; + bool powerChanged = false; const uint16_t ufHealth = fieldIndex(UF::UNIT_FIELD_HEALTH); const uint16_t ufPowerBase = fieldIndex(UF::UNIT_FIELD_POWER1); const uint16_t ufMaxHealth = fieldIndex(UF::UNIT_FIELD_MAXHEALTH); @@ -12136,6 +12138,7 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem if (key == ufHealth) { uint32_t oldHealth = unit->getHealth(); unit->setHealth(val); + healthChanged = true; if (val == 0) { if (block.guid == autoAttackTarget) { stopAutoAttack(); @@ -12178,7 +12181,7 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem } // Specific fields checked BEFORE power/maxpower range checks // (Classic packs maxHealth/level/faction adjacent to power indices) - } else if (key == ufMaxHealth) { unit->setMaxHealth(val); } + } else if (key == ufMaxHealth) { unit->setMaxHealth(val); healthChanged = true; } else if (key == ufBytes0) { unit->setPowerType(static_cast((val >> 24) & 0xFF)); } else if (key == ufFlags) { unit->setUnitFlags(val); } @@ -12272,8 +12275,23 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem // Power/maxpower range checks AFTER all specific fields else if (key >= ufPowerBase && key < ufPowerBase + 7) { unit->setPowerByType(static_cast(key - ufPowerBase), val); + powerChanged = true; } else if (key >= ufMaxPowerBase && key < ufMaxPowerBase + 7) { unit->setMaxPowerByType(static_cast(key - ufMaxPowerBase), val); + powerChanged = true; + } + } + + // Fire UNIT_HEALTH / UNIT_POWER events for Lua addons + if (addonEventCallback_ && (healthChanged || powerChanged)) { + std::string unitId; + if (block.guid == playerGuid) unitId = "player"; + else if (block.guid == targetGuid) unitId = "target"; + else if (block.guid == focusGuid) unitId = "focus"; + else if (block.guid == petGuid_) unitId = "pet"; + if (!unitId.empty()) { + if (healthChanged) addonEventCallback_("UNIT_HEALTH", {unitId}); + if (powerChanged) addonEventCallback_("UNIT_POWER", {unitId}); } } @@ -18948,6 +18966,16 @@ void GameHandler::handleSpellStart(network::Packet& packet) { hearthstonePreloadCallback_(homeBindMapId_, homeBindPos_.x, homeBindPos_.y, homeBindPos_.z); } } + + // Fire UNIT_SPELLCAST_START for Lua addons + if (addonEventCallback_) { + std::string unitId; + if (data.casterUnit == playerGuid) unitId = "player"; + else if (data.casterUnit == targetGuid) unitId = "target"; + else if (data.casterUnit == focusGuid) unitId = "focus"; + if (!unitId.empty()) + addonEventCallback_("UNIT_SPELLCAST_START", {unitId, std::to_string(data.spellId)}); + } } void GameHandler::handleSpellGo(network::Packet& packet) { @@ -19084,6 +19112,16 @@ void GameHandler::handleSpellGo(network::Packet& packet) { if (tgt == playerGuid) { playerIsHit = true; } if (data.casterUnit == playerGuid && tgt != playerGuid && tgt != 0) { playerHitEnemy = true; } } + // Fire UNIT_SPELLCAST_SUCCEEDED for Lua addons + if (addonEventCallback_) { + std::string unitId; + if (data.casterUnit == playerGuid) unitId = "player"; + else if (data.casterUnit == targetGuid) unitId = "target"; + else if (data.casterUnit == focusGuid) unitId = "focus"; + if (!unitId.empty()) + addonEventCallback_("UNIT_SPELLCAST_SUCCEEDED", {unitId, std::to_string(data.spellId)}); + } + if (playerIsHit || playerHitEnemy) { if (auto* renderer = core::Application::getInstance().getRenderer()) { if (auto* ssm = renderer->getSpellSoundManager()) { @@ -19202,6 +19240,17 @@ void GameHandler::handleAuraUpdate(network::Packet& packet, bool isAll) { (*auraList)[slot] = aura; } + // Fire UNIT_AURA event for Lua addons + if (addonEventCallback_) { + std::string unitId; + if (data.guid == playerGuid) unitId = "player"; + else if (data.guid == targetGuid) unitId = "target"; + else if (data.guid == focusGuid) unitId = "focus"; + else if (data.guid == petGuid_) unitId = "pet"; + if (!unitId.empty()) + addonEventCallback_("UNIT_AURA", {unitId}); + } + // If player is mounted but we haven't identified the mount aura yet, // check newly added auras (aura update may arrive after mountDisplayId) if (data.guid == playerGuid && currentMountDisplayId_ != 0 && mountAuraSpellId_ == 0) { @@ -19597,6 +19646,11 @@ void GameHandler::handleGroupList(network::Packet& packet) { } else if (nowInGroup && partyData.memberCount != prevCount) { LOG_INFO("Group updated: ", partyData.memberCount, " members"); } + // Fire GROUP_ROSTER_UPDATE / PARTY_MEMBERS_CHANGED for Lua addons + if (addonEventCallback_) { + addonEventCallback_("GROUP_ROSTER_UPDATE", {}); + addonEventCallback_("PARTY_MEMBERS_CHANGED", {}); + } } void GameHandler::handleGroupUninvite(network::Packet& packet) {