mirror of
https://github.com/brues-code/pfUI.git
synced 2026-09-22 07:36:56 +00:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 967487e283 | |||
| ab11096b01 | |||
| 0402db380a | |||
| 68fe7e22db | |||
| a93e9f530c | |||
| 3be0585039 | |||
| ee7c729bcb | |||
| 62e0d4eb07 | |||
| a4ceb8ba53 | |||
| 80b677a6f8 | |||
| f6683b31af | |||
| b47df9272d | |||
| c0b717878d | |||
| 31c95606d0 | |||
| 3fe072c594 | |||
| 9706a74d16 | |||
| f9b0b5983a | |||
| 176e131d6b | |||
| a17229abb1 | |||
| e0cf01772c | |||
| c2d4106170 | |||
| dbfd337d42 | |||
| d834be9f0b | |||
| 82a07e1e35 | |||
| 703d7770ca | |||
| bd722b0f0f | |||
| e5c51fff48 | |||
| 6570afb70a | |||
| 0f97e8b0bd | |||
| 64c62a2a25 | |||
| 4d316ed43a | |||
| db0195f0f7 | |||
| 3d2d5f9254 | |||
| dfbeeb454a | |||
| 439d5a397a | |||
| 5524ad3b91 | |||
| 0f59a3a9b5 | |||
| ffdf376ac4 | |||
| 9bdde06150 | |||
| 8bf6672114 | |||
| 29b948e6fa | |||
| 5f61f94a24 | |||
| 65678da3d2 | |||
| 9b7aca8541 | |||
| 60525eec3a | |||
| c3829c2bd2 | |||
| 28de6fd835 | |||
| fc75e091a4 | |||
| ef478b0552 | |||
| 760d8992b6 | |||
| 62a99ac410 | |||
| deeec89955 | |||
| cca9771ef0 | |||
| 371872bb90 | |||
| 591045a606 | |||
| f657a9e7e8 | |||
| 0cbd9ecf98 | |||
| 6ca482fcaa | |||
| 8902d43f40 | |||
| 8f006c0643 | |||
| de7dacf788 | |||
| e0bcbbf2dc | |||
| 65bbea6fa9 | |||
| 822c873132 | |||
| 96fd486187 | |||
| b79132b9f4 | |||
| d62a1222df | |||
| be6ae25315 | |||
| d6951b386e | |||
| fd21b24a74 | |||
| 90ac4f9de0 |
+2
-2
@@ -1,2 +1,2 @@
|
||||
github: [shagu]
|
||||
ko_fi: shagu
|
||||
github: [brues]
|
||||
buy_me_a_coffee: brues
|
||||
|
||||
@@ -4,16 +4,18 @@ about: Report a bug or issue with pfUI
|
||||
title: "[Bug Description] "
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Please fill out ALL fields below. Issues without this info may be closed. -->
|
||||
|
||||
**pfUI Branch:**
|
||||
- [ ] Master
|
||||
- [ ] experiment
|
||||
**pfUI Version:**
|
||||
|
||||
**Your ClassicAPI Version:**
|
||||
|
||||
**Your Nampower Version:**
|
||||
|
||||
|
||||
**Your Nampower Version:** (e.g. latest, or specific commit/date)
|
||||
<!-- Skip if using Compat branch -->
|
||||
|
||||
**Other Addons installed:**
|
||||
<!-- List any other addons you are using, or "none" -->
|
||||
@@ -45,4 +47,3 @@ Paste error log here
|
||||
## Screenshot / Video
|
||||
<!-- Attach a screenshot or video showing the issue. This is REQUIRED. -->
|
||||
<!-- Drag & drop images here or paste a link -->
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ about: Suggest a new feature or improvement for pfUI
|
||||
title: "[Feature] "
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Feature Description
|
||||
@@ -20,4 +21,3 @@ assignees: ''
|
||||
|
||||
## Examples
|
||||
<!-- If possible, show examples from other addons or screenshots/mockups of how it could look -->
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
- Hard dependency on [ClassicAPI](https://github.com/brues-code/ClassicAPI) — modern `C_*` namespaces and engine polyfills (focus, nameplates, GUIDs).
|
||||
- TBC/Wrath/expansion plumbing removed; vanilla 1.12 + Turtle WoW only.
|
||||
- Nameplate overhaul — GUID-keyed caches, per-tick allocation cuts, name-collision filtering, configurable name text position.
|
||||
- Unit auras rebuilt on `C_UnitAuras` — replaces the old tooltip-scraping aura tracker with structured engine data, so buff/debuff durations, stack counts, and source attribution are accurate without polling.
|
||||
- Equipment manager — new backport of Blizzard's gear-set UI, integrated into the character pane. Save, swap, and edit sets without a third-party addon.
|
||||
- Bag sorting — new built-in feature (previously pfUI only deferred to third-party sorters).
|
||||
- `/focus` and `/clearfocus` moved to ClassicAPI; `/focusname` retained in pfUI.
|
||||
- questitem refactored onto `C_QuestLog`.
|
||||
- `updatenotify` isolated to its own `pfUI-brues` addon-message prefix so we don't share traffic with upstream pfUI installs.
|
||||
|
||||
See full commit history at https://github.com/brues-code/pfUI/commits/master for details.
|
||||
+14
-34
@@ -28,14 +28,6 @@ function pfUI.api.HasNampower()
|
||||
return GetNampowerVersion and true or false
|
||||
end
|
||||
|
||||
local isTurtleWoW
|
||||
function pfUI.api.IsTurtleWoW()
|
||||
if isTurtleWoW == nil then
|
||||
isTurtleWoW = C_Spell.GetSpellTexture(46050) == "Interface\\Icons\\Trade_Survival"
|
||||
end
|
||||
return isTurtleWoW
|
||||
end
|
||||
|
||||
-- [ GetUnitDistance ]
|
||||
-- Returns distance to unit using best available method
|
||||
-- 'unit1' [string] first unit (default: "player")
|
||||
@@ -193,10 +185,7 @@ end
|
||||
-- name [string] The localized name of the buff.
|
||||
-- return: [bool] true if unit has buff otherwise "nil"
|
||||
function pfUI.api.UnitHasBuff(unit, name)
|
||||
for _, aura in ipairs(C_UnitAuras.GetUnitAuras(unit, "HELPFUL")) do
|
||||
if aura.name == name then return true end
|
||||
end
|
||||
return nil
|
||||
return C_UnitAuras.GetAuraDataBySpellName(unit, name, "HELPFUL") ~= nil or nil
|
||||
end
|
||||
|
||||
-- [ GetUnbuffedRoster ]
|
||||
@@ -211,7 +200,7 @@ function pfUI.api.GetUnbuffedRoster(name)
|
||||
end
|
||||
end
|
||||
|
||||
if UnitInRaid("player") then
|
||||
if IsInRaid() then
|
||||
for i=1,40 do check("raid"..i) end
|
||||
else
|
||||
check("player")
|
||||
@@ -437,13 +426,17 @@ function pfUI.api.GetBagFamily(bag)
|
||||
|
||||
local id = GetInventoryItemID("player", ContainerIDToInventoryID(bag))
|
||||
if id then
|
||||
local _, _, _, _, _, itemType, subType = GetItemInfo(id)
|
||||
local bagsubtype = L["bagtypes"][subType]
|
||||
|
||||
if bagsubtype == "DEFAULT" then return "BAG" end
|
||||
if bagsubtype == "SOULBAG" then return "SOULBAG" end
|
||||
if bagsubtype == "QUIVER" then return "QUIVER" end
|
||||
if bagsubtype == nil then return "SPECIAL" end
|
||||
-- classID 1 = Container (bags), 11 = Quiver
|
||||
-- Container subclasses: 0 = Bag (default), 1 = Soul Bag, 2+ = specialty (herb/enchanting/etc.)
|
||||
-- Quiver subclasses: 2 = Quiver (arrows), 3 = Ammo Pouch (bullets)
|
||||
local _, _, _, _, _, classID, subClassID = C_Item.GetItemInfoInstant(id)
|
||||
if classID == 1 then
|
||||
if subClassID == 0 then return "BAG" end
|
||||
if subClassID == 1 then return "SOULBAG" end
|
||||
return "SPECIAL"
|
||||
elseif classID == 11 then
|
||||
return "QUIVER"
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
@@ -488,7 +481,7 @@ end
|
||||
-- 'msg' [string] the message to send
|
||||
function pfUI.api.SendChatMessageWide(msg)
|
||||
local channel = "SAY"
|
||||
if UnitInRaid("player") then
|
||||
if IsInRaid() then
|
||||
if ( IsRaidLeader() or IsRaidOfficer() ) then
|
||||
channel = "RAID_WARNING"
|
||||
else
|
||||
@@ -549,19 +542,6 @@ do -- create a scope so we don't have to worry about upvalue collisions
|
||||
end
|
||||
end
|
||||
|
||||
-- [ HookScript ]
|
||||
-- Securely post-hooks a script handler.
|
||||
-- 'f' [frame] the frame which needs a hook
|
||||
-- 'script' [string] the handler to hook
|
||||
-- 'func' [function] the function that should be added
|
||||
function HookScript(f, script, func)
|
||||
local prev = f:GetScript(script)
|
||||
f:SetScript(script, function(a1,a2,a3,a4,a5,a6,a7,a8,a9)
|
||||
if prev then prev(a1,a2,a3,a4,a5,a6,a7,a8,a9) end
|
||||
func(a1,a2,a3,a4,a5,a6,a7,a8,a9)
|
||||
end)
|
||||
end
|
||||
|
||||
-- [ HookAddonOrVariable ]
|
||||
-- Sets a function to be called automatically once an addon gets loaded
|
||||
-- 'addon' [string] addon or variable name
|
||||
|
||||
+22
-3
@@ -714,8 +714,11 @@ function pfUI:LoadConfig()
|
||||
pfUI:UpdateConfig("castbar", "player", "txtleftoffy", "0")
|
||||
pfUI:UpdateConfig("castbar", "player", "showlag", "0")
|
||||
pfUI:UpdateConfig("castbar", "player", "showrank", "0")
|
||||
pfUI:UpdateConfig("castbar", "player", "mergetradeskill", "0")
|
||||
pfUI:UpdateConfig("castbar", "player", "txtrightoffx", "0")
|
||||
pfUI:UpdateConfig("castbar", "player", "txtrightoffy", "0")
|
||||
pfUI:UpdateConfig("castbar", "player", "namealign", "LEFT")
|
||||
pfUI:UpdateConfig("castbar", "player", "timealign", "RIGHT")
|
||||
pfUI:UpdateConfig("castbar", "target", "hide_pfui", "0")
|
||||
pfUI:UpdateConfig("castbar", "target", "width", "-1")
|
||||
pfUI:UpdateConfig("castbar", "target", "height", "-1")
|
||||
@@ -728,6 +731,8 @@ function pfUI:LoadConfig()
|
||||
pfUI:UpdateConfig("castbar", "target", "showrank", "0")
|
||||
pfUI:UpdateConfig("castbar", "target", "txtrightoffx", "0")
|
||||
pfUI:UpdateConfig("castbar", "target", "txtrightoffy", "0")
|
||||
pfUI:UpdateConfig("castbar", "target", "namealign", "LEFT")
|
||||
pfUI:UpdateConfig("castbar", "target", "timealign", "RIGHT")
|
||||
pfUI:UpdateConfig("castbar", "focus", "hide_pfui", "0")
|
||||
pfUI:UpdateConfig("castbar", "focus", "width", "-1")
|
||||
pfUI:UpdateConfig("castbar", "focus", "height", "-1")
|
||||
@@ -740,6 +745,8 @@ function pfUI:LoadConfig()
|
||||
pfUI:UpdateConfig("castbar", "focus", "showrank", "0")
|
||||
pfUI:UpdateConfig("castbar", "focus", "txtrightoffx", "0")
|
||||
pfUI:UpdateConfig("castbar", "focus", "txtrightoffy", "0")
|
||||
pfUI:UpdateConfig("castbar", "focus", "namealign", "LEFT")
|
||||
pfUI:UpdateConfig("castbar", "focus", "timealign", "RIGHT")
|
||||
pfUI:UpdateConfig("castbar", nil, "use_unitfonts", "0")
|
||||
|
||||
pfUI:UpdateConfig("tooltip", nil, "position", "chat")
|
||||
@@ -747,6 +754,7 @@ function pfUI:LoadConfig()
|
||||
pfUI:UpdateConfig("tooltip", nil, "cursoroffset", "20")
|
||||
pfUI:UpdateConfig("tooltip", nil, "extguild", "1")
|
||||
pfUI:UpdateConfig("tooltip", nil, "itemid", "0")
|
||||
pfUI:UpdateConfig("tooltip", nil, "movespeed", "0")
|
||||
pfUI:UpdateConfig("tooltip", nil, "alpha", "0.8")
|
||||
pfUI:UpdateConfig("tooltip", nil, "alwaysperc", "0")
|
||||
pfUI:UpdateConfig("tooltip", "compare", "basestats", "1")
|
||||
@@ -817,7 +825,7 @@ function pfUI:LoadConfig()
|
||||
pfUI:UpdateConfig("nameplates", nil, "selfdebuff", "0")
|
||||
pfUI:UpdateConfig("nameplates", nil, "showdebuffs_hostile", "1")
|
||||
pfUI:UpdateConfig("nameplates", nil, "showdebuffs_friendly", "0")
|
||||
pfUI:UpdateConfig("nameplates", nil, "guessdebuffs", "1")
|
||||
pfUI:UpdateConfig("nameplates", nil, "owndebuffs", "0")
|
||||
pfUI:UpdateConfig("nameplates", nil, "clickthrough", "0")
|
||||
pfUI:UpdateConfig("nameplates", nil, "rightclick", "1")
|
||||
pfUI:UpdateConfig("nameplates", nil, "clickthreshold", "0.5")
|
||||
@@ -1370,7 +1378,6 @@ function pfUI:MigrateConfig()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Remove "Show only own debuffs" from unitframes and nameplates
|
||||
-- (feature was removed; only Target Debuff Bar in buffwatch keeps it)
|
||||
if pfUI_config.nameplates then
|
||||
@@ -1386,5 +1393,17 @@ function pfUI:MigrateConfig()
|
||||
end
|
||||
end
|
||||
|
||||
pfUI_config.version = pfUI.version.string
|
||||
-- Stamp the version we've migrated to. Dev / git-cloned builds carry
|
||||
-- "@project-version@" in the toc, which pfUI.lua normalizes to "dev" —
|
||||
-- writing that here would have checkversion() parse it back as 0/0/0 on
|
||||
-- the next /reload and re-fire every migration block, clobbering user
|
||||
-- toggles (notably the >3.6.1 buff migration that rewrites
|
||||
-- buffs.{buffs,debuffs,weapons} from the deprecated global.hide{,w}buff
|
||||
-- knobs). Stamp a far-future sentinel instead so checkversion() can't
|
||||
-- flip true again on subsequent loads.
|
||||
if pfUI.version.string == "dev" then
|
||||
pfUI_config.version = "999.999.999"
|
||||
else
|
||||
pfUI_config.version = pfUI.version.string
|
||||
end
|
||||
end
|
||||
+5
-8
@@ -569,7 +569,7 @@ end
|
||||
-- 'frame' [frame] the modelframe that should be used
|
||||
function pfUI.api.EnableClickRotate(frame)
|
||||
frame:EnableMouse(true)
|
||||
HookScript(frame, "OnUpdate", function()
|
||||
frame:HookScript("OnUpdate", function()
|
||||
if this.rotate then
|
||||
local x,_ = GetCursorPosition()
|
||||
if this.curx > x then
|
||||
@@ -582,14 +582,14 @@ function pfUI.api.EnableClickRotate(frame)
|
||||
end
|
||||
end)
|
||||
|
||||
HookScript(frame, "OnMouseDown", function()
|
||||
frame:HookScript("OnMouseDown", function()
|
||||
if arg1 == "LeftButton" then
|
||||
this.rotate = true
|
||||
this.curx, this.cury = GetCursorPosition()
|
||||
end
|
||||
end)
|
||||
|
||||
HookScript(frame, "OnMouseUp", function()
|
||||
frame:HookScript("OnMouseUp", function()
|
||||
this.rotate, this.curx, this.cury = nil, nil, nil
|
||||
end)
|
||||
end
|
||||
@@ -615,7 +615,6 @@ function pfUI.api.SetHighlight(frame, cr, cg, cb)
|
||||
frame.rr, frame.rg, frame.rb, frame.ra = GetStringColor(pfUI_config.appearance.border.color)
|
||||
|
||||
if not frame.pfEnterLeave then
|
||||
if not frame.HookScript then frame.HookScript = HookScript end
|
||||
local enter, leave = frame:GetScript("OnEnter"), frame:GetScript("OnLeave")
|
||||
|
||||
if enter then
|
||||
@@ -844,7 +843,6 @@ function pfUI.api.SkinScrollbar(frame, always)
|
||||
-- always show parent frame
|
||||
if always then
|
||||
RunOOC(function()
|
||||
if not parent.HookScript then parent.HookScript = HookScript end
|
||||
parent:HookScript("OnHide", function() this:Show() end)
|
||||
end)
|
||||
end
|
||||
@@ -1172,10 +1170,9 @@ function pfUI.api.CreateQuestionDialog(text, yes, no, editbox, onclose)
|
||||
local width = 200
|
||||
|
||||
-- delay the auto sizing, to make sure the font rendering happened
|
||||
question:SetScript("OnUpdate", function()
|
||||
RunNextFrame(function()
|
||||
if question.text:GetStringWidth() > width then width = question.text:GetStringWidth() end
|
||||
question:SetWidth( width + 2*padding)
|
||||
this:SetScript("OnUpdate", nil)
|
||||
question:SetWidth(width + 2*padding)
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
+46
-60
@@ -73,8 +73,8 @@ end
|
||||
|
||||
local function BuffOnClick()
|
||||
if this:GetParent().label == "player" then
|
||||
local bid = GetPlayerBuff(PLAYER_BUFF_START_ID + this.id, "HELPFUL")
|
||||
if bid >= 0 then CancelPlayerBuff(bid) end
|
||||
local aura = C_UnitAuras.GetAuraDataByIndex("player", this.id, "HELPFUL")
|
||||
if aura and aura.spellId then C_Spell.CancelSpellByID(aura.spellId) end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -87,13 +87,16 @@ local function DebuffOnEnter()
|
||||
if this:GetParent().label ~= "player" then
|
||||
local parent = this:GetParent()
|
||||
|
||||
-- For "only own debuffs" mode: find the REAL slot by matching spell name AND caster
|
||||
if parent.config and parent.config.selfdebuff == "1" and libdebuff then
|
||||
local ownDebuffName = libdebuff:UnitOwnDebuff(unitstr, this.id)
|
||||
if ownDebuffName then
|
||||
-- selfdebuff filters the displayed list to player-cast harmful auras, but
|
||||
-- SetUnitAura's index has to be into the engine's full HARMFUL list. Look
|
||||
-- up the displayed aura via the PLAYER filter, then scan engine slots for
|
||||
-- one whose name + sourceGUID match.
|
||||
if parent.config and parent.config.selfdebuff == "1" then
|
||||
local ownAura = C_UnitAuras.GetAuraDataByIndex(unitstr, this.id, "HARMFUL|PLAYER")
|
||||
if ownAura then
|
||||
for gameSlot = 1, 16 do
|
||||
local gameName, _, _, _, _, _, _, gameCaster = libdebuff:UnitDebuff(unitstr, gameSlot)
|
||||
if gameName == ownDebuffName and gameCaster == "player" then
|
||||
local check = C_UnitAuras.GetDebuffDataByIndex(unitstr, gameSlot)
|
||||
if check and check.name == ownAura.name and check.sourceGUID == ownAura.sourceGUID then
|
||||
GameTooltip:SetUnitAura(unitstr, gameSlot, "HARMFUL")
|
||||
return
|
||||
end
|
||||
@@ -111,8 +114,8 @@ end
|
||||
|
||||
local function DebuffOnClick()
|
||||
if this:GetParent().label == "player" then
|
||||
local bid = GetPlayerBuff(PLAYER_BUFF_START_ID + this.id, "HARMFUL")
|
||||
if bid >= 0 then CancelPlayerBuff(bid) end
|
||||
local aura = C_UnitAuras.GetAuraDataByIndex("player", this.id, "HARMFUL")
|
||||
if aura and aura.spellId then C_Spell.CancelSpellByID(aura.spellId) end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -302,12 +305,12 @@ function pfUI.uf:UpdateVisibility()
|
||||
local id = self.cache_raid
|
||||
|
||||
-- always show self in raidframes
|
||||
if not UnitInRaid("player") and GetNumPartyMembers() == 0 and C.unitframes.selfinraid == "1" and id == 1 then
|
||||
if not IsInRaid() and IsInGroup() and C.unitframes.selfinraid == "1" and id == 1 then
|
||||
self.id = ""
|
||||
self.label = "player"
|
||||
|
||||
-- use raidframes for groups
|
||||
elseif not UnitInRaid("player") and GetNumPartyMembers() > 0 and C.unitframes.raidforgroup == "1" then
|
||||
elseif not IsInRaid() and IsInGroup() and C.unitframes.raidforgroup == "1" then
|
||||
if id == 1 then
|
||||
self.id = ""
|
||||
self.label = "player"
|
||||
@@ -344,12 +347,12 @@ function pfUI.uf:UpdateVisibility()
|
||||
-- frame shall not be visible
|
||||
visibility = "hide"
|
||||
self.visible = nil
|
||||
elseif C["unitframes"]["group"]["hide_in_raid"] == "1" and self.label and strsub(self.label,0,5) == "party" and UnitInRaid("player") then
|
||||
elseif C["unitframes"]["group"]["hide_in_raid"] == "1" and self.label and strsub(self.label,0,5) == "party" and IsInRaid() then
|
||||
-- hide group while in raid and option is set
|
||||
visibility = "hide"
|
||||
self.visible = nil
|
||||
elseif ( self.fname == "Group0" or self.fname == "PartyPet0" or self.fname == "Party0Target" )
|
||||
and (GetNumPartyMembers() <= 0 or (C["unitframes"]["group"]["hide_in_raid"] == "1" and UnitInRaid("player"))) then
|
||||
and (not IsInGroup() or (C["unitframes"]["group"]["hide_in_raid"] == "1" and IsInRaid())) then
|
||||
-- hide self in group if solo or hide in raid is set
|
||||
visibility = "hide"
|
||||
self.visible = nil
|
||||
@@ -1011,7 +1014,7 @@ function pfUI.uf.OnEvent()
|
||||
-- Raid frames: update_full is set by raid.lua GUID tracker
|
||||
elseif this.label == "pet" and event == "UNIT_PET" then
|
||||
this.update_full = true
|
||||
elseif this.label == "player" and (event == "PLAYER_AURAS_CHANGED" or event == "UNIT_INVENTORY_CHANGED") then
|
||||
elseif this.label == "player" and (event == "PLAYER_AURAS_CHANGED" or event == "PLAYER_EQUIPMENT_CHANGED") then
|
||||
this.update_aura = true
|
||||
elseif this.label == "pet" and event == "UNIT_HAPPINESS" then
|
||||
this.update_full = true
|
||||
@@ -1549,7 +1552,7 @@ function pfUI.uf:EnableEvents()
|
||||
f:RegisterEvent("UNIT_FACTION")
|
||||
f:RegisterEvent("UNIT_AURA") -- frame=buff, frame=debuff
|
||||
f:RegisterEvent("PLAYER_AURAS_CHANGED") -- label=player && frame=buff
|
||||
f:RegisterEvent("UNIT_INVENTORY_CHANGED") -- label=player && frame=buff
|
||||
f:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") -- label=player && frame=buff (ClassicAPI: weapon-enchant buffs)
|
||||
f:RegisterEvent("PARTY_MEMBERS_CHANGED") -- label=party, frame=leaderIcon
|
||||
f:RegisterEvent("PARTY_LEADER_CHANGED") -- frame=leaderIcon
|
||||
f:RegisterEvent("RAID_ROSTER_UPDATE") -- label=raidIcon
|
||||
@@ -1770,7 +1773,7 @@ function pfUI.uf:RefreshIndicators(unit)
|
||||
local unitstr = unit.label .. unit.id
|
||||
|
||||
if unit.leaderIcon then -- Leader Icon
|
||||
if unit.config.leadericon == "1" and UnitIsPartyLeader(unitstr) and ( GetNumPartyMembers() > 0 or GetNumRaidMembers() > 0 ) then
|
||||
if unit.config.leadericon == "1" and UnitIsPartyLeader(unitstr) and IsInGroup() then
|
||||
unit.leaderIcon:Show()
|
||||
else
|
||||
unit.leaderIcon:Hide()
|
||||
@@ -1899,6 +1902,8 @@ function pfUI.uf:RefreshUnit(unit, component)
|
||||
end
|
||||
if duration > 0 then
|
||||
CooldownFrame_SetTimer(unit.buffs[i].cd, start, duration, 1)
|
||||
else
|
||||
CooldownFrame_SetTimer(unit.buffs[i].cd, 0, 0, 0)
|
||||
end
|
||||
elseif aura.duration > 0 then
|
||||
local guid = UnitGUID(unitstr)
|
||||
@@ -1909,6 +1914,8 @@ function pfUI.uf:RefreshUnit(unit, component)
|
||||
else
|
||||
CooldownFrame_SetTimer(unit.buffs[i].cd, 0, 0, 0)
|
||||
end
|
||||
else
|
||||
CooldownFrame_SetTimer(unit.buffs[i].cd, 0, 0, 0)
|
||||
end
|
||||
else
|
||||
unit.buffs[i]:Hide()
|
||||
@@ -1971,16 +1978,15 @@ function pfUI.uf:RefreshUnit(unit, component)
|
||||
invert_h * ((row+buffrow)*(multiply*default_border + unit.config.debuffsize + 1) + (multiply*default_border + 1)))
|
||||
end
|
||||
|
||||
local aura
|
||||
if unit.label ~= "player" and selfdebuff == "1" then
|
||||
_, _, texture, stacks, dtype = libdebuff:UnitOwnDebuff(unitstr, i)
|
||||
-- selfdebuff narrows to player-cast harmful auras via the PLAYER filter.
|
||||
-- Player-frame debuffs aren't gated on it (it'd hide most party-applied
|
||||
-- effects on you).
|
||||
local filter = (unit.label ~= "player" and selfdebuff == "1") and "HARMFUL|PLAYER" or "HARMFUL"
|
||||
local aura = C_UnitAuras.GetAuraDataByIndex(unitstr, i, filter)
|
||||
if aura then
|
||||
texture, stacks, dtype = aura.icon, aura.applications, aura.dispelName
|
||||
else
|
||||
aura = C_UnitAuras.GetDebuffDataByIndex(unitstr, i)
|
||||
if aura then
|
||||
texture, stacks, dtype = aura.icon, aura.applications, aura.dispelName
|
||||
else
|
||||
texture, stacks, dtype = nil, 0, nil
|
||||
end
|
||||
texture, stacks, dtype = nil, 0, nil
|
||||
end
|
||||
|
||||
unit.debuffs[i].texture:SetTexture(texture)
|
||||
@@ -1992,8 +1998,9 @@ function pfUI.uf:RefreshUnit(unit, component)
|
||||
unit.debuffs[i]:Show()
|
||||
|
||||
if aura and aura.expirationTime > 0 then
|
||||
-- Player path: real engine expirationTime. Cap start to now to keep pfUI's
|
||||
-- cooldown text out of the 2^32-wraparound branch for talent-extended debuffs.
|
||||
-- Cap start to now so talent-extended debuffs (expirationTime past
|
||||
-- the dbc base duration) don't push start into the future and trip
|
||||
-- CooldownFrame_SetTimer's 2^32-wraparound branch.
|
||||
local now = GetTime()
|
||||
local start = aura.expirationTime - aura.duration
|
||||
local duration = aura.duration
|
||||
@@ -2002,17 +2009,11 @@ function pfUI.uf:RefreshUnit(unit, component)
|
||||
end
|
||||
if duration > 0 then
|
||||
CooldownFrame_SetTimer(unit.debuffs[i].cd, start, duration, 1)
|
||||
else
|
||||
CooldownFrame_SetTimer(unit.debuffs[i].cd, 0, 0, 0)
|
||||
end
|
||||
elseif libdebuff and selfdebuff == "1" then
|
||||
local _, _, _, _, _, duration, timeleft = libdebuff:UnitOwnDebuff(unitstr, i)
|
||||
if duration and timeleft then
|
||||
CooldownFrame_SetTimer(unit.debuffs[i].cd, GetTime() + timeleft - duration, duration, 1)
|
||||
end
|
||||
elseif libdebuff then
|
||||
local _, _, _, _, _, duration, timeleft = libdebuff:UnitDebuff(unitstr, i)
|
||||
if duration and timeleft then
|
||||
CooldownFrame_SetTimer(unit.debuffs[i].cd, GetTime() + timeleft - duration, duration, 1)
|
||||
end
|
||||
else
|
||||
CooldownFrame_SetTimer(unit.debuffs[i].cd, 0, 0, 0)
|
||||
end
|
||||
|
||||
if stacks > 1 then
|
||||
@@ -2198,29 +2199,16 @@ function pfUI.uf:RefreshUnit(unit, component)
|
||||
end
|
||||
end
|
||||
|
||||
local debuffFilter = unit.config.selfdebuff == "1" and "HARMFUL|PLAYER" or "HARMFUL"
|
||||
for i=1,16 do -- scan for custom debuffs
|
||||
local aura = C_UnitAuras.GetDebuffDataByIndex(unitstr, i)
|
||||
local aura = C_UnitAuras.GetAuraDataByIndex(unitstr, i, debuffFilter)
|
||||
if aura then
|
||||
local name = aura.name
|
||||
local texture = aura.icon
|
||||
local timeleft = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or nil
|
||||
|
||||
-- libdebuff provides caster correlation + reconstructed timer for non-player units
|
||||
if libdebuff then
|
||||
if unit.config.selfdebuff == "1" then
|
||||
name, _, texture, _, _, _, timeleft = libdebuff:UnitOwnDebuff(unitstr, i)
|
||||
else
|
||||
name, _, texture, _, _, _, timeleft = libdebuff:UnitDebuff(unitstr, i)
|
||||
end
|
||||
end
|
||||
|
||||
if name then
|
||||
for _, filter in pairs(unit.indicator_custom) do
|
||||
if filter == string.lower(name) then
|
||||
pfUI.uf:AddIcon(unit, pos, texture, timeleft, aura.applications)
|
||||
pos = pos + 1
|
||||
break
|
||||
end
|
||||
for _, filter in pairs(unit.indicator_custom) do
|
||||
if filter == string.lower(aura.name) then
|
||||
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft, aura.applications)
|
||||
pos = pos + 1
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2858,8 +2846,6 @@ function pfUI.uf:GetStatusValue(unit, pos)
|
||||
-- Use libhealth for mob health estimation (overrides Nampower/Standard)
|
||||
if pfUI.libhealth and pfUI.libhealth.enabled then
|
||||
rhp, rhpmax = pfUI.libhealth:GetUnitHealth(unitstr)
|
||||
elseif unit.label == "target" and (MobHealth3 or MobHealthFrame) and MobHealth_GetTargetCurHP() then
|
||||
rhp, rhpmax = MobHealth_GetTargetCurHP(), MobHealth_GetTargetMaxHP()
|
||||
end
|
||||
|
||||
if config == "unit" then
|
||||
|
||||
+10
-31
@@ -2,11 +2,6 @@
|
||||
setfenv(1, pfUI:GetEnvironment())
|
||||
|
||||
-- [[ Constants ]]--
|
||||
CASTBAR_EVENT_CAST_DELAY = "SPELLCAST_DELAYED"
|
||||
CASTBAR_EVENT_CHANNEL_DELAY = "SPELLCAST_CHANNEL_UPDATE"
|
||||
CASTBAR_EVENT_CAST_START = "SPELLCAST_START"
|
||||
CASTBAR_EVENT_CHANNEL_START = "SPELLCAST_CHANNEL_START"
|
||||
|
||||
EVENTS_MINIMAP_ZONE_UPDATE = {"PLAYER_ENTERING_WORLD", "MINIMAP_ZONE_CHANGED"}
|
||||
|
||||
MICRO_BUTTONS = {
|
||||
@@ -33,32 +28,16 @@ ACTIONBAR_SECURE_TEMPLATE_BUTTON = nil
|
||||
UNITFRAME_SECURE_TEMPLATE = nil
|
||||
|
||||
--[[ Vanilla API Extensions ]]--
|
||||
function hooksecurefunc(tbl, name, func, prepend)
|
||||
if type(tbl) == "string" then
|
||||
prepend, func, name, tbl = func, name, tbl, _G
|
||||
end
|
||||
|
||||
if not tbl or not tbl[name] then return end
|
||||
|
||||
pfUI.hooks[tostring(func)] = {}
|
||||
pfUI.hooks[tostring(func)]["old"] = tbl[name]
|
||||
pfUI.hooks[tostring(func)]["new"] = func
|
||||
|
||||
if prepend then
|
||||
pfUI.hooks[tostring(func)]["function"] = function(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16)
|
||||
pfUI.hooks[tostring(func)]["new"](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16)
|
||||
return pfUI.hooks[tostring(func)]["old"](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16)
|
||||
end
|
||||
else
|
||||
pfUI.hooks[tostring(func)]["function"] = function(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16)
|
||||
local ok, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16 = pcall(pfUI.hooks[tostring(func)]["old"], a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16)
|
||||
if not ok then return end
|
||||
pfUI.hooks[tostring(func)]["new"](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16)
|
||||
return r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16
|
||||
end
|
||||
end
|
||||
|
||||
tbl[name] = pfUI.hooks[tostring(func)]["function"]
|
||||
-- Safe post-hook helper. The global `hooksecurefunc` belongs to ClassicAPI
|
||||
-- (its C implementation); this wrapper only adds pfUI's missing-target guard:
|
||||
-- ClassicAPI errors when target[name] isn't a function, whereas a lot of our
|
||||
-- call sites hook optional/late-loaded frames and rely on a silent no-op.
|
||||
-- Normalizes the string form, skips when the target is absent, then delegates
|
||||
-- to the C version (uncapped args, callback-pcall, taint parity).
|
||||
function pfUI.hooksecurefunc(tbl, name, func)
|
||||
if type(tbl) == "string" then tbl, name, func = _G, tbl, name end
|
||||
if not tbl or type(tbl[name]) ~= "function" then return end
|
||||
return _G.hooksecurefunc(tbl, name, func)
|
||||
end
|
||||
|
||||
do -- GetItemInfo
|
||||
|
||||
Vendored
-2620
File diff suppressed because it is too large
Load Diff
Vendored
-2634
File diff suppressed because it is too large
Load Diff
Vendored
-2628
File diff suppressed because it is too large
Load Diff
Vendored
-2625
File diff suppressed because it is too large
Load Diff
Vendored
-2591
File diff suppressed because it is too large
Load Diff
Vendored
-2628
File diff suppressed because it is too large
Load Diff
Vendored
-2612
File diff suppressed because it is too large
Load Diff
Vendored
-55
@@ -2417,58 +2417,3 @@ pfSellData = {
|
||||
[23725]=2600, [23727]=2600, [23728]=2600, [23743]=11, [24071]=393470, [24101]=400000,
|
||||
[24102]=400000, [24222]=221054, [24231]=211, [24232]=900, [24281]=1210, [24282]=18250,
|
||||
[24283]=152345, [24358]=365815 }
|
||||
pfMapOverlayData = {
|
||||
["Alterac"] = {"CHILLWINDPOINT:308:250:660:261", "CORRAHNSDAGGER:173:277:411:391", "CRUSHRIDGEHOLD:269:230:339:167:283:0", "DALARAN:277:283:39:275", "DANDREDSFOLD:273:224:282:0", "GALLOWSCORNER:183:185:415:287", "GAVINSNAZE:140:161:235:486", "GROWLESSCAVE:175:156:327:382", "LORDAMEREINTERNMENTCAMP:315:255:55:413", "MISTYSHORE:204:268:207:139", "RUINSOFALTERAC:252:249:274:201", "RAVENHOLDTMANOR:217:165:737:452", "SOFERASNAZE:241:312:469:311", "STRAHNBRAD:355:289:555:110", "THEHEADLAND:142:187:325:481", "THEUPLANDS:219:183:470:84",},
|
||||
["AlteracValley"] = {"DUNBALDAR:254:228:356:18:3299:3318", "FROSTWOLFKEEP:220:278:408:382:3299:3318", "ICEBLOODGARRISON:286:288:343:179:3299:3318",},
|
||||
["Arathi"] = {"BOULDERFISTHALL:190:214:442:371", "BOULDERGOR:222:214:244:167", "CIRCLEOFEASTBINDING:140:213:568:125", "CIRCLEOFINNERBINDING:186:164:302:321", "CIRCLEOFOUTERBINDING:149:137:429:301", "CIRCLEOFWESTBINDING:156:189:152:71", "DABYRIESFARMSTEAD:160:171:482:197", "FALDIRSCOVE:247:197:180:428", "FARWELLSTEAD:112:128:267:31", "GOSHEKFARM:208:177:539:284", "HAMMERFALL:182:235:665:128:318:0", "NORTHFOLDMANOR:202:205:204:119", "REFUGEPOINT:157:189:380:214", "RUINSOFZULRASAZ:160:160:346:29", "STROMGARDEKEEP:220:213:122:295", "THANDOLSPAN:175:198:369:424", "THORADINSWALL:173:225:103:148", "WITHERBARKVILLAGE:197:194:566:339", "WILDTUSKVILLAGE:183:102:379:145",},
|
||||
["Ashenvale"] = {"ASTRANAAR:190:173:281:259", "BOUGHSHADOW:140:190:862:157", "FALLENSKYLAKE:219:190:555:433:432:0", "FELFIREHILL:241:244:720:349", "FIRESCARSHRINE:153:161:194:332", "FORESTSONG:162:142:817:232", "IRISLAKE:185:190:401:226", "LAKEFALATHIM:115:181:138:144", "MAESTRASPOST:198:290:216:46", "MYSTRALLAKE:262:228:363:354:420:2359", "NIGHTRUN:212:244:604:263:431:0", "RAYNEWOODRETREAT:165:235:527:243", "SATYRNAAR:163:172:702:230", "THEHOWLINGVALE:196:172:471:147", "THERUINSOFSTARDUST:141:135:268:382", "THESHRINEOFAESSINA:205:179:113:268", "THEZORAMSTRAND:228:235:26:33", "THISTLEFURVILLAGE:243:184:210:164", "WARSONGLUMBERCAMP:185:146:804:319",},
|
||||
["Aszhara"] = {"BAYOFSTORMS:257:285:486:208", "BITTERREACHES:231:173:651:46", "FORLORNRIDGE:210:247:198:373", "HALDARRENCAMPMENT:191:138:83:339", "JAGGEDREEF:556:153:373:9", "LAKEMENNAR:301:184:302:437", "LEGASHENCAMPMENT:224:131:484:49", "RAVENCRESTMONUMENT:227:115:559:506", "RUINSOFELDARATH:251:268:246:228", "SHADOWSONGSHRINE:213:170:43:428", "SOUTHRIDGEBEACH:355:207:397:360", "TEMPLEOFARKKORAN:177:187:689:160", "THALASSIANBASECAMP:224:144:507:126", "THERUINEDREACHES:384:116:403:549", "THESHATTEREDSTRAND:148:199:412:202", "TIMBERMAWHOLD:209:194:258:175", "TOWEROFELDARA:107:145:824:114", "URSOLAN:219:212:327:94", "VALORMOK:201:158:93:236",},
|
||||
["Badlands"] = {"AGMONDSEND:249:256:354:396", "ANGORFORTRESS:186:241:327:101", "APOCRYPHANSREST:251:195:20:315", "CAMPBOFF:195:207:506:361", "CAMPCAGG:247:205:15:432:347:0", "CAMPKOSH:214:211:556:56", "CRYSTALLINEPINNACLE:140:155:609:337", "DUSTWINDGULCH:229:190:506:216", "HAMMERTOESDIGSITE:185:177:455:131", "KARGATH:232:245:0:154", "LETHLORRAVINE:344:395:627:173", "MIRAGEFLATS:276:229:153:387", "REDBRANDSDIGSITE:325:155:496:454", "RUINSOFCORTHAN:210:200:203:124", "SCALEBANERIDGE:205:179:751:142", "THEDUSTBOWL:253:266:166:203", "THEMAKERSTERRACE:230:172:399:16:1517:0", "VALLEYOFFANGS:215:215:358:265",},
|
||||
["Balor"] = {"BILGERATCOMPOUND:174:141:323:76", "CROAKINGPLATEAU:371:154:397:201", "GRAHANESTATE:93:116:306:392", "GULLWINGWRECKAGE:110:105:221:34", "LANGSTONORCHARD:102:129:410:346", "RUINSOFBREEZEHAVEN:163:141:450:256", "SCURRYINGTHICKET:144:158:283:161", "SIOUTPOST:175:232:597:389", "SORROWMORELAKE:174:140:303:277", "STORMBREAKERPOINT:243:197:583:212", "STORMREAVERSPIRE:151:119:491:429", "STORMWROUGHTCASTLE:202:166:458:332", "TREACHEROUSCRAGS:243:171:381:457", "VANDERFARMSTEAD:146:140:330:372", "WINDROCKCLIFFS:192:279:256:303",},
|
||||
["Barrens"] = {"AGAMAGOR:183:173:348:242", "ANCHORSEDGE:118:83:690:300", "BAELMODAN:120:123:435:482", "BLACKTHORNRIDGE:140:123:340:465", "BOULDERLODEMINE:109:105:560:0", "BRAMBLESCAR:112:152:446:304", "CAMPTAURAJO:130:113:370:355", "DREADMISTPEAK:121:93:423:68", "FARWATCHPOST:85:152:572:58", "FIELDOFGIANTS:194:137:363:410", "GROLDOMFARM:112:107:499:67", "HONORSSTAND:128:128:306:130", "LUSHWATEROASIS:160:169:372:186", "NORTHWATCHFOLD:134:107:533:310", "RAPTORGROUNDS:101:99:512:299", "RATCHET:116:118:556:190", "RAZORFENDOWNS:137:106:414:562", "RAZORFENKRAUL:116:123:345:539", "THECROSSROADS:142:140:436:127", "THEDRYHILLS:185:134:324:36", "THEFORGOTTENPOOLS:104:116:391:120", "THEMERCHANTCOAST:80:123:584:249", "THEMORSHANRAMPART:118:91:418:0", "THESLUDGEFEN:153:113:463:0", "THESTAGNANTOASIS:147:126:484:211", "THORNHILL:128:120:502:123",},
|
||||
["BlackstoneIsland"] = {"BLACKASHCOALPITS:303:474:209:178", "BLACKASHMINE:251:204:397:222", "GAZZIKSWORKSHOP:329:247:439:403", "RUSTGATELUMBERYARD:217:245:551:241", "RUSTGATERIDGE:396:226:372:360", "THEWATERHOLE:386:239:382:43", "VENTURECOSLUMS:270:192:498:129",},
|
||||
["BlastedLands"] = {"ALTAROFSTORMS:169:142:318:141", "DARKPORTAL:254:208:460:265", "DREADMAULHOLD:200:171:351:21", "DREADMAULPOST:228:183:370:204", "GARRISONARMORY:155:192:480:15", "NETHERGARDEKEEP:169:178:567:35", "RISEOFTHEDEFILER:150:131:414:131", "SERPENTSCOIL:211:155:510:148", "THETAINTEDSCAR:370:438:220:184",},
|
||||
["BurningSteppes"] = {"ALTAROFSTORMS:215:210:41:114", "BLACKROCKMOUNTAIN:249:272:178:104", "BLACKROCKPASS:259:299:597:285", "BLACKROCKSTRONGHOLD:232:258:342:120", "DRACODAR:405:309:61:262", "DREADMAULROCK:203:214:717:174", "MORGANSVIGIL:287:265:715:311", "PILLAROFASH:313:261:382:289", "RUINSOFTHAURISSAN:258:275:521:105", "TERRORWINGPATH:271:296:730:52",},
|
||||
["Darkshore"] = {"AMETHARAN:163:178:340:323", "AUBERDINE:134:179:323:191", "BASHALARAN:153:171:379:199", "CLIFFSPRINGRIVER:205:161:391:112:445:0", "GROVEOFTHEANCIENTS:180:138:314:436", "REMTRAVELSEXCAVATION:148:164:245:504", "RUINSOFMATHYSTRA:176:205:519:3", "THEMASTERSGLAIVE:146:139:346:529", "TOWEROFALTHALAXX:142:159:484:110",},
|
||||
["DeadwindPass"] = {"DEADMANSCROSSING:368:353:257:81:2938:0", "KARAZHAN:287:234:277:342", "THEVICE:254:257:433:305",},
|
||||
["Desolace"] = {"ETHELRETHOR:189:237:318:68", "GELKISVILLAGE:181:235:300:433", "KODOGRAVEYARD:253:237:396:251", "KOLKARVILLAGE:206:208:615:221", "KORMEKSHUT:156:147:562:187", "MAGRAMVILLAGE:182:272:606:373", "MANNOROCCOVEN:270:271:408:385", "NIJELSPOINT:188:246:561:0", "RANAZJARISLE:88:89:248:11", "SARGERON:274:237:632:38", "SHADOWBREAKRAVINE:190:181:698:454", "SHADOWPREYVILLAGE:215:218:175:395", "TETHRISARAN:185:137:440:0", "THUNDERAXEFORTRESS:174:207:455:108", "VALLEYOFSPEARS:234:274:217:221",},
|
||||
["DunMorogh"] = {"AMBERSTILLRANCH:105:104:582:288", "ANVILMAR:209:159:167:414:77:0", "BREWNALLVILLAGE:94:102:263:256", "CHILLBREEZEVALLEY:160:113:281:304", "COLDRIDGEPASS:134:123:300:387", "FROSTMANEHOLD:112:118:225:291", "GNOMERAGON:158:176:178:164", "GOLBOLARQUARRY:144:149:619:301", "HELMSBEDLAKE:131:153:706:284:716:0", "ICEFLOWLAKE:117:167:286:173", "IRONFORGE:291:187:414:166", "IRONFORGEAIRFIELDS:213:233:582:100", "KHARANOS:175:186:397:296:189:0", "MISTYPINEREFUGE:117:155:507:224", "NORTHERNGATEOUTPOST:115:155:766:177", "RUGFORDSMOUNTAINREST:199:114:739:406", "SHIMMERRIDGE:115:173:355:169", "SOUTHERNGATEOUTPOST:113:106:800:284", "THEGRIZZLEDDEN:178:155:321:328", "THETUNDRIDHILLS:135:115:531:328",},
|
||||
["Durotar"] = {"DRYGULCHRAVINE:139:134:438:92:410:0", "ECHOISLES:175:236:561:432", "KOLKARCRAG:131:96:426:487", "ORGRIMMAR:427:150:251:0", "RAZORHILL:188:207:445:182", "RAZORMANEGROUNDS:196:202:318:204", "SENJINVILLAGE:133:167:485:398", "SKULLROCK:118:79:469:51", "SPARKWATERPORT:122:134:522:94", "THUNDERRIDGE:156:174:341:72", "TIRAGARDEKEEP:160:148:477:300", "VALLEYOFTRIALS:186:184:370:335",},
|
||||
["Duskwood"] = {"ADDLESSTEAD:255:231:67:353", "BRIGHTWOODGROVE:191:315:520:131", "DARKSHIRE:291:259:644:173:576:0", "MANORMISTMANTLE:174:150:668:134", "RAVENHILL:166:115:117:317", "RAVENHILLCEMETARY:323:276:99:165:243:0", "THEDARKENEDBANK:886:184:105:44", "THEHUSHEDBANK:136:306:29:143", "THEROTTINGORCHARD:231:210:549:380", "THEYORGENFARMSTEAD:211:235:403:393", "TRANQUILGARDENSCEMETARY:204:196:692:363", "TWILIGHTGROVE:327:384:314:99", "VULGOLOGREMOUND:231:266:259:365",},
|
||||
["Dustwallow"] = {"ALCAZISLAND:176:179:671:29", "BACKBAYWETLANDS:384:243:248:196", "BRACKENWALLVILLAGE:264:264:239:2:507:0", "THEDENOFFLAME:246:235:264:320", "THERAMOREISLE:214:187:542:232", "THEWYRMBOG:263:229:372:387", "WITCHHILL:230:311:431:1:518:0", "WESTHAVENHOLLOW:166:149:603:428",},
|
||||
["EasternPlaguelands"] = {"BLACKWOODLAKE:217:221:451:207", "CORINSCROSSING:153:146:544:373", "CROWNGUARDTOWER:193:152:299:408", "DARROWSHIRE:195:167:316:497", "EASTWALLTOWER:164:143:600:250", "FORLORNSUMMIT:192:185:64:225", "LAKEMERELDAR:240:164:543:469", "LIGHTSHOPECHAPEL:165:238:721:304", "NORTHDALE:172:191:628:135", "NORTHPASSTOWER:228:178:465:118", "PESTILENTSCAR:189:241:416:349", "PLAGUEWOOD:346:258:178:92", "QUELLITHIENLODGE:216:136:430:44", "STRATHOLME:227:189:202:15", "TERRORDALE:175:129:86:137", "THEFUNGALVALE:202:199:277:267", "THEINFECTISSCAR:183:262:628:298", "THEMARRISSTEAD:182:194:164:367", "THENOXIOUSGLADE:211:202:730:172", "THEUNDERCROFT:171:136:178:484", "THONDRORILRIVER:210:348:12:235", "TYRSHAND:201:159:724:475", "ZULMASHAR:189:152:621:36",},
|
||||
["Elwynn"] = {"BRACKWELLPUMPKINPATCH:222:223:592:431", "CRYSTALLAKE:196:192:435:345", "EASTVALELOGGINGCAMP:237:190:713:339", "FARGODEEPMINE:238:230:247:438:63:64", "FORESTSEDGE:242:335:132:333:120:0", "GOLDSHIRE:219:199:261:280", "JERODSLANDING:206:212:437:445", "NORTHSHIREVALLEY:237:244:390:154", "RIDGEPOINTTOWER:287:207:710:445", "STONECAIRNLAKE:278:230:601:203", "STORMWIND:452:392:21:3", "TOWEROFAZORA:232:226:561:303:54:0",},
|
||||
["Felwood"] = {"BLOODVENOMFALLS:225:132:297:269", "DEADWOODVILLAGE:162:129:415:539", "EMERALDSANCTUARY:171:150:411:436", "FELPAWVILLAGE:224:138:489:0:1769:0", "IRONTREEWOODS:200:203:426:60", "JADEFIREGLEN:151:145:339:469", "JADEFIRERUN:181:159:338:35", "JAEDENAR:236:122:275:333", "MORLOSARAN:129:151:503:515", "RUINSOFCONSTELLAS:224:142:303:387", "SHATTERSCARVALE:218:186:315:130", "TALONBRANCHGLADE:145:130:554:97",},
|
||||
["Feralas"] = {"CAMPMOJACHE:146:148:694:238", "CHIMAERAROOSTVALE:106:150:785:321", "DIREMAUL:214:180:461:208", "DREAMBOUGH:141:122:459:0", "FERALSCARVALE:103:104:491:335", "FRAYFEATHERHIGHLANDS:101:163:485:391", "GORDUNNIOUTPOST:154:128:697:148", "GRIMTOTEMCOMPOUND:110:186:630:172", "ISLEOFDREAD:205:285:197:381", "LOWERWILDS:214:152:756:204", "ONEIROS:101:103:499:74", "RUINSOFISILDIEN:179:241:547:326", "RUINSOFRAVENWIND:178:149:310:0", "SARDORISLE:167:168:216:241", "THEFORGOTTENCOAST:132:316:409:258", "THETWINCOLOSSALS:278:235:323:78", "THEWRITHINGDEEP:214:205:625:305:2519:0",},
|
||||
["Gillijim"] = {"DEEPTIDESANCTUM:204:117:199:512", "DISTILLERYISLE:90:69:234:216", "FAELONSFOLLY:108:103:331:304", "GILLIJIMSTRAND:108:324:641:160", "JADEMINE:141:149:500:256", "KALKORPOINT:123:115:421:210", "KAZONISLAND:194:113:432:51", "MAULOGGPOST:137:227:512:401", "MAULOGGREFUGE:231:132:615:465", "RUINSOFZULRAZAR:215:136:369:384", "SILVERCOAST:245:184:311:386", "SILVERSANDBAR:47:128:256:398", "SOUTHSEASANDBAR:125:172:178:63", "TANGLEWOOD:135:188:576:297", "ZULRAZAR:137:168:375:282",},
|
||||
["Gilneas"] = {"BLACKTHORNSCAMP:160:163:32:171", "BROLOKMOUND:140:120:467:349", "DAWNSTONEMINE:130:144:292:155", "FREYSHEARKEEP:72:70:624:405", "GILNEASCITY:295:242:89:105", "GLAYMORESTEAD:140:127:483:129", "GREYMANESWATCH:119:158:580:311", "HOLLOWWEBCEMETARY:180:167:215:395", "HOLLOWWEBWOODS:171:118:341:452", "NORTHGATETOWER:130:137:397:172", "OLDROCKPASS:155:165:290:37", "RAVENSHIRE:194:237:472:431", "RAVENWOODKEEP:253:174:496:494", "ROSEWICKPLANTATION:134:140:331:155", "RUINSOFGREYSHIRE:192:144:230:260", "SHADEMORETAVERN:117:120:279:336", "SOUTHMIREORCHARD:202:133:302:354", "STILLWARDCHURCH:213:166:471:220", "THEDRYROCKMINE:129:127:145:262", "THEDRYROCKPIT:230:217:69:295", "THEGREYMANEWALL:131:173:412:48", "THEOVERGROWNACRE:134:154:390:269",},
|
||||
["GrimReaches"] = {"BAGGOTHSRAMPART:112:113:399:238", "BARLEYCRESTFARMSTEAD:116:88:499:222", "BRANGARSFOLLY:108:123:568:33", "DUNKITHAS:137:95:470:329", "EASTRIDGEOUTPOST:166:107:374:165", "GETHKAR:118:124:444:84", "GROLDANSEXCAVATION:125:117:567:231", "LAKEKITHAS:217:150:481:270", "RUINSOFSTOLGAZKEEP:134:146:366:53", "SALGAZMINES:150:119:535:380", "SHATTERBLADEPOST:186:130:512:126", "SLATEBEARDSFORGE:136:99:457:404", "THEGRIMHOLLOW:288:200:396:463", "THEHIGHPASS:97:141:409:315", "ZARMGETHPOINT:143:76:405:16", "ZARMGETHSTRONGHOLD:120:150:487:17",},
|
||||
["Hilsbrad"] = {"AZURELOADMINE:146:174:188:288", "DARROWHILL:173:132:426:164", "DUNGAROK:225:257:644:305", "DURNHOLDEKEEP:367:347:614:82", "EASTERNSTRAND:208:301:537:350", "HILLSBRADFIELDS:282:257:211:166", "NETHANDERSTEAD:190:215:554:249", "PURGATIONISLE:120:94:111:485", "SOUTHPOINTTOWER:271:207:11:203", "SOUTHSHORE:219:245:423:213", "TARRENMILL:195:291:524:9", "WESTERNSTRAND:272:139:215:377",},
|
||||
["Hinterlands"] = {"AERIEPEAK:240:195:20:250", "AGOLWATHA:188:180:382:173", "HIRIWATHA:201:118:188:314", "JINTHAALOR:223:277:514:338", "PLAGUEMISTRAVINE:124:203:170:158", "QUELDANILLODGE:168:181:248:194", "SERADANE:260:263:519:25", "SHADRAALOR:181:170:247:394", "SHAOLWATHA:265:190:580:247", "SKULKROCK:148:133:520:241", "THEALTAROFZUL:186:153:382:371", "THECREEPINGRUIN:168:157:417:268", "THEOVERLOOKCLIFFS:153:299:702:309", "THERASAZTRAILS:118:133:210:381", "VALORWINDLAKE:153:160:329:309",},
|
||||
["Hyjal"] = {"BARKSKINPLATEAU:207:167:390:297", "BARKSKINVILLAGE:502:293:417:346", "BLEAKHOLLOWCRATER:297:335:401:20", "CIRCLEOFPOWER:277:331:203:63", "DARKHOLLOWPASS:214:237:342:389", "NORDANAAR:139:146:778:118", "NORDRASSILGLADE:418:408:578:0", "RUINSOFTELENNAS:165:193:141:236", "THEEMERALDGATEWAY:309:244:109:353", "ZULHATHA:191:210:3:194",},
|
||||
["Icepoint"] = {"KANEQNUUN:512:430:237:129",},
|
||||
["Lapidis"] = {"BRIGHTCOAST:298:213:237:246", "CAELANSREST:147:111:517:230", "CROWNISLAND:140:95:512:33", "GORDOSHHEIGHTS:253:243:256:89", "HAZURRIGLADE:87:82:486:306", "SHANKSREEF:207:90:446:109", "THEROCK:74:72:687:149", "TOWEROFLAPIDIS:203:118:487:175", "WALLOWINGCOAST:240:184:505:326", "ZULHAZU:227:178:348:407",},
|
||||
["LochModan"] = {"GRIZZLEPAWRIDGE:262:343:326:325", "IRONBANDSEXCAVATIONSITE:321:294:491:276", "MOGROSHSTRONGHOLD:287:265:558:61", "NORTHGATEPASS:195:273:143:24:925:0", "SILVERSTREAMMINE:201:243:247:25", "STONESPLINTERVALLEY:226:255:230:363", "STONEWROUGHTDAM:260:149:354:23", "THEFARSTRIDERLODGE:162:182:741:298", "THELOCH:288:380:367:99", "THELSAMAR:243:205:225:214", "VALLEYOFKINGS:166:224:123:382",},
|
||||
["Moonglade"] = {"LAKEELUNEARA:530:484:256:101:2361:0",},
|
||||
["Moonwhisper"] = {"ANSHESRESPITE:170:120:398:81", "ANCESTRALGROUNDS:164:82:295:88", "BLACKROOTHOLD:162:137:495:465", "BLACKROOTVILLAGE:224:143:411:525", "FOULHEARTSANCTUM:157:149:496:232", "GROVEOFTHEMOON:152:146:464:347", "LUNARCLAWDEN:84:60:674:376", "MARASETHIL:96:94:640:417", "MOONHOOFRETREAT:172:144:419:167", "MOONHOOFVILLAGE:163:157:570:188", "MOONSILKHOLLOW:125:114:577:458", "MOROGAIVILLAGE:133:127:571:372", "NARVALISPOINT:162:139:503:118", "RUINSOFNENDIS:183:130:584:275", "STARSHARDCRADLE:122:109:334:121", "TYRANDAS:158:169:580:61", "VYSNAGOSASREST:163:102:420:8",},
|
||||
["Mulgore"] = {"BAELDUNDIGSITE:175:151:269:227", "BLOODHOOFVILLAGE:236:182:378:311", "PALEMANEROCK:102:183:315:317", "RAVAGEDCARAVAN:110:100:482:269", "REDCLOUDMESA:436:231:285:437:221:0", "REDROCKS:154:138:522:92", "SUNTAILPASS:129:116:533:18", "THEGOLDENPLAINS:184:217:441:91", "THEROLLINGPLAINS:231:166:536:369", "THEVENTURECOMINE:187:204:549:251", "THUNDERBLUFF:254:217:264:70", "THUNDERHORNWATERWELL:118:137:384:248", "WILDMANEWATERWELL:152:111:299:10", "WINDFURYRIDGE:182:119:405:2", "WINTERHOOFWATERWELL:144:107:470:379",},
|
||||
["Northwind"] = {"ABBEYGARDENS:250:224:512:33", "AMBERSHIRE:234:215:334:278", "AMBERWOODKEEP:243:205:187:308", "BLACKROCKBREACH:255:201:721:222", "BRISTLEWHISKERCAVERN:197:185:568:232", "CINDERFALLPASS:229:229:733:348", "CRAWFORDWINERY:160:222:513:285", "CRYSTALFALLS:320:205:627:464", "GRIMMENLAKE:218:210:550:352", "MERCHANTSHIGHROAD:353:249:256:420", "NORTHRIDGEPOINT:298:314:349:31", "NORTHWINDLOGGINGCAMP:224:185:258:171", "RUINSOFBIRKHAVEN:180:208:645:116", "SHERWOODQUARRY:293:287:702:1", "STILLHEARTPORT:174:155:116:217", "TOWEROFMAGILOU:181:223:189:169", "WITCHCOVEN:147:162:257:81",},
|
||||
["Redridge"] = {"ALTHERSMILL:222:260:407:134", "GALARDELLVALLEY:234:240:661:168:96:0", "LAKEEVERSTILL:522:260:140:249", "LAKERIDGEHIGHWAY:411:274:196:341", "LAKESHIRE:325:179:92:204", "REDRIDGECANYONS:345:232:132:79:98:0", "RENDERSCAMP:264:248:284:4:998:0", "RENDERSVALLEY:453:248:491:365", "REDWALLKEEP:162:110:830:466", "STONEWATCH:243:289:507:221:999:0", "STONEWATCHFALLS:308:200:601:325", "THREECORNERS:360:337:0:288",},
|
||||
["SearingGorge"] = {"BLACKCHARCAVE:264:224:84:371", "DUSTFIREVALLEY:446:355:428:11", "FIREWATCHRIDGE:385:413:95:39", "GRIMSILTDIGSITE:292:210:501:306", "TANNERCAMP:292:215:552:413", "THECAULDRON:414:310:257:178", "THESEAOFCINDERS:345:273:256:395",},
|
||||
["Silithus"] = {"HIVEASHI:431:276:305:34:3426:0", "HIVEREGAL:469:335:262:333:3427:0", "HIVEZORA:343:424:117:188", "SOUTHWINDVILLAGE:346:348:519:83:3077:0", "THECRYSTALVALE:285:274:121:31", "THESCARABWALL:271:145:125:523:2741:0", "TWILIGHTBASECAMP:297:238:355:206:3097:2739",},
|
||||
["Silverpine"] = {"AMBERMILL:204:210:512:279", "BERENSPERIL:206:146:508:433", "DEEPELEMMINE:127:143:484:274", "FENRISISLE:234:187:593:88:232:0", "MALDENSORCHARD:228:138:479:9:239:0", "NORTHTIDESHOLLOW:152:104:337:141", "OLSENSFARTHING:128:152:401:267", "PYREWOODVILLAGE:122:108:405:456", "SHADOWFANGKEEP:185:131:380:373", "THEDEADFIELD:141:134:419:82", "THEDECREPITFERRY:144:156:475:160", "THEGREYMANEWALL:175:185:398:465", "THESEPULCHER:193:135:360:184", "THESHININGSTRAND:225:192:475:27:227:0", "THESKITTERINGDARK:160:165:302:18",},
|
||||
["StonetalonMountains"] = {"AMANIALOR:504:252:8:5", "BAELHARDUL:357:252:512:257", "BLACKSANDOILFIELDS:504:504:8:5", "BOULDERSLIDERAVINE:90:76:586:547", "BRAMBLETHORNPASS:382:359:512:257", "BROKENCLIFFMINE:252:504:260:5", "CAMPAPARAJE:222:100:670:552", "GRIMTOTEMPOST:146:77:675:519", "MALAKAJIN:137:86:662:562", "MIRKFALLONLAKE:504:504:260:5", "POWDERTOWN:252:252:260:257", "SISHIRCANYON:252:264:512:257", "STONETALONPEAK:258:252:260:5", "SUNROCKRETREAT:504:252:260:257", "THECHARREDVALE:252:308:260:257", "THEEARTHENRING:504:252:260:257", "VENTURECOMPANYCAMP:252:504:260:5", "WEBWINDERPATH:256:324:512:256", "WINDSHEARCRAG:256:256:512:256:1277:0",},
|
||||
["Stranglethorn"] = {"BALALRUINS:83:65:245:101", "BALIAMAHRUINS:100:133:376:133", "BLOODSAILCOMPOUND:154:163:201:291", "BOOTYBAY:137:125:207:435:312:0", "CRYSTALVEINMINE:111:114:350:280", "GROMGOLBASECAMP:101:98:265:137", "JAGUEROISLE:132:107:319:498", "KALAIRUINS:86:89:303:93", "KURZENSCOMPOUND:141:139:394:5", "LAKENAZFERITI:124:120:332:60", "MISTVALEVALLEY:120:116:283:372", "MIZJAHRUINS:93:102:315:133", "MOSHOGGOGREMOUND:123:168:435:96", "NEKMANIWELLSPRING:80:102:215:365", "NESINGWARYSEXPEDITION:128:98:274:29", "REBELCAMP:164:86:289:0", "RUINSOFABORAZ:87:87:356:340", "RUINSOFJUBUWAL:100:105:312:304", "RUINSOFZULKUNDA:113:132:201:8", "RUINSOFZULMAMWE:162:122:397:213", "THEARENA:186:176:241:194", "THEVILEREEF:177:160:159:98", "VENTURECOBASECAMP:96:120:391:66", "WILDSHORE:151:175:237:431", "ZIATAJAIRUINS:127:120:364:231", "ZULGURUB:230:213:489:12", "ZUULDAIARUINS:103:105:160:47",},
|
||||
["SwampOfSorrows"] = {"FALLOWSANCTUARY:348:292:501:0", "ITHARIUSSCAVE:230:232:0:270", "MISTYREEDSTRAND:250:664:752:0:1978:0", "MISTYVALLEY:211:179:29:148", "POOLOFTEARS:280:256:576:227", "SORROWMURK:199:351:734:128", "SPLINTERSPEARJUNCTION:258:222:138:245", "STAGALBOG:328:238:561:386", "STONARD:343:303:287:244", "SORROWGUARDKEEP:220:174:0:266", "THEHARBORAGE:216:187:182:157", "THESHIFTINGMIRE:303:221:294:118",},
|
||||
["Tanaris"] = {"ABYSSALSANDS:194:159:376:207", "BROKENPILLAR:93:165:481:243", "CAVERNSOFTIME:139:132:567:262", "DUNEMAULCOMPOUND:186:127:337:298", "EASTMOONRUINS:143:130:405:355", "GADGETZAN:156:149:430:102", "LANDSENDBEACH:184:148:456:514", "LOSTRIGGERCOVE:141:173:635:230", "NOONSHADERUINS:129:191:530:43", "SANDSORROWWATCH:175:157:311:111", "SOUTHBREAKSHORE:192:151:510:304", "SOUTHMOONRUINS:185:196:324:366", "STEAMWHEEDLEPORT:136:129:599:86", "SLICKWICKOILRIG:237:181:129:436", "THEGAPINGCHASM:198:188:458:380", "THENOXIOUSLAIR:155:180:265:209", "THISTLESHRUBVALLEY:166:234:217:294", "VALLEYOFTHEWATCHERS:125:115:311:470", "WATERSPRINGFIELD:145:164:516:177", "ZALASHJISDEN:90:127:619:157", "ZULFARRAK:191:166:265:5",},
|
||||
["TelAbim"] = {"BIXXLESSTOREHOUSE:300:127:368:178", "HIGHVALERISE:191:187:468:248", "TAZZOSSHACK:229:232:453:391", "TELCOBASECAMP:184:183:342:440", "THEDERELICTCAMP:165:205:364:273", "THEJAGGEDISLES:313:256:385:16",},
|
||||
["Teldrassil"] = {"BANETHILHOLLOW:136:191:392:289", "DARNASSUS:294:252:110:251", "DOLANAAR:170:119:469:327", "GNARLPINEHOLD:230:158:316:417", "LAKEALAMETH:237:163:445:388", "POOLSOFARLITHRIEN:125:180:337:314", "RUTTHERANVILLAGE:110:91:504:548", "SHADOWGLEN:198:201:504:164", "STARBREEZEVILLAGE:153:175:588:303", "THEORACLEGLADE:150:223:279:135", "URSANHEIGHTS:229:154:243:423", "WELLSPRINGLAKE:166:245:383:98:265:0",},
|
||||
["ThalassianHighlands"] = {"ALAHTHALAS:470:276:429:38", "ANASTERIANPARK:229:219:385:256", "BRINTHILIEN:180:211:395:450", "FELSTRIDERRETREAT:201:207:165:428", "ISLEOFETERNALAUTUMN:204:167:236:117", "RUINSOFNASHALARAN:249:266:36:151", "SILVERSUNMINE:208:189:288:373", "THEFARSTRIDE:230:199:212:255", "THELASTRUNESTONE:179:203:507:379",},
|
||||
["ThousandNeedles"] = {"CAMPETHOK:289:300:7:1:2237:0", "DARKCLOUDPINNACLE:189:182:267:138", "FREEWINDPOST:193:173:366:273", "HIGHPERCH:178:174:39:164", "SPLITHOOFCRAG:193:175:400:203", "THEGREATLIFT:191:163:213:78", "THESCREECHINGCANYON:240:231:187:206", "THESHIMMERINGFLATS:312:355:612:308", "WINDBREAKCANYON:225:213:501:249",},
|
||||
["Tirisfal"] = {"AGAMANDMILLS:244:191:342:148", "BALNIRFARMSTEAD:179:153:639:335", "BRIGHTWATERLAKE:176:280:598:143", "BRILL:121:144:540:305", "BULWARK:184:169:712:374", "COLDHEARTHMANOR:136:125:479:328", "CRUSADEROUTPOST:154:121:701:293", "DEATHKNELL:210:186:236:337", "GARRENSHAUNT:148:199:507:154", "GLENSHIRE:163:161:157:393:5033:5039", "MONASTARY:175:157:762:139:161:0", "NIGHTMAREVALE:209:151:370:355", "RUINSOFLORDAERON:299:216:469:370", "SCARLETWATCHPOST:141:223:701:113", "SOLLIDENFARMSTEAD:225:138:247:259", "STEEPCLIFFPORT:121:120:5:350", "STILLWATERPOND:168:123:401:279", "THECORINTHFARMSTEAD:116:152:73:402:5041:0", "THEWHISPERINGFOREST:243:175:86:263:5035:0", "VENOMWEBVALE:199:183:776:225",},
|
||||
["UngoroCrater"] = {"FIREPLUMERIDGE:277:258:377:185", "GOLAKKAHOTSPRINGS:299:334:133:158", "IRONSTONEPLATEAU:276:281:588:69", "LAKKARITARPITS:553:255:170:13", "TERRORRUN:333:275:166:375", "THEMARSHLANDS:295:341:568:247", "THESLITHERINGSCAR:326:260:378:403",},
|
||||
["WesternPlaguelands"] = {"CAERDARROW:158:150:608:421", "DALSONSTEARS:209:138:388:271", "DARROWMERELAKE:359:261:510:348", "FELSTONEFIELD:149:119:306:315", "GAHRRONSWITHERING:165:191:527:257", "HEARTHGLEN:331:280:312:20", "NORTHRIDGELUMBERCAMP:205:168:390:170", "RUINSOFANDORHOL:275:219:265:360", "SORROWHILL:290:201:360:467", "THEBULWARK:216:172:143:300", "THEWEEPINGCAVE:148:192:572:203", "THEWRITHINGHAUNT:159:177:457:329", "THONDRORILRIVER:193:326:597:94",},
|
||||
["Westfall"] = {"ALEXSTONFARMSTEAD:275:182:220:274", "DEMONTSPLACE:166:155:226:391", "FURLBROWSPUMPKINFARM:186:191:399:24", "GOLDCOASTQUARRY:194:241:234:113", "JANGOLODEMINE:185:187:323:44", "MOONBROOK:208:174:317:349:919:0", "SALDEANSFARM:191:184:478:119", "SENTINELHILL:161:210:461:259", "THEDAGGERHILLS:235:149:349:430", "THEDEADACRE:164:213:542:267", "THEDUSTPLAINS:260:204:538:393", "THEJANSENSTEAD:143:183:498:6", "THEMOLSENFARM:199:180:342:161", "WESTFALLLIGHTHOUSE:306:161:171:482",},
|
||||
["Wetlands"] = {"ANGERFANGENCAMPMENT:196:150:364:241", "BLACKCHANNELMARSH:213:173:95:260", "BLUEGILLMARSH:195:165:111:157", "DIREFORGEHILL:233:225:515:128", "DUNMODR:173:147:419:42", "DUNAGRATH:222:190:97:335", "GRIMBATOL:317:331:623:242:1037:0", "HAWKSVIGIL:170:162:264:353", "IRONBEARDSTOMB:165:156:368:135", "MENETHILHARBOR:156:116:27:321", "MOSSHIDEFEN:215:224:539:277", "RAPTORRIDGE:159:115:640:191", "SALTSPRAYGLEN:170:214:254:58", "SUNDOWNMARSH:265:212:113:100", "THEGREENBELT:153:209:473:143", "THELGANROCK:207:165:478:383:836:0", "WHELGARSEXCAVATIONSITE:172:156:264:224",},
|
||||
["Winterspring"] = {"DARKWHISPERGORGE:247:191:452:447", "EVERLOOK:151:189:516:114", "FROSTFIREHOTSPRINGS:226:124:231:180", "FROSTSABERROCK:240:172:375:11", "FROSTWHISPERGORGE:184:144:531:383", "ICETHISTLEHILLS:116:155:617:247", "LAKEKELTHERIL:203:176:409:205", "MAZTHORIL:174:169:499:265", "OWLWINGTHICKET:149:127:600:348", "STARFALLVILLAGE:174:148:399:143", "THEHIDDENGROVE:162:174:562:33", "TIMBERMAWPOST:223:112:235:248", "WINTERFALLVILLAGE:133:120:622:163",},
|
||||
}
|
||||
|
||||
Vendored
+6
@@ -750,6 +750,9 @@ pfUI_translation["enUS"] = {
|
||||
["Show Totems Indicators"] = nil,
|
||||
["Shrink & Return"] = nil,
|
||||
["Size"] = nil,
|
||||
["Spell Name Alignment"] = nil,
|
||||
["Spell Name X Offset"] = nil,
|
||||
["Spell Name Y Offset"] = nil,
|
||||
["Skin"] = nil,
|
||||
["Skins"] = nil,
|
||||
["Slow"] = nil,
|
||||
@@ -798,6 +801,9 @@ pfUI_translation["enUS"] = {
|
||||
["Threshold To Trust Health Estimation"] = nil,
|
||||
["Time"] = nil,
|
||||
["Timer"] = nil,
|
||||
["Timer Alignment"] = nil,
|
||||
["Timer X Offset"] = nil,
|
||||
["Timer Y Offset"] = nil,
|
||||
["Time Remaining"] = nil,
|
||||
["Timer In Minutes"] = nil,
|
||||
["Timestamp Brackets"] = nil,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<Include file="..\libs\libtipscan.lua"/>
|
||||
<Include file="..\libs\libspell.lua"/>
|
||||
<Include file="..\libs\libcast.lua"/>
|
||||
<Include file="..\libs\libdebuff.lua"/>
|
||||
<Include file="..\libs\librange.lua"/>
|
||||
<Include file="..\libs\libunitscan.lua"/>
|
||||
|
||||
@@ -1,571 +0,0 @@
|
||||
-- load pfUI environment
|
||||
setfenv(1, pfUI:GetEnvironment())
|
||||
|
||||
--[[ libcast ]]--
|
||||
-- A pfUI library that detects and saves all ongoing castbars of players, NPCs and enemies.
|
||||
-- The library also includes spells that usually don't have a castbar like Multi-Shot and Aimed Shot.
|
||||
-- This is exclusivly used for vanilla in order to provide pfGetChannelInfo and pfGetCastInfo functions.
|
||||
--
|
||||
-- External functions:
|
||||
-- pfGetChannelInfo(unit)
|
||||
-- Returns information on the spell currently cast by the specified unit.
|
||||
-- Returns nil if no spell is being cast.
|
||||
--
|
||||
-- cast[String] - The name of the spell, or nil if no spell is being cast.
|
||||
-- nameSubtext[String] - (DUMMY) The string describing the rank of the spell, e.g. "Rank 1".
|
||||
-- text[String] - The name to be displayed.
|
||||
-- texture[String] - The texture path associated with the spell.
|
||||
-- startTime[Number] - Specifies when casting has begun, in milliseconds.
|
||||
-- endTime[Number] - Specifies when casting will end, in milliseconds.
|
||||
-- isTradeSkill[Boolean] - (DUMMY) Specifies if the cast is a tradeskill
|
||||
--
|
||||
-- pfGetCastInfo(unit)
|
||||
-- Returns information on the spell currently channeled by the specified unit.
|
||||
-- Returns nil if no spell is being channeled.
|
||||
--
|
||||
-- cast[String] - The name of the spell, or nil if no spell is being cast.
|
||||
-- nameSubtext[String] - (DUMMY) The string describing the rank of the spell, e.g. "Rank 1".
|
||||
-- text[String] - The name to be displayed.
|
||||
-- texture[String] - The texture path associated with the spell.
|
||||
-- startTime[Number] - Specifies when casting has begun, in milliseconds.
|
||||
-- endTime[Number] - Specifies when casting will end, in milliseconds.
|
||||
-- isTradeSkill[Boolean] - (DUMMY) Specifies if the cast is a tradeskill
|
||||
--
|
||||
-- Internal functions:
|
||||
-- libcast:AddAction(mob, spell, channel)
|
||||
-- Adds a spell to the database by using pfUI's spell database
|
||||
-- to obtain durations and icons
|
||||
--
|
||||
-- libcast:RemoveAction(mob, spell)
|
||||
-- Removes the castbar of a given mob, if `spell` is an interrupt.
|
||||
-- spell can be set to "INTERRUPT" to force remove an action.
|
||||
--
|
||||
|
||||
-- return instantly when another libcast is already active
|
||||
if pfUI.api.libcast then return end
|
||||
|
||||
local lastcasttex, lastrank, _
|
||||
|
||||
local libcast = CreateFrame("Frame", "pfEnemyCast")
|
||||
local player = UnitName("player")
|
||||
|
||||
pfGetChannelInfo = function(unit)
|
||||
-- convert to name if unitstring was given
|
||||
local unitName = pfValidUnits[unit] and UnitName(unit) or unit
|
||||
|
||||
-- Get GUID if Nampower is available
|
||||
local guid = nil
|
||||
|
||||
-- Check if unit itself is a GUID (starts with "0x")
|
||||
if type(unit) == "string" and string.sub(unit, 1, 2) == "0x" then
|
||||
guid = unit -- unit IS the GUID
|
||||
elseif pfValidUnits[unit] and UnitExists then
|
||||
-- unit is a token like "target" - get GUID from it
|
||||
local unitGuid = UnitGUID(unit)
|
||||
guid = unitGuid
|
||||
end
|
||||
|
||||
-- For player: ALWAYS use libcast.db because it handles channel updates correctly
|
||||
local isPlayer = unit == "player" or unitName == player
|
||||
|
||||
if isPlayer then
|
||||
local cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
local db = libcast.db[player]
|
||||
|
||||
if db and db.cast and db.start + db.casttime / 1000 > GetTime() then
|
||||
if not db.channel then return end
|
||||
cast = db.cast
|
||||
nameSubtext = db.rank
|
||||
text = ""
|
||||
texture = db.icon
|
||||
startTime = db.start * 1000
|
||||
endTime = startTime + db.casttime
|
||||
isTradeSkill = nil
|
||||
elseif db then
|
||||
db.cast = nil
|
||||
db.rank = nil
|
||||
db.start = nil
|
||||
db.casttime = nil
|
||||
db.icon = nil
|
||||
db.channel = nil
|
||||
end
|
||||
|
||||
return cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
end
|
||||
|
||||
-- For non-player units: use libdebuff GUID-based tracking or libcast.db
|
||||
-- Try GUID-based lookup first (from libdebuff's SPELL_START tracking)
|
||||
local db = nil
|
||||
if guid and pfUI.libdebuff_casts and pfUI.libdebuff_casts[guid] then
|
||||
-- Use libdebuff's cast tracking (from SPELL_START_OTHER events)
|
||||
local castData = pfUI.libdebuff_casts[guid]
|
||||
if castData.event == "START" and castData.endTime and castData.endTime > GetTime() then
|
||||
-- Convert libdebuff format to libcast format
|
||||
db = {
|
||||
cast = castData.spellName,
|
||||
rank = nil,
|
||||
start = castData.startTime,
|
||||
casttime = castData.duration * 1000, -- Convert back to ms
|
||||
icon = castData.icon,
|
||||
channel = nil -- TODO: libdebuff should distinguish channel vs cast
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback to name-based lookup (CHAT_MSG castbars)
|
||||
-- Skip when GUID is available to avoid same-name mob bleed
|
||||
if not db and not guid and libcast.db[unitName] then
|
||||
db = libcast.db[unitName]
|
||||
end
|
||||
|
||||
-- Fallback to libcast.db for non-player units
|
||||
local cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
|
||||
if db and db.cast and db.start + db.casttime / 1000 > GetTime() then
|
||||
if not db.channel then return end
|
||||
cast = db.cast
|
||||
nameSubtext = db.rank
|
||||
text = ""
|
||||
texture = db.icon
|
||||
startTime = db.start * 1000
|
||||
endTime = startTime + db.casttime
|
||||
isTradeSkill = nil
|
||||
elseif db then
|
||||
db.cast = nil
|
||||
db.rank = nil
|
||||
db.start = nil
|
||||
db.casttime = nil
|
||||
db.icon = nil
|
||||
db.channel = nil
|
||||
end
|
||||
|
||||
return cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
end
|
||||
|
||||
pfGetCastInfo = function(unit)
|
||||
-- convert to name if unitstring was given
|
||||
local unitName = pfValidUnits[unit] and UnitName(unit) or unit
|
||||
|
||||
-- Get GUID if Nampower is available
|
||||
local guid = nil
|
||||
|
||||
-- Check if unit itself is a GUID (starts with "0x")
|
||||
if type(unit) == "string" and string.sub(unit, 1, 2) == "0x" then
|
||||
guid = unit -- unit IS the GUID
|
||||
elseif pfValidUnits[unit] and UnitExists then
|
||||
-- unit is a token like "target" - get GUID from it
|
||||
local unitGuid = UnitGUID(unit)
|
||||
guid = unitGuid
|
||||
end
|
||||
|
||||
-- For player: ALWAYS use libcast.db because it handles pushback correctly
|
||||
local isPlayer = unit == "player" or unitName == player
|
||||
|
||||
if isPlayer then
|
||||
local cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
local db = libcast.db[player]
|
||||
|
||||
if db and db.cast and db.start + db.casttime / 1000 > GetTime() then
|
||||
if db.channel then return end
|
||||
cast = db.cast
|
||||
nameSubtext = db.rank or ""
|
||||
text = ""
|
||||
texture = db.icon
|
||||
startTime = db.start * 1000
|
||||
endTime = startTime + db.casttime
|
||||
isTradeSkill = nil
|
||||
elseif db then
|
||||
db.cast = nil
|
||||
db.rank = nil
|
||||
db.start = nil
|
||||
db.casttime = nil
|
||||
db.icon = nil
|
||||
db.channel = nil
|
||||
end
|
||||
|
||||
return cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
end
|
||||
|
||||
-- For non-player units: use libdebuff GUID-based tracking or libcast.db
|
||||
-- Try GUID-based lookup first (from libdebuff's SPELL_START tracking)
|
||||
local db = nil
|
||||
if guid and pfUI.libdebuff_casts and pfUI.libdebuff_casts[guid] then
|
||||
-- Use libdebuff's cast tracking (from SPELL_START_OTHER events)
|
||||
local castData = pfUI.libdebuff_casts[guid]
|
||||
if castData.event == "START" and castData.endTime and castData.endTime > GetTime() then
|
||||
-- Convert libdebuff format to libcast format
|
||||
db = {
|
||||
cast = castData.spellName,
|
||||
rank = nil,
|
||||
start = castData.startTime,
|
||||
casttime = castData.duration * 1000, -- Convert back to ms
|
||||
icon = castData.icon,
|
||||
channel = nil -- TODO: libdebuff should distinguish channel vs cast
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback to name-based lookup (CHAT_MSG castbars)
|
||||
-- Skip when GUID is available to avoid same-name mob bleed
|
||||
if not db and not guid and libcast.db[unitName] then
|
||||
db = libcast.db[unitName]
|
||||
end
|
||||
|
||||
-- Fallback to libcast.db for non-player units
|
||||
local cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
|
||||
if db and db.cast and db.start + db.casttime / 1000 > GetTime() then
|
||||
if db.channel then return end
|
||||
cast = db.cast
|
||||
nameSubtext = db.rank or ""
|
||||
text = ""
|
||||
texture = db.icon
|
||||
startTime = db.start * 1000
|
||||
endTime = startTime + db.casttime
|
||||
isTradeSkill = nil
|
||||
elseif db then
|
||||
db.cast = nil
|
||||
db.rank = nil
|
||||
db.start = nil
|
||||
db.casttime = nil
|
||||
db.icon = nil
|
||||
db.channel = nil
|
||||
end
|
||||
|
||||
return cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
end
|
||||
|
||||
function libcast:AddAction(mob, spell, channel)
|
||||
if not mob or not spell then return nil end
|
||||
|
||||
if L["spells"][spell] ~= nil then
|
||||
local casttime = L["spells"][spell].t
|
||||
local icon = L["spells"][spell].icon and string.format("%s%s", "Interface\\Icons\\", L["spells"][spell].icon) or nil
|
||||
|
||||
-- add cast action to the database
|
||||
if not self.db[mob] then self.db[mob] = {} end
|
||||
self.db[mob].cast = spell
|
||||
self.db[mob].rank = nil
|
||||
self.db[mob].start = GetTime()
|
||||
self.db[mob].casttime = casttime
|
||||
self.db[mob].icon = icon
|
||||
self.db[mob].channel = channel
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function libcast:RemoveAction(mob, spell)
|
||||
if self.db[mob] and ( L["interrupts"][spell] ~= nil or spell == "INTERRUPT" ) then
|
||||
|
||||
-- remove cast action to the database
|
||||
self.db[mob].cast = nil
|
||||
self.db[mob].rank = nil
|
||||
self.db[mob].start = nil
|
||||
self.db[mob].casttime = nil
|
||||
self.db[mob].icon = nil
|
||||
self.db[mob].channel = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- main data
|
||||
libcast.db = { [player] = {} }
|
||||
|
||||
-- environmental casts
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_FRIENDLYPLAYER_DAMAGE")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_FRIENDLYPLAYER_BUFF")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_BUFFS")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_PARTY_DAMAGE")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_PARTY_BUFF")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE")
|
||||
libcast:RegisterEvent("CHAT_MSG_SPELL_CREATURE_VS_CREATURE_BUFF")
|
||||
|
||||
-- player spells
|
||||
libcast:RegisterEvent("SPELLCAST_START")
|
||||
libcast:RegisterEvent("SPELLCAST_STOP")
|
||||
libcast:RegisterEvent("SPELLCAST_FAILED")
|
||||
libcast:RegisterEvent("SPELLCAST_INTERRUPTED")
|
||||
libcast:RegisterEvent("SPELLCAST_DELAYED")
|
||||
libcast:RegisterEvent("SPELLCAST_CHANNEL_START")
|
||||
libcast:RegisterEvent("SPELLCAST_CHANNEL_STOP")
|
||||
libcast:RegisterEvent("SPELLCAST_CHANNEL_UPDATE")
|
||||
|
||||
local mob, spell, icon, _
|
||||
local lastSpellId = nil -- spellId cached from SPELL_START_SELF (Nampower)
|
||||
|
||||
libcast:SetScript("OnEvent", function()
|
||||
-- Fill database with player casts
|
||||
if event == "SPELLCAST_START" then
|
||||
-- Get icon via spellId cached from SPELL_START_SELF
|
||||
icon = lastSpellId and C_Spell.GetSpellTexture(lastSpellId) or nil
|
||||
-- fallback to L["spells"] / lastcasttex if no icon resolved
|
||||
if not icon then
|
||||
icon = L["spells"][arg1] and L["spells"][arg1].icon and string.format("%s%s", "Interface\\Icons\\", L["spells"][arg1].icon) or lastcasttex
|
||||
end
|
||||
lastSpellId = nil
|
||||
|
||||
this.db[player].cast = arg1
|
||||
this.db[player].rank = lastrank
|
||||
this.db[player].start = GetTime()
|
||||
this.db[player].casttime = arg2
|
||||
this.db[player].icon = icon
|
||||
this.db[player].channel = nil
|
||||
|
||||
if not L["spells"][arg1] or not L["spells"][arg1].icon or not L["spells"][arg1].t then
|
||||
L["spells"][arg1] = L["spells"][arg1] or { }
|
||||
L["spells"][arg1].icon = L["spells"][arg1].icon or icon
|
||||
L["spells"][arg1].t = L["spells"][arg1].t or arg2
|
||||
end
|
||||
lastcasttex, lastrank = nil, nil
|
||||
elseif event == "SPELLCAST_STOP" or event == "SPELLCAST_FAILED" or event == "SPELLCAST_INTERRUPTED" then
|
||||
lastSpellId = nil
|
||||
if this.db[player] and not this.db[player].channel then
|
||||
-- remove cast action to the database
|
||||
this.db[player].cast = nil
|
||||
this.db[player].rank = nil
|
||||
this.db[player].rank = nil
|
||||
this.db[player].start = nil
|
||||
this.db[player].casttime = nil
|
||||
this.db[player].icon = nil
|
||||
this.db[player].channel = nil
|
||||
else
|
||||
lastcasttex, lastrank = nil, nil
|
||||
end
|
||||
elseif event == "SPELLCAST_DELAYED" then
|
||||
if this.db[player].cast then
|
||||
-- Pushback: increase casttime instead of shifting start
|
||||
-- arg1 is the delay amount in milliseconds
|
||||
this.db[player].casttime = this.db[player].casttime + arg1
|
||||
end
|
||||
elseif event == "SPELLCAST_CHANNEL_START" then
|
||||
-- add cast action to the database
|
||||
this.db[player].cast = arg2
|
||||
this.db[player].rank = lastrank
|
||||
this.db[player].start = GetTime()
|
||||
this.db[player].casttime = arg1
|
||||
this.db[player].icon = L["spells"][arg2] and L["spells"][arg2].icon and string.format("%s%s", "Interface\\Icons\\", L["spells"][arg2].icon) or lastcasttex
|
||||
this.db[player].channel = true
|
||||
|
||||
lastcasttex, lastrank = nil, nil
|
||||
elseif event == "SPELLCAST_CHANNEL_STOP" then
|
||||
if this.db[player] and this.db[player].channel then
|
||||
-- remove cast action to the database
|
||||
this.db[player].cast = nil
|
||||
this.db[player].rank = nil
|
||||
this.db[player].start = nil
|
||||
this.db[player].casttime = nil
|
||||
this.db[player].icon = nil
|
||||
this.db[player].channel = nil
|
||||
end
|
||||
elseif event == "SPELLCAST_CHANNEL_UPDATE" then
|
||||
if this.db[player].cast then
|
||||
this.db[player].start = -this.db[player].casttime/1000 + GetTime() + arg1/1000
|
||||
end
|
||||
-- Fill database with environmental casts
|
||||
elseif arg1 then
|
||||
-- (.+) begins to cast (.+).
|
||||
mob, spell = cmatch(arg1, SPELLCASTOTHERSTART)
|
||||
if libcast:AddAction(mob, spell) then return end
|
||||
|
||||
-- (.+) begins to perform (.+).
|
||||
mob, spell = cmatch(arg1, SPELLPERFORMOTHERSTART)
|
||||
if libcast:AddAction(mob, spell) then return end
|
||||
|
||||
-- (.+) gains (.+).
|
||||
mob, spell = cmatch(arg1, AURAADDEDOTHERHELPFUL)
|
||||
if libcast:RemoveAction(mob, spell) then return end
|
||||
|
||||
-- (.+) is afflicted by (.+).
|
||||
mob, spell = cmatch(arg1, AURAADDEDOTHERHARMFUL)
|
||||
if libcast:RemoveAction(mob, spell) then return end
|
||||
|
||||
-- Your (.+) hits (.+) for (%d+).
|
||||
spell, mob = cmatch(arg1, SPELLLOGSELFOTHER)
|
||||
if libcast:RemoveAction(mob, spell) then return end
|
||||
|
||||
-- Your (.+) crits (.+) for (%d+).
|
||||
spell, mob = cmatch(arg1, SPELLLOGCRITSELFOTHER)
|
||||
if libcast:RemoveAction(mob, spell) then return end
|
||||
|
||||
-- (.+)'s (.+) %a hits (.+) for (%d+).
|
||||
_, spell, mob = cmatch(arg1, SPELLLOGOTHEROTHER)
|
||||
if libcast:RemoveAction(mob, spell) then return end
|
||||
|
||||
-- (.+)'s (.+) %a crits (.+) for (%d+).
|
||||
_, spell, mob = cmatch(arg1, SPELLLOGCRITOTHEROTHER)
|
||||
if libcast:RemoveAction(mob, spell) then return end
|
||||
|
||||
-- You interrupt (.+)'s (.+).
|
||||
mob, _ = cmatch(arg1, SPELLINTERRUPTSELFOTHER)
|
||||
if libcast:RemoveAction(mob, "INTERRUPT") then return end
|
||||
|
||||
-- (.+) interrupts (.+)'s (.+).
|
||||
_, mob, _ = cmatch(arg1, SPELLINTERRUPTOTHEROTHER)
|
||||
if libcast:RemoveAction(mob, "INTERRUPT") then return end
|
||||
end
|
||||
end)
|
||||
|
||||
--[[ Custom Casts
|
||||
Enable Castbars for spells that don't have a castbar by default
|
||||
(e.g Multi-Shot and Aimed Shot)
|
||||
]]--
|
||||
local aimedshot = L["customcast"]["AIMEDSHOT"]
|
||||
local multishot = L["customcast"]["MULTISHOT"]
|
||||
|
||||
-- Shot-timer haste lookup. Keyed by icon path because some of these buffs have
|
||||
-- spell-ID variants across server flavors but stable icons. Berserking is the
|
||||
-- variable Troll-racial calculation (more haste at lower HP); other entries are
|
||||
-- flat duration multipliers.
|
||||
local SHOT_HASTE = {
|
||||
["Interface\\Icons\\Racial_Troll_Berserk"] = "berserking",
|
||||
["Interface\\Icons\\Ability_Hunter_RunningShot"] = 1.4,
|
||||
["Interface\\Icons\\Ability_Warrior_InnerRage"] = 1.3,
|
||||
["Interface\\Icons\\Inv_Trinket_Naxxramas04"] = 1.2,
|
||||
}
|
||||
|
||||
function libcast.ApplyShotHaste(duration)
|
||||
for _, a in ipairs(C_UnitAuras.GetUnitAuras("player", "HELPFUL")) do
|
||||
local mult = SHOT_HASTE[a.icon]
|
||||
if mult == "berserking" then
|
||||
local hp = UnitHealth("player") / UnitHealthMax("player")
|
||||
local berserk = hp >= 0.40 and (1.30 - hp) / 3 or 0.3
|
||||
duration = duration / (1 + berserk)
|
||||
elseif mult then
|
||||
duration = duration / mult
|
||||
end
|
||||
end
|
||||
return duration
|
||||
end
|
||||
|
||||
libcast.customcast = {}
|
||||
libcast.customcast[strlower(aimedshot)] = function(begin, duration)
|
||||
if begin then
|
||||
local duration = libcast.ApplyShotHaste(duration or 3000)
|
||||
|
||||
local _,_, lag = GetNetStats()
|
||||
local start = GetTime() + lag/1000
|
||||
|
||||
-- add cast action to the database
|
||||
libcast.db[player].cast = aimedshot
|
||||
libcast.db[player].rank = lastrank
|
||||
libcast.db[player].start = start
|
||||
libcast.db[player].casttime = duration
|
||||
libcast.db[player].icon = "Interface\\Icons\\Inv_spear_07"
|
||||
libcast.db[player].channel = nil
|
||||
else
|
||||
-- remove cast action to the database
|
||||
libcast.db[player].cast = nil
|
||||
libcast.db[player].rank = nil
|
||||
libcast.db[player].start = nil
|
||||
libcast.db[player].casttime = nil
|
||||
libcast.db[player].icon = nil
|
||||
libcast.db[player].channel = nil
|
||||
end
|
||||
end
|
||||
|
||||
libcast.customcast[strlower(multishot)] = function(begin, duration)
|
||||
if begin then
|
||||
local duration = libcast.ApplyShotHaste(duration or 500)
|
||||
|
||||
local _,_, lag = GetNetStats()
|
||||
local start = GetTime() + lag/1000
|
||||
|
||||
-- add cast action to the database
|
||||
libcast.db[player].cast = multishot
|
||||
libcast.db[player].rank = lastrank
|
||||
libcast.db[player].start = start
|
||||
libcast.db[player].casttime = duration
|
||||
libcast.db[player].icon = "Interface\\Icons\\Ability_upgrademoonglaive"
|
||||
libcast.db[player].channel = nil
|
||||
else
|
||||
-- remove cast action to the database
|
||||
libcast.db[player].cast = nil
|
||||
libcast.db[player].rank = nil
|
||||
libcast.db[player].start = nil
|
||||
libcast.db[player].casttime = nil
|
||||
libcast.db[player].icon = nil
|
||||
libcast.db[player].channel = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function CastCustom(id, bookType, rawSpellName, rank, texture, castingTime)
|
||||
if not id or not rawSpellName then return end -- ignore if the spell is not found
|
||||
if not castingTime or castingTime == 0 then
|
||||
-- instant-cast: clear lastcasttex so next cast doesn't inherit this icon
|
||||
lastcasttex, lastrank = nil, nil
|
||||
return
|
||||
end
|
||||
|
||||
lastrank = rank
|
||||
lastcasttex = texture
|
||||
|
||||
local func = libcast.customcast[strlower(rawSpellName)]
|
||||
if not func then return end
|
||||
|
||||
if GetSpellCooldown(id, bookType) == 0 or pfGetCastInfo(player) then return end -- detect casting
|
||||
|
||||
func(true)
|
||||
end
|
||||
|
||||
hooksecurefunc("UseContainerItem", function(id, index)
|
||||
lastcasttex = GetContainerItemInfo(id, index)
|
||||
end)
|
||||
|
||||
hooksecurefunc("CastSpell", function(id, bookType)
|
||||
local cachedRawSpellName, cachedRank, cachedTexture, cachedCastingTime, _, _, cachedSpellId, cachedBookType = libspell.GetSpellInfo(id, bookType)
|
||||
|
||||
CastCustom(cachedSpellId, cachedBookType, cachedRawSpellName, cachedRank, cachedTexture, cachedCastingTime)
|
||||
end)
|
||||
|
||||
hooksecurefunc("CastSpellByName", function(spellCasted, target)
|
||||
local cachedRawSpellName, cachedRank, cachedTexture, cachedCastingTime, _, _, cachedSpellId, cachedBookType = libspell.GetSpellInfo(spellCasted)
|
||||
|
||||
CastCustom(cachedSpellId, cachedBookType, cachedRawSpellName, cachedRank, cachedTexture, cachedCastingTime)
|
||||
end)
|
||||
|
||||
hooksecurefunc("UseAction", function(slot, target, button)
|
||||
if not IsCurrentAction(slot) then return end
|
||||
|
||||
-- Resolve action slot → spellID (handles both spell and macro actions),
|
||||
-- then resolve to spellbook slot for libspell. GetMacroSpell returns the
|
||||
-- highest rank the player actually knows, so FindSpellBookSlotByID's
|
||||
-- "spell must be in spellbook" requirement is satisfied.
|
||||
local kind, id = GetActionInfo(slot)
|
||||
local spellID
|
||||
if kind == "spell" then
|
||||
spellID = id
|
||||
elseif kind == "macro" then
|
||||
local _, _, sid = GetMacroSpell(id)
|
||||
spellID = sid
|
||||
end
|
||||
if not spellID then return end
|
||||
|
||||
local sbSlot = FindSpellBookSlotByID(spellID)
|
||||
if not sbSlot then return end
|
||||
|
||||
local cachedRawSpellName, cachedRank, cachedTexture, cachedCastingTime, _, _, cachedSpellId, cachedBookType = libspell.GetSpellInfo(sbSlot, BOOKTYPE_SPELL)
|
||||
|
||||
CastCustom(cachedSpellId, cachedBookType, cachedRawSpellName, cachedRank, cachedTexture, cachedCastingTime)
|
||||
end)
|
||||
|
||||
-- Cache spellId from SPELL_START_SELF so SPELLCAST_START can use it for icon lookup
|
||||
pfUI.libdebuff_spell_start_self_hooks = pfUI.libdebuff_spell_start_self_hooks or {}
|
||||
pfUI.libdebuff_spell_start_self_hooks["libcast_icon"] = function(spellId)
|
||||
lastSpellId = spellId
|
||||
end
|
||||
|
||||
-- add libcast to pfUI API
|
||||
pfUI.api.libcast = libcast
|
||||
+49
-461
@@ -11,9 +11,12 @@ setfenv(1, pfUI:GetEnvironment())
|
||||
-- This eliminates ~400 lines of error-prone shift logic while maintaining full
|
||||
-- multi-caster tracking support.
|
||||
--
|
||||
-- libdebuff:UnitDebuff(unit, id)
|
||||
-- Returns debuff informations on the given effect of the specified unit.
|
||||
-- name, rank, texture, stacks, dtype, duration, timeleft, caster
|
||||
-- The public per-aura readers (UnitDebuff, UnitOwnDebuff) were retired in favor
|
||||
-- of ClassicAPI's C_UnitAuras (which now provides sourceUnit/sourceGUID and
|
||||
-- non-player expirationTime). What remains in libdebuff is the cast-event
|
||||
-- bookkeeping consumed by GetBestAuraCast (libpredict HoT tracking) and the
|
||||
-- libdebuff_*_hooks broadcast surface (subscribers in actionbar / swingtimer
|
||||
-- / libtotem react to SPELL_GO and SPELL_FAILED).
|
||||
|
||||
-- return instantly when another libdebuff is already active
|
||||
if pfUI.api.libdebuff then return end
|
||||
@@ -40,16 +43,8 @@ if GetNampowerVersion then
|
||||
end
|
||||
end
|
||||
|
||||
-- Nampower startup check: show version info and ensure CVars are set.
|
||||
-- Runs on first OnUpdate after PLAYER_ENTERING_WORLD to give Nampower time to initialize.
|
||||
local nampowerCheckFrame = CreateFrame("Frame")
|
||||
nampowerCheckFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
nampowerCheckFrame:SetScript("OnEvent", function()
|
||||
-- Defer to next frame so Nampower is fully initialized
|
||||
this:SetScript("OnUpdate", function()
|
||||
this:SetScript("OnUpdate", nil)
|
||||
this:UnregisterAllEvents()
|
||||
this:SetScript("OnEvent", nil)
|
||||
EventUtil.ContinueOnPlayerLogin(function()
|
||||
RunNextFrame(function()
|
||||
|
||||
if GetNampowerVersion then
|
||||
local major, minor, patch = GetNampowerVersion()
|
||||
@@ -57,8 +52,6 @@ nampowerCheckFrame:SetScript("OnEvent", function()
|
||||
local versionString = major .. "." .. minor .. "." .. patch
|
||||
|
||||
if major > 3 or (major == 3 and minor > 0) or (major == 3 and minor == 0 and patch >= 0) then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff33ff99[libdebuff]|r Nampower v" .. versionString .. " detected - GetUnitField mode enabled!")
|
||||
|
||||
if SetCVar and GetCVar then
|
||||
local cvarsToEnable = {
|
||||
"NP_EnableSpellStartEvents",
|
||||
@@ -88,8 +81,6 @@ nampowerCheckFrame:SetScript("OnEvent", function()
|
||||
|
||||
if enabledCount > 0 then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff33ff99[libdebuff]|r Enabled " .. enabledCount .. " Nampower CVars")
|
||||
elseif alreadyEnabledCount == table.getn(cvarsToEnable) then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff33ff99[libdebuff]|r All required Nampower CVars already enabled")
|
||||
end
|
||||
if failedCount > 0 then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffffcc00[libdebuff]|r Warning: Could not check/set " .. failedCount .. " CVars")
|
||||
@@ -142,8 +133,6 @@ local iconCache = pfUI.libdebuff_icon_cache
|
||||
|
||||
-- Cast Tracking: [casterGuid] = {spellID, spellName, icon, startTime, duration, endTime}
|
||||
-- Shared with nameplates for cast-bar display
|
||||
pfUI.libdebuff_casts = pfUI.libdebuff_casts or {}
|
||||
pfUI.libdebuff_item_icons = pfUI.libdebuff_item_icons or {} -- [casterGuid] = icon (persists across SPELL_GO)
|
||||
|
||||
-- Cleveroids API: [targetGUID][spellID] = {start, duration, caster, stacks}
|
||||
pfUI.libdebuff_objects_guid = pfUI.libdebuff_objects_guid or {}
|
||||
@@ -203,10 +192,6 @@ pfUI.libdebuff_spell_cast_hooks = pfUI.libdebuff_spell_cast_hooks or {}
|
||||
pfUI.libdebuff_downrank_blocked_hooks = pfUI.libdebuff_downrank_blocked_hooks or {}
|
||||
local AURA_CAST_DEDUPE_WINDOW = 0.1 -- Ignore duplicates within 100ms
|
||||
|
||||
-- Captured combo points from SPELL_CAST_EVENT (before client consumes them)
|
||||
-- SPELL_CAST_EVENT fires BEFORE UnitAura updates, so GetComboPoints() still works
|
||||
local capturedCP = nil
|
||||
|
||||
-- Pending cast info for libpredict (heal prediction target tracking)
|
||||
-- SPELL_CAST_EVENT fires with targetGuid BEFORE SPELLCAST_START,
|
||||
-- which allows libpredict to know the correct target for queued casts.
|
||||
@@ -279,50 +264,10 @@ local debuffOverwritePairs = {
|
||||
["Demoralizing Roar"] = "Demoralizing Shout",
|
||||
}
|
||||
|
||||
-- Combopoint-based abilities: Only show timers for OUR casts
|
||||
-- Format: [spellName] = { base = N, perCP = N }
|
||||
-- Duration formula: duration = base + combopoints * perCP
|
||||
local combopointAbilities = {
|
||||
-- Druid
|
||||
["Rip"] = { base = 8, perCP = 2 },
|
||||
|
||||
-- Rogue
|
||||
["Rupture"] = { base = 6, perCP = 2 },
|
||||
["Kidney Shot"] = { base = 1, perCP = 1 },
|
||||
["Slice and Dice"] = { base = 9, perCP = 3 },
|
||||
["Expose Armor"] = { base = 30, perCP = 0 }, -- fixed 30s
|
||||
}
|
||||
|
||||
-- ============================================================================
|
||||
-- HELPER FUNCTIONS
|
||||
-- ============================================================================
|
||||
|
||||
-- Check if spell is a combo-point ability
|
||||
local function IsComboPointAbility(spellName)
|
||||
if not spellName then return false end
|
||||
return combopointAbilities[spellName] ~= nil
|
||||
end
|
||||
|
||||
-- Get combo-point spell data (base duration and per-CP bonus)
|
||||
local function GetComboPointData(spellName)
|
||||
if not spellName then return nil, nil end
|
||||
local cpData = combopointAbilities[spellName]
|
||||
if cpData then
|
||||
return cpData.base, cpData.perCP
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
-- Player GUID Cache
|
||||
local playerGUID = nil
|
||||
local function GetPlayerGUID()
|
||||
if not playerGUID and UnitGUID then
|
||||
local guid = UnitGUID("player")
|
||||
playerGUID = guid
|
||||
end
|
||||
return playerGUID
|
||||
end
|
||||
|
||||
-- Debug Stats
|
||||
pfUI.libdebuff_debugstats = pfUI.libdebuff_debugstats or {
|
||||
enabled = false,
|
||||
@@ -426,7 +371,7 @@ local function GetDebuffSlotMap(guid)
|
||||
local spellId = auras[auraSlot]
|
||||
if spellId and spellId > 0 then
|
||||
displaySlot = displaySlot + 1
|
||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
||||
local spellName = C_Spell.GetSpellName(spellId)
|
||||
local texture = libdebuff:GetSpellIcon(spellId)
|
||||
local stacks = (auraApps and auraApps[auraSlot] or 0) + 1
|
||||
local dtype = nil
|
||||
@@ -462,7 +407,7 @@ local function GetSlotCaster(guid, auraSlot, spellName)
|
||||
end
|
||||
|
||||
-- Fallback: Check ownDebuffs
|
||||
local myGuid = GetPlayerGUID()
|
||||
local myGuid = GetPlayerGuid()
|
||||
if ownDebuffs[guid] and ownDebuffs[guid][spellName] then
|
||||
return myGuid, true
|
||||
end
|
||||
@@ -653,16 +598,7 @@ function libdebuff:GetDuration(effect, rank)
|
||||
local rank = L["debuffs"][effect][rank] and rank or libdebuff:GetMaxRank(effect)
|
||||
local duration = L["debuffs"][effect][rank]
|
||||
|
||||
if effect == L["dyndebuffs"]["Rupture"] then
|
||||
local cp = GetComboPoints() or 0
|
||||
duration = duration + cp*2
|
||||
elseif effect == L["dyndebuffs"]["Kidney Shot"] then
|
||||
local cp = GetComboPoints() or 0
|
||||
duration = duration + cp*1
|
||||
elseif effect == "Rip" or effect == L["dyndebuffs"]["Rip"] then
|
||||
local cp = GetComboPoints() or 0
|
||||
duration = 8 + cp*2
|
||||
elseif effect == L["dyndebuffs"]["Demoralizing Shout"] then
|
||||
if effect == L["dyndebuffs"]["Demoralizing Shout"] then
|
||||
local _,_,_,_,count = GetTalentInfo(2,1)
|
||||
if count and count > 0 then duration = duration + ( duration / 100 * (count*10)) end
|
||||
elseif effect == L["dyndebuffs"]["Shadow Word: Pain"] then
|
||||
@@ -787,219 +723,6 @@ function libdebuff:AddEffect(unit, unitlevel, effect, duration, caster, rank)
|
||||
lastspell = libdebuff.objects[unit][unitlevel][effect]
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- MAIN API: UnitDebuff (GetUnitField-based)
|
||||
-- ============================================================================
|
||||
|
||||
local cache = {}
|
||||
|
||||
function libdebuff:UnitDebuff(unit, displaySlot)
|
||||
local unitname = UnitName(unit)
|
||||
local unitlevel = UnitLevel(unit)
|
||||
local duration, timeleft = nil, -1
|
||||
local rank = nil
|
||||
local caster = nil
|
||||
local effect = nil
|
||||
local texture = nil
|
||||
local stacks = 0
|
||||
local dtype = nil
|
||||
|
||||
-- Nampower: Use GetUnitField for ALL debuff data (no Blizzard UnitDebuff needed)
|
||||
if hasNampower and UnitGUID then
|
||||
local guid = UnitGUID(unit)
|
||||
if not guid then
|
||||
-- Safety fallback: no GUID available (should not happen with Nampower)
|
||||
local aura = C_UnitAuras.GetDebuffDataByIndex(unit, displaySlot)
|
||||
if aura then
|
||||
return aura.name, rank, aura.icon, aura.applications, aura.dispelName, duration, timeleft, caster
|
||||
end
|
||||
return effect, rank, texture, stacks, dtype, duration, timeleft, caster
|
||||
end
|
||||
|
||||
-- Get current slot map from GetUnitField (cached 50ms)
|
||||
local slotMap = GetDebuffSlotMap(guid)
|
||||
if not slotMap or not slotMap[displaySlot] then
|
||||
return nil
|
||||
end
|
||||
|
||||
local slotData = slotMap[displaySlot]
|
||||
effect = slotData.spellName
|
||||
texture = slotData.texture
|
||||
stacks = slotData.stacks
|
||||
dtype = slotData.dtype
|
||||
local auraSlot = slotData.auraSlot
|
||||
|
||||
-- Get caster info for this slot
|
||||
local slotCasterGuid, isOurs = GetSlotCaster(guid, auraSlot, effect)
|
||||
|
||||
if isOurs then
|
||||
-- OUR debuff - get timer from ownDebuffs
|
||||
if ownDebuffs[guid] and ownDebuffs[guid][effect] then
|
||||
local data = ownDebuffs[guid][effect]
|
||||
local remaining = (data.startTime + data.duration) - GetTime()
|
||||
if remaining > 0 then
|
||||
duration = data.duration
|
||||
timeleft = remaining
|
||||
caster = "player"
|
||||
rank = data.rank
|
||||
elseif remaining > -1 then
|
||||
-- Grace period - show 0 timeleft
|
||||
duration = data.duration
|
||||
timeleft = 0
|
||||
caster = "player"
|
||||
rank = data.rank
|
||||
end
|
||||
end
|
||||
else
|
||||
-- OTHER player's debuff - get timer from allAuraCasts
|
||||
if slotCasterGuid and allAuraCasts[guid] and allAuraCasts[guid][effect] then
|
||||
local data = allAuraCasts[guid][effect][slotCasterGuid]
|
||||
if data then
|
||||
local remaining = (data.startTime + data.duration) - GetTime()
|
||||
if remaining > 0 and data.duration > 0 then
|
||||
duration = data.duration
|
||||
timeleft = remaining
|
||||
caster = "other"
|
||||
rank = data.rank
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback: Search all casters if specific one not found
|
||||
if not duration and allAuraCasts[guid] and allAuraCasts[guid][effect] then
|
||||
for anyCasterGuid, data in pairs(allAuraCasts[guid][effect]) do
|
||||
local remaining = (data.startTime + data.duration) - GetTime()
|
||||
if remaining > 0 and data.duration > 0 then
|
||||
duration = data.duration
|
||||
timeleft = remaining
|
||||
caster = "other"
|
||||
rank = data.rank
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return effect, rank, texture, stacks, dtype, duration, timeleft, caster
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- FALLBACK: Legacy (non-Nampower) system
|
||||
-- ============================================================================
|
||||
|
||||
local aura = C_UnitAuras.GetDebuffDataByIndex(unit, displaySlot)
|
||||
if aura then
|
||||
texture = aura.icon
|
||||
stacks = aura.applications
|
||||
dtype = aura.dispelName
|
||||
effect = aura.name
|
||||
end
|
||||
|
||||
|
||||
if effect and libdebuff.objects[unitname] then
|
||||
for level, effects in pairs(libdebuff.objects[unitname]) do
|
||||
if effects[effect] and effects[effect].duration then
|
||||
local timeleft = effects[effect].start and
|
||||
effects[effect].start + effects[effect].duration - GetTime()
|
||||
|
||||
if timeleft and timeleft > 0 then
|
||||
return effect, effects[effect].rank, texture, stacks, dtype,
|
||||
effects[effect].duration, timeleft, effects[effect].caster
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return effect, rank, texture, stacks, dtype, duration, timeleft, caster
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- API: UnitOwnDebuff (only OUR debuffs)
|
||||
-- ============================================================================
|
||||
|
||||
-- Pre-defined sort function for UnitOwnDebuff (avoids closure creation per call)
|
||||
local _ownDebuffSortFunc = function(a, b)
|
||||
if a.data.startTime == b.data.startTime then
|
||||
return a.spellName < b.spellName
|
||||
end
|
||||
return a.data.startTime < b.data.startTime
|
||||
end
|
||||
|
||||
function libdebuff:UnitOwnDebuff(unit, id)
|
||||
if hasNampower and UnitGUID then
|
||||
local guid = UnitGUID(unit)
|
||||
if guid and ownDebuffs[guid] then
|
||||
-- Build sorted list of our active debuffs
|
||||
local sortedDebuffs = {}
|
||||
local now = GetTime()
|
||||
|
||||
local toRemove = nil
|
||||
for spellName, data in pairs(ownDebuffs[guid]) do
|
||||
local timeleft = (data.startTime + data.duration) - now
|
||||
if timeleft > 0 then
|
||||
local count = table.getn(sortedDebuffs) + 1
|
||||
sortedDebuffs[count] = {
|
||||
spellName = spellName,
|
||||
data = data,
|
||||
timeleft = timeleft
|
||||
}
|
||||
elseif data.pending then
|
||||
if timeleft < -2 then
|
||||
toRemove = toRemove or {}
|
||||
toRemove[spellName] = true
|
||||
end
|
||||
else
|
||||
toRemove = toRemove or {}
|
||||
toRemove[spellName] = true
|
||||
end
|
||||
end
|
||||
if toRemove then
|
||||
for spellName in pairs(toRemove) do
|
||||
ownDebuffs[guid][spellName] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Sort by startTime (oldest first = lowest display slot)
|
||||
-- If startTime is equal (e.g. after Carnage refresh), use spellName for stable sorting
|
||||
table.sort(sortedDebuffs, _ownDebuffSortFunc)
|
||||
|
||||
-- Return debuff at position 'id'
|
||||
if sortedDebuffs[id] then
|
||||
local entry = sortedDebuffs[id]
|
||||
local texture = entry.data.texture or "Interface\\Icons\\INV_Misc_QuestionMark"
|
||||
local displayTimeleft = entry.timeleft > 0 and entry.timeleft or 0
|
||||
|
||||
-- Get dtype from SpellRec DBC via stored spellId
|
||||
local entryDtype = nil
|
||||
if entry.data.spellId and GetSpellRecField then
|
||||
local dispelId = GetSpellRecField(entry.data.spellId, "dispel")
|
||||
if dispelId and dispelId > 0 then
|
||||
entryDtype = dispelTypeMap[dispelId]
|
||||
end
|
||||
end
|
||||
|
||||
return entry.spellName, entry.data.rank, texture, 1, entryDtype, entry.data.duration, displayTimeleft, "player"
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Fallback: Iterate through all debuffs and filter
|
||||
for k in pairs(cache) do cache[k] = nil end
|
||||
local count = 1
|
||||
for i=1,16 do
|
||||
local effect, rank, texture, stacks, dtype, duration, timeleft, caster = libdebuff:UnitDebuff(unit, i)
|
||||
if effect and not cache[effect] and caster and caster == "player" then
|
||||
cache[effect] = true
|
||||
if count == id then
|
||||
return effect, rank, texture, stacks, dtype, duration, timeleft, caster
|
||||
else
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- API: GetBestAuraCast (for libpredict HoT tracking)
|
||||
-- ============================================================================
|
||||
@@ -1012,7 +735,7 @@ function libdebuff:GetBestAuraCast(guid, spellName)
|
||||
local data = ownDebuffs[guid][spellName]
|
||||
local timeleft = (data.startTime + data.duration) - GetTime()
|
||||
if timeleft > 0 then
|
||||
return data.startTime, data.duration, timeleft, data.rank, GetPlayerGUID()
|
||||
return data.startTime, data.duration, timeleft, data.rank, GetPlayerGuid()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1039,33 +762,6 @@ function libdebuff:GetBestAuraCast(guid, spellName)
|
||||
return nil
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- API: GetEnhancedDebuffs (for external modules)
|
||||
-- ============================================================================
|
||||
|
||||
function libdebuff:GetEnhancedDebuffs(targetGUID)
|
||||
if not targetGUID then return nil end
|
||||
local result = {}
|
||||
|
||||
if ownDebuffs[targetGUID] then
|
||||
local myGuid = GetPlayerGUID()
|
||||
for spellName, data in pairs(ownDebuffs[targetGUID]) do
|
||||
local timeleft = (data.startTime + data.duration) - GetTime()
|
||||
if timeleft > 0 then
|
||||
result[spellName] = result[spellName] or {}
|
||||
result[spellName][myGuid] = {
|
||||
startTime = data.startTime,
|
||||
duration = data.duration,
|
||||
texture = data.texture,
|
||||
rank = data.rank
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- NAMPOWER EVENT HANDLING
|
||||
-- ============================================================================
|
||||
@@ -1097,7 +793,7 @@ if hasNampower then
|
||||
-- Carnage triggered! Refresh Rip & Rake
|
||||
local guid = carnageState.targetGuid
|
||||
local refreshTime = GetTime()
|
||||
local myGuid = GetPlayerGUID()
|
||||
local myGuid = GetPlayerGuid()
|
||||
|
||||
-- Refresh in ownDebuffs - only if timer still active
|
||||
if ownDebuffs[guid] then
|
||||
@@ -1144,12 +840,8 @@ if hasNampower then
|
||||
pfTarget.update_aura = true
|
||||
end
|
||||
end
|
||||
|
||||
if pfUI.nameplates and pfUI.nameplates.OnAuraUpdate then
|
||||
pfUI.nameplates:OnAuraUpdate(guid)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
carnageState = nil
|
||||
this:Hide()
|
||||
end)
|
||||
@@ -1180,32 +872,22 @@ if hasNampower then
|
||||
return
|
||||
|
||||
elseif event == "SPELLCAST_CHANNEL_STOP" then
|
||||
-- Channel interrupted by player - clear ownDebuffs for the channeled spell immediately.
|
||||
-- DEBUFF_REMOVED fires later (0.5-1s server lag), causing phantom debuff display.
|
||||
-- We look up the active channel cast and pre-clear ownDebuffs for its target.
|
||||
local myGuid = GetPlayerGUID()
|
||||
local castData = myGuid and pfUI.libdebuff_casts[myGuid]
|
||||
if castData and castData.event == "CHANNEL" and castData.spellName then
|
||||
local spellName = castData.spellName
|
||||
-- Channel interrupted by player - clear ownDebuffs for the channeled spell
|
||||
-- via C_Spell.ChannelInfo. DEBUFF_REMOVED fires later (0.5-1s server lag),
|
||||
-- causing phantom debuff display without this pre-clear.
|
||||
local spellName = C_Spell.ChannelInfo()
|
||||
if spellName then
|
||||
local targetGuid = UnitGUID and UnitGUID("target")
|
||||
if targetGuid and ownDebuffs[targetGuid] and ownDebuffs[targetGuid][spellName] then
|
||||
local data = ownDebuffs[targetGuid][spellName]
|
||||
-- Only clear if the timer is still active (not already expired naturally)
|
||||
local remaining = (data.startTime + data.duration) - GetTime()
|
||||
if remaining > 0 then
|
||||
ownDebuffs[targetGuid][spellName] = nil
|
||||
end
|
||||
end
|
||||
pfUI.libdebuff_casts[myGuid] = nil
|
||||
end
|
||||
|
||||
elseif event == "PLAYER_ENTERING_WORLD" then
|
||||
GetPlayerGUID()
|
||||
elseif event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_TALENT_UPDATE" then
|
||||
UpdateCarnageRank()
|
||||
|
||||
elseif event == "PLAYER_TALENT_UPDATE" then
|
||||
UpdateCarnageRank()
|
||||
|
||||
elseif event == "UNIT_HEALTH" then
|
||||
local guid = arg1
|
||||
if guid and UnitIsDead and UnitIsDead(guid) then
|
||||
@@ -1218,48 +900,15 @@ if hasNampower then
|
||||
end
|
||||
|
||||
elseif event == "SPELL_START_SELF" or event == "SPELL_START_OTHER" then
|
||||
local itemId = arg1
|
||||
local spellId = arg2
|
||||
local casterGuid = arg3
|
||||
local spellType = arg8 or 0 -- 0=Normal, 1=Channel, 2=Autorepeating
|
||||
-- arg6=castTime, arg7=channel duration
|
||||
-- prefer arg6 if present — some spells (e.g. Volley post-rework) still send
|
||||
-- arg8=1 but now have a real cast time in arg6, so we only fall back to arg7
|
||||
-- when arg6 is nil (true channels like Blizzard)
|
||||
-- arg6=castTime (ms), arg7=channel duration (ms), arg8=spellType
|
||||
-- For channels: arg6=0 (no cast time), arg7=duration, spellType=1
|
||||
-- For normal casts: arg6=castTime, arg7=0, spellType=0
|
||||
-- Note: "not arg6" is wrong in Lua since 0 is truthy - use arg6 == 0 or nil
|
||||
-- arg6=castTime (ms), arg7=channel duration (ms). Prefer arg6 when set —
|
||||
-- some spells (e.g. post-rework Volley) flag as channel via arg8 but ship a
|
||||
-- real cast time in arg6, so we only fall back to arg7 for true channels
|
||||
-- like Blizzard where arg6 is 0/nil.
|
||||
local castTime = (arg6 and arg6 > 0) and arg6 or arg7
|
||||
local isChannel = spellType == 1 and (not arg6 or arg6 == 0)
|
||||
|
||||
if not casterGuid or not spellId then return end
|
||||
|
||||
local spellName = C_Spell.GetSpellName(spellId)
|
||||
local icon = libdebuff:GetSpellIcon(spellId)
|
||||
|
||||
-- Use item icon for item-triggered casts
|
||||
if itemId and itemId > 0 then
|
||||
icon = C_Item.GetItemIconByID(itemId) or icon
|
||||
pfUI.libdebuff_item_icons[casterGuid] = {
|
||||
icon = icon,
|
||||
name = GetItemInfo(itemId),
|
||||
}
|
||||
else
|
||||
pfUI.libdebuff_item_icons[casterGuid] = nil
|
||||
end
|
||||
|
||||
pfUI.libdebuff_casts[casterGuid] = {
|
||||
spellID = spellId,
|
||||
itemID = itemId and itemId > 0 and itemId or nil,
|
||||
spellName = spellName,
|
||||
icon = icon,
|
||||
startTime = GetTime(),
|
||||
duration = castTime and castTime / 1000 or 0,
|
||||
endTime = castTime and (GetTime() + castTime / 1000) or nil,
|
||||
event = isChannel and "CHANNEL" or "START"
|
||||
}
|
||||
|
||||
if event == "SPELL_START_SELF" and pfUI.libdebuff_spell_start_self_hooks then
|
||||
for _, fn in pairs(pfUI.libdebuff_spell_start_self_hooks) do
|
||||
fn(spellId, casterGuid, arg4, castTime)
|
||||
@@ -1277,16 +926,7 @@ if hasNampower then
|
||||
local targetGuid = arg4
|
||||
local numHit = arg6 or 0
|
||||
local numMissed = arg7 or 0
|
||||
|
||||
-- Clear cast bar only if SPELL_GO matches the active cast
|
||||
-- (Reactive procs like Frost Armor trigger SPELL_GO but shouldn't clear the castbar)
|
||||
-- Don't clear channels on SPELL_GO - channels persist until duration expires or SPELL_FAILED
|
||||
if casterGuid and pfUI.libdebuff_casts[casterGuid] then
|
||||
if pfUI.libdebuff_casts[casterGuid].spellID == spellId and pfUI.libdebuff_casts[casterGuid].event ~= "CHANNEL" then
|
||||
pfUI.libdebuff_casts[casterGuid] = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Fire registered SPELL_GO_SELF hooks BEFORE miss guard
|
||||
-- (Swingtimer needs to see ALL casts, even misses, for swing reset)
|
||||
if event == "SPELL_GO_SELF" and pfUI.libdebuff_spell_go_hooks then
|
||||
@@ -1297,15 +937,15 @@ if hasNampower then
|
||||
|
||||
if numMissed > 0 or numHit == 0 then return end
|
||||
|
||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
||||
local spellRankString = GetSpellRecField and GetSpellRecField(spellId, "rank")
|
||||
local spellName = C_Spell.GetSpellName(spellId)
|
||||
if not spellName then return end
|
||||
|
||||
local spellRankString = C_Spell.GetSpellSubtext(spellId)
|
||||
|
||||
local castRank = 0
|
||||
if spellRankString and spellRankString ~= "" then
|
||||
castRank = tonumber((string.gsub(spellRankString, "Rank ", ""))) or 0
|
||||
end
|
||||
|
||||
|
||||
-- Store in pendingCasts for DEBUFF_ADDED correlation.
|
||||
-- If this cast is a downrank of an already active debuff, fire the downrank blocked hook
|
||||
-- so external addons (e.g. SuperCleveRoidMacros) don't need to re-implement this check.
|
||||
@@ -1342,7 +982,7 @@ if hasNampower then
|
||||
local selfdebuffMode = pfUI_config and pfUI_config.buffbar and
|
||||
pfUI_config.buffbar.tdebuff and pfUI_config.buffbar.tdebuff.selfdebuff == "1"
|
||||
if selfdebuffMode then
|
||||
local myGuid2 = GetPlayerGUID()
|
||||
local myGuid2 = GetPlayerGuid()
|
||||
if casterGuid == myGuid2 then
|
||||
local duration = libdebuff:GetDuration(spellName, castRank) or 0
|
||||
if duration > 0 then
|
||||
@@ -1372,7 +1012,7 @@ if hasNampower then
|
||||
end
|
||||
|
||||
-- Store rank for our casts
|
||||
local myGuid = GetPlayerGUID()
|
||||
local myGuid = GetPlayerGuid()
|
||||
if casterGuid == myGuid then
|
||||
lastCastRanks[spellName] = {
|
||||
rank = castRank,
|
||||
@@ -1410,14 +1050,6 @@ if hasNampower then
|
||||
|
||||
elseif event == "SPELL_FAILED_OTHER" then
|
||||
local casterGuid = arg1
|
||||
local spellId = arg2
|
||||
|
||||
if casterGuid and pfUI.libdebuff_casts[casterGuid] then
|
||||
-- Only clear if spellID matches to avoid clearing a cast that already moved on
|
||||
if pfUI.libdebuff_casts[casterGuid].spellID == spellId then
|
||||
pfUI.libdebuff_casts[casterGuid] = nil
|
||||
end
|
||||
end
|
||||
if pfUI.libdebuff_spell_failed_other_hooks then
|
||||
for _, fn in pairs(pfUI.libdebuff_spell_failed_other_hooks) do
|
||||
fn(casterGuid, arg2)
|
||||
@@ -1453,11 +1085,6 @@ if hasNampower then
|
||||
pfUI.libpredict_pending_cast.time = nil
|
||||
end
|
||||
|
||||
-- Only capture CPs for combo-point abilities
|
||||
if spellName and IsComboPointAbility(spellName) then
|
||||
capturedCP = GetComboPoints() or 0
|
||||
end
|
||||
|
||||
-- Fire registered SPELL_CAST_EVENT hooks
|
||||
if pfUI.libdebuff_spell_cast_hooks then
|
||||
for _, fn in pairs(pfUI.libdebuff_spell_cast_hooks) do
|
||||
@@ -1479,7 +1106,7 @@ if hasNampower then
|
||||
if not spellId then return end
|
||||
if not targetGuid or targetGuid == "" or targetGuid == "0x0000000000000000" then return end
|
||||
|
||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
||||
local spellName = C_Spell.GetSpellName(spellId)
|
||||
if not spellName then return end
|
||||
|
||||
-- Deduplicate: Ignore if we processed this exact cast recently (within 100ms)
|
||||
@@ -1505,37 +1132,17 @@ if hasNampower then
|
||||
|
||||
local duration = durationMs and (durationMs / 1000) or 0
|
||||
local startTime = GetTime()
|
||||
local myGuid = GetPlayerGUID()
|
||||
local myGuid = GetPlayerGuid()
|
||||
local isOurs = (myGuid and casterGuid == myGuid)
|
||||
|
||||
if debugStats.enabled and isOurs then
|
||||
debugStats.aura_cast = debugStats.aura_cast + 1
|
||||
end
|
||||
|
||||
-- Combo-point abilities: Calculate duration based on CPs used
|
||||
if IsComboPointAbility(spellName) then
|
||||
if isOurs then
|
||||
-- OWN casts: use captured CPs from SPELL_CAST_EVENT (if available)
|
||||
local cp = capturedCP or 0
|
||||
local base, perCP = GetComboPointData(spellName)
|
||||
if base and perCP then
|
||||
duration = base + cp * perCP
|
||||
else
|
||||
-- Fallback to legacy database
|
||||
duration = libdebuff:GetDuration(spellName, rankNum)
|
||||
end
|
||||
capturedCP = nil -- consumed
|
||||
else
|
||||
-- OTHER players: CP unknown, no timer (except Expose Armor = fixed 30s)
|
||||
local base, perCP = GetComboPointData(spellName)
|
||||
if perCP and perCP == 0 and base then
|
||||
duration = base -- fixed duration (Expose Armor)
|
||||
else
|
||||
duration = 0 -- CP unknown for other players
|
||||
end
|
||||
end
|
||||
elseif duration == 0 then
|
||||
-- Non-CP managed spells: use database if AURA_CAST returned 0
|
||||
-- Duration comes from nampower's AURA_CAST event. ClassicAPI's
|
||||
-- C_UnitAuras now resolves combo-scaled durations server-side, so
|
||||
-- libdebuff only needs the database fallback when AURA_CAST reports 0.
|
||||
if duration == 0 then
|
||||
duration = libdebuff:GetDuration(spellName, rankNum) or 0
|
||||
end
|
||||
|
||||
@@ -1635,22 +1242,14 @@ if hasNampower then
|
||||
|
||||
end
|
||||
|
||||
-- Notify nameplates
|
||||
if pfUI.nameplates and pfUI.nameplates.OnAuraUpdate then
|
||||
pfUI.nameplates:OnAuraUpdate(targetGuid)
|
||||
end
|
||||
|
||||
-- Notify unitframes of debuff updates (UNIT_AURA doesn't fire on refreshes!)
|
||||
-- Check player
|
||||
if UnitGUID("player") then
|
||||
local playerGuid = UnitGUID("player")
|
||||
if playerGuid == targetGuid and pfPlayer then
|
||||
pfPlayer.update_aura = true
|
||||
end
|
||||
if IsPlayerGuid(targetGuid) and pfPlayer then
|
||||
pfPlayer.update_aura = true
|
||||
end
|
||||
|
||||
-- Check target
|
||||
if UnitGUID("target") then
|
||||
if UnitExists("target") then
|
||||
local tarUnitGUID = UnitGUID("target")
|
||||
if tarUnitGUID == targetGuid and pfTarget then
|
||||
pfTarget.update_aura = true
|
||||
@@ -1744,7 +1343,7 @@ if hasNampower then
|
||||
|
||||
-- Invalidate slot map cache for this GUID
|
||||
|
||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
||||
local spellName = C_Spell.GetSpellName(spellId)
|
||||
if not spellName then return end
|
||||
|
||||
if debugStats.enabled then
|
||||
@@ -1797,7 +1396,7 @@ if hasNampower then
|
||||
end
|
||||
end
|
||||
|
||||
local myGuid = GetPlayerGUID()
|
||||
local myGuid = GetPlayerGuid()
|
||||
local isOurs = (myGuid and casterGuid == myGuid)
|
||||
|
||||
-- Fallback: Check ownDebuffs timing
|
||||
@@ -1832,7 +1431,6 @@ if hasNampower then
|
||||
-- CRITICAL FIX: Update ownDebuffs here too for refresh timing!
|
||||
-- This prevents the gap between DEBUFF_REMOVED and AURA_CAST where buffwatch shows nothing
|
||||
if isOurs and casterGuid then
|
||||
local myGuid = GetPlayerGUID()
|
||||
if myGuid and casterGuid == myGuid then
|
||||
-- Check if we have timer data from allAuraCasts
|
||||
if allAuraCasts[guid] and allAuraCasts[guid][spellName] and allAuraCasts[guid][spellName][casterGuid] then
|
||||
@@ -1857,11 +1455,7 @@ if hasNampower then
|
||||
|
||||
-- Cleanup expired timers
|
||||
CleanupExpiredTimers(guid)
|
||||
|
||||
-- Notify nameplates
|
||||
if pfUI.nameplates and pfUI.nameplates.OnAuraUpdate then
|
||||
pfUI.nameplates:OnAuraUpdate(guid)
|
||||
end
|
||||
|
||||
if pfUI.libdebuff_debuff_added_other_hooks then
|
||||
for _, fn in pairs(pfUI.libdebuff_debuff_added_other_hooks) do
|
||||
fn(arg1, arg2, arg3, arg4)
|
||||
@@ -1878,9 +1472,9 @@ if hasNampower then
|
||||
local auraSlot = auraSlot_0based and (auraSlot_0based + 1) or nil
|
||||
|
||||
-- Invalidate slot map cache for this GUID
|
||||
|
||||
local spellName = (GetSpellRecField and GetSpellRecField(spellId, "name")) or "?"
|
||||
|
||||
|
||||
local spellName = C_Spell.GetSpellName(spellId) or "?"
|
||||
|
||||
if debugStats.enabled then
|
||||
debugStats.debuff_removed = debugStats.debuff_removed + 1
|
||||
if IsCurrentTarget(guid) then
|
||||
@@ -1888,13 +1482,13 @@ if hasNampower then
|
||||
GetDebugTimestamp(), displaySlot, auraSlot or -1, auraSlot_0based or -1, spellName))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- If unit is dead, cleanup all
|
||||
if UnitIsDead and UnitIsDead(guid) then
|
||||
CleanupUnit(guid)
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
-- Get auraSlot from event parameter (Nampower 2.29+)
|
||||
-- Fallback to displayToAura mapping if not available
|
||||
local foundAuraSlot = auraSlot
|
||||
@@ -1950,11 +1544,7 @@ if hasNampower then
|
||||
|
||||
-- Cleanup expired timers
|
||||
CleanupExpiredTimers(guid)
|
||||
|
||||
-- Notify nameplates
|
||||
if pfUI.nameplates and pfUI.nameplates.OnAuraUpdate then
|
||||
pfUI.nameplates:OnAuraUpdate(guid)
|
||||
end
|
||||
|
||||
if pfUI.libdebuff_debuff_removed_other_hooks then
|
||||
for _, fn in pairs(pfUI.libdebuff_debuff_removed_other_hooks) do
|
||||
fn(arg1, arg2, arg3, arg4)
|
||||
@@ -2094,5 +1684,3 @@ _G.SlashCmdList["MEMCHECK"] = function()
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00No ownSlots/allSlots (eliminated by GetUnitField approach!)|r")
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff============================================================|r")
|
||||
end
|
||||
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff33ff99[libdebuff]|r GetUnitField Edition loaded!")
|
||||
+61
-121
@@ -44,78 +44,13 @@ local healGuidToName = {} -- [casterGuid] = casterName, for SPELL_FAILED_OTHER c
|
||||
local ress_timers = {} -- [target][sender] = expiry_timestamp (60s rez window)
|
||||
local RESS_TIMEOUT = 60 -- Vanilla: rez offer expires after 60s
|
||||
|
||||
local PRAYER_OF_HEALING
|
||||
do -- Prayer of Healing
|
||||
local locales = {
|
||||
["deDE"] = "Gebet der Heilung",
|
||||
["enUS"] = "Prayer of Healing",
|
||||
["esES"] = "Rezo de curación",
|
||||
["frFR"] = "Prière de soins",
|
||||
["koKR"] = "치유의 기원",
|
||||
["ruRU"] = "Молитва исцеления",
|
||||
["zhCN"] = "治疗祷言",
|
||||
}
|
||||
|
||||
PRAYER_OF_HEALING = locales[GetLocale()] or locales["enUS"]
|
||||
end
|
||||
|
||||
local REJUVENATION
|
||||
do -- Rejuvenation
|
||||
local locales = {
|
||||
["deDE"] = "Verjüngung",
|
||||
["enUS"] = "Rejuvenation",
|
||||
["esES"] = "Rejuvenecimiento",
|
||||
["frFR"] = "Récupération",
|
||||
["koKR"] = "회복",
|
||||
["ruRU"] = "Омоложение",
|
||||
["zhCN"] = "回春术",
|
||||
}
|
||||
|
||||
REJUVENATION = locales[GetLocale()] or locales["enUS"]
|
||||
end
|
||||
|
||||
local RENEW
|
||||
do -- Renew
|
||||
local locales = {
|
||||
["deDE"] = "Erneuerung",
|
||||
["enUS"] = "Renew",
|
||||
["esES"] = "Renovar",
|
||||
["frFR"] = "Rénovation",
|
||||
["koKR"] = "소생",
|
||||
["ruRU"] = "Обновление",
|
||||
["zhCN"] = "恢复",
|
||||
}
|
||||
|
||||
RENEW = locales[GetLocale()] or locales["enUS"]
|
||||
end
|
||||
|
||||
local REGROWTH
|
||||
do -- Regrowth
|
||||
local locales = {
|
||||
["deDE"] = "Nachwachsen",
|
||||
["enUS"] = "Regrowth",
|
||||
["esES"] = "Recrecimiento",
|
||||
["frFR"] = "Rétablissement",
|
||||
["koKR"] = "재생",
|
||||
["ruRU"] = "Восстановление",
|
||||
["zhCN"] = "愈合",
|
||||
}
|
||||
|
||||
REGROWTH = locales[GetLocale()] or locales["enUS"]
|
||||
end
|
||||
|
||||
|
||||
-- Spell IDs for SPELL_GO_SELF callback (Nampower) - Instant HoT detection
|
||||
local SPELL_IDS = {
|
||||
-- Rejuvenation (all ranks)
|
||||
[774] = "Reju", [1058] = "Reju", [1430] = "Reju", [2090] = "Reju", [2091] = "Reju",
|
||||
[3627] = "Reju", [8910] = "Reju", [9839] = "Reju", [9840] = "Reju", [9841] = "Reju",
|
||||
[25299] = "Reju", [26981] = "Reju", [26982] = "Reju",
|
||||
-- Renew (all ranks)
|
||||
[139] = "Renew", [6074] = "Renew", [6075] = "Renew", [6076] = "Renew", [6077] = "Renew",
|
||||
[6078] = "Renew", [10927] = "Renew", [10928] = "Renew", [10929] = "Renew", [25315] = "Renew",
|
||||
[25221] = "Renew", [25222] = "Renew",
|
||||
}
|
||||
-- Localized spell names resolved once from canonical rank-1 spellIDs.
|
||||
-- Every rank shares the same name, so per-rank comparisons elsewhere can
|
||||
-- be done against these constants without per-locale or per-rank tables.
|
||||
local PRAYER_OF_HEALING = C_Spell.GetSpellName(596) -- Prayer of Healing (Rank 1)
|
||||
local REJUVENATION = C_Spell.GetSpellName(774) -- Rejuvenation (Rank 1)
|
||||
local RENEW = C_Spell.GetSpellName(139) -- Renew (Rank 1)
|
||||
local REGROWTH = C_Spell.GetSpellName(8936) -- Regrowth (Rank 1)
|
||||
|
||||
local libpredict = CreateFrame("Frame")
|
||||
libpredict:RegisterEvent("UNIT_HEALTH")
|
||||
@@ -173,7 +108,7 @@ end
|
||||
|
||||
local function isRezSpell(spellId)
|
||||
if not L["resurrections"] then return false end
|
||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
||||
local spellName = C_Spell.GetSpellName(spellId)
|
||||
return spellName and L["resurrections"][spellName]
|
||||
end
|
||||
|
||||
@@ -192,7 +127,7 @@ end)
|
||||
-- SPELL_START_SELF: own cast started (heals + rez)
|
||||
pfUI.libdebuff_spell_start_self_hooks = pfUI.libdebuff_spell_start_self_hooks or {}
|
||||
pfUI.libdebuff_spell_start_self_hooks["libpredict"] = function(spellId, casterGuid, targetGuid, castTime)
|
||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
||||
local spellName = C_Spell.GetSpellName(spellId)
|
||||
if not spellName then return end
|
||||
|
||||
local pendingTarget = nil
|
||||
@@ -255,7 +190,7 @@ pfUI.libdebuff_spell_start_self_hooks["libpredict"] = function(spellId, casterGu
|
||||
-- selfcast (ALT) = player, otherwise = current target
|
||||
-- Use this to find the correct group to heal
|
||||
local pohTarget = target or player
|
||||
if GetNumRaidMembers() > 0 then
|
||||
if IsInRaid() then
|
||||
-- Raid: find pohTarget's subgroup and heal only those members
|
||||
-- (Turtle WoW changed PoH to heal the target's group, not the caster's group)
|
||||
local targetGroup
|
||||
@@ -319,7 +254,7 @@ end
|
||||
-- SPELL_GO_SELF: own cast landed (HealStop + Regrowth timer)
|
||||
pfUI.libdebuff_spell_go_hooks["libpredict_sender"] = function(spellId)
|
||||
libpredict:HealStop(player)
|
||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
||||
local spellName = C_Spell.GetSpellName(spellId)
|
||||
if spellName == REGROWTH then
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
if libpredict.sender.regrowth_timer then
|
||||
@@ -337,7 +272,7 @@ end
|
||||
-- Signature: fn(spellId, casterGuid, targetGuid, castTime)
|
||||
pfUI.libdebuff_spell_start_other_hooks = pfUI.libdebuff_spell_start_other_hooks or {}
|
||||
pfUI.libdebuff_spell_start_other_hooks["libpredict"] = function(spellId, casterGuid, targetGuid, castTime)
|
||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
||||
local spellName = C_Spell.GetSpellName(spellId)
|
||||
if not spellName then return end
|
||||
|
||||
local casterName = resolveNameFromGuid(casterGuid)
|
||||
@@ -364,7 +299,7 @@ pfUI.libdebuff_spell_start_other_hooks["libpredict"] = function(spellId, casterG
|
||||
|
||||
-- Prayer of Healing: heal entire subgroup of the target
|
||||
if spellName == PRAYER_OF_HEALING then
|
||||
if GetNumRaidMembers() > 0 then
|
||||
if IsInRaid() then
|
||||
local targetGroup
|
||||
for i = 1, GetNumRaidMembers() do
|
||||
local rname, _, subgroup = GetRaidRosterInfo(i)
|
||||
@@ -404,8 +339,13 @@ end
|
||||
-- Signature: fn(spellId, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
|
||||
pfUI.libdebuff_spell_go_hooks = pfUI.libdebuff_spell_go_hooks or {}
|
||||
pfUI.libdebuff_spell_go_hooks["libpredict"] = function(spellId, a1, a2, a3, a4, a5, a6, a7)
|
||||
-- Instant HoTs
|
||||
local hotType = SPELL_IDS[spellId]
|
||||
-- Instant HoTs — classify by name (rank-independent) instead of a
|
||||
-- hardcoded per-rank ID table.
|
||||
local spellName = C_Spell.GetSpellName(spellId)
|
||||
local hotType
|
||||
if spellName == REJUVENATION then hotType = "Reju"
|
||||
elseif spellName == RENEW then hotType = "Renew"
|
||||
end
|
||||
if hotType then
|
||||
local targetGuid = a4
|
||||
local targetName = resolveNameFromGuid(targetGuid)
|
||||
@@ -426,9 +366,9 @@ pfUI.libdebuff_spell_go_hooks["libpredict"] = function(spellId, a1, a2, a3, a4,
|
||||
local rankStr = tostring(rank)
|
||||
if libpredict.sender and libpredict.sender.SendHealCommMsg then
|
||||
libpredict.sender:SendHealCommMsg(hotType .. "/" .. targetName .. "/" .. duration .. "/" .. rankStr .. "/")
|
||||
elseif GetNumRaidMembers() > 0 then
|
||||
elseif IsInRaid() then
|
||||
SendAddonMessage("HealComm", hotType .. "/" .. targetName .. "/" .. duration .. "/" .. rankStr .. "/", "RAID")
|
||||
elseif GetNumPartyMembers() > 0 then
|
||||
elseif IsInGroup() then
|
||||
SendAddonMessage("HealComm", hotType .. "/" .. targetName .. "/" .. duration .. "/" .. rankStr .. "/", "PARTY")
|
||||
end
|
||||
end
|
||||
@@ -546,30 +486,45 @@ function libpredict:ParseComm(sender, msg)
|
||||
rank = tonumber(rankStr)
|
||||
end
|
||||
end
|
||||
elseif select and pfGetCastInfo then
|
||||
elseif select then
|
||||
-- latest healcomm
|
||||
msgtype = tonumber(string.sub(msg, 1, 3))
|
||||
if not msgtype then return end
|
||||
|
||||
-- Resolve sender's name to a group unit token so C_Spell can query
|
||||
-- the cast. Group rosters are tiny (44 slots max) so the walk is cheap
|
||||
-- relative to the cost of receiving a HealComm message.
|
||||
local function senderUnit()
|
||||
if UnitName("player") == sender then return "player" end
|
||||
for i = 1, GetNumPartyMembers() do
|
||||
if UnitName("party"..i) == sender then return "party"..i end
|
||||
end
|
||||
for i = 1, GetNumRaidMembers() do
|
||||
if UnitName("raid"..i) == sender then return "raid"..i end
|
||||
end
|
||||
end
|
||||
|
||||
if msgtype == 0 then
|
||||
msgtype = "Heal"
|
||||
heal = tonumber(string.sub(msg, 4, 8))
|
||||
target = string.sub(msg,9, -1)
|
||||
|
||||
local starttime = select(5, pfGetCastInfo(sender))
|
||||
local endtime = select(6, pfGetCastInfo(sender))
|
||||
if not starttime or not endtime then return end
|
||||
time = endtime - starttime
|
||||
local unit = senderUnit()
|
||||
if not unit then return end
|
||||
local _, _, _, startMs, endMs = C_Spell.UnitCastingInfo(unit)
|
||||
if not startMs or not endMs then return end
|
||||
time = (endMs - startMs) / 1000
|
||||
elseif msgtype == 1 then
|
||||
msgtype = "Stop"
|
||||
elseif msgtype == 2 then
|
||||
msgtype = "Heal"
|
||||
heal = tonumber(string.sub(msg,4, 8))
|
||||
target = {strsplit(":", string.sub(msg,9, -1))}
|
||||
local starttime = select(5, pfGetCastInfo(sender))
|
||||
local endtime = select(6, pfGetCastInfo(sender))
|
||||
if not starttime or not endtime then return end
|
||||
time = endtime - starttime
|
||||
local unit = senderUnit()
|
||||
if not unit then return end
|
||||
local _, _, _, startMs, endMs = C_Spell.UnitCastingInfo(unit)
|
||||
if not startMs or not endMs then return end
|
||||
time = (endMs - startMs) / 1000
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -861,7 +816,7 @@ local hotsetbonus = libtipscan:GetScanner("hotsetbonus")
|
||||
resetcache:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
resetcache:RegisterEvent("LEARNED_SPELL_IN_TAB")
|
||||
resetcache:RegisterEvent("CHARACTER_POINTS_CHANGED")
|
||||
resetcache:RegisterEvent("UNIT_INVENTORY_CHANGED")
|
||||
resetcache:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
|
||||
resetcache:SetScript("OnEvent", function()
|
||||
if event == "PLAYER_ENTERING_WORLD" then
|
||||
-- load and initialize previous caches of spell amounts
|
||||
@@ -872,10 +827,7 @@ resetcache:SetScript("OnEvent", function()
|
||||
cache = pfUI_cache["prediction"][realm][player]["heals"]
|
||||
end
|
||||
|
||||
if event == "UNIT_INVENTORY_CHANGED" or "PLAYER_ENTERING_WORLD" then
|
||||
-- skip non-player events
|
||||
if arg1 and arg1 ~= "player" then return end
|
||||
|
||||
if event == "PLAYER_EQUIPMENT_CHANGED" or event == "PLAYER_ENTERING_WORLD" then
|
||||
local gear = ""
|
||||
for id = 1, 18 do
|
||||
gear = gear .. (GetInventoryItemLink("player",id) or "")
|
||||
@@ -944,18 +896,8 @@ local INSTANT_HOT_COOLDOWN = 1.0 -- 1 Sekunde Cooldown (GCD ist 1.5s)
|
||||
-- Pending HoTs Queue - wird nach Delay verifiziert
|
||||
local pendingHots = {}
|
||||
|
||||
-- Helper: check if buff is present on unit
|
||||
local function UnitHasBuff(unit, buffName)
|
||||
for i = 1, 32 do
|
||||
local name = UnitBuff(unit, i)
|
||||
if not name then break end
|
||||
if name == buffName then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Gather Data by User Actions
|
||||
hooksecurefunc("CastSpell", function(id, bookType)
|
||||
pfUI.hooksecurefunc("CastSpell", function(id, bookType)
|
||||
if not libpredict.sender.enabled then return end
|
||||
local effect, rank = libspell.GetSpellInfo(id, bookType)
|
||||
if not effect then return end
|
||||
@@ -1008,12 +950,10 @@ hooksecurefunc("CastSpell", function(id, bookType)
|
||||
end
|
||||
end)
|
||||
|
||||
hooksecurefunc("CastSpellByName", function(effect, target)
|
||||
pfUI.hooksecurefunc("CastSpellByName", function(effect, target)
|
||||
if not libpredict.sender.enabled then return end
|
||||
local effect, rank = libspell.GetSpellInfo(effect)
|
||||
if not effect then return end
|
||||
local mouseover = pfUI and pfUI.uf and pfUI.uf.mouseover and pfUI.uf.mouseover.unit
|
||||
mouseover = mouseover and UnitCanAssist("player", mouseover) and UnitName(mouseover)
|
||||
|
||||
local default = UnitName("target") and UnitCanAssist("player", "target") and UnitName("target") or UnitName("player")
|
||||
|
||||
@@ -1032,13 +972,13 @@ hooksecurefunc("CastSpellByName", function(effect, target)
|
||||
if not libpredict.sender.current_cast then
|
||||
spell_queue[1] = effect
|
||||
spell_queue[2] = effect.. ( rank or "" )
|
||||
spell_queue[3] = target or mouseover or default
|
||||
spell_queue[3] = target or default
|
||||
end
|
||||
|
||||
-- Instant HoTs: libdebuff/Nampower via GetHotDuration, hook method as fallback
|
||||
|
||||
if effect == REJUVENATION then
|
||||
local hotTarget = target or mouseover or default
|
||||
local hotTarget = target or default
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
local key = "Reju" .. hotTarget
|
||||
|
||||
@@ -1055,7 +995,7 @@ hooksecurefunc("CastSpellByName", function(effect, target)
|
||||
local rankStr = rankNum and tostring(rankNum) or "0"
|
||||
libpredict.sender:SendHealCommMsg("Reju/"..hotTarget.."/"..rejuvDuration.."/"..rankStr.."/")
|
||||
elseif effect == RENEW then
|
||||
local hotTarget = target or mouseover or default
|
||||
local hotTarget = target or default
|
||||
local now = pfUI.uf.now or GetTime()
|
||||
local key = "Renew" .. hotTarget
|
||||
|
||||
@@ -1074,7 +1014,7 @@ hooksecurefunc("CastSpellByName", function(effect, target)
|
||||
end
|
||||
end)
|
||||
|
||||
hooksecurefunc("UseAction", function(slot, target, selfcast)
|
||||
pfUI.hooksecurefunc("UseAction", function(slot, target, selfcast)
|
||||
if not libpredict.sender.enabled then return end
|
||||
if not IsCurrentAction(slot) then return end
|
||||
local kind, id = GetActionInfo(slot)
|
||||
@@ -1138,10 +1078,10 @@ libpredict.sender = CreateFrame("Frame", "pfPredictionSender", UIParent)
|
||||
libpredict.sender.enabled = true
|
||||
libpredict.sender.SendHealCommMsg = function(self, msg)
|
||||
-- Smart channel selection: Only send to relevant channel to avoid duplicates
|
||||
if GetNumRaidMembers() > 0 then
|
||||
if IsInRaid() then
|
||||
-- In raid: Only send to RAID (includes all raid members)
|
||||
SendAddonMessage("HealComm", msg, "RAID")
|
||||
elseif GetNumPartyMembers() > 0 then
|
||||
elseif IsInGroup() then
|
||||
-- In party: Only send to PARTY
|
||||
SendAddonMessage("HealComm", msg, "PARTY")
|
||||
end
|
||||
@@ -1150,10 +1090,10 @@ libpredict.sender.SendHealCommMsg = function(self, msg)
|
||||
end
|
||||
libpredict.sender.SendResCommMsg = function(self, msg)
|
||||
-- Smart channel selection: Only send to relevant channel to avoid duplicates
|
||||
if GetNumRaidMembers() > 0 then
|
||||
if IsInRaid() then
|
||||
-- In raid: Only send to RAID (includes all raid members)
|
||||
SendAddonMessage("CTRA", msg, "RAID")
|
||||
elseif GetNumPartyMembers() > 0 then
|
||||
elseif IsInGroup() then
|
||||
-- In party: Only send to PARTY
|
||||
SendAddonMessage("CTRA", msg, "PARTY")
|
||||
end
|
||||
@@ -1229,7 +1169,7 @@ libpredict.sender:SetScript("OnEvent", function()
|
||||
local amount = arg4
|
||||
local isCrit = arg5 == 1
|
||||
local isPeriodic = arg6 == 1
|
||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
||||
local spellName = C_Spell.GetSpellName(spellId)
|
||||
if spellName and spell_queue[1] == spellName then
|
||||
UpdateCache(spell_queue[2], amount, isCrit)
|
||||
end
|
||||
@@ -1249,9 +1189,9 @@ libpredict.sender:SetScript("OnEvent", function()
|
||||
local casterName = resolveNameFromGuid(casterGuid)
|
||||
if not casterName or casterName == player then return end -- own heals handled by SPELL_HEAL_BY_SELF
|
||||
|
||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
||||
local spellName = C_Spell.GetSpellName(spellId)
|
||||
if not spellName then return end
|
||||
local rankStr = GetSpellRecField and GetSpellRecField(spellId, "rank") or ""
|
||||
local rankStr = C_Spell.GetSpellSubtext(spellId)
|
||||
local spellKey = spellName .. (rankStr or "")
|
||||
|
||||
foreignCache[casterName] = foreignCache[casterName] or {}
|
||||
|
||||
+1
-1
@@ -54,6 +54,6 @@ end
|
||||
|
||||
pfUI.api.libtooltip = libtooltip
|
||||
|
||||
hooksecurefunc(GameTooltip, "SetBagItem", function(self, container, slot)
|
||||
pfUI.hooksecurefunc(GameTooltip, "SetBagItem", function(self, container, slot)
|
||||
_, libtooltip.itemCount = GetContainerItemInfo(container, slot)
|
||||
end)
|
||||
|
||||
+3
-3
@@ -241,14 +241,14 @@ pfUI.libdebuff_spell_go_hooks["libtotem"] = function(spellId)
|
||||
end
|
||||
|
||||
-- Hook CastSpellByName to store pending name/icon per slot
|
||||
hooksecurefunc("CastSpellByName", function(effect, target)
|
||||
pfUI.hooksecurefunc("CastSpellByName", function(effect, target)
|
||||
local name, rank, icon, _, _, _, spellId = libspell.GetSpellInfo(effect)
|
||||
if not name then return end
|
||||
libtotem:CheckAddQueue(name, rank, icon, spellId)
|
||||
end)
|
||||
|
||||
-- Hook CastSpell to store pending name/icon per slot
|
||||
hooksecurefunc("CastSpell", function(id, bookType)
|
||||
pfUI.hooksecurefunc("CastSpell", function(id, bookType)
|
||||
if not id or not bookType then return end
|
||||
if bookType ~= BOOKTYPE_SPELL and bookType ~= BOOKTYPE_PET then return end
|
||||
local name, rank, icon, _, _, _, spellId = libspell.GetSpellInfo(id, bookType)
|
||||
@@ -259,7 +259,7 @@ end)
|
||||
-- Hook UseAction. GetActionInfo + GetMacroSpell give us the spellID
|
||||
-- directly for both spell-action and macro-action slots, so the
|
||||
-- tooltip-scan fallback (and the "no spellId available" caveat) goes away.
|
||||
hooksecurefunc("UseAction", function(slot, target, selfcast)
|
||||
pfUI.hooksecurefunc("UseAction", function(slot, target, selfcast)
|
||||
if not IsCurrentAction(slot) then return end
|
||||
local kind, id = GetActionInfo(slot)
|
||||
local name, rank, spellID
|
||||
|
||||
@@ -335,8 +335,7 @@ pfUI:RegisterModule("actionbar", function ()
|
||||
local slfcast = C.bars.altself == "1" and IsAltKeyDown() and true or self.slfcast
|
||||
slfcast = C.bars.rightself == "1" and arg1 and arg1 == "RightButton" and true or slfcast
|
||||
self.slfcast = nil
|
||||
|
||||
if ( pfUI_config.bars.keydown == "1" and keystate == "down" and not drag_active ) or (pfUI_config.bars.keydown == "0" and keystate == "up" or drag_active ) or self.bar == 11 or mouse then
|
||||
if ( C.bars.keydown == "1" and keystate == "down" and not drag_active ) or (C.bars.keydown == "0" and keystate == "up" or drag_active ) or self.bar == 11 or mouse then
|
||||
if self.bar == 11 then
|
||||
CastShapeshiftForm(self.id)
|
||||
elseif grid == 1 then
|
||||
@@ -1013,10 +1012,8 @@ pfUI:RegisterModule("actionbar", function ()
|
||||
-- no button available, create a new one
|
||||
if not exists then
|
||||
-- prepare the button for vanilla
|
||||
if not f.HookScript then
|
||||
f.HookScript = HookScript
|
||||
f:SetScript("OnClick", ButtonClick)
|
||||
end
|
||||
if not f.HookScript then f.HookScript = HookScript end
|
||||
f:SetScript("OnClick", ButtonClick)
|
||||
|
||||
if bar ~= 11 then
|
||||
f:RegisterForDrag("LeftButton", "RightButton")
|
||||
@@ -1358,6 +1355,7 @@ pfUI:RegisterModule("actionbar", function ()
|
||||
|
||||
-- make stance bar dodge by default
|
||||
bars[i]:SetScript("OnShow", function()
|
||||
if pfUI.unlock and pfUI.unlock:IsShown() then return end
|
||||
if bars[11] and bars[11]:IsShown() then
|
||||
bars[11]:ClearAllPoints()
|
||||
bars[11]:SetPoint("BOTTOM", bars[12], "TOP", 0, 3*border)
|
||||
@@ -1367,6 +1365,7 @@ pfUI:RegisterModule("actionbar", function ()
|
||||
|
||||
-- restore old stance bar position
|
||||
bars[i]:SetScript("OnHide", function()
|
||||
if pfUI.unlock and pfUI.unlock:IsShown() then return end
|
||||
if bars[11] and bars[11]:IsShown() then
|
||||
bars[11]:ClearAllPoints()
|
||||
bars[11]:SetPoint("BOTTOM", bars[6], "TOP", 0, 3*border)
|
||||
|
||||
+4
-14
@@ -134,19 +134,9 @@ pfUI:RegisterModule("addoncompat", function ()
|
||||
end
|
||||
|
||||
-- run the addonconflict queue when firstrun is ready
|
||||
local delay = CreateFrame("Frame")
|
||||
delay:SetScript("OnUpdate", function()
|
||||
-- throttle to to one query per .1 second
|
||||
if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + .1 end
|
||||
|
||||
-- make sure the firstrun dialog has finished
|
||||
if pfUI.firstrun and pfUI.firstrun.steps then
|
||||
for _, step in pairs(pfUI.firstrun.steps) do
|
||||
if not pfUI_init[step.name] then return end
|
||||
end
|
||||
end
|
||||
|
||||
if pfUI.firstrun and pfUI.firstrun.completed then
|
||||
RunQueue()
|
||||
this:SetScript("OnUpdate", nil)
|
||||
end)
|
||||
else
|
||||
pfUI.events:RegisterCallback("firstrun:complete", RunQueue, "addoncompat")
|
||||
end
|
||||
end)
|
||||
|
||||
+5
-8
@@ -1,5 +1,4 @@
|
||||
pfUI:RegisterModule("afkcam", function ()
|
||||
local MARKED_AFK_CAPTURE = SanitizePattern(MARKED_AFK_MESSAGE)
|
||||
local social_chats = {
|
||||
"CHAT_MSG_SAY",
|
||||
"CHAT_MSG_WHISPER",
|
||||
@@ -151,9 +150,7 @@ pfUI:RegisterModule("afkcam", function ()
|
||||
delay:SetScript("OnUpdate", function()
|
||||
if ( this.tick or 0) > GetTime() then return else this.tick = GetTime() + 1 end
|
||||
|
||||
local name = UnitName("player")
|
||||
local cast = pfGetCastInfo(name)
|
||||
if not cast then cast = pfGetChannelInfo(name) end
|
||||
local cast = C_Spell.UnitCastingInfo("player") or C_Spell.UnitChannelInfo("player")
|
||||
if not this.delay then this.delay = 0 end
|
||||
|
||||
if cast then
|
||||
@@ -167,10 +164,10 @@ pfUI:RegisterModule("afkcam", function ()
|
||||
end)
|
||||
|
||||
afkcam:SetScript("OnEvent", function()
|
||||
if event == "CHAT_MSG_SYSTEM" then
|
||||
if (arg1 == _G.MARKED_AFK) or strfind(arg1, MARKED_AFK_CAPTURE) then
|
||||
if event == "PLAYER_FLAGS_CHANGED" then
|
||||
if UnitIsAFK('player') then
|
||||
delay:Show()
|
||||
elseif (arg1 == _G.CLEARED_AFK) then
|
||||
elseif delay:IsVisible() then
|
||||
delay:Hide()
|
||||
this:stop()
|
||||
end
|
||||
@@ -182,7 +179,7 @@ pfUI:RegisterModule("afkcam", function ()
|
||||
end
|
||||
end)
|
||||
|
||||
afkcam:RegisterEvent("CHAT_MSG_SYSTEM")
|
||||
afkcam:RegisterEvent("PLAYER_FLAGS_CHANGED")
|
||||
afkcam:RegisterEvent("PLAYER_REGEN_DISABLED")
|
||||
afkcam:RegisterEvent("PLAYER_LEAVING_WORLD") -- reseting cvars on PLAYER_LOGOUT crashes the client ¯\_(ツ)_/¯
|
||||
end)
|
||||
|
||||
@@ -19,12 +19,7 @@ pfUI:RegisterModule("autovendor", function ()
|
||||
local startGold = GetMoney()
|
||||
C_MerchantFrame.SellAllJunkItems()
|
||||
|
||||
local reporter = CreateFrame("Frame")
|
||||
reporter.deadline = GetTime() + 0.3
|
||||
reporter:SetScript("OnUpdate", function()
|
||||
if GetTime() < this.deadline then return end
|
||||
this:SetScript("OnUpdate", nil)
|
||||
this:Hide()
|
||||
C_Timer.After(0.3, function()
|
||||
local income = GetMoney() - startGold
|
||||
if income > 0 then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(T["Your vendor trash has been sold and you earned"] .. " " .. CreateGoldString(income))
|
||||
|
||||
+3
-3
@@ -12,7 +12,9 @@ pfUI:RegisterModule("bubbles", function ()
|
||||
pfUI.bubbles:RegisterEvent("CHAT_MSG_MONSTER_PARTY")
|
||||
|
||||
pfUI.bubbles:SetScript("OnEvent", function()
|
||||
pfUI.bubbles:SetScript("OnUpdate", pfUI.bubbles.ScanBubbles)
|
||||
-- Bubble frames are attached to WorldFrame after the chat event fires,
|
||||
-- so wait one tick before scanning.
|
||||
RunNextFrame(function() pfUI.bubbles:ScanBubbles() end)
|
||||
end)
|
||||
|
||||
function pfUI.bubbles:IsBubble(f)
|
||||
@@ -66,7 +68,5 @@ pfUI:RegisterModule("bubbles", function ()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
pfUI.bubbles:SetScript("OnUpdate", nil)
|
||||
end
|
||||
end)
|
||||
|
||||
+4
-4
@@ -60,6 +60,7 @@ pfUI:RegisterModule("buff", function ()
|
||||
buff.mode = buff.btype
|
||||
buff.expirationTime = aura.expirationTime
|
||||
buff.stackCount = aura.applications
|
||||
buff.spellId = aura.spellId
|
||||
buff.texture:SetTexture(aura.icon)
|
||||
|
||||
if buff.btype == "HARMFUL" then
|
||||
@@ -146,9 +147,8 @@ pfUI:RegisterModule("buff", function ()
|
||||
CancelItemTempEnchantment(1)
|
||||
elseif CancelItemTempEnchantment and this.mode and this.mode == "OFFHAND" then
|
||||
CancelItemTempEnchantment(2)
|
||||
else
|
||||
local bid = GetPlayerBuff(PLAYER_BUFF_START_ID + this.id, this.btype)
|
||||
if bid >= 0 then CancelPlayerBuff(bid) end
|
||||
elseif this.spellId then
|
||||
C_Spell.CancelSpellByID(this.spellId)
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -159,7 +159,7 @@ pfUI:RegisterModule("buff", function ()
|
||||
|
||||
pfUI.buff = CreateFrame("Frame", "pfGlobalBuffFrame", UIParent)
|
||||
pfUI.buff:RegisterEvent("PLAYER_AURAS_CHANGED")
|
||||
pfUI.buff:RegisterEvent("UNIT_INVENTORY_CHANGED")
|
||||
pfUI.buff:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
|
||||
pfUI.buff:RegisterEvent("UNIT_MODEL_CHANGED")
|
||||
pfUI.buff:RegisterEvent("BUFF_UPDATE_DURATION_SELF")
|
||||
pfUI.buff:RegisterEvent("DEBUFF_UPDATE_DURATION_SELF")
|
||||
|
||||
+16
-21
@@ -74,18 +74,11 @@ pfUI:RegisterModule("buffwatch", function ()
|
||||
end
|
||||
|
||||
local function GetBuffData(unit, id, type, selfdebuff)
|
||||
if unit == "player" then
|
||||
local aura = C_UnitAuras.GetAuraDataByIndex("player", id, type)
|
||||
if not aura then return end
|
||||
local remaining = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or 0
|
||||
return remaining, aura.icon, aura.name, aura.applications
|
||||
elseif libdebuff and selfdebuff then
|
||||
local name, _, texture, stacks, _, _, timeleft = libdebuff:UnitOwnDebuff(unit, id)
|
||||
return timeleft, texture, name, stacks
|
||||
elseif libdebuff then
|
||||
local name, _, texture, stacks, _, _, timeleft = libdebuff:UnitDebuff(unit, id)
|
||||
return timeleft, texture, name, stacks
|
||||
end
|
||||
local filter = (selfdebuff and type == "HARMFUL") and "HARMFUL|PLAYER" or type
|
||||
local aura = C_UnitAuras.GetAuraDataByIndex(unit, id, filter)
|
||||
if not aura then return end
|
||||
local remaining = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or 0
|
||||
return remaining, aura.icon, aura.name, aura.applications
|
||||
end
|
||||
|
||||
local function StatusBarOnClick()
|
||||
@@ -106,8 +99,8 @@ pfUI:RegisterModule("buffwatch", function ()
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffcc" .. skill .. "|r" .. T["is now blacklisted."])
|
||||
end
|
||||
elseif this.parent.unit == "player" then
|
||||
local bid = GetPlayerBuff(PLAYER_BUFF_START_ID + this.id, this.type)
|
||||
if bid >= 0 then CancelPlayerBuff(bid) end
|
||||
local aura = C_UnitAuras.GetAuraDataByIndex("player", this.id, this.type)
|
||||
if aura and aura.spellId then C_Spell.CancelSpellByID(aura.spellId) end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -117,15 +110,17 @@ pfUI:RegisterModule("buffwatch", function ()
|
||||
if this.unit == "player" then
|
||||
GameTooltip:SetUnitAura("player", this.id, this.type)
|
||||
elseif this.type == "HARMFUL" then
|
||||
-- For "only own debuffs" mode: find the REAL slot by matching spell name AND caster
|
||||
-- selfdebuff filters the displayed list to player-cast harmful auras, but
|
||||
-- SetUnitAura's index has to be into the engine's full HARMFUL list. Look
|
||||
-- up the displayed aura via the PLAYER filter, then scan engine slots for
|
||||
-- one whose name + sourceGUID match.
|
||||
local config = this.parent and this.parent.config
|
||||
if config and config.selfdebuff == "1" and libdebuff then
|
||||
local ownDebuffName = libdebuff:UnitOwnDebuff(this.unit, this.id)
|
||||
if ownDebuffName then
|
||||
-- Search through all game slots to find OUR debuff with matching name
|
||||
if config and config.selfdebuff == "1" then
|
||||
local ownAura = C_UnitAuras.GetAuraDataByIndex(this.unit, this.id, "HARMFUL|PLAYER")
|
||||
if ownAura then
|
||||
for gameSlot = 1, 16 do
|
||||
local gameName, _, _, _, _, _, _, gameCaster = libdebuff:UnitDebuff(this.unit, gameSlot)
|
||||
if gameName == ownDebuffName and gameCaster == "player" then
|
||||
local check = C_UnitAuras.GetDebuffDataByIndex(this.unit, gameSlot)
|
||||
if check and check.name == ownAura.name and check.sourceGUID == ownAura.sourceGUID then
|
||||
GameTooltip:SetUnitAura(this.unit, gameSlot, "HARMFUL")
|
||||
break
|
||||
end
|
||||
|
||||
+347
-202
@@ -16,6 +16,141 @@ pfUI:RegisterModule("castbar", function ()
|
||||
end
|
||||
end
|
||||
|
||||
-- Clear cast state on the bar. Shows the bar full for one frame, then
|
||||
-- OnUpdate fades it out.
|
||||
local function ClearBar(cb)
|
||||
cb.startTime, cb.endTime, cb.isChannel = nil, nil, nil
|
||||
cb.activeName, cb.spellID = nil, nil
|
||||
cb.isTradeskill = nil
|
||||
cb.tradeskillTotal, cb.tradeskillCompleted, cb.tradeskillSpellID = nil, nil, nil
|
||||
cb.tradeskillSingleMs, cb.currentCraftStart = nil, nil
|
||||
cb.lastMax = nil
|
||||
cb.delay = 0
|
||||
cb.bar:SetMinMaxValues(1, 100)
|
||||
cb.bar:SetValue(100)
|
||||
if cb.bar.spark then cb.bar.spark:Hide() end
|
||||
cb.fadeout = 1
|
||||
end
|
||||
|
||||
-- "Spellname (N)" label for an active tradeskill merge — N is the number
|
||||
-- of crafts remaining. Falls back to the bare spell name when ≤1 left.
|
||||
local function UpdateTradeskillLabel(cb)
|
||||
if not cb.tradeskillTotal or not cb.activeName or not cb.showname then return end
|
||||
local remaining = cb.tradeskillTotal - (cb.tradeskillCompleted or 0)
|
||||
if remaining > 1 then
|
||||
cb.bar.left:SetText(string.format("%s (%d)", cb.activeName, remaining))
|
||||
else
|
||||
cb.bar.left:SetText(cb.activeName)
|
||||
end
|
||||
end
|
||||
|
||||
-- Stretch the bar to span all queued crafts (Quartz-style merge): the bar
|
||||
-- fills continuously across the chain instead of resetting per craft.
|
||||
-- Also arms the per-craft spark — a thin marker that crosses the bar once
|
||||
-- per craft (so it moves N× faster than the main fill on an N-stack).
|
||||
local function EnterTradeskillMerge(cb, startMs, endMs, count)
|
||||
cb.tradeskillTotal = count
|
||||
cb.tradeskillCompleted = 0
|
||||
cb.tradeskillSpellID = cb.spellID
|
||||
local single = endMs - startMs
|
||||
cb.tradeskillSingleMs = single
|
||||
cb.currentCraftStart = startMs
|
||||
local mergedEnd = startMs + single * count
|
||||
cb.endTime = mergedEnd
|
||||
local duration = (mergedEnd - startMs) / 1000
|
||||
cb.bar:SetMinMaxValues(0, duration)
|
||||
cb.lastMax = duration
|
||||
if cb.bar.spark then cb.bar.spark:Show() end
|
||||
UpdateTradeskillLabel(cb)
|
||||
end
|
||||
|
||||
-- Mark the start of craft 2..N within an active merge (called from the
|
||||
-- SPELLCAST_START / SPELL_START_SELF handlers). Resets the per-craft
|
||||
-- spark to the left edge of the bar.
|
||||
local function StartTradeskillCraft(cb)
|
||||
cb.currentCraftStart = GetTime() * 1000
|
||||
UpdateTradeskillLabel(cb)
|
||||
end
|
||||
|
||||
-- Stamp the bar with cast data and render text/icon/lag once. OnUpdate
|
||||
-- then animates the fill from this state without touching C_Spell.
|
||||
local function StampBar(cb, name, tex, startMs, endMs, spellID, isChannel, delayMs, isTradeskill)
|
||||
cb.startTime = startMs
|
||||
cb.endTime = endMs
|
||||
cb.isChannel = isChannel
|
||||
cb.spellID = spellID
|
||||
cb.activeName = name
|
||||
cb.isTradeskill = isTradeskill
|
||||
cb.delay = (delayMs or 0) / 1000
|
||||
cb:SetAlpha(1)
|
||||
cb.fadeout = nil
|
||||
|
||||
cb.bar:SetStatusBarColor(strsplit(",", C.appearance.castbar[isChannel and "channelcolor" or "castbarcolor"]))
|
||||
|
||||
local rank = ""
|
||||
if spellID and GetSpellRecField then
|
||||
rank = GetSpellRecField(spellID, "rank") or ""
|
||||
end
|
||||
local spellname = (cb.showname and name) and (name .. " ") or ""
|
||||
local rankstr = (cb.showrank and rank ~= "") and string.format("|cffaaffcc[%s]|r", rank) or ""
|
||||
cb.bar.left:SetText(spellname .. rankstr)
|
||||
|
||||
if tex and cb.showicon then
|
||||
local size = cb:GetHeight()
|
||||
cb.icon:Show()
|
||||
cb.icon:SetHeight(size)
|
||||
cb.icon:SetWidth(size)
|
||||
cb.icon.texture:SetTexture(tex)
|
||||
cb.bar:SetPoint("TOPLEFT", cb.icon, "TOPRIGHT", cb.spacing, 0)
|
||||
else
|
||||
cb.bar:SetPoint("TOPLEFT", cb, 0, 0)
|
||||
cb.icon:Hide()
|
||||
end
|
||||
|
||||
local duration = (endMs - startMs) / 1000
|
||||
if cb.showlag then
|
||||
local _, _, lag = GetNetStats()
|
||||
cb.bar.lag:SetWidth(math.min(cb:GetWidth(), cb:GetWidth() / duration * (lag/1000)))
|
||||
cb.bar.lag:Show()
|
||||
else
|
||||
cb.bar.lag:Hide()
|
||||
end
|
||||
|
||||
cb.bar:SetMinMaxValues(0, duration)
|
||||
cb.lastMax = duration
|
||||
end
|
||||
|
||||
-- One-shot poll: read C_Spell for the bar's unit, stamp or clear. Called
|
||||
-- from event handlers (cast start, target/focus change), never per-frame.
|
||||
local function RefreshBar(cb)
|
||||
local query = cb.unitstr ~= "" and cb.unitstr or cb.unitname
|
||||
if not query or (cb.unitstr ~= "" and not UnitExists(cb.unitstr)) then
|
||||
ClearBar(cb)
|
||||
return
|
||||
end
|
||||
local name, _, tex, startMs, endMs, isTradeskill, _, _, spellID, _, delayMs = C_Spell.UnitCastingInfo(query)
|
||||
local isChan
|
||||
if not name then
|
||||
name, _, tex, startMs, endMs, _, _, spellID = C_Spell.UnitChannelInfo(query)
|
||||
isChan = true
|
||||
end
|
||||
-- Synthetic fallback for abilities the engine treats as instant-cast but
|
||||
-- that have a meaningful wait window (e.g. Turtle WoW Steady Shot on the
|
||||
-- ranged-swing queue). Per-unit table populated by module-side hooks.
|
||||
if not name and pfUI.synthetic_casts and pfUI.synthetic_casts[query] then
|
||||
local s = pfUI.synthetic_casts[query]
|
||||
if s.endMs > GetTime() * 1000 then
|
||||
name, tex, startMs, endMs, spellID = s.name, s.icon, s.startMs, s.endMs, s.spellID
|
||||
isChan = nil
|
||||
end
|
||||
end
|
||||
if name and startMs and endMs then
|
||||
StampBar(cb, name, tex, startMs, endMs, spellID, isChan, delayMs, isTradeskill)
|
||||
else
|
||||
ClearBar(cb)
|
||||
end
|
||||
end
|
||||
|
||||
local function CreateCastbar(name, parent, unitstr, unitname)
|
||||
local cb = CreateFrame("Frame", name, parent or UIParent)
|
||||
|
||||
@@ -58,7 +193,7 @@ pfUI:RegisterModule("castbar", function ()
|
||||
cb.bar.left:SetFontObject(GameFontWhite)
|
||||
cb.bar.left:SetTextColor(1,1,1,1)
|
||||
cb.bar.left:SetFont(font, font_size, "OUTLINE")
|
||||
cb.bar.left:SetJustifyH("left")
|
||||
cb.bar.left:SetJustifyH(C.castbar[unitstr].namealign or "LEFT")
|
||||
|
||||
-- text right
|
||||
cb.bar.right = cb.bar:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
||||
@@ -69,245 +204,244 @@ pfUI:RegisterModule("castbar", function ()
|
||||
cb.bar.right:SetFontObject(GameFontWhite)
|
||||
cb.bar.right:SetTextColor(1,1,1,1)
|
||||
cb.bar.right:SetFont(font, font_size, "OUTLINE")
|
||||
cb.bar.right:SetJustifyH("right")
|
||||
cb.bar.right:SetJustifyH(C.castbar[unitstr].timealign or "RIGHT")
|
||||
|
||||
cb.bar.lag = cb.bar:CreateTexture(nil, "OVERLAY")
|
||||
cb.bar.lag:SetPoint("TOPRIGHT", cb.bar, "TOPRIGHT", 0, 0)
|
||||
cb.bar.lag:SetPoint("BOTTOMRIGHT", cb.bar, "BOTTOMRIGHT", 0, 0)
|
||||
cb.bar.lag:SetTexture(1,.2,.2,.2)
|
||||
|
||||
-- OnUpdate script with throttle for performance optimization
|
||||
-- Per-craft progress spark for tradeskill merge — a thin vertical line
|
||||
-- that crosses the bar once per craft in the chain (so on a 5-stack it
|
||||
-- moves 5x faster than the main fill). Position is updated by OnUpdate
|
||||
-- while a merge is active; hidden otherwise.
|
||||
cb.bar.spark = cb.bar:CreateTexture(nil, "OVERLAY")
|
||||
cb.bar.spark:SetTexture(1, 1, 1, 0.8)
|
||||
cb.bar.spark:SetWidth(2)
|
||||
cb.bar.spark:SetPoint("TOP", cb.bar, "TOPLEFT", 0, 0)
|
||||
cb.bar.spark:SetPoint("BOTTOM", cb.bar, "BOTTOMLEFT", 0, 0)
|
||||
cb.bar.spark:Hide()
|
||||
|
||||
-- OnUpdate animates the bar fill and fades it out on completion. All
|
||||
-- state transitions (cast start/stop/interrupt, channel start/stop,
|
||||
-- pushback) come from the event handler below — we never poll C_Spell
|
||||
-- here.
|
||||
cb:SetScript("OnUpdate", function()
|
||||
-- Throttle for performance
|
||||
if (this.tick or 0) > GetTime() then return end
|
||||
this.tick = GetTime() + 0.020 -- ~50 FPS for smooth castbar
|
||||
this.tick = GetTime() + 0.020 -- ~50 FPS
|
||||
|
||||
if this.drag and this.drag:IsShown() then
|
||||
this:SetAlpha(1)
|
||||
return
|
||||
end
|
||||
|
||||
if not UnitExists(this.unitstr) then
|
||||
this:SetAlpha(0)
|
||||
end
|
||||
|
||||
if this.fadeout and this:GetAlpha() > 0 then
|
||||
if this:GetAlpha() == 0 then
|
||||
this.fadeout = nil
|
||||
end
|
||||
|
||||
this:SetAlpha(this:GetAlpha()-0.05)
|
||||
this:SetAlpha(this:GetAlpha() - 0.05)
|
||||
if this:GetAlpha() <= 0 then this.fadeout = nil end
|
||||
return
|
||||
end
|
||||
|
||||
local channel = nil
|
||||
local query = this.unitstr ~= "" and this.unitstr or this.unitname
|
||||
if not query then return end
|
||||
|
||||
-- Check if we have a GUID-based focus (Turtle WoW native GUID)
|
||||
local focusGuid = nil
|
||||
if this.unitstr and string.find(this.unitstr, "^0x") then
|
||||
focusGuid = this.unitstr
|
||||
elseif this.unitstr and this.unitstr == "player" and UnitGUID then
|
||||
focusGuid = UnitGUID("player")
|
||||
elseif this.unitstr and this.unitstr ~= "player" then
|
||||
local guid = UnitGUID(this.unitstr)
|
||||
if guid then focusGuid = guid end
|
||||
end
|
||||
this.focusGuid = focusGuid
|
||||
|
||||
-- Try libdebuff_casts first for GUID-based units (works with Turtle GUID + Nampower events)
|
||||
local cast, nameSubtext, text, texture, startTime, endTime
|
||||
local castBlocked = false
|
||||
if focusGuid and pfUI.libdebuff_casts and pfUI.libdebuff_casts[focusGuid] then
|
||||
local castData = pfUI.libdebuff_casts[focusGuid]
|
||||
if castData.event == "CAST" or castData.event == "FAIL" then
|
||||
castBlocked = true
|
||||
pfUI.libdebuff_casts[focusGuid] = nil
|
||||
elseif (castData.event == "START" or castData.event == "CHANNEL") and castData.endTime and castData.endTime > GetTime() then
|
||||
cast = castData.spellName
|
||||
texture = castData.icon
|
||||
startTime = castData.startTime * 1000
|
||||
endTime = castData.endTime * 1000
|
||||
-- Try to get rank from spell DB via spellID (nameSubtext is not stored in libdebuff_casts)
|
||||
if castData.spellID and GetSpellRecField then
|
||||
nameSubtext = GetSpellRecField(castData.spellID, "rank") or ""
|
||||
else
|
||||
nameSubtext = ""
|
||||
end
|
||||
if castData.event == "CHANNEL" then
|
||||
channel = cast
|
||||
end
|
||||
end
|
||||
if not this.endTime then
|
||||
if this:GetAlpha() ~= 0 then this:SetAlpha(0) end
|
||||
return
|
||||
end
|
||||
|
||||
local useLibcastForPlayer = this.unitstr == "player"
|
||||
|
||||
-- For player: use player name to query libcast.db directly
|
||||
if not cast and useLibcastForPlayer then
|
||||
query = UnitName("player")
|
||||
-- Non-player bars: if the unit disappears (target died / detarget),
|
||||
-- drop the bar immediately.
|
||||
if this.unitstr ~= "" and this.unitstr ~= "player" and not UnitExists(this.unitstr) then
|
||||
ClearBar(this)
|
||||
return
|
||||
end
|
||||
|
||||
-- Fallback: pfGetCastInfo only when no focusGuid (Nampower not available for this unit).
|
||||
-- If we have a focusGuid, libdebuff is authoritative - don't fall back to libcast
|
||||
-- even if no cast is active (prevents false positives e.g. spellbook clicks on CD spells).
|
||||
if not cast and not castBlocked and not focusGuid and pfGetCastInfo then
|
||||
cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = pfGetCastInfo(query)
|
||||
local now = GetTime()
|
||||
local endSec = this.endTime / 1000
|
||||
if now >= endSec then
|
||||
ClearBar(this)
|
||||
return
|
||||
end
|
||||
|
||||
if not cast and not castBlocked and not focusGuid and pfGetChannelInfo then
|
||||
channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = pfGetChannelInfo(this.unitstr or this.unitname)
|
||||
cast = channel
|
||||
local startSec = this.startTime / 1000
|
||||
local max = endSec - startSec
|
||||
local cur = this.isChannel and (endSec - now) or (now - startSec)
|
||||
if cur > max then cur = max end
|
||||
if cur < 0 then cur = 0 end
|
||||
|
||||
this.bar:SetValue(cur)
|
||||
|
||||
-- Per-craft spark for a tradeskill merge: position by (now - current
|
||||
-- craft's start) / single-craft duration, clamped to [0, 1]. Snaps back
|
||||
-- to the left edge each time StartTradeskillCraft fires for craft N+1.
|
||||
if this.tradeskillTotal and this.tradeskillSingleMs and this.currentCraftStart then
|
||||
local craftElapsed = now * 1000 - this.currentCraftStart
|
||||
local p = craftElapsed / this.tradeskillSingleMs
|
||||
if p < 0 then p = 0 elseif p > 1 then p = 1 end
|
||||
local x = p * this.bar:GetWidth() - 1
|
||||
this.bar.spark:ClearAllPoints()
|
||||
this.bar.spark:SetPoint("TOP", this.bar, "TOPLEFT", x, 0)
|
||||
this.bar.spark:SetPoint("BOTTOM", this.bar, "BOTTOMLEFT", x, 0)
|
||||
end
|
||||
|
||||
if cast then
|
||||
local duration = endTime - startTime
|
||||
local max = duration / 1000
|
||||
local cur = GetTime() - startTime / 1000
|
||||
|
||||
this:SetAlpha(1)
|
||||
|
||||
local spellname = this.showname and cast and cast .. " " or ""
|
||||
local rank = this.showrank and nameSubtext and nameSubtext ~= "" and string.format("|cffaaffcc[%s]|r", nameSubtext) or ""
|
||||
|
||||
if this.endTime ~= endTime then
|
||||
this.bar:SetStatusBarColor(strsplit(",", C.appearance.castbar[(channel and "channelcolor" or "castbarcolor")]))
|
||||
this.bar.left:SetText(spellname .. rank)
|
||||
this.fadeout = nil
|
||||
this.endTime = endTime
|
||||
|
||||
-- set texture
|
||||
if texture and this.showicon then
|
||||
local size = this:GetHeight()
|
||||
this.icon:Show()
|
||||
this.icon:SetHeight(size)
|
||||
this.icon:SetWidth(size)
|
||||
|
||||
-- Override with item icon from libdebuff_casts or persistent item icon cache
|
||||
local useTexture = texture
|
||||
local useItemName = nil
|
||||
if pfUI.libdebuff_casts or pfUI.libdebuff_item_icons then
|
||||
local castGuid = nil
|
||||
if this.unitstr and UnitExists then
|
||||
local guid = UnitGUID(this.unitstr)
|
||||
castGuid = guid
|
||||
end
|
||||
if castGuid then
|
||||
-- First check active cast data
|
||||
if pfUI.libdebuff_casts and pfUI.libdebuff_casts[castGuid] and pfUI.libdebuff_casts[castGuid].itemID then
|
||||
useTexture = pfUI.libdebuff_casts[castGuid].icon or texture
|
||||
-- Fallback to persistent item icon cache
|
||||
elseif pfUI.libdebuff_item_icons and pfUI.libdebuff_item_icons[castGuid] then
|
||||
useTexture = pfUI.libdebuff_item_icons[castGuid].icon or texture
|
||||
useItemName = pfUI.libdebuff_item_icons[castGuid].name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
this.icon.texture:SetTexture(useTexture)
|
||||
this.bar:SetPoint("TOPLEFT", this.icon, "TOPRIGHT", this.spacing, 0)
|
||||
|
||||
-- Override spell name with item name for item-triggered casts
|
||||
if useItemName and this.showname then
|
||||
this.bar.left:SetText(useItemName .. " " .. rank)
|
||||
end
|
||||
else
|
||||
this.bar:SetPoint("TOPLEFT", this, 0, 0)
|
||||
this.icon:Hide()
|
||||
end
|
||||
|
||||
if this.showlag then
|
||||
local _, _, lag = GetNetStats()
|
||||
local width = this:GetWidth() / (duration/1000) * (lag/1000)
|
||||
this.bar.lag:SetWidth(math.min(this:GetWidth(), width))
|
||||
else
|
||||
this.bar.lag:Hide()
|
||||
end
|
||||
if this.showtimer then
|
||||
if (this.delay or 0) > 0 then
|
||||
local prefix = "|cffffaaaa" .. (this.isChannel and "-" or "+") .. FormatCastbarTime(this.delay) .. " |r "
|
||||
this.bar.right:SetText(prefix .. FormatCastbarTime(cur) .. " / " .. FormatCastbarTime(max))
|
||||
else
|
||||
this.bar.right:SetText(FormatCastbarTime(cur) .. " / " .. FormatCastbarTime(max))
|
||||
end
|
||||
|
||||
local newMax = duration / 1000
|
||||
if this.lastMax ~= newMax then
|
||||
this.bar:SetMinMaxValues(0, newMax)
|
||||
this.lastMax = newMax
|
||||
end
|
||||
|
||||
if channel then
|
||||
cur = max + startTime/1000 - GetTime()
|
||||
end
|
||||
|
||||
cur = cur > max and max or cur
|
||||
cur = cur < 0 and 0 or cur
|
||||
|
||||
this.bar:SetValue(cur)
|
||||
|
||||
if this.showtimer then
|
||||
if this.delay and this.delay > 0 then
|
||||
local delay = "|cffffaaaa" .. (channel and "-" or "+") .. FormatCastbarTime(this.delay) .. " |r "
|
||||
this.bar.right:SetText(delay .. FormatCastbarTime(cur) .. " / " .. FormatCastbarTime(max))
|
||||
else
|
||||
this.bar.right:SetText(FormatCastbarTime(cur) .. " / " .. FormatCastbarTime(max))
|
||||
end
|
||||
end
|
||||
|
||||
this.fadeout = nil
|
||||
else
|
||||
this.bar:SetMinMaxValues(1,100)
|
||||
this.bar:SetValue(100)
|
||||
this.lastMax = nil
|
||||
this.fadeout = 1
|
||||
this.delay = 0
|
||||
this.itemIconApplied = nil
|
||||
end
|
||||
end)
|
||||
|
||||
-- register for spell delay
|
||||
-- Prefer Nampower's SPELL_DELAYED_SELF (gives casterGuid + delayMs directly).
|
||||
-- Fall back to vanilla SPELLCAST_DELAYED if Nampower is not available.
|
||||
local playerarg = nil
|
||||
local function ApplyPushback(delayMs)
|
||||
if not delayMs or delayMs <= 0 or not this.endTime then return end
|
||||
this.delay = (this.delay or 0) + delayMs / 1000
|
||||
this.endTime = this.endTime + delayMs
|
||||
local focusGuid = this.focusGuid
|
||||
if focusGuid and pfUI.libdebuff_casts and pfUI.libdebuff_casts[focusGuid] then
|
||||
pfUI.libdebuff_casts[focusGuid].endTime = this.endTime / 1000
|
||||
-- Cast lifecycle events. Player bars react to vanilla SPELLCAST_*; non-
|
||||
-- player bars also react to Nampower SPELL_*_OTHER (gated by the
|
||||
-- NP_EnableSpell{Start,Go}Events CVars, enabled by libdebuff) plus the
|
||||
-- retarget event. Player events also feed non-player bars for the
|
||||
-- target=self case.
|
||||
cb:RegisterEvent("SPELLCAST_START")
|
||||
cb:RegisterEvent("SPELLCAST_STOP")
|
||||
cb:RegisterEvent("SPELLCAST_FAILED")
|
||||
cb:RegisterEvent("SPELLCAST_INTERRUPTED")
|
||||
cb:RegisterEvent("SPELLCAST_CHANNEL_START")
|
||||
cb:RegisterEvent("SPELLCAST_CHANNEL_STOP")
|
||||
cb:RegisterEvent("SPELLCAST_CHANNEL_UPDATE")
|
||||
cb:RegisterEvent("SPELL_DELAYED_SELF")
|
||||
-- Chained same-spell recasts never run the client cast path (the 1.12
|
||||
-- engine short-circuits at spellID == current-cast), so vanilla
|
||||
-- SPELLCAST_START never fires for them. nampower's SPELL_START_SELF
|
||||
-- (server-driven) is the only signal that shows them.
|
||||
cb:RegisterEvent("SPELL_START_SELF")
|
||||
if unitstr == "player" then
|
||||
cb:RegisterEvent("SPELL_GO_SELF")
|
||||
end
|
||||
if unitstr ~= "player" and unitstr ~= "" then
|
||||
cb:RegisterEvent("SPELL_START_OTHER")
|
||||
cb:RegisterEvent("SPELL_FAILED_OTHER")
|
||||
if unitstr == "target" then
|
||||
cb:RegisterEvent("PLAYER_TARGET_CHANGED")
|
||||
elseif unitstr == "focus" then
|
||||
cb:RegisterEvent("PLAYER_FOCUS_CHANGED")
|
||||
end
|
||||
end
|
||||
|
||||
cb:RegisterEvent("SPELL_DELAYED_SELF")
|
||||
cb:RegisterEvent(CASTBAR_EVENT_CAST_DELAY)
|
||||
cb:RegisterEvent(CASTBAR_EVENT_CHANNEL_DELAY)
|
||||
cb:RegisterEvent(CASTBAR_EVENT_CAST_START)
|
||||
cb:RegisterEvent(CASTBAR_EVENT_CHANNEL_START)
|
||||
cb:SetScript("OnEvent", function()
|
||||
if this.unitstr and not UnitIsUnit(this.unitstr, "player") then return end
|
||||
local unit = this.unitstr
|
||||
|
||||
if event == "SPELL_DELAYED_SELF" then
|
||||
-- arg1=casterGuid, arg2=delayMs (Nampower, most accurate)
|
||||
ApplyPushback(arg2)
|
||||
|
||||
elseif event == CASTBAR_EVENT_CAST_DELAY then
|
||||
-- SPELLCAST_DELAYED fallback intentionally removed - addon requires Nampower.
|
||||
-- Cast pushback is handled by SPELL_DELAYED_SELF above.
|
||||
if event == "PLAYER_TARGET_CHANGED" or event == "PLAYER_FOCUS_CHANGED" then
|
||||
RefreshBar(this)
|
||||
return
|
||||
end
|
||||
|
||||
elseif event == CASTBAR_EVENT_CHANNEL_DELAY then
|
||||
-- SPELLCAST_CHANNEL_UPDATE fires when a channel is pushed back by damage.
|
||||
-- arg1 = new remaining time in ms. Channel ends sooner = newEndTime < this.endTime.
|
||||
if not this.endTime or not arg1 then return end
|
||||
local newEndTime = GetTime() * 1000 + arg1
|
||||
local diff = this.endTime - newEndTime -- positive = time lost to pushback
|
||||
if diff > 50 then
|
||||
this.delay = (this.delay or 0) + diff / 1000
|
||||
this.endTime = newEndTime
|
||||
local focusGuid = this.focusGuid
|
||||
if focusGuid and pfUI.libdebuff_casts and pfUI.libdebuff_casts[focusGuid] then
|
||||
pfUI.libdebuff_casts[focusGuid].endTime = newEndTime / 1000
|
||||
if event == "SPELL_START_OTHER" then
|
||||
-- arg3=casterGuid. Defer one frame so ClassicAPI's UnitChannelInfo
|
||||
-- can see the engine's +0x228 broadcast for remote-unit channels
|
||||
-- (the cohook+packet handler runs in the same frame; the broadcast
|
||||
-- propagates after).
|
||||
if arg3 == UnitGUID(unit) then
|
||||
local target = this
|
||||
RunNextFrame(function() RefreshBar(target) end)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if event == "SPELL_FAILED_OTHER" then
|
||||
if arg1 == UnitGUID(unit) then ClearBar(this) end
|
||||
return
|
||||
end
|
||||
|
||||
-- Vanilla SPELLCAST_* + SPELL_DELAYED_SELF fire only for the local
|
||||
-- player. Non-player bars handle them only when their unit currently
|
||||
-- resolves to the player (target=self / focus=self).
|
||||
if not UnitIsUnit(unit, 'player') then return end
|
||||
|
||||
if event == "SPELLCAST_START" or event == "SPELLCAST_CHANNEL_START" then
|
||||
if this.tradeskillTotal then
|
||||
-- Mid-chain craft N+1 of N. Keep the merged bar; resync the spark
|
||||
-- to the new craft's start and refresh the "(N)" count label.
|
||||
StartTradeskillCraft(this)
|
||||
else
|
||||
RefreshBar(this)
|
||||
if this.isTradeskill and (this.pendingTradeskillCount or 0) > 1
|
||||
and C.castbar.player.mergetradeskill == "1" then
|
||||
EnterTradeskillMerge(this, this.startTime, this.endTime, this.pendingTradeskillCount)
|
||||
end
|
||||
this.pendingTradeskillCount = nil
|
||||
end
|
||||
elseif event == "SPELL_START_SELF" then
|
||||
-- Catches chained same-spell recasts (no SPELLCAST_START fires) —
|
||||
-- including tradeskill chaining where craft 2..N reuse one spell.
|
||||
-- Defer one frame so ClassicAPI's SMSG_SPELL_START co-hook has
|
||||
-- stamped g_cast before we poll, regardless of co-hook order.
|
||||
if this.tradeskillTotal then
|
||||
StartTradeskillCraft(this)
|
||||
else
|
||||
local target = this
|
||||
RunNextFrame(function()
|
||||
-- Re-check: SPELL_START_SELF (nampower co-hook) fires before vanilla
|
||||
-- SPELLCAST_START on the same packet, so SPELLCAST_START may have
|
||||
-- entered merge in this same frame. Don't restamp over it.
|
||||
if target.tradeskillTotal then
|
||||
StartTradeskillCraft(target)
|
||||
return
|
||||
end
|
||||
RefreshBar(target)
|
||||
if target.unitstr == "player" and target.isTradeskill
|
||||
and (target.pendingTradeskillCount or 0) > 1
|
||||
and C.castbar.player.mergetradeskill == "1" then
|
||||
EnterTradeskillMerge(target, target.startTime, target.endTime, target.pendingTradeskillCount)
|
||||
end
|
||||
target.pendingTradeskillCount = nil
|
||||
end)
|
||||
end
|
||||
elseif event == "SPELLCAST_CHANNEL_STOP" then
|
||||
-- A channel's stop can arrive after a following cast already claimed
|
||||
-- the bar (channel->cast transition); only clear if a channel is
|
||||
-- actually being shown, so it doesn't wipe an active cast bar.
|
||||
if this.isChannel then ClearBar(this) end
|
||||
elseif event == "SPELLCAST_STOP" then
|
||||
-- During a tradeskill chain, SPELL_GO_SELF already counted this craft
|
||||
-- and either cleared the bar (chain done) or kept it running. Only a
|
||||
-- non-merge cast clears here.
|
||||
if not this.tradeskillTotal then ClearBar(this) end
|
||||
elseif event == "SPELLCAST_FAILED" or event == "SPELLCAST_INTERRUPTED" then
|
||||
ClearBar(this)
|
||||
elseif event == "SPELL_GO_SELF" then
|
||||
-- arg2 = spellId. In a tradeskill merge, count each successful craft
|
||||
-- and clear when the chain is done.
|
||||
if this.tradeskillTotal and arg2 == this.tradeskillSpellID then
|
||||
this.tradeskillCompleted = (this.tradeskillCompleted or 0) + 1
|
||||
if this.tradeskillCompleted >= this.tradeskillTotal then
|
||||
ClearBar(this)
|
||||
else
|
||||
UpdateTradeskillLabel(this)
|
||||
end
|
||||
end
|
||||
|
||||
elseif event == CASTBAR_EVENT_CAST_START or event == CASTBAR_EVENT_CHANNEL_START then
|
||||
playerarg = true
|
||||
this.delay = 0
|
||||
elseif event == "SPELL_DELAYED_SELF" then
|
||||
-- Cast pushback. nampower's event carries the delay (arg2); apply it
|
||||
-- locally rather than re-polling, so the bar doesn't depend on
|
||||
-- ClassicAPI's SMSG_SPELL_DELAYED co-hook having bumped g_cast before
|
||||
-- this fires (co-hook order vs nampower is not guaranteed).
|
||||
if not this.endTime or not arg2 then return end
|
||||
local delayMs = tonumber(arg2) or 0
|
||||
if delayMs > 0 then
|
||||
this.delay = (this.delay or 0) + delayMs / 1000
|
||||
this.endTime = this.endTime + delayMs
|
||||
local newDuration = (this.endTime - this.startTime) / 1000
|
||||
this.bar:SetMinMaxValues(0, newDuration)
|
||||
this.lastMax = newDuration
|
||||
end
|
||||
elseif event == "SPELLCAST_CHANNEL_UPDATE" then
|
||||
-- Channel pushback. ClassicAPI doesn't track channel delay in
|
||||
-- g_channel, so we adjust endTime + delay locally and resize the
|
||||
-- bar so OnUpdate animates against the new total.
|
||||
if not this.endTime or not arg1 then return end
|
||||
local newEndMs = GetTime() * 1000 + arg1
|
||||
local diff = this.endTime - newEndMs
|
||||
if diff > 50 then
|
||||
this.delay = (this.delay or 0) + diff / 1000
|
||||
this.endTime = newEndMs
|
||||
local newDuration = (this.endTime - this.startTime) / 1000
|
||||
this.bar:SetMinMaxValues(0, newDuration)
|
||||
this.lastMax = newDuration
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -350,6 +484,17 @@ pfUI:RegisterModule("castbar", function ()
|
||||
end
|
||||
|
||||
UpdateMovable(pfUI.castbar.player)
|
||||
|
||||
-- Tradeskill merge: hook DoTradeSkill so the player castbar knows the
|
||||
-- requested count before the first SPELLCAST_START fires. Always-on hook
|
||||
-- (the config knob is read at event time so toggling takes effect on the
|
||||
-- next craft without a /reload). DoTradeSkill is synchronous; the server
|
||||
-- roundtrip to SPELLCAST_START gives us plenty of time after this hook.
|
||||
pfUI.hooksecurefunc("DoTradeSkill", function(index, num)
|
||||
if pfUI.castbar.player then
|
||||
pfUI.castbar.player.pendingTradeskillCount = tonumber(num) or 1
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- [[ pfTargetCastbar ]] --
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ pfUI:RegisterModule("chat", function ()
|
||||
end
|
||||
end
|
||||
|
||||
hooksecurefunc("UnitPopup_OnClick", function(self)
|
||||
pfUI.hooksecurefunc("UnitPopup_OnClick", function(self)
|
||||
if this.value == "IGNORE_PLAYER" then
|
||||
AddIgnore(_G[UIDROPDOWNMENU_INIT_MENU].name)
|
||||
end
|
||||
@@ -449,7 +449,7 @@ pfUI:RegisterModule("chat", function ()
|
||||
end
|
||||
end
|
||||
|
||||
hooksecurefunc("FCF_SaveDock", pfUI.chat.RefreshChat)
|
||||
pfUI.hooksecurefunc("FCF_SaveDock", pfUI.chat.RefreshChat)
|
||||
|
||||
if C.chat.global.tabmouse == "1" then
|
||||
pfUI.chat.mouseovertab = CreateFrame("Frame")
|
||||
|
||||
@@ -136,5 +136,5 @@ pfUI:RegisterModule("cooldown", function ()
|
||||
|
||||
-- vanilla does not have a cooldown frame type, so we hook the
|
||||
-- regular SetTimer function that each one is calling.
|
||||
hooksecurefunc("CooldownFrame_SetTimer", SetCooldown)
|
||||
pfUI.hooksecurefunc("CooldownFrame_SetTimer", SetCooldown)
|
||||
end)
|
||||
@@ -8,9 +8,9 @@ pfUI:RegisterModule("easteregg", function ()
|
||||
local pvpking = CreateFrame("Frame", "pfPvPKing", UIParent)
|
||||
pvpking:Hide()
|
||||
|
||||
pvpking:RegisterEvent("CHAT_MSG_SYSTEM")
|
||||
pvpking:RegisterEvent("PLAYER_FLAGS_CHANGED")
|
||||
pvpking:SetScript("OnEvent", function()
|
||||
if strfind(arg1, "You are now", 1) and strfind(arg1, "(AFK)", 1) then
|
||||
if UnitIsAFK('player') then
|
||||
_G.CHAT_FLAG_AFK = title .. " "
|
||||
this.time = GetTime()
|
||||
this:Show()
|
||||
@@ -61,14 +61,13 @@ pfUI:RegisterModule("easteregg", function ()
|
||||
end)
|
||||
|
||||
-- trigger fireworks when being AFK
|
||||
fireworks:RegisterEvent("CHAT_MSG_SYSTEM")
|
||||
fireworks:RegisterEvent("PLAYER_FLAGS_CHANGED")
|
||||
fireworks:SetScript("OnEvent", function()
|
||||
if strfind(arg1, _G.MARKED_AFK) or strfind(arg1, _G.MARKED_AFK_MESSAGE) then
|
||||
local isAFK = UnitIsAFK('player')
|
||||
if isAFK then
|
||||
this:SetAlpha(0)
|
||||
this:Show()
|
||||
elseif strfind(arg1, _G.CLEARED_AFK) then
|
||||
this:Hide()
|
||||
end
|
||||
this:SetShown(isAFK)
|
||||
end)
|
||||
|
||||
-- basic explosion animation
|
||||
|
||||
+126
-219
@@ -1,243 +1,150 @@
|
||||
pfUI:RegisterModule("eqcompare", function ()
|
||||
local sides = { "Left", "Right" }
|
||||
local loc = pfUI.cache["locale"]
|
||||
for key, value in pairs(L["itemtypes"]) do setglobal(key, value) end
|
||||
INVTYPE_WEAPON_OTHER = INVTYPE_WEAPON.."_other"
|
||||
INVTYPE_FINGER_OTHER = INVTYPE_FINGER.."_other"
|
||||
INVTYPE_TRINKET_OTHER = INVTYPE_TRINKET.."_other"
|
||||
pfUI.eqcompare = {}
|
||||
|
||||
local function AddHeader(tooltip)
|
||||
local name = tooltip:GetName()
|
||||
local function ShowCompareItem(self, link, shift)
|
||||
self = self or GameTooltip
|
||||
shift = shift or IsShiftKeyDown()
|
||||
|
||||
-- shift all entries one line down
|
||||
for i=tooltip:NumLines(), 1, -1 do
|
||||
for _, side in pairs(sides) do
|
||||
local current = _G[name.."Text"..side..i]
|
||||
local below = _G[name.."Text"..side..i+1]
|
||||
if not link or (not shift and (C.tooltip.compare.showalways ~= "1" or C_Item.IsEquippedItem(link))) then
|
||||
return
|
||||
end
|
||||
|
||||
if current and current:IsShown() then
|
||||
local text = current:GetText()
|
||||
local r, g, b = current:GetTextColor()
|
||||
local shoppingTooltip1, shoppingTooltip2 = unpack(self.shoppingTooltips or { ShoppingTooltip1, ShoppingTooltip2 });
|
||||
|
||||
if text and text ~= "" then
|
||||
if tooltip:NumLines() < i+1 then
|
||||
-- add new line if required
|
||||
tooltip:AddLine(text, r, g, b, true)
|
||||
else
|
||||
-- update existing lines
|
||||
below:SetText(text)
|
||||
below:SetTextColor(r, g, b)
|
||||
below:Show()
|
||||
local SEPARATION = 6;
|
||||
local backdrop = shoppingTooltip1.GetBackdrop and shoppingTooltip1:GetBackdrop();
|
||||
local GAP = SEPARATION + ((type(backdrop) == "table" and backdrop.edgeSize) or 0);
|
||||
|
||||
-- hide processed line
|
||||
current:Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
local item1 = nil;
|
||||
local item2 = nil;
|
||||
local side = "left";
|
||||
if ( shoppingTooltip1:SetHyperlinkCompareItem(link, 1, shift, self) ) then
|
||||
item1 = true;
|
||||
end
|
||||
if ( shoppingTooltip2:SetHyperlinkCompareItem(link, 2, shift, self) ) then
|
||||
item2 = true;
|
||||
end
|
||||
|
||||
-- find correct side
|
||||
local rightDist = 0;
|
||||
local leftPos = self:GetLeft();
|
||||
local rightPos = self:GetRight();
|
||||
if ( not rightPos ) then
|
||||
rightPos = 0;
|
||||
end
|
||||
if ( not leftPos ) then
|
||||
leftPos = 0;
|
||||
end
|
||||
|
||||
rightDist = GetScreenWidth() - rightPos;
|
||||
|
||||
if (leftPos and (rightDist < leftPos)) then
|
||||
side = "left";
|
||||
else
|
||||
side = "right";
|
||||
end
|
||||
|
||||
-- see if we should slide the tooltip
|
||||
if ( self:GetAnchorType() and self:GetAnchorType() ~= "ANCHOR_PRESERVE" ) then
|
||||
local totalWidth = 0;
|
||||
if ( item1 ) then
|
||||
totalWidth = totalWidth + shoppingTooltip1:GetWidth();
|
||||
end
|
||||
if ( item2 ) then
|
||||
totalWidth = totalWidth + shoppingTooltip2:GetWidth();
|
||||
end
|
||||
|
||||
if ( (side == "left") and (totalWidth > leftPos) ) then
|
||||
self:SetAnchorType(self:GetAnchorType(), (totalWidth - leftPos), 0);
|
||||
elseif ( (side == "right") and (rightPos + totalWidth) > GetScreenWidth() ) then
|
||||
self:SetAnchorType(self:GetAnchorType(), -((rightPos + totalWidth) - GetScreenWidth()), 0);
|
||||
end
|
||||
end
|
||||
|
||||
-- add label to first line
|
||||
_G[name.."TextLeft1"]:SetTextColor(.5, .5, .5, 1)
|
||||
_G[name.."TextLeft1"]:SetText(CURRENTLY_EQUIPPED)
|
||||
_G[name.."TextLeft1"]:Show()
|
||||
if ( item1 ) then
|
||||
shoppingTooltip1:SetOwner(self, "ANCHOR_NONE");
|
||||
shoppingTooltip1:ClearAllPoints();
|
||||
if ( side and side == "left" ) then
|
||||
shoppingTooltip1:SetPoint("TOPRIGHT", self, "TOPLEFT", -GAP, -10);
|
||||
else
|
||||
shoppingTooltip1:SetPoint("TOPLEFT", self, "TOPRIGHT", GAP, -10);
|
||||
end
|
||||
shoppingTooltip1:SetHyperlinkCompareItem(link, 1, shift, self);
|
||||
shoppingTooltip1:Show();
|
||||
|
||||
-- update tooltip sizes
|
||||
tooltip:Show()
|
||||
if ( item2 ) then
|
||||
shoppingTooltip2:SetOwner(shoppingTooltip1, "ANCHOR_NONE");
|
||||
shoppingTooltip2:ClearAllPoints();
|
||||
if ( side and side == "left" ) then
|
||||
shoppingTooltip2:SetPoint("TOPRIGHT", shoppingTooltip1, "TOPLEFT", -GAP, 0);
|
||||
else
|
||||
shoppingTooltip2:SetPoint("TOPLEFT", shoppingTooltip1, "TOPRIGHT", GAP, 0);
|
||||
end
|
||||
shoppingTooltip2:SetHyperlinkCompareItem(link, 2, shift, self);
|
||||
shoppingTooltip2:Show();
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local slotTable = {
|
||||
[INVTYPE_2HWEAPON] = "MainHandSlot",
|
||||
[INVTYPE_BODY] = "ShirtSlot",
|
||||
[INVTYPE_CHEST] = "ChestSlot",
|
||||
[INVTYPE_CLOAK] = "BackSlot",
|
||||
[INVTYPE_FEET] = "FeetSlot",
|
||||
[INVTYPE_FINGER] = "Finger0Slot",
|
||||
[INVTYPE_FINGER_OTHER] = "Finger1Slot",
|
||||
[INVTYPE_HAND] = "HandsSlot",
|
||||
[INVTYPE_HEAD] = "HeadSlot",
|
||||
[INVTYPE_HOLDABLE] = "SecondaryHandSlot",
|
||||
[INVTYPE_LEGS] = "LegsSlot",
|
||||
[INVTYPE_NECK] = "NeckSlot",
|
||||
[INVTYPE_RANGED] = "RangedSlot",
|
||||
[INVTYPE_RELIC] = "RangedSlot",
|
||||
[INVTYPE_ROBE] = "ChestSlot",
|
||||
[INVTYPE_SHIELD] = "SecondaryHandSlot",
|
||||
[INVTYPE_SHOULDER] = "ShoulderSlot",
|
||||
[INVTYPE_TABARD] = "TabardSlot",
|
||||
[INVTYPE_TRINKET] = "Trinket0Slot",
|
||||
[INVTYPE_TRINKET_OTHER] = "Trinket1Slot",
|
||||
[INVTYPE_WAIST] = "WaistSlot",
|
||||
[INVTYPE_WEAPON] = "MainHandSlot",
|
||||
[INVTYPE_WEAPON_OTHER] = "SecondaryHandSlot",
|
||||
[INVTYPE_WEAPONMAINHAND] = "MainHandSlot",
|
||||
[INVTYPE_WEAPONOFFHAND] = "SecondaryHandSlot",
|
||||
[INVTYPE_WRIST] = "WristSlot",
|
||||
local prevMerchant = ShoppingTooltip1.SetMerchantCompareItem
|
||||
local function SetMerchantCompareItem(self, index, compareItem)
|
||||
if C.tooltip.compare.basestats == "1" and compareItem == 1 then
|
||||
ShowCompareItem(nil, GetMerchantItemLink(index), 1)
|
||||
return false
|
||||
end
|
||||
return prevMerchant and prevMerchant(self, index, compareItem)
|
||||
end
|
||||
|
||||
[INVTYPE_WAND] = "RangedSlot",
|
||||
[INVTYPE_GUN] = "RangedSlot",
|
||||
[INVTYPE_PROJECTILE] = "AmmoSlot",
|
||||
[INVTYPE_CROSSBOW] = "RangedSlot",
|
||||
[INVTYPE_THROWN] = "RangedSlot",
|
||||
local prevAuction = ShoppingTooltip1.SetAuctionCompareItem
|
||||
local function SetAuctionCompareItem(self, type, index, compareItem)
|
||||
if C.tooltip.compare.basestats == "1" and compareItem == 1 then
|
||||
ShowCompareItem(nil, GetAuctionItemLink(type, index), 1)
|
||||
return false
|
||||
end
|
||||
return prevAuction and prevAuction(self, type, index, compareItem)
|
||||
end
|
||||
|
||||
ShoppingTooltip1.SetMerchantCompareItem = SetMerchantCompareItem
|
||||
ShoppingTooltip2.SetMerchantCompareItem = SetMerchantCompareItem
|
||||
ShoppingTooltip1.SetAuctionCompareItem = SetAuctionCompareItem
|
||||
ShoppingTooltip2.SetAuctionCompareItem = SetAuctionCompareItem
|
||||
|
||||
local TooltipHooks = {
|
||||
SetLootRollItem = GetLootRollItemLink,
|
||||
SetLootItem = GetLootSlotLink,
|
||||
SetQuestLogItem = GetQuestLogItemLink,
|
||||
SetQuestItem = GetQuestItemLink,
|
||||
SetHyperlink = function(link) return link end,
|
||||
SetBagItem = GetContainerItemLink,
|
||||
SetInboxItem = GetInboxItemLink,
|
||||
SetInventoryItem = GetInventoryItemLink,
|
||||
SetTradeSkillItem = function(skillIndex, reagentIndex)
|
||||
if reagentIndex then
|
||||
return GetTradeSkillReagentItemLink(skillIndex, reagentIndex)
|
||||
else
|
||||
return GetTradeSkillItemLink(skillIndex)
|
||||
end
|
||||
end,
|
||||
SetAuctionSellItem = GetAuctionSellItemLink,
|
||||
SetTradePlayerItem = GetTradePlayerItemLink,
|
||||
SetTradeTargetItem = GetTradeTargetItemLink
|
||||
}
|
||||
|
||||
local function startsWith(str, start)
|
||||
return string.sub(str, 1, string.len(start)) == start
|
||||
end
|
||||
|
||||
local function ExtractAttributes(tooltip)
|
||||
local name = tooltip:GetName()
|
||||
|
||||
-- get the name/header of the last set comparison tooltip
|
||||
local comparetooltip = pfUI.eqcompare.tooltip:GetName()
|
||||
local iname = _G[comparetooltip .. "TextLeft1"] and _G[comparetooltip .. "TextLeft1"]:GetText()
|
||||
|
||||
-- only run once per item
|
||||
if tooltip.pfCompLastName == iname then return end
|
||||
|
||||
tooltip.pfCompData = {}
|
||||
tooltip.pfCompLastName = iname
|
||||
|
||||
for i=1,30 do
|
||||
local widget = _G[name.."TextLeft"..i]
|
||||
if widget and widget:GetObjectType() == "FontString" then
|
||||
local text = widget:GetText()
|
||||
if text and not string.find(text, "-", 1, true) then
|
||||
local start = 1
|
||||
if startsWith(text, "\+") or startsWith(text, "\(") then start = 2 end
|
||||
|
||||
local space = string.find(text, " ", 1, true)
|
||||
if space then
|
||||
local value = tonumber(string.sub(text, start, space-1))
|
||||
if value and text then
|
||||
-- we've found an attr
|
||||
local attr = string.sub(text, space, string.len(text))
|
||||
tooltip.pfCompData[attr] = { value = tonumber(value), widget = widget }
|
||||
end
|
||||
end
|
||||
end
|
||||
local function makeHook(getter)
|
||||
return function(tooltip, arg1, arg2, arg3)
|
||||
if C.tooltip.compare.basestats == "1" then
|
||||
ShowCompareItem(tooltip, getter(arg1, arg2, arg3))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function CompareAttributes(data, targetData)
|
||||
if not data then return end
|
||||
|
||||
for attr,v in pairs(data) do
|
||||
if targetData then
|
||||
local target = targetData[attr]
|
||||
if target then
|
||||
if v.value ~= target.value and v.widget:GetText() then
|
||||
if v.value > target.value then
|
||||
if not strfind(v.widget:GetText(), "|cff88ff88") and not strfind(v.widget:GetText(), "|cffff8888") then
|
||||
v.widget:SetText(v.widget:GetText() .. "|cff88ff88 (+" .. round(v.value - target.value, 1) .. ")")
|
||||
end
|
||||
elseif not v.widget.compSet then
|
||||
if not strfind(v.widget:GetText(), "|cff88ff88") and not strfind(v.widget:GetText(), "|cffff8888") then
|
||||
v.widget:SetText(v.widget:GetText() .. "|cffff8888 (-" .. round(target.value - v.value, 1) .. ")")
|
||||
end
|
||||
end
|
||||
target.processed = true
|
||||
else
|
||||
target.processed = true
|
||||
end
|
||||
else
|
||||
-- this attribute doesnt exist in target
|
||||
if v.widget and v.widget:GetText() then
|
||||
if not strfind(v.widget:GetText(), "|cff88ff88") and not strfind(v.widget:GetText(), "|cffff8888") then
|
||||
v.widget:SetText(v.widget:GetText() .. "|cff88ff88 (+" .. v.value .. ")")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _,target in pairs(targetData) do
|
||||
if target and not target.processed then
|
||||
-- we are an extra value
|
||||
local text = target.widget:GetText()
|
||||
if text and not strfind(text, "|cff88ff88") and not strfind(text, "|cffff8888") then
|
||||
target.widget:SetText(text .. "|cff88ff88 (+" .. target.value .. ")")
|
||||
end
|
||||
end
|
||||
local function HookTooltip(tooltip)
|
||||
for setter, getter in pairs(TooltipHooks) do
|
||||
pfUI.hooksecurefunc(tooltip, setter, makeHook(getter))
|
||||
end
|
||||
end
|
||||
|
||||
pfUI.eqcompare = {}
|
||||
pfUI.eqcompare.GameTooltipShow = function()
|
||||
-- use this tooltip for the next comparison
|
||||
pfUI.eqcompare.tooltip = this
|
||||
HookTooltip(GameTooltip)
|
||||
|
||||
if not IsShiftKeyDown() and C.tooltip.compare.showalways ~= "1" then return end
|
||||
local rawborder, border = GetBorderSize()
|
||||
|
||||
for i=1,this:NumLines() do
|
||||
local tmpText = _G[this:GetName() .. "TextLeft"..i]
|
||||
for slotType, slotName in pairs(slotTable) do
|
||||
if tmpText:GetText() == slotType then
|
||||
local slotID = GetInventorySlotInfo(slotTable[slotType])
|
||||
|
||||
-- determine screen part
|
||||
local ltrigger = GetScreenWidth() / 2
|
||||
local x = GetCursorPosition()
|
||||
x = x / UIParent:GetEffectiveScale()
|
||||
if x > ltrigger then ltrigger = nil end
|
||||
|
||||
-- first tooltip
|
||||
ShoppingTooltip1:SetOwner(this, "ANCHOR_NONE")
|
||||
ShoppingTooltip1:ClearAllPoints()
|
||||
|
||||
if ltrigger then
|
||||
ShoppingTooltip1:SetPoint("BOTTOMLEFT", this, "BOTTOMRIGHT", 0, 0)
|
||||
else
|
||||
ShoppingTooltip1:SetPoint("BOTTOMRIGHT", this, "BOTTOMLEFT", -border*2-1, 0)
|
||||
end
|
||||
|
||||
ShoppingTooltip1:SetInventoryItem("player", slotID)
|
||||
ShoppingTooltip1:Show()
|
||||
AddHeader(ShoppingTooltip1)
|
||||
|
||||
-- second tooltip
|
||||
if slotTable[slotType .. "_other"] then
|
||||
local slotID_other = GetInventorySlotInfo(slotTable[slotType .. "_other"])
|
||||
|
||||
ShoppingTooltip2:SetOwner(this, "ANCHOR_NONE")
|
||||
ShoppingTooltip2:ClearAllPoints()
|
||||
|
||||
if ltrigger then
|
||||
ShoppingTooltip2:SetPoint("BOTTOMLEFT", ShoppingTooltip1, "BOTTOMRIGHT", 0, 0)
|
||||
else
|
||||
ShoppingTooltip2:SetPoint("BOTTOMRIGHT", ShoppingTooltip1, "BOTTOMLEFT", -border*2-1, 0)
|
||||
end
|
||||
|
||||
ShoppingTooltip2:SetInventoryItem("player", slotID_other)
|
||||
ShoppingTooltip2:Show()
|
||||
AddHeader(ShoppingTooltip2)
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- add HookScript method if not already existing
|
||||
GameTooltip.HookScript = GameTooltip.HookScript or HookScript
|
||||
ShoppingTooltip1.HookScript = ShoppingTooltip1.HookScript or HookScript
|
||||
ShoppingTooltip2.HookScript = ShoppingTooltip2.HookScript or HookScript
|
||||
|
||||
pfUI.eqcompare.ShoppingTooltipShow = function()
|
||||
-- abort if no comparison tooltip has been set
|
||||
if not pfUI.eqcompare.tooltip then return end
|
||||
|
||||
ExtractAttributes(this)
|
||||
ExtractAttributes(pfUI.eqcompare.tooltip)
|
||||
CompareAttributes(pfUI.eqcompare.tooltip.pfCompData, this.pfCompData)
|
||||
end
|
||||
|
||||
-- Add Gametooltip Hooks
|
||||
GameTooltip:HookScript("OnShow", pfUI.eqcompare.GameTooltipShow)
|
||||
if C.tooltip.compare.basestats == "1" then
|
||||
ShoppingTooltip1:HookScript("OnShow", pfUI.eqcompare.ShoppingTooltipShow)
|
||||
ShoppingTooltip2:HookScript("OnShow", pfUI.eqcompare.ShoppingTooltipShow)
|
||||
end
|
||||
pfUI.eqcompare.HookTooltip = HookTooltip
|
||||
end)
|
||||
|
||||
@@ -940,10 +940,10 @@ pfUI:RegisterModule("equipmentmanager", function()
|
||||
|
||||
-- Tie popout visibility to the EM sidecar. They appear when the
|
||||
-- sidecar opens, hide when it closes, and the flyout closes too.
|
||||
HookScript(frame, "OnShow", function()
|
||||
frame:HookScript("OnShow", function()
|
||||
for _, b in ipairs(popoutButtons) do b:Show() end
|
||||
end)
|
||||
HookScript(frame, "OnHide", function()
|
||||
frame:HookScript("OnHide", function()
|
||||
for _, b in ipairs(popoutButtons) do b:Hide() end
|
||||
if flyout then flyout:Hide() end
|
||||
end)
|
||||
@@ -1048,11 +1048,10 @@ pfUI:RegisterModule("equipmentmanager", function()
|
||||
events:RegisterEvent("EQUIPMENT_SETS_CHANGED")
|
||||
events:RegisterEvent("EQUIPMENT_SWAP_FINISHED")
|
||||
events:RegisterEvent("EQUIPMENT_SWAP_PENDING")
|
||||
events:RegisterEvent("BAG_UPDATE_DELAYED") -- ClassicAPI debounced; ~4x fewer refreshes than BAG_UPDATE
|
||||
events:RegisterEvent("UNIT_INVENTORY_CHANGED")
|
||||
events:RegisterEvent("BAG_UPDATE_DELAYED")
|
||||
events:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
|
||||
events:SetScript("OnEvent", function()
|
||||
if not frame:IsShown() then return end
|
||||
if event == "UNIT_INVENTORY_CHANGED" and arg1 ~= "player" then return end
|
||||
pfUI.equipmentmanager.Refresh()
|
||||
end)
|
||||
|
||||
|
||||
+5
-24
@@ -1,29 +1,10 @@
|
||||
pfUI:RegisterModule("feigndeath", function ()
|
||||
local cache = { }
|
||||
local scanner = libtipscan:GetScanner("feigndeath")
|
||||
local healthbar = scanner:GetChildren()
|
||||
|
||||
local cache_update = CreateFrame("Frame")
|
||||
cache_update:RegisterEvent("UNIT_HEALTH")
|
||||
cache_update:RegisterEvent("PLAYER_TARGET_CHANGED")
|
||||
cache_update:SetScript("OnEvent", function()
|
||||
if event == "PLAYER_TARGET_CHANGED" and UnitIsDead("target") then
|
||||
scanner:SetUnit("target")
|
||||
cache[UnitName("target")] = healthbar:GetValue()
|
||||
elseif event == "UNIT_HEALTH" and UnitIsDead(arg1) and UnitName(arg1) then
|
||||
scanner:SetUnit(arg1)
|
||||
cache[UnitName(arg1)] = healthbar:GetValue()
|
||||
elseif event == "UNIT_HEALTH" and UnitName(arg1) then
|
||||
cache[UnitName(arg1)] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
local oldUnitHealth = UnitHealth
|
||||
function UnitHealth(arg)
|
||||
if UnitIsDead(arg) and cache[UnitName(arg)] then
|
||||
return cache[UnitName(arg)]
|
||||
else
|
||||
return oldUnitHealth(arg)
|
||||
function UnitHealth(unit)
|
||||
if UnitIsFeignDeath(unit) then
|
||||
local hp = GetUnitField(unit, "health")
|
||||
if hp and hp > 0 then return hp end
|
||||
end
|
||||
return oldUnitHealth(unit)
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -46,6 +46,11 @@ pfUI:RegisterModule("firstrun", function ()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if not self.completed then
|
||||
self.completed = true
|
||||
pfUI.events:TriggerEvent("firstrun:complete")
|
||||
end
|
||||
end
|
||||
|
||||
-- main function to create wizard windows
|
||||
|
||||
+1
-3
@@ -50,10 +50,8 @@ function SlashCmdList.PFFOCUSNAME(msg)
|
||||
FocusUnit("target")
|
||||
end
|
||||
|
||||
local restore = CreateFrame("Frame")
|
||||
restore:SetScript("OnUpdate", function()
|
||||
RunNextFrame(function()
|
||||
UIErrorsFrame:RegisterEvent("UI_ERROR_MESSAGE")
|
||||
restore:SetScript("OnUpdate", nil)
|
||||
end)
|
||||
|
||||
if prevGUID and prevGUID ~= "0x0000000000000000" then
|
||||
|
||||
+2
-2
@@ -150,7 +150,7 @@ pfUI:RegisterSkin("Friends", function ()
|
||||
end
|
||||
|
||||
-- set positions
|
||||
hooksecurefunc("WhoList_Update", function()
|
||||
pfUI.hooksecurefunc("WhoList_Update", function()
|
||||
for i = 1, WHOS_TO_DISPLAY do
|
||||
local level = _G["WhoFrameButton"..i.."Level"]
|
||||
level:ClearAllPoints()
|
||||
@@ -231,7 +231,7 @@ pfUI:RegisterSkin("Friends", function ()
|
||||
end
|
||||
|
||||
-- set positions
|
||||
hooksecurefunc("GuildStatus_Update", function()
|
||||
pfUI.hooksecurefunc("GuildStatus_Update", function()
|
||||
for i = 1, GUILDMEMBERS_TO_DISPLAY do
|
||||
local level = _G["GuildFrameButton"..i.."Level"]
|
||||
level:ClearAllPoints()
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ pfUI:RegisterModule("gm", function ()
|
||||
-- pet dropdown
|
||||
-- table.insert(UnitPopupMenus["PET"], "GM_HEADER")
|
||||
|
||||
hooksecurefunc("UnitPopup_OnClick", function()
|
||||
pfUI.hooksecurefunc("UnitPopup_OnClick", function()
|
||||
local dropdownFrame = _G[UIDROPDOWNMENU_INIT_MENU]
|
||||
local button = this.value
|
||||
local unit = dropdownFrame.unit
|
||||
|
||||
+55
-23
@@ -212,6 +212,7 @@ pfUI:RegisterModule("gui", function ()
|
||||
if not this:GetParent():IsShown() then
|
||||
category[config] = r .. "," .. g .. "," .. b .. "," .. a
|
||||
if ufunc then ufunc() else pfUI.gui.settingChanged = true end
|
||||
pfUI.events:TriggerEvent("config:changed", category, config)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -278,6 +279,7 @@ pfUI:RegisterModule("gui", function ()
|
||||
if this:GetText() ~= this:GetParent().category[this:GetParent().config] then
|
||||
this:GetParent().category[this:GetParent().config] = this:GetText()
|
||||
if ufunc then ufunc() else pfUI.gui.settingChanged = true end
|
||||
pfUI.events:TriggerEvent("config:changed", category, config)
|
||||
end
|
||||
this:SetTextColor(.2,1,.8,1)
|
||||
else
|
||||
@@ -328,6 +330,7 @@ pfUI:RegisterModule("gui", function ()
|
||||
end
|
||||
|
||||
if ufunc then ufunc() else pfUI.gui.settingChanged = true end
|
||||
pfUI.events:TriggerEvent("config:changed", category, config)
|
||||
end)
|
||||
|
||||
if category[config] == "1" then frame.input:SetChecked() end
|
||||
@@ -360,6 +363,7 @@ pfUI:RegisterModule("gui", function ()
|
||||
if category and category[config] ~= value then
|
||||
category[config] = value
|
||||
if ufunc then ufunc() else pfUI.gui.settingChanged = true end
|
||||
pfUI.events:TriggerEvent("config:changed", category, config)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -394,6 +398,7 @@ pfUI:RegisterModule("gui", function ()
|
||||
end
|
||||
category[config] = newconf
|
||||
if ufunc then ufunc() else pfUI.gui.settingChanged = true end
|
||||
pfUI.events:TriggerEvent("config:changed", category, config)
|
||||
frame.input:UpdateMenu()
|
||||
end)
|
||||
|
||||
@@ -409,6 +414,7 @@ pfUI:RegisterModule("gui", function ()
|
||||
CreateQuestionDialog(T["New entry:"], function()
|
||||
category[config] = category[config] .. "#" .. this:GetParent().input:GetText()
|
||||
if ufunc then ufunc() else pfUI.gui.settingChanged = true end
|
||||
pfUI.events:TriggerEvent("config:changed", category, config)
|
||||
frame.input:UpdateMenu()
|
||||
end, false, true)
|
||||
end)
|
||||
@@ -906,6 +912,11 @@ pfUI:RegisterModule("gui", function ()
|
||||
"1:" .. T["1 Decimal (2.1)"],
|
||||
"2:" .. T["2 Decimals (2.14)"],
|
||||
},
|
||||
["castbaralign"] = {
|
||||
"LEFT:" .. T["Left"],
|
||||
"CENTER:" .. T["Center"],
|
||||
"RIGHT:" .. T["Right"],
|
||||
},
|
||||
["orientation"] = {
|
||||
"HORIZONTAL:" .. T["Horizontal"],
|
||||
"VERTICAL:" .. T["Vertical"],
|
||||
@@ -2380,11 +2391,7 @@ pfUI:RegisterModule("gui", function ()
|
||||
|
||||
if c == "player" then
|
||||
CreateConfig(nil, T["Player SP/Haste Display"], nil, nil, "header")
|
||||
CreateConfig(nil, T["Haste Display"], C.unitframes[c], "display_haste", "dropdown", {
|
||||
"0:"..T["None"],
|
||||
"1:"..T["Haste (cast speed increase)"],
|
||||
"2:"..T["Effective Haste (Haste * cast time reduction)"], -- Only affects mages/warlocks I believe
|
||||
})
|
||||
CreateConfig(nil, T["Haste Display"], C.unitframes[c], "display_haste", "checkbox")
|
||||
CreateConfig(nil, T["Haste Display Color"], C.unitframes[c], "display_haste_color", "color")
|
||||
CreateConfig(nil, T["Display Spell Power"], C.unitframes[c], "display_spellpower", "checkbox")
|
||||
CreateConfig(nil, T["Use Custom Spell Power Color (unchecked = biggest school color)"], C.unitframes[c], "display_sp_color_override", "checkbox")
|
||||
@@ -2749,63 +2756,88 @@ pfUI:RegisterModule("gui", function ()
|
||||
CreateConfig(nil, T["Enable Extended Guild Information"], C.tooltip, "extguild", "checkbox")
|
||||
CreateConfig(nil, T["Always Show Health In Percent"], C.tooltip, "alwaysperc", "checkbox")
|
||||
CreateConfig(nil, T["Show Item IDs"], C.tooltip, "itemid", "checkbox")
|
||||
CreateConfig(nil, T["Show Movement Speed"], C.tooltip, "movespeed", "checkbox")
|
||||
CreateConfig(nil, T["Custom Transparency"], C.tooltip, "alpha")
|
||||
CreateConfig(nil, T["Status Bar Texture"], C.tooltip.statusbar, "texture", "dropdown", pfUI.gui.dropdowns.uf_bartexture)
|
||||
CreateConfig(nil, T["Compare Item Base Stats"], C.tooltip.compare, "basestats", "checkbox")
|
||||
CreateConfig(nil, T["Always Show Item Comparison"], C.tooltip.compare, "showalways", "checkbox")
|
||||
local showAlways = CreateConfig(nil, T["Always Show Item Comparison"], C.tooltip.compare, "showalways", "checkbox")
|
||||
local function gate()
|
||||
local on = C.tooltip.compare.basestats == "1"
|
||||
if on then
|
||||
showAlways.input:Enable()
|
||||
showAlways.caption:SetTextColor(1, 1, 1)
|
||||
else
|
||||
showAlways.input:Disable()
|
||||
showAlways.caption:SetTextColor(0.5, 0.5, 0.5)
|
||||
end
|
||||
end
|
||||
gate()
|
||||
pfUI.events:RegisterCallback("config:changed", function(_, cat, key)
|
||||
if cat == C.tooltip.compare and key == "basestats" then gate() end
|
||||
end, "eqcompare-showalways-gate")
|
||||
CreateConfig(nil, T["Always Show Extended Vendor Values"], C.tooltip.vendor, "showalways", "checkbox")
|
||||
CreateConfig(U["questitem"], T["Show Related Quest On Questitems"], C.tooltip.questitem, "showquest", "checkbox")
|
||||
CreateConfig(U["questitem"], T["Show Required Questitem Count"], C.tooltip.questitem, "showcount", "checkbox")
|
||||
end)
|
||||
|
||||
CreateGUIEntry(T["Castbar"], nil, function()
|
||||
CreateGUIEntry(T["Castbar"], T["General"], function()
|
||||
CreateConfig(nil, T["Use Unit Fonts"], C.castbar, "use_unitfonts", "checkbox")
|
||||
CreateConfig(nil, T["Casting Color"], C.appearance.castbar, "castbarcolor", "color")
|
||||
CreateConfig(nil, T["Channeling Color"], C.appearance.castbar, "channelcolor", "color")
|
||||
CreateConfig(nil, T["Castbar Texture"], C.appearance.castbar, "texture", "dropdown", pfUI.gui.dropdowns.uf_bartexture)
|
||||
CreateConfig(nil, T["Disable Blizzard Castbar"], C.castbar.player, "hide_blizz", "checkbox")
|
||||
end)
|
||||
|
||||
CreateConfig(nil, T["Player Castbar"], nil, nil, "header")
|
||||
CreateGUIEntry(T["Castbar"], T["Player"], function()
|
||||
CreateConfig(nil, T["Disable Player Castbar"], C.castbar.player, "hide_pfui", "checkbox")
|
||||
CreateConfig(nil, T["Castbar Width"], C.castbar.player, "width")
|
||||
CreateConfig(nil, T["Castbar Height"], C.castbar.player, "height")
|
||||
CreateConfig(nil, T["Show Spell Icon"], C.castbar.player, "showicon", "checkbox")
|
||||
CreateConfig(nil, T["Show Spell Name"], C.castbar.player, "showname", "checkbox")
|
||||
CreateConfig(nil, T["Spell Name Alignment"], C.castbar.player, "namealign", "dropdown", pfUI.gui.dropdowns.castbaralign)
|
||||
CreateConfig(nil, T["Spell Name X Offset"], C.castbar.player, "txtleftoffx")
|
||||
CreateConfig(nil, T["Spell Name Y Offset"], C.castbar.player, "txtleftoffy")
|
||||
CreateConfig(nil, T["Show Timer"], C.castbar.player, "showtimer", "checkbox")
|
||||
CreateConfig(nil, T["Left Text X Offset"], C.castbar.player, "txtleftoffx")
|
||||
CreateConfig(nil, T["Left Text Y Offset"], C.castbar.player, "txtleftoffy")
|
||||
CreateConfig(nil, T["Timer Alignment"], C.castbar.player, "timealign", "dropdown", pfUI.gui.dropdowns.castbaralign)
|
||||
CreateConfig(nil, T["Timer X Offset"], C.castbar.player, "txtrightoffx")
|
||||
CreateConfig(nil, T["Timer Y Offset"], C.castbar.player, "txtrightoffy")
|
||||
CreateConfig(nil, T["Show Lag"], C.castbar.player, "showlag", "checkbox")
|
||||
CreateConfig(nil, T["Show Rank"], C.castbar.player, "showrank", "checkbox")
|
||||
CreateConfig(nil, T["Right Text X Offset"], C.castbar.player, "txtrightoffx")
|
||||
CreateConfig(nil, T["Right Text Y Offset"], C.castbar.player, "txtrightoffy")
|
||||
CreateConfig(nil, T["Merge Tradeskill Casts"], C.castbar.player, "mergetradeskill", "checkbox")
|
||||
end)
|
||||
|
||||
CreateConfig(nil, T["Target Castbar"], nil, nil, "header")
|
||||
CreateGUIEntry(T["Castbar"], T["Target"], function()
|
||||
CreateConfig(nil, T["Disable Target Castbar"], C.castbar.target, "hide_pfui", "checkbox")
|
||||
CreateConfig(nil, T["Castbar Width"], C.castbar.target, "width")
|
||||
CreateConfig(nil, T["Castbar Height"], C.castbar.target, "height")
|
||||
CreateConfig(nil, T["Show Spell Icon"], C.castbar.target, "showicon", "checkbox")
|
||||
CreateConfig(nil, T["Show Spell Name"], C.castbar.target, "showname", "checkbox")
|
||||
CreateConfig(nil, T["Spell Name Alignment"], C.castbar.target, "namealign", "dropdown", pfUI.gui.dropdowns.castbaralign)
|
||||
CreateConfig(nil, T["Spell Name X Offset"], C.castbar.target, "txtleftoffx")
|
||||
CreateConfig(nil, T["Spell Name Y Offset"], C.castbar.target, "txtleftoffy")
|
||||
CreateConfig(nil, T["Show Timer"], C.castbar.target, "showtimer", "checkbox")
|
||||
CreateConfig(nil, T["Left Text X Offset"], C.castbar.target, "txtleftoffx")
|
||||
CreateConfig(nil, T["Left Text Y Offset"], C.castbar.target, "txtleftoffy")
|
||||
CreateConfig(nil, T["Timer Alignment"], C.castbar.target, "timealign", "dropdown", pfUI.gui.dropdowns.castbaralign)
|
||||
CreateConfig(nil, T["Timer X Offset"], C.castbar.target, "txtrightoffx")
|
||||
CreateConfig(nil, T["Timer Y Offset"], C.castbar.target, "txtrightoffy")
|
||||
CreateConfig(nil, T["Show Lag"], C.castbar.target, "showlag", "checkbox")
|
||||
CreateConfig(nil, T["Show Rank"], C.castbar.target, "showrank", "checkbox")
|
||||
CreateConfig(nil, T["Right Text X Offset"], C.castbar.target, "txtrightoffx")
|
||||
CreateConfig(nil, T["Right Text Y Offset"], C.castbar.target, "txtrightoffy")
|
||||
end)
|
||||
|
||||
CreateConfig(nil, T["Focus Castbar"], nil, nil, "header")
|
||||
CreateGUIEntry(T["Castbar"], T["Focus"], function()
|
||||
CreateConfig(nil, T["Disable Focus Castbar"], C.castbar.focus, "hide_pfui", "checkbox")
|
||||
CreateConfig(nil, T["Castbar Width"], C.castbar.focus, "width")
|
||||
CreateConfig(nil, T["Castbar Height"], C.castbar.focus, "height")
|
||||
CreateConfig(nil, T["Show Spell Icon"], C.castbar.focus, "showicon", "checkbox")
|
||||
CreateConfig(nil, T["Show Spell Name"], C.castbar.focus, "showname", "checkbox")
|
||||
CreateConfig(nil, T["Spell Name Alignment"], C.castbar.focus, "namealign", "dropdown", pfUI.gui.dropdowns.castbaralign)
|
||||
CreateConfig(nil, T["Spell Name X Offset"], C.castbar.focus, "txtleftoffx")
|
||||
CreateConfig(nil, T["Spell Name Y Offset"], C.castbar.focus, "txtleftoffy")
|
||||
CreateConfig(nil, T["Show Timer"], C.castbar.focus, "showtimer", "checkbox")
|
||||
CreateConfig(nil, T["Left Text X Offset"], C.castbar.focus, "txtleftoffx")
|
||||
CreateConfig(nil, T["Left Text Y Offset"], C.castbar.focus, "txtleftoffy")
|
||||
CreateConfig(nil, T["Timer Alignment"], C.castbar.focus, "timealign", "dropdown", pfUI.gui.dropdowns.castbaralign)
|
||||
CreateConfig(nil, T["Timer X Offset"], C.castbar.focus, "txtrightoffx")
|
||||
CreateConfig(nil, T["Timer Y Offset"], C.castbar.focus, "txtrightoffy")
|
||||
CreateConfig(nil, T["Show Lag"], C.castbar.focus, "showlag", "checkbox")
|
||||
CreateConfig(nil, T["Show Rank"], C.castbar.focus, "showrank", "checkbox")
|
||||
CreateConfig(nil, T["Right Text X Offset"], C.castbar.focus, "txtrightoffx")
|
||||
CreateConfig(nil, T["Right Text Y Offset"], C.castbar.focus, "txtrightoffy")
|
||||
end)
|
||||
|
||||
CreateGUIEntry(T["Chat"], nil, function()
|
||||
@@ -2895,10 +2927,10 @@ pfUI:RegisterModule("gui", function ()
|
||||
CreateConfig(U["nameplates"], T["Enable Debuffs"], C.nameplates, "showdebuffs", "checkbox")
|
||||
CreateConfig(U["nameplates"], T["Show Debuffs on Hostile"], C.nameplates, "showdebuffs_hostile", "checkbox")
|
||||
CreateConfig(U["nameplates"], T["Show Debuffs on Friendly"], C.nameplates, "showdebuffs_friendly", "checkbox")
|
||||
CreateConfig(U["nameplates"], T["Only Show Your Debuffs"], C.nameplates, "owndebuffs", "checkbox")
|
||||
CreateConfig(U["nameplates"], T["Debuff Position"], C.nameplates.debuffs, "position", "dropdown", pfUI.gui.dropdowns.debuffposition)
|
||||
CreateConfig(U["nameplates"], T["Debuff Icon Offset"], C.nameplates, "debuffoffset")
|
||||
CreateConfig(U["nameplates"], T["Debuff Icon Size"], C.nameplates, "debuffsize")
|
||||
CreateConfig(U["nameplates"], T["Estimate Debuffs"], C.nameplates, "guessdebuffs", "checkbox")
|
||||
CreateConfig(U["nameplates"], T["Show Debuff Stacks"], C.nameplates.debuffs, "showstacks", "checkbox")
|
||||
CreateConfig(U["nameplates"], T["Enable Debuff Timers"], C.nameplates, "debufftimers", "checkbox")
|
||||
CreateConfig(U["nameplates"], T["Show Timer Text"], C.nameplates, "debufftext", "checkbox")
|
||||
|
||||
@@ -13,15 +13,6 @@ pfUI:RegisterModule("innervatecall", function ()
|
||||
|
||||
local INNERVATE_SPELLID = 29166
|
||||
|
||||
-- Cache player GUID
|
||||
local playerGuid = nil
|
||||
local function GetPlayerGuid()
|
||||
if not playerGuid and UnitGUID then
|
||||
playerGuid = UnitGUID("player")
|
||||
end
|
||||
return playerGuid
|
||||
end
|
||||
|
||||
-- GUID → name resolution for the target. UnitTokenFromGUID walks the
|
||||
-- engine's known unit tokens (player, party, raid, target, ...) and
|
||||
-- returns the first one currently bound to the GUID, so we don't have
|
||||
@@ -39,11 +30,11 @@ pfUI:RegisterModule("innervatecall", function ()
|
||||
return "BATTLEGROUND"
|
||||
end
|
||||
|
||||
if GetNumRaidMembers() > 0 then
|
||||
if IsInRaid() then
|
||||
return "RAID"
|
||||
end
|
||||
|
||||
if GetNumPartyMembers() > 0 then
|
||||
if IsInGroup() then
|
||||
return "PARTY"
|
||||
end
|
||||
|
||||
@@ -75,7 +66,7 @@ pfUI:RegisterModule("innervatecall", function ()
|
||||
if spellId ~= INNERVATE_SPELLID then return end
|
||||
|
||||
-- Only announce our own casts
|
||||
if casterGuid ~= GetPlayerGuid() then return end
|
||||
if not IsPlayerGuid(casterGuid) then return end
|
||||
|
||||
-- Resolve target name from GUID
|
||||
local targetName = ResolveTargetName(targetGuid) or "Unknown"
|
||||
@@ -96,14 +87,10 @@ pfUI:RegisterModule("innervatecall", function ()
|
||||
end
|
||||
end
|
||||
|
||||
local readyAt = GetTime() + cdRemaining
|
||||
frame:SetScript("OnUpdate", function()
|
||||
if GetTime() >= readyAt then
|
||||
frame:SetScript("OnUpdate", nil)
|
||||
local ch = GetAnnounceChannel()
|
||||
if ch then
|
||||
SendChatMessage(">> Innervate is ready <<", ch)
|
||||
end
|
||||
C_Timer.After(cdRemaining, function()
|
||||
local ch = GetAnnounceChannel()
|
||||
if ch then
|
||||
SendChatMessage(">> Innervate is ready <<", ch)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -41,7 +41,7 @@ pfUI:RegisterModule("itemcount", function ()
|
||||
end
|
||||
end)
|
||||
|
||||
hooksecurefunc("SetItemRef", function()
|
||||
pfUI.hooksecurefunc("SetItemRef", function()
|
||||
if ItemRefTooltip:HasItem() then
|
||||
local _, _, id = ItemRefTooltip:GetItem()
|
||||
if id then AddCounts(ItemRefTooltip, id) end
|
||||
|
||||
+3
-3
@@ -323,7 +323,7 @@ pfUI:RegisterModule("loot", function ()
|
||||
end
|
||||
|
||||
function pfUI.loot:InitGroupDropDown()
|
||||
local inRaid = UnitInRaid("player")
|
||||
local inRaid = IsInRaid()
|
||||
if UIDROPDOWNMENU_MENU_LEVEL == 1 then
|
||||
if ( inRaid ) then
|
||||
pfUI.loot:BuildRaidMenu(UIDROPDOWNMENU_MENU_LEVEL)
|
||||
@@ -392,7 +392,7 @@ pfUI:RegisterModule("loot", function ()
|
||||
end
|
||||
pfUI.loot:RemoveMasterlootMenus() -- remove then add to ensure no duplicate menus
|
||||
pfUI.loot:AddMasterLootMenus()
|
||||
hooksecurefunc("UnitPopup_OnClick",function()
|
||||
pfUI.hooksecurefunc("UnitPopup_OnClick",function()
|
||||
local dropdownFrame = _G[UIDROPDOWNMENU_INIT_MENU]
|
||||
if not dropdownFrame then return end
|
||||
local button = this.value
|
||||
@@ -405,7 +405,7 @@ pfUI:RegisterModule("loot", function ()
|
||||
end
|
||||
end
|
||||
end)
|
||||
hooksecurefunc("UnitPopup_HideButtons",function()
|
||||
pfUI.hooksecurefunc("UnitPopup_HideButtons",function()
|
||||
local dropdownFrame = _G[UIDROPDOWNMENU_INIT_MENU]
|
||||
local unit = dropdownFrame.unit
|
||||
local name = dropdownFrame.name
|
||||
|
||||
@@ -66,9 +66,5 @@ pfUI:RegisterModule("macrotweak", function ()
|
||||
end)
|
||||
|
||||
-- Check conflicts after one tick so all addons have finished loading
|
||||
local watcher = CreateFrame("Frame")
|
||||
watcher:SetScript("OnUpdate", function()
|
||||
this:SetScript("OnUpdate", nil)
|
||||
CheckConflicts()
|
||||
end)
|
||||
RunNextFrame(CheckConflicts)
|
||||
end)
|
||||
+4
-4
@@ -54,7 +54,7 @@ pfUI:RegisterModule("map", function ()
|
||||
if not this.hooked then
|
||||
this.hooked = true
|
||||
|
||||
HookScript(WorldMapFrame, "OnShow", function()
|
||||
WorldMapFrame:HookScript("OnShow", function()
|
||||
-- customize
|
||||
this:EnableKeyboard(false)
|
||||
this:EnableMouseWheel(1)
|
||||
@@ -66,7 +66,7 @@ pfUI:RegisterModule("map", function ()
|
||||
pfOrigSetMapToCurrentZone()
|
||||
end)
|
||||
|
||||
HookScript(WorldMapFrame, "OnMouseWheel", function()
|
||||
WorldMapFrame:HookScript("OnMouseWheel", function()
|
||||
if IsShiftKeyDown() then
|
||||
alpha = clamp(WorldMapFrame:GetAlpha() + arg1/10, 0.1, 1.0)
|
||||
WorldMapFrame:SetAlpha(alpha)
|
||||
@@ -91,11 +91,11 @@ pfUI:RegisterModule("map", function ()
|
||||
SaveMovable(this, true)
|
||||
end)
|
||||
|
||||
HookScript(WorldMapFrame, "OnDragStart", function()
|
||||
WorldMapFrame:HookScript("OnDragStart", function()
|
||||
WorldMapFrame:StartMoving()
|
||||
end)
|
||||
|
||||
HookScript(WorldMapFrame, "OnDragStop",function()
|
||||
WorldMapFrame:HookScript("OnDragStop",function()
|
||||
WorldMapFrame:StopMovingOrSizing()
|
||||
SaveMovable(this, true)
|
||||
end)
|
||||
|
||||
@@ -50,7 +50,7 @@ pfUI:RegisterModule("mapcolors", function ()
|
||||
end
|
||||
|
||||
local function UpdateUnitFrames(unit_button_name)
|
||||
if GetNumRaidMembers() > 0 then
|
||||
if IsInRaid() then
|
||||
for i=1, MAX_RAID_MEMBERS do
|
||||
local frame_name = unit_button_name.."Raid"..i
|
||||
local frame = _G[frame_name]
|
||||
@@ -68,7 +68,7 @@ pfUI:RegisterModule("mapcolors", function ()
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif GetNumPartyMembers() > 0 then
|
||||
elseif IsInGroup() then
|
||||
for i=1, MAX_PARTY_MEMBERS do
|
||||
local frame_name = unit_button_name.."Party"..i
|
||||
local frame = _G[frame_name]
|
||||
@@ -125,13 +125,13 @@ pfUI:RegisterModule("mapcolors", function ()
|
||||
|
||||
-- WorldMap
|
||||
Initialize('WorldMap')
|
||||
hooksecurefunc('WorldMapButton_OnUpdate', function()
|
||||
pfUI.hooksecurefunc('WorldMapButton_OnUpdate', function()
|
||||
if ( this.tick or .5) > GetTime() then return else this.tick = GetTime() + .5 end
|
||||
UpdateUnitFrames('WorldMap')
|
||||
end)
|
||||
|
||||
if C.appearance.worldmap.colornames == "1" then
|
||||
hooksecurefunc('WorldMapUnit_OnEnter', function()
|
||||
pfUI.hooksecurefunc('WorldMapUnit_OnEnter', function()
|
||||
if ( this.tick or .5) > GetTime() then return else this.tick = GetTime() + .5 end
|
||||
UpdateUnitColors('WorldMap', WorldMapTooltip)
|
||||
end)
|
||||
@@ -141,13 +141,13 @@ pfUI:RegisterModule("mapcolors", function ()
|
||||
HookAddonOrVariable("Blizzard_BattlefieldMinimap", function()
|
||||
Initialize('BattlefieldMinimap')
|
||||
|
||||
hooksecurefunc('BattlefieldMinimap_OnUpdate', function()
|
||||
pfUI.hooksecurefunc('BattlefieldMinimap_OnUpdate', function()
|
||||
if ( this.tick or .5) > GetTime() then return else this.tick = GetTime() + .5 end
|
||||
UpdateUnitFrames('BattlefieldMinimap')
|
||||
end)
|
||||
|
||||
if C.appearance.worldmap.colornames == "1" then
|
||||
hooksecurefunc('BattlefieldMinimapUnit_OnEnter', function()
|
||||
pfUI.hooksecurefunc('BattlefieldMinimapUnit_OnEnter', function()
|
||||
if ( this.tick or .5) > GetTime() then return else this.tick = GetTime() + .5 end
|
||||
UpdateUnitColors('BattlefieldMinimap', GameTooltip)
|
||||
end)
|
||||
|
||||
+33
-70
@@ -30,25 +30,6 @@ pfUI:RegisterModule("mapreveal", function ()
|
||||
pfUI.mapreveal:UpdateConfig()
|
||||
end)
|
||||
|
||||
local function unpack_hash(prefix, hash)
|
||||
local _, stored_prefix, textureName, textureWidth, textureHeight, offsetX, offsetY, mapPointX, mapPointY, name
|
||||
_, _, stored_prefix, textureName, textureWidth, textureHeight, offsetX, offsetY = string.find(hash, "^([|]?)([^:]+):([^:]+):([^:]+):([^:]+):([^:]+)")
|
||||
if (not textureName or not offsetY) then
|
||||
return
|
||||
end
|
||||
if (offsetY) then
|
||||
_, _, mapPointX, mapPointY = string.find(hash,"^[|]?[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:([^:]+):([^:]+)")
|
||||
end
|
||||
if (not mapPointY) then
|
||||
mapPointX = 0 mapPointY = 0
|
||||
end
|
||||
if (stored_prefix ~= "|") then
|
||||
name = textureName
|
||||
textureName = string.format("%s%s",prefix,textureName)
|
||||
end
|
||||
return textureName, textureWidth + 0, textureHeight + 0, offsetX + 0, offsetY + 0, mapPointX + 0, mapPointY + 0, name
|
||||
end
|
||||
|
||||
local explores = {}
|
||||
local explorecaches = {}
|
||||
local alreadyknown = {} -- per-zone accumulator: { [zone] = { [texName] = true } }
|
||||
@@ -88,13 +69,6 @@ pfUI:RegisterModule("mapreveal", function ()
|
||||
end
|
||||
end
|
||||
|
||||
-- overlay data with lazy init
|
||||
local overlayData = setmetatable(pfMapOverlayData, {__index = function(t,k)
|
||||
local v = {}
|
||||
rawset(t,k,v)
|
||||
return v
|
||||
end})
|
||||
|
||||
local function pfWorldMapFrame_Update()
|
||||
-- clear stale caches
|
||||
for k in pairs(explorecaches) do explorecaches[k] = nil end
|
||||
@@ -108,7 +82,6 @@ pfUI:RegisterModule("mapreveal", function ()
|
||||
local mapFileName = GetMapInfo()
|
||||
if not mapFileName then mapFileName = "World" end
|
||||
|
||||
local prefix = string.format("Interface\\WorldMap\\%s\\", mapFileName)
|
||||
local numOverlays = GetNumMapOverlays()
|
||||
|
||||
-- accumulate explored overlays per zone (never clear, only add)
|
||||
@@ -123,12 +96,18 @@ pfUI:RegisterModule("mapreveal", function ()
|
||||
-- hide explore icons
|
||||
for _, frame in pairs(explores) do frame:Hide() end
|
||||
|
||||
local zoneData = overlayData[mapFileName]
|
||||
-- ClassicAPI: full overlay list for the viewed zone (explored + unexplored),
|
||||
-- read straight from WorldMapOverlay.dbc. Replaces the hand-measured pfMapOverlayData.
|
||||
local zoneData = C_Map.GetMapOverlays() or {}
|
||||
local textureCount = 0
|
||||
|
||||
for i, hash in ipairs(zoneData) do
|
||||
local textureName, textureWidth, textureHeight, offsetX, offsetY, mapPointX, mapPointY, name = unpack_hash(prefix, hash)
|
||||
if not textureName then break end
|
||||
for i, overlay in ipairs(zoneData) do
|
||||
local name = overlay.textureName -- bare, e.g. "DRYGULCHRAVINE"
|
||||
local textureName = overlay.texturePath -- full engine path (for SetTexture)
|
||||
local textureWidth = overlay.textureWidth
|
||||
local textureHeight = overlay.textureHeight
|
||||
local offsetX = overlay.offsetX
|
||||
local offsetY = overlay.offsetY
|
||||
|
||||
-- explore magnifying glass icon
|
||||
explores[i] = explores[i] or CreateFrame("Frame", nil, WorldMapDetailFrame)
|
||||
@@ -146,6 +125,8 @@ pfUI:RegisterModule("mapreveal", function ()
|
||||
explore.tex:SetTexCoord(.08, .92, .08, .92)
|
||||
explore.tex:SetAllPoints()
|
||||
|
||||
-- `alreadyknown` stores the FULL paths GetMapOverlayInfo returns,
|
||||
-- so compare with the full path, not the bare name.
|
||||
if C.appearance.worldmap.mapexploration == "1" and not zoneKnown[string.upper(textureName)] then
|
||||
explore.tex:SetTexture("Interface\\WorldMap\\WorldMap-MagnifyingGlass")
|
||||
explore:Show()
|
||||
@@ -155,49 +136,31 @@ pfUI:RegisterModule("mapreveal", function ()
|
||||
|
||||
-- render overlay texture tiles on BORDER draw layer
|
||||
-- Blizzard's explored overlays on ARTWORK draw on top of BORDER
|
||||
--
|
||||
-- overlay.tiles is pre-resolved by ClassicAPI: per-tile file /
|
||||
-- draw size / texcoords / canvas position, with Octo's data
|
||||
-- quirks (sliver columns the DBC rect rounds away, foreign tiles
|
||||
-- appended to the number sequence, upscaled re-exports) already
|
||||
-- disambiguated from the actual BLP dimensions. No 256px grid
|
||||
-- math here — deriving the grid from textureWidth/Height is
|
||||
-- exactly what shears quirky overlays (e.g. Icepoint's Kaneq'nuun).
|
||||
if C.appearance.worldmap.mapreveal == "1" then
|
||||
local numH = math.ceil(textureWidth / 256)
|
||||
local numV = math.ceil(textureHeight / 256)
|
||||
local texPixW, texFileW, texPixH, texFileH
|
||||
for _, tile in ipairs(overlay.tiles) do
|
||||
textureCount = textureCount + 1
|
||||
local tex = pfGetOverlay(textureCount)
|
||||
|
||||
for j = 1, numV do
|
||||
if j < numV then
|
||||
texPixH = 256
|
||||
texFileH = 256
|
||||
else
|
||||
texPixH = mod(textureHeight, 256)
|
||||
if texPixH == 0 then texPixH = 256 end
|
||||
texFileH = 16
|
||||
while texFileH < texPixH do texFileH = texFileH * 2 end
|
||||
end
|
||||
tex:SetWidth(tile.width)
|
||||
tex:SetHeight(tile.height)
|
||||
tex:SetTexCoord(0, tile.texCoordX, 0, tile.texCoordY)
|
||||
tex:ClearAllPoints()
|
||||
tex:SetPoint("TOPLEFT", "WorldMapDetailFrame", "TOPLEFT", tile.offsetX, -tile.offsetY)
|
||||
tex:SetTexture(tile.file)
|
||||
|
||||
for k = 1, numH do
|
||||
textureCount = textureCount + 1
|
||||
local tex = pfGetOverlay(textureCount)
|
||||
explorecaches[name] = explorecaches[name] or {}
|
||||
explorecaches[name][tex] = true
|
||||
|
||||
if k < numH then
|
||||
texPixW = 256
|
||||
texFileW = 256
|
||||
else
|
||||
texPixW = mod(textureWidth, 256)
|
||||
if texPixW == 0 then texPixW = 256 end
|
||||
texFileW = 16
|
||||
while texFileW < texPixW do texFileW = texFileW * 2 end
|
||||
end
|
||||
|
||||
tex:SetWidth(texPixW)
|
||||
tex:SetHeight(texPixH)
|
||||
tex:SetTexCoord(0, texPixW/texFileW, 0, texPixH/texFileH)
|
||||
tex:ClearAllPoints()
|
||||
tex:SetPoint("TOPLEFT", "WorldMapDetailFrame", "TOPLEFT", offsetX + 256*(k-1), -(offsetY + 256*(j-1)))
|
||||
tex:SetTexture(string.format("%s%s", textureName, ((j-1)*numH + k)))
|
||||
|
||||
explorecaches[name] = explorecaches[name] or {}
|
||||
explorecaches[name][tex] = true
|
||||
|
||||
tex:SetVertexColor(r,g,b,a)
|
||||
tex:Show()
|
||||
end
|
||||
tex:SetVertexColor(r,g,b,a)
|
||||
tex:Show()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ pfUI:RegisterModule("minimap", function ()
|
||||
|
||||
pfUI.minimap:UpdateConfig()
|
||||
|
||||
hooksecurefunc("ToggleMinimap", function()
|
||||
pfUI.hooksecurefunc("ToggleMinimap", function()
|
||||
if pfUI.farmmap and pfUI.farmmap:IsShown() then
|
||||
Minimap:Hide()
|
||||
return
|
||||
|
||||
+9
-75
@@ -1,39 +1,6 @@
|
||||
pfUI:RegisterModule("mouseover", function ()
|
||||
pfUI.uf.mouseover = CreateFrame("Frame", "pfMouseOver", UIParent)
|
||||
|
||||
-- Prepare a list of units that can be used via SpellTargetUnit
|
||||
local st_units = { [1] = "player", [2] = "target", [3] = "mouseover" }
|
||||
for i=1, MAX_PARTY_MEMBERS do table.insert(st_units, "party"..i) end
|
||||
for i=1, MAX_RAID_MEMBERS do table.insert(st_units, "raid"..i) end
|
||||
|
||||
-- Try to find a valid (friendly) unitstring that can be used for
|
||||
-- SpellTargetUnit(unit) to avoid another target switch
|
||||
local function GetUnitString(unit)
|
||||
for index, unitstr in pairs(st_units) do
|
||||
if UnitIsUnit(unit, unitstr) then
|
||||
return unitstr
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Same as CastSpellByName but with disabled AutoSelfCast
|
||||
local function NoSelfCast(spell, onself)
|
||||
local cvar_selfcast = GetCVar("AutoSelfCast")
|
||||
|
||||
if cvar_selfcast ~= "0" then
|
||||
SetCVar("AutoSelfCast", "0")
|
||||
pcall(CastSpellByName, spell, onself)
|
||||
SetCVar("AutoSelfCast", cvar_selfcast)
|
||||
else
|
||||
CastSpellByName(spell, onself)
|
||||
end
|
||||
end
|
||||
|
||||
_G.SLASH_PFCAST1, _G.SLASH_PFCAST2 = "/pfcast", "/pfmouse"
|
||||
function SlashCmdList.PFCAST(msg)
|
||||
local restore_target = true
|
||||
local func = pfUI.api.TryMemoizedFuncLoadstringForSpellCasts(msg)
|
||||
local unit = "mouseover"
|
||||
|
||||
@@ -50,51 +17,18 @@ pfUI:RegisterModule("mouseover", function ()
|
||||
end
|
||||
end
|
||||
|
||||
-- Nampower: CastSpellByName supports a second unit parameter directly.
|
||||
-- unit is already resolved to "mouseover", "target" or "player" at this point.
|
||||
if not func and GetNampowerVersion then
|
||||
-- Spell-name path: Nampower's CastSpellByName takes a second unit
|
||||
-- parameter directly, no target swap dance required.
|
||||
if not func then
|
||||
CastSpellByName(msg, unit)
|
||||
return
|
||||
end
|
||||
|
||||
-- If target and mouseover are friendly units, we can't use spell target as it
|
||||
-- would cast on the target instead of the mouseover. However, if the mouseover
|
||||
-- is friendly and the target is not, we can try to obtain the best unitstring
|
||||
-- for the later SpellTargetUnit() call.
|
||||
local unitstr = not UnitCanAssist("player", "target") and UnitCanAssist("player", unit) and GetUnitString(unit)
|
||||
|
||||
if UnitIsUnit("target", unit) or (not func and unitstr) then
|
||||
-- no target change required, we can either use spell target
|
||||
-- or the unit is already our current target.
|
||||
restore_target = false
|
||||
else
|
||||
-- The spelltarget can't be used here, we need to switch
|
||||
-- and restore the target during spell cast
|
||||
TargetUnit(unit)
|
||||
end
|
||||
|
||||
if func then
|
||||
func()
|
||||
else
|
||||
-- write temporary unit name
|
||||
pfUI.uf.mouseover.unit = unit
|
||||
|
||||
-- cast without self cast cvar setting
|
||||
-- to allow spells to use spelltarget
|
||||
NoSelfCast(msg)
|
||||
|
||||
-- set spell target to unitstring (or selfcast)
|
||||
if SpellIsTargeting() then SpellTargetUnit(unitstr or "player") end
|
||||
|
||||
-- clean up spell target in error case
|
||||
if SpellIsTargeting() then SpellStopTargeting() end
|
||||
|
||||
-- remove temporary mouseover unit
|
||||
pfUI.uf.mouseover.unit = nil
|
||||
end
|
||||
|
||||
if restore_target then
|
||||
TargetLastTarget()
|
||||
end
|
||||
-- Macro path: switch target so the macro's spell calls land on `unit`,
|
||||
-- then restore.
|
||||
local restore_target = not UnitIsUnit("target", unit)
|
||||
if restore_target then TargetUnit(unit) end
|
||||
func()
|
||||
if restore_target then TargetLastTarget() end
|
||||
end
|
||||
end)
|
||||
+102
-281
@@ -3,8 +3,6 @@ pfUI:RegisterModule("nameplates", function ()
|
||||
pcall(SetCVar, "ShowVKeyCastbar", 0)
|
||||
|
||||
-- Local function references for performance
|
||||
local pfGetCastInfo = pfGetCastInfo -- provided by libcast for vanilla
|
||||
local pfGetChannelInfo = pfGetChannelInfo -- provided by libcast for vanilla
|
||||
local GetTime = GetTime
|
||||
local UnitExists = UnitExists
|
||||
local UnitName = UnitName
|
||||
@@ -65,9 +63,30 @@ pfUI:RegisterModule("nameplates", function ()
|
||||
|
||||
local raidGuidCache = {} -- guid -> name (rebuilt on RAID_ROSTER_UPDATE/PARTY_MEMBERS_CHANGED)
|
||||
|
||||
-- Helper function to safely access libdebuff cast data
|
||||
-- Resolve a plate GUID to its cast/channel info via C_Spell. Returns a
|
||||
-- compact struct (spellName / icon / startTime / endTime / duration /
|
||||
-- isChannel) or nil when the unit isn't casting / the GUID can't map to a
|
||||
-- live token.
|
||||
local function GetCastInfo(guid)
|
||||
return pfUI.libdebuff_casts and pfUI.libdebuff_casts[guid]
|
||||
if not guid then return nil end
|
||||
local unit = UnitTokenFromGUID(guid)
|
||||
if not unit then return nil end
|
||||
local name, _, texture, startMs, endMs, _, _, _, spellID = C_Spell.UnitCastingInfo(unit)
|
||||
local isChannel
|
||||
if not name then
|
||||
name, _, texture, startMs, endMs, _, _, spellID = C_Spell.UnitChannelInfo(unit)
|
||||
isChannel = true
|
||||
end
|
||||
if not name or not startMs or not endMs then return nil end
|
||||
return {
|
||||
spellName = name,
|
||||
spellID = spellID,
|
||||
icon = texture,
|
||||
startTime = startMs / 1000,
|
||||
endTime = endMs / 1000,
|
||||
duration = (endMs - startMs) / 1000,
|
||||
isChannel = isChannel,
|
||||
}
|
||||
end
|
||||
|
||||
local guidTargetTokenCache = {} -- guid -> "<guid>target" interned string
|
||||
@@ -78,45 +97,9 @@ pfUI:RegisterModule("nameplates", function ()
|
||||
local threatMemory = {} -- guid -> true if mob had player targeted
|
||||
local debuffSeen = {} -- reusable table for debuff tracking (avoid GC churn)
|
||||
|
||||
-- PERF: Module-level IterDebuffs callback to avoid closure allocation per call
|
||||
local _iterDebuffCount = 0
|
||||
local function iterDebuffCallback(auraSlot, spellId, effect, texture, stacks, dtype, duration, timeleft)
|
||||
if not texture or string.find(texture, "QuestionMark") then return end
|
||||
_iterDebuffCount = _iterDebuffCount + 1
|
||||
if _iterDebuffCount > 16 then return end
|
||||
local b = debuffDisplayBuf[_iterDebuffCount]
|
||||
b.effect, b.texture, b.stacks, b.dtype, b.duration, b.timeleft = effect, texture, stacks, dtype, duration, timeleft
|
||||
end
|
||||
|
||||
-- PERF: Module-level IterDebuffs callback for PlateCacheDebuffs — same
|
||||
-- rationale; PlateCacheDebuffs runs per visible plate per throttled tick.
|
||||
local _pcdSelf, _pcdNow, _pcdId
|
||||
local function iterPlateCacheDebuffsCallback(auraSlot, spellId, effect, texture, stacks, dtype, duration, timeleft)
|
||||
if not effect or not texture then return end
|
||||
_pcdId = _pcdId + 1
|
||||
if _pcdId > 16 then return end
|
||||
local stop = (timeleft and timeleft > 0) and (_pcdNow + timeleft) or nil
|
||||
local start = stop and (stop - (duration or 0)) or _pcdNow
|
||||
local cache = _pcdSelf.debuffcache[_pcdId] or {}
|
||||
cache.effect = effect
|
||||
cache.texture = texture
|
||||
cache.stacks = stacks
|
||||
cache.duration = duration or 0
|
||||
cache.start = start
|
||||
cache.stop = stop
|
||||
cache.empty = nil
|
||||
_pcdSelf.debuffcache[_pcdId] = cache
|
||||
end
|
||||
|
||||
-- PERF: visiblePlateCount maintained event-driven (NAME_PLATE_UNIT_ADDED/_REMOVED)
|
||||
local visiblePlateCount = 0
|
||||
|
||||
-- wipe polyfill
|
||||
local wipe = wipe or function(t) for k in pairs(t) do t[k] = nil end end
|
||||
|
||||
-- Player GUID for filtering
|
||||
local PlayerGUID = UnitGUID("player")
|
||||
|
||||
-- ============================================================================
|
||||
-- OPTIMIZATION: Config caching
|
||||
-- ============================================================================
|
||||
@@ -134,6 +117,7 @@ pfUI:RegisterModule("nameplates", function ()
|
||||
cfg.showdebuffs = C.nameplates["showdebuffs"] == "1"
|
||||
cfg.showdebuffs_hostile = C.nameplates["showdebuffs_hostile"] == "1"
|
||||
cfg.showdebuffs_friendly = C.nameplates["showdebuffs_friendly"] == "1"
|
||||
cfg.owndebuffs = C.nameplates["owndebuffs"] == "1"
|
||||
cfg.targetzoom = C.nameplates.targetzoom == "1"
|
||||
cfg.zoomval = (tonumber(C.nameplates.targetzoomval) or 0.4) + 1
|
||||
cfg.width = tonumber(C.nameplates.width) or 120
|
||||
@@ -274,15 +258,6 @@ pfUI:RegisterModule("nameplates", function ()
|
||||
return
|
||||
end
|
||||
|
||||
local function wipe(table)
|
||||
if type(table) ~= "table" then
|
||||
return
|
||||
end
|
||||
for k in pairs(table) do
|
||||
table[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function DisableObject(object)
|
||||
if not object then return end
|
||||
if not object.GetObjectType then return end
|
||||
@@ -400,60 +375,6 @@ pfUI:RegisterModule("nameplates", function ()
|
||||
end
|
||||
end
|
||||
|
||||
local function PlateCacheDebuffs(self, unitstr, verify)
|
||||
if not self.debuffcache then self.debuffcache = {} end
|
||||
if not libdebuff then return end
|
||||
|
||||
local now = GetTime()
|
||||
|
||||
-- Clear existing cache slots
|
||||
for id = 1, 16 do
|
||||
if self.debuffcache[id] then
|
||||
self.debuffcache[id].empty = true
|
||||
end
|
||||
end
|
||||
|
||||
-- Use IterDebuffs if Nampower available, else fall back to slot loop
|
||||
if unitstr and libdebuff.IterDebuffs and UnitGUID then
|
||||
_pcdSelf, _pcdNow, _pcdId = self, now, 0
|
||||
libdebuff:IterDebuffs(unitstr, iterPlateCacheDebuffsCallback)
|
||||
else
|
||||
for id = 1, 16 do
|
||||
local effect, _, texture, stacks, _, duration, timeleft
|
||||
effect, _, texture, stacks, _, duration, timeleft = libdebuff:UnitDebuff(unitstr, id)
|
||||
if effect and timeleft and timeleft > 0 then
|
||||
local start = now - ( (duration or 0) - ( timeleft or 0) )
|
||||
local stop = now + timeleft
|
||||
self.debuffcache[id] = self.debuffcache[id] or {}
|
||||
self.debuffcache[id].effect = effect
|
||||
self.debuffcache[id].texture = texture
|
||||
self.debuffcache[id].stacks = stacks
|
||||
self.debuffcache[id].duration = duration or 0
|
||||
self.debuffcache[id].start = start
|
||||
self.debuffcache[id].stop = stop
|
||||
self.debuffcache[id].empty = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.verify = verify
|
||||
end
|
||||
|
||||
local function PlateUnitDebuff(self, id)
|
||||
-- break on unknown data
|
||||
if not self.debuffcache then return end
|
||||
if not self.debuffcache[id] then return end
|
||||
if not self.debuffcache[id].stop then return end
|
||||
|
||||
-- break on timeout debuffs
|
||||
if self.debuffcache[id].empty then return end
|
||||
if self.debuffcache[id].stop < GetTime() then return end
|
||||
|
||||
-- return cached debuff
|
||||
local c = self.debuffcache[id]
|
||||
return c.effect, c.rank, c.texture, c.stacks, c.dtype, c.duration, (c.stop - GetTime())
|
||||
end
|
||||
|
||||
local function CreateDebuffIcon(plate, index)
|
||||
plate.debuffs[index] = CreateFrame("Frame", plate.platename.."Debuff"..index, plate)
|
||||
plate.debuffs[index]:Hide()
|
||||
@@ -549,32 +470,9 @@ nameplates:RegisterEvent("NAME_PLATE_UNIT_ADDED")
|
||||
nameplates:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
|
||||
if GetUnitField then
|
||||
nameplates:RegisterEvent("UNIT_FLAGS_GUID")
|
||||
nameplates:RegisterEvent("UNIT_AURA_GUID")
|
||||
end
|
||||
|
||||
-- Cast tracking handled by libdebuff (SPELL_START/GO/FAILED events)
|
||||
-- No local event registration needed
|
||||
|
||||
-- Callback from libdebuff when auras change (GUID-based, event-driven)
|
||||
nameplates.OnAuraUpdate = function(self, guid)
|
||||
if not guid then return end
|
||||
|
||||
-- GUID is actual GUID (0xF13000...) from Nampower events
|
||||
local plate = C_NamePlate.GetNamePlateForGUID(guid)
|
||||
if plate and plate.nameplate then
|
||||
-- Mark nameplate for aura update in next OnUpdate cycle
|
||||
plate.nameplate.auraUpdate = true
|
||||
end
|
||||
end
|
||||
|
||||
-- Hook into libdebuff timer signal (fires when slotTimers written or cleared)
|
||||
pfUI.libdebuff_on_unit_updated = pfUI.libdebuff_on_unit_updated or {}
|
||||
table.insert(pfUI.libdebuff_on_unit_updated, function(guid)
|
||||
local plate = C_NamePlate.GetNamePlateForGUID(guid)
|
||||
if plate and plate.nameplate then
|
||||
plate.nameplate.auraUpdate = true
|
||||
end
|
||||
end)
|
||||
|
||||
nameplates:SetScript("OnEvent", function()
|
||||
-- Stop event handling during logout to prevent crash 132
|
||||
if event == "PLAYER_LOGOUT" then
|
||||
@@ -588,7 +486,6 @@ end
|
||||
|
||||
elseif event == "PLAYER_ENTERING_WORLD" or event == "ZONE_CHANGED_NEW_AREA" then
|
||||
if event == "PLAYER_ENTERING_WORLD" then
|
||||
_, PlayerGUID = UnitExists("player")
|
||||
CacheConfig()
|
||||
this:SetGameVariables()
|
||||
RebuildRaidGuidCache()
|
||||
@@ -665,10 +562,6 @@ end
|
||||
if threatMemory[guid] then threatMemory[guid] = nil end
|
||||
if guidTargetTokenCache[guid] then guidTargetTokenCache[guid] = nil end
|
||||
if combatColorCache[guid] then combatColorCache[guid] = nil end
|
||||
local castInfo = GetCastInfo(guid)
|
||||
if castInfo and castInfo.endTime and castInfo.endTime < GetTime() then
|
||||
if pfUI.libdebuff_casts then pfUI.libdebuff_casts[guid] = nil end
|
||||
end
|
||||
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
|
||||
if plate and plate.nameplate and plate.nameplate.cachedGuid == guid then
|
||||
plate.nameplate.cachedGuid = nil
|
||||
@@ -683,6 +576,17 @@ end
|
||||
plate.nameplate.eventcache = true
|
||||
end
|
||||
|
||||
elseif event == "UNIT_AURA_GUID" then
|
||||
-- Nampower: fires when a unit's aura set changes (add/remove/modify).
|
||||
-- arg1 = guid. Flag the matching plate so OnUpdate triggers a fresh
|
||||
-- C_UnitAuras read on the next tick instead of waiting on the 0.5s
|
||||
-- throttle — covers expirations, dispels, refreshes, and stack changes
|
||||
-- in one event.
|
||||
local plate = C_NamePlate.GetNamePlateForGUID(arg1)
|
||||
if plate and plate.nameplate then
|
||||
plate.nameplate.auraUpdate = true
|
||||
end
|
||||
|
||||
elseif event == "PLAYER_TARGET_CHANGED" then
|
||||
-- Flag target plate for update via GUID registry
|
||||
local targetGuid = UnitGUID("target")
|
||||
@@ -773,8 +677,6 @@ end
|
||||
nameplate:EnableMouse(0)
|
||||
nameplate.parent = parent
|
||||
nameplate.cache = {}
|
||||
nameplate.UnitDebuff = PlateUnitDebuff
|
||||
nameplate.CacheDebuffs = PlateCacheDebuffs
|
||||
nameplate.original = {}
|
||||
|
||||
-- create shortcuts for all known elements and disable them
|
||||
@@ -793,7 +695,7 @@ end
|
||||
end
|
||||
end
|
||||
|
||||
HookScript(nameplate.original.healthbar, "OnValueChanged", nameplates.OnValueChanged)
|
||||
nameplate.original.healthbar:HookScript("OnValueChanged", nameplates.OnValueChanged)
|
||||
|
||||
-- adjust sizes and scaling of the nameplate
|
||||
nameplate:SetScale(UIParent:GetScale())
|
||||
@@ -1028,10 +930,15 @@ end
|
||||
-- name — name alone misses pool reuse between same-named units (e.g. plate
|
||||
-- held a player "Ironforge Guard" and is now reassigned to the NPC by the
|
||||
-- same name), which would leak a stale "PLAYER" hint into GetUnitInfo.
|
||||
-- Wipe the whole cache table: the PERF gates below ("only update X when
|
||||
-- X changed") would otherwise skip bar/color/text updates when the new
|
||||
-- unit happens to share a cached value with the previous occupant
|
||||
-- (e.g., both at 60% HP percentage on plate pool reuse → bar stays at
|
||||
-- the old fill until the new mob actually changes HP).
|
||||
if plate.cache.name ~= name or plate.cache.guid ~= plate.cachedGuid then
|
||||
table.wipe(plate.cache)
|
||||
plate.cache.name = name
|
||||
plate.cache.guid = plate.cachedGuid
|
||||
plate.cache.player = nil
|
||||
plate.cdCache = nil -- new unit, reset spell-keyed timer cache
|
||||
plate.name:SetText(GetNameString(name))
|
||||
end
|
||||
@@ -1078,12 +985,6 @@ end
|
||||
-- remove unitstr on unit name mismatch
|
||||
if unitstr and UnitName(unitstr) ~= name then unitstr = nil end
|
||||
|
||||
-- use mobhealth values if addon is running
|
||||
if (MobHealth3 or MobHealthFrame) and target and name == UnitName('target') and MobHealth_GetTargetCurHP() then
|
||||
hp = MobHealth_GetTargetCurHP() > 0 and MobHealth_GetTargetCurHP() or hp
|
||||
hpmax = MobHealth_GetTargetMaxHP() > 0 and MobHealth_GetTargetMaxHP() or hpmax
|
||||
end
|
||||
|
||||
-- always make sure to keep plate visible
|
||||
plate:Show()
|
||||
|
||||
@@ -1278,47 +1179,27 @@ end
|
||||
local isFriendly = unittype == "FRIENDLY_PLAYER" or unittype == "FRIENDLY_NPC"
|
||||
local showDebuffsForType = cfg.showdebuffs and (isFriendly and cfg.showdebuffs_friendly or (not isFriendly and cfg.showdebuffs_hostile))
|
||||
if showDebuffsForType then
|
||||
-- PERF: Cache verify string - only allocate new string when name/level actually changes
|
||||
if name ~= plate.cachedVerifyName or level ~= plate.cachedVerifyLevel then
|
||||
plate.cachedVerifyName = name
|
||||
plate.cachedVerifyLevel = level
|
||||
plate.cachedVerify = (name or "") .. ":" .. (level or "")
|
||||
end
|
||||
local verify = plate.cachedVerify
|
||||
|
||||
-- update cached debuffs
|
||||
if C.nameplates["guessdebuffs"] == "1" and unitstr then
|
||||
plate:CacheDebuffs(unitstr, verify)
|
||||
end
|
||||
|
||||
-- update all debuff icons
|
||||
-- Use IterDebuffs when Nampower available to avoid blind 16-slot loop
|
||||
-- debuffDisplayBuf is a module-level reusable buffer (no GC churn)
|
||||
-- Pull debuffs from C_UnitAuras (HARMFUL range). owndebuffs adds the
|
||||
-- PLAYER filter token so only auras whose caster GUID matches the local
|
||||
-- player come through. debuffDisplayBuf is a module-level reusable
|
||||
-- buffer (no GC churn).
|
||||
local debuffCount = 0
|
||||
for i = 1, 16 do debuffDisplayBuf[i].effect = nil end -- clear previous
|
||||
if unitstr and libdebuff and libdebuff.IterDebuffs and UnitGUID then
|
||||
|
||||
_iterDebuffCount = 0
|
||||
libdebuff:IterDebuffs(unitstr, iterDebuffCallback)
|
||||
debuffCount = _iterDebuffCount
|
||||
elseif unitstr and libdebuff then
|
||||
for i = 1, 16 do
|
||||
local effect, rank, texture, stacks, dtype, duration, timeleft
|
||||
effect, rank, texture, stacks, dtype, duration, timeleft = libdebuff:UnitDebuff(unitstr, i)
|
||||
if effect then
|
||||
debuffCount = debuffCount + 1
|
||||
local b = debuffDisplayBuf[debuffCount]
|
||||
b.effect, b.texture, b.stacks, b.dtype, b.duration, b.timeleft = effect, texture, stacks, dtype, duration, timeleft
|
||||
end
|
||||
end
|
||||
elseif plate.verify == verify then
|
||||
for i = 1, 16 do
|
||||
local effect, rank, texture, stacks, dtype, duration, timeleft = plate:UnitDebuff(i)
|
||||
if effect then
|
||||
debuffCount = debuffCount + 1
|
||||
local b = debuffDisplayBuf[debuffCount]
|
||||
b.effect, b.texture, b.stacks, b.dtype, b.duration, b.timeleft = effect, texture, stacks, dtype, duration, timeleft
|
||||
end
|
||||
for i = 1, 16 do debuffDisplayBuf[i].effect = nil end
|
||||
if unitstr then
|
||||
local filter = cfg.owndebuffs and "HARMFUL|PLAYER" or "HARMFUL"
|
||||
local auras = C_UnitAuras.GetUnitAuras(unitstr, filter)
|
||||
local now = GetTime()
|
||||
for _, aura in ipairs(auras) do
|
||||
if debuffCount >= 16 then break end
|
||||
debuffCount = debuffCount + 1
|
||||
local timeleft = (aura.expirationTime and aura.expirationTime > 0) and (aura.expirationTime - now) or nil
|
||||
local b = debuffDisplayBuf[debuffCount]
|
||||
b.effect = aura.name
|
||||
b.texture = aura.icon
|
||||
b.stacks = aura.applications
|
||||
b.dtype = aura.dispelName
|
||||
b.duration = aura.duration
|
||||
b.timeleft = timeleft
|
||||
end
|
||||
end
|
||||
for i = 1, 16 do
|
||||
@@ -1346,8 +1227,15 @@ end
|
||||
end
|
||||
|
||||
if duration and timeleft and cfg.debufftimers then
|
||||
-- C_UnitAuras returns the Spell.dbc base duration, which talents
|
||||
-- can extend past — Shadow Affinity bumps SW:P from 18s to 24s,
|
||||
-- so a fresh cast has timeleft > duration and `now + timeleft -
|
||||
-- duration` lands in the future, which CooldownFrame_SetTimer
|
||||
-- treats as "not yet started" (no swirl). Widen to whichever is
|
||||
-- larger so start <= now.
|
||||
local effDuration = duration > timeleft and duration or timeleft
|
||||
plate.cdCache = plate.cdCache or {}
|
||||
local newStart = GetTime() + timeleft - duration
|
||||
local newStart = GetTime() + timeleft - effDuration
|
||||
local slotCache = plate.cdCache[index]
|
||||
local cachedStart = slotCache and slotCache.effect == effect and slotCache.start
|
||||
local cd = plate.debuffs[index].cd
|
||||
@@ -1361,7 +1249,7 @@ end
|
||||
cd.cachedText = cfg.debufftext
|
||||
cd.configCached = true
|
||||
end
|
||||
CooldownFrame_SetTimer(cd, newStart, duration, 1)
|
||||
CooldownFrame_SetTimer(cd, newStart, effDuration, 1)
|
||||
plate.cdCache[index] = plate.cdCache[index] or {}
|
||||
plate.cdCache[index].effect = effect
|
||||
plate.cdCache[index].start = newStart
|
||||
@@ -1415,9 +1303,7 @@ end
|
||||
local isCastingNonTarget = not target and nameplate.castbar and nameplate.castbar:IsShown()
|
||||
if not isCastingNonTarget and not target and cfg.showcastbar and nameplate.cachedGuid then
|
||||
local castInfo = GetCastInfo(nameplate.cachedGuid)
|
||||
if castInfo and castInfo.spellID and castInfo.endTime and castInfo.endTime > now then
|
||||
isCastingNonTarget = true
|
||||
elseif pfGetCastInfo and nameplate.castUpdate then
|
||||
if castInfo and castInfo.endTime > now then
|
||||
isCastingNonTarget = true
|
||||
end
|
||||
end
|
||||
@@ -1588,17 +1474,6 @@ end
|
||||
update = true
|
||||
end
|
||||
|
||||
-- PERF: scan for debuff timeouts using indexed access instead of pairs()
|
||||
if nameplate.debuffcache then
|
||||
for id = 1, 16 do
|
||||
local data = nameplate.debuffcache[id]
|
||||
if data and ( not data.stop or data.stop < now ) and not data.empty then
|
||||
data.empty = true
|
||||
update = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- use timer based updates
|
||||
if not nameplate.tick or nameplate.tick < now then
|
||||
update = true
|
||||
@@ -1716,94 +1591,40 @@ end
|
||||
-- Shared castbar update logic (used by both dedicated frame and central loop)
|
||||
nameplates.UpdateCastbar = function(nameplate, now)
|
||||
if not nameplate or not nameplate.castbar then return end
|
||||
local unitstr = nameplate.cachedGuid
|
||||
local castInfo = unitstr and GetCastInfo(unitstr)
|
||||
local castInfo = GetCastInfo(nameplate.cachedGuid)
|
||||
if not castInfo or castInfo.endTime < now then
|
||||
nameplate.castbar.isShown = nil
|
||||
nameplate.castbar.lastEndTime = nil
|
||||
nameplate.castbar:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
if castInfo and castInfo.spellID then
|
||||
if castInfo.startTime + castInfo.duration < now then
|
||||
wipe(castInfo)
|
||||
nameplate.castbar:Hide()
|
||||
elseif castInfo.event == "CAST" or castInfo.event == "FAIL" then
|
||||
wipe(castInfo)
|
||||
nameplate.castbar:Hide()
|
||||
else
|
||||
-- Only update min/max, color and icon once per cast (when endTime changes)
|
||||
local isChannel = castInfo.event == "CHANNEL"
|
||||
local duration = castInfo.endTime - castInfo.startTime
|
||||
if nameplate.castbar.lastEndTime ~= castInfo.endTime then
|
||||
nameplate.castbar.lastEndTime = castInfo.endTime
|
||||
nameplate.castbar.lastTextTick = nil
|
||||
-- Use relative 0..duration range (same as castbar.lua) to avoid
|
||||
-- floating-point precision loss with large absolute timestamps
|
||||
nameplate.castbar:SetMinMaxValues(0, duration)
|
||||
nameplate.castbar:SetStatusBarColor(strsplit(",", C.appearance.castbar[(isChannel and "channelcolor" or "castbarcolor")]))
|
||||
if castInfo.icon then
|
||||
nameplate.castbar.icon.tex:SetTexture(castInfo.icon)
|
||||
nameplate.castbar.icon.tex:SetTexCoord(.1,.9,.1,.9)
|
||||
end
|
||||
if cfg.spellname then
|
||||
nameplate.castbar.spell:SetText(castInfo.spellName)
|
||||
else
|
||||
nameplate.castbar.spell:SetText("")
|
||||
end
|
||||
end
|
||||
local barValue
|
||||
if isChannel then
|
||||
barValue = castInfo.endTime - now
|
||||
else
|
||||
barValue = now - castInfo.startTime
|
||||
end
|
||||
barValue = barValue < 0 and 0 or barValue
|
||||
barValue = barValue > duration and duration or barValue
|
||||
nameplate.castbar:SetValue(barValue)
|
||||
SetCastbarText(nameplate.castbar, castInfo.endTime - now)
|
||||
if not nameplate.castbar.isShown then nameplate.castbar.isShown = true; nameplate.castbar:Show() end
|
||||
local isChannel = castInfo.isChannel
|
||||
local duration = castInfo.duration
|
||||
if nameplate.castbar.lastEndTime ~= castInfo.endTime then
|
||||
nameplate.castbar.lastEndTime = castInfo.endTime
|
||||
nameplate.castbar.lastTextTick = nil
|
||||
-- Relative 0..duration range to avoid float precision loss with large
|
||||
-- absolute timestamps.
|
||||
nameplate.castbar:SetMinMaxValues(0, duration)
|
||||
nameplate.castbar:SetStatusBarColor(strsplit(",", C.appearance.castbar[(isChannel and "channelcolor" or "castbarcolor")]))
|
||||
if castInfo.icon then
|
||||
nameplate.castbar.icon.tex:SetTexture(castInfo.icon)
|
||||
nameplate.castbar.icon.tex:SetTexCoord(.1,.9,.1,.9)
|
||||
end
|
||||
else
|
||||
-- libcast fallback (vanilla without Nampower)
|
||||
if unitstr and pfGetCastInfo then
|
||||
local cast, _, _, texture, startTime, endTime = pfGetCastInfo(unitstr)
|
||||
local channel
|
||||
if not cast then
|
||||
channel, _, _, texture, startTime, endTime = pfGetChannelInfo(unitstr)
|
||||
end
|
||||
if cast or channel then
|
||||
local effect = cast or channel
|
||||
local duration = endTime - startTime
|
||||
local max = duration / 1000
|
||||
local cur = now - startTime / 1000
|
||||
if channel then cur = max + startTime / 1000 - now end
|
||||
if cur < 0 then cur = 0 end
|
||||
if cur > max then cur = max end
|
||||
if nameplate.castbar.lastEndTime ~= endTime then
|
||||
nameplate.castbar.lastEndTime = endTime
|
||||
nameplate.castbar.lastTextTick = nil
|
||||
nameplate.castbar:SetMinMaxValues(0, max)
|
||||
nameplate.castbar:SetStatusBarColor(strsplit(",", C.appearance.castbar[(channel and "channelcolor" or "castbarcolor")]))
|
||||
if texture then
|
||||
nameplate.castbar.icon.tex:SetTexture(texture)
|
||||
nameplate.castbar.icon.tex:SetTexCoord(.1, .9, .1, .9)
|
||||
end
|
||||
if cfg.spellname then
|
||||
nameplate.castbar.spell:SetText(effect)
|
||||
else
|
||||
nameplate.castbar.spell:SetText("")
|
||||
end
|
||||
end
|
||||
nameplate.castbar:SetValue(cur)
|
||||
SetCastbarText(nameplate.castbar, channel and cur or (max - cur))
|
||||
if not nameplate.castbar.isShown then nameplate.castbar.isShown = true; nameplate.castbar:Show() end
|
||||
else
|
||||
nameplate.castbar.isShown = nil
|
||||
nameplate.castbar.lastEndTime = nil
|
||||
nameplate.castbar:Hide()
|
||||
end
|
||||
if cfg.spellname then
|
||||
nameplate.castbar.spell:SetText(castInfo.spellName)
|
||||
else
|
||||
nameplate.castbar.isShown = nil
|
||||
nameplate.castbar.lastEndTime = nil
|
||||
nameplate.castbar:Hide()
|
||||
nameplate.castbar.spell:SetText("")
|
||||
end
|
||||
end
|
||||
|
||||
local barValue = isChannel and (castInfo.endTime - now) or (now - castInfo.startTime)
|
||||
if barValue < 0 then barValue = 0 end
|
||||
if barValue > duration then barValue = duration end
|
||||
nameplate.castbar:SetValue(barValue)
|
||||
SetCastbarText(nameplate.castbar, castInfo.endTime - now)
|
||||
if not nameplate.castbar.isShown then nameplate.castbar.isShown = true; nameplate.castbar:Show() end
|
||||
end
|
||||
|
||||
-- Dedicated frame that updates ONLY the target plate castbar.
|
||||
|
||||
+3
-4
@@ -424,7 +424,8 @@ pfUI:RegisterModule("panel", function()
|
||||
widget:RegisterEvent("PLAYER_REGEN_ENABLED")
|
||||
widget:RegisterEvent("PLAYER_DEAD")
|
||||
widget:RegisterEvent("PLAYER_UNGHOST")
|
||||
widget:RegisterEvent("UNIT_INVENTORY_CHANGED")
|
||||
widget:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") -- ClassicAPI: equip/unequip changes total durability
|
||||
widget:RegisterEvent("UPDATE_INVENTORY_DURABILITY") -- ClassicAPI: combat wear / repair
|
||||
|
||||
widget.Click = function() ToggleCharacter("PaperDollFrame") end
|
||||
widget.Tooltip = function()
|
||||
@@ -456,8 +457,6 @@ pfUI:RegisterModule("panel", function()
|
||||
end
|
||||
end
|
||||
widget:SetScript("OnEvent", function()
|
||||
if event == "UNIT_INVENTORY_CHANGED" and arg1 ~= "player" then return end
|
||||
|
||||
local totalCurr, totalMax = 0, 0
|
||||
for id = INVSLOT_FIRST_EQUIPPED, INVSLOT_LAST_EQUIPPED do
|
||||
local cur, max = GetInventoryItemDurability(id)
|
||||
@@ -533,7 +532,7 @@ pfUI:RegisterModule("panel", function()
|
||||
-- Hearthstone bind location
|
||||
local hearth = CreateFrame("Frame", "pfPanelBindLocation", UIParent)
|
||||
hearth:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
hearth:RegisterEvent("CHAT_MSG_SYSTEM")
|
||||
hearth:RegisterEvent("HEARTHSTONE_BOUND")
|
||||
hearth:SetScript("OnEvent", function()
|
||||
pfUI.panel:OutputPanel("bindlocation", T["Hearthstone"] .. ": " .. (GetBindLocation() or T["Not Set"]))
|
||||
end)
|
||||
|
||||
+17
-50
@@ -38,38 +38,6 @@ pfUI:RegisterModule("player", function ()
|
||||
playerFrame.myclass = myclass
|
||||
playerFrame.isSpellCaster = myclass ~= "WARRIOR" and myclass ~= "ROGUE" and myclass ~= "HUNTER"
|
||||
|
||||
-- Compute class-based casting speed modifier and cache on the frame.
|
||||
-- This is re-evaluated on LEARNED_SPELL_IN_TAB (with 1s delay) so talent changes are handled.
|
||||
-- Not sure if there are any other effects that give % cast reduction time
|
||||
local function UpdatePlayerModCastingTime()
|
||||
playerFrame.modCastingTime = 1
|
||||
if myclass == "MAGE" then
|
||||
local _, _, _, _, acceleratedArcana = GetTalentInfo(1, 16)
|
||||
if acceleratedArcana and acceleratedArcana > 0 then
|
||||
playerFrame.modCastingTime = 0.95
|
||||
end
|
||||
elseif myclass == "WARLOCK" then
|
||||
local _, _, _, _, rapidDeter = GetTalentInfo(1, 14)
|
||||
if rapidDeter and rapidDeter > 0 then
|
||||
playerFrame.modCastingTime = 1 - (rapidDeter * 0.03)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local talentFrame = CreateFrame("Frame")
|
||||
talentFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
talentFrame:RegisterEvent("LEARNED_SPELL_IN_TAB")
|
||||
talentFrame:SetScript("OnEvent", function()
|
||||
-- Delay 1s for both PLAYER_ENTERING_WORLD and LEARNED_SPELL_IN_TAB
|
||||
local checkAt = GetTime() + 1
|
||||
talentFrame:SetScript("OnUpdate", function()
|
||||
if GetTime() >= checkAt then
|
||||
talentFrame:SetScript("OnUpdate", nil)
|
||||
UpdatePlayerModCastingTime()
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
-- Convert "r,g,b,a" config color string to a 6-char hex string, or nil if unset
|
||||
local function cfgColorToHex(colorStr)
|
||||
if not colorStr or colorStr == "" then return nil end
|
||||
@@ -98,26 +66,25 @@ pfUI:RegisterModule("player", function ()
|
||||
if not cfg then
|
||||
return
|
||||
end
|
||||
local hasteMode = cfg.display_haste -- "0"=none, "1"=modCastSpeed, "2"=modCastSpeed*modCastingTime
|
||||
-- display_haste: "0"=hidden, "1"=show modCastSpeed (gear haste). Talent-
|
||||
-- side cast-time reductions show up in the actual cast bar via
|
||||
-- C_Spell.UnitCastingInfo; double-folding them into this overlay was
|
||||
-- mixing two different concepts into one number.
|
||||
local showHaste = cfg.display_haste == "1"
|
||||
local showSP = cfg.display_spellpower == "1"
|
||||
|
||||
local isSpellCaster = playerFrame.isSpellCaster
|
||||
if (hasteMode == "0" or not isSpellCaster) and not showSP then
|
||||
if (not showHaste or not isSpellCaster) and not showSP then
|
||||
playerFrame.infoTopCenterText:SetText("")
|
||||
return
|
||||
end
|
||||
|
||||
local haste = GetUnitField("player", "modCastSpeed")
|
||||
local modCastingTime = playerFrame.modCastingTime or 1
|
||||
local text = ""
|
||||
|
||||
if isSpellCaster and haste then
|
||||
if showHaste and isSpellCaster and haste then
|
||||
local hasteHex = cfgColorToHex(cfg.display_haste_color) or "FFFFFF"
|
||||
if hasteMode == "1" then
|
||||
text = string.format("|cff%s%.1f%%|r", hasteHex, (1 / haste - 1) * 100)
|
||||
elseif hasteMode == "2" then
|
||||
text = string.format("|cff%s%.1f%%|r", hasteHex, (1 / (haste * modCastingTime) - 1) * 100)
|
||||
end
|
||||
text = string.format("|cff%s%.1f%%|r", hasteHex, (1 / haste - 1) * 100)
|
||||
end
|
||||
|
||||
if showSP and isSpellCaster then
|
||||
@@ -151,21 +118,21 @@ pfUI:RegisterModule("player", function ()
|
||||
end
|
||||
|
||||
-- Add throttle to player frame OnUpdate
|
||||
-- Throttle the unit frame's existing OnUpdate to ~20 FPS so the per-frame
|
||||
-- work stays cheap.
|
||||
if pfUI.uf.player:GetScript("OnUpdate") then
|
||||
local originalOnUpdate = pfUI.uf.player:GetScript("OnUpdate")
|
||||
pfUI.uf.player:SetScript("OnUpdate", function()
|
||||
if (this.throttleTick or 0) > GetTime() then
|
||||
return
|
||||
end
|
||||
this.throttleTick = GetTime() + 0.05 -- Default: 20 FPS
|
||||
if (this.throttleTick or 0) > GetTime() then return end
|
||||
this.throttleTick = GetTime() + 0.05
|
||||
originalOnUpdate()
|
||||
if (this.infoTextTick or 0) <= GetTime() then
|
||||
this.infoTextTick = GetTime() + 0.25 -- Don't need to update haste/SP text as often
|
||||
UpdateInfoText()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Haste / spell-power overlay text — refreshes 4×/sec on its own ticker,
|
||||
-- independent of the unit frame's OnUpdate cadence.
|
||||
C_Timer.NewTicker(0.25, UpdateInfoText)
|
||||
|
||||
-- Replace default's RESET_INSTANCES button with an always working one
|
||||
UnitPopupButtons["RESET_INSTANCES_FIX"] = { text = RESET_INSTANCES, dist = 0 }
|
||||
for id, text in pairs(UnitPopupMenus["SELF"]) do
|
||||
@@ -174,7 +141,7 @@ pfUI:RegisterModule("player", function ()
|
||||
end
|
||||
end
|
||||
|
||||
hooksecurefunc("UnitPopup_OnClick", function()
|
||||
pfUI.hooksecurefunc("UnitPopup_OnClick", function()
|
||||
local button = this.value
|
||||
if button == "RESET_INSTANCES_FIX" then
|
||||
StaticPopup_Show("CONFIRM_RESET_INSTANCES")
|
||||
|
||||
@@ -95,7 +95,7 @@ pfUI:RegisterModule("questitem", function ()
|
||||
-- itemref tooltip (chat link clicks): hooksecurefunc runs after SetItemRef
|
||||
-- populates ItemRefTooltip, so we just read the item back out of the tooltip
|
||||
-- instead of re-parsing the "item:NNN" out of the link string.
|
||||
hooksecurefunc("SetItemRef", function()
|
||||
pfUI.hooksecurefunc("SetItemRef", function()
|
||||
if IsModifierKeyDown() then return end
|
||||
if ItemRefTooltip:HasItem() then
|
||||
local _, _, id = ItemRefTooltip:GetItem()
|
||||
|
||||
+2
-2
@@ -106,7 +106,7 @@ pfUI:RegisterModule("raid", function ()
|
||||
this.pendingUpdate = nil
|
||||
|
||||
-- don't proceed without raid
|
||||
if not UnitInRaid("player") then return end
|
||||
if not IsInRaid() then return end
|
||||
|
||||
-- clear all existing frames
|
||||
for i=1, maxraid do SetRaidIndex(pfUI.uf.raid[i], 0) end
|
||||
@@ -164,7 +164,7 @@ pfUI:RegisterModule("raid", function ()
|
||||
end
|
||||
end
|
||||
|
||||
hooksecurefunc("UnitPopup_OnClick", function()
|
||||
pfUI.hooksecurefunc("UnitPopup_OnClick", function()
|
||||
local dropdownFrame = UIDROPDOWNMENU_INIT_MENU and _G[UIDROPDOWNMENU_INIT_MENU]
|
||||
if not dropdownFrame then return end
|
||||
local button = this.value
|
||||
|
||||
@@ -40,7 +40,7 @@ pfUI:RegisterModule("sellvalue", function ()
|
||||
end
|
||||
end)
|
||||
|
||||
hooksecurefunc("SetItemRef", function()
|
||||
pfUI.hooksecurefunc("SetItemRef", function()
|
||||
if IsModifierKeyDown() then return end
|
||||
if ItemRefTooltip:HasItem() then
|
||||
local _, _, id = ItemRefTooltip:GetItem()
|
||||
|
||||
+131
-194
@@ -1,93 +1,78 @@
|
||||
pfUI:RegisterModule("share", function ()
|
||||
local function serialize(tbl, comp, name, ignored, spacing)
|
||||
local spacing = spacing or ""
|
||||
local match = nil
|
||||
local tname = ( spacing == "" and "" or "[\"" ) .. name .. ( spacing == "" and "" or "\"]" )
|
||||
local str = spacing .. tname .. " = {\n"
|
||||
-- Profile sharing rides ClassicAPI's C_EncodingUtil:
|
||||
-- export: diff vs defaults -> SerializeCBOR -> CompressString (zlib)
|
||||
-- -> EncodeBase64, prefixed with the format magic
|
||||
-- import: the reverse, into a plain table — no loadstring, so a
|
||||
-- pasted profile can't execute code
|
||||
-- The Decode/Encode button converts between the transport blob and an
|
||||
-- editable JSON view of the same table.
|
||||
local MAGIC = "!pf1!"
|
||||
|
||||
-- Deep-diff `tbl` against `default`, keeping only changed or added
|
||||
-- values. `ignored` keys are skipped at the top level only (matches the
|
||||
-- old serializer: "position"/"disabled" when Ignore Layout is checked).
|
||||
-- Returns nil when nothing differs.
|
||||
local function DiffConfig(tbl, default, ignored)
|
||||
local diff = nil
|
||||
for k, v in pairs(tbl) do
|
||||
if not ( ignored[k] and spacing == "" ) and ( not comp or not comp[k] or comp[k] ~= tbl[k] ) then
|
||||
if not ( ignored and ignored[k] ) then
|
||||
local dv = default and default[k]
|
||||
if type(v) == "table" then
|
||||
local result = serialize(tbl[k], comp and comp[k], k, ignored, spacing .. " ")
|
||||
if result then
|
||||
match = true
|
||||
str = str .. result
|
||||
local sub = DiffConfig(v, type(dv) == "table" and dv or nil)
|
||||
if sub then
|
||||
diff = diff or {}
|
||||
diff[k] = sub
|
||||
end
|
||||
elseif type(v) == "string" then
|
||||
match = true
|
||||
local escaped = string.gsub(v, "\\", "\\\\")
|
||||
escaped = string.gsub(escaped, "\"", "\\\"")
|
||||
str = str .. spacing .. " [\""..k.."\"] = \"".. escaped .."\",\n"
|
||||
elseif type(v) == "number" then
|
||||
match = true
|
||||
str = str .. spacing .. " [\""..k.."\"] = ".. string.gsub(v, "\\", "\\\\") ..",\n"
|
||||
elseif v ~= dv and ( type(v) == "string" or type(v) == "number" or type(v) == "boolean" ) then
|
||||
diff = diff or {}
|
||||
diff[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
str = str .. spacing .. "}" .. ( spacing == "" and "" or "," ) .. "\n"
|
||||
return match and str or nil
|
||||
return diff
|
||||
end
|
||||
|
||||
local function compress(input)
|
||||
-- based on Rochet2's lzw compression
|
||||
if type(input) ~= "string" then
|
||||
return nil
|
||||
-- EditBoxes don't soft-wrap one giant unbroken line; chunk the blob.
|
||||
local function wrap(str, width)
|
||||
local out = {}
|
||||
for i = 1, strlen(str), width do
|
||||
table.insert(out, strsub(str, i, i + width - 1))
|
||||
end
|
||||
local len = strlen(input)
|
||||
if len <= 1 then
|
||||
return "u"..input
|
||||
end
|
||||
|
||||
local dict = {}
|
||||
for i = 0, 255 do
|
||||
local ic, iic = strchar(i), strchar(i, 0)
|
||||
dict[ic] = iic
|
||||
end
|
||||
local a, b = 0, 1
|
||||
|
||||
local result = {"c"}
|
||||
local resultlen = 1
|
||||
local n = 2
|
||||
local word = ""
|
||||
for i = 1, len do
|
||||
local c = strsub(input, i, i)
|
||||
local wc = word..c
|
||||
if not dict[wc] then
|
||||
local write = dict[word]
|
||||
if not write then
|
||||
return nil
|
||||
end
|
||||
result[n] = write
|
||||
resultlen = resultlen + strlen(write)
|
||||
n = n+1
|
||||
if len <= resultlen then
|
||||
return "u"..input
|
||||
end
|
||||
local str = wc
|
||||
if a >= 256 then
|
||||
a, b = 0, b+1
|
||||
if b >= 256 then
|
||||
dict = {}
|
||||
b = 1
|
||||
end
|
||||
end
|
||||
dict[str] = strchar(a,b)
|
||||
a = a+1
|
||||
word = c
|
||||
else
|
||||
word = wc
|
||||
end
|
||||
end
|
||||
result[n] = dict[word]
|
||||
resultlen = resultlen+strlen(result[n])
|
||||
n = n+1
|
||||
if len <= resultlen then
|
||||
return "u"..input
|
||||
end
|
||||
return table.concat(result)
|
||||
return table.concat(out, "\n")
|
||||
end
|
||||
|
||||
local function Encode(tbl)
|
||||
return MAGIC .. wrap(C_EncodingUtil.EncodeBase64(
|
||||
C_EncodingUtil.CompressString(
|
||||
C_EncodingUtil.SerializeCBOR(tbl))), 92)
|
||||
end
|
||||
|
||||
local function Decode(text)
|
||||
if not text then return nil end
|
||||
text = gsub(text, "%s", "")
|
||||
if strsub(text, 1, strlen(MAGIC)) ~= MAGIC then return nil end
|
||||
local ok, result = pcall(function()
|
||||
return C_EncodingUtil.DeserializeCBOR(
|
||||
C_EncodingUtil.DecompressString(
|
||||
C_EncodingUtil.DecodeBase64(strsub(text, strlen(MAGIC) + 1))))
|
||||
end)
|
||||
if ok and type(result) == "table" then return result end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function DecodeJSON(text)
|
||||
if not text or gsub(text, "%s", "") == "" then return nil end
|
||||
local ok, result = pcall(function()
|
||||
return C_EncodingUtil.DeserializeJSON(text)
|
||||
end)
|
||||
if ok and type(result) == "table" then return result end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Legacy import (pre-!pf1! exports): base64 -> LZW -> Lua source
|
||||
-- "pfUI_config = {...}". The base64 layer is standard and handled by
|
||||
-- C_EncodingUtil.DecodeBase64; only the custom LZW format needs the old
|
||||
-- Lua decoder. Import-only — new exports always use the CBOR pipeline.
|
||||
local function decompress(input)
|
||||
-- based on Rochet2's lzw compression
|
||||
if type(input) ~= "string" or strlen(input) < 1 then
|
||||
@@ -159,92 +144,32 @@ pfUI:RegisterModule("share", function ()
|
||||
return table.concat(result)
|
||||
end
|
||||
|
||||
local function enc(to_encode)
|
||||
local index_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
local bit_pattern = ''
|
||||
local encoded = ''
|
||||
local trailing = ''
|
||||
local function DecodeLegacy(text)
|
||||
if not text or gsub(text, "%s", "") == "" then return nil end
|
||||
|
||||
for i = 1, string.len(to_encode) do
|
||||
local remaining = tonumber(string.byte(string.sub(to_encode, i, i)))
|
||||
local bin_bits = ''
|
||||
for i = 7, 0, -1 do
|
||||
local current_power = math.pow(2, i)
|
||||
if remaining >= current_power then
|
||||
bin_bits = bin_bits .. '1'
|
||||
remaining = remaining - current_power
|
||||
else
|
||||
bin_bits = bin_bits .. '0'
|
||||
end
|
||||
end
|
||||
bit_pattern = bit_pattern .. bin_bits
|
||||
-- encoded blob? peel base64 + LZW down to Lua source. Raw (already
|
||||
-- decoded) source pastes are accepted as-is.
|
||||
local source = text
|
||||
local stripped = gsub(text, "%s", "")
|
||||
local ok, decoded = pcall(function()
|
||||
return C_EncodingUtil.DecodeBase64(stripped)
|
||||
end)
|
||||
if ok and decoded then
|
||||
local decompressed = decompress(decoded)
|
||||
if decompressed then source = decompressed end
|
||||
end
|
||||
|
||||
if mod(string.len(bit_pattern), 3) == 2 then
|
||||
trailing = '=='
|
||||
bit_pattern = bit_pattern .. '0000000000000000'
|
||||
elseif mod(string.len(bit_pattern), 3) == 1 then
|
||||
trailing = '='
|
||||
bit_pattern = bit_pattern .. '00000000'
|
||||
end
|
||||
local chunk = loadstring(source)
|
||||
if not chunk then return nil end
|
||||
|
||||
local count = 0
|
||||
for i = 1, string.len(bit_pattern), 6 do
|
||||
local byte = string.sub(bit_pattern, i, i+5)
|
||||
local offset = tonumber(tonumber(byte, 2))
|
||||
encoded = encoded .. string.sub(index_table, offset+1, offset+1)
|
||||
count = count + 1
|
||||
if count >= 92 then
|
||||
encoded = encoded .. "\n"
|
||||
count = 0
|
||||
end
|
||||
end
|
||||
|
||||
return string.sub(encoded, 1, -1 - string.len(trailing)) .. trailing
|
||||
end
|
||||
|
||||
local function dec(to_decode)
|
||||
local index_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
local padded = gsub(to_decode,"%s", "")
|
||||
local unpadded = gsub(padded,"=", "")
|
||||
local bit_pattern = ''
|
||||
local decoded = ''
|
||||
|
||||
to_decode = gsub(to_decode,"\n", "")
|
||||
to_decode = gsub(to_decode," ", "")
|
||||
|
||||
for i = 1, string.len(unpadded) do
|
||||
local char = string.sub(to_decode, i, i)
|
||||
local offset, _ = string.find(index_table, char)
|
||||
if offset == nil then return nil end
|
||||
|
||||
local remaining = tonumber(offset-1)
|
||||
local bin_bits = ''
|
||||
for i = 7, 0, -1 do
|
||||
local current_power = math.pow(2, i)
|
||||
if remaining >= current_power then
|
||||
bin_bits = bin_bits .. '1'
|
||||
remaining = remaining - current_power
|
||||
else
|
||||
bin_bits = bin_bits .. '0'
|
||||
end
|
||||
end
|
||||
|
||||
bit_pattern = bit_pattern .. string.sub(bin_bits, 3)
|
||||
end
|
||||
|
||||
for i = 1, string.len(bit_pattern), 8 do
|
||||
local byte = string.sub(bit_pattern, i, i+7)
|
||||
decoded = decoded .. strchar(tonumber(byte, 2))
|
||||
end
|
||||
|
||||
local padding_length = string.len(padded)-string.len(unpadded)
|
||||
|
||||
if (padding_length == 1 or padding_length == 2) then
|
||||
decoded = string.sub(decoded,1,-2)
|
||||
end
|
||||
|
||||
return decoded
|
||||
-- Sandbox the chunk: an empty environment means it can assign its
|
||||
-- config table but can't reach any global or API function — a legacy
|
||||
-- profile string is data, never code.
|
||||
local sandbox = {}
|
||||
setfenv(chunk, sandbox)
|
||||
if not pcall(chunk) then return nil end
|
||||
if type(sandbox.pfUI_config) == "table" then return sandbox.pfUI_config end
|
||||
return nil
|
||||
end
|
||||
|
||||
do -- Window
|
||||
@@ -309,33 +234,43 @@ pfUI:RegisterModule("share", function ()
|
||||
this:GetParent():UpdateScrollChildRect()
|
||||
this:GetParent():UpdateScrollState()
|
||||
|
||||
local _, error = loadstring(f.scroll.text:GetText())
|
||||
if error or string.gsub(this:GetText(), " ", "") == "" then
|
||||
f.loadButton:Disable()
|
||||
f.loadButton.text:SetTextColor(1,.5,.5,1)
|
||||
else
|
||||
local text = this:GetText()
|
||||
local blob = Decode(text)
|
||||
local json = not blob and DecodeJSON(text) or nil
|
||||
local legacy = not blob and not json and DecodeLegacy(text) or nil
|
||||
|
||||
if blob or json or legacy then
|
||||
f.loadButton:Enable()
|
||||
f.loadButton.text:SetTextColor(.5,1,.5,1)
|
||||
else
|
||||
f.loadButton:Disable()
|
||||
f.loadButton.text:SetTextColor(1,.5,.5,1)
|
||||
end
|
||||
|
||||
local trydec = dec(this:GetText())
|
||||
if string.gsub(this:GetText(), " ", "") == "" then
|
||||
f.readButton.text:SetText(T["N/A"])
|
||||
f.readButton:Disable()
|
||||
elseif not trydec or trydec == "" then
|
||||
f.readButton:Enable()
|
||||
f.readButton.text:SetText(T["Encode"])
|
||||
f.readButton.func = function()
|
||||
local compressed = enc(compress(f.scroll.text:GetText()))
|
||||
f.scroll.text:SetText(compressed)
|
||||
end
|
||||
else
|
||||
if blob or legacy then
|
||||
-- decoding a legacy string yields the JSON view; re-encoding it
|
||||
-- from there produces a new-format blob (migration path)
|
||||
f.readButton:Enable()
|
||||
f.readButton.text:SetText(T["Decode"])
|
||||
f.readButton.func = function()
|
||||
local uncompressed = decompress(dec(f.scroll.text:GetText()))
|
||||
f.scroll.text:SetText(uncompressed)
|
||||
local current = f.scroll.text:GetText()
|
||||
local config = Decode(current) or DecodeLegacy(current)
|
||||
if config then
|
||||
f.scroll.text:SetText(C_EncodingUtil.SerializeJSON(config))
|
||||
end
|
||||
end
|
||||
elseif json then
|
||||
f.readButton:Enable()
|
||||
f.readButton.text:SetText(T["Encode"])
|
||||
f.readButton.func = function()
|
||||
local config = DecodeJSON(f.scroll.text:GetText())
|
||||
if config then
|
||||
f.scroll.text:SetText(Encode(config))
|
||||
end
|
||||
end
|
||||
else
|
||||
f.readButton:Disable()
|
||||
f.readButton.text:SetText(T["N/A"])
|
||||
end
|
||||
end)
|
||||
f.scroll:SetScrollChild(f.scroll.text)
|
||||
@@ -387,21 +322,23 @@ pfUI:RegisterModule("share", function ()
|
||||
f.loadButton.text:SetFont(pfUI.font_default, pfUI_config.global.font_size, "OUTLINE")
|
||||
f.loadButton.text:SetText(T["Import"])
|
||||
f.loadButton:SetScript("OnClick", function()
|
||||
local ImportConfig, error = loadstring(f.scroll.text:GetText())
|
||||
if not error and f.scroll.text:GetText() ~= "" then
|
||||
ImportConfig()
|
||||
pfUI:LoadConfig()
|
||||
local text = f.scroll.text:GetText()
|
||||
local config = Decode(text) or DecodeJSON(text) or DecodeLegacy(text)
|
||||
if not config then return end
|
||||
|
||||
-- Skip firstrun wizard when importing a shared profile
|
||||
-- The imported config is a complete setup, no wizard needed
|
||||
if pfUI.firstrun and pfUI.firstrun.steps then
|
||||
for _, step in pairs(pfUI.firstrun.steps) do
|
||||
pfUI_init[step.name] = true
|
||||
end
|
||||
_G.pfUI_config = config
|
||||
C = _G.pfUI_config
|
||||
pfUI:LoadConfig()
|
||||
|
||||
-- Skip firstrun wizard when importing a shared profile
|
||||
-- The imported config is a complete setup, no wizard needed
|
||||
if pfUI.firstrun and pfUI.firstrun.steps then
|
||||
for _, step in pairs(pfUI.firstrun.steps) do
|
||||
pfUI_init[step.name] = true
|
||||
end
|
||||
|
||||
CreateQuestionDialog(T["Some settings need to reload the UI to take effect.\nDo you want to reloadUI now?"], ReloadUI)
|
||||
end
|
||||
|
||||
CreateQuestionDialog(T["Some settings need to reload the UI to take effect.\nDo you want to reloadUI now?"], ReloadUI)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -445,9 +382,9 @@ pfUI:RegisterModule("share", function ()
|
||||
ignored["position"] = f.ignorePosition:GetChecked()
|
||||
ignored["disabled"] = f.ignorePosition:GetChecked()
|
||||
|
||||
local compressed = enc(compress(serialize(myconfig, defconfig, "pfUI_config", ignored)))
|
||||
f.scroll.text:SetText(compressed)
|
||||
f.scroll.text.value = compressed
|
||||
local encoded = Encode(DiffConfig(myconfig, defconfig, ignored) or {})
|
||||
f.scroll.text:SetText(encoded)
|
||||
f.scroll.text.value = encoded
|
||||
f.scroll:SetVerticalScroll(0)
|
||||
end)
|
||||
end
|
||||
@@ -457,4 +394,4 @@ pfUI:RegisterModule("share", function ()
|
||||
f:Show()
|
||||
end
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
+2
-2
@@ -51,12 +51,12 @@ pfUI:RegisterModule("skin", function ()
|
||||
DurabilityFrame.SetPoint = function() return end
|
||||
|
||||
if C.appearance.cd.blizzard == "1" then
|
||||
hooksecurefunc("PaperDollItemSlotButton_Update", function()
|
||||
pfUI.hooksecurefunc("PaperDollItemSlotButton_Update", function()
|
||||
local cooldown = _G[this:GetName().."Cooldown"]
|
||||
if cooldown then cooldown.pfCooldownType = "BLIZZARD" end
|
||||
end)
|
||||
|
||||
hooksecurefunc("SpellButton_UpdateButton", function()
|
||||
pfUI.hooksecurefunc("SpellButton_UpdateButton", function()
|
||||
local cooldown = _G[this:GetName().."Cooldown"]
|
||||
if cooldown then cooldown.pfCooldownType = "BLIZZARD" end
|
||||
end)
|
||||
|
||||
@@ -10,7 +10,7 @@ pfUI:RegisterModule("socialmod", function ()
|
||||
end
|
||||
end)
|
||||
do -- add colors to guild list
|
||||
hooksecurefunc("GuildStatus_Update", function()
|
||||
pfUI.hooksecurefunc("GuildStatus_Update", function()
|
||||
local playerzone = GetRealZoneText()
|
||||
local off = FauxScrollFrame_GetOffset(GuildListScrollFrame)
|
||||
for i=1, GUILDMEMBERS_TO_DISPLAY, 1 do
|
||||
@@ -70,7 +70,7 @@ pfUI:RegisterModule("socialmod", function ()
|
||||
end
|
||||
|
||||
do -- add colors to friend list
|
||||
hooksecurefunc("FriendsList_Update", function()
|
||||
pfUI.hooksecurefunc("FriendsList_Update", function()
|
||||
if GetNumFriends() == 0 then return end
|
||||
|
||||
local playerzone = GetRealZoneText()
|
||||
@@ -123,7 +123,7 @@ pfUI:RegisterModule("socialmod", function ()
|
||||
end
|
||||
|
||||
do -- add colors to who list
|
||||
hooksecurefunc("WhoList_Update", function()
|
||||
pfUI.hooksecurefunc("WhoList_Update", function()
|
||||
local num, max = GetNumWhoResults()
|
||||
local off = FauxScrollFrame_GetOffset(WhoListScrollFrame)
|
||||
|
||||
|
||||
+7
-98
@@ -23,6 +23,13 @@ SlashCmdList["PFDLLSTATUS"] = function()
|
||||
chat:AddMessage(" |cffff0000Nampower|r: Not detected")
|
||||
end
|
||||
|
||||
-- ClassicAPI
|
||||
if CLASSIC_API_VERSION then
|
||||
chat:AddMessage(" |cff00ff00ClassicAPI|r: v" .. CLASSIC_API_VERSION)
|
||||
else
|
||||
chat:AddMessage(" |cffff0000ClassicAPI|r: Not detected")
|
||||
end
|
||||
|
||||
-- Check if castbar exists for indicator positioning
|
||||
if pfUI.castbar and pfUI.castbar.player then
|
||||
chat:AddMessage(" |cff00ff00Castbar|r: Available for indicator anchoring")
|
||||
@@ -174,102 +181,4 @@ pfUI:RegisterModule("superwow", function ()
|
||||
end
|
||||
end
|
||||
|
||||
-- Enhance libcast with SuperWoW data for NPCs and other players
|
||||
-- Player casts use SPELLCAST_* events for proper pushback handling
|
||||
local supercast = CreateFrame("Frame")
|
||||
local playerGuid = nil
|
||||
|
||||
supercast:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
supercast:RegisterEvent("UNIT_CASTEVENT")
|
||||
supercast:RegisterEvent("PLAYER_LOGOUT")
|
||||
supercast:SetScript("OnEvent", function()
|
||||
-- Handle shutdown to prevent crash 132
|
||||
if event == "PLAYER_LOGOUT" then
|
||||
this:UnregisterAllEvents()
|
||||
this:SetScript("OnEvent", nil)
|
||||
return
|
||||
end
|
||||
|
||||
if event == "PLAYER_ENTERING_WORLD" then
|
||||
-- Cache player GUID
|
||||
if UnitExists then
|
||||
local guid = UnitGUID("player")
|
||||
playerGuid = guid
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local guid = arg1
|
||||
local isPlayer = guid == playerGuid
|
||||
|
||||
-- For non-player units: disable combat parsing events (one-time init)
|
||||
if not isPlayer and not supercast.init then
|
||||
-- disable combat parsing events in superwow mode (for non-player units)
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_FRIENDLYPLAYER_DAMAGE")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_FRIENDLYPLAYER_BUFF")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_BUFFS")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_PARTY_DAMAGE")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_PARTY_BUFF")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE")
|
||||
libcast:UnregisterEvent("CHAT_MSG_SPELL_CREATURE_VS_CREATURE_BUFF")
|
||||
supercast.init = true
|
||||
end
|
||||
|
||||
if arg3 == "START" or arg3 == "CAST" or arg3 == "CHANNEL" then
|
||||
local target = arg2
|
||||
local event_type = arg3
|
||||
local spell_id = arg4
|
||||
local timer = arg5
|
||||
|
||||
local spell = C_Spell.GetSpellName(spell_id) or UNKNOWN
|
||||
local icon = C_Spell.GetSpellTexture(spell_id) or "Interface\\Icons\\INV_Misc_QuestionMark"
|
||||
|
||||
-- skip on buff procs during cast
|
||||
if event_type == "CAST" then
|
||||
if not libcast.db[guid] or libcast.db[guid].cast ~= spell then
|
||||
-- ignore casts without 'START' event, while there is already another cast.
|
||||
-- those events can be for example a frost shield proc while casting frostbolt.
|
||||
-- we want to keep the cast itself, so we simply skip those.
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- For player: store in libcast.db[playerName] so pushback tracking works
|
||||
-- For others: store by GUID
|
||||
local dbKey = isPlayer and UnitName("player") or guid
|
||||
|
||||
-- add cast action to the database
|
||||
if not libcast.db[dbKey] then libcast.db[dbKey] = {} end
|
||||
libcast.db[dbKey].cast = spell
|
||||
libcast.db[dbKey].rank = nil
|
||||
libcast.db[dbKey].start = GetTime()
|
||||
libcast.db[dbKey].casttime = timer or 0
|
||||
libcast.db[dbKey].icon = icon
|
||||
libcast.db[dbKey].channel = event_type == "CHANNEL" or false
|
||||
elseif arg3 == "FAIL" then
|
||||
-- For player: use playerName, for others: use GUID
|
||||
local dbKey = isPlayer and UnitName("player") or guid
|
||||
|
||||
-- delete all cast entries
|
||||
if libcast.db[dbKey] then
|
||||
libcast.db[dbKey].cast = nil
|
||||
libcast.db[dbKey].rank = nil
|
||||
libcast.db[dbKey].start = nil
|
||||
libcast.db[dbKey].casttime = nil
|
||||
libcast.db[dbKey].icon = nil
|
||||
libcast.db[dbKey].channel = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
end)
|
||||
+110
-97
@@ -10,6 +10,11 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
local ON_SWING_QUEUED = 0
|
||||
local ON_SWING_QUEUE_POPPED = 1
|
||||
|
||||
-- Spell.dbc bits used to mirror server-side swing-reset rules.
|
||||
local FLAG_AUTOATTACK = tonumber("0x08", 16) -- SPELL_INTERRUPT_FLAG_AUTOATTACK
|
||||
local ATTR_KEEP_SWINGS = tonumber("0x20000", 16) -- SPELL_ATTR_EX2_NOT_RESET_AUTO_ACTIONS
|
||||
local ATTR_ON_NEXT_SWING = tonumber("0x04", 16) -- SPELL_ATTR_ON_NEXT_SWING
|
||||
|
||||
-- Consolidate state into a table to avoid Lua 5.0 upvalue limit (32 max)
|
||||
local S = {
|
||||
mhTimer = 0, mhTimerMax = 1,
|
||||
@@ -27,56 +32,30 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
isDruid = false,
|
||||
cachedHSSlots = {}, cachedCleaveSlots = {},
|
||||
useSpellQueueEvent = false,
|
||||
playerGUID = nil,
|
||||
swingThrottle = 0,
|
||||
onSwingCache = {},
|
||||
}
|
||||
|
||||
-- Ranged spell IDs
|
||||
local RANGED_SPELLIDS = {
|
||||
[75] = true, -- Auto Shot (Hunter)
|
||||
[2764] = true, -- Throw (Warrior/Rogue)
|
||||
}
|
||||
-- Wand "Shoot" runs on the ranged bar but is INDEPENDENT of the melee
|
||||
-- swing clock — casters melee-weave between mainhand swings and wand
|
||||
-- fires, both timers tick concurrently. Every other ranged-auto-attack
|
||||
-- (Hunter Auto Shot, any future auto-repeat ranged spell) replaces MH.
|
||||
local WAND_SHOOT_SPELLID = 5019
|
||||
local THROW_SPELLID = 2764 -- one-shot ranged, not auto-repeat
|
||||
|
||||
-- Spells that DELAY the swing timer by their cast duration but do NOT reset it.
|
||||
-- Slam: vanilla behavior on Turtle WoW - delays swing, does not reset.
|
||||
-- Hammer of Wrath: Turtle WoW changed behavior - does not reset swing timer.
|
||||
local swingDelaySpells = {
|
||||
[1464] = true, [8820] = true, [11604] = true, [11605] = true, -- Slam R1-R4
|
||||
[24275] = true, [24274] = true, [24239] = true, -- Hammer of Wrath R1-R3
|
||||
}
|
||||
|
||||
-- SPELL_ATTR_ON_NEXT_SWING (bit 2, value 4): spell replaces next auto-attack swing.
|
||||
-- ATTR_ON_NEXT_SWING: spell replaces next auto-attack swing.
|
||||
-- Covers Raptor Strike, Maul, Mongoose Bite, Holy Strike, etc. automatically.
|
||||
local ATTR_ON_NEXT_SWING = 4
|
||||
local function IsOnSwingSpell(spellId)
|
||||
if S.onSwingCache[spellId] ~= nil then return S.onSwingCache[spellId] end
|
||||
local rec = GetSpellRec(spellId)
|
||||
local result = rec and bit.band(rec.attributes, ATTR_ON_NEXT_SWING) ~= 0 or false
|
||||
local attr = GetSpellRecField(spellId, "attributes") or 0
|
||||
local result = bit.band(attr, ATTR_ON_NEXT_SWING) ~= 0
|
||||
S.onSwingCache[spellId] = result
|
||||
return result
|
||||
end
|
||||
|
||||
-- Heroic Strike spell IDs (all ranks)
|
||||
local hsSpellIDs = {
|
||||
[78] = true, [284] = true, [285] = true, [1608] = true,
|
||||
[11564] = true, [11565] = true, [11566] = true, [11567] = true,
|
||||
}
|
||||
|
||||
-- Cleave spell IDs (all ranks)
|
||||
local cleaveSpellIDs = {
|
||||
[845] = true, [7369] = true, [11608] = true, [11609] = true,
|
||||
[20569] = true,
|
||||
}
|
||||
|
||||
-- Maul spell IDs (all ranks 1 to 7)
|
||||
local maulSpellIDs = {
|
||||
[6807] = true,
|
||||
[6808] = true,
|
||||
[6809] = true,
|
||||
[8972] = true,
|
||||
[9745] = true, [9880] = true, [9881] = true,
|
||||
}
|
||||
-- HS / Cleave / Maul are detected dynamically via IsOnSwingSpell (the
|
||||
-- SPELL_ATTR_ON_NEXT_SWING bit) + name comparison against the rank-1
|
||||
-- canonical names cached below — no per-rank ID maintenance.
|
||||
|
||||
-- Read config
|
||||
local sw_width = tonumber(C.unitframes.swingtimerwidth) or 200
|
||||
@@ -355,14 +334,17 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
pfUI.swingtimer:Show()
|
||||
end
|
||||
|
||||
-- Reset ranged countdown
|
||||
local function ResetRanged()
|
||||
-- Reset ranged countdown. replaceMH=true (Hunter Auto Shot, Throw) stops
|
||||
-- the melee swing clock while ranged ticks; replaceMH=false (wand Shoot)
|
||||
-- leaves it running for melee weaving.
|
||||
local function ResetRanged(replaceMH)
|
||||
if not sw_showranged then return end
|
||||
UpdateWeaponSpeeds()
|
||||
if S.raSpeed <= 0 then return end
|
||||
-- Ranged replaces MH bar
|
||||
S.mhActive = false
|
||||
pfUI.swingtimer.mainhand:Hide()
|
||||
if replaceMH then
|
||||
S.mhActive = false
|
||||
pfUI.swingtimer.mainhand:Hide()
|
||||
end
|
||||
S.raTimerMax = S.raSpeed
|
||||
S.raTimer = S.raSpeed
|
||||
S.raActive = true
|
||||
@@ -406,11 +388,35 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
pfUI.swingtimer:Hide()
|
||||
end
|
||||
|
||||
-- HS/Cleave helpers. Canonical rank-1 spellIDs resolve to the localized
|
||||
-- spell name once, so the per-slot comparison is locale-independent without
|
||||
-- per-rank hardcoding (every rank of Heroic Strike returns the same name).
|
||||
local HS_NAME = C_Spell.GetSpellName(78) -- Heroic Strike (Rank 1)
|
||||
local CLEAVE_NAME = C_Spell.GetSpellName(845) -- Cleave (Rank 1)
|
||||
-- HS/Cleave/Maul helpers. Canonical rank-1 spellIDs resolve to the
|
||||
-- localized spell name once, so per-spell classification is rank- and
|
||||
-- locale-independent without per-rank ID hardcoding (every rank of
|
||||
-- Heroic Strike etc. returns the same name).
|
||||
local HS_NAME = C_Spell.GetSpellName(78) -- Heroic Strike (Rank 1)
|
||||
local CLEAVE_NAME = C_Spell.GetSpellName(845) -- Cleave (Rank 1)
|
||||
local MAUL_NAME = C_Spell.GetSpellName(6807) -- Maul (Rank 1)
|
||||
|
||||
-- Classify an on-next-swing spell as "hs" / "cleave" / "maul" / nil.
|
||||
-- IsOnSwingSpell gates the family (ATTR_ON_NEXT_SWING bit 0x04) so
|
||||
-- unrelated spells return nil cheaply.
|
||||
local function ClassifyOnSwingSpell(spellId)
|
||||
if not IsOnSwingSpell(spellId) then return nil end
|
||||
local name = C_Spell.GetSpellName(spellId)
|
||||
if name == HS_NAME then return "hs"
|
||||
elseif name == CLEAVE_NAME then return "cleave"
|
||||
elseif name == MAUL_NAME then return "maul"
|
||||
end
|
||||
end
|
||||
|
||||
-- Set the queue flags from a ClassifyOnSwingSpell result. nil = no-op
|
||||
-- (preserves prior flag state when the queued spell isn't one we
|
||||
-- color-code, matching the legacy table-lookup behavior).
|
||||
local function SetQueuedKind(kind)
|
||||
if not kind then return end
|
||||
S.hsQueued = (kind == "hs")
|
||||
S.cleaveQueued = (kind == "cleave")
|
||||
S.maulQueued = (kind == "maul")
|
||||
end
|
||||
|
||||
local function RebuildQueueSlotCache()
|
||||
if not S.isWarrior or not sw_hsqueue or S.useSpellQueueEvent then return end
|
||||
@@ -694,11 +700,17 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
local spellStartFrame = CreateFrame("Frame")
|
||||
spellStartFrame:RegisterEvent("SPELL_START_SELF")
|
||||
spellStartFrame:SetScript("OnEvent", function()
|
||||
if arg1 and arg1 > 0 then
|
||||
S.pendingCastSpellId = arg1
|
||||
-- Slam (and other delay-only spells): freeze the swing timer during cast
|
||||
-- so it pauses instead of ticking down and expiring mid-cast
|
||||
if swingDelaySpells[arg1] and S.mhActive then
|
||||
if not (arg1 and arg1 > 0) then return end
|
||||
S.pendingCastSpellId = arg1
|
||||
-- Freeze the swing timer for cast-time spells that DON'T reset auto-
|
||||
-- attack on completion (Slam, Hammer of Wrath on Turtle, etc.) — those
|
||||
-- let the swing resume from where it paused. Detect dynamically via the
|
||||
-- absent AUTOATTACK interrupt flag (8); spells with that bit reset on
|
||||
-- SPELL_GO_SELF so freezing isn't necessary. Subsumes the old hardcoded
|
||||
-- swingDelaySpells list (no list maintenance for new Slam-style spells).
|
||||
if S.mhActive then
|
||||
local iflags = GetSpellRecField(arg1, "interruptFlags") or 0
|
||||
if bit.band(iflags, FLAG_AUTOATTACK) == 0 then
|
||||
S.mhFrozenAt = GetTime()
|
||||
end
|
||||
end
|
||||
@@ -718,52 +730,60 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
-- SPELL_GO hook via libdebuff
|
||||
pfUI.libdebuff_spell_go_hooks = pfUI.libdebuff_spell_go_hooks or {}
|
||||
pfUI.libdebuff_spell_go_hooks["swingtimer"] = function(spellId)
|
||||
if RANGED_SPELLIDS[spellId] then
|
||||
ResetRanged()
|
||||
-- C_Spell.IsRangedAutoAttackSpell catches both Auto Shot (75) and
|
||||
-- wand Shoot (5019) via Spell.dbc's AUTO_REPEAT attribute (covers
|
||||
-- any future auto-repeat ranged spell automatically). Wand is the
|
||||
-- one independent of the MH swing — everything else replaces it.
|
||||
-- Throw isn't auto-repeat (single-shot) so it's handled explicitly.
|
||||
if C_Spell.IsRangedAutoAttackSpell(spellId) then
|
||||
ResetRanged(spellId ~= WAND_SHOOT_SPELLID)
|
||||
return
|
||||
elseif swingDelaySpells[spellId] then
|
||||
-- Swing-delay spells (Slam, Hammer of Wrath on Turtle WoW):
|
||||
-- Delay the swing timer by cast duration, do NOT reset it.
|
||||
if S.mhFrozenAt then
|
||||
local castDuration = GetTime() - S.mhFrozenAt
|
||||
S.mhTimer = S.mhTimer + castDuration
|
||||
S.mhTimerMax = S.mhTimerMax + castDuration
|
||||
S.mhFrozenAt = nil
|
||||
end
|
||||
S.pendingCastSpellId = nil
|
||||
elseif spellId == THROW_SPELLID then
|
||||
ResetRanged(true)
|
||||
return
|
||||
elseif hsSpellIDs[spellId] or cleaveSpellIDs[spellId] or maulSpellIDs[spellId] or IsOnSwingSpell(spellId) then
|
||||
S.hsQueued = false; S.cleaveQueued = false
|
||||
S.maulQueued = false
|
||||
ResetMH()
|
||||
elseif cleaveSpellIDs[spellId] then
|
||||
S.hsQueued = false; S.cleaveQueued = false
|
||||
elseif IsOnSwingSpell(spellId) then
|
||||
-- On-next-swing ability (HS / Cleave / Maul / Raptor Strike / etc.)
|
||||
-- — the swing fires as the spell consumes it. Drop the queued color.
|
||||
S.hsQueued = false; S.cleaveQueued = false; S.maulQueued = false
|
||||
ResetMH()
|
||||
else
|
||||
-- Any spell with interruptFlags > 0 resets the swing timer
|
||||
-- (Moonfire, Faerie Fire, Wrath, Starfire etc. - NOT Insect Swarm which has flags=0)
|
||||
local _rec = GetSpellRec(spellId)
|
||||
if _rec and _rec.interruptFlags and _rec.interruptFlags > 0 then
|
||||
-- Mirror the server rule for "does this spell reset the auto-attack
|
||||
-- swing" (Spell::IsMeleeAttackResetSpell in Turtle's core):
|
||||
-- InterruptFlags has SPELL_INTERRUPT_FLAG_AUTOATTACK (0x08)
|
||||
-- AND AttributesEx2 lacks NOT_RESET_AUTO_ACTIONS (0x20000).
|
||||
-- If neither path resets and we're holding a frozen-swing-during-cast
|
||||
-- (mhFrozenAt set by SPELL_START_SELF for non-AUTOATTACK spells), this
|
||||
-- is a Slam-style cast — push the timer forward by the cast duration
|
||||
-- so the bar resumes from where it paused.
|
||||
local iflags = GetSpellRecField(spellId, "interruptFlags") or 0
|
||||
if bit.band(iflags, FLAG_AUTOATTACK) ~= 0
|
||||
and bit.band(GetSpellRecField(spellId, "attributesEx2") or 0, ATTR_KEEP_SWINGS) == 0 then
|
||||
if S.mhActive and S.mhSpeed > 0 then
|
||||
UpdateWeaponSpeeds()
|
||||
S.mhTimerMax = S.mhSpeed
|
||||
S.mhTimer = S.mhSpeed
|
||||
end
|
||||
if S.ohActive and S.ohSpeed > 0 then
|
||||
S.ohTimerMax = S.ohSpeed
|
||||
S.ohTimer = S.ohSpeed
|
||||
end
|
||||
elseif S.mhFrozenAt then
|
||||
local castDuration = GetTime() - S.mhFrozenAt
|
||||
S.mhTimer = S.mhTimer + castDuration
|
||||
S.mhTimerMax = S.mhTimerMax + castDuration
|
||||
S.mhFrozenAt = nil
|
||||
end
|
||||
end
|
||||
S.pendingCastSpellId = nil
|
||||
end
|
||||
|
||||
-- SPELL_CAST_EVENT hook: HS/Cleave queue tracking
|
||||
-- SPELL_CAST_EVENT hook: HS/Cleave/Maul queue tracking
|
||||
pfUI.libdebuff_spell_cast_hooks = pfUI.libdebuff_spell_cast_hooks or {}
|
||||
pfUI.libdebuff_spell_cast_hooks["swingtimer"] = function(success, spellId)
|
||||
if hsSpellIDs[spellId] then
|
||||
S.hsQueued = true; S.cleaveQueued = false; S.maulQueued = false
|
||||
elseif cleaveSpellIDs[spellId] then
|
||||
S.cleaveQueued = true; S.hsQueued = false; S.maulQueued = false
|
||||
elseif maulSpellIDs[spellId] then
|
||||
S.maulQueued = true; S.hsQueued = false; S.cleaveQueued = false
|
||||
end
|
||||
local kind = ClassifyOnSwingSpell(spellId)
|
||||
if not kind then return end
|
||||
S.useSpellQueueEvent = true
|
||||
SetQueuedKind(kind)
|
||||
end
|
||||
|
||||
|
||||
@@ -771,7 +791,7 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
events:RegisterEvent("AUTO_ATTACK_SELF")
|
||||
events:RegisterEvent("AUTO_ATTACK_OTHER")
|
||||
events:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
events:RegisterEvent("UNIT_INVENTORY_CHANGED")
|
||||
events:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") -- ClassicAPI: per-slot, equipment-only
|
||||
events:RegisterEvent("PLAYER_REGEN_DISABLED")
|
||||
events:RegisterEvent("PLAYER_REGEN_ENABLED")
|
||||
events:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
|
||||
@@ -814,8 +834,7 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
elseif event == "AUTO_ATTACK_OTHER" then
|
||||
-- Parry haste: enemy attacked the player and player parried
|
||||
local targetGuid = arg2
|
||||
if not targetGuid or not S.playerGUID then return end
|
||||
if targetGuid ~= S.playerGUID then return end
|
||||
if not targetGuid or not IsPlayerGuid(targetGuid) then return end
|
||||
local victimState = arg5 or 0
|
||||
-- VICTIMSTATE_PARRY = 3
|
||||
-- Vanilla: parry reduces the NEXT swing timer by 40% of weapon speed,
|
||||
@@ -847,13 +866,7 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
local spellId = arg2 or 0
|
||||
if eventCode == ON_SWING_QUEUED then
|
||||
S.useSpellQueueEvent = true
|
||||
if hsSpellIDs[spellId] then
|
||||
S.hsQueued = true; S.cleaveQueued = false; S.maulQueued = false
|
||||
elseif cleaveSpellIDs[spellId] then
|
||||
S.cleaveQueued = true; S.hsQueued = false; S.maulQueued = false
|
||||
elseif maulSpellIDs[spellId] then
|
||||
S.maulQueued = true; S.hsQueued = false; S.cleaveQueued = false
|
||||
end
|
||||
SetQueuedKind(ClassifyOnSwingSpell(spellId))
|
||||
elseif eventCode == ON_SWING_QUEUE_POPPED then
|
||||
S.hsQueued = false; S.cleaveQueued = false; S.maulQueued = false
|
||||
end
|
||||
@@ -868,12 +881,12 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
local _, class = UnitClass("player")
|
||||
S.isWarrior = (class == "WARRIOR")
|
||||
S.isDruid = (class == "DRUID")
|
||||
S.playerGUID = UnitGUID("player")
|
||||
UpdateWeaponSpeeds()
|
||||
RebuildQueueSlotCache()
|
||||
|
||||
elseif event == "UNIT_INVENTORY_CHANGED" then
|
||||
if arg1 and arg1 ~= "player" then return end
|
||||
elseif event == "PLAYER_EQUIPMENT_CHANGED" then
|
||||
-- arg1 = changed slot; only weapon slots (main/off/ranged) affect swing speed
|
||||
if not INVSLOTS_EQUIPABLE_IN_COMBAT[arg1] then return end
|
||||
UpdateWeaponSpeeds()
|
||||
if S.ohSpeed == 0 then
|
||||
S.ohActive = false
|
||||
@@ -898,7 +911,7 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
S.maulQueued = false
|
||||
|
||||
elseif event == "UNIT_DIED" then
|
||||
if arg1 and arg1 == S.playerGUID then
|
||||
if IsPlayerGuid(arg1) then
|
||||
ResetAll()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -267,7 +267,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
|
||||
SW_BarFrame1_Selector:Hide()
|
||||
|
||||
-- let user select mode by clicking the title
|
||||
HookScript(SW_BarFrame1_Title, "OnMouseUp", function()
|
||||
SW_BarFrame1_Title:HookScript("OnMouseUp", function()
|
||||
local page = SW_Settings and SW_Settings.BarFrames and SW_Settings.BarFrames.SW_BarFrame1 and SW_Settings.BarFrames.SW_BarFrame1.Selected
|
||||
if page then
|
||||
local target = (arg1 == "LeftButton") and (page + 1) or (arg1 == "RightButton") and (page - 1)
|
||||
@@ -530,7 +530,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
|
||||
end
|
||||
|
||||
-- replace wim class colors with pfUI ones
|
||||
hooksecurefunc("WIM_InitClassProps", function()
|
||||
pfUI.hooksecurefunc("WIM_InitClassProps", function()
|
||||
for class in pairs(PFUI_CLASS_COLORS) do
|
||||
local wimclass = _G[format("WIM_LOCALIZED_%s",class)]
|
||||
local colorstr = "|c" .. PFUI_CLASS_COLORS[class].colorStr
|
||||
@@ -547,7 +547,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
|
||||
btnClose:SetWidth(13)
|
||||
btnClose:SetHeight(13)
|
||||
end
|
||||
hooksecurefunc("WIM_Icon_DropDown_Update", function()
|
||||
pfUI.hooksecurefunc("WIM_Icon_DropDown_Update", function()
|
||||
for i=1,_G.WIM_MaxMenuCount do
|
||||
local btn = _G["WIM_ConversationMenuTellButton"..i]
|
||||
if i==1 and btn:IsEnabled() == 0 then return end
|
||||
@@ -729,7 +729,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
|
||||
SkinScrollbar(WIM_HelpScrollFrameScrollBar)
|
||||
end
|
||||
|
||||
hooksecurefunc("WIM_WindowOnShow", function()
|
||||
pfUI.hooksecurefunc("WIM_WindowOnShow", function()
|
||||
if this.backdrop then return end -- already skinned
|
||||
|
||||
local windowname = this:GetName()
|
||||
@@ -904,7 +904,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
|
||||
end
|
||||
|
||||
-- trigger the event whenever SuperMacro got an update
|
||||
hooksecurefunc("SM_UpdateActionSpell", function()
|
||||
pfUI.hooksecurefunc("SM_UpdateActionSpell", function()
|
||||
for slot=1,120 do pfUI.bars.update[slot] = true end
|
||||
end)
|
||||
end)
|
||||
@@ -922,7 +922,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
|
||||
pfUI.bars.skip_macro = true
|
||||
|
||||
-- send clevermacro events to pfUI actionbars
|
||||
hooksecurefunc("ActionButton_OnEvent", function(event)
|
||||
pfUI.hooksecurefunc("ActionButton_OnEvent", function(event)
|
||||
events(this, event)
|
||||
end)
|
||||
end)
|
||||
@@ -943,8 +943,8 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
|
||||
CreateBackdropShadow(AtlasLootTooltip)
|
||||
|
||||
if pfUI.eqcompare then
|
||||
HookScript(AtlasLootTooltip, "OnShow", pfUI.eqcompare.GameTooltipShow)
|
||||
HookScript(AtlasLootTooltip, "OnHide", function()
|
||||
pfUI.eqcompare.HookTooltip(AtlasLootTooltip)
|
||||
AtlasLootTooltip:HookScript("OnHide", function()
|
||||
ShoppingTooltip1:Hide()
|
||||
ShoppingTooltip2:Hide()
|
||||
end)
|
||||
@@ -1182,7 +1182,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
|
||||
-- use pfUI frames to draw healComm predictions
|
||||
local pfHookHealCommSendAddonMessage = HealComm.SendAddonMessage
|
||||
function HealComm.SendAddonMessage(this, msg)
|
||||
if not UnitInRaid("player") and GetNumPartyMembers() < 1 then
|
||||
if not IsInGroup() then
|
||||
libpredict:ParseChatMessage(UnitName("player"), msg, "HealComm")
|
||||
end
|
||||
pfHookHealCommSendAddonMessage(this, msg)
|
||||
|
||||
+8
-8
@@ -146,14 +146,6 @@ pfUI:RegisterModule("tooltip", function ()
|
||||
rhp, rhpmax = hp, hpmax
|
||||
elseif pfUI.libhealth and pfUI.libhealth.enabled then
|
||||
rhp, rhpmax, estimated = pfUI.libhealth:GetUnitHealthByName(this.name, this.level, tonumber(hp), tonumber(hpmax))
|
||||
elseif MobHealthFrame then
|
||||
local index = (this.name or "") .. ":" .. (this.level or "")
|
||||
local ppp = MobHealth_PPP(index)
|
||||
if perc and ppp and ppp > 0 and not UnitIsUnit("mouseover", "pet") then
|
||||
rhp = round(hp * ppp)
|
||||
rhpmax = round(100 * ppp)
|
||||
estimated = true
|
||||
end
|
||||
end
|
||||
|
||||
if C.tooltip.alwaysperc == "0" and ( estimated or hpmax > 100 or round(hpmax/100*hp) ~= hp ) then
|
||||
@@ -241,6 +233,14 @@ pfUI:RegisterModule("tooltip", function ()
|
||||
end
|
||||
end
|
||||
|
||||
if C.tooltip.movespeed == "1" then
|
||||
local currentSpeed = GetUnitSpeed(unit)
|
||||
if currentSpeed and currentSpeed > 0 then
|
||||
local pct = floor(currentSpeed / 7 * 100 + 0.5)
|
||||
GameTooltip:AddLine(T["Speed"] .. ": " .. pct .. "%", 0.7, 0.7, 1)
|
||||
end
|
||||
end
|
||||
|
||||
if hp and hpm then
|
||||
if hp >= 1000 then hp = round(hp / 1000, 1) .. "k" end
|
||||
if hpm >= 1000 then hpm = round(hpm / 1000, 1) .. "k" end
|
||||
|
||||
+37
-913
@@ -1,5 +1,5 @@
|
||||
-- skip module initialization on every other client than turtle-wow
|
||||
if not TargetHPText or not TargetHPPercText then return end
|
||||
if not TURTLE_WOW_VERSION then return end
|
||||
|
||||
pfUI:RegisterModule("turtle-wow", function ()
|
||||
-- Manage Turtle WoW's GroupUI (Turtle_GroupUI addon) vs pfUI frames.
|
||||
@@ -47,7 +47,7 @@ pfUI:RegisterModule("turtle-wow", function ()
|
||||
-- Check if pfUI is handling group or raid for the current situation
|
||||
local function pfUIHandlesGroupOrRaid()
|
||||
local disabled = C["disabled"] or {}
|
||||
if GetNumRaidMembers() > 0 then
|
||||
if IsInRaid() then
|
||||
if not C.unitframes.raid then return false end
|
||||
-- Hide Turtle frames if: pfUI raid module is active, OR player disabled visibility
|
||||
if disabled["raid"] == "1" then
|
||||
@@ -65,14 +65,14 @@ pfUI:RegisterModule("turtle-wow", function ()
|
||||
|
||||
HookAddonOrVariable("GroupFrame", function()
|
||||
-- After Turtle's own init, hide frames if pfUI handles them
|
||||
hooksecurefunc("GroupFrame_Toggle", function()
|
||||
pfUI.hooksecurefunc("GroupFrame_Toggle", function()
|
||||
if pfUIHandlesGroupOrRaid() then
|
||||
DisableTurtleGroupFrames()
|
||||
end
|
||||
end)
|
||||
|
||||
-- After every group/raid update, re-hide if pfUI handles them
|
||||
hooksecurefunc("GroupFrame_Update", function()
|
||||
pfUI.hooksecurefunc("GroupFrame_Update", function()
|
||||
if pfUIHandlesGroupOrRaid() then
|
||||
DisableTurtleGroupFrames()
|
||||
end
|
||||
@@ -86,64 +86,6 @@ pfUI:RegisterModule("turtle-wow", function ()
|
||||
L["debuffs"]['Moonfire'] = {[1]=9.0,[2]=18.0,[3]=18.0,[4]=18.0,[5]=18.0,[6]=18.0,[7]=18.0,[8]=18.0,[9]=18.0,[10]=18.0,[0]=18.0}
|
||||
L["debuffs"]['Deep Wound'] = {[0]=6.0}
|
||||
|
||||
-- add custom spell logic to libdebuff
|
||||
HookScript(libdebuff, "OnEvent", function()
|
||||
if event == "CHAT_MSG_SPELL_SELF_DAMAGE" then
|
||||
-- refresh paladin judgements on holy strike
|
||||
-- taken from: https://github.com/doorknob6/pfUI-turtle/blob/master/modules/debuffs.lua
|
||||
local holystrike = string.find(string.sub(arg1,6,17), "Holy Strike")
|
||||
--arg2 is spell dmg when it hits, nil when it misses
|
||||
if holystrike and arg2 then
|
||||
for seal in L["judgements"] do
|
||||
local name = UnitName("target")
|
||||
local level = UnitLevel("target")
|
||||
if name and libdebuff.objects[name] then
|
||||
if level and
|
||||
libdebuff.objects[name][level] and
|
||||
libdebuff.objects[name][level][seal] then
|
||||
libdebuff:AddEffect(name, level, seal)
|
||||
elseif libdebuff.objects[name][0] and
|
||||
libdebuff.objects[name][0][seal] then
|
||||
libdebuff:AddEffect(name, 0, seal)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- refresh rip and rake duration on ferocious bite (Turtle WoW feature)
|
||||
-- Only refresh if Ferocious Bite actually hit (not missed/dodged/parried/etc.)
|
||||
local match = string.find(arg1, "Ferocious Bite")
|
||||
if match and arg2 and not libdebuff:DidSpellFail("Ferocious Bite") then
|
||||
local name = UnitName("target")
|
||||
local level = UnitLevel("target")
|
||||
|
||||
-- Refresh Rip mit existierender Duration
|
||||
if libdebuff.objects[name] and libdebuff.objects[name][level] and libdebuff.objects[name][level]["Rip"] then
|
||||
local existingDuration = libdebuff.objects[name][level]["Rip"].duration
|
||||
libdebuff:AddEffect(name, level, "Rip", existingDuration)
|
||||
end
|
||||
|
||||
-- Refresh Rake mit existierender Duration
|
||||
if libdebuff.objects[name] and libdebuff.objects[name][level] and libdebuff.objects[name][level]["Rake"] then
|
||||
local existingDuration = libdebuff.objects[name][level]["Rake"].duration
|
||||
libdebuff:AddEffect(name, level, "Rake", existingDuration)
|
||||
end
|
||||
end
|
||||
|
||||
-- refresh Immolate duration after cast Conflagrate
|
||||
-- Only refresh if Conflagrate actually hit
|
||||
local conflagrate = string.find(string.sub(arg1,6,17), "Conflagrate")
|
||||
if conflagrate and arg2 and not libdebuff:DidSpellFail("Conflagrate") then
|
||||
local name = UnitName("target")
|
||||
local level = UnitLevel("target")
|
||||
if libdebuff.objects[name] and libdebuff.objects[name][level] and libdebuff.objects[name][level]["Immolate"] then
|
||||
local duration = libdebuff.objects[name][level]["Immolate"].duration
|
||||
libdebuff:UpdateDuration(name, level, "Immolate", duration - 3)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- turtle wow totemic recall clear totem indicators
|
||||
local _, class = UnitClass("player")
|
||||
if libtotem and class == "SHAMAN" then
|
||||
@@ -312,49 +254,48 @@ pfUI:RegisterModule("turtle-wow", function ()
|
||||
end
|
||||
end)
|
||||
|
||||
-- add Trueshot recognition to to custom castbars.
|
||||
if not libcast.customcast["steadyshot"] then
|
||||
-- add trueshot to pfUI's custom casts
|
||||
local player = UnitName("player")
|
||||
-- Synthetic Steady Shot cast bar. The engine treats Steady Shot as
|
||||
-- instant, but the arrow doesn't actually fire until the next ranged
|
||||
-- swing (~1.4s baseline). Drive a synthetic entry off Nampower's
|
||||
-- SPELL_QUEUE_EVENT so hunters see a tickdown matching the swing wait.
|
||||
-- castbar.lua reads pfUI.synthetic_casts[unit] as a fallback when
|
||||
-- C_Spell.UnitCastingInfo returns nil.
|
||||
pfUI_locale["enUS"]["customcast"]["TRUESHOT"] = "Steady Shot"
|
||||
pfUI_locale["zhCN"]["customcast"]["TRUESHOT"] = "稳固射击"
|
||||
pfUI.synthetic_casts = pfUI.synthetic_casts or {}
|
||||
|
||||
-- add locales
|
||||
pfUI_locale["enUS"]["customcast"]["TRUESHOT"] = "Steady Shot"
|
||||
pfUI_locale["zhCN"]["customcast"]["TRUESHOT"] = "稳固射击"
|
||||
local trueshot = L["customcast"]["TRUESHOT"] or ""
|
||||
local steadyShotName = L["customcast"]["TRUESHOT"]
|
||||
local STEADY_SHOT_ICON = "Interface\\Icons\\Ability_hunter_steadyshot"
|
||||
local STEADY_SHOT_DURATION_MS = 1400 -- not haste-scaled; close enough for the visual cue
|
||||
local ON_SWING_QUEUED, ON_SWING_QUEUE_POPPED = 0, 1
|
||||
|
||||
libcast.customcast[strlower(trueshot)] = function(begin, duration)
|
||||
if begin then
|
||||
-- cast time is 1sec, however it takes 1.4sec to fire in average
|
||||
local duration = libcast.ApplyShotHaste(duration or 1400)
|
||||
|
||||
local _,_, lag = GetNetStats()
|
||||
local start = GetTime() + lag/1000
|
||||
|
||||
-- add cast action to the database
|
||||
libcast.db[player].cast = trueshot
|
||||
libcast.db[player].rank = lastrank
|
||||
libcast.db[player].start = start
|
||||
libcast.db[player].casttime = duration
|
||||
libcast.db[player].icon = "Interface\\Icons\\Ability_hunter_steadyshot"
|
||||
libcast.db[player].channel = nil
|
||||
else
|
||||
-- remove cast action to the database
|
||||
libcast.db[player].cast = nil
|
||||
libcast.db[player].rank = nil
|
||||
libcast.db[player].start = nil
|
||||
libcast.db[player].casttime = nil
|
||||
libcast.db[player].icon = nil
|
||||
libcast.db[player].channel = nil
|
||||
end
|
||||
local steadyShot = CreateFrame("Frame")
|
||||
steadyShot:RegisterEvent("SPELL_QUEUE_EVENT")
|
||||
steadyShot:SetScript("OnEvent", function()
|
||||
if event ~= "SPELL_QUEUE_EVENT" then return end
|
||||
local eventCode, spellId = arg1, arg2
|
||||
if eventCode ~= ON_SWING_QUEUED and eventCode ~= ON_SWING_QUEUE_POPPED then return end
|
||||
if C_Spell.GetSpellName(spellId) ~= steadyShotName then return end
|
||||
if eventCode == ON_SWING_QUEUED then
|
||||
local now = GetTime() * 1000
|
||||
pfUI.synthetic_casts["player"] = {
|
||||
name = steadyShotName,
|
||||
icon = STEADY_SHOT_ICON,
|
||||
startMs = now,
|
||||
endMs = now + STEADY_SHOT_DURATION_MS,
|
||||
spellID = spellId,
|
||||
}
|
||||
else
|
||||
pfUI.synthetic_casts["player"] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- add skin to twow's talent inspect frame
|
||||
if pfUI.skin["Inspect"] and pfUI_config["disabled"]["skin_Inspect"] ~= "1" then
|
||||
local initialized = false
|
||||
|
||||
HookAddonOrVariable("Blizzard_InspectUI", function()
|
||||
hooksecurefunc("InspectFrame_Show", function()
|
||||
pfUI.hooksecurefunc("InspectFrame_Show", function()
|
||||
-- break if theres nothing left to do
|
||||
if initialized then return end
|
||||
|
||||
@@ -414,823 +355,6 @@ pfUI:RegisterModule("turtle-wow", function ()
|
||||
end
|
||||
end)
|
||||
|
||||
-- add turtle-wow overlay values
|
||||
-- coordinates from paokkerkir/ShaguTweaks-extras, measured for TurtleWoW 1.18.1
|
||||
pfMapOverlayData = {
|
||||
["Durotar"] = {
|
||||
"DRYGULCHRAVINE:139:134:438:92",
|
||||
"ECHOISLES:175:236:561:432",
|
||||
"KOLKARCRAG:131:96:426:487",
|
||||
"ORGRIMMAR:427:150:251:0",
|
||||
"RAZORHILL:188:207:445:182",
|
||||
"RAZORMANEGROUNDS:196:202:318:204",
|
||||
"SENJINVILLAGE:133:167:485:398",
|
||||
"SKULLROCK:118:79:469:51",
|
||||
"SparkwaterPort:122:134:522:94",
|
||||
"THUNDERRIDGE:156:174:341:72",
|
||||
"TIRAGARDEKEEP:160:148:477:300",
|
||||
"VALLEYOFTRIALS:186:184:370:335",
|
||||
},
|
||||
["Mulgore"] = {
|
||||
"BAELDUNDIGSITE:175:151:269:227",
|
||||
"BLOODHOOFVILLAGE:236:182:378:311",
|
||||
"PALEMANEROCK:102:183:315:317",
|
||||
"RAVAGEDCARAVAN:110:100:482:269",
|
||||
"REDCLOUDMESA:436:231:285:437",
|
||||
"REDROCKS:154:138:522:92",
|
||||
"SuntailPass:129:116:533:18",
|
||||
"THEGOLDENPLAINS:184:217:441:91",
|
||||
"THEROLLINGPLAINS:231:166:536:369",
|
||||
"THEVENTURECOMINE:187:204:549:251",
|
||||
"THUNDERBLUFF:254:217:264:70",
|
||||
"THUNDERHORNWATERWELL:118:137:384:248",
|
||||
"WILDMANEWATERWELL:152:111:299:10",
|
||||
"WINDFURYRIDGE:182:119:405:2",
|
||||
"WINTERHOOFWATERWELL:144:107:470:379",
|
||||
},
|
||||
["Barrens"] = {
|
||||
"AGAMAGOR:183:173:348:242",
|
||||
"AnchorsEdge:118:83:690:300",
|
||||
"BAELMODAN:120:123:435:482",
|
||||
"BLACKTHORNRIDGE:140:123:340:465",
|
||||
"BOULDERLODEMINE:109:105:560:0",
|
||||
"BRAMBLESCAR:112:152:446:304",
|
||||
"CAMPTAURAJO:130:113:370:355",
|
||||
"DREADMISTPEAK:121:93:423:68",
|
||||
"FARWATCHPOST:85:152:572:58",
|
||||
"FIELDOFGIANTS:194:137:363:410",
|
||||
"GROLDOMFARM:112:107:499:67",
|
||||
"HONORSSTAND:128:128:306:130",
|
||||
"LUSHWATEROASIS:160:169:372:186",
|
||||
"NORTHWATCHFOLD:134:107:533:310",
|
||||
"RAPTORGROUNDS:101:99:512:299",
|
||||
"RATCHET:116:118:556:190",
|
||||
"RAZORFENDOWNS:137:106:414:562",
|
||||
"RAZORFENKRAUL:116:123:345:539",
|
||||
"THECROSSROADS:142:140:436:127",
|
||||
"THEDRYHILLS:185:134:324:36",
|
||||
"THEFORGOTTENPOOLS:104:116:391:120",
|
||||
"THEMERCHANTCOAST:80:123:584:249",
|
||||
"THEMORSHANRAMPART:118:91:418:0",
|
||||
"THESLUDGEFEN:153:113:463:0",
|
||||
"THESTAGNANTOASIS:147:126:484:211",
|
||||
"THORNHILL:128:120:502:123",
|
||||
},
|
||||
["Alterac"] = {
|
||||
"CHILLWINDPOINT:308:250:660:261",
|
||||
"CORRAHNSDAGGER:173:277:411:391",
|
||||
"CRUSHRIDGEHOLD:269:230:339:167",
|
||||
"DALARAN:277:283:39:275",
|
||||
"DANDREDSFOLD:273:224:282:0",
|
||||
"GALLOWSCORNER:183:185:415:287",
|
||||
"GAVINSNAZE:140:161:235:486",
|
||||
"GROWLESSCAVE:175:156:327:382",
|
||||
"LORDAMEREINTERNMENTCAMP:315:255:55:413",
|
||||
"MISTYSHORE:204:268:207:139",
|
||||
"RUINSOFALTERAC:252:249:274:201",
|
||||
"RavenholdtManor:217:165:737:452",
|
||||
"SOFERASNAZE:241:312:469:311",
|
||||
"STRAHNBRAD:355:289:555:110",
|
||||
"THEHEADLAND:142:187:325:481",
|
||||
"THEUPLANDS:219:183:470:84",
|
||||
},
|
||||
["Arathi"] = {
|
||||
"BOULDERFISTHALL:190:214:442:371",
|
||||
"BOULDERGOR:222:214:244:167",
|
||||
"CIRCLEOFEASTBINDING:140:213:568:125",
|
||||
"CIRCLEOFINNERBINDING:186:164:302:321",
|
||||
"CIRCLEOFOUTERBINDING:149:137:429:301",
|
||||
"CIRCLEOFWESTBINDING:156:189:152:71",
|
||||
"DABYRIESFARMSTEAD:160:171:482:197",
|
||||
"FALDIRSCOVE:247:197:180:428",
|
||||
"FarwellStead:112:128:267:31",
|
||||
"GOSHEKFARM:208:177:539:284",
|
||||
"HAMMERFALL:182:235:665:128",
|
||||
"NORTHFOLDMANOR:202:205:204:119",
|
||||
"REFUGEPOINT:157:189:380:214",
|
||||
"RuinsOfZulRasaz:160:160:346:29",
|
||||
"STROMGARDEKEEP:220:213:122:295",
|
||||
"THANDOLSPAN:175:198:369:424",
|
||||
"THORADINSWALL:173:225:103:148",
|
||||
"WITHERBARKVILLAGE:197:194:566:339",
|
||||
"WildtuskVillage:183:102:379:145",
|
||||
},
|
||||
["Badlands"] = {
|
||||
"AGMONDSEND:249:256:354:396",
|
||||
"ANGORFORTRESS:186:241:327:101",
|
||||
"APOCRYPHANSREST:251:195:20:315",
|
||||
"CAMPBOFF:195:207:506:361",
|
||||
"CAMPCAGG:247:205:15:432",
|
||||
"CAMPKOSH:214:211:556:56",
|
||||
"CrystallinePinnacle:140:155:609:337",
|
||||
"DUSTWINDGULCH:229:190:506:216",
|
||||
"HAMMERTOESDIGSITE:185:177:455:131",
|
||||
"KARGATH:232:245:0:154",
|
||||
"LETHLORRAVINE:344:395:627:173",
|
||||
"MIRAGEFLATS:276:229:153:387",
|
||||
"RedbrandsDigsite:325:155:496:454",
|
||||
"RuinsOfCorthan:210:200:203:124",
|
||||
"ScalebaneRidge:205:179:751:142",
|
||||
"THEDUSTBOWL:253:266:166:203",
|
||||
"THEMAKERSTERRACE:230:172:399:16",
|
||||
"VALLEYOFFANGS:215:215:358:265",
|
||||
},
|
||||
["BlastedLands"] = {
|
||||
"ALTAROFSTORMS:169:142:318:141",
|
||||
"DARKPORTAL:254:208:460:265",
|
||||
"DREADMAULHOLD:200:171:351:21",
|
||||
"DREADMAULPOST:228:183:370:204",
|
||||
"GARRISONARMORY:155:192:480:15",
|
||||
"NETHERGARDEKEEP:169:178:567:35",
|
||||
"RISEOFTHEDEFILER:150:131:414:131",
|
||||
"SERPENTSCOIL:211:155:510:148",
|
||||
"THETAINTEDSCAR:370:438:220:184",
|
||||
},
|
||||
["Tirisfal"] = {
|
||||
"AGAMANDMILLS:244:191:342:148",
|
||||
"BALNIRFARMSTEAD:179:153:639:335",
|
||||
"BRIGHTWATERLAKE:176:280:598:143",
|
||||
"BRILL:121:144:540:305",
|
||||
"BULWARK:184:169:712:374",
|
||||
"COLDHEARTHMANOR:136:125:479:328",
|
||||
"CRUSADEROUTPOST:154:121:701:293",
|
||||
"DEATHKNELL:210:186:236:337",
|
||||
"GARRENSHAUNT:148:199:507:154",
|
||||
"GLENSHIRE:163:161:157:393",
|
||||
"MONASTARY:175:157:762:139",
|
||||
"NIGHTMAREVALE:209:151:370:355",
|
||||
"RUINSOFLORDAERON:299:216:469:370",
|
||||
"SCARLETWATCHPOST:141:223:701:113",
|
||||
"SOLLIDENFARMSTEAD:225:138:247:259",
|
||||
"STEEPCLIFFPORT:121:120:5:350",
|
||||
"STILLWATERPOND:168:123:401:279",
|
||||
"THECORINTHFARMSTEAD:116:152:73:402",
|
||||
"THEWHISPERINGFOREST:243:175:86:263",
|
||||
"VENOMWEBVALE:199:183:776:225",
|
||||
},
|
||||
["Silverpine"] = {
|
||||
"AMBERMILL:204:210:512:279",
|
||||
"BERENSPERIL:206:146:508:433",
|
||||
"DEEPELEMMINE:127:143:484:274",
|
||||
"FENRISISLE:234:187:593:88",
|
||||
"MALDENSORCHARD:228:138:479:9",
|
||||
"NORTHTIDESHOLLOW:152:104:337:141",
|
||||
"OLSENSFARTHING:128:152:401:267",
|
||||
"PYREWOODVILLAGE:122:108:405:456",
|
||||
"SHADOWFANGKEEP:185:131:380:373",
|
||||
"THEDEADFIELD:141:134:419:82",
|
||||
"THEDECREPITFERRY:144:156:475:160",
|
||||
"THEGREYMANEWALL:175:185:398:465",
|
||||
"THESEPULCHER:193:135:360:184",
|
||||
"THESHININGSTRAND:225:192:475:27",
|
||||
"THESKITTERINGDARK:160:165:302:18",
|
||||
},
|
||||
["WesternPlaguelands"] = {
|
||||
"CAERDARROW:158:150:608:421",
|
||||
"DALSONSTEARS:209:138:388:271",
|
||||
"DARROWMERELAKE:359:261:510:348",
|
||||
"FELSTONEFIELD:149:119:306:315",
|
||||
"GAHRRONSWITHERING:165:191:527:257",
|
||||
"HEARTHGLEN:331:280:312:20",
|
||||
"NORTHRIDGELUMBERCAMP:205:168:390:170",
|
||||
"RUINSOFANDORHOL:275:219:265:360",
|
||||
"SORROWHILL:290:201:360:467",
|
||||
"THEBULWARK:216:172:143:300",
|
||||
"THEWEEPINGCAVE:148:192:572:203",
|
||||
"THEWRITHINGHAUNT:159:177:457:329",
|
||||
"THONDRORILRIVER:193:326:597:94",
|
||||
},
|
||||
["EasternPlaguelands"] = {
|
||||
"BLACKWOODLAKE:217:221:451:207",
|
||||
"CORINSCROSSING:153:146:544:373",
|
||||
"CROWNGUARDTOWER:193:152:299:408",
|
||||
"DARROWSHIRE:195:167:316:497",
|
||||
"EASTWALLTOWER:164:143:600:250",
|
||||
"ForlornSummit:192:185:64:225",
|
||||
"LAKEMERELDAR:240:164:543:469",
|
||||
"LIGHTSHOPECHAPEL:165:238:721:304",
|
||||
"NORTHDALE:172:191:628:135",
|
||||
"NORTHPASSTOWER:228:178:465:118",
|
||||
"PESTILENTSCAR:189:241:416:349",
|
||||
"PLAGUEWOOD:346:258:178:92",
|
||||
"QUELLITHIENLODGE:216:136:430:44",
|
||||
"STRATHOLME:227:189:202:15",
|
||||
"TERRORDALE:175:129:86:137",
|
||||
"THEFUNGALVALE:202:199:277:267",
|
||||
"THEINFECTISSCAR:183:262:628:298",
|
||||
"THEMARRISSTEAD:182:194:164:367",
|
||||
"THENOXIOUSGLADE:211:202:730:172",
|
||||
"THEUNDERCROFT:171:136:178:484",
|
||||
"THONDRORILRIVER:210:348:12:235",
|
||||
"TYRSHAND:201:159:724:475",
|
||||
"ZULMASHAR:189:152:621:36",
|
||||
},
|
||||
["Hilsbrad"] = {
|
||||
"AZURELOADMINE:146:174:188:288",
|
||||
"DARROWHILL:173:132:426:164",
|
||||
"DUNGAROK:225:257:644:305",
|
||||
"DURNHOLDEKEEP:367:347:614:82",
|
||||
"EASTERNSTRAND:208:301:537:350",
|
||||
"HILLSBRADFIELDS:282:257:211:166",
|
||||
"NETHANDERSTEAD:190:215:554:249",
|
||||
"PURGATIONISLE:120:94:111:485",
|
||||
"SOUTHPOINTTOWER:271:207:11:203",
|
||||
"SOUTHSHORE:219:245:423:213",
|
||||
"TARRENMILL:195:291:524:9",
|
||||
"WESTERNSTRAND:272:139:215:377",
|
||||
},
|
||||
["Hinterlands"] = {
|
||||
"AERIEPEAK:240:195:20:250",
|
||||
"AGOLWATHA:188:180:382:173",
|
||||
"HIRIWATHA:201:118:188:314",
|
||||
"JINTHAALOR:223:277:514:338",
|
||||
"PLAGUEMISTRAVINE:124:203:170:158",
|
||||
"QUELDANILLODGE:168:181:248:194",
|
||||
"SERADANE:260:263:519:25",
|
||||
"SHADRAALOR:181:170:247:394",
|
||||
"SHAOLWATHA:265:190:580:247",
|
||||
"SKULKROCK:148:133:520:241",
|
||||
"THEALTAROFZUL:186:153:382:371",
|
||||
"THECREEPINGRUIN:168:157:417:268",
|
||||
"THEOVERLOOKCLIFFS:153:299:702:309",
|
||||
"TheRasazTrails:118:133:210:381",
|
||||
"VALORWINDLAKE:153:160:329:309",
|
||||
},
|
||||
["DunMorogh"] = {
|
||||
"AMBERSTILLRANCH:105:104:582:288",
|
||||
"ANVILMAR:209:159:167:414",
|
||||
"BREWNALLVILLAGE:94:102:263:256",
|
||||
"CHILLBREEZEVALLEY:160:113:281:304",
|
||||
"COLDRIDGEPASS:134:123:300:387",
|
||||
"FROSTMANEHOLD:112:118:225:291",
|
||||
"GNOMERAGON:158:176:178:164",
|
||||
"GOLBOLARQUARRY:144:149:619:301",
|
||||
"HELMSBEDLAKE:131:153:706:284",
|
||||
"ICEFLOWLAKE:117:167:286:173",
|
||||
"IRONFORGE:291:187:414:166",
|
||||
"IronforgeAirfields:213:233:582:100",
|
||||
"KHARANOS:175:186:397:296",
|
||||
"MISTYPINEREFUGE:117:155:507:224",
|
||||
"NORTHERNGATEOUTPOST:115:155:766:177",
|
||||
"RugfordsMountainRest:199:114:739:406",
|
||||
"SHIMMERRIDGE:115:173:355:169",
|
||||
"SOUTHERNGATEOUTPOST:113:106:800:284",
|
||||
"THEGRIZZLEDDEN:178:155:321:328",
|
||||
"THETUNDRIDHILLS:135:115:531:328",
|
||||
},
|
||||
["SearingGorge"] = {
|
||||
"BLACKCHARCAVE:264:224:84:371",
|
||||
"DUSTFIREVALLEY:446:355:428:11",
|
||||
"FIREWATCHRIDGE:385:413:95:39",
|
||||
"GRIMSILTDIGSITE:292:210:501:306",
|
||||
"TANNERCAMP:292:215:552:413",
|
||||
"THECAULDRON:414:310:257:178",
|
||||
"THESEAOFCINDERS:345:273:256:395",
|
||||
},
|
||||
["BurningSteppes"] = {
|
||||
"ALTAROFSTORMS:215:210:41:114",
|
||||
"BLACKROCKMOUNTAIN:249:272:178:104",
|
||||
"BLACKROCKPASS:259:299:597:285",
|
||||
"BLACKROCKSTRONGHOLD:232:258:342:120",
|
||||
"DRACODAR:405:309:61:262",
|
||||
"DREADMAULROCK:203:214:717:174",
|
||||
"MORGANSVIGIL:287:265:715:311",
|
||||
"PILLAROFASH:313:261:382:289",
|
||||
"RUINSOFTHAURISSAN:258:275:521:105",
|
||||
"TERRORWINGPATH:271:296:730:52",
|
||||
},
|
||||
["Elwynn"] = {
|
||||
"BRACKWELLPUMPKINPATCH:222:223:592:431",
|
||||
"CRYSTALLAKE:196:192:435:345",
|
||||
"EASTVALELOGGINGCAMP:237:190:713:339",
|
||||
"FARGODEEPMINE:238:230:247:438",
|
||||
"FORESTSEDGE:242:335:132:333",
|
||||
"GOLDSHIRE:219:199:261:280",
|
||||
"JERODSLANDING:206:212:437:445",
|
||||
"NORTHSHIREVALLEY:237:244:390:154",
|
||||
"RIDGEPOINTTOWER:287:207:710:445",
|
||||
"STONECAIRNLAKE:278:230:601:203",
|
||||
"STORMWIND:452:392:21:3",
|
||||
"TOWEROFAZORA:232:226:561:303",
|
||||
},
|
||||
["DeadwindPass"] = {
|
||||
"DEADMANSCROSSING:368:353:257:81",
|
||||
"KARAZHAN:287:234:277:342",
|
||||
"THEVICE:254:257:433:305",
|
||||
},
|
||||
["Duskwood"] = {
|
||||
"ADDLESSTEAD:255:231:67:353",
|
||||
"BRIGHTWOODGROVE:191:315:520:131",
|
||||
"DARKSHIRE:291:259:644:173",
|
||||
"MANORMISTMANTLE:174:150:668:134",
|
||||
"RAVENHILL:166:115:117:317",
|
||||
"RAVENHILLCEMETARY:323:276:99:165",
|
||||
"THEDARKENEDBANK:886:184:105:44",
|
||||
"THEHUSHEDBANK:136:306:29:143",
|
||||
"THEROTTINGORCHARD:231:210:549:380",
|
||||
"THEYORGENFARMSTEAD:211:235:403:393",
|
||||
"TRANQUILGARDENSCEMETARY:204:196:692:363",
|
||||
"TWILIGHTGROVE:327:384:314:99",
|
||||
"VULGOLOGREMOUND:231:266:259:365",
|
||||
},
|
||||
["LochModan"] = {
|
||||
"GRIZZLEPAWRIDGE:262:343:326:325",
|
||||
"IRONBANDSEXCAVATIONSITE:321:294:491:276",
|
||||
"MOGROSHSTRONGHOLD:287:265:558:61",
|
||||
"NORTHGATEPASS:195:273:143:24",
|
||||
"SILVERSTREAMMINE:201:243:247:25",
|
||||
"STONESPLINTERVALLEY:226:255:230:363",
|
||||
"STONEWROUGHTDAM:260:149:354:23",
|
||||
"THEFARSTRIDERLODGE:162:182:741:298",
|
||||
"THELOCH:288:380:367:99",
|
||||
"THELSAMAR:243:205:225:214",
|
||||
"VALLEYOFKINGS:166:224:123:382",
|
||||
},
|
||||
["Redridge"] = {
|
||||
"ALTHERSMILL:222:260:407:134",
|
||||
"GALARDELLVALLEY:234:240:661:168",
|
||||
"LAKEEVERSTILL:522:260:140:249",
|
||||
"LAKERIDGEHIGHWAY:411:274:196:341",
|
||||
"LAKESHIRE:325:179:92:204",
|
||||
"REDRIDGECANYONS:345:232:132:79",
|
||||
"RENDERSCAMP:264:248:284:4",
|
||||
"RENDERSVALLEY:453:248:491:365",
|
||||
"RedwallKeep:162:110:830:466",
|
||||
"STONEWATCH:243:289:507:221",
|
||||
"STONEWATCHFALLS:308:200:601:325",
|
||||
"THREECORNERS:360:337:0:288",
|
||||
},
|
||||
["Stranglethorn"] = {
|
||||
"BALALRUINS:83:65:245:101",
|
||||
"BALIAMAHRUINS:100:133:376:133",
|
||||
"BLOODSAILCOMPOUND:154:163:201:291",
|
||||
"BOOTYBAY:137:125:207:435",
|
||||
"CRYSTALVEINMINE:111:114:350:280",
|
||||
"GROMGOLBASECAMP:101:98:265:137",
|
||||
"JAGUEROISLE:132:107:319:498",
|
||||
"KALAIRUINS:86:89:303:93",
|
||||
"KURZENSCOMPOUND:141:139:394:5",
|
||||
"LAKENAZFERITI:124:120:332:60",
|
||||
"MISTVALEVALLEY:120:116:283:372",
|
||||
"MIZJAHRUINS:93:102:315:133",
|
||||
"MOSHOGGOGREMOUND:123:168:435:96",
|
||||
"NEKMANIWELLSPRING:80:102:215:365",
|
||||
"NESINGWARYSEXPEDITION:128:98:274:29",
|
||||
"REBELCAMP:164:86:289:0",
|
||||
"RUINSOFABORAZ:87:87:356:340",
|
||||
"RUINSOFJUBUWAL:100:105:312:304",
|
||||
"RUINSOFZULKUNDA:113:132:201:8",
|
||||
"RUINSOFZULMAMWE:162:122:397:213",
|
||||
"THEARENA:186:176:241:194",
|
||||
"THEVILEREEF:177:160:159:98",
|
||||
"VENTURECOBASECAMP:96:120:391:66",
|
||||
"WILDSHORE:151:175:237:431",
|
||||
"ZIATAJAIRUINS:127:120:364:231",
|
||||
"ZULGURUB:230:213:489:12",
|
||||
"ZUULDAIARUINS:103:105:160:47",
|
||||
},
|
||||
["SwampOfSorrows"] = {
|
||||
"FALLOWSANCTUARY:348:292:501:0",
|
||||
"ITHARIUSSCAVE:230:232:0:270",
|
||||
"MISTYREEDSTRAND:250:664:752:0",
|
||||
"MISTYVALLEY:211:179:29:148",
|
||||
"POOLOFTEARS:280:256:576:227",
|
||||
"SORROWMURK:199:351:734:128",
|
||||
"SPLINTERSPEARJUNCTION:258:222:138:245",
|
||||
"STAGALBOG:328:238:561:386",
|
||||
"STONARD:343:303:287:244",
|
||||
"SorrowguardKeep:220:174:0:266",
|
||||
"THEHARBORAGE:216:187:182:157",
|
||||
"THESHIFTINGMIRE:303:221:294:118",
|
||||
},
|
||||
["Westfall"] = {
|
||||
"ALEXSTONFARMSTEAD:275:182:220:274",
|
||||
"DEMONTSPLACE:166:155:226:391",
|
||||
"FURLBROWSPUMPKINFARM:186:191:399:24",
|
||||
"GOLDCOASTQUARRY:194:241:234:113",
|
||||
"JANGOLODEMINE:185:187:323:44",
|
||||
"MOONBROOK:208:174:317:349",
|
||||
"SALDEANSFARM:191:184:478:119",
|
||||
"SENTINELHILL:161:210:461:259",
|
||||
"THEDAGGERHILLS:235:149:349:430",
|
||||
"THEDEADACRE:164:213:542:267",
|
||||
"THEDUSTPLAINS:260:204:538:393",
|
||||
"THEJANSENSTEAD:143:183:498:6",
|
||||
"THEMOLSENFARM:199:180:342:161",
|
||||
"WESTFALLLIGHTHOUSE:306:161:171:482",
|
||||
},
|
||||
["Wetlands"] = {
|
||||
"ANGERFANGENCAMPMENT:196:150:364:241",
|
||||
"BLACKCHANNELMARSH:213:173:95:260",
|
||||
"BLUEGILLMARSH:195:165:111:157",
|
||||
"DIREFORGEHILL:233:225:515:128",
|
||||
"DUNMODR:173:147:419:42",
|
||||
"DunAgrath:222:190:97:335",
|
||||
"GRIMBATOL:317:331:623:242",
|
||||
"HawksVigil:170:162:264:353",
|
||||
"IRONBEARDSTOMB:165:156:368:135",
|
||||
"MENETHILHARBOR:156:116:27:321",
|
||||
"MOSSHIDEFEN:215:224:539:277",
|
||||
"RAPTORRIDGE:159:115:640:191",
|
||||
"SALTSPRAYGLEN:170:214:254:58",
|
||||
"SUNDOWNMARSH:265:212:113:100",
|
||||
"THEGREENBELT:153:209:473:143",
|
||||
"THELGANROCK:207:165:478:383",
|
||||
"WHELGARSEXCAVATIONSITE:172:156:264:224",
|
||||
},
|
||||
["Teldrassil"] = {
|
||||
"BANETHILHOLLOW:136:191:392:289",
|
||||
"DARNASSUS:294:252:110:251",
|
||||
"DOLANAAR:170:119:469:327",
|
||||
"GNARLPINEHOLD:230:158:316:417",
|
||||
"LAKEALAMETH:237:163:445:388",
|
||||
"POOLSOFARLITHRIEN:125:180:337:314",
|
||||
"RUTTHERANVILLAGE:110:91:504:548",
|
||||
"SHADOWGLEN:198:201:504:164",
|
||||
"STARBREEZEVILLAGE:153:175:588:303",
|
||||
"THEORACLEGLADE:150:223:279:135",
|
||||
"UrsanHeights:229:154:243:423",
|
||||
"WELLSPRINGLAKE:166:245:383:98",
|
||||
},
|
||||
["Darkshore"] = {
|
||||
"AMETHARAN:163:178:340:323",
|
||||
"AUBERDINE:134:179:323:191",
|
||||
"BASHALARAN:153:171:379:199",
|
||||
"CLIFFSPRINGRIVER:205:161:391:112",
|
||||
"GROVEOFTHEANCIENTS:180:138:314:436",
|
||||
"REMTRAVELSEXCAVATION:148:164:245:504",
|
||||
"RUINSOFMATHYSTRA:176:205:519:3",
|
||||
"THEMASTERSGLAIVE:146:139:346:529",
|
||||
"TOWEROFALTHALAXX:142:159:484:110",
|
||||
},
|
||||
["Ashenvale"] = {
|
||||
"ASTRANAAR:190:173:281:259",
|
||||
"BOUGHSHADOW:140:190:862:157",
|
||||
"FALLENSKYLAKE:219:190:555:433",
|
||||
"FELFIREHILL:241:244:720:349",
|
||||
"FIRESCARSHRINE:153:161:194:332",
|
||||
"ForestSong:162:142:817:232",
|
||||
"IRISLAKE:185:190:401:226",
|
||||
"LAKEFALATHIM:115:181:138:144",
|
||||
"MAESTRASPOST:198:290:216:46",
|
||||
"MYSTRALLAKE:262:228:363:354",
|
||||
"NIGHTRUN:212:244:604:263",
|
||||
"RAYNEWOODRETREAT:165:235:527:243",
|
||||
"SATYRNAAR:163:172:702:230",
|
||||
"THEHOWLINGVALE:196:172:471:147",
|
||||
"THERUINSOFSTARDUST:141:135:268:382",
|
||||
"THESHRINEOFAESSINA:205:179:113:268",
|
||||
"THEZORAMSTRAND:228:235:26:33",
|
||||
"THISTLEFURVILLAGE:243:184:210:164",
|
||||
"WARSONGLUMBERCAMP:185:146:804:319",
|
||||
},
|
||||
["ThousandNeedles"] = {
|
||||
"CAMPETHOK:289:300:7:1",
|
||||
"DARKCLOUDPINNACLE:189:182:267:138",
|
||||
"FREEWINDPOST:193:173:366:273",
|
||||
"HIGHPERCH:178:174:39:164",
|
||||
"SPLITHOOFCRAG:193:175:400:203",
|
||||
"THEGREATLIFT:191:163:213:78",
|
||||
"THESCREECHINGCANYON:240:231:187:206",
|
||||
"THESHIMMERINGFLATS:312:355:612:308",
|
||||
"WINDBREAKCANYON:225:213:501:249",
|
||||
},
|
||||
["StonetalonMountains"] = {
|
||||
"AMANIALOR:504:252:8:5",
|
||||
"BAELHARDUL:357:252:512:257",
|
||||
"BLACKSANDOILFIELDS:504:504:8:5",
|
||||
"BOULDERSLIDERAVINE:90:76:586:547",
|
||||
"BRAMBLETHORNPASS:382:359:512:257",
|
||||
"BROKENCLIFFMINE:252:504:260:5",
|
||||
"CAMPAPARAJE:222:100:670:552",
|
||||
"GRIMTOTEMPOST:146:77:675:519",
|
||||
"MALAKAJIN:137:86:662:562",
|
||||
"MIRKFALLONLAKE:504:504:260:5",
|
||||
"POWDERTOWN:252:252:260:257",
|
||||
"SISHIRCANYON:252:264:512:257",
|
||||
"STONETALONPEAK:258:252:260:5",
|
||||
"SUNROCKRETREAT:504:252:260:257",
|
||||
"THECHARREDVALE:252:308:260:257",
|
||||
"THEEARTHENRING:504:252:260:257",
|
||||
"VENTURECOMPANYCAMP:252:504:260:5",
|
||||
"WEBWINDERPATH:256:324:512:256",
|
||||
"WINDSHEARCRAG:256:256:512:256",
|
||||
},
|
||||
["Desolace"] = {
|
||||
"ETHELRETHOR:189:237:318:68",
|
||||
"GELKISVILLAGE:181:235:300:433",
|
||||
"KODOGRAVEYARD:253:237:396:251",
|
||||
"KOLKARVILLAGE:206:208:615:221",
|
||||
"KORMEKSHUT:156:147:562:187",
|
||||
"MAGRAMVILLAGE:182:272:606:373",
|
||||
"MANNOROCCOVEN:270:271:408:385",
|
||||
"NIJELSPOINT:188:246:561:0",
|
||||
"RANAZJARISLE:88:89:248:11",
|
||||
"SARGERON:274:237:632:38",
|
||||
"SHADOWBREAKRAVINE:190:181:698:454",
|
||||
"SHADOWPREYVILLAGE:215:218:175:395",
|
||||
"TETHRISARAN:185:137:440:0",
|
||||
"THUNDERAXEFORTRESS:174:207:455:108",
|
||||
"VALLEYOFSPEARS:234:274:217:221",
|
||||
},
|
||||
["Feralas"] = {
|
||||
"CAMPMOJACHE:146:148:694:238",
|
||||
"ChimaeraRoostVale:106:150:785:321",
|
||||
"DIREMAUL:214:180:461:208",
|
||||
"DREAMBOUGH:141:122:459:0",
|
||||
"FERALSCARVALE:103:104:491:335",
|
||||
"FRAYFEATHERHIGHLANDS:101:163:485:391",
|
||||
"GORDUNNIOUTPOST:154:128:697:148",
|
||||
"GRIMTOTEMCOMPOUND:110:186:630:172",
|
||||
"ISLEOFDREAD:205:285:197:381",
|
||||
"LOWERWILDS:214:152:756:204",
|
||||
"ONEIROS:101:103:499:74",
|
||||
"RUINSOFISILDIEN:179:241:547:326",
|
||||
"RUINSOFRAVENWIND:178:149:310:0",
|
||||
"SARDORISLE:167:168:216:241",
|
||||
"THEFORGOTTENCOAST:132:316:409:258",
|
||||
"THETWINCOLOSSALS:278:235:323:78",
|
||||
"THEWRITHINGDEEP:214:205:625:305",
|
||||
},
|
||||
["Dustwallow"] = {
|
||||
"ALCAZISLAND:176:179:671:29",
|
||||
"BACKBAYWETLANDS:384:243:248:196",
|
||||
"BRACKENWALLVILLAGE:264:264:239:2",
|
||||
"THEDENOFFLAME:246:235:264:320",
|
||||
"THERAMOREISLE:214:187:542:232",
|
||||
"THEWYRMBOG:263:229:372:387",
|
||||
"WITCHHILL:230:311:431:1",
|
||||
"WesthavenHollow:166:149:603:428",
|
||||
},
|
||||
["Tanaris"] = {
|
||||
"ABYSSALSANDS:194:159:376:207",
|
||||
"BROKENPILLAR:93:165:481:243",
|
||||
"CAVERNSOFTIME:139:132:567:262",
|
||||
"DUNEMAULCOMPOUND:186:127:337:298",
|
||||
"EASTMOONRUINS:143:130:405:355",
|
||||
"GADGETZAN:156:149:430:102",
|
||||
"LANDSENDBEACH:184:148:456:514",
|
||||
"LOSTRIGGERCOVE:141:173:635:230",
|
||||
"NOONSHADERUINS:129:191:530:43",
|
||||
"SANDSORROWWATCH:175:157:311:111",
|
||||
"SOUTHBREAKSHORE:192:151:510:304",
|
||||
"SOUTHMOONRUINS:185:196:324:366",
|
||||
"STEAMWHEEDLEPORT:136:129:599:86",
|
||||
"SlickwickOilRig:237:181:129:436",
|
||||
"THEGAPINGCHASM:198:188:458:380",
|
||||
"THENOXIOUSLAIR:155:180:265:209",
|
||||
"THISTLESHRUBVALLEY:166:234:217:294",
|
||||
"VALLEYOFTHEWATCHERS:125:115:311:470",
|
||||
"WATERSPRINGFIELD:145:164:516:177",
|
||||
"ZALASHJISDEN:90:127:619:157",
|
||||
"ZULFARRAK:191:166:265:5",
|
||||
},
|
||||
["Aszhara"] = {
|
||||
"BAYOFSTORMS:257:285:486:208",
|
||||
"BITTERREACHES:231:173:651:46",
|
||||
"FORLORNRIDGE:210:247:198:373",
|
||||
"HALDARRENCAMPMENT:191:138:83:339",
|
||||
"JAGGEDREEF:556:153:373:9",
|
||||
"LAKEMENNAR:301:184:302:437",
|
||||
"LEGASHENCAMPMENT:224:131:484:49",
|
||||
"RAVENCRESTMONUMENT:227:115:559:506",
|
||||
"RUINSOFELDARATH:251:268:246:228",
|
||||
"SHADOWSONGSHRINE:213:170:43:428",
|
||||
"SOUTHRIDGEBEACH:355:207:397:360",
|
||||
"TEMPLEOFARKKORAN:177:187:689:160",
|
||||
"THALASSIANBASECAMP:224:144:507:126",
|
||||
"THERUINEDREACHES:384:116:403:549",
|
||||
"THESHATTEREDSTRAND:148:199:412:202",
|
||||
"TIMBERMAWHOLD:209:194:258:175",
|
||||
"TOWEROFELDARA:107:145:824:114",
|
||||
"URSOLAN:219:212:327:94",
|
||||
"VALORMOK:201:158:93:236",
|
||||
},
|
||||
["Felwood"] = {
|
||||
"BLOODVENOMFALLS:225:132:297:269",
|
||||
"DEADWOODVILLAGE:162:129:415:539",
|
||||
"EMERALDSANCTUARY:171:150:411:436",
|
||||
"FELPAWVILLAGE:224:138:489:0",
|
||||
"IRONTREEWOODS:200:203:426:60",
|
||||
"JADEFIREGLEN:151:145:339:469",
|
||||
"JADEFIRERUN:181:159:338:35",
|
||||
"JAEDENAR:236:122:275:333",
|
||||
"MORLOSARAN:129:151:503:515",
|
||||
"RUINSOFCONSTELLAS:224:142:303:387",
|
||||
"SHATTERSCARVALE:218:186:315:130",
|
||||
"TALONBRANCHGLADE:145:130:554:97",
|
||||
},
|
||||
["UngoroCrater"] = {
|
||||
"FIREPLUMERIDGE:277:258:377:185",
|
||||
"GOLAKKAHOTSPRINGS:299:334:133:158",
|
||||
"IRONSTONEPLATEAU:276:281:588:69",
|
||||
"LAKKARITARPITS:553:255:170:13",
|
||||
"TERRORRUN:333:275:166:375",
|
||||
"THEMARSHLANDS:295:341:568:247",
|
||||
"THESLITHERINGSCAR:326:260:378:403",
|
||||
},
|
||||
["Moonglade"] = {
|
||||
"LAKEELUNEARA:530:484:256:101",
|
||||
},
|
||||
["Silithus"] = {
|
||||
"HIVEASHI:431:276:305:34",
|
||||
"HIVEREGAL:469:335:262:333",
|
||||
"HIVEZORA:343:424:117:188",
|
||||
"SOUTHWINDVILLAGE:346:348:519:83",
|
||||
"THECRYSTALVALE:285:274:121:31",
|
||||
"THESCARABWALL:271:145:125:523",
|
||||
"TWILIGHTBASECAMP:297:238:355:206",
|
||||
},
|
||||
["Winterspring"] = {
|
||||
"DARKWHISPERGORGE:247:191:452:447",
|
||||
"EVERLOOK:151:189:516:114",
|
||||
"FROSTFIREHOTSPRINGS:226:124:231:180",
|
||||
"FROSTSABERROCK:240:172:375:11",
|
||||
"FROSTWHISPERGORGE:184:144:531:383",
|
||||
"ICETHISTLEHILLS:116:155:617:247",
|
||||
"LAKEKELTHERIL:203:176:409:205",
|
||||
"MAZTHORIL:174:169:499:265",
|
||||
"OWLWINGTHICKET:149:127:600:348",
|
||||
"STARFALLVILLAGE:174:148:399:143",
|
||||
"THEHIDDENGROVE:162:174:562:33",
|
||||
"TIMBERMAWPOST:223:112:235:248",
|
||||
"WINTERFALLVILLAGE:133:120:622:163",
|
||||
},
|
||||
["AlteracValley"] = {
|
||||
"DUNBALDAR:254:228:356:18",
|
||||
"FROSTWOLFKEEP:220:278:408:382",
|
||||
"ICEBLOODGARRISON:286:288:343:179",
|
||||
},
|
||||
["Hyjal"] = {
|
||||
"BARKSKINPLATEAU:207:167:390:297",
|
||||
"BARKSKINVILLAGE:502:293:417:346",
|
||||
"BLEAKHOLLOWCRATER:297:335:401:20",
|
||||
"CIRCLEOFPOWER:277:331:203:63",
|
||||
"DARKHOLLOWPASS:214:237:342:389",
|
||||
"NORDANAAR:139:146:778:118",
|
||||
"NORDRASSILGLADE:418:408:578:0",
|
||||
"RUINSOFTELENNAS:165:193:141:236",
|
||||
"THEEMERALDGATEWAY:309:244:109:353",
|
||||
"ZULHATHA:191:210:3:194",
|
||||
},
|
||||
["Lapidis"] = {
|
||||
"BRIGHTCOAST:298:213:237:246",
|
||||
"CAELANSREST:147:111:517:230",
|
||||
"CROWNISLAND:140:95:512:33",
|
||||
"GORDOSHHEIGHTS:253:243:256:89",
|
||||
"HAZURRIGLADE:87:82:486:306",
|
||||
"SHANKSREEF:207:90:446:109",
|
||||
"THEROCK:74:72:687:149",
|
||||
"TOWEROFLAPIDIS:203:118:487:175",
|
||||
"WALLOWINGCOAST:240:184:505:326",
|
||||
"ZULHAZU:227:178:348:407",
|
||||
},
|
||||
["Gillijim"] = {
|
||||
"DEEPTIDESANCTUM:204:117:199:512",
|
||||
"DISTILLERYISLE:90:69:234:216",
|
||||
"FAELONSFOLLY:108:103:331:304",
|
||||
"GILLIJIMSTRAND:108:324:641:160",
|
||||
"JADEMINE:141:149:500:256",
|
||||
"KALKORPOINT:123:115:421:210",
|
||||
"KAZONISLAND:194:113:432:51",
|
||||
"MAULOGGPOST:137:227:512:401",
|
||||
"MAULOGGREFUGE:231:132:615:465",
|
||||
"RUINSOFZULRAZAR:215:136:369:384",
|
||||
"SILVERCOAST:245:184:311:386",
|
||||
"SILVERSANDBAR:47:128:256:398",
|
||||
"SOUTHSEASANDBAR:125:172:178:63",
|
||||
"TANGLEWOOD:135:188:576:297",
|
||||
"ZULRAZAR:137:168:375:282",
|
||||
},
|
||||
["TelAbim"] = {
|
||||
"BIXXLESSTOREHOUSE:300:127:368:178",
|
||||
"HIGHVALERISE:191:187:468:248",
|
||||
"TAZZOSSHACK:229:232:453:391",
|
||||
"TELCOBASECAMP:184:183:342:440",
|
||||
"THEDERELICTCAMP:165:205:364:273",
|
||||
"THEJAGGEDISLES:313:256:385:16",
|
||||
},
|
||||
["Gilneas"] = {
|
||||
"BLACKTHORNSCAMP:160:163:32:171",
|
||||
"BROLOKMOUND:140:120:467:349",
|
||||
"DAWNSTONEMINE:130:144:292:155",
|
||||
"FREYSHEARKEEP:72:70:624:405",
|
||||
"GILNEASCITY:295:242:89:105",
|
||||
"GLAYMORESTEAD:140:127:483:129",
|
||||
"GREYMANESWATCH:119:158:580:311",
|
||||
"HOLLOWWEBCEMETARY:180:167:215:395",
|
||||
"HOLLOWWEBWOODS:171:118:341:452",
|
||||
"NORTHGATETOWER:130:137:397:172",
|
||||
"OLDROCKPASS:155:165:290:37",
|
||||
"RAVENSHIRE:194:237:472:431",
|
||||
"RAVENWOODKEEP:253:174:496:494",
|
||||
"ROSEWICKPLANTATION:134:140:331:155",
|
||||
"RUINSOFGREYSHIRE:192:144:230:260",
|
||||
"SHADEMORETAVERN:117:120:279:336",
|
||||
"SOUTHMIREORCHARD:202:133:302:354",
|
||||
"STILLWARDCHURCH:213:166:471:220",
|
||||
"THEDRYROCKMINE:129:127:145:262",
|
||||
"THEDRYROCKPIT:230:217:69:295",
|
||||
"THEGREYMANEWALL:131:173:412:48",
|
||||
"THEOVERGROWNACRE:134:154:390:269",
|
||||
},
|
||||
["Icepoint"] = {
|
||||
"KANEQNUUN:512:430:237:129",
|
||||
},
|
||||
["BlackstoneIsland"] = {
|
||||
"BLACKASHCOALPITS:303:474:209:178",
|
||||
"BLACKASHMINE:251:204:397:222",
|
||||
"GAZZIKSWORKSHOP:329:247:439:403",
|
||||
"RUSTGATELUMBERYARD:217:245:551:241",
|
||||
"RUSTGATERIDGE:396:226:372:360",
|
||||
"THEWATERHOLE:386:239:382:43",
|
||||
"VENTURECOSLUMS:270:192:498:129",
|
||||
},
|
||||
["ThalassianHighlands"] = {
|
||||
"ALAHTHALAS:470:276:429:38",
|
||||
"ANASTERIANPARK:229:219:385:256",
|
||||
"BRINTHILIEN:180:211:395:450",
|
||||
"FELSTRIDERRETREAT:201:207:165:428",
|
||||
"ISLEOFETERNALAUTUMN:204:167:236:117",
|
||||
"RUINSOFNASHALARAN:249:266:36:151",
|
||||
"SILVERSUNMINE:208:189:288:373",
|
||||
"THEFARSTRIDE:230:199:212:255",
|
||||
"THELASTRUNESTONE:179:203:507:379",
|
||||
},
|
||||
["GrimReaches"] = {
|
||||
"BAGGOTHSRAMPART:112:113:399:238",
|
||||
"BARLEYCRESTFARMSTEAD:116:88:499:222",
|
||||
"BRANGARSFOLLY:108:123:568:33",
|
||||
"DUNKITHAS:137:95:470:329",
|
||||
"EASTRIDGEOUTPOST:166:107:374:165",
|
||||
"GETHKAR:118:124:444:84",
|
||||
"GROLDANSEXCAVATION:125:117:567:231",
|
||||
"LAKEKITHAS:217:150:481:270",
|
||||
"RUINSOFSTOLGAZKEEP:134:146:366:53",
|
||||
"SALGAZMINES:150:119:535:380",
|
||||
"SHATTERBLADEPOST:186:130:512:126",
|
||||
"SLATEBEARDSFORGE:136:99:457:404",
|
||||
"THEGRIMHOLLOW:288:200:396:463",
|
||||
"THEHIGHPASS:97:141:409:315",
|
||||
"ZARMGETHPOINT:143:76:405:16",
|
||||
"ZARMGETHSTRONGHOLD:120:150:487:17",
|
||||
},
|
||||
["Balor"] = {
|
||||
"BILGERATCOMPOUND:174:141:323:76",
|
||||
"CROAKINGPLATEAU:371:154:397:201",
|
||||
"GRAHANESTATE:93:116:306:392",
|
||||
"GULLWINGWRECKAGE:110:105:221:34",
|
||||
"LANGSTONORCHARD:102:129:410:346",
|
||||
"RUINSOFBREEZEHAVEN:163:141:450:256",
|
||||
"SCURRYINGTHICKET:144:158:283:161",
|
||||
"SIOUTPOST:175:232:597:389",
|
||||
"SORROWMORELAKE:174:140:303:277",
|
||||
"STORMBREAKERPOINT:243:197:583:212",
|
||||
"STORMREAVERSPIRE:151:119:491:429",
|
||||
"STORMWROUGHTCASTLE:202:166:458:332",
|
||||
"TREACHEROUSCRAGS:243:171:381:457",
|
||||
"VANDERFARMSTEAD:146:140:330:372",
|
||||
"WINDROCKCLIFFS:192:279:256:303",
|
||||
},
|
||||
["Northwind"] = {
|
||||
"ABBEYGARDENS:250:224:512:33",
|
||||
"AMBERSHIRE:234:215:334:278",
|
||||
"AMBERWOODKEEP:243:205:187:308",
|
||||
"BLACKROCKBREACH:255:201:721:222",
|
||||
"BRISTLEWHISKERCAVERN:197:185:568:232",
|
||||
"CINDERFALLPASS:229:229:733:348",
|
||||
"CRAWFORDWINERY:160:222:513:285",
|
||||
"CRYSTALFALLS:320:205:627:464",
|
||||
"GRIMMENLAKE:218:210:550:352",
|
||||
"MERCHANTSHIGHROAD:353:249:256:420",
|
||||
"NORTHRIDGEPOINT:298:314:349:31",
|
||||
"NORTHWINDLOGGINGCAMP:224:185:258:171",
|
||||
"RUINSOFBIRKHAVEN:180:208:645:116",
|
||||
"SHERWOODQUARRY:293:287:702:1",
|
||||
"STILLHEARTPORT:174:155:116:217",
|
||||
"TOWEROFMAGILOU:181:223:189:169",
|
||||
"WITCHCOVEN:147:162:257:81",
|
||||
},
|
||||
["Moonwhisper"] = {
|
||||
"AnShesRespite:170:120:398:81",
|
||||
"AncestralGrounds:164:82:295:88",
|
||||
"BlackrootHold:162:137:495:465",
|
||||
"BlackrootVillage:224:143:411:525",
|
||||
"FoulheartSanctum:157:149:496:232",
|
||||
"GroveOfTheMoon:152:146:464:347",
|
||||
"LunarclawDen:84:60:674:376",
|
||||
"MarasEthil:96:94:640:417",
|
||||
"MoonhoofRetreat:172:144:419:167",
|
||||
"MoonhoofVillage:163:157:570:188",
|
||||
"MoonsilkHollow:125:114:577:458",
|
||||
"MoroGaiVillage:133:127:571:372",
|
||||
"NarvalisPoint:162:139:503:118",
|
||||
"RuinsOfNendis:183:130:584:275",
|
||||
"StarshardCradle:122:109:334:121",
|
||||
"Tyrandas:158:169:580:61",
|
||||
"VysnagosasRest:163:102:420:8",
|
||||
},
|
||||
}
|
||||
|
||||
-- add turtle-wow sell values
|
||||
pfSellData = {
|
||||
|
||||
@@ -41,12 +41,12 @@ pfUI:RegisterModule("unusable", function ()
|
||||
end
|
||||
|
||||
-- update on regular pfUI button updates
|
||||
hooksecurefunc(pfUI.bag, "UpdateSlot", function(self, bag, slot)
|
||||
pfUI.hooksecurefunc(pfUI.bag, "UpdateSlot", function(self, bag, slot)
|
||||
pfUI.unusable:UpdateSlot(bag, slot)
|
||||
end)
|
||||
|
||||
-- update on bank frame itemlock updates
|
||||
hooksecurefunc("BankFrameItemButton_UpdateLock", function()
|
||||
pfUI.hooksecurefunc("BankFrameItemButton_UpdateLock", function()
|
||||
pfUI.unusable:UpdateSlot(-1, this:GetID())
|
||||
end)
|
||||
end)
|
||||
|
||||
+63
-54
@@ -1,18 +1,31 @@
|
||||
pfUI:RegisterModule("xpbar", function ()
|
||||
local rawborder, default_border = GetBorderSize()
|
||||
local parse_faction = SanitizePattern(FACTION_STANDING_INCREASED)
|
||||
|
||||
-- Rested-XP gain tracking constants
|
||||
local REST_WINDOW = 300 -- seconds of sliding-window samples for rate calc
|
||||
|
||||
|
||||
local REST_CAP_MUL = 1.5 -- target rested cap = UnitXPMax * 1.5
|
||||
if IsTurtleWoW() then
|
||||
if TURTLE_WOW_VERSION then
|
||||
REST_CAP_MUL = 1.13
|
||||
end
|
||||
|
||||
-- Resolve the faction to display: the explicitly remembered one (last to
|
||||
-- gain rep) if set, else the player's watched faction. Returns the same
|
||||
-- 5-tuple GetFactionInfo's relevant slots produced.
|
||||
local function GetRepDisplay(factionID)
|
||||
if factionID then
|
||||
local name, _, standingID, barMin, barMax, barValue = GetFactionInfoByID(factionID)
|
||||
if name then return name, standingID, barMin, barMax, barValue end
|
||||
end
|
||||
local w = C_Reputation.GetWatchedFactionData()
|
||||
if w then
|
||||
return w.name, w.reaction, w.currentReactionThreshold, w.nextReactionThreshold, w.currentStanding
|
||||
end
|
||||
end
|
||||
|
||||
local data = CreateFrame("Frame", "pfExperienceBarData", UIParent)
|
||||
data.rest_samples = {}
|
||||
data:RegisterEvent("CHAT_MSG_COMBAT_FACTION_CHANGE")
|
||||
data:RegisterEvent("FACTION_STANDING_CHANGED")
|
||||
data:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
data:RegisterEvent("PLAYER_LEVEL_UP")
|
||||
data:RegisterEvent("UPDATE_EXHAUSTION")
|
||||
@@ -35,16 +48,17 @@ pfUI:RegisterModule("xpbar", function ()
|
||||
while this.rest_samples[1] and (now - this.rest_samples[1][1]) > REST_WINDOW do
|
||||
table.remove(this.rest_samples, 1)
|
||||
end
|
||||
elseif event == "CHAT_MSG_COMBAT_FACTION_CHANGE" then
|
||||
local _,_, faction, amount = string.find(arg1, parse_faction)
|
||||
this.faction = faction or this.faction
|
||||
elseif event == "FACTION_STANDING_CHANGED" then
|
||||
-- arg1=factionID, arg2=newStanding, arg3=repGained — no chat-string
|
||||
-- parsing, no locale dependency.
|
||||
this.factionID = arg1
|
||||
elseif event == "UPDATE_FACTION" then
|
||||
-- drop the auto-tracked faction when the user changes the watched one
|
||||
local watched = C_Reputation.GetWatchedFactionData()
|
||||
local watchedName = watched and watched.name or nil
|
||||
if watchedName ~= this.watched then
|
||||
this.watched = watchedName
|
||||
this.faction = nil
|
||||
local watchedID = watched and watched.factionID or nil
|
||||
if watchedID ~= this.watchedID then
|
||||
this.watchedID = watchedID
|
||||
this.factionID = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -132,24 +146,21 @@ local function OnEnter(self)
|
||||
table.insert(lines, { T["XP"], "|cffffffff" .. xp .. " / " .. xpmax .. " (" .. xp_perc .. "%)" })
|
||||
table.insert(lines, { T["Remaining"], "|cffffffff" .. remaining .. " (" .. remaining_perc .. "%)" })
|
||||
elseif mode == "REP" then
|
||||
for i=1, 99 do
|
||||
local name, description, standingID, barMin, barMax, barValue, atWarWith, canToggleAtWar, isHeader, isCollapsed, isWatched = GetFactionInfo(i)
|
||||
if ( isWatched and not self.faction ) or ( self.faction and name == self.faction) then
|
||||
barMax = barMax - barMin
|
||||
barValue = barValue - barMin
|
||||
local name, standingID, barMin, barMax, barValue = GetRepDisplay(self.factionID)
|
||||
if name then
|
||||
barMax = barMax - barMin
|
||||
barValue = barValue - barMin
|
||||
|
||||
local color = FACTION_BAR_COLORS[standingID]
|
||||
if color then
|
||||
color = rgbhex(color.r + .3, color.g + .3, color.b + .3)
|
||||
else
|
||||
color = rgbhex(.5, .5, .5)
|
||||
end
|
||||
|
||||
table.insert(lines, { "|cff555555" .. T["Reputation"], "" })
|
||||
table.insert(lines, { color .. name .. " (" .. GetText("FACTION_STANDING_LABEL"..standingID, gender) .. ")"})
|
||||
table.insert(lines, { barValue .. " / " .. barMax .. " (" .. round(barValue / barMax * 100) .. "%)" })
|
||||
break
|
||||
local color = FACTION_BAR_COLORS[standingID]
|
||||
if color then
|
||||
color = rgbhex(color.r + .3, color.g + .3, color.b + .3)
|
||||
else
|
||||
color = rgbhex(.5, .5, .5)
|
||||
end
|
||||
|
||||
table.insert(lines, { "|cff555555" .. T["Reputation"], "" })
|
||||
table.insert(lines, { color .. name .. " (" .. GetText("FACTION_STANDING_LABEL"..standingID, gender) .. ")"})
|
||||
table.insert(lines, { barValue .. " / " .. barMax .. " (" .. round(barValue / barMax * 100) .. "%)" })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -195,10 +206,10 @@ end
|
||||
-- set either experience, reputation or flex-rep handler
|
||||
local mode = self.display
|
||||
if self.display == "XPFLEX" then
|
||||
self.faction = data.faction or nil
|
||||
self.factionID = data.factionID or nil
|
||||
mode = UnitLevel("player") < MAX_PLAYER_LEVEL and "XP" or "REP"
|
||||
elseif self.display == "FLEX" then
|
||||
self.faction = data.faction or nil
|
||||
self.factionID = data.factionID or nil
|
||||
mode = "REP"
|
||||
end
|
||||
|
||||
@@ -208,7 +219,7 @@ end
|
||||
end
|
||||
|
||||
-- skip on events of no interest
|
||||
if mode == "XP" and ( event == "CHAT_MSG_COMBAT_FACTION_CHANGE" or event == "UPDATE_FACTION" ) then return end
|
||||
if mode == "XP" and ( event == "FACTION_STANDING_CHANGED" or event == "UPDATE_FACTION" ) then return end
|
||||
if mode == "REP" and ( event == "PLAYER_XP_UPDATE" or event == "UPDATE_EXHAUSTION" ) then return end
|
||||
|
||||
if mode == "XP" then
|
||||
@@ -254,33 +265,31 @@ end
|
||||
elseif mode == "REP" then
|
||||
self.restedbar:Hide()
|
||||
|
||||
for i=1, 99 do
|
||||
local name, description, standingID, barMin, barMax, barValue, atWarWith, canToggleAtWar, isHeader, isCollapsed, isWatched = GetFactionInfo(i)
|
||||
if ( isWatched and not self.faction ) or ( self.faction and name == self.faction) then
|
||||
self.enabled = true
|
||||
self:SetAlpha(1)
|
||||
local name, standingID, barMin, barMax, barValue = GetRepDisplay(self.factionID)
|
||||
if name then
|
||||
self.enabled = true
|
||||
self:SetAlpha(1)
|
||||
|
||||
barMax = barMax - barMin
|
||||
barValue = barValue - barMin
|
||||
barMax = barMax - barMin
|
||||
barValue = barValue - barMin
|
||||
|
||||
self.bar:SetMinMaxValues(0, barMax)
|
||||
self.bar:SetValue(barValue)
|
||||
self.bar:SetMinMaxValues(0, barMax)
|
||||
self.bar:SetValue(barValue)
|
||||
|
||||
local color = FACTION_BAR_COLORS[standingID]
|
||||
if color then
|
||||
self.bar:SetStatusBarColor((color.r + .5) * .5, (color.g + .5) * .5, (color.b + .5) * .5, 1)
|
||||
else
|
||||
self.bar:SetStatusBarColor(.5,.5,.5,1)
|
||||
end
|
||||
|
||||
local text = "%s: %s%% (%s)"
|
||||
local perc = round(barValue / barMax * 100)
|
||||
local standing = GetText("FACTION_STANDING_LABEL"..standingID, gender)
|
||||
self.bar.text:SetText(string.format(text, name, perc, standing))
|
||||
|
||||
self.tick = GetTime() + self.timeout
|
||||
return
|
||||
local color = FACTION_BAR_COLORS[standingID]
|
||||
if color then
|
||||
self.bar:SetStatusBarColor((color.r + .5) * .5, (color.g + .5) * .5, (color.b + .5) * .5, 1)
|
||||
else
|
||||
self.bar:SetStatusBarColor(.5,.5,.5,1)
|
||||
end
|
||||
|
||||
local text = "%s: %s%% (%s)"
|
||||
local perc = round(barValue / barMax * 100)
|
||||
local standing = GetText("FACTION_STANDING_LABEL"..standingID, gender)
|
||||
self.bar.text:SetText(string.format(text, name, perc, standing))
|
||||
|
||||
self.tick = GetTime() + self.timeout
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
@@ -361,7 +370,7 @@ end
|
||||
-- auto hide
|
||||
b:EnableMouse(true)
|
||||
|
||||
b:RegisterEvent("CHAT_MSG_COMBAT_FACTION_CHANGE")
|
||||
b:RegisterEvent("FACTION_STANDING_CHANGED")
|
||||
b:RegisterEvent("UNIT_PET")
|
||||
b:RegisterEvent("UNIT_LEVEL")
|
||||
b:RegisterEvent("UNIT_PET_EXPERIENCE")
|
||||
|
||||
@@ -27,7 +27,7 @@ do
|
||||
-- ClassicAPI dependency check.
|
||||
-- pfUI relies pervasively on the modern C_* / SuperWoW / nameplate / focus
|
||||
-- API surface that ClassicAPI polyfills, so presence is required.
|
||||
local PFUI_CLASSIC_API_MIN = 10403 -- (X*10000 + Y*100 + Z)
|
||||
local PFUI_CLASSIC_API_MIN = 10600 -- (X*10000 + Y*100 + Z)
|
||||
local PFUI_CLASSIC_API_LATEST = PFUI_CLASSIC_API_MIN
|
||||
local PFUI_CLASSIC_API_WEBSITE = "https://github.com/brues-code/ClassicAPI"
|
||||
local PFUI_CLASSIC_API_LATEST_URL = PFUI_CLASSIC_API_WEBSITE .. "/releases/latest"
|
||||
@@ -99,9 +99,12 @@ pfUI.skins = {}
|
||||
pfUI.environment = {}
|
||||
pfUI.movables = {}
|
||||
pfUI.version = {}
|
||||
pfUI.hooks = {}
|
||||
pfUI.env = {}
|
||||
|
||||
pfUI.events = Mixin({}, CallbackRegistryMixin)
|
||||
pfUI.events:OnLoad()
|
||||
pfUI.events:SetUndefinedEventsAllowed(true)
|
||||
|
||||
-- check if macro addons are loaded (disables macrotweak/macroscan)
|
||||
function pfUI:MacroAddonsLoaded()
|
||||
return IsAddOnLoaded("Supermacro") or IsAddOnLoaded("SuperCleveRoidMacros") or IsAddOnLoaded("UltimaMacros")
|
||||
|
||||
@@ -12,7 +12,7 @@ pfUI:RegisterSkin("Auctionhouse", function ()
|
||||
SkinArrowButton(AuctionsNextPageButton, "right", 18)
|
||||
end
|
||||
|
||||
hooksecurefunc("AuctionFrame_OnShow", function()
|
||||
pfUI.hooksecurefunc("AuctionFrame_OnShow", function()
|
||||
AuctionFrame:ClearAllPoints()
|
||||
AuctionFrame:SetPoint("TOPLEFT", 10, -104)
|
||||
end)
|
||||
@@ -76,7 +76,7 @@ pfUI:RegisterSkin("Auctionhouse", function ()
|
||||
item:SetPoint("LEFT", 2, 0)
|
||||
end
|
||||
end
|
||||
hooksecurefunc("AuctionFrameBrowse_Update", function()
|
||||
pfUI.hooksecurefunc("AuctionFrameBrowse_Update", function()
|
||||
for i = 1, NUM_BROWSE_TO_DISPLAY do
|
||||
HandleIcon(_G["BrowseButton"..i.."Item"], _G["BrowseButton"..i.."ItemIconTexture"])
|
||||
end
|
||||
@@ -145,7 +145,7 @@ pfUI:RegisterSkin("Auctionhouse", function ()
|
||||
item:ClearAllPoints()
|
||||
item:SetPoint("LEFT", 2, 0)
|
||||
end
|
||||
hooksecurefunc("AuctionFrameBid_Update", function()
|
||||
pfUI.hooksecurefunc("AuctionFrameBid_Update", function()
|
||||
for i = 1, NUM_BIDS_TO_DISPLAY do
|
||||
HandleIcon(_G["BidButton"..i.."Item"], _G["BidButton"..i.."ItemIconTexture"])
|
||||
end
|
||||
@@ -191,14 +191,14 @@ pfUI:RegisterSkin("Auctionhouse", function ()
|
||||
item:ClearAllPoints()
|
||||
item:SetPoint("LEFT", 2, 0)
|
||||
end
|
||||
hooksecurefunc("AuctionFrameAuctions_Update", function()
|
||||
pfUI.hooksecurefunc("AuctionFrameAuctions_Update", function()
|
||||
for i = 1, NUM_AUCTIONS_TO_DISPLAY do
|
||||
HandleIcon(_G["AuctionsButton"..i.."Item"], _G["AuctionsButton"..i.."ItemIconTexture"])
|
||||
end
|
||||
end)
|
||||
|
||||
SkinButton(AuctionsItemButton)
|
||||
hooksecurefunc("AuctionSellItemButton_OnEvent", function()
|
||||
pfUI.hooksecurefunc("AuctionSellItemButton_OnEvent", function()
|
||||
if event ~= "NEW_AUCTION_UPDATE" then return end
|
||||
HandleIcon(AuctionsItemButton, AuctionsItemButton:GetNormalTexture())
|
||||
end)
|
||||
|
||||
@@ -15,11 +15,11 @@ pfUI:RegisterSkin("Battlefield Minimap", function ()
|
||||
BattlefieldMinimapTabText:ClearAllPoints()
|
||||
BattlefieldMinimapTabText:SetPoint("CENTER", 0, 0)
|
||||
|
||||
HookScript(BattlefieldMinimap, "OnShow", function()
|
||||
BattlefieldMinimap:HookScript("OnShow", function()
|
||||
BattlefieldMinimapTab:Hide()
|
||||
end)
|
||||
|
||||
hooksecurefunc("BattlefieldMinimap_ShowOpacity", function()
|
||||
pfUI.hooksecurefunc("BattlefieldMinimap_ShowOpacity", function()
|
||||
OpacityFrame:ClearAllPoints()
|
||||
OpacityFrame:SetPoint("TOPRIGHT", "BattlefieldMinimap", "TOPLEFT", -2*border, 0)
|
||||
end)
|
||||
|
||||
@@ -2,25 +2,13 @@ pfUI:RegisterSkin("Character", function ()
|
||||
local rawborder, border = GetBorderSize()
|
||||
local bpad = rawborder > 1 and border - GetPerfectPixel() or GetPerfectPixel()
|
||||
|
||||
-- Compatibility
|
||||
if PlayerTitleDropDown then -- tbc, wotlk
|
||||
-- Character Tab
|
||||
SkinDropDown(PlayerTitleDropDown)
|
||||
PlayerTitleDropDown:SetPoint("TOP", CharacterLevelText, "BOTTOM", 0, -2)
|
||||
PlayerTitleDropDownText:SetPoint("LEFT", PlayerTitleDropDown.backdrop, "LEFT", 6, 2)
|
||||
SkinDropDown(PlayerStatFrameLeftDropDown)
|
||||
SkinDropDown(PlayerStatFrameRightDropDown)
|
||||
-- Honor Tab
|
||||
StripTextures(HonorFrame)
|
||||
StripTextures(ArenaFrame)
|
||||
|
||||
-- Honor Tab
|
||||
StripTextures(PVPFrame)
|
||||
else -- vanilla
|
||||
-- Honor Tab
|
||||
StripTextures(HonorFrame)
|
||||
|
||||
HonorFrameProgressBar:SetStatusBarTexture(pfUI.media["img:bar"])
|
||||
CreateBackdrop(HonorFrameProgressBar)
|
||||
HonorFrameProgressBar:SetHeight(24)
|
||||
end
|
||||
HonorFrameProgressBar:SetStatusBarTexture(pfUI.media["img:bar"])
|
||||
CreateBackdrop(HonorFrameProgressBar)
|
||||
HonorFrameProgressBar:SetHeight(24)
|
||||
|
||||
local magicResTextureCords = {
|
||||
{0.21875, 0.78125, 0.25, 0.3203125},
|
||||
@@ -148,22 +136,19 @@ pfUI:RegisterSkin("Character", function ()
|
||||
end
|
||||
end
|
||||
|
||||
HookScript(CharacterFrame, "OnShow", function()
|
||||
pfUI.hooksecurefunc("CharacterFrame_OnShow", function()
|
||||
RefreshCharacterSlots()
|
||||
RefreshPetPosition()
|
||||
end)
|
||||
|
||||
if not this.hooked then
|
||||
hooksecurefunc("PaperDollItemSlotButton_Update", function()
|
||||
-- update only character slots!
|
||||
if string.find(this:GetName(), "^Character.-Slot$") then
|
||||
RefreshCharacterSlot(this)
|
||||
end
|
||||
end)
|
||||
hooksecurefunc("PetTab_Update", RefreshPetPosition)
|
||||
this.hooked = true
|
||||
pfUI.hooksecurefunc("PaperDollItemSlotButton_Update", function()
|
||||
if this:GetParent() == PaperDollFrame then
|
||||
RefreshCharacterSlot(this)
|
||||
end
|
||||
end)
|
||||
|
||||
pfUI.hooksecurefunc("PetTab_Update", RefreshPetPosition)
|
||||
|
||||
StripTextures(PaperDollFrame)
|
||||
StripTextures(CharacterAttributesFrame)
|
||||
StripTextures(CharacterResistanceFrame)
|
||||
@@ -285,7 +270,7 @@ pfUI:RegisterSkin("Character", function ()
|
||||
-- the FactionStanding text from `bar.standingText` on mouseout, so we
|
||||
-- stash our augmented text there too — otherwise hovering a bar strips
|
||||
-- the "(N)" suffix off.
|
||||
hooksecurefunc("ReputationFrame_Update", function()
|
||||
pfUI.hooksecurefunc("ReputationFrame_Update", function()
|
||||
if C.character.reputation.repRequired ~= "1" then return end
|
||||
local offset = FauxScrollFrame_GetOffset(ReputationListScrollFrame)
|
||||
for i = 1, NUM_FACTIONS_DISPLAYED do
|
||||
|
||||
@@ -150,7 +150,7 @@ pfUI:RegisterSkin("Friends", function ()
|
||||
end
|
||||
|
||||
-- set positions
|
||||
hooksecurefunc("WhoList_Update", function()
|
||||
pfUI.hooksecurefunc("WhoList_Update", function()
|
||||
for i = 1, WHOS_TO_DISPLAY do
|
||||
local level = _G["WhoFrameButton"..i.."Level"]
|
||||
level:ClearAllPoints()
|
||||
@@ -231,7 +231,7 @@ pfUI:RegisterSkin("Friends", function ()
|
||||
end
|
||||
|
||||
-- set positions
|
||||
hooksecurefunc("GuildStatus_Update", function()
|
||||
pfUI.hooksecurefunc("GuildStatus_Update", function()
|
||||
for i = 1, GUILDMEMBERS_TO_DISPLAY do
|
||||
local level = _G["GuildFrameButton"..i.."Level"]
|
||||
level:ClearAllPoints()
|
||||
|
||||
@@ -24,11 +24,11 @@ pfUI:RegisterSkin("Gossip and Quest", function ()
|
||||
QuestRewardItemHighlightBG:SetTexture(1,1,1,.2)
|
||||
QuestRewardItemHighlightBG:SetAllPoints()
|
||||
|
||||
hooksecurefunc("QuestFrameItems_Update", function()
|
||||
pfUI.hooksecurefunc("QuestFrameItems_Update", function()
|
||||
QuestRewardItemHighlight:Hide()
|
||||
end)
|
||||
|
||||
hooksecurefunc("QuestRewardItem_OnClick", function()
|
||||
pfUI.hooksecurefunc("QuestRewardItem_OnClick", function()
|
||||
if this.type == "choice" then
|
||||
QuestRewardItemHighlight:SetAllPoints(this.backdrop)
|
||||
QuestRewardItemHighlight:Show()
|
||||
|
||||
@@ -86,7 +86,7 @@ pfUI:RegisterSkin("Books", function ()
|
||||
ItemTextScrollFrameScrollBarScrollDownButton.Hide = function(self) self:Disable() end
|
||||
|
||||
local first
|
||||
HookScript(ItemTextFrame, "OnShow", function()
|
||||
ItemTextFrame:HookScript("OnShow", function()
|
||||
if not first then -- it is necessary to update the scrollbar when you first open the frame
|
||||
ItemTextScrollFrameScrollBar:Show()
|
||||
ItemTextScrollFrameScrollBar:Hide()
|
||||
|
||||
@@ -12,7 +12,7 @@ pfUI:RegisterSkin("Mailbox", function ()
|
||||
StripTextures(SendMailPackageButton)
|
||||
SkinButton(SendMailPackageButton, nil, nil, nil, nil, true)
|
||||
|
||||
hooksecurefunc("SendMailFrame_Update", function()
|
||||
pfUI.hooksecurefunc("SendMailFrame_Update", function()
|
||||
HandleIcon(SendMailPackageButton, SendMailPackageButton:GetNormalTexture())
|
||||
|
||||
local _, itemID = GetSendMailItemLink()
|
||||
@@ -62,7 +62,7 @@ pfUI:RegisterSkin("Mailbox", function ()
|
||||
do -- OpenMailFrame
|
||||
SkinButton(OpenMailPackageButton, nil, nil, nil, OpenMailPackageButtonIconTexture)
|
||||
|
||||
hooksecurefunc("InboxFrame_OnClick", function(index)
|
||||
pfUI.hooksecurefunc("InboxFrame_OnClick", function(index)
|
||||
local _, itemID = GetInboxItemLink(index)
|
||||
if itemID then
|
||||
local quality = C_Item.GetItemQualityByID(itemID)
|
||||
|
||||
@@ -6,7 +6,7 @@ pfUI:RegisterSkin("Merchant", function ()
|
||||
if MerchantGuildBankRepairButton then -- tbc
|
||||
SkinButton(MerchantGuildBankRepairButton, nil, nil, nil, MerchantGuildBankRepairButtonIcon)
|
||||
MerchantGuildBankRepairButtonIcon:SetTexCoord(.59, .82, .06, .54)
|
||||
hooksecurefunc("MerchantFrame_UpdateRepairButtons", function()
|
||||
pfUI.hooksecurefunc("MerchantFrame_UpdateRepairButtons", function()
|
||||
MerchantGuildBankRepairButton:ClearAllPoints()
|
||||
MerchantGuildBankRepairButton:SetPoint("RIGHT", MerchantBuyBackItemItemButton, "LEFT", -14, 0)
|
||||
MerchantRepairAllButton:ClearAllPoints()
|
||||
@@ -56,7 +56,7 @@ pfUI:RegisterSkin("Merchant", function ()
|
||||
moneyFrame:SetPoint("BOTTOMLEFT", itemButton, "BOTTOMRIGHT", 5, 1)
|
||||
end
|
||||
|
||||
hooksecurefunc("MerchantFrame_UpdateMerchantInfo", function()
|
||||
pfUI.hooksecurefunc("MerchantFrame_UpdateMerchantInfo", function()
|
||||
if MerchantFrame.selectedTab == 1 then
|
||||
for i = 3, 11, 2 do
|
||||
_G["MerchantItem"..i]:ClearAllPoints()
|
||||
|
||||
@@ -11,7 +11,7 @@ pfUI:RegisterSkin("Options - New", function ()
|
||||
CreateBackdropShadow(OptionsFrame)
|
||||
EnableMovable(OptionsFrame)
|
||||
|
||||
HookScript(OptionsFrame, "OnShow", function()
|
||||
OptionsFrame:HookScript("OnShow", function()
|
||||
this:ClearAllPoints()
|
||||
this:SetPoint("CENTER", 0, 0)
|
||||
end)
|
||||
@@ -109,13 +109,13 @@ pfUI:RegisterSkin("Options - New", function ()
|
||||
local color = PFUI_CLASS_COLORS[class]
|
||||
SetHighlight(btn, color.r, color.g, color.b)
|
||||
btn:SetFont(pfUI.font_default, pfUI_config.global.font_size, "OUTLINE")
|
||||
HookScript(btn, "OnMouseDown", function()
|
||||
btn:HookScript("OnMouseDown", function()
|
||||
StripTextures(this, true)
|
||||
this:SetBackdrop(pfUI.backdrop)
|
||||
this:SetBackdropColor(br, bg, bb, 0.75)
|
||||
this:SetBackdropBorderColor(er, eg, eb, 1)
|
||||
end)
|
||||
HookScript(btn, "OnMouseUp", function()
|
||||
btn:HookScript("OnMouseUp", function()
|
||||
StripTextures(this, true)
|
||||
this:SetBackdrop(pfUI.backdrop)
|
||||
this:SetBackdropColor(br, bg, bb, 0.75)
|
||||
@@ -184,7 +184,7 @@ pfUI:RegisterSkin("Options - New", function ()
|
||||
end
|
||||
|
||||
-- hook after category selection: UpdateOptions is local so we hook its caller
|
||||
hooksecurefunc("OptionsListButton_OnClick", SkinControls)
|
||||
pfUI.hooksecurefunc("OptionsListButton_OnClick", SkinControls)
|
||||
-- also cover initial load
|
||||
HookScript(OptionsFrame, "OnShow", SkinControls)
|
||||
OptionsFrame:HookScript("OnShow", SkinControls)
|
||||
end)
|
||||
@@ -44,7 +44,7 @@ pfUI:RegisterSkin("Options - Sound", function ()
|
||||
|
||||
EnableMovable(SoundOptionsFrame)
|
||||
|
||||
HookScript(SoundOptionsFrame, "OnShow", function()
|
||||
SoundOptionsFrame:HookScript("OnShow", function()
|
||||
this:ClearAllPoints()
|
||||
this:SetPoint("CENTER", 0, 0)
|
||||
end)
|
||||
|
||||
@@ -19,7 +19,7 @@ pfUI:RegisterSkin("Options - Video", function ()
|
||||
slider:SetPoint(point, anchor, anchorPoint, x, y - shift)
|
||||
end
|
||||
|
||||
hooksecurefunc("OptionsFrame_Load", function()
|
||||
pfUI.hooksecurefunc("OptionsFrame_Load", function()
|
||||
OptionsFramePixelShaders:SetWidth(230)
|
||||
OptionsFrameMiscellaneous:ClearAllPoints()
|
||||
OptionsFrameMiscellaneous:SetPoint("LEFT", OptionsFramePixelShaders, "RIGHT", 6, 0)
|
||||
@@ -49,7 +49,7 @@ pfUI:RegisterSkin("Options - Video", function ()
|
||||
|
||||
EnableMovable(OptionsFrame)
|
||||
|
||||
HookScript(OptionsFrame, "OnShow", function()
|
||||
OptionsFrame:HookScript("OnShow", function()
|
||||
this:ClearAllPoints()
|
||||
this:SetPoint("CENTER", 0, 0)
|
||||
end)
|
||||
|
||||
@@ -192,7 +192,7 @@ pfUI:RegisterSkin("Profession", function ()
|
||||
reagentlabel:SetTextColor(1,1,1,1)
|
||||
|
||||
local scanner = libtipscan:GetScanner(name)
|
||||
hooksecurefunc(SetSelection, function(id)
|
||||
pfUI.hooksecurefunc(SetSelection, function(id)
|
||||
if id and id ~= 0 then
|
||||
detailscroll:Show()
|
||||
HandleIcon(icon, icon:GetNormalTexture())
|
||||
|
||||
@@ -13,7 +13,7 @@ pfUI:RegisterSkin("Quest Log", function ()
|
||||
|
||||
StripTextures(QUEST_COUNT)
|
||||
QUEST_COUNT:ClearAllPoints()
|
||||
hooksecurefunc("QuestLogUpdateQuestCount", function(numQuests)
|
||||
pfUI.hooksecurefunc("QuestLogUpdateQuestCount", function(numQuests)
|
||||
QUEST_COUNT:ClearAllPoints()
|
||||
QUEST_COUNT:SetPoint("BOTTOMRIGHT", QuestLogFrame, "TOPRIGHT", 0, -50)
|
||||
end)
|
||||
@@ -24,7 +24,7 @@ pfUI:RegisterSkin("Quest Log", function ()
|
||||
QUEST_COUNT:SetPoint("TOPRIGHT", -10, -30)
|
||||
end
|
||||
|
||||
hooksecurefunc("QuestLog_OnShow", function()
|
||||
pfUI.hooksecurefunc("QuestLog_OnShow", function()
|
||||
QuestLogFrame:ClearAllPoints()
|
||||
QuestLogFrame:SetPoint("TOPLEFT", 10, -104)
|
||||
end)
|
||||
@@ -102,13 +102,13 @@ pfUI:RegisterSkin("Quest Log", function ()
|
||||
end
|
||||
end)
|
||||
|
||||
HookScript(QuestLogDetailScrollFrame, "OnHide", function()
|
||||
QuestLogDetailScrollFrame:HookScript("OnHide", function()
|
||||
SkinArrowButton(QuestLogFrameExpandButton, "RIGHT", 21)
|
||||
QuestLogDetailScrollFrame:Hide()
|
||||
QuestLogFrame:SetWidth(340)
|
||||
end)
|
||||
|
||||
HookScript(QuestLogDetailScrollFrame, "OnShow", function()
|
||||
QuestLogDetailScrollFrame:HookScript("OnShow", function()
|
||||
SkinArrowButton(QuestLogFrameExpandButton, "LEFT", 21)
|
||||
QuestLogDetailScrollFrame:Show()
|
||||
QuestLogFrame:SetWidth(676)
|
||||
@@ -163,7 +163,7 @@ pfUI:RegisterSkin("Quest Log", function ()
|
||||
QuestLogListScrollFrame:SetPoint("TOPLEFT", 10, -54)
|
||||
QuestLogListScrollFrame:SetHeight(350)
|
||||
|
||||
hooksecurefunc("QuestLog_Update", function()
|
||||
pfUI.hooksecurefunc("QuestLog_Update", function()
|
||||
local numEntries = GetNumQuestLogEntries()
|
||||
local questIndex, text, level, questTag, isHeader
|
||||
|
||||
|
||||
@@ -38,12 +38,12 @@ pfUI:RegisterSkin("Readycheck", function ()
|
||||
frame.bar.text:SetPoint("CENTER", 0, 0)
|
||||
|
||||
local max
|
||||
hooksecurefunc("ShowReadyCheck", function()
|
||||
pfUI.hooksecurefunc("ShowReadyCheck", function()
|
||||
max = ReadyCheckFrame.timer
|
||||
frame.bar:SetMinMaxValues(0, max)
|
||||
end)
|
||||
|
||||
hooksecurefunc(update_func, function()
|
||||
pfUI.hooksecurefunc(update_func, function()
|
||||
if not ReadyCheckFrame.timer then return end
|
||||
|
||||
local perc = ReadyCheckFrame.timer/max
|
||||
|
||||
@@ -32,7 +32,7 @@ pfUI:RegisterSkin("GM Survey", function ()
|
||||
CreateBackdrop(GMSurveyCommentFrame, nil, true, .75)
|
||||
SkinScrollbar(GMSurveyCommentScrollFrameScrollBar)
|
||||
GMSurveyFrameComment:SetMaxLetters(2000)
|
||||
hooksecurefunc("GMSurveyFrame_Update", function()
|
||||
pfUI.hooksecurefunc("GMSurveyFrame_Update", function()
|
||||
GMSurveyFrameComment:SetWidth(505)
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -7,7 +7,7 @@ pfUI:RegisterSkin("Tooltips", function ()
|
||||
CreateBackdropShadow(tooltip)
|
||||
end
|
||||
|
||||
HookScript(WorldMapTooltip, "OnShow", function()
|
||||
WorldMapTooltip:HookScript("OnShow", function()
|
||||
CreateBackdrop(WorldMapTooltip, nil, nil, alpha)
|
||||
CreateBackdropShadow(WorldMapTooltip)
|
||||
end)
|
||||
@@ -16,7 +16,7 @@ pfUI:RegisterSkin("Tooltips", function ()
|
||||
|
||||
for _, tooltip in pairs({ShoppingTooltip1, ShoppingTooltip2}) do
|
||||
tooltip:SetClampedToScreen(true)
|
||||
HookScript(tooltip, "OnShow", function()
|
||||
tooltip:HookScript("OnShow", function()
|
||||
local a, b, c, x, y = this:GetPoint()
|
||||
if not x or x == 0 then x = (border*2) + ( x or 0 ) + 1 end
|
||||
if a then this:SetPoint(a, b, c, x, y) end
|
||||
|
||||
@@ -41,7 +41,7 @@ pfUI:RegisterSkin("Trade", function ()
|
||||
RecipientButtonBG:SetAllPoints()
|
||||
end
|
||||
|
||||
hooksecurefunc("TradeFrame_UpdateTargetItem", function(id)
|
||||
pfUI.hooksecurefunc("TradeFrame_UpdateTargetItem", function(id)
|
||||
HandleIcon(_G["TradeRecipientItem"..id.."ItemButton"], _G["TradeRecipientItem"..id..'IconTexture'])
|
||||
end)
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ pfUI:RegisterSkin("Trainer", function ()
|
||||
|
||||
StripTextures(ClassTrainerSkillIcon)
|
||||
SkinButton(ClassTrainerSkillIcon, nil, nil, nil, nil, true)
|
||||
hooksecurefunc("ClassTrainer_SetSelection", function()
|
||||
pfUI.hooksecurefunc("ClassTrainer_SetSelection", function()
|
||||
HandleIcon(ClassTrainerSkillIcon, ClassTrainerSkillIcon:GetNormalTexture())
|
||||
end)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user