local AddonName, F = ... F.UI = {} local CFG = { width = 600, height = 24, texture = "Interface\\Buttons\\WHITE8x8", font = "Fonts\\FRIZQT__.TTF", fontSize = 14, barFontSize = 12, -- Colors (R, G, B, A) colorBack = {0.0, 0.0, 0.0, 0.8}, colorBorder = {0, 0, 0, 1}, colorMain = {0.76, 0.38, 1, 1}, -- Base Purple colorRested = {0.34, 0.61, 0.99, .8}, -- Rested Blue colorQuest = {1, 0.64, 0.0078, 0.25}, -- Quest Yellow colorQuestComplete = {1, 0.64, 0.0078, 0.8}, -- Completed Quest } function F.UI.CreateStatusBar(parent, layer, subLevel, color) local bar = CreateFrame("StatusBar", nil, parent) bar:SetAllPoints() bar:SetStatusBarTexture(CFG.texture) bar:SetStatusBarColor(unpack(color)) bar:GetStatusBarTexture():SetDrawLayer(layer, subLevel) return bar end function F.UI.Create() local f = CreateFrame("Frame", "NXP_Frame", UIParent) f:SetSize(CFG.width, CFG.height) f:SetPoint("TOP", UIParent, "TOP", 0, 100) -- Default Position f:SetFrameStrata("LOW") f:SetMovable(false) f:EnableMouse(false) F.Frame = f -- 2. Background & Border local bg = f:CreateTexture(nil, "BACKGROUND") bg:SetAllPoints() bg:SetTexture(CFG.texture) bg:SetVertexColor(unpack(CFG.colorBack)) -- Rested XP Bar F.BarRested = F.UI.CreateStatusBar(f, "BACKGROUND", 2, CFG.colorRested) -- Quest XP Bar F.BarQuest = F.UI.CreateStatusBar(f, "BORDER", 1, CFG.colorQuest) -- Completed Quest Bar F.BarQuestComplete = F.UI.CreateStatusBar(f, "BORDER", 2, CFG.colorQuestComplete) -- Main XP Bar F.BarMain = F.UI.CreateStatusBar(f, "ARTWORK", 1, CFG.colorMain) -- XP Ticks (20 segments) local tickContainer = CreateFrame("Frame", nil, f) tickContainer:SetAllPoints() tickContainer.ticks = {} for i = 1, 19 do local tick = tickContainer:CreateTexture(nil, "OVERLAY") tick:SetColorTexture(0, 0, 0, 0.35) tick:SetWidth(1) tickContainer.ticks[i] = tick end F.TickContainer = tickContainer local overlay = CreateFrame("Frame", nil, f) overlay:SetAllPoints() overlay:SetFrameLevel(f:GetFrameLevel() + 10) F.Overlay = overlay -- level label F.LevelText = overlay:CreateFontString(nil, "OVERLAY") F.LevelText:SetFont(CFG.font, CFG.barFontSize, "OUTLINE") F.LevelText:SetPoint("LEFT", overlay, "LEFT", 8, 0) F.LevelText:SetJustifyH("LEFT") F.LevelText:SetDrawLayer("OVERLAY", 2) F.LevelText:SetTextColor(1, 1, 1, 1) -- current / max XP F.TextCenter = overlay:CreateFontString(nil, "OVERLAY") F.TextCenter:SetFont(CFG.font, CFG.barFontSize, "OUTLINE") F.TextCenter:SetPoint("CENTER", overlay, "CENTER", 0, 0) F.TextCenter:SetJustifyH("CENTER") F.TextCenter:SetDrawLayer("OVERLAY", 2) F.TextCenter:SetTextColor(1, 1, 1, 1) -- percent text F.TextRight = overlay:CreateFontString(nil, "OVERLAY") F.TextRight:SetFont(CFG.font, CFG.barFontSize, "OUTLINE") F.TextRight:SetPoint("RIGHT", overlay, "RIGHT", -8, 0) F.TextRight:SetJustifyH("RIGHT") F.TextRight:SetDrawLayer("OVERLAY", 2) F.TextRight:SetTextColor(1, 1, 1, 1) -- text below the bar (completed quests/rested) F.InfoText = overlay:CreateFontString(nil, "OVERLAY") F.InfoText:SetFont(CFG.font, CFG.fontSize - 2, "OUTLINE") F.InfoText:SetPoint("TOP", overlay, "BOTTOM", 0, -8) F.InfoText:SetJustifyH("CENTER") F.InfoText:SetDrawLayer("OVERLAY", 2) F.InfoText:SetTextColor(1, 1, 1, 1) -- quest/rested XP amounts F.QuestXPText = overlay:CreateFontString(nil, "OVERLAY") F.QuestXPText:SetFont(CFG.font, CFG.fontSize - 2, "OUTLINE") F.QuestXPText:SetPoint("TOP", overlay, "BOTTOM", 0, -22) F.QuestXPText:SetJustifyH("CENTER") F.QuestXPText:SetDrawLayer("OVERLAY", 2) F.QuestXPText:SetTextColor(1, 1, 1, 1) -- Leveling info F.LevelingText = overlay:CreateFontString(nil, "OVERLAY") F.LevelingText:SetFont(CFG.font, CFG.fontSize - 2, "OUTLINE") F.LevelingText:SetPoint("BOTTOMLEFT", overlay, "TOPLEFT", 0, 6) F.LevelingText:SetJustifyH("LEFT") F.LevelingText:SetDrawLayer("OVERLAY", 2) F.LevelingText:SetTextColor(1, 1, 1, 1) F.UI.Update() F.UI.HideBlizzardBars() end function F.UI.HideBlizzardBars() if not (F and F.DB and F.DB.hideBlizzardXPBar) then return end if MainMenuExpBar then MainMenuExpBar:UnregisterAllEvents() MainMenuExpBar:Hide() end if ReputationWatchBar then ReputationWatchBar:UnregisterAllEvents() ReputationWatchBar:Hide() end if StatusTrackingBarManager then StatusTrackingBarManager:UnregisterAllEvents() StatusTrackingBarManager:Hide() end if MainMenuBarPerformanceBar then MainMenuBarPerformanceBar:UnregisterAllEvents() MainMenuBarPerformanceBar:Hide() end end function F.UI.Update() if not F.Frame then F.UI.Create() end local s = F.State local db = F.DB if db and db.barWidth and db.barHeight then local width = db.barWidth if width then local scale = F.Frame:GetEffectiveScale() or 1 local stepPixels = math.floor(((width / 20) * scale) + 0.5) width = (stepPixels / scale) * 20 end F.Frame:SetSize(width or db.barWidth, db.barHeight) end if db then local anchor = db.anchorPoint or "TOP" local x = tonumber(db.offsetX) or 0 local y = tonumber(db.offsetY) or 0 if anchor == "TOP" then y = -y end F.Frame:ClearAllPoints() F.Frame:SetPoint(anchor, UIParent, anchor, x, y) end if F.TickContainer then if db and db.showTicks then local width = F.Frame:GetWidth() local height = F.Frame:GetHeight() if width and width > 0 then local step = width / 20 local scale = F.Frame:GetEffectiveScale() or 1 local opacity = 0.35 if db and type(db.tickOpacity) == "number" then opacity = db.tickOpacity end for i = 1, 19 do local tick = F.TickContainer.ticks[i] if tick then local x = step * i x = math.floor((x * scale) + 0.5) / scale tick:ClearAllPoints() tick:SetColorTexture(0, 0, 0, opacity) tick:SetPoint("TOPLEFT", F.Frame, "TOPLEFT", x, 0) tick:SetPoint("BOTTOMLEFT", F.Frame, "BOTTOMLEFT", x, 0) tick:Show() end end F.TickContainer:Show() else F.TickContainer:Hide() end else F.TickContainer:Hide() end end local function resolveColor(color, fallback) if type(color) == "table" then if color.r then return color.r or 1, color.g or 1, color.b or 1, color.a or 1 end if color[1] then return color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1 end end return fallback[1], fallback[2], fallback[3], fallback[4] end if db then local fontPath = db.fontPath or CFG.font F.LevelText:SetFont(fontPath, CFG.barFontSize, "OUTLINE") F.TextCenter:SetFont(fontPath, CFG.barFontSize, "OUTLINE") F.TextRight:SetFont(fontPath, CFG.barFontSize, "OUTLINE") F.InfoText:SetFont(fontPath, CFG.fontSize - 2, "OUTLINE") F.LevelingText:SetFont(fontPath, CFG.fontSize - 2, "OUTLINE") if F.QuestXPText then F.QuestXPText:SetFont(fontPath, CFG.fontSize - 2, "OUTLINE") end local mainTexture = db.barTextureMain or CFG.texture local restedTexture = db.barTextureRested or CFG.texture local questTexture = db.barTextureQuest or CFG.texture local questCompleteTexture = db.barTextureQuestComplete or CFG.texture F.BarMain:SetStatusBarTexture(mainTexture) F.BarRested:SetStatusBarTexture(restedTexture) F.BarQuest:SetStatusBarTexture(questTexture) F.BarQuestComplete:SetStatusBarTexture(questCompleteTexture) F.BarMain:SetStatusBarColor(resolveColor(db.colorMain, CFG.colorMain)) F.BarRested:SetStatusBarColor(resolveColor(db.colorRested, CFG.colorRested)) F.BarQuest:SetStatusBarColor(resolveColor(db.colorQuest, CFG.colorQuest)) F.BarQuestComplete:SetStatusBarColor(resolveColor(db.colorQuestComplete, CFG.colorQuestComplete)) end if s.isMaxLevel and not db.showAtMaxLevel then F.Frame:Hide() return else F.Frame:Show() end if s.isMaxLevel then F.BarMain:Hide() F.BarQuest:Hide() F.BarRested:Hide() else F.BarMain:Show() end local maxXP = (s.maxXP > 0) and s.maxXP or 1 F.BarMain:SetMinMaxValues(0, maxXP) F.BarMain:SetValue(math.min(s.currentXP, maxXP)) local projectedComplete = s.currentXP + (s.completeXP or 0) local projectedQuest = projectedComplete + (db.showIncompleteQuestBar and (s.incompleteXP or 0) or 0) if s.maxXP > 0 and projectedComplete > s.currentXP then F.BarQuestComplete:Show() F.BarQuestComplete:SetMinMaxValues(0, maxXP) F.BarQuestComplete:SetValue(math.min(projectedComplete, s.maxXP)) else F.BarQuestComplete:Hide() end if s.maxXP > 0 and projectedQuest > projectedComplete and projectedComplete < s.maxXP then F.BarQuest:Show() F.BarQuest:SetMinMaxValues(0, maxXP) F.BarQuest:SetValue(math.min(projectedQuest, s.maxXP)) else F.BarQuest:Hide() end local projectedRested = s.currentXP + (s.restedXP or 0) if s.maxXP > 0 and (s.restedXP or 0) > 0 and s.currentXP < s.maxXP then F.BarRested:Show() F.BarRested:SetMinMaxValues(0, maxXP) F.BarRested:SetValue(math.min(projectedRested, s.maxXP)) else F.BarRested:Hide() end local hourlyXP, timeToLevel, sessionTime = F.State:GetSessionStats() local percentXP = (s.maxXP > 0) and (s.currentXP / s.maxXP * 100) or 0 local percentRested = (s.maxXP > 0) and (s.restedXP / s.maxXP * 100) or 0 if db.showLevelText then F.LevelText:SetText("Level " .. s.level) F.LevelText:Show() else F.LevelText:SetText("") F.LevelText:Hide() end if db.showXPText then if s.isMaxLevel then F.TextCenter:SetText("Level " .. s.level .. " (Max)") else F.TextCenter:SetText(string.format("%s / %s", FormatLargeNumber(s.currentXP), FormatLargeNumber(s.maxXP))) end F.TextCenter:Show() else F.TextCenter:SetText("") F.TextCenter:Hide() end local percentStr = "" if not s.isMaxLevel and s.maxXP > 0 then local completedXP = (s.completeXP or 0) local percentCompleted = (s.maxXP > 0) and (completedXP / s.maxXP * 100) or 0 local percentWithComplete = percentXP + percentCompleted if completedXP > 0 then percentStr = string.format("%.1f%% (%.1f%%)", percentXP, math.min(100, percentWithComplete)) elseif (s.restedXP or 0) > 0 and db.showQuestRestedText then local percentWithRest = percentXP + percentRested percentStr = string.format("%.1f%% (%.1f%%)", percentXP, math.min(100, percentWithRest)) else percentStr = string.format("%.1f%%", percentXP) end end if db.showPercentText and percentStr ~= "" and s.maxXP > 0 then F.TextRight:SetText(percentStr) F.TextRight:Show() else F.TextRight:SetText("") F.TextRight:Hide() end do local infoParts = {} if db.showQuestRestedText and s.maxXP > 0 then if db.questTrackingEnabled ~= false and (s.completeXP or 0) > 0 then table.insert(infoParts, string.format("Completed Quests: %.1f%%", ((s.completeXP or 0) / s.maxXP * 100))) end if (s.restedXP or 0) > 0 then table.insert(infoParts, string.format("Rested Experience: %.1f%%", ((s.restedXP or 0) / s.maxXP * 100))) end end local levelingStr = "" if db.showXPHourText and not s.isMaxLevel and hourlyXP > 0 then local xpRateText = hourlyXP > 10000 and (F.Utils.Round(hourlyXP / 1000, 1) .. "K") or FormatLargeNumber(hourlyXP) local timerText = timeToLevel > 0 and F.Utils.FormatTime(timeToLevel) or "--" levelingStr = string.format("Leveling in: %s (%s XP/Hr)", timerText, xpRateText) end if db.showQuestRestedText and #infoParts > 0 then F.InfoText:SetText(table.concat(infoParts, " - ")) F.InfoText:Show() else F.InfoText:SetText("") F.InfoText:Hide() end if db.showLevelingText and levelingStr ~= "" then F.LevelingText:SetText(levelingStr) F.LevelingText:Show() else F.LevelingText:SetText("") F.LevelingText:Hide() end end do local xpParts = {} if db.questTrackingEnabled ~= false then if db.showIncompleteQuestXPText and (s.incompleteXP or 0) > 0 then table.insert(xpParts, string.format("Uncompleted Quest XP: %s", FormatLargeNumber(s.incompleteXP or 0))) end if db.showCompletedQuestXPText and (s.completeXP or 0) > 0 then table.insert(xpParts, string.format("Completed Quest XP: %s", FormatLargeNumber(s.completeXP or 0))) end end if db.showRestedXPText and (s.restedXP or 0) > 0 then table.insert(xpParts, string.format("Rested XP: %s", FormatLargeNumber(s.restedXP or 0))) end if #xpParts > 0 then F.QuestXPText:SetText(table.concat(xpParts, " - ")) F.QuestXPText:Show() else F.QuestXPText:SetText("") F.QuestXPText:Hide() end end end