diff --git a/README.md b/README.md index 1212fbdf..e46c989e 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ [![Octo WoW](https://img.shields.io/badge/Octo%20WoW-1.18.1-brightgreen.svg)](https://octowow.st/) [![ClassicAPI](https://img.shields.io/badge/ClassicAPI-Required-purple.svg)](https://github.com/brues-code/ClassicAPI) -[![SuperWoW](https://img.shields.io/badge/SuperWoW-Required-purple.svg)](https://github.com/balakethelock/SuperWoW) [![Nampower](https://img.shields.io/badge/Nampower-Required-purple.svg)](https://github.com/brues-code/nampower) +[![SuperWoW](https://img.shields.io/badge/SuperWoW-Optional-yellow.svg)](https://github.com/balakethelock/SuperWoW) [![UnitXP](https://img.shields.io/badge/UnitXP__SP3-Optional-yellow.svg)](https://codeberg.org/konaka/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.** @@ -36,21 +36,20 @@ 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 - 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 + +### [SuperWoW](https://github.com/balakethelock/SuperWoW) + +Provides: +- Tracks party/raid units on the minimap ### [UnitXP_SP3](https://codeberg.org/konaka/UnitXP_SP3) @@ -91,7 +90,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 diff --git a/api/config.lua b/api/config.lua index f90c63e0..78957742 100644 --- a/api/config.lua +++ b/api/config.lua @@ -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") diff --git a/api/unitframes.lua b/api/unitframes.lua index 512c6e0b..e1d775cc 100644 --- a/api/unitframes.lua +++ b/api/unitframes.lua @@ -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 diff --git a/modules/actionbar.lua b/modules/actionbar.lua index 7905b294..a81196c1 100644 --- a/modules/actionbar.lua +++ b/modules/actionbar.lua @@ -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 diff --git a/modules/addonbuttons.lua b/modules/addonbuttons.lua index 2b84bb93..e58c3143 100644 --- a/modules/addonbuttons.lua +++ b/modules/addonbuttons.lua @@ -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() diff --git a/modules/castbar.lua b/modules/castbar.lua index 8cc13074..2b51c8ed 100644 --- a/modules/castbar.lua +++ b/modules/castbar.lua @@ -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 diff --git a/modules/marktracking.lua b/modules/marktracking.lua index 4d56ee62..c06c53f4 100644 --- a/modules/marktracking.lua +++ b/modules/marktracking.lua @@ -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) \ No newline at end of file diff --git a/modules/minimap.lua b/modules/minimap.lua index 29748fd9..9f4abdd9 100644 --- a/modules/minimap.lua +++ b/modules/minimap.lua @@ -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) diff --git a/modules/nameplates.lua b/modules/nameplates.lua index 82cca4b4..1f6bcb21 100644 --- a/modules/nameplates.lua +++ b/modules/nameplates.lua @@ -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 diff --git a/modules/panel.lua b/modules/panel.lua index fc5c5ebf..11a078b9 100644 --- a/modules/panel.lua +++ b/modules/panel.lua @@ -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 diff --git a/modules/turtle-wow.lua b/modules/turtle-wow.lua index 613d7802..7d74c4a6 100644 --- a/modules/turtle-wow.lua +++ b/modules/turtle-wow.lua @@ -80,10 +80,7 @@ pfUI:RegisterModule("turtle-wow", function () end) end - local delay = CreateFrame("Frame") - delay:SetScript("OnUpdate", function() - this:Hide() - + RunNextFrame(function() -- correct positions of new game menu layout if GameMenuButtonShop and (GameMenuButtonPFUI or GameMenuButtonPFUIAddOns) then -- calculate new offset for the shop button diff --git a/pfUI.lua b/pfUI.lua index a3dfe8a5..27552d32 100644 --- a/pfUI.lua +++ b/pfUI.lua @@ -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"