Something went wrong. Try again.
World of Warcraft: Forever addon that adds profession tracking.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685local FILL_ATLAS = "UI-HUD-ExperienceBar-Fill-Experience"local FRAME_ATLAS = "UI-HUD-ExperienceBar-Frame"local BACKGROUND_ATLAS = "UI-HUD-ExperienceBar-Background"
local ACTION_BARS = { "MainActionBar", "MainMenuBar", "MultiBarBottomLeft", "MultiBarBottomRight", "MultiBar5", "MultiBar6", "MultiBar7", "StanceBar", "PetActionBar", "PossessActionBar",}
local barlocal watchlocal lastSignaturelocal elapsed = 0local hooked = {}local pushed = {}local EnsureWatch
local function CopyTexture(dest, src) if not dest or not src then return false end if src.GetAtlas then local atlas = src:GetAtlas() if atlas and atlas ~= "" and dest.SetAtlas then dest:SetAtlas(atlas, false) return true end end if src.GetTexture then local file = src:GetTexture() if file then dest:SetTexture(file) if src.GetTexCoord and dest.SetTexCoord then dest:SetTexCoord(src:GetTexCoord()) end return true end end return falseend
local function TrySetAtlas(texture, atlas) if not texture or not atlas or atlas == "" or not texture.SetAtlas then return false end return pcall(texture.SetAtlas, texture, atlas, false)end
local function TrySetAtlases(texture, ...) for i = 1, select("#", ...) do if TrySetAtlas(texture, select(i, ...)) then return true end end return falseend
local FRAME_PIXEL_WIDTH = 571local GUTTER_WIDTH_FRACTION = 2 / FRAME_PIXEL_WIDTHlocal GUTTER_CENTERS = { 56.5 / FRAME_PIXEL_WIDTH, 113.5 / FRAME_PIXEL_WIDTH, 170.5 / FRAME_PIXEL_WIDTH, 227.5 / FRAME_PIXEL_WIDTH, 284.5 / FRAME_PIXEL_WIDTH, 341.5 / FRAME_PIXEL_WIDTH, 398.5 / FRAME_PIXEL_WIDTH, 455.5 / FRAME_PIXEL_WIDTH, 512.5 / FRAME_PIXEL_WIDTH,}
local function DisableAtlasSlice(texture) if texture.SetHorizTile then texture:SetHorizTile(false) end if texture.SetVertTile then texture:SetVertTile(false) end if texture.SetTextureSliceMargins then pcall(texture.SetTextureSliceMargins, texture, 0, 0, 0, 0) end if texture.SetTextureSliceMode and Enum and Enum.TextureSliceMode and Enum.TextureSliceMode.Stretched then pcall(texture.SetTextureSliceMode, texture, Enum.TextureSliceMode.Stretched) end if texture.SetSnapToPixelGrid then pcall(texture.SetSnapToPixelGrid, texture, true) end if texture.SetTexelSnappingBias then pcall(texture.SetTexelSnappingBias, texture, 0) endend
local function SetRawAtlas(texture, atlas) if not texture or not atlas or atlas == "" then return false end local info = C_Texture and C_Texture.GetAtlasInfo and C_Texture.GetAtlasInfo(atlas) local file = info and (info.file or info.filename) if not info or not file or not info.leftTexCoord or not info.width or info.width == 0 then local applied = TrySetAtlas(texture, atlas) if applied then DisableAtlasSlice(texture) end return applied end texture:SetTexture(file) texture:SetTexCoord(info.leftTexCoord, info.rightTexCoord, info.topTexCoord, info.bottomTexCoord) DisableAtlasSlice(texture) return trueend
local function ApplyGutterSlice(texture, center) local info = C_Texture and C_Texture.GetAtlasInfo and C_Texture.GetAtlasInfo(FRAME_ATLAS) local file = info and (info.file or info.filename) if not info or not file then texture:SetColorTexture(0.55, 0.54, 0.5, 0.95) return end texture:SetTexture(file) local left = info.leftTexCoord or 0 local span = (info.rightTexCoord or 1) - left local half = GUTTER_WIDTH_FRACTION / 2 texture:SetTexCoord( left + (center - half) * span, left + (center + half) * span, info.topTexCoord or 0, info.bottomTexCoord or 1) DisableAtlasSlice(texture)end
local function LayoutGutters(frame) local gutters = frame.Gutters local border = frame.BarFrameTexture if not gutters or not border then return end local width = border:GetWidth() if not width or width < 1 then width = frame.StatusBar and frame.StatusBar:GetWidth() end if not width or width < 1 then return end local sliceWidth = math.max(1, width * GUTTER_WIDTH_FRACTION) for i, texture in ipairs(gutters) do local x = width * GUTTER_CENTERS[i] texture:ClearAllPoints() texture:SetWidth(sliceWidth) texture:SetPoint("TOP", border, "TOPLEFT", x, 0) texture:SetPoint("BOTTOM", border, "BOTTOMLEFT", x, 0) texture:Show() endend
local function SyncBarLevels(frame) local base = frame:GetFrameLevel() local status = frame.StatusBar if status then status:SetFrameStrata("LOW") status:SetFrameLevel(base + 1) end if frame.Overlay then frame.Overlay:SetFrameStrata(frame:GetFrameStrata() or "MEDIUM") frame.Overlay:SetFrameLevel(base + 20) endend
local function FormatBarText(prof) if not prof.maxRank or prof.maxRank <= 0 then return prof.name end return string.format("%s %d/%d", prof.name, prof.rank or 0, prof.maxRank)end
local function IsExperienceFill(status) local tex = status and status.GetStatusBarTexture and status:GetStatusBarTexture() local atlas = tex and tex.GetAtlas and tex:GetAtlas() return atlas and atlas:find("Experience", 1, true)end
local function FindLiveBar(container) if not container or not container.bars then return end local fallback for _, candidate in pairs(container.bars) do if candidate and candidate.StatusBar then local visible = (not candidate.ShouldBeVisible or candidate:ShouldBeVisible()) and candidate:IsShown() if visible then if IsExperienceFill(candidate.StatusBar) then return candidate end fallback = fallback or candidate end end end return fallback or container.bars[1]end
local function ContainerActive(container) if not container or not container:IsShown() then return false end if (container:GetAlpha() or 1) < 0.05 then return false end if container.bars then for _, candidate in pairs(container.bars) do if candidate and candidate.StatusBar and candidate:IsShown() then if not candidate.ShouldBeVisible or candidate:ShouldBeVisible() then return true end end end return false end return trueend
local function TopContainer() local top, topY local containers = { MainStatusTrackingBarContainer, SecondaryStatusTrackingBarContainer, } for _, container in ipairs(containers) do if ContainerActive(container) then local y = container:GetTop() if y and (not topY or y > topY) then top, topY = container, y end end end return top or MainStatusTrackingBarContainerend
local function TextureAtlas(texture) if texture and texture.GetAtlas then local atlas = texture:GetAtlas() if atlas and atlas ~= "" then return atlas end endend
local function FindBackgroundTexture(status, container) if status and status.Background then return status.Background end if container and container.Background then return container.Background end if not status then return end local regions = { status:GetRegions() } for _, region in ipairs(regions) do if region.GetDrawLayer and region.GetAtlas then local layer = region:GetDrawLayer() if layer == "BACKGROUND" and region ~= status:GetStatusBarTexture() then return region end end endend
local function VisibleBar(container) if not container or not container.bars then return end for _, candidate in pairs(container.bars) do if candidate and candidate.StatusBar and candidate:IsShown() then if not candidate.ShouldBeVisible or candidate:ShouldBeVisible() then return candidate end end endend
local function GetLiveArt() local main = MainStatusTrackingBarContainer local second = SecondaryStatusTrackingBarContainer local fillBar = FindLiveBar(main) if not fillBar or not IsExperienceFill(fillBar.StatusBar) then fillBar = FindLiveBar(second) or fillBar end local gutterContainer = TopContainer() or main local gutterBar = VisibleBar(gutterContainer) or fillBar local fillStatus = fillBar and fillBar.StatusBar local gutterStatus = gutterBar and gutterBar.StatusBar return { gutterContainer = gutterContainer, gutterStatus = gutterStatus, fillStatus = fillStatus, frameTexture = gutterContainer and gutterContainer.BarFrameTexture, frameAtlas = TextureAtlas(gutterContainer and gutterContainer.BarFrameTexture) or FRAME_ATLAS, fillAtlas = TextureAtlas(fillStatus and fillStatus.GetStatusBarTexture and fillStatus:GetStatusBarTexture()) or FILL_ATLAS, backgroundAtlas = TextureAtlas(FindBackgroundTexture(gutterStatus or fillStatus, gutterContainer)) or BACKGROUND_ATLAS, }end
local function GetInsets(parent, child) if not parent or not child then return 0, 0, 0, 0 end local parentLeft, parentRight = parent:GetLeft(), parent:GetRight() local parentTop, parentBottom = parent:GetTop(), parent:GetBottom() local childLeft, childRight = child:GetLeft(), child:GetRight() local childTop, childBottom = child:GetTop(), child:GetBottom() if not parentLeft or not childLeft then return 0, 0, 0, 0 end return childLeft - parentLeft, parentRight - childRight, parentTop - childTop, childBottom - parentBottomend
local function PlaceLike(dest, src, srcParent, destParent) dest:ClearAllPoints() if not src or not srcParent then dest:SetAllPoints(destParent) return end local left, right, top, bottom = GetInsets(srcParent, src) dest:SetPoint("TOPLEFT", destParent, "TOPLEFT", left, -top) dest:SetPoint("BOTTOMRIGHT", destParent, "BOTTOMRIGHT", -right, bottom)end
local function ApplyFill(status, atlas) local applied = false local current = status:GetStatusBarTexture() if current and current.SetAtlas then applied = TrySetAtlases(current, atlas, FILL_ATLAS) end if not applied then status:SetStatusBarTexture("Interface\\Buttons\\WHITE8X8") current = status:GetStatusBarTexture() applied = current and TrySetAtlases(current, atlas, FILL_ATLAS) end current = status:GetStatusBarTexture() if current and current.SetDesaturated then current:SetDesaturated(true) end local r, g, b = Professor:GetBarColor() status:SetStatusBarColor(r, g, b, 1) current = status:GetStatusBarTexture() if current and current.SetDesaturated then current:SetDesaturated(true) end if current and current.SetDrawLayer then current:SetDrawLayer("ARTWORK", 0) endend
local function ApplyXPLook(frame) local live = GetLiveArt() local gutter = live.gutterContainer local height = 17 if gutter then height = gutter:GetHeight() or height frame:SetFrameStrata(gutter:GetFrameStrata() or "MEDIUM") frame:SetFrameLevel((gutter:GetFrameLevel() or 0) + 4) end frame:SetHeight(height)
local status = frame.StatusBar PlaceLike(status, live.gutterStatus, gutter, frame)
if not TrySetAtlases(frame.Background, live.backgroundAtlas, BACKGROUND_ATLAS) then frame.Background:SetColorTexture(0, 0, 0, 0.55) end frame.Background:SetAllPoints(status)
ApplyFill(status, live.fillAtlas)
local border = frame.BarFrameTexture border:SetAlpha(1) border:SetDrawLayer("OVERLAY", 4) local frameAtlas = live.frameAtlas or FRAME_ATLAS if not SetRawAtlas(border, frameAtlas) and live.frameTexture then CopyTexture(border, live.frameTexture) end DisableAtlasSlice(border) if live.frameTexture and live.frameTexture.GetBlendMode and border.SetBlendMode then border:SetBlendMode(live.frameTexture:GetBlendMode()) end if live.frameTexture then PlaceLike(border, live.frameTexture, gutter, frame) else border:ClearAllPoints() border:SetAllPoints(frame) end
SyncBarLevels(frame) LayoutGutters(frame)end
local function CreateBar() local frame = CreateFrame("Frame", "ProfessorBar", UIParent) frame.StatusBar = CreateFrame("StatusBar", nil, frame) frame.Background = frame.StatusBar:CreateTexture(nil, "BACKGROUND") frame.Overlay = CreateFrame("Frame", nil, frame) frame.Overlay:SetAllPoints() frame.Overlay:EnableMouse(false) frame.BarFrameTexture = frame.Overlay:CreateTexture(nil, "OVERLAY", nil, 4) frame.Gutters = {} for i, center in ipairs(GUTTER_CENTERS) do local texture = frame.Overlay:CreateTexture(nil, "OVERLAY", nil, 6) ApplyGutterSlice(texture, center) frame.Gutters[i] = texture end frame.Text = frame.Overlay:CreateFontString(nil, "OVERLAY", "TextStatusBarText") frame.Text:SetDrawLayer("OVERLAY", 7) frame.Text:SetPoint("CENTER", frame.StatusBar, "CENTER", 0, 0)
frame:EnableMouse(true) frame:HookScript("OnShow", function(self) SyncBarLevels(self) LayoutGutters(self) end) frame:HookScript("OnSizeChanged", function(self) LayoutGutters(self) end) frame:SetScript("OnEnter", function(self) self.Text:Show() end) frame:SetScript("OnLeave", function(self) if not Professor:ShouldShowText() then self.Text:Hide() end end)
ApplyXPLook(frame) frame:Hide() return frameend
local function Acquire() if not bar then bar = CreateBar() end return barend
local function Signature() local function bits(frame) if not frame then return "x" end return string.format("%s:%.1f:%.1f:%.1f:%s", frame:GetName() or "?", frame:GetLeft() or -1, frame:GetBottom() or -1, frame:GetWidth() or -1, frame:IsShown() and "1" or "0") end return bits(MainStatusTrackingBarContainer) .. "|" .. bits(SecondaryStatusTrackingBarContainer)end
local function EditModeLive() local manager = EditModeManagerFrame if not manager then return false end if manager.IsEditModeActive then return manager:IsEditModeActive() end return manager.editModeActiveend
local function ClearPushes(stackTop) for frame in pairs(pushed) do if stackTop and frame.GetBottom and frame.AdjustPointsOffset then local bottom = frame:GetBottom() if bottom then local delta = stackTop - bottom if math.abs(delta) >= 0.5 then frame:AdjustPointsOffset(0, delta) end end end pushed[frame] = nil endend
local function IsDependent(frame, other) if not frame or not other or frame == other then return false end local parent = frame:GetParent() while parent and parent ~= UIParent do if parent == other then return true end parent = parent:GetParent() end local points = frame:GetNumPoints() or 0 for i = 1, points do local _, rel = frame:GetPoint(i) if rel == other then return true end local relParent = rel while relParent and relParent ~= UIParent do if relParent == other then return true end relParent = relParent:GetParent() end end return falseend
local function HorizontallyOverlaps(frame, left, right) local frameLeft, frameRight = frame:GetLeft(), frame:GetRight() if not frameLeft or not frameRight then return false end return frameRight > left + 20 and frameLeft < right - 20end
local function NudgeActionBars(ourBar) if not ourBar or not ourBar:IsShown() or EditModeLive() then return end local stackTop = ourBar:GetBottom() local desiredBottom = ourBar:GetTop() if not stackTop or not desiredBottom then return end local left = ourBar:GetLeft() or 0 local right = ourBar:GetRight() or 0 local group, lowest = {}, nil for _, name in ipairs(ACTION_BARS) do local frame = _G[name] if frame and frame ~= ourBar and frame:IsVisible() and frame.AdjustPointsOffset then local bottom = frame:GetBottom() if bottom and HorizontallyOverlaps(frame, left, right) then local onStack = bottom >= stackTop - 8 and bottom <= stackTop + 80 local already = math.abs(bottom - desiredBottom) < 8 if onStack or already then if not lowest or bottom < lowest - 2 then lowest = bottom group = { frame } elseif math.abs(bottom - lowest) <= 2 then group[#group + 1] = frame end end end end end for _, frame in ipairs(group) do local dependent = false for _, other in ipairs(group) do if IsDependent(frame, other) then dependent = true break end end if not dependent then local bottom = frame:GetBottom() local delta = desiredBottom - bottom if math.abs(delta) >= 0.5 then frame:AdjustPointsOffset(0, delta) end pushed[frame] = true end endend
function Professor:LayoutBars() local professions = self:GetVisibleProfessions() local anchor = TopContainer() local frame = Acquire() if #professions == 0 then local stackTop = frame:IsShown() and frame:GetBottom() frame:Hide() ClearPushes(stackTop) lastSignature = Signature() return end
ApplyXPLook(frame) frame:ClearAllPoints() if anchor then frame:SetPoint("BOTTOMLEFT", anchor, "TOPLEFT", 0, 0) frame:SetPoint("BOTTOMRIGHT", anchor, "TOPRIGHT", 0, 0) else frame:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, 80) frame:SetWidth(804) end frame:Show() LayoutGutters(frame) NudgeActionBars(frame) lastSignature = Signature()end
function Professor:UpdateBars() EnsureWatch() local professions = self:GetVisibleProfessions() local frame = Acquire() if #professions == 0 then self:LayoutBars() return end local prof = professions[1] local status = frame.StatusBar status:SetMinMaxValues(0, prof.maximum) status:SetValue(prof.progress) frame.Text:SetText(FormatBarText(prof)) if self:ShouldShowText() then frame.Text:Show() else frame.Text:Hide() end self:LayoutBars()end
local function HookContainer(container) if not container or hooked[container] then return end hooked[container] = true container:HookScript("OnShow", function() Professor:LayoutBars() end) container:HookScript("OnHide", function() Professor:LayoutBars() end) container:HookScript("OnSizeChanged", function() Professor:LayoutBars() end)end
EnsureWatch = function() HookContainer(MainStatusTrackingBarContainer) HookContainer(SecondaryStatusTrackingBarContainer) if watch then return end
if EventRegistry and EventRegistry.RegisterCallback then pcall(EventRegistry.RegisterCallback, EventRegistry, "EditMode.Exit", function() Professor:LayoutBars() end, Professor) end if EditModeManagerFrame and EditModeManagerFrame.ExitEditMode then hooksecurefunc(EditModeManagerFrame, "ExitEditMode", function() Professor:LayoutBars() end) end if StatusTrackingBarManager and StatusTrackingBarManager.UpdateBarsShown then hooksecurefunc(StatusTrackingBarManager, "UpdateBarsShown", function() if bar and bar:IsShown() then NudgeActionBars(bar) end end) end
watch = CreateFrame("Frame") watch:SetScript("OnUpdate", function(_, dt) elapsed = elapsed + dt if elapsed < 0.25 then return end elapsed = 0 HookContainer(MainStatusTrackingBarContainer) HookContainer(SecondaryStatusTrackingBarContainer) if Signature() ~= lastSignature then Professor:LayoutBars() elseif bar and bar:IsShown() then NudgeActionBars(bar) end end)end