mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
feat: add UnitCastingInfo/UnitChannelInfo Lua API and fix SMSG_CAST_FAILED events
Expose cast/channel state to Lua addons via UnitCastingInfo(unit) and UnitChannelInfo(unit), matching the WoW API signature (name, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible). Works for player, target, focus, and pet units using existing UnitCastState tracking. Also fix handleCastFailed (SMSG_CAST_FAILED, Classic/TBC path) to fire UNIT_SPELLCAST_FAILED and UNIT_SPELLCAST_STOP events — previously only the WotLK SMSG_CAST_RESULT path fired these, leaving Classic/TBC addons unaware of cast failures. Adds isChannel field to UnitCastState and getCastTimeTotal() accessor.
This commit is contained in:
parent
1482694495
commit
ce4f93dfcb
3 changed files with 91 additions and 0 deletions
|
|
@ -882,6 +882,7 @@ public:
|
||||||
uint32_t getCurrentCastSpellId() const { return currentCastSpellId; }
|
uint32_t getCurrentCastSpellId() const { return currentCastSpellId; }
|
||||||
float getCastProgress() const { return castTimeTotal > 0 ? (castTimeTotal - castTimeRemaining) / castTimeTotal : 0.0f; }
|
float getCastProgress() const { return castTimeTotal > 0 ? (castTimeTotal - castTimeRemaining) / castTimeTotal : 0.0f; }
|
||||||
float getCastTimeRemaining() const { return castTimeRemaining; }
|
float getCastTimeRemaining() const { return castTimeRemaining; }
|
||||||
|
float getCastTimeTotal() const { return castTimeTotal; }
|
||||||
|
|
||||||
// Repeat-craft queue
|
// Repeat-craft queue
|
||||||
void startCraftQueue(uint32_t spellId, int count);
|
void startCraftQueue(uint32_t spellId, int count);
|
||||||
|
|
@ -896,6 +897,7 @@ public:
|
||||||
// Unit cast state (tracked per GUID for target frame + boss frames)
|
// Unit cast state (tracked per GUID for target frame + boss frames)
|
||||||
struct UnitCastState {
|
struct UnitCastState {
|
||||||
bool casting = false;
|
bool casting = false;
|
||||||
|
bool isChannel = false; ///< true for channels (MSG_CHANNEL_START), false for casts (SMSG_SPELL_START)
|
||||||
uint32_t spellId = 0;
|
uint32_t spellId = 0;
|
||||||
float timeRemaining = 0.0f;
|
float timeRemaining = 0.0f;
|
||||||
float timeTotal = 0.0f;
|
float timeTotal = 0.0f;
|
||||||
|
|
|
||||||
|
|
@ -1077,6 +1077,84 @@ static int lua_UnitAuraGeneric(lua_State* L) {
|
||||||
return lua_UnitAura(L, wantBuff);
|
return lua_UnitAura(L, wantBuff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- UnitCastingInfo / UnitChannelInfo ----------
|
||||||
|
// Internal helper: pushes cast/channel info for a unit.
|
||||||
|
// Returns number of Lua return values (0 if not casting/channeling the requested type).
|
||||||
|
static int lua_UnitCastInfo(lua_State* L, bool wantChannel) {
|
||||||
|
auto* gh = getGameHandler(L);
|
||||||
|
if (!gh) { lua_pushnil(L); return 1; }
|
||||||
|
|
||||||
|
const char* uid = luaL_optstring(L, 1, "player");
|
||||||
|
std::string uidStr(uid ? uid : "player");
|
||||||
|
|
||||||
|
// GetTime epoch for consistent time values
|
||||||
|
static auto sStart = std::chrono::steady_clock::now();
|
||||||
|
double nowSec = std::chrono::duration<double>(
|
||||||
|
std::chrono::steady_clock::now() - sStart).count();
|
||||||
|
|
||||||
|
// Resolve cast state for the unit
|
||||||
|
bool isCasting = false;
|
||||||
|
bool isChannel = false;
|
||||||
|
uint32_t spellId = 0;
|
||||||
|
float timeTotal = 0.0f;
|
||||||
|
float timeRemaining = 0.0f;
|
||||||
|
bool interruptible = true;
|
||||||
|
|
||||||
|
if (uidStr == "player") {
|
||||||
|
isCasting = gh->isCasting();
|
||||||
|
isChannel = gh->isChanneling();
|
||||||
|
spellId = gh->getCurrentCastSpellId();
|
||||||
|
timeTotal = gh->getCastTimeTotal();
|
||||||
|
timeRemaining = gh->getCastTimeRemaining();
|
||||||
|
// Player interruptibility: always true for own casts (server controls actual interrupt)
|
||||||
|
interruptible = true;
|
||||||
|
} else {
|
||||||
|
uint64_t guid = resolveUnitGuid(gh, uidStr);
|
||||||
|
if (guid == 0) { lua_pushnil(L); return 1; }
|
||||||
|
const auto* state = gh->getUnitCastState(guid);
|
||||||
|
if (!state) { lua_pushnil(L); return 1; }
|
||||||
|
isCasting = state->casting;
|
||||||
|
isChannel = state->isChannel;
|
||||||
|
spellId = state->spellId;
|
||||||
|
timeTotal = state->timeTotal;
|
||||||
|
timeRemaining = state->timeRemaining;
|
||||||
|
interruptible = state->interruptible;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCasting) { lua_pushnil(L); return 1; }
|
||||||
|
|
||||||
|
// UnitCastingInfo: only returns for non-channel casts
|
||||||
|
// UnitChannelInfo: only returns for channels
|
||||||
|
if (wantChannel != isChannel) { lua_pushnil(L); return 1; }
|
||||||
|
|
||||||
|
// Spell name + icon
|
||||||
|
const std::string& name = gh->getSpellName(spellId);
|
||||||
|
std::string iconPath = gh->getSpellIconPath(spellId);
|
||||||
|
|
||||||
|
// Time values in milliseconds (WoW API convention)
|
||||||
|
double startTimeMs = (nowSec - (timeTotal - timeRemaining)) * 1000.0;
|
||||||
|
double endTimeMs = (nowSec + timeRemaining) * 1000.0;
|
||||||
|
|
||||||
|
// Return values match WoW API:
|
||||||
|
// UnitCastingInfo: name, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible
|
||||||
|
// UnitChannelInfo: name, text, texture, startTime, endTime, isTradeSkill, notInterruptible
|
||||||
|
lua_pushstring(L, name.empty() ? "Unknown" : name.c_str()); // name
|
||||||
|
lua_pushstring(L, ""); // text (sub-text, usually empty)
|
||||||
|
if (!iconPath.empty()) lua_pushstring(L, iconPath.c_str());
|
||||||
|
else lua_pushstring(L, "Interface\\Icons\\INV_Misc_QuestionMark"); // texture
|
||||||
|
lua_pushnumber(L, startTimeMs); // startTime (ms)
|
||||||
|
lua_pushnumber(L, endTimeMs); // endTime (ms)
|
||||||
|
lua_pushboolean(L, gh->isProfessionSpell(spellId) ? 1 : 0); // isTradeSkill
|
||||||
|
if (!wantChannel) {
|
||||||
|
lua_pushnumber(L, spellId); // castID (UnitCastingInfo only)
|
||||||
|
}
|
||||||
|
lua_pushboolean(L, interruptible ? 0 : 1); // notInterruptible
|
||||||
|
return wantChannel ? 7 : 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lua_UnitCastingInfo(lua_State* L) { return lua_UnitCastInfo(L, false); }
|
||||||
|
static int lua_UnitChannelInfo(lua_State* L) { return lua_UnitCastInfo(L, true); }
|
||||||
|
|
||||||
// --- Action API ---
|
// --- Action API ---
|
||||||
|
|
||||||
static int lua_SendChatMessage(lua_State* L) {
|
static int lua_SendChatMessage(lua_State* L) {
|
||||||
|
|
@ -3486,6 +3564,8 @@ void LuaEngine::registerCoreAPI() {
|
||||||
{"UnitBuff", lua_UnitBuff},
|
{"UnitBuff", lua_UnitBuff},
|
||||||
{"UnitDebuff", lua_UnitDebuff},
|
{"UnitDebuff", lua_UnitDebuff},
|
||||||
{"UnitAura", lua_UnitAuraGeneric},
|
{"UnitAura", lua_UnitAuraGeneric},
|
||||||
|
{"UnitCastingInfo", lua_UnitCastingInfo},
|
||||||
|
{"UnitChannelInfo", lua_UnitChannelInfo},
|
||||||
{"GetNumAddOns", lua_GetNumAddOns},
|
{"GetNumAddOns", lua_GetNumAddOns},
|
||||||
{"GetAddOnInfo", lua_GetAddOnInfo},
|
{"GetAddOnInfo", lua_GetAddOnInfo},
|
||||||
{"GetAddOnMetadata", lua_GetAddOnMetadata},
|
{"GetAddOnMetadata", lua_GetAddOnMetadata},
|
||||||
|
|
|
||||||
|
|
@ -7519,6 +7519,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
} else {
|
} else {
|
||||||
auto& s = unitCastStates_[chanCaster];
|
auto& s = unitCastStates_[chanCaster];
|
||||||
s.casting = true;
|
s.casting = true;
|
||||||
|
s.isChannel = true;
|
||||||
s.spellId = chanSpellId;
|
s.spellId = chanSpellId;
|
||||||
s.timeTotal = chanTotalMs / 1000.0f;
|
s.timeTotal = chanTotalMs / 1000.0f;
|
||||||
s.timeRemaining = s.timeTotal;
|
s.timeRemaining = s.timeTotal;
|
||||||
|
|
@ -19363,6 +19364,13 @@ void GameHandler::handleCastFailed(network::Packet& packet) {
|
||||||
if (auto* sfx = renderer->getUiSoundManager())
|
if (auto* sfx = renderer->getUiSoundManager())
|
||||||
sfx->playError();
|
sfx->playError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fire UNIT_SPELLCAST_FAILED + UNIT_SPELLCAST_STOP so Lua addons can react
|
||||||
|
if (addonEventCallback_) {
|
||||||
|
addonEventCallback_("UNIT_SPELLCAST_FAILED", {"player", std::to_string(data.spellId)});
|
||||||
|
addonEventCallback_("UNIT_SPELLCAST_STOP", {"player", std::to_string(data.spellId)});
|
||||||
|
}
|
||||||
|
if (spellCastFailedCallback_) spellCastFailedCallback_(data.spellId);
|
||||||
}
|
}
|
||||||
|
|
||||||
static audio::SpellSoundManager::MagicSchool schoolMaskToMagicSchool(uint32_t mask) {
|
static audio::SpellSoundManager::MagicSchool schoolMaskToMagicSchool(uint32_t mask) {
|
||||||
|
|
@ -19383,6 +19391,7 @@ void GameHandler::handleSpellStart(network::Packet& packet) {
|
||||||
if (data.casterUnit != playerGuid && data.castTime > 0) {
|
if (data.casterUnit != playerGuid && data.castTime > 0) {
|
||||||
auto& s = unitCastStates_[data.casterUnit];
|
auto& s = unitCastStates_[data.casterUnit];
|
||||||
s.casting = true;
|
s.casting = true;
|
||||||
|
s.isChannel = false;
|
||||||
s.spellId = data.spellId;
|
s.spellId = data.spellId;
|
||||||
s.timeTotal = data.castTime / 1000.0f;
|
s.timeTotal = data.castTime / 1000.0f;
|
||||||
s.timeRemaining = s.timeTotal;
|
s.timeRemaining = s.timeTotal;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue