From 29e7359ada2f3386ee794d2af1d3c9947909f5b1 Mon Sep 17 00:00:00 2001 From: pepopo978 Date: Thu, 26 Feb 2026 11:45:56 -0800 Subject: [PATCH] refactor and switch to using nampower events --- core.lua | 10 +- curses.lua | 246 +++++++++++++++++++-------------------------- filter.lua | 2 +- global.lua | 32 ++++-- settings.lua | 2 +- spells/hunter.lua | 40 ++++---- spells/warlock.lua | 6 +- ui.lua | 4 +- utils.lua | 2 +- 9 files changed, 158 insertions(+), 186 deletions(-) diff --git a/core.lua b/core.lua index 07d9918..1045cf2 100644 --- a/core.lua +++ b/core.lua @@ -1,4 +1,4 @@ -if not Cursive.superwow then +if not Cursive.nampower then return end @@ -24,10 +24,6 @@ Cursive.core.add = function(unit) end Cursive.core.addGuid = function(guid) - -- check if first two characters are 0x - if string.sub(guid, 1, 2) ~= "0x" then - return - end if UnitExists(guid) and not UnitIsDead(guid) then Cursive.core.guids[guid] = GetTime() end @@ -41,8 +37,8 @@ Cursive.core.enable = function() -- unitstr Cursive.core:RegisterEvent("PLAYER_TARGET_CHANGED") -- arg1 - Cursive.core:RegisterEvent("UNIT_COMBAT") -- this can get called with player/target/raid1 etc - Cursive.core:RegisterEvent("UNIT_MODEL_CHANGED") + Cursive.core:RegisterEvent("UNIT_COMBAT_GUID") -- this can get called with player/target/raid1 etc + Cursive.core:RegisterEvent("UNIT_MODEL_CHANGED_GUID") end Cursive.core.disable = function() diff --git a/curses.lua b/curses.lua index 2e8008f..932ed1e 100644 --- a/curses.lua +++ b/curses.lua @@ -1,4 +1,4 @@ -if not Cursive.superwow then +if not Cursive.nampower then return end local L = AceLibrary("AceLocale-2.2"):new("Cursive") @@ -20,6 +20,7 @@ local curses = { [52551] = true, [52552] = true, }, + travelTimeSpellIds = {}, darkHarvestData = {}, guids = {}, isChanneling = false, @@ -76,14 +77,6 @@ local curses = { -- combat events for curses local fades_test = L["(.+) fades from (.+)"] -local resist_test = L["Your (.+) was resisted by (.+)"] -local missed_test = L["Your (.+) missed (.+)"] -local parry_test = L["Your (.+) is parried by (.+)"] -local immune_test = L["Your (.+) fail.+\. (.+) is immune"] -local block_test = L["Your (.+) was blocked by (.+)"] -local dodge_test = L["Your (.+) was dodged by (.+)"] - -local molten_blast_test = L["Your Molten Blast(.+)for .+ Fire damage"] local lastGuid = nil @@ -131,9 +124,39 @@ function curses:LoadCurses() curses.trackedCurseIds = getWarriorSpells() end + -- build travelTimeSpellIds from spell data + curses.travelTimeSpellIds = {} + for id, data in pairs(curses.trackedCurseIds) do + if data.travelTime then + curses.travelTimeSpellIds[id] = true + end + end + + -- register travel time spell tracking + if next(curses.travelTimeSpellIds) and not Cursive:IsEventRegistered("SPELL_DAMAGE_EVENT_SELF") then + Cursive:RegisterEvent("SPELL_DAMAGE_EVENT_SELF", function(targetGuid, casterGuid, spellID, amount, mitigationStr, hitInfo, spellSchool, effectAuraStr) + if curses.travelTimeSpellIds[spellID] and curses.trackedCurseIds[spellID] then + lastGuid = targetGuid + local duration = curses:GetCurseDuration(spellID) + curses:ApplyCurse(spellID, targetGuid, GetTime(), duration) + end + end) + end + -- load shared debuffs curses.sharedDebuffs = getSharedDebuffs() + -- register faerie fire tracking from other players + if Cursive.db.profile.shareddebuffs.faeriefire then + if not Cursive:IsEventRegistered("SPELL_GO_OTHER") then + Cursive:RegisterEvent("SPELL_GO_OTHER", function(itemId, spellID, casterGuid, targetGuid, castFlags, numTargetsHit, numTargetsMissed) + if curses.sharedDebuffs.faeriefire[spellID] then + curses.sharedDebuffGuids.faeriefire[targetGuid] = GetTime() + end + end) + end + end + -- go through spell slots and local i = 1 while true do @@ -152,7 +175,7 @@ function curses:LoadCurses() for id, data in pairs(curses.trackedCurseIds) do -- get the texture - local name, rank, texture = SpellInfo(id) + local texture = GetSpellIconTexture(GetSpellRecField(id, "spellIconID")) -- update trackedCurseNamesToTextures curses.trackedCurseNamesToTextures[data.name] = texture -- update trackedCurseIds @@ -274,155 +297,92 @@ end); Cursive:RegisterEvent("SPELLCAST_CHANNEL_STOP", StopChanneling); Cursive:RegisterEvent("SPELLCAST_INTERRUPTED", StopChanneling); -Cursive:RegisterEvent("UNIT_CASTEVENT", function(casterGuid, targetGuid, event, spellID, castDuration) - -- immolate will fire both start and cast - if event == "CAST" then - local _, guid = UnitExists("player") - if casterGuid ~= guid then - -- check for faeriefire - if Cursive.db.profile.shareddebuffs.faeriefire and curses.sharedDebuffs.faeriefire[spellID] then - curses.sharedDebuffGuids.faeriefire[targetGuid] = GetTime() - end - - return - end - - -- store pending cast +-- player spell completions +Cursive:RegisterEvent("SPELL_GO_SELF", function(itemId, spellID, casterGuid, targetGuid, castFlags, numTargetsHit, numTargetsMissed) + if numTargetsMissed > 0 then + curses.pendingCast = {} + elseif numTargetsHit > 0 or curses.travelTimeSpellIds[spellID] then curses.pendingCast = { spellID = spellID, targetGuid = targetGuid, - castDuration = castDuration } + end - if curses.isDruid then - -- track ferocious bite cast time and target - if spellID == 22557 or - spellID == 22568 or - spellID == 22827 or - spellID == 22828 or - spellID == 22829 or - spellID == 31018 then - curses.lastFerociousBiteTime = GetTime() - curses.lastFerociousBiteTargetGuid = targetGuid - end - end + if curses.isDruid then + -- track ferocious bite cast time and target + if spellID == 22557 or + spellID == 22568 or + spellID == 22827 or + spellID == 22828 or + spellID == 22829 or + spellID == 31018 then + curses.lastFerociousBiteTime = GetTime() + curses.lastFerociousBiteTargetGuid = targetGuid + end + end - if curses.isShaman then - if spellID >= 36916 and spellID <= 36921 then - curses.lastMoltenBlastTargetGuid = targetGuid - end - end + if curses.isShaman then + if spellID >= 36916 and spellID <= 36921 then + curses.lastMoltenBlastTargetGuid = targetGuid + if numTargetsHit > 0 then + local flame_shock = L["flame shock"] + if curses:HasCurse(flame_shock, targetGuid, 0) then + curses.guids[targetGuid][flame_shock]["start"] = GetTime() + end + end + end + end - -- delay to check for resists/failures - local delay = 0.2 - - local _, _, nping = GetNetStats() - -- ignore extreme pings - if nping and nping > 0 and nping < 500 then - delay = 0.05 + (nping / 1000.0) -- convert to seconds - end - - if curses.trackedCurseIds[spellID] then + if numTargetsHit > 0 then + if curses.trackedCurseIds[spellID] and not curses.travelTimeSpellIds[spellID] then lastGuid = targetGuid - local duration = curses:GetCurseDuration(spellID) - delay - Cursive:ScheduleEvent("addCurse" .. targetGuid .. curses.trackedCurseIds[spellID].name, curses.ApplyCurse, delay, self, spellID, targetGuid, GetTime(), duration) + local duration = curses:GetCurseDuration(spellID) + curses:ApplyCurse(spellID, targetGuid, GetTime(), duration) elseif curses.conflagrateSpellIds[spellID] then - Cursive:ScheduleEvent("updateCurse" .. targetGuid .. L["conflagrate"], curses.UpdateCurse, delay, self, spellID, targetGuid, GetTime()) + curses:UpdateCurse(spellID, targetGuid, GetTime()) end - elseif event == "START" then - if curses.trackedCurseIds[spellID] then - local _, guid = UnitExists("player") - if casterGuid ~= guid then - return - end + end +end) - -- store pending cast - curses.pendingCast = { - spellID = spellID, - targetGuid = targetGuid, - castDuration = castDuration - } - end - elseif event == "FAIL" then - if curses.trackedCurseIds[spellID] then - local _, guid = UnitExists("player") - if casterGuid ~= guid then - return - end - -- clear pending cast - curses.pendingCast = {} - end - elseif event == "CHANNEL" then - -- dark harvest - if curses.darkHarvestSpellIds[spellID] then - local _, guid = UnitExists("player") - if casterGuid ~= guid then - return - end +-- player spell start (cast time spells) +Cursive:RegisterEvent("SPELL_START_SELF", function(itemId, spellID, casterGuid, targetGuid, castFlags, castTime) + if curses.trackedCurseIds[spellID] then + curses.pendingCast = { + spellID = spellID, + targetGuid = targetGuid, + castDuration = castTime / 1000 + } + end +end) - curses.darkHarvestData = { - spellID = spellID, - targetGuid = targetGuid, - castDuration = curses:ScanTooltipForDuration(spellID), - start = GetTime() - } - end +-- player spell failure +Cursive:RegisterEvent("SPELL_FAILED_SELF", function(spellID, spellResult, failedByServer) + if curses.trackedCurseIds[spellID] then + curses.pendingCast = {} + end +end) + +-- player channel start (dark harvest) +Cursive:RegisterEvent("SPELL_CHANNEL_START", function(spellID, targetGuid, durationMs) + if curses.darkHarvestSpellIds[spellID] then + curses.darkHarvestData = { + spellID = spellID, + targetGuid = targetGuid, + castDuration = curses:ScanTooltipForDuration(spellID), + start = GetTime() + } end end) -Cursive:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE", - function(message) - local spell_failed_tests = { - resist_test, - immune_test - } - -- only some classes have melee spells that need to check for dodge, parry, miss, block - if playerClassName == "DRUID" or playerClassName == "HUNTER" or playerClassName == "ROGUE" then - spell_failed_tests = { - resist_test, - immune_test, - missed_test, - parry_test, - block_test, - dodge_test - } - end +Cursive:RegisterEvent("SPELL_MISS_SELF", function(casterGuid, targetGuid, spellID, missInfo) + curses.pendingCast = {} - local spellName, failedTarget - for _, test in pairs(spell_failed_tests) do - local _, _, foundSpell, foundTarget = string.find(message, test) - if foundSpell and foundTarget then - spellName = foundSpell - failedTarget = foundTarget - break - end - end - - if spellName and failedTarget then - spellName = curses:GetLowercaseSpellName(spellName) - - curses.pendingCast = {} - - if curses.trackedCurseNamesToTextures[spellName] and lastGuid then - Cursive:CancelScheduledEvent("addCurse" .. lastGuid .. spellName) - -- check if sound should be played - if curses:ShouldPlayResistSound(lastGuid) then - PlaySoundFile("Interface\\AddOns\\Cursive\\Sounds\\resist.mp3") - end - elseif spellName == L["conflagrate"] and lastGuid then - Cursive:CancelScheduledEvent("updateCurse" .. lastGuid .. spellName) - end - return - end - - if curses.isShaman and string.find(message, molten_blast_test) then - local flame_shock = L["flame shock"] - if curses:HasCurse(flame_shock, curses.lastMoltenBlastTargetGuid, 0) then - curses.guids[curses.lastMoltenBlastTargetGuid][flame_shock]["start"] = GetTime() -- reset start time to current time - end - end - end -) -- resists + if curses.trackedCurseIds[spellID] and lastGuid then + if curses:ShouldPlayResistSound(lastGuid) then + PlaySoundFile("Interface\\AddOns\\Cursive\\Sounds\\resist.mp3") + end + end +end) Cursive:RegisterEvent("CHAT_MSG_SPELL_AURA_GONE_OTHER", function(message) -- check if spell that faded is relevant diff --git a/filter.lua b/filter.lua index 24a1063..f5ded95 100644 --- a/filter.lua +++ b/filter.lua @@ -1,4 +1,4 @@ -if not Cursive.superwow then +if not Cursive.nampower then return end diff --git a/global.lua b/global.lua index e32d14a..2c414d8 100644 --- a/global.lua +++ b/global.lua @@ -1,22 +1,36 @@ local L = AceLibrary("AceLocale-2.2"):new("Cursive") Cursive = AceLibrary("AceAddon-2.0"):new("AceEvent-2.0", "AceDebug-2.0", "AceModuleCore-2.0", "AceConsole-2.0", "AceDB-2.0", "AceHook-2.1") -Cursive.superwow = true +local function HasMinimumNampowerVersion(major, minor, patch) + if GetNampowerVersion then + local installedMajor, installedMinor, installedPatch = GetNampowerVersion() -if not GetPlayerBuffID or not CombatLogAdd or not SpellInfo then - local notify = CreateFrame("Frame", "CursiveNoSuperwow", UIParent) - notify:SetScript("OnUpdate", function() - DEFAULT_CHAT_FRAME:AddMessage(L["|cffffcc00Cursive:|cffffaaaa Couldn't detect SuperWoW."]) - this:Hide() - end) + if installedMajor > major then + return true + elseif installedMajor == major and installedMinor > minor then + return true + elseif installedMajor == major and installedMinor == minor and installedPatch >= patch then + return true + end + end - Cursive.superwow = false + return false +end + +Cursive.nampower = HasMinimumNampowerVersion(2, 37, 0) + +if not Cursive.nampower then + DEFAULT_CHAT_FRAME:AddMessage("|cffff6060Cursive:|r could not detect nampower >= v2.37.0") + return end function Cursive:OnEnable() - if not Cursive.superwow then + if not Cursive.nampower then return end + SetCVar("NP_EnableSpellStartEvents", 1) + SetCVar("NP_EnableSpellGoEvents", 1) + DEFAULT_CHAT_FRAME:AddMessage(L["|cffffcc00Cursive:|cffffaaaa Loaded. /cursive for commands and minimap icon for options."]) Cursive.curses:LoadCurses() diff --git a/settings.lua b/settings.lua index dca9fd3..7027608 100644 --- a/settings.lua +++ b/settings.lua @@ -1,4 +1,4 @@ -if not Cursive.superwow then +if not Cursive.nampower then return end diff --git a/spells/hunter.lua b/spells/hunter.lua index 31a5764..8619954 100644 --- a/spells/hunter.lua +++ b/spells/hunter.lua @@ -1,34 +1,34 @@ local L = AceLibrary("AceLocale-2.2"):new("Cursive") function getHunterSpells() return { - [3043] = { name = L["scorpid sting"], rank = 1, duration = 20 }, - [14275] = { name = L["scorpid sting"], rank = 2, duration = 20 }, - [14276] = { name = L["scorpid sting"], rank = 3, duration = 20 }, - [14277] = { name = L["scorpid sting"], rank = 4, duration = 20 }, + [3043] = { name = L["scorpid sting"], rank = 1, duration = 20, travelTime = true }, + [14275] = { name = L["scorpid sting"], rank = 2, duration = 20, travelTime = true }, + [14276] = { name = L["scorpid sting"], rank = 3, duration = 20, travelTime = true }, + [14277] = { name = L["scorpid sting"], rank = 4, duration = 20, travelTime = true }, - [1978] = { name = L["serpent sting"], rank = 1, duration = 15 }, - [13549] = { name = L["serpent sting"], rank = 2, duration = 15 }, - [13550] = { name = L["serpent sting"], rank = 3, duration = 15 }, - [13551] = { name = L["serpent sting"], rank = 4, duration = 15 }, - [13552] = { name = L["serpent sting"], rank = 5, duration = 15 }, - [13553] = { name = L["serpent sting"], rank = 6, duration = 15 }, - [13554] = { name = L["serpent sting"], rank = 7, duration = 15 }, - [13555] = { name = L["serpent sting"], rank = 8, duration = 15 }, - [25295] = { name = L["serpent sting"], rank = 9, duration = 15 }, + [1978] = { name = L["serpent sting"], rank = 1, duration = 15, travelTime = true }, + [13549] = { name = L["serpent sting"], rank = 2, duration = 15, travelTime = true }, + [13550] = { name = L["serpent sting"], rank = 3, duration = 15, travelTime = true }, + [13551] = { name = L["serpent sting"], rank = 4, duration = 15, travelTime = true }, + [13552] = { name = L["serpent sting"], rank = 5, duration = 15, travelTime = true }, + [13553] = { name = L["serpent sting"], rank = 6, duration = 15, travelTime = true }, + [13554] = { name = L["serpent sting"], rank = 7, duration = 15, travelTime = true }, + [13555] = { name = L["serpent sting"], rank = 8, duration = 15, travelTime = true }, + [25295] = { name = L["serpent sting"], rank = 9, duration = 15, travelTime = true }, - [3034] = { name = L["viper sting"], rank = 1, duration = 8 }, - [14279] = { name = L["viper sting"], rank = 2, duration = 8 }, - [14280] = { name = L["viper sting"], rank = 3, duration = 8 }, + [3034] = { name = L["viper sting"], rank = 1, duration = 8, travelTime = true }, + [14279] = { name = L["viper sting"], rank = 2, duration = 8, travelTime = true }, + [14280] = { name = L["viper sting"], rank = 3, duration = 8, travelTime = true }, [2974] = { name = L["wing clip"], rank = 1, duration = 10 }, [14267] = { name = L["wing clip"], rank = 2, duration = 10 }, [14268] = { name = L["wing clip"], rank = 3, duration = 10 }, - [5116] = { name = L["concussive shot"], rank = 1, duration = 4 }, + [5116] = { name = L["concussive shot"], rank = 1, duration = 4, travelTime = true }, - [19386] = { name = L["wyvern sting"], rank = 1, duration = 12 }, - [24132] = { name = L["wyvern sting"], rank = 2, duration = 12 }, - [24133] = { name = L["wyvern sting"], rank = 3, duration = 12 }, + [19386] = { name = L["wyvern sting"], rank = 1, duration = 12, travelTime = true }, + [24132] = { name = L["wyvern sting"], rank = 2, duration = 12, travelTime = true }, + [24133] = { name = L["wyvern sting"], rank = 3, duration = 12, travelTime = true }, [19306] = { name = L["counterattack"], rank = 1, duration = 5 }, [20909] = { name = L["counterattack"], rank = 2, duration = 5 }, diff --git a/spells/warlock.lua b/spells/warlock.lua index eeb98e2..2b71619 100644 --- a/spells/warlock.lua +++ b/spells/warlock.lua @@ -60,9 +60,9 @@ function getWarlockSpells() [11668] = { name = L["immolate"], rank = 7, duration = 15 }, [25309] = { name = L["immolate"], rank = 8, duration = 15 }, - [6789] = { name = L["death coil"], rank = 1, duration = 3 }, - [17925] = { name = L["death coil"], rank = 2, duration = 3 }, - [17926] = { name = L["death coil"], rank = 3, duration = 3 }, + [6789] = { name = L["death coil"], rank = 1, duration = 3, travelTime = true }, + [17925] = { name = L["death coil"], rank = 2, duration = 3, travelTime = true }, + [17926] = { name = L["death coil"], rank = 3, duration = 3, travelTime = true }, [710] = { name = L["banish"], rank = 1, duration = 20 }, [18647] = { name = L["banish"], rank = 2, duration = 30 }, diff --git a/ui.lua b/ui.lua index 612e294..12ffc35 100644 --- a/ui.lua +++ b/ui.lua @@ -1,4 +1,4 @@ -if not Cursive.superwow then +if not Cursive.nampower then return end @@ -184,6 +184,7 @@ ui.BarEnter = function() end this.parent.hover = true + SetMouseoverUnit(this.parent.guid) GameTooltip_SetDefaultAnchor(GameTooltip, this) GameTooltip:SetUnit(this.parent.guid) GameTooltip:Show() @@ -191,6 +192,7 @@ end ui.BarLeave = function() this.parent.hover = false + SetMouseoverUnit() GameTooltip:Hide() end diff --git a/utils.lua b/utils.lua index 4ac74aa..9b38faf 100644 --- a/utils.lua +++ b/utils.lua @@ -1,4 +1,4 @@ -if not Cursive.superwow then +if not Cursive.nampower then return end