local BUTTON_HEIGHT, ICON_SIZE, GAP, TEXT_OFFSET, FONT_SIZE = 17, 14, 10, 5, 12 local f = CreateFrame("Frame", nil, UIParent) local g = CreateFrame("Frame", nil, f) -- gear sets local t = CreateFrame"Frame" -- timers local highlight = f:CreateTexture() highlight:SetTexture"Interface\\QuestFrame\\UI-QuestTitleHighlight" highlight:SetBlendMode"ADD" highlight:SetAlpha(0) local backdrop = { bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", edgeSize=16, tile = false, tileSize=0, insets = { left=3, right=3, top=3, bottom=3 } } local ShowGears, specIndex, gears, popupData local block, char, tip, tipshown, config local DEFAULT_ICON = "Interface\\Icons\\Spell_Shadow_SacrificialShield" local orgSetActiveSpecGroup = SetActiveSpecGroup local POPUP_SET_ALIAS = "ABSS_SET_ALIAS" local BLOCK, SPEC, GEAR = 0, 1, 2 local spam1 = ERR_LEARN_ABILITY_S:gsub("%.", "%."):gsub("%%s", "(.*)") local spam2 = ERR_LEARN_SPELL_S:gsub("%.", "%."):gsub("%%s", "(.*)") local spam3 = ERR_SPELL_UNLEARNED_S:gsub("%.", "%."):gsub("%%s", "(.*)") local function SpamFilter(self, event, msg) if strfind(msg, spam1) or strfind(msg, spam2) or strfind(msg, spam3) then return true end end local function OnEquipmentSetChange(oldName, newName) for i=1, GetNumSpecializations() do if char[i].set == oldName then char[i].set = newName end end f:PLAYER_TALENT_UPDATE() end hooksecurefunc("ModifyEquipmentSet", OnEquipmentSetChange) hooksecurefunc("DeleteEquipmentSet", OnEquipmentSetChange) local function WearSet(arg, isGearIndex) -- defaults to talent group index local set = type(arg) == "number" and (isGearIndex and GetEquipmentSetInfo(arg) or char[arg].set) or type(arg) == "string" and arg if set and GetEquipmentSetInfoByName(set) then UseEquipmentSet(set) end end local function DelayWearSet(self, elapsed) t:SetScript("OnUpdate",nil) ChatFrame_RemoveMessageEventFilter("CHAT_MSG_SYSTEM", SpamFilter) WearSet(GetActiveSpecGroup()) end function SetActiveSpecGroup(...) f:RegisterEvent"UNIT_SPELLCAST_SUCCEEDED" f:RegisterEvent"UNIT_SPELLCAST_STOP" ChatFrame_AddMessageEventFilter("CHAT_MSG_SYSTEM", SpamFilter) return orgSetActiveSpecGroup(...) end function f:UNIT_SPELLCAST_STOP(event, unit) if unit ~= "player" then return end f:UnregisterEvent"UNIT_SPELLCAST_SUCCEEDED" f:UnregisterEvent"UNIT_SPELLCAST_STOP" if event == "UNIT_SPELLCAST_SUCCEEDED" then return t:SetScript("OnUpdate", DelayWearSet) end ChatFrame_RemoveMessageEventFilter("CHAT_MSG_SYSTEM", SpamFilter) end f.UNIT_SPELLCAST_SUCCEEDED = f.UNIT_SPELLCAST_STOP local function GetTalentText(group) local spec = group or GetActiveSpecGroup() local id,name,desc,icon,back,role = GetSpecializationInfo(spec) local finalIcon = icon or DEFAULT_ICON return name, finalIcon end local hints = { [BLOCK] = [[|cffffd100Hints [|cffffffffBlock|r|cffffd100]|r $Click|r to swap talent spec. $Right-Click|r to open talent frame. $Middle-Click|r to toggle hints.]], [SPEC] = [[|cffffd100Hints|r $Click|r to set an alias. $Shift+Click|r to equip set. $Middle-Click|r to toggle hints. $MouseWheel|r to resize tooltip.]], [GEAR] = [[|cffffd100Hints|r $Click|r to associate set. $Shift+Click|r to equip set.]], } local function UpdateHints(cat) if cat == BLOCK and (not f.onBlock or config.hideBlockHints) or cat ~= BLOCK and config.hideHints then return tip:Hide() end local showRight = f:GetCenter() > UIParent:GetWidth()/config.scale/2 and "LEFT" or "RIGHT" local showBelow = select(2, f:GetCenter()) > UIParent:GetHeight()/config.scale/2 and "TOP" or "BOTTOM" tip:SetOwner(f, "ANCHOR_NONE") tip:SetPoint(showBelow..showRight, f, (showBelow == "TOP" and "BOTTOM" or "TOP")..showRight ) tip:AddLine(hints[cat]:gsub("%$","|cffff8020"), .2, 1, .2) tip:Show() end local function Gear_OnClick(button, click) if click == "LeftButton" and IsShiftKeyDown() then return button.index > 0 and WearSet(button.index, true) end local spec = char[specIndex] spec.set = button.index > 0 and button.gearName:GetText() or nil for i, b in next, gears do local gearName = b.gearName:GetText() if gearName == spec.set or not spec.set and gearName == NONE then b.check:Show() else b.check:Hide() end end f:PLAYER_TALENT_UPDATE() end local function Spec_OnClick(button, click) if click == "LeftButton" and IsShiftKeyDown() then return WearSet(button.index) elseif click == "MiddleButton" then config.hideHints = not config.hideHints return UpdateHints(SPEC) end local data = char[button.index] if StaticPopup_FindVisible( POPUP_SET_ALIAS, data ) then return StaticPopup_Hide( POPUP_SET_ALIAS, data ) end local specName, specIcon = GetTalentText(button.index) if not StaticPopupDialogs[POPUP_SET_ALIAS] then local dialog = { text = "Set an alias for %s (spec %i).\nLeave blank to remove alias.", button3 = "Use gear set", OnAccept = function(self, spec) local input = self.editBox:GetText() spec[popupData] = input ~= "" and input or nil spec.aliasIsSetName = false f:PLAYER_TALENT_UPDATE() end, OnShow = function(self, spec) self.editBox:SetText(spec[popupData]or"") self.editBox:SetFocus() end, OnAlt = function(self, spec) spec.aliasIsSetName = true f:PLAYER_TALENT_UPDATE() end, EditBoxOnEnterPressed = function(self, spec) local p = self:GetParent() StaticPopupDialogs[POPUP_SET_ALIAS].OnAccept(p, spec) p:Hide() end, EditBoxOnEscapePressed = function(self) self:GetParent():Hide() end, multiple = true, } for k, v in next, StaticPopupDialogs.RENAME_GUILD do if not dialog[k] then dialog[k] = v end end dialog.OnCancel, dialog.OnAlt = dialog.OnAlt, dialog.OnCancel dialog.button3, dialog.button2 = dialog.button2, dialog.button3 StaticPopupDialogs[POPUP_SET_ALIAS] = dialog end local talentPlate = (" |T%s:17:17:0:0:25:25:2:23:2:23|t %s"):format( specIcon, button.specName:GetText() ) popupData = specName StaticPopup_Show( POPUP_SET_ALIAS, talentPlate, button.index, data) end local function Gear_OnEnter(b) if not (b and b.index) then return end highlight:SetAllPoints(b) highlight:SetAlpha(1) UpdateHints(GEAR) end local function Gear_OnLeave(b) highlight:ClearAllPoints() tip:Hide() if b then highlight:SetAlpha(0) end if not (g:IsMouseOver() or f:IsMouseOver() or f.onBlock) then f:Hide() end end local function Menu_OnEnter(b) if not (b and b.index) then return end highlight:SetAllPoints(b) highlight:SetAlpha(1) UpdateHints(SPEC) ShowGears(b.index) end local function Menu_OnLeave(b) highlight:ClearAllPoints() tip:Hide() if b then highlight:SetAlpha(0) end if not (g:IsMouseOver() or f:IsMouseOver() or f.onBlock) then f:Hide() end end local function CreateTx(parent, ...) local tx = parent:CreateTexture() tx:SetSize( ICON_SIZE, ICON_SIZE ) tx:SetTexCoord( 2/25, 23/25, 2/25, 23/25 ) tx:SetPoint(...) return tx end local baseFont = GameFontNormal:GetFont() local function CreateFS(parent, justify, ...) local fs = parent:CreateFontString( nil, "OVERLAY", "SystemFont_Shadow_Med1" ) fs:SetJustifyH(justify) fs:SetFont(baseFont, FONT_SIZE) fs:SetPoint(...) return fs end gears = setmetatable( {}, { __index = function( table, key ) local button = CreateFrame( "Button", nil, g ) table[key] = button button.index = key button:RegisterForClicks"AnyUp" button:SetScript( "OnEnter", Gear_OnEnter ) button:SetScript( "OnLeave", Gear_OnLeave ) button:SetScript( "OnClick", Gear_OnClick ) button:SetHeight(BUTTON_HEIGHT) button.check = CreateTx(button, "LEFT") button.check:SetTexture"Interface\\Buttons\\UI-CheckBox-Check" button.gearIcon = CreateTx(button, "LEFT", button.check, "RIGHT", TEXT_OFFSET, 0) button.gearName = CreateFS(button, "LEFT", "LEFT", button.gearIcon, "RIGHT", TEXT_OFFSET, 0) local y = -GAP - key*BUTTON_HEIGHT button:SetPoint("TOPLEFT", GAP, y) button:SetPoint("TOPRIGHT", -GAP, y) return button end } ) local function SetGearData(index, checked, gearIcon, gearName) local button = gears[index] if checked then button.check:Show() else button.check:Hide() end button.gearIcon:SetTexture(gearIcon) button.gearName:SetText(gearName) return button, button.gearName:GetStringWidth() end local buttons = setmetatable( {}, { __index = function( table, key ) local button = CreateFrame( "Button", nil, f ) table[key] = button button.index = key button:RegisterForClicks"AnyUp" button:SetScript( "OnEnter", Menu_OnEnter ) button:SetScript( "OnLeave", Menu_OnLeave ) button:SetScript( "OnClick", Spec_OnClick) button:SetHeight(BUTTON_HEIGHT) button.specIcon = CreateTx(button, "LEFT") button.specName = CreateFS(button, "LEFT", "LEFT", ICON_SIZE + TEXT_OFFSET, 0) button.aliasText= CreateFS(button, "LEFT", "LEFT", button.specName, "RIGHT", GAP, 0) button.gearName = CreateFS(button, "LEFT", "RIGHT") button.gearName:SetTextColor(1,.82,0) button.gearIcon = CreateTx(button, "RIGHT", button.gearName, "LEFT", -TEXT_OFFSET, 0) local y = -GAP - (key-1)*BUTTON_HEIGHT button:SetPoint("TOPLEFT", GAP, y) button:SetPoint("TOPRIGHT", -GAP, y) return button end } ) local function SetButtonData(index, active, specIcon, specName, alias, gearIcon, gearName) local button = buttons[index] button.specIcon:SetTexture(specIcon) button.specName:SetFormattedText("%s%s|r", active and "|cff19ff19" or "|cffff1919", specName) if alias then button.aliasText:SetFormattedText("|cffffffff%s",alias) else button.aliasText:SetText"" end button.gearIcon:SetTexture(gearIcon and "Interface\\Icons\\"..gearIcon or "") button.gearName:SetText(gearIcon and gearName or "") return button, button.specName:GetStringWidth(), alias and button.aliasText:GetStringWidth() or 0, gearIcon and button.gearName:GetStringWidth() or 0 end local function UpdateTablet() f:SetScale(config.scale) local talentGroup = GetActiveSpecGroup() local specC, aliasC, gearC = 0, 0, 0 local hasAlias, hasGear local nbSpec = GetNumSpecGroups() for i = 1, nbSpec do local id,specName,description,specIcon,background,role = GetSpecializationInfo(i) local group = char[i] local alias,gearName,gearIcon if group then alias, gearName, gearIcon = group[specName], group.set end if gearName then hasGear = true gearIcon = GetEquipmentSetInfoByName(gearName or "") end if alias then hasAlias = true end local button, specW, aliasW, gearW = SetButtonData(i, i==talentGroup, specIcon, specName or "unknown", alias, gearIcon, gearName) if specW > specC then specC = specW end if aliasW > aliasC then aliasC = aliasW end if gearW > gearC then gearC = gearW end end local maxWidth = ICON_SIZE + TEXT_OFFSET + specC + (hasAlias and GAP + aliasC or 0) + (hasGear and GAP + ICON_SIZE + TEXT_OFFSET + gearC or 0) for index, button in next, buttons do if hasAlias then button.specName:SetWidth(specC) end if hasGear then button.gearName:SetWidth(gearC) end end f:SetSize( GAP + maxWidth + GAP, GAP + nbSpec * BUTTON_HEIGHT + GAP ) if not (f.onBlock or f:IsMouseOver() or g:IsMouseOver()) then f:Hide() end end ShowGears = function(_specIndex) if not _specIndex then return end specIndex = _specIndex local nbGear = GetNumEquipmentSets() if not nbGear or nbGear == 0 then return end g:Show() if char[specIndex] then local currGear = char[specIndex].set end local button, maxWidth = SetGearData(0, not currGear, "", NONE) for i=1, nbGear do local gearName, gearIcon = GetEquipmentSetInfo(i) local button, width = SetGearData(i, currGear == gearName, gearIcon, gearName) if width > maxWidth then maxWidth = width end end g:SetSize( (GAP + ICON_SIZE + TEXT_OFFSET)*2 + maxWidth, GAP + (nbGear+1) * BUTTON_HEIGHT + GAP ) for k, v in next, gears do if k>nbGear then v:Hide() else v:Show() end end g:ClearAllPoints() local horiz = f:GetCenter() > UIParent:GetWidth()/config.scale/2 and "RIGHT" or "LEFT" local verti = (f:GetPoint()=="TOP"and f:GetTop() or f:GetBottom()) > UIParent:GetHeight()*.5 and "TOP" or "BOTTOM" g:SetPoint(verti..horiz, f, verti..(horiz=="LEFT"and"RIGHT"or"LEFT"), 0, (1-specIndex)*BUTTON_HEIGHT ) end local tiptacBKG = { tile = false, insets = {} } -- Setup Gradient Tip (from TipTac) local function SetupGradientTip(tip,cfg) local g = tip.ttGradient; if not cfg.gradientTip then return g and g:Hide() elseif not g then g = tip:CreateTexture() g:SetTexture(1,1,1,1) tip.ttGradient = g end g:SetGradientAlpha("VERTICAL",0,0,0,0,unpack(cfg.gradientColor)) g:SetPoint("TOPLEFT",cfg.backdropInsets,cfg.backdropInsets * -1) g:SetPoint("BOTTOMRIGHT",tip,"TOPRIGHT",cfg.backdropInsets * -1,-36) g:Show() end local function SetTabletBG(frame, cfg) if TipTac then frame:SetBackdrop(tiptacBKG) frame:SetBackdropColor(unpack(cfg.tipColor)) frame:SetBackdropBorderColor(unpack(cfg.tipBorderColor)) SetupGradientTip(frame,cfg) elseif Skinner then Skinner:applySkin(frame) else frame:SetBackdrop(backdrop) if frame.ttGradient then frame.ttGradient:Hide() end frame:SetBackdropColor(.1, .1, .1, .85) frame:SetBackdropBorderColor(.3, .3, .3, .9) end end local function AnchorTablet(frame) f:Show() f.isTop, f.onBlock = select(2, frame:GetCenter()) > UIParent:GetHeight() / 2, true f:ClearAllPoints() f:SetPoint(f.isTop and "TOP" or "BOTTOM", frame, f.isTop and "BOTTOM" or "TOP") local cfg if TipTac then cfg = TipTac_Config tiptacBKG.bgFile = cfg.tipBackdropBG tiptacBKG.edgeFile = cfg.tipBackdropEdge tiptacBKG.edgeSize = cfg.backdropEdgeSize tiptacBKG.insets.left = cfg.backdropInsets tiptacBKG.insets.right = cfg.backdropInsets tiptacBKG.insets.top = cfg.backdropInsets tiptacBKG.insets.bottom = cfg.backdropInsets end SetTabletBG(f, cfg) SetTabletBG(g, cfg) UpdateHints(BLOCK) UpdateTablet() end block = LibStub("LibDataBroker-1.1"):NewDataObject("|cFFFFB366Ara|r SpecSwitcher", { type = "data source", icon = DEFAULT_ICON, iconCoords = { .08, .92, .08, .92 }, text = "00/00/00", OnEnter = AnchorTablet, OnLeave = function() f.onBlock = nil tip:Hide() if not f:IsMouseOver() then f:Hide() end end, OnClick = function(self, button) if button == "RightButton" then ToggleTalentFrame() elseif button == "MiddleButton" then config.hideBlockHints = not config.hideBlockHints UpdateHints(BLOCK) else SetActiveSpecGroup( 3 - GetActiveSpecGroup() ) end end }) function f:PLAYER_TALENT_UPDATE() local curr = char[GetActiveSpecGroup()] local spec, icon = GetTalentText() block.icon = curr.aliasIsSetName and GetEquipmentSetInfoByName(curr.set or "") and ("Interface\\Icons\\"..GetEquipmentSetInfoByName(curr.set or "")) or icon block.text = curr.aliasIsSetName and curr.set or curr[spec] or spec if f:IsVisible() then UpdateTablet() end end function f:ACTIVE_TALENT_GROUP_CHANGED() t:SetScript("OnUpdate", DelayWearSet) end function f:ADDON_LOADED(event, addon) if addon ~= "Ara_Broker_SpecSwitcher" then return end AraSpecSwitcherDBPC = AraSpecSwitcherDBPC or { {}, {} } char = AraSpecSwitcherDBPC AraSpecSwitcherDB = AraSpecSwitcherDB or { scale=1 } config = AraSpecSwitcherDB f:SetBackdrop(backdrop) f:SetFrameStrata"TOOLTIP" f:SetClampedToScreen(true) f:SetScript( "OnEnter", Menu_OnEnter ) f:SetScript( "OnLeave", Menu_OnLeave ) f:SetScript( "OnHide", function() g:Hide() end ) f:SetScript( "OnMouseWheel", function(self, delta) config.scale = config.scale - delta * 0.05 UpdateTablet() end) g:SetBackdrop(backdrop) g:SetFrameStrata"TOOLTIP" g:SetFrameLevel(0) g:SetScript( "OnEnter", Gear_OnEnter ) g:SetScript( "OnLeave", Gear_OnLeave) t:SetScript( "OnUpdate", function() t:SetScript("OnUpdate", nil) f:PLAYER_TALENT_UPDATE() end ) f:RegisterEvent"PLAYER_TALENT_UPDATE" f:RegisterEvent"ACTIVE_TALENT_GROUP_CHANGED" f:RegisterEvent"PLAYER_ENTERING_WORLD" f:RegisterEvent"PLAYER_LEAVING_WORLD" tip = GameTooltip f:UnregisterEvent(event) f.ADDON_LOADED = nil end function f:PLAYER_LEAVING_WORLD() f:UnregisterEvent"PLAYER_TALENT_UPDATE" end function f:PLAYER_ENTERING_WORLD() f:RegisterEvent"PLAYER_TALENT_UPDATE" end f:SetScript( "OnEvent", function(self, event, ...) return self[event](self, event, ...) end ) f:RegisterEvent"ADDON_LOADED"