mirror of
https://github.com/brues-code/pfUI.git
synced 2026-10-19 02:50:50 +00:00
Compare commits
3
Commits
f338deebe7
...
350b1d63ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
350b1d63ae | ||
|
|
c7f42491ef | ||
|
|
6b458c9e27 |
@@ -2,9 +2,9 @@
|
||||
|
||||
[](https://octowow.st/)
|
||||
[](https://github.com/brues-code/ClassicAPI)
|
||||
[](https://github.com/balakethelock/SuperWoW)
|
||||
[](https://github.com/brues-code/nampower)
|
||||
[](https://codeberg.org/konaka/UnitXP_SP3)
|
||||
[](https://github.com/balakethelock/SuperWoW)
|
||||
[](https://github.com/brues-code/UnitXP_SP3)
|
||||
|
||||
**A pfUI fork specifically optimized for ClassicAPI on [Octo WoW](https://octowow.st/) which requires Nampower and SuperWoW with optional UnitXP_SP3 DLL integration.**
|
||||
|
||||
@@ -16,7 +16,7 @@ This version includes significant performance improvements and DLL-enhanced feat
|
||||
4. Copy "pfUI" into Wow-Directory\Interface\AddOns
|
||||
5. Restart Wow
|
||||
|
||||
## Optional DLL Enhancements
|
||||
## DLL Enhancements
|
||||
|
||||
Since pfUI 6.0.0 includes integrations with client-side DLLs for enhanced functionality. These DLLs are permitted on Octo WoW:
|
||||
|
||||
@@ -36,23 +36,24 @@ Provides:
|
||||
- Faster, safer profile sharing - Engine-side serialize/compress/base64 via C_EncodingUtil
|
||||
- C_Timer - After / NewTicker replacing hand-rolled OnUpdate throttles
|
||||
- Feign death, shapeshift and quest-item detection via real API calls
|
||||
- Mouseover Unit Frames
|
||||
- Click-casting
|
||||
- Loot Roll History (/loothistory)
|
||||
- New item highlighting
|
||||
- Plenty other functions
|
||||
|
||||
### [SuperWoW](https://github.com/balakethelock/SuperWoW)
|
||||
|
||||
Provides:
|
||||
- UnitPosition for distance calculations
|
||||
- SetMouseoverUnit for improved targeting
|
||||
|
||||
### [Nampower](https://github.com/brues-code/nampower)
|
||||
|
||||
Provides:
|
||||
- Spell queue indicator
|
||||
- GCD indicator
|
||||
- Reactive spell detection
|
||||
- Enhanced cast information
|
||||
|
||||
### [UnitXP_SP3](https://codeberg.org/konaka/UnitXP_SP3)
|
||||
### [SuperWoW](https://github.com/balakethelock/SuperWoW)
|
||||
|
||||
Provides:
|
||||
- Tracks party/raid units on the minimap
|
||||
|
||||
### [UnitXP_SP3](https://github.com/brues-code/UnitXP_SP3)
|
||||
|
||||
Provides:
|
||||
- Line of Sight detection
|
||||
@@ -79,6 +80,7 @@ Use `/pfdll` in-game to check which DLLs are detected.
|
||||
/swapfocus Toggle Focus and Target-Frame
|
||||
/pftest Toggle pfUI Unitframe Test Mode
|
||||
/abp Addon Button Panel
|
||||
/loothistory Show Loot Roll History
|
||||
|
||||
## Languages
|
||||
pfUI supports and contains language specific code for the following gameclients.
|
||||
@@ -91,7 +93,7 @@ pfUI supports and contains language specific code for the following gameclients.
|
||||
* Russian (ruRU)
|
||||
|
||||
## Recommended Addons
|
||||
* [pfQuest](https://shagu.org/pfQuest) A simple database and quest helper
|
||||
* [pfQuest](https://github.com/brues-code/pfQuest) A simple database and quest helper
|
||||
* [SuperCleveRoidMacros](https://github.com/brues-code/SuperCleveRoidMacros) Supports modern macro formats
|
||||
|
||||
## Plugins
|
||||
|
||||
@@ -155,6 +155,7 @@ function pfUI:LoadConfig()
|
||||
pfUI:UpdateConfig("appearance", "cd", "dynamicsize", "1")
|
||||
pfUI:UpdateConfig("appearance", "castbar", "castbarcolor", ".7,.7,.9,.8")
|
||||
pfUI:UpdateConfig("appearance", "castbar", "channelcolor", ".9,.9,.7,.8")
|
||||
pfUI:UpdateConfig("appearance", "castbar", "failcolor", "1,.2,.2,.8")
|
||||
pfUI:UpdateConfig("appearance", "castbar", "texture", "Interface\\AddOns\\pfUI\\img\\bar")
|
||||
pfUI:UpdateConfig("appearance", "infight", "screen", "0")
|
||||
pfUI:UpdateConfig("appearance", "infight", "aggro", "0")
|
||||
|
||||
+31
-157
@@ -238,6 +238,7 @@ function pfUI.uf:UpdateVisibility()
|
||||
end
|
||||
|
||||
local unitstr = string.format("%s%s", self.label or "", self.id or "")
|
||||
self:SetAttribute("unit", unitstr ~= "" and unitstr or nil)
|
||||
local visibility = string.format("[target=%s,exists] show; hide", unitstr)
|
||||
|
||||
-- Group frames are redundant when the group is already shown as a raid grid:
|
||||
@@ -1110,11 +1111,7 @@ function pfUI.uf.OnUpdate()
|
||||
|
||||
-- update ressurections
|
||||
local ress = libpredict:UnitHasIncomingResurrection(unit)
|
||||
if ress and UnitIsDeadOrGhost(unit) then
|
||||
this.ressIcon:Show()
|
||||
else
|
||||
this.ressIcon:Hide()
|
||||
end
|
||||
this.ressIcon:SetShown(ress and UnitIsDeadOrGhost(unit))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1177,52 +1174,6 @@ function pfUI.uf.OnUpdate()
|
||||
end
|
||||
end
|
||||
|
||||
function pfUI.uf.OnEnter()
|
||||
if not this.label then return end
|
||||
|
||||
local unitstr = this.label .. this.id
|
||||
if UnitExists(unitstr) then
|
||||
SetMouseoverUnit(unitstr)
|
||||
end
|
||||
|
||||
if this.config.showtooltip == "0" then return end
|
||||
GameTooltip_SetDefaultAnchor(GameTooltip, this)
|
||||
GameTooltip:SetUnit(this.label .. this.id)
|
||||
GameTooltip:Show()
|
||||
end
|
||||
|
||||
function pfUI.uf.OnLeave()
|
||||
SetMouseoverUnit("")
|
||||
|
||||
GameTooltip:FadeOut()
|
||||
end
|
||||
|
||||
function pfUI.uf.OnClick()
|
||||
if not this.label and this.unitname then
|
||||
TargetByName(this.unitname, true)
|
||||
else
|
||||
pfUI.uf:ClickAction(arg1)
|
||||
end
|
||||
end
|
||||
|
||||
function pfUI.uf:RightClickAction(unit)
|
||||
if unit == "player" then
|
||||
ToggleDropDownMenu(1, nil, PlayerFrameDropDown, "cursor")
|
||||
elseif unit == "target" then
|
||||
ToggleDropDownMenu(1, nil, TargetFrameDropDown, "cursor")
|
||||
elseif unit == "pet" then
|
||||
ToggleDropDownMenu(1, nil, PetFrameDropDown, "cursor")
|
||||
elseif unit == "party" or strfind(unit, "party%d") then
|
||||
ToggleDropDownMenu(1, nil, getglobal("PartyMemberFrame" .. this.id .. "DropDown"), "cursor")
|
||||
elseif unit == "raid" or strfind(unit, "raid%d") then
|
||||
local name = this.lastUnit
|
||||
local unitstr = this.label .. this.id
|
||||
FriendsDropDown.displayMode = "MENU"
|
||||
FriendsDropDown.initialize = function() UnitPopup_ShowMenu(_G[UIDROPDOWNMENU_OPEN_MENU], "PARTY", unitstr, name, id) end
|
||||
ToggleDropDownMenu(1, nil, FriendsDropDown, "cursor")
|
||||
end
|
||||
end
|
||||
|
||||
function pfUI.uf:EnableEvents()
|
||||
local f = self
|
||||
|
||||
@@ -1261,13 +1212,12 @@ end
|
||||
function pfUI.uf:EnableScripts()
|
||||
local f = self
|
||||
|
||||
f:SetScript("OnClick", pfUI.uf.OnClick)
|
||||
f:SetAttribute("type1", "target")
|
||||
f:SetAttribute("type2", "menu")
|
||||
|
||||
f:SetScript("OnShow", pfUI.uf.OnShow)
|
||||
f:SetScript("OnEvent", pfUI.uf.OnEvent)
|
||||
f:SetScript("OnUpdate", pfUI.uf.OnUpdate)
|
||||
f:SetScript("OnEnter", pfUI.uf.OnEnter)
|
||||
f:SetScript("OnLeave", pfUI.uf.OnLeave)
|
||||
f:EnableClickCast()
|
||||
|
||||
-- add frame to visibility refresh handler
|
||||
@@ -1423,6 +1373,10 @@ function pfUI.uf:CreateUnitFrame(unit, id, config, tick)
|
||||
f:Hide()
|
||||
end
|
||||
|
||||
if f.label ~= "" then
|
||||
f:SetAttribute("unit", f.label .. f.id)
|
||||
end
|
||||
|
||||
-- register frame for clique
|
||||
_G.ClickCastFrames = ClickCastFrames or {}
|
||||
ClickCastFrames[f] = true
|
||||
@@ -1477,11 +1431,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 IsInGroup() then
|
||||
unit.leaderIcon:Show()
|
||||
else
|
||||
unit.leaderIcon:Hide()
|
||||
end
|
||||
unit.leaderIcon:SetShown(unit.config.leadericon == "1" and UnitIsPartyLeader(unitstr) and IsInGroup())
|
||||
end
|
||||
|
||||
if unit.lootIcon then -- Loot Icon
|
||||
@@ -1491,29 +1441,16 @@ function pfUI.uf:RefreshIndicators(unit)
|
||||
-- no third return value here.. but leaving this as a hint
|
||||
local method, group, raid = GetLootMethod()
|
||||
local name = group and UnitName(group == 0 and "player" or "party"..group) or raid and UnitName("raid"..raid) or nil
|
||||
|
||||
if name and name == UnitName(unitstr) then
|
||||
unit.lootIcon:Show()
|
||||
else
|
||||
unit.lootIcon:Hide()
|
||||
end
|
||||
unit.lootIcon:SetShown(name and name == UnitName(unitstr))
|
||||
end
|
||||
end
|
||||
|
||||
if unit.pvpIcon then -- PvP Icon
|
||||
if unit.config.showPVP == "1" and UnitIsPVP(unitstr) then
|
||||
unit.pvpIcon:Show()
|
||||
else
|
||||
unit.pvpIcon:Hide()
|
||||
end
|
||||
unit.pvpIcon:SetShown(unit.config.showPVP == "1" and UnitIsPVP(unitstr))
|
||||
end
|
||||
|
||||
if unit.restIcon and unit:GetName() == "pfPlayer" then -- Rest Icon
|
||||
if C.unitframes.player.showRest == "1" and UnitIsUnit(unitstr, "player") and IsResting() then
|
||||
unit.restIcon:Show()
|
||||
else
|
||||
unit.restIcon:Hide()
|
||||
end
|
||||
unit.restIcon:SetShown(C.unitframes.player.showRest == "1" and UnitIsUnit(unitstr, "player") and IsResting())
|
||||
end
|
||||
|
||||
if unit.happinessIcon and unit:GetName() == "pfPet" then -- Happiness Icon
|
||||
@@ -1583,7 +1520,7 @@ function pfUI.uf:RefreshUnit(unit, component)
|
||||
if not unit.hp then return end
|
||||
if not unit.power then return end
|
||||
if not unit.id then unit.id = "" end
|
||||
local component = component or ""
|
||||
component = component or ""
|
||||
|
||||
-- don't update scanner activity
|
||||
if unit.label == "target" or unit.label == "targettarget" or unit.label == "targettargettarget" then
|
||||
@@ -2126,14 +2063,6 @@ function pfUI.uf:RefreshUnit(unit, component)
|
||||
end
|
||||
end
|
||||
|
||||
local buttons = {
|
||||
[1] = "LeftButton",
|
||||
[2] = "RightButton",
|
||||
[3] = "MiddleButton",
|
||||
[4] = "Button4",
|
||||
[5] = "Button5",
|
||||
}
|
||||
|
||||
local modifiers = {
|
||||
[""] = "",
|
||||
["alt"] = "_alt",
|
||||
@@ -2143,86 +2072,31 @@ local modifiers = {
|
||||
|
||||
function pfUI.uf:EnableClickCast()
|
||||
if self.config.clickcast ~= "1" then return end
|
||||
for bid, button in pairs(buttons) do
|
||||
for bid = 1, 5 do -- LeftButton, RightButton, MiddleButton, Button4, Button5
|
||||
for modifier, mconf in pairs(modifiers) do
|
||||
local bconf = bid == 1 and "" or bid
|
||||
if pfUI_config.unitframes["clickcast"..bconf..mconf] ~= "" then
|
||||
-- fill clickaction table
|
||||
self.clickactions = self.clickactions or {}
|
||||
self.clickactions[modifier..button] = pfUI_config.unitframes["clickcast"..bconf..mconf]
|
||||
local action = pfUI_config.unitframes["clickcast"..bconf..mconf]
|
||||
if action and action ~= "" then
|
||||
local prefix = modifier ~= "" and (modifier .. "-") or ""
|
||||
local low = string.lower(action)
|
||||
if low == "menu" then
|
||||
self:SetAttribute(prefix .. "type" .. bid, "menu")
|
||||
elseif low == "target" then
|
||||
self:SetAttribute(prefix .. "type" .. bid, "target")
|
||||
elseif low == "focus" then
|
||||
self:SetAttribute(prefix .. "type" .. bid, "focus")
|
||||
elseif string.find(action, "^/") then
|
||||
self:SetAttribute(prefix .. "type" .. bid, "macro")
|
||||
self:SetAttribute(prefix .. "macrotext" .. bid, action)
|
||||
else
|
||||
self:SetAttribute(prefix .. "type" .. bid, "spell")
|
||||
self:SetAttribute(prefix .. "spell" .. bid, action)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function pfUI.uf:ClickAction(button)
|
||||
local label = this.label or ""
|
||||
local id = this.id or ""
|
||||
local unitstr = label .. id
|
||||
local showmenu = button == "RightButton" and true or nil
|
||||
if SpellIsTargeting() and button == "RightButton" then
|
||||
SpellStopTargeting()
|
||||
return
|
||||
end
|
||||
|
||||
if SpellIsTargeting() and button == "LeftButton" then
|
||||
SpellTargetUnit(unitstr)
|
||||
elseif CursorHasItem() then
|
||||
DropItemOnUnit(unitstr)
|
||||
end
|
||||
|
||||
-- run click casting if enabled
|
||||
local modstring = ""
|
||||
modstring = IsAltKeyDown() and modstring.."alt" or modstring
|
||||
modstring = IsControlKeyDown() and modstring.."ctrl" or modstring
|
||||
modstring = IsShiftKeyDown() and modstring.."shift" or modstring
|
||||
modstring = modstring..button
|
||||
if this.clickactions and this.clickactions[modstring] then
|
||||
local action_lower = string.lower(this.clickactions[modstring])
|
||||
if string.find(action_lower, "^menu") then
|
||||
-- show menu
|
||||
showmenu = true
|
||||
elseif string.find(action_lower, "^target") then
|
||||
-- target unit
|
||||
showmenu = nil
|
||||
else
|
||||
-- run click cast action
|
||||
local is_macro = string.find(this.clickactions[modstring], "^%/(.+)")
|
||||
|
||||
local tswitch = UnitIsUnit(unitstr, "target")
|
||||
TargetUnit(unitstr)
|
||||
|
||||
if is_macro then
|
||||
RunMacroText(this.clickactions[modstring])
|
||||
else
|
||||
CastSpellByName(this.clickactions[modstring])
|
||||
end
|
||||
|
||||
if not tswitch then TargetLastTarget() end
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- dropdown menus
|
||||
if showmenu then
|
||||
pfUI.uf:RightClickAction(label)
|
||||
return
|
||||
end
|
||||
|
||||
-- drop food on petframe
|
||||
if label == "pet" and CursorHasItem() then
|
||||
local playerClass = UnitClassBase("player")
|
||||
if playerClass == "HUNTER" then
|
||||
DropItemOnUnit("pet")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- default click
|
||||
TargetUnit(unitstr)
|
||||
end
|
||||
|
||||
function pfUI.uf:AddIcon(frame, pos, icon, timeleft, stacks, start, duration)
|
||||
local showtime = frame.config.indicator_time == "1" and true or nil
|
||||
local showstacks = frame.config.indicator_stacks == "1" and true or nil
|
||||
|
||||
+24
-40
@@ -1561,60 +1561,44 @@ pfUI:RegisterModule("actionbar", function ()
|
||||
if reagent_slots[slot] ~= newID then
|
||||
reagent_slots[slot] = newID
|
||||
if newID then
|
||||
reagent_counts[newID] = reagent_counts[newID] or 0
|
||||
reagent_counts[newID] = reagent_counts[newID] or C_Item.GetItemCount(newID)
|
||||
end
|
||||
updatecache[slot] = true
|
||||
end
|
||||
end
|
||||
|
||||
-- Recount every tracked reagent and flag its buttons for a refresh.
|
||||
local RecountReagents = function()
|
||||
for itemID in pairs(reagent_counts) do
|
||||
reagent_counts[itemID] = C_Item.GetItemCount(itemID)
|
||||
end
|
||||
for slot in pairs(reagent_slots) do
|
||||
updatecache[slot] = true
|
||||
end
|
||||
end
|
||||
|
||||
local reagentcounter = CreateFrame("Frame", "pfReagentCounter", UIParent)
|
||||
reagentcounter:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
reagentcounter:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
|
||||
reagentcounter:RegisterEvent("BAG_UPDATE_DELAYED")
|
||||
reagentcounter:SetScript("OnEvent", function()
|
||||
if event == "BAG_UPDATE_DELAYED" then
|
||||
this.event = true
|
||||
if event == "ACTIONBAR_SLOT_CHANGED" then
|
||||
-- arg1 is the changed slot; 0 (or nil) means "all slots"
|
||||
if arg1 and arg1 > 0 then
|
||||
UpdateSlot(arg1)
|
||||
else
|
||||
for slot = 1, 120 do UpdateSlot(slot) end
|
||||
end
|
||||
elseif event == "BAG_UPDATE_DELAYED" then
|
||||
-- inventory changed: refresh the counts we already track
|
||||
RecountReagents()
|
||||
else
|
||||
this.scan = 1
|
||||
-- PLAYER_ENTERING_WORLD: seed the full reagent map (UpdateSlot seeds
|
||||
-- each new reagent's count; a BAG_UPDATE_DELAYED follows during login)
|
||||
for slot = 1, 120 do UpdateSlot(slot) end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Reagent counter update with throttle for performance optimization
|
||||
reagentcounter:SetScript("OnUpdate", function()
|
||||
-- Throttle entire function to 10 FPS for smooth scanning
|
||||
if (this.tick_update or 0) > GetTime() then return end
|
||||
this.tick_update = GetTime() + 0.1
|
||||
|
||||
-- scan one action slot per update
|
||||
if this.scan and this.scan <= 120 then
|
||||
UpdateSlot(this.scan)
|
||||
this.scan = this.scan + 1
|
||||
end
|
||||
|
||||
-- trigger reagent count updates after action scans
|
||||
if this.scan and this.scan >= 120 then
|
||||
this.event = true
|
||||
this.scan = nil
|
||||
end
|
||||
|
||||
-- queue events to fire only once per second
|
||||
if not this.event then return end
|
||||
if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + 1 end
|
||||
|
||||
-- scan for all reagent item counts
|
||||
for itemID in pairs(reagent_counts) do
|
||||
reagent_counts[itemID] = C_Item.GetItemCount(itemID)
|
||||
end
|
||||
|
||||
-- update all actionbar buttons
|
||||
for slot in pairs(reagent_slots) do
|
||||
updatecache[slot] = true
|
||||
end
|
||||
|
||||
-- remove event trigger
|
||||
this.event = nil
|
||||
end)
|
||||
|
||||
function IsReagentAction(slot)
|
||||
return reagent_slots[slot] and true or nil
|
||||
end
|
||||
|
||||
+16
-13
@@ -397,22 +397,25 @@ pfUI:RegisterModule("addonbuttons", function ()
|
||||
pfUI.addonbuttons:ProcessButtons()
|
||||
end)
|
||||
|
||||
pfUI.addonbuttons:SetScript("OnUpdate", function()
|
||||
-- Initial setup on the next frame, once other addons' minimap buttons exist
|
||||
RunNextFrame(function()
|
||||
-- check if the panel should be shown by default
|
||||
if not this.initialized then
|
||||
if C.abuttons.showdefault == "1" and GetNumButtons() > 0 then
|
||||
pfUI.addonbuttons:Show()
|
||||
else
|
||||
pfUI.addonbuttons:Hide()
|
||||
end
|
||||
|
||||
-- update all buttons
|
||||
pfUI.addonbuttons:ProcessButtons()
|
||||
this.initialized = true
|
||||
if C.abuttons.showdefault == "1" and GetNumButtons() > 0 then
|
||||
pfUI.addonbuttons:Show()
|
||||
else
|
||||
pfUI.addonbuttons:Hide()
|
||||
end
|
||||
|
||||
-- throttle updates to once per 5 seconds
|
||||
if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + 5 end
|
||||
-- update all buttons and apply workarounds
|
||||
pfUI.addonbuttons:ProcessButtons()
|
||||
for k, v in pairs(pfUI.addonbuttons.overrides) do
|
||||
_G[k] = v
|
||||
end
|
||||
end)
|
||||
|
||||
-- Rescan minimap buttons every 5 seconds while the panel is shown
|
||||
C_Timer.NewTicker(5, function()
|
||||
if not pfUI.addonbuttons:IsShown() then return end
|
||||
|
||||
-- reload/rescan minimap buttons
|
||||
pfUI.addonbuttons:ProcessButtons()
|
||||
|
||||
+112
-137
@@ -16,9 +16,13 @@ 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)
|
||||
-- Clear cast state on the bar and start the fade-out. On a normal end the bar
|
||||
-- is left at its current fill (a completed cast is already ~full; nothing
|
||||
-- snaps to full, which used to flash for a frame when the next cast stamped
|
||||
-- the bar). When `failed` is set for a cast that was actually in progress, the
|
||||
-- bar flashes full red before fading — the cancelled-cast indicator.
|
||||
local function ClearBar(cb, failed)
|
||||
local wasActive = cb.endTime ~= nil
|
||||
cb.startTime, cb.endTime, cb.isChannel = nil, nil, nil
|
||||
cb.activeName, cb.spellID = nil, nil
|
||||
cb.isTradeskill = nil
|
||||
@@ -26,8 +30,11 @@ pfUI:RegisterModule("castbar", function ()
|
||||
cb.tradeskillSingleMs, cb.currentCraftStart = nil, nil
|
||||
cb.lastMax = nil
|
||||
cb.delay = 0
|
||||
cb.bar:SetMinMaxValues(1, 100)
|
||||
cb.bar:SetValue(100)
|
||||
if failed and wasActive then
|
||||
cb.bar:SetStatusBarColor(GetStringColor(C.appearance.castbar.failcolor))
|
||||
cb.bar:SetMinMaxValues(0, 1)
|
||||
cb.bar:SetValue(1)
|
||||
end
|
||||
if cb.bar.spark then cb.bar.spark:Hide() end
|
||||
cb.fadeout = 1
|
||||
end
|
||||
@@ -65,8 +72,8 @@ pfUI:RegisterModule("castbar", function ()
|
||||
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.
|
||||
-- UNIT_SPELLCAST_START handler). Resets the per-craft spark to the left
|
||||
-- edge of the bar.
|
||||
local function StartTradeskillCraft(cb)
|
||||
cb.currentCraftStart = GetTime() * 1000
|
||||
local remaining = cb.tradeskillTotal - (cb.tradeskillCompleted or 0)
|
||||
@@ -79,7 +86,7 @@ pfUI:RegisterModule("castbar", function ()
|
||||
|
||||
-- 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)
|
||||
local function StampBar(cb, name, tex, startMs, endMs, spellID, isChannel, delayMs, isTradeskill, rank)
|
||||
cb.startTime = startMs
|
||||
cb.endTime = endMs
|
||||
cb.isChannel = isChannel
|
||||
@@ -92,10 +99,13 @@ pfUI:RegisterModule("castbar", function ()
|
||||
|
||||
cb.bar:SetStatusBarColor(GetStringColor(C.appearance.castbar[isChannel and "channelcolor" or "castbarcolor"]))
|
||||
|
||||
local rank = ""
|
||||
if spellID then
|
||||
-- Rank: prefer the value the UNIT_SPELLCAST_* event delivered (arg5, passed
|
||||
-- through by RefreshBar). Only the retarget re-poll has no event in hand, so
|
||||
-- it falls back to a lookup.
|
||||
if not rank and spellID then
|
||||
rank = C_Spell.GetSpellSubtext(spellID) or ""
|
||||
end
|
||||
rank = rank or ""
|
||||
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)
|
||||
@@ -122,11 +132,21 @@ pfUI:RegisterModule("castbar", function ()
|
||||
|
||||
cb.bar:SetMinMaxValues(0, duration)
|
||||
cb.lastMax = duration
|
||||
|
||||
-- Prime the fill on this frame. StampBar otherwise leaves the previous
|
||||
-- value in place (ClearBar, run on the prior cast's STOP, leaves it full),
|
||||
-- so the bar would flash full for the frame between here and the next
|
||||
-- OnUpdate tick. Reset the throttle too so the timer text updates promptly.
|
||||
local nowSec = GetTime()
|
||||
local cur = isChannel and (endMs / 1000 - nowSec) or (nowSec - startMs / 1000)
|
||||
if cur < 0 then cur = 0 elseif cur > duration then cur = duration end
|
||||
cb.bar:SetValue(cur)
|
||||
cb.tick = 0
|
||||
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 function RefreshBar(cb, rank)
|
||||
local query = cb.unitstr ~= "" and cb.unitstr or cb.unitname
|
||||
if not query or (cb.unitstr ~= "" and not UnitExists(cb.unitstr)) then
|
||||
ClearBar(cb)
|
||||
@@ -149,7 +169,7 @@ pfUI:RegisterModule("castbar", function ()
|
||||
end
|
||||
end
|
||||
if name and startMs and endMs then
|
||||
StampBar(cb, name, tex, startMs, endMs, spellID, isChan, delayMs, isTradeskill)
|
||||
StampBar(cb, name, tex, startMs, endMs, spellID, isChan, delayMs, isTradeskill, rank)
|
||||
else
|
||||
ClearBar(cb)
|
||||
end
|
||||
@@ -294,35 +314,27 @@ pfUI:RegisterModule("castbar", function ()
|
||||
end
|
||||
end)
|
||||
|
||||
-- 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
|
||||
-- Cast lifecycle, entirely on ClassicAPI's UNIT_SPELLCAST_* events. They
|
||||
-- fire per unit token: arg1=="player" for the player's own casts, and the
|
||||
-- remote token(s) ("target", "focus", ...) for other units -- so one set of
|
||||
-- events drives every bar with no Nampower dependency. The handler routes an
|
||||
-- event to this bar when arg1 matches its unit, or -- since the player's own
|
||||
-- casts only ever fire arg1=="player" -- when the bar's unit resolves to the
|
||||
-- player (target=self). PLAYER_TARGET/FOCUS_CHANGED re-polls so a unit
|
||||
-- already mid-cast when it becomes the target/focus still shows.
|
||||
cb:RegisterEvent("UNIT_SPELLCAST_START")
|
||||
cb:RegisterEvent("UNIT_SPELLCAST_STOP")
|
||||
cb:RegisterEvent("UNIT_SPELLCAST_FAILED")
|
||||
cb:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
|
||||
cb:RegisterEvent("UNIT_SPELLCAST_DELAYED")
|
||||
cb:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
|
||||
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
|
||||
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
|
||||
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
|
||||
if unitstr == "target" then
|
||||
cb:RegisterEvent("PLAYER_TARGET_CHANGED")
|
||||
elseif unitstr == "focus" then
|
||||
cb:RegisterEvent("PLAYER_FOCUS_CHANGED")
|
||||
end
|
||||
|
||||
cb:SetScript("OnEvent", function()
|
||||
@@ -333,83 +345,33 @@ pfUI:RegisterModule("castbar", function ()
|
||||
return
|
||||
end
|
||||
|
||||
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
|
||||
-- UNIT_SPELLCAST_* fire per unit token (arg1). Handle an event when it's
|
||||
-- for this bar's unit, or -- since the player's own casts only ever fire
|
||||
-- arg1=="player" -- when this bar's unit currently resolves to the player
|
||||
-- (target=self / focus=self).
|
||||
-- Args: arg1=unit, arg2=castGUID, arg3=spellID, arg4=name, arg5=rank.
|
||||
if arg1 ~= unit and not (arg1 == "player" and UnitIsUnit(unit, "player")) then
|
||||
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 event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then
|
||||
-- START also fires per craft in a same-spell chain, so an active merge
|
||||
-- just resyncs the current craft's spark/label instead of restamping.
|
||||
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)
|
||||
RefreshBar(this, arg5)
|
||||
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
|
||||
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
|
||||
-- Per-craft completion during a tradeskill merge (arg3 = spellID):
|
||||
-- count and clear when the chain is done. A no-op for normal casts,
|
||||
-- which are cleared by UNIT_SPELLCAST_STOP.
|
||||
if this.tradeskillTotal and arg3 == this.tradeskillSpellID then
|
||||
this.tradeskillCompleted = (this.tradeskillCompleted or 0) + 1
|
||||
if this.tradeskillCompleted >= this.tradeskillTotal then
|
||||
ClearBar(this)
|
||||
@@ -417,33 +379,46 @@ pfUI:RegisterModule("castbar", function ()
|
||||
UpdateTradeskillLabel(this)
|
||||
end
|
||||
end
|
||||
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
|
||||
elseif event == "UNIT_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 == "UNIT_SPELLCAST_STOP" then
|
||||
-- STOP fires between crafts in a merge too (each craft is a new cast);
|
||||
-- UNIT_SPELLCAST_SUCCEEDED owns the count, so only a non-merge cast
|
||||
-- clears here.
|
||||
if not this.tradeskillTotal then ClearBar(this) end
|
||||
elseif event == "UNIT_SPELLCAST_FAILED" or event == "UNIT_SPELLCAST_INTERRUPTED" then
|
||||
ClearBar(this, true)
|
||||
elseif event == "UNIT_SPELLCAST_DELAYED" or event == "UNIT_SPELLCAST_CHANNEL_UPDATE" then
|
||||
-- Pushback: a cast delayed later or a channel shortened. Following
|
||||
-- Quartz, re-poll just the times and accumulate the shift into a running
|
||||
-- this.delay for the +/- indicator, rather than a full restamp (which
|
||||
-- would reset that total and re-render icon/text). ClassicAPI moves
|
||||
-- endMs (SpellDelayed_h bumps g_cast.endMs; the MSG_CHANNEL_UPDATE
|
||||
-- co-hook rewrites g_channel.endMs) while startMs stays put, so we diff
|
||||
-- endMs. delay is a positive magnitude; OnUpdate signs it "+" for casts
|
||||
-- and "-" for channels. Skipped during a tradeskill merge.
|
||||
if this.endTime and not this.tradeskillTotal then
|
||||
local query = this.unitstr ~= "" and this.unitstr or this.unitname
|
||||
local startMs, endMs
|
||||
if this.isChannel then
|
||||
local _, _, _, s, e = C_Spell.UnitChannelInfo(query)
|
||||
startMs, endMs = s, e
|
||||
else
|
||||
local _, _, _, s, e = C_Spell.UnitCastingInfo(query)
|
||||
startMs, endMs = s, e
|
||||
end
|
||||
if startMs and endMs then
|
||||
local shift = this.isChannel and (this.endTime - endMs) or (endMs - this.endTime)
|
||||
this.delay = (this.delay or 0) + shift / 1000
|
||||
this.startTime = startMs
|
||||
this.endTime = endMs
|
||||
local newDuration = (endMs - startMs) / 1000
|
||||
this.bar:SetMinMaxValues(0, newDuration)
|
||||
this.lastMax = newDuration
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -489,10 +464,10 @@ pfUI:RegisterModule("castbar", function ()
|
||||
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.
|
||||
-- requested count before the first UNIT_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 UNIT_SPELLCAST_START gives us plenty of time.
|
||||
hooksecurefunc("DoTradeSkill", function(index, num)
|
||||
if pfUI.castbar.player then
|
||||
pfUI.castbar.player.pendingTradeskillCount = tonumber(num) or 1
|
||||
|
||||
+54
-59
@@ -1,57 +1,32 @@
|
||||
pfUI:RegisterModule("marktracking", function ()
|
||||
-- Requires mark1-mark8 unit tokens (Turtle WoW / Nampower)
|
||||
if not UnitExists("mark1") and not UnitExists("mark8") then
|
||||
if not pcall(function() UnitExists("mark1") end) then return end
|
||||
end
|
||||
|
||||
local rawborder, border = GetBorderSize()
|
||||
|
||||
-- Parse color strings "r,g,b,a" into components
|
||||
local function ParseColor(str, dr, dg, db, da)
|
||||
if not str or str == "" then return dr, dg, db, da end
|
||||
local _, _, r, g, b, a = string.find(str, "([%d%.]+),([%d%.]+),([%d%.]+),([%d%.]+)")
|
||||
if r then
|
||||
return tonumber(r) or dr, tonumber(g) or dg, tonumber(b) or db, tonumber(a) or da
|
||||
end
|
||||
return dr, dg, db, da
|
||||
end
|
||||
|
||||
local markerOrder = { 8, 7, 6, 5, 4, 3, 2, 1 } -- skull, cross, square, moon, triangle, diamond, circle, star
|
||||
local markerTokens = {}
|
||||
for i = 1, 8 do markerTokens[i] = "mark" .. i end
|
||||
|
||||
-- Default colors per marker
|
||||
local defaultColors = {
|
||||
[1] = { 1.0, 0.9, 0.0, 1 }, -- star: yellow
|
||||
[2] = { 1.0, 0.5, 0.0, 1 }, -- circle: orange
|
||||
[3] = { 0.8, 0.0, 0.8, 1 }, -- diamond: purple
|
||||
[4] = { 0.0, 0.8, 0.0, 1 }, -- triangle: green
|
||||
[5] = { 0.7, 0.7, 0.7, 1 }, -- moon: silver
|
||||
[6] = { 0.0, 0.4, 0.9, 1 }, -- square: blue
|
||||
[7] = { 0.9, 0.0, 0.0, 1 }, -- cross: red
|
||||
[8] = { 1.0, 1.0, 1.0, 1 }, -- skull: white
|
||||
}
|
||||
|
||||
local markerConfigKeys = {
|
||||
[1] = "raidmarkercolor_star",
|
||||
[2] = "raidmarkercolor_circle",
|
||||
[3] = "raidmarkercolor_diamond",
|
||||
[4] = "raidmarkercolor_triangle",
|
||||
[5] = "raidmarkercolor_moon",
|
||||
[6] = "raidmarkercolor_square",
|
||||
[7] = "raidmarkercolor_cross",
|
||||
[8] = "raidmarkercolor_skull",
|
||||
"raidmarkercolor_star",
|
||||
"raidmarkercolor_circle",
|
||||
"raidmarkercolor_diamond",
|
||||
"raidmarkercolor_triangle",
|
||||
"raidmarkercolor_moon",
|
||||
"raidmarkercolor_square",
|
||||
"raidmarkercolor_cross",
|
||||
"raidmarkercolor_skull",
|
||||
}
|
||||
|
||||
local markerColors = {}
|
||||
for i = 1, 8 do
|
||||
local d = defaultColors[i]
|
||||
local r, g, b, a = ParseColor(C.unitframes[markerConfigKeys[i]], d[1], d[2], d[3], d[4])
|
||||
markerColors[i] = { r, g, b, a }
|
||||
for i, markKey in ipairs(markerConfigKeys) do
|
||||
markerTokens[i] = "mark" .. i
|
||||
local r, g, b, a = GetStringColor(C.unitframes[markKey])
|
||||
markerColors[i] = { tonumber(r), tonumber(g), tonumber(b), tonumber(a) }
|
||||
end
|
||||
|
||||
local FALLBACK_INTERVAL = 1.0 -- safety net for units that come into range after marker was set
|
||||
local elapsed = 0
|
||||
local isUnlocked = false
|
||||
local ROW_HEIGHT = tonumber(C.unitframes.raidmarkerheight) or 14
|
||||
local BAR_WIDTH = tonumber(C.unitframes.raidmarkerwidth) or 80
|
||||
@@ -125,19 +100,16 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
|
||||
local i = markerOrder[idx]
|
||||
|
||||
local row = CreateFrame("Button", nil, pfUI.marktracking)
|
||||
row:SetWidth(TOTAL_ROW_WIDTH)
|
||||
row:SetHeight(ROW_HEIGHT)
|
||||
row:SetSize(TOTAL_ROW_WIDTH, ROW_HEIGHT)
|
||||
row:Hide()
|
||||
|
||||
row:RegisterForClicks("LeftButtonUp")
|
||||
row:SetScript("OnClick", function()
|
||||
TargetUnit(markerTokens[this.markerIndex])
|
||||
end)
|
||||
row:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||||
row:SetAttribute("type1", "target")
|
||||
row:SetAttribute("type2", "menu")
|
||||
|
||||
-- raid icon
|
||||
row.icon = row:CreateTexture(nil, "ARTWORK")
|
||||
row.icon:SetWidth(ROW_HEIGHT)
|
||||
row.icon:SetHeight(ROW_HEIGHT)
|
||||
row.icon:SetSize(ROW_HEIGHT, ROW_HEIGHT)
|
||||
row.icon:SetPoint("LEFT", row, "LEFT", 1, 0)
|
||||
local markTex = C.unitframes.blizzard_raidicons == "1" and "Interface\\TargetingFrame\\UI-RaidTargetingIcons" or pfUI.media["img:raidicons"]
|
||||
row.icon:SetTexture(markTex)
|
||||
@@ -145,8 +117,7 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
|
||||
|
||||
-- portrait (right side)
|
||||
row.portrait = row:CreateTexture(nil, "ARTWORK")
|
||||
row.portrait:SetWidth(PORTRAIT_SIZE)
|
||||
row.portrait:SetHeight(PORTRAIT_SIZE)
|
||||
row.portrait:SetSize(PORTRAIT_SIZE, PORTRAIT_SIZE)
|
||||
row.portrait:SetPoint("RIGHT", row, "RIGHT", -1, 0)
|
||||
row.portrait:SetTexCoord(.1, .9, .1, .9)
|
||||
if not rm_showportrait then row.portrait:Hide() end
|
||||
@@ -204,8 +175,8 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
|
||||
local token = markerTokens[i]
|
||||
|
||||
if UnitExists(token) and not UnitIsDead(token) then
|
||||
local hp = UnitHealth(token)
|
||||
local maxhp = UnitHealthMax(token)
|
||||
local hp, maxhp = UnitHealth(token), UnitHealthMax(token)
|
||||
row:SetAttribute('unit', token)
|
||||
|
||||
if hp and maxhp and maxhp > 0 and hp > 0 then
|
||||
local pct = hp / maxhp
|
||||
@@ -267,6 +238,27 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
|
||||
end
|
||||
end
|
||||
|
||||
-- Fast per-row refresh for a single mark's UNIT_HEALTH/UNIT_MAXHEALTH: moves
|
||||
-- just the bar + hp text. If the mark's visibility flips (comes into range,
|
||||
-- dies, hp crosses 0) the visible rows re-pack, so hand off to UpdateDisplay.
|
||||
local function UpdateRow(i)
|
||||
if isUnlocked then return end
|
||||
local row = pfUI.marktracking.rows[i]
|
||||
if not row then return end
|
||||
local token = markerTokens[i]
|
||||
local hp, maxhp = UnitHealth(token), UnitHealthMax(token)
|
||||
local shouldShow = UnitExists(token) and not UnitIsDead(token)
|
||||
and hp and maxhp and maxhp > 0 and hp > 0 and true or false
|
||||
if shouldShow ~= (row:IsShown() and true or false) then
|
||||
UpdateDisplay()
|
||||
return
|
||||
end
|
||||
if not shouldShow then return end
|
||||
local pct = hp / maxhp
|
||||
row.health:SetValue(pct)
|
||||
if rm_showpct then row.hptext:SetText(math.ceil(pct * 100) .. "%") end
|
||||
end
|
||||
|
||||
-- Unlock mode: show fixed 1-row placeholder so positioning works correctly
|
||||
if pfUI.unlock then
|
||||
local origShow = pfUI.unlock:GetScript("OnShow")
|
||||
@@ -292,24 +284,27 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
|
||||
-- Event-driven scanner frame
|
||||
local scanner = CreateFrame("Frame")
|
||||
|
||||
-- RAID_TARGET_UPDATE: fires when a raid marker is set/cleared
|
||||
-- PLAYER_ENTERING_WORLD: fires on login, reload, and zone transitions
|
||||
-- UNIT_HEALTH/UNIT_MAXHEALTH: fires on HP changes for real-time bar updates
|
||||
-- RAID_TARGET_UPDATE: a raid marker was set/cleared -> full refresh
|
||||
-- PLAYER_ENTERING_WORLD: login/reload/zone -> full refresh
|
||||
-- UNIT_HEALTH/UNIT_MAXHEALTH: ClassicAPI fires these per token; with the mark
|
||||
-- tokens observed they arrive as arg1 == "markN", so we refresh just that
|
||||
-- one row (UpdateRow) instead of rescanning all eight.
|
||||
scanner:RegisterEvent("RAID_TARGET_UPDATE")
|
||||
scanner:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
scanner:RegisterEvent("UNIT_HEALTH")
|
||||
scanner:RegisterEvent("UNIT_MAXHEALTH")
|
||||
|
||||
scanner:SetScript("OnEvent", function()
|
||||
if event == "UNIT_HEALTH" or event == "UNIT_MAXHEALTH" then
|
||||
-- arg1 is the token; "markN" -> N, nil for any non-mark token.
|
||||
local i = arg1 and tonumber(string.match(arg1, "^mark(%d)"))
|
||||
if i then UpdateRow(i) end
|
||||
return
|
||||
end
|
||||
UpdateDisplay()
|
||||
end)
|
||||
|
||||
-- Fallback poll at 1s: catches units that come into range AFTER a marker was set
|
||||
-- Fallback poll: catches units that come into range AFTER a marker was set
|
||||
-- (no event fires for that case, so we need this safety net)
|
||||
scanner:SetScript("OnUpdate", function()
|
||||
elapsed = elapsed + arg1
|
||||
if elapsed < FALLBACK_INTERVAL then return end
|
||||
elapsed = 0
|
||||
UpdateDisplay()
|
||||
end)
|
||||
C_Timer.NewTicker(FALLBACK_INTERVAL, UpdateDisplay)
|
||||
end)
|
||||
+7
-9
@@ -131,18 +131,16 @@ pfUI:RegisterModule("minimap", function ()
|
||||
|
||||
-- Create coordinates text frame with location configurable
|
||||
pfUI.minimapCoordinates = CreateFrame("Frame", "pfMinimapCoord", pfUI.minimap)
|
||||
pfUI.minimapCoordinates:SetScript("OnUpdate", function()
|
||||
-- Throttle to update coords every 0.1 seconds
|
||||
if ( this.tick or 0) > GetTime() then return end
|
||||
this.tick = GetTime() + .1
|
||||
|
||||
-- Update coords every 0.1 seconds
|
||||
C_Timer.NewTicker(0.1, function()
|
||||
if C.appearance.minimap.coordstext == "off" then return end
|
||||
|
||||
this.posX, this.posY = GetPlayerMapPosition("player")
|
||||
if this.posX ~= 0 and this.posY ~= 0 then
|
||||
this.text:SetText(string.format("%.1f, %.1f", round(this.posX * 100, 1), round(this.posY * 100, 1)))
|
||||
local coord = pfUI.minimapCoordinates
|
||||
coord.posX, coord.posY = GetPlayerMapPosition("player")
|
||||
if coord.posX ~= 0 and coord.posY ~= 0 then
|
||||
coord.text:SetText(string.format("%.1f, %.1f", round(coord.posX * 100, 1), round(coord.posY * 100, 1)))
|
||||
else
|
||||
this.text:SetText("|cffffaaaaN/A")
|
||||
coord.text:SetText("|cffffaaaaN/A")
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
+37
-41
@@ -64,18 +64,19 @@ pfUI:RegisterModule("nameplates", function ()
|
||||
|
||||
local raidGuidCache = {} -- guid -> name (rebuilt on RAID_ROSTER_UPDATE/PARTY_MEMBERS_CHANGED)
|
||||
|
||||
-- Per-GUID cast state, populated by nampower's SPELL_START_OTHER events and
|
||||
-- cleared on SPELL_FAILED_OTHER / plate removal / expiry. This replaces the
|
||||
-- old per-tick C_Spell poll on every visible plate: cast detection is now
|
||||
-- event driven, and GetCastInfo just reads this cache.
|
||||
-- Per-GUID cast state, populated from ClassicAPI's UNIT_SPELLCAST_* events
|
||||
-- (which now fire for nameplate tokens) and cleared on STOP / plate removal /
|
||||
-- expiry. This replaces the old per-tick C_Spell poll on every visible plate:
|
||||
-- cast detection is event driven, and GetCastInfo just reads this cache.
|
||||
local castState = {}
|
||||
-- guid -> nameplate, maintained on NAME_PLATE_UNIT_ADDED/_REMOVED so a cast
|
||||
-- event can find its plate in O(1) and only cache casts we actually show.
|
||||
local plateByGuid = {}
|
||||
|
||||
-- One-shot C_Spell poll. Only used to seed a plate that spawns while its
|
||||
-- unit is already mid-cast (its SPELL_START_OTHER fired before the plate
|
||||
-- existed). Never called per frame.
|
||||
-- One-shot C_Spell poll. Builds the cast struct for a UNIT_SPELLCAST_* event
|
||||
-- (the payload carries no timing) and seeds a plate that spawns while its unit
|
||||
-- is already mid-cast (its START fired before the plate existed). Picks cast
|
||||
-- vs channel itself. Never called per frame.
|
||||
local function PollCastInfo(unit)
|
||||
if not unit then return nil end
|
||||
local name, _, texture, startMs, endMs, _, _, _, spellID = C_Spell.UnitCastingInfo(unit)
|
||||
@@ -477,8 +478,10 @@ nameplates:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
|
||||
nameplates:RegisterEvent("UNIT_AURA")
|
||||
nameplates:RegisterEvent("UNIT_FLAGS")
|
||||
nameplates:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
|
||||
nameplates:RegisterEvent("SPELL_START_OTHER")
|
||||
nameplates:RegisterEvent("SPELL_FAILED_OTHER")
|
||||
nameplates:RegisterEvent("UNIT_SPELLCAST_START")
|
||||
nameplates:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
|
||||
nameplates:RegisterEvent("UNIT_SPELLCAST_STOP")
|
||||
nameplates:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
|
||||
|
||||
nameplates:SetScript("OnEvent", function()
|
||||
-- Stop event handling during logout to prevent crash 132
|
||||
@@ -562,9 +565,9 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
|
||||
plate.nameplate.unit = arg1
|
||||
if guid then
|
||||
plateByGuid[guid] = plate.nameplate
|
||||
-- Seed: the unit may already be mid-cast (its SPELL_START_OTHER fired
|
||||
-- before this plate existed). One poll here catches that; ongoing
|
||||
-- casts arrive via the event.
|
||||
-- Seed: the unit may already be mid-cast (its UNIT_SPELLCAST_START
|
||||
-- fired before this plate existed). One poll here catches that;
|
||||
-- ongoing casts arrive via the event.
|
||||
castState[guid] = PollCastInfo(arg1)
|
||||
end
|
||||
nameplates.OnShow(plate)
|
||||
@@ -609,39 +612,32 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
|
||||
if pn then pn.eventcache = true end
|
||||
end
|
||||
|
||||
elseif event == "SPELL_START_OTHER" then
|
||||
-- nampower: arg2=spellId, arg3=casterGuid, arg6=castTime(ms),
|
||||
-- arg7=channel duration(ms, 0 if not a channel), arg8=spellType
|
||||
-- (1 = channel). Cache the cast only for a unit we have a plate for, so
|
||||
elseif event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then
|
||||
-- ClassicAPI fires UNIT_SPELLCAST_* per unit token, including the caster's
|
||||
-- "nameplateN". The payload has no timing, so poll it (PollCastInfo picks
|
||||
-- cast vs channel) and cache -- only for a unit we have a plate for, so
|
||||
-- the table stays bounded to on-screen casters.
|
||||
local casterGuid = arg3
|
||||
local plate = casterGuid and plateByGuid[casterGuid]
|
||||
if plate then
|
||||
local isChannel = arg8 == 1
|
||||
local durationMs = isChannel and arg7 or arg6
|
||||
if durationMs and durationMs > 0 then
|
||||
local spellId = arg2
|
||||
local now = GetTime()
|
||||
castState[casterGuid] = {
|
||||
spellName = C_Spell.GetSpellName(spellId),
|
||||
spellID = spellId,
|
||||
icon = C_Spell.GetSpellTexture(spellId),
|
||||
startTime = now,
|
||||
endTime = now + durationMs / 1000,
|
||||
duration = durationMs / 1000,
|
||||
isChannel = isChannel,
|
||||
}
|
||||
plate.castUpdate = true -- bypass the throttle so the bar shows now
|
||||
if arg1 and strfind(arg1, "^nameplate") then
|
||||
local guid = UnitGUID(arg1)
|
||||
local plate = guid and plateByGuid[guid]
|
||||
if plate then
|
||||
castState[guid] = PollCastInfo(arg1)
|
||||
if castState[guid] then
|
||||
plate.castUpdate = true -- bypass the throttle so the bar shows now
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif event == "SPELL_FAILED_OTHER" then
|
||||
-- nampower: arg1=casterGuid, arg2=spellId. Clear on interrupt/failure.
|
||||
local casterGuid = arg1
|
||||
if casterGuid and castState[casterGuid] then
|
||||
castState[casterGuid] = nil
|
||||
local plate = plateByGuid[casterGuid]
|
||||
if plate then plate.castUpdate = true end
|
||||
elseif event == "UNIT_SPELLCAST_STOP" or event == "UNIT_SPELLCAST_CHANNEL_STOP" then
|
||||
-- Cast/channel ended (natural, interrupted, or cancelled -- the poll fires
|
||||
-- STOP for all three). Clear the cached cast and refresh its plate.
|
||||
if arg1 and strfind(arg1, "^nameplate") then
|
||||
local guid = UnitGUID(arg1)
|
||||
if guid and castState[guid] then
|
||||
castState[guid] = nil
|
||||
local plate = plateByGuid[guid]
|
||||
if plate then plate.castUpdate = true end
|
||||
end
|
||||
end
|
||||
|
||||
elseif event == "UNIT_AURA" then
|
||||
|
||||
+17
-36
@@ -37,18 +37,12 @@ pfUI:RegisterModule("panel", function()
|
||||
return
|
||||
end
|
||||
if arg1 == "LeftButton" then
|
||||
if widget.timerFrame:IsShown() then
|
||||
widget.timerFrame:Hide()
|
||||
else
|
||||
widget.timerFrame:Show()
|
||||
end
|
||||
widget.timerFrame:SetShown(not widget.timerFrame:IsShown())
|
||||
elseif arg1 == "RightButton" then
|
||||
widget.timerFrame.Snapshot = GetTime()
|
||||
end
|
||||
end
|
||||
widget:SetScript("OnUpdate",function()
|
||||
if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + 1 end
|
||||
|
||||
C_Timer.NewTicker(1, function()
|
||||
local secondsenabled = C.panel.seconds == "1"
|
||||
local fmt
|
||||
if C.global.twentyfour == "0" then
|
||||
@@ -62,8 +56,7 @@ pfUI:RegisterModule("panel", function()
|
||||
|
||||
widget.timerFrame = CreateFrame("Frame", "pfUITimer", UIParent)
|
||||
widget.timerFrame:Hide()
|
||||
widget.timerFrame:SetWidth(120)
|
||||
widget.timerFrame:SetHeight(35)
|
||||
widget.timerFrame:SetSize(120, 35)
|
||||
widget.timerFrame:SetPoint("TOP", 0, -100)
|
||||
UpdateMovable(widget.timerFrame)
|
||||
|
||||
@@ -103,11 +96,9 @@ pfUI:RegisterModule("panel", function()
|
||||
pfUI.panel:OutputPanel("combat", T["Combat"] .. ": " .. NOT_APPLICABLE)
|
||||
end
|
||||
end)
|
||||
widget.combat:SetScript("OnUpdate", function()
|
||||
if not this.tick then this.tick = GetTime() end
|
||||
if GetTime() <= this.tick + 1 then return else this.tick = GetTime() end
|
||||
if this.combat then
|
||||
pfUI.panel:OutputPanel("combat", "|cffffaaaa" .. SecondsToTime(ceil(GetTime() - this.combat)))
|
||||
C_Timer.NewTicker(1, function()
|
||||
if widget.combat.combat then
|
||||
pfUI.panel:OutputPanel("combat", "|cffffaaaa" .. SecondsToTime(ceil(GetTime() - widget.combat.combat)))
|
||||
end
|
||||
end)
|
||||
end
|
||||
@@ -153,9 +144,7 @@ pfUI:RegisterModule("panel", function()
|
||||
pfUI.addons:Show()
|
||||
end
|
||||
end
|
||||
widget:SetScript("OnUpdate",function()
|
||||
if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + 1 end
|
||||
|
||||
C_Timer.NewTicker(1, function()
|
||||
fps = floor(GetFramerate())
|
||||
_, _, lag = GetNetStats()
|
||||
|
||||
@@ -346,7 +335,7 @@ pfUI:RegisterModule("panel", function()
|
||||
widget:RegisterEvent("PLAYER_GUILD_UPDATE")
|
||||
widget.Tooltip = function()
|
||||
-- skip without guild
|
||||
if not GetGuildInfo("player") then return end
|
||||
if not IsInGuild() then return end
|
||||
|
||||
local raidparty = {}
|
||||
for i=1,4 do -- detect people in group
|
||||
@@ -398,7 +387,7 @@ pfUI:RegisterModule("panel", function()
|
||||
end
|
||||
widget.Click = function() ToggleFriendsFrame(3) end
|
||||
widget:SetScript("OnEvent", function()
|
||||
if GetGuildInfo("player") then
|
||||
if IsInGuild() then
|
||||
local count = 0
|
||||
for i = 1, GetNumGuildMembers() do
|
||||
local _, _, _, _, _, _, _, _, online = GetGuildRosterInfo(i)
|
||||
@@ -411,9 +400,8 @@ pfUI:RegisterModule("panel", function()
|
||||
end
|
||||
end)
|
||||
|
||||
widget:SetScript("OnUpdate",function()
|
||||
if ( this.tick or 60) > GetTime() then return else this.tick = GetTime() + 60 end
|
||||
if GetGuildInfo("player") then GuildRoster() end
|
||||
C_Timer.NewTicker(60, function()
|
||||
if IsInGuild() then GuildRoster() end
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -492,11 +480,7 @@ pfUI:RegisterModule("panel", function()
|
||||
GameTooltip:Show()
|
||||
end
|
||||
widget.Click = function()
|
||||
if WorldMapFrame:IsShown() then
|
||||
WorldMapFrame:Hide()
|
||||
else
|
||||
WorldMapFrame:Show()
|
||||
end
|
||||
WorldMapFrame:SetShown(not WorldMapFrame:IsShown())
|
||||
end
|
||||
widget:SetScript("OnEvent", function()
|
||||
pfUI.panel:OutputPanel("zone", GetMinimapZoneText(), widget.Tooltip, widget.Click)
|
||||
@@ -645,8 +629,7 @@ pfUI:RegisterModule("panel", function()
|
||||
local frame = CreateFrame("Button", nil, parent)
|
||||
frame:SetFrameLevel(0)
|
||||
frame:ClearAllPoints()
|
||||
frame:SetWidth(width)
|
||||
frame:SetHeight(parent:GetHeight())
|
||||
frame:SetSize(width, parent:GetHeight())
|
||||
frame:SetPoint(location, 0, 0)
|
||||
frame.text = frame:CreateFontString("Status", "LOW", "GameFontNormal")
|
||||
frame.text:ClearAllPoints()
|
||||
@@ -687,8 +670,7 @@ pfUI:RegisterModule("panel", function()
|
||||
local imgstring = "img:" .. leftright
|
||||
parent.texture:SetTexture(pfUI.media[imgstring])
|
||||
parent.texture:SetPoint("CENTER", 0, 0)
|
||||
parent.texture:SetWidth(8)
|
||||
parent.texture:SetHeight(8)
|
||||
parent.texture:SetSize(8, 8)
|
||||
parent.texture:SetVertexColor(.25,.25,.25,1)
|
||||
return parent.texture
|
||||
end
|
||||
@@ -710,7 +692,7 @@ pfUI:RegisterModule("panel", function()
|
||||
UpdateMovable(pfUI.panel.left)
|
||||
|
||||
pfUI.panel.left.hide:SetScript("OnClick", function()
|
||||
if pfUI.chat.left:IsShown() then pfUI.chat.left:Hide() else pfUI.chat.left:Show() end
|
||||
pfUI.chat.left:SetShown(not pfUI.chat.left:IsShown())
|
||||
end)
|
||||
|
||||
if not pfUI.chat then pfUI.panel.left.hide:Hide() end
|
||||
@@ -747,7 +729,7 @@ pfUI:RegisterModule("panel", function()
|
||||
UpdateMovable(pfUI.panel.right)
|
||||
|
||||
pfUI.panel.right.hide:SetScript("OnClick", function()
|
||||
if pfUI.chat.right:IsShown() then pfUI.chat.right:Hide() else pfUI.chat.right:Show() end
|
||||
pfUI.chat.right:SetShown(not pfUI.chat.right:IsShown())
|
||||
end)
|
||||
|
||||
if not pfUI.chat then pfUI.panel.right.hide:Hide() end
|
||||
@@ -796,8 +778,7 @@ pfUI:RegisterModule("panel", function()
|
||||
pfUI.panel.microbutton = CreateFrame("Frame", "pfPanelMicroButton", UIParent)
|
||||
pfUI.panel.microbutton:SetPoint("TOP", pfUI.panel.minimap, "BOTTOM", 0, -2*default_border)
|
||||
UpdateMovable(pfUI.panel.microbutton)
|
||||
pfUI.panel.microbutton:SetHeight(23)
|
||||
pfUI.panel.microbutton:SetWidth(145)
|
||||
pfUI.panel.microbutton:SetSize(145, 23)
|
||||
pfUI.panel.microbutton:SetFrameStrata("MEDIUM")
|
||||
|
||||
for i=1,table.getn(MICRO_BUTTONS) do
|
||||
|
||||
@@ -23,7 +23,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 = 10802 -- (X*10000 + Y*100 + Z)
|
||||
local PFUI_CLASSIC_API_MIN = 10900 -- (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"
|
||||
|
||||
Reference in New Issue
Block a user