Mercurial > wow > kbf
view KBF.lua @ 64:e5c07fdfb70b
remove the old hacked weapon enchant stuff since SAH properly supports it now, but turn it off because due to bugs either in KBF or SAH (both?), you get overlapping messed up displays whenever you have extra, non-buff frames
author | Chris Mellon <arkanes@gmail.com> |
---|---|
date | Fri, 02 Dec 2011 06:15:35 -0600 |
parents | 31eac67dd283 |
children | e19d0380b9f3 |
line wrap: on
line source
local _, kbf = ... KBF = kbf -- make global for debugging local kbf = LibStub("AceAddon-3.0"):NewAddon(kbf, "KBF", "AceEvent-3.0", "AceConsole-3.0") function kbf:OnInitialize() -- config settings - account wide shared profile by default self.db = LibStub("AceDB-3.0"):New("KBFSavedVars", self.defaultConfig, true) -- create frames here so that they will be correctly stored in location cache by -- the UI. self.anchor, self.secureHeader, self.consolidateHeader, self.consolidateProxy = self:CreateCoreFrames() self.debuffFrames = {} self:RegisterEvent("UNIT_AURA") self:RegisterEvent("UNIT_ENTERING_VEHICLE", "PollForVehicleChange") self:RegisterEvent("UNIT_EXITING_VEHICLE", "PollForVehicleChange") LibStub("AceConfig-3.0"):RegisterOptionsTable("KBF", self.options); self.profilesFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("KBF", "KBF"); self:RegisterChatCommand("kbf", "ToggleAnchor") self.oocQueue = {} end function kbf:OnEnable() -- set up the countdown timer -- TODO: Fancy enable/disable based on whether you have any timed buffs. -- Not a big deal, how often do you care about that -- also TODO: Maybe should bucket OnUpdates somehow -- AceTimer repeating events can only happen at 0.1 seconds, which is probably -- fast enough for updating, but makes the animation look jerky -- need to experiment with using animation groups self.update = CreateFrame("FRAME") self.update:SetScript("OnUpdate", function() self:OnUpdate() end) self.dirty = true -- force an immediate scan on login self:HideBlizzardBuffFrames() end -- naming convention -- a "frame" is the top-level button (secure button from the header, or one I make myself) -- that will contain the UI information about the buff -- a "bar" is a frame that has the icon, status bar, ect associated with it -- Secure aura header doesn't self-bind to vehicle, -- so this only works out of combat. But thats better than nothing... function kbf:PollForVehicleChange(event, unit) if unit ~= "player" then return end self.dirty = true local function performSwap() if UnitHasVehicleUI("player") then -- only swap if we're in a "real" vehicle with its own actions -- There is possibly a timing issue here where -- we have set the poll flag but the unit is not -- actually "in" the vehicle yet. I'm hoping thats -- handled by using exited/entered events instead of exiting/entering self.secureHeader:SetAttribute("unit", "vehicle") else self.secureHeader:SetAttribute("unit", "player") end end self:QueueForOOC(performSwap) end function kbf:HideBlizzardBuffFrames() local function HideBlizFrame(frame) if not frame then return end frame:UnregisterAllEvents() frame:SetScript("OnUpdate", nil) frame:Hide() frame.Show = function() end end HideBlizFrame(BuffFrame) HideBlizFrame(ConsolidatedBuffs) HideBlizFrame(TemporaryEnchantFrame) end -- enqueues a callable that will be run once in-combat lockdown is past -- all callables will be executed in a single run, in the order they were enqueued -- if called when OOC, the function will be called immediately, unless the alwaysQueue parameter is true, -- in which case it will be appended normally function kbf:QueueForOOC(func, alwaysQueue) if InCombatLockdown() or alwaysQueue then tinsert(self.oocQueue, func) else func() end end function kbf:OnUpdate() -- TODO: only start this polling when we leave combat? while #self.oocQueue > 0 and not InCombatLockdown() do local func = table.remove(self.oocQueue) func() end local unit = self.secureHeader:GetAttribute("unit") local buffCount = 0 for idx=1,99 do local frame = self.secureHeader:GetAttribute("child"..idx) if not (frame and frame:IsShown()) then break end local hasbuff = UnitAura(unit, frame:GetAttribute("index")) buffCount = buffCount + 1 if self.dirty then if self:BindBarToBuff(frame, unit) then break end end self:UpdateBarExpirationTime(frame) -- Don't forget to refresh shown tooltips if (GameTooltip:IsOwned(frame)) then self:OnEnter(frame) end end -- consolidated buffs if self.consolidateProxy:IsShown() then for idx=1,99 do local frame = self.consolidateHeader:GetAttribute("child"..idx) if not (frame and frame:IsShown()) then break end if self.dirty then if self:BindBarToBuff(frame, unit) then break end end self:UpdateBarExpirationTime(frame) -- Don't forget to refresh shown tooltips if ( GameTooltip:IsOwned(frame) ) then self:OnEnter(frame) end end buffCount = buffCount+1 end -- SAH correctly binds the weapon enchant templates now, but when temp enchants -- are present and used, it seems that it doesn't correctly hide un-bound -- buff frames, which breaks all the layout and so forth. for weapon=3,1,-1 do local tempEnchant = self.secureHeader:GetAttribute("tempEnchant"..weapon) if tempEnchant and tempEnchant:IsShown() then self:BindBarToWeaponEnchant(tempEnchant) self:UpdateBarExpirationTime(tempEnchant) buffCount = buffCount + 1 end end -- debuffs -- Since debuffs aren't cancellable, don't need to use the secure header -- for them. This could be rewritten to support useful features like -- sorting & scaling and stuff. Honestly, should at least be alphabetical. for idx=1,99 do local frame = self.debuffFrames[idx] if self.dirty then local name, rank, icon, stacks, debuffType, duration, expirationTime = UnitAura(unit, idx, "HARMFUL") if not name then -- out of debuffs, hide all the rest of them for jdx = idx, 99 do local bar = self.debuffFrames[jdx] if bar then bar:Hide() else break end end break end if not frame then frame = self:ConstructBar(nil, 1, 0, 0) self.debuffFrames[idx] = frame end self:SetBarAppearance(frame, name, icon, stacks, duration, expirationTime) frame:ClearAllPoints() -- position it under all the buffs, with a half-bar spacing frame:SetPoint("TOP", self.secureHeader, "TOP", 0, (buffCount * -16) - 8) frame:Show() frame.filter = "HARMFUL" frame.unit = unit frame.index = idx frame:SetScript("OnEnter", function() kbf:OnEnter(frame) end) frame:SetScript("OnLeave", function() GameTooltip:Hide() end) frame:EnableMouse(true) buffCount = buffCount + 1 else -- not dirty, so no frame means we're done if not frame then break end end self:UpdateBarExpirationTime(frame) if ( GameTooltip:IsOwned(frame) ) then self:OnEnter(frame) end end self.dirty = nil end function kbf:UNIT_AURA(event, unit) if unit ~= self.secureHeader:GetAttribute("unit") then return end self.dirty = true end function kbf:UpdateBarExpirationTime(frame) if frame.expirationTime then local remaining = frame.expirationTime - GetTime() remaining = math.max(0, remaining) local perc = remaining / frame.duration frame.timertext:SetText(self:FormatTimeText(remaining)) frame.statusbar:SetValue(remaining) end end local gratt = LibStub:GetLibrary("LibGratuity-3.0") function kbf:BindBarToWeaponEnchant(parentFrame, slotOverride) -- allow passing of explicit slot in order to work around aura header bug local slot = slotOverride or parentFrame:GetAttribute("target-slot") local itemIndex = slot - 15 -- 1MH, 2OF local RETURNS_PER_ITEM = 3 local hasEnchant, remaining, enchantCharges = select(RETURNS_PER_ITEM * (itemIndex - 1) + 1, GetWeaponEnchantInfo()) -- remaining time is in milliseconds if not hasEnchant then return end -- this should never happen local remaining = remaining / 1000 local icon = GetInventoryItemTexture("player", slot) local maxDuration if select(2, UnitClass("player")) == "ROGUE" then -- Rogues are probably using poisons, which are an hour maxDuration = 60 * 60 else -- everyone else is probably using something thats a half hour maxDuration = 30 * 60 end local duration = max(maxDuration, remaining) local expirationTime = GetTime() + remaining local name = GetItemInfo(GetInventoryItemID("player", slot)) -- try to figure out what the weapon enchant is -- tooltip string -> {spellid, duration} local knownEnchants = { ["Flametongue"] = {8024, 30*60}, ["Frostbrand"] = {8033, 30*60}, ["Earthliving"] = {51730, 30*60}, ["Windfury"] = {8232, 30*60}, ["Instant Poison"] = {8680, 60*60}, ["Wound Poison"] = {13218, 60*60}, ["Deadly Poison"] = {2823, 60*60}, } local spellId = nil if gratt then gratt:SetInventoryItem("player", slot) for tag, info in pairs(knownEnchants) do if gratt:Find(tag) then spellId, duration = unpack(info) name, _, _ = GetSpellInfo(spellId) local slots = {[16] = "Main Hand", [17] = "Off Hand", [18] = "Thrown"} name = tag .. " (" .. slots[slot] .. ")" break end end end parentFrame.spellId = spellId if not parentFrame.icon then self:ConstructBar(parentFrame, 1, 0, 1) end self:SetBarAppearance(parentFrame, name, icon, enchantCharges, duration, expirationTime) end function kbf:BindBarToBuff(parentFrame, unit) local index = parentFrame:GetAttribute("index") local filter = parentFrame:GetAttribute("filter") local name, rank, icon, stacks, debuffType, duration, expirationTime, unitCaster, isStealable, shouldConsolidate, spellId = UnitAura(unit, index, filter) if not name then return end if not parentFrame.icon then self:ConstructBar(parentFrame) end self:SetBarAppearance(parentFrame, name, icon, stacks, duration, expirationTime) end function kbf:SetBarAppearance(parentFrame, name, icon, stacks, duration, expirationTime) parentFrame.icon:SetNormalTexture(icon) if stacks and stacks > 0 then parentFrame.text:SetText(string.format("%s(%d)", name, stacks)) else parentFrame.text:SetText(name) end parentFrame.timertext:SetText(self:FormatTimeText(duration)) -- store duration information if duration and duration > 0 then parentFrame.expirationTime = expirationTime parentFrame.duration = duration parentFrame.statusbar:SetMinMaxValues(0, duration) else parentFrame.expirationTime = nil parentFrame.duration = 0 parentFrame.statusbar:SetMinMaxValues(0,1) parentFrame.statusbar:SetValue(1) end end -- expects time seconds function kbf:FormatTimeText(time) if not time or time == 0 then return "" end local timetext local h = floor(time/3600) local m = time - (h*3600) m = floor(m/60) local s = time - ((h*3600) + (m*60)) if h > 0 then timetext = ("%d:%02d"):format(h, m) elseif m > 0 then timetext = string.format("%d:%02d", m, floor(s)) elseif s < 10 then timetext = string.format("%1.1f", s) else timetext = string.format("%.0f", floor(s)) end return timetext end function KBF:OnEnter(button, motion) -- this is for the secure buttons, so use the attributes local unit = SecureButton_GetModifiedUnit(button) or button.unit -- will perform vehicle toggle local filter = button:GetAttribute("filter") or button.filter local index = button:GetAttribute("index") or button.index if unit and filter and index then -- I'd like a better place to position this but it's funky for right now, handle it later GameTooltip:SetOwner(button, "ANCHOR_BOTTOMLEFT"); GameTooltip:SetFrameLevel(button:GetFrameLevel() + 2); GameTooltip:SetUnitAura(unit, index, filter); return end local slot = button:GetAttribute("target-slot") -- temp enchant if slot then GameTooltip:SetOwner(button, "ANCHOR_BOTTOMLEFT"); GameTooltip:SetFrameLevel(button:GetFrameLevel() + 2); if button.spellId then -- TODO: This might be too big of a tooltip to care about that much. -- Maybe I should just have a single line with the weapon name --GameTooltip:SetInventoryItem(unit, slot) local name = GetItemInfo(GetInventoryItemID("player", slot)) local r, g, b = GetItemQualityColor(GetInventoryItemQuality("player", slot)) GameTooltip:SetText(name, r, g, b) local slots = {[16] = "Main Hand", [17] = "Off Hand", [18] = "Thrown"} GameTooltip:AddLine(slots[slot]) GameTooltip:AddLine(" ") GameTooltip:AddSpellByID(button.spellId) else GameTooltip:SetInventoryItem(unit, slot) end end end -- creates a icon + statusbar bar function kbf:ConstructBar(frame, r, g, b) local texture = "Interface\\TargetingFrame\\UI-StatusBar" -- Because of secureframe suckiness, these height & width numbers -- have to be consistent with the stuff in KBF.xml local height = self.staticConfig.BAR_HEIGHT local width = self.staticConfig.BAR_WIDTH -- this is the width *without* the icon local font, _, style = GameFontHighlight:GetFont() local r = r or 0 local g = g or 1 local b = b or 0 local bgcolor = {r, g, b, 0.5} local color = {r, g, b, 1} local fontsize = 11 local timertextwidth = fontsize * 3.6 local textcolor = {1, 1, 1, 1} local timertextcolor = {1, 1, 1, 1} if not frame then frame = CreateFrame("Button", nil, UIParent) -- the "top level" frame that represents the bar as a whole frame:SetHeight(height) frame:SetWidth(width + height) end local bar = frame bar.icon = CreateFrame("Button", nil, bar) -- the icon bar.statusbarbg = CreateFrame("StatusBar", nil, bar) -- the bars background bar.statusbar = CreateFrame("StatusBar", nil, bar) -- and the bars foreground bar.text = bar.statusbar:CreateFontString(nil, "OVERLAY") -- the label text bar.timertext = bar.statusbar:CreateFontString(nil, "OVERLAY") -- and the timer text -- the icon bar.icon:ClearAllPoints() bar.icon:SetPoint("LEFT", bar, "LEFT", 0, 0) -- icons are square bar.icon:SetWidth(height) bar.icon:SetHeight(height) --bar.icon:EnableMouse(false) -- the status bar background & foreground local function setupStatusBar(sb, color) sb:ClearAllPoints() sb:SetHeight(height) sb:SetWidth(width) -- offset the height of the frame on the x-axis for the icon. sb:SetPoint("TOPLEFT", bar, "TOPLEFT", height, 0) sb:SetStatusBarTexture(texture) sb:GetStatusBarTexture():SetVertTile(false) sb:GetStatusBarTexture():SetHorizTile(false) sb:SetStatusBarColor(unpack(color)) sb:SetMinMaxValues(0,1) sb:SetValue(1) end setupStatusBar(bar.statusbarbg, bgcolor) setupStatusBar(bar.statusbar, color) bar.statusbarbg:SetFrameLevel(bar.statusbarbg:GetFrameLevel()-1) -- make sure the bg frame stays in the back -- timer text bar.timertext:SetFontObject(GameFontHighlight) bar.timertext:SetFont(GameFontHighlight:GetFont()) bar.timertext:SetHeight(height) bar.timertext:SetWidth(timertextwidth) bar.timertext:SetPoint("LEFT", bar.statusbar, "LEFT", 2, 0) bar.timertext:SetJustifyH("LEFT") bar.timertext:SetText("time") bar.timertext:SetTextColor(timertextcolor[1], timertextcolor[2], timertextcolor[3], timertextcolor[4]) -- and the label text bar.text:SetFontObject(GameFontHighlight) bar.text:SetFont(GameFontHighlight:GetFont()) bar.text:SetHeight(height) bar.text:SetPoint("LEFT", bar.timertext, "RIGHT", 0, 0) bar.text:SetPoint("RIGHT", bar.statusbar, "RIGHT", 0, 0) bar.text:SetJustifyH("LEFT") bar.text:SetText("text") bar.text:SetTextColor(textcolor[1], textcolor[2], textcolor[3], textcolor[4]) return bar end function kbf:CreateCoreFrames() -- this is the visible anchor frame that the user interacts with -- to move the buffs around local height = self.staticConfig.BAR_HEIGHT local width = self.staticConfig.BAR_WIDTH -- this is the width *without* the icon local anchor = CreateFrame("FRAME", "KBFAnchorFrame", UIParent) anchor:SetClampedToScreen(true) anchor:SetBackdrop({bgFile = "Interface/Tooltips/UI-Tooltip-Background", edgeFile = "Interface/Tooltips/UI-Tooltip-Border", tile = true, tileSize = height, edgeSize = 12, insets = { left = 4, right = 4, top = 4, bottom = 4 }, }) local text = anchor:CreateFontString(nil, "OVERLAY") -- the label text text:SetFontObject(GameFontHighlight) text:SetFont(GameFontHighlight:GetFont()) text:SetPoint("TOPLEFT", anchor, "TOPLEFT", 0, 0) text:SetPoint("BOTTOMRIGHT", anchor, "BOTTOMRIGHT", 0, 0) text:SetText("KBF ANCHOR") anchor:SetWidth(height + width) anchor:SetHeight(height) -- movability anchor:EnableMouse(true) anchor:SetMovable(true) anchor:RegisterForDrag("LeftButton") anchor:SetScript("OnDragStart", anchor.StartMoving) anchor:SetScript("OnDragStop", anchor.StopMovingOrSizing) anchor:ClearAllPoints() anchor:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", 0, 0) anchor:Hide() -- this is the parent & host for the secure aura buttons. local secureHeader = CreateFrame("FRAME", "KBFBuffFrame", UIParent, "SecureAuraHeaderTemplate") self:SetCommonSecureHeaderAttributes(secureHeader) if self.db.profile.consolidateBuffs then secureHeader:SetAttribute("consolidateTo", 99) end secureHeader:SetPoint("TOP", anchor, "TOP", 0, 0) -- this is the "button" in the aura flow that represents the consolidated buffs. -- pre-creating it here in order to perform customization local consolidateProxy = CreateFrame("BUTTON", nil, UIParent, "SecureHandlerClickTemplate") consolidateProxy:SetNormalTexture("Interface\\TargetingFrame\\UI-StatusBar") consolidateProxy:SetWidth(200 +16) consolidateProxy:SetHeight(16) secureHeader:SetAttribute("consolidateProxy", consolidateProxy) --secureHeader:SetFrameRef("proxy", consolidateProxy) -- this is the equivilent of the secureHeader for the consolidated buffs -- pre-creating again, so we can customize/size/position it local consolidateHeader = CreateFrame("FRAME", "KBFConsolidatedAnchorFrame", consolidateProxy) self:SetCommonSecureHeaderAttributes(consolidateHeader) secureHeader:SetAttribute("consolidateHeader", consolidateHeader) consolidateProxy:SetAttribute("header", consolidateHeader); consolidateProxy:SetFrameRef("header", consolidateHeader) consolidateProxy:SetAttribute("_onclick", [[ local frame = self:GetFrameRef("header") if frame:IsShown() then frame:Hide() else frame:Show() end ]]) consolidateProxy:EnableMouse(true) consolidateProxy:RegisterForClicks("AnyUp") -- position it relative to the proxy, so it can appear where we want it consolidateHeader:SetPoint("TOPRIGHT", anchor, "TOPLEFT", 0, 0) consolidateHeader:SetWidth(height + width) consolidateHeader:SetHeight(height) consolidateHeader:Show() return anchor, secureHeader, consolidateHeader, consolidateProxy end --- sets the attributes needed by all the headers function kbf:SetCommonSecureHeaderAttributes(frame) frame:SetAttribute("filter", "HELPFUL") frame:SetAttribute("toggleForVehicle", true) -- this doesn't actually work right now, but maybe it eventually will frame:SetAttribute("template", "KBFSecureUnitAuraTemplate") frame:SetAttribute("point", "TOP") frame:SetAttribute("wrapAfter", 100) -- required due to bugs in secure header frame:SetAttribute("xOffset", 0) frame:SetAttribute("yOffset", -16) frame:SetAttribute("minWidth", 216) frame:SetAttribute("minHeight", 16) frame:SetAttribute("unit", "player") frame:SetAttribute("sortMethod", "NAME") frame:SetAttribute("sortOrder", "-") frame:SetAttribute("weaponTemplate", "KBFSecureUnitAuraTemplate") -- TODO: Enabling temp enchant support breaks layout for regular buffs frame:SetAttribute("includeWeapons", nil) frame:Show() -- has to be shown, otherwise the child frames don't show return frame end function kbf:ShowAnchor() self.secureHeader:ClearAllPoints() self.secureHeader:SetPoint("TOP", self.anchor, "BOTTOM", 0, 0) self.anchor:Show() end function kbf:HideAnchor() self.secureHeader:ClearAllPoints() self.secureHeader:SetPoint("TOP", self.anchor, "TOP", 0, 0) self.anchor:Hide() end function kbf:ToggleAnchor() if self.anchor:IsShown() then self:HideAnchor() else self:ShowAnchor() end end