A World of Warcraft Experience Bar addon
worldofwarcraft
wow
addon
midnight
1local AddonName, F = ...
2
3F.UI = {}
4
5local CFG = {
6 width = 600,
7 height = 24,
8 texture = "Interface\\Buttons\\WHITE8x8",
9 font = "Fonts\\FRIZQT__.TTF",
10 fontSize = 14,
11 barFontSize = 12,
12
13 -- Colors (R, G, B, A)
14 colorBack = {0.0, 0.0, 0.0, 0.8},
15 colorBorder = {0, 0, 0, 1},
16
17 colorMain = {0.76, 0.38, 1, 1}, -- Base Purple
18 colorRested = {0.34, 0.61, 0.99, .8}, -- Rested Blue
19 colorQuest = {1, 0.64, 0.0078, 0.25}, -- Quest Yellow
20 colorQuestComplete = {1, 0.64, 0.0078, 0.8}, -- Completed Quest
21}
22
23function F.UI.CreateStatusBar(parent, layer, subLevel, color)
24 local bar = CreateFrame("StatusBar", nil, parent)
25 bar:SetAllPoints()
26 bar:SetStatusBarTexture(CFG.texture)
27 bar:SetStatusBarColor(unpack(color))
28 bar:GetStatusBarTexture():SetDrawLayer(layer, subLevel)
29 return bar
30end
31
32function F.UI.Create()
33 local f = CreateFrame("Frame", "NXP_Frame", UIParent)
34 f:SetSize(CFG.width, CFG.height)
35 f:SetPoint("TOP", UIParent, "TOP", 0, 100) -- Default Position
36 f:SetFrameStrata("LOW")
37
38 f:SetMovable(false)
39 f:EnableMouse(false)
40
41 F.Frame = f
42
43 -- 2. Background & Border
44 local bg = f:CreateTexture(nil, "BACKGROUND")
45 bg:SetAllPoints()
46 bg:SetTexture(CFG.texture)
47 bg:SetVertexColor(unpack(CFG.colorBack))
48
49 -- Rested XP Bar
50 F.BarRested = F.UI.CreateStatusBar(f, "BACKGROUND", 2, CFG.colorRested)
51
52 -- Quest XP Bar
53 F.BarQuest = F.UI.CreateStatusBar(f, "BORDER", 1, CFG.colorQuest)
54
55 -- Completed Quest Bar
56 F.BarQuestComplete = F.UI.CreateStatusBar(f, "BORDER", 2, CFG.colorQuestComplete)
57
58 -- Main XP Bar
59 F.BarMain = F.UI.CreateStatusBar(f, "ARTWORK", 1, CFG.colorMain)
60
61 -- XP Ticks (20 segments)
62 local tickContainer = CreateFrame("Frame", nil, f)
63 tickContainer:SetAllPoints()
64 tickContainer.ticks = {}
65 for i = 1, 19 do
66 local tick = tickContainer:CreateTexture(nil, "OVERLAY")
67 tick:SetColorTexture(0, 0, 0, 0.35)
68 tick:SetWidth(1)
69 tickContainer.ticks[i] = tick
70 end
71 F.TickContainer = tickContainer
72
73 local overlay = CreateFrame("Frame", nil, f)
74 overlay:SetAllPoints()
75 overlay:SetFrameLevel(f:GetFrameLevel() + 10)
76 F.Overlay = overlay
77
78 -- level label
79 F.LevelText = overlay:CreateFontString(nil, "OVERLAY")
80 F.LevelText:SetFont(CFG.font, CFG.barFontSize, "OUTLINE")
81 F.LevelText:SetPoint("LEFT", overlay, "LEFT", 8, 0)
82 F.LevelText:SetJustifyH("LEFT")
83 F.LevelText:SetDrawLayer("OVERLAY", 2)
84 F.LevelText:SetTextColor(1, 1, 1, 1)
85
86 -- current / max XP
87 F.TextCenter = overlay:CreateFontString(nil, "OVERLAY")
88 F.TextCenter:SetFont(CFG.font, CFG.barFontSize, "OUTLINE")
89 F.TextCenter:SetPoint("CENTER", overlay, "CENTER", 0, 0)
90 F.TextCenter:SetJustifyH("CENTER")
91 F.TextCenter:SetDrawLayer("OVERLAY", 2)
92 F.TextCenter:SetTextColor(1, 1, 1, 1)
93
94 -- percent text
95 F.TextRight = overlay:CreateFontString(nil, "OVERLAY")
96 F.TextRight:SetFont(CFG.font, CFG.barFontSize, "OUTLINE")
97 F.TextRight:SetPoint("RIGHT", overlay, "RIGHT", -8, 0)
98 F.TextRight:SetJustifyH("RIGHT")
99 F.TextRight:SetDrawLayer("OVERLAY", 2)
100 F.TextRight:SetTextColor(1, 1, 1, 1)
101
102 -- text below the bar (completed quests/rested)
103 F.InfoText = overlay:CreateFontString(nil, "OVERLAY")
104 F.InfoText:SetFont(CFG.font, CFG.fontSize - 2, "OUTLINE")
105 F.InfoText:SetPoint("TOP", overlay, "BOTTOM", 0, -8)
106 F.InfoText:SetJustifyH("CENTER")
107 F.InfoText:SetDrawLayer("OVERLAY", 2)
108 F.InfoText:SetTextColor(1, 1, 1, 1)
109
110 -- quest/rested XP amounts
111 F.QuestXPText = overlay:CreateFontString(nil, "OVERLAY")
112 F.QuestXPText:SetFont(CFG.font, CFG.fontSize - 2, "OUTLINE")
113 F.QuestXPText:SetPoint("TOP", overlay, "BOTTOM", 0, -22)
114 F.QuestXPText:SetJustifyH("CENTER")
115 F.QuestXPText:SetDrawLayer("OVERLAY", 2)
116 F.QuestXPText:SetTextColor(1, 1, 1, 1)
117
118 -- Leveling info
119 F.LevelingText = overlay:CreateFontString(nil, "OVERLAY")
120 F.LevelingText:SetFont(CFG.font, CFG.fontSize - 2, "OUTLINE")
121 F.LevelingText:SetPoint("BOTTOMLEFT", overlay, "TOPLEFT", 0, 6)
122 F.LevelingText:SetJustifyH("LEFT")
123 F.LevelingText:SetDrawLayer("OVERLAY", 2)
124 F.LevelingText:SetTextColor(1, 1, 1, 1)
125
126 F.UI.Update()
127
128 F.UI.HideBlizzardBars()
129end
130
131function F.UI.HideBlizzardBars()
132 if not (F and F.DB and F.DB.hideBlizzardXPBar) then return end
133
134 if MainMenuExpBar then
135 MainMenuExpBar:UnregisterAllEvents()
136 MainMenuExpBar:Hide()
137 end
138
139 if ReputationWatchBar then
140 ReputationWatchBar:UnregisterAllEvents()
141 ReputationWatchBar:Hide()
142 end
143
144 if StatusTrackingBarManager then
145 StatusTrackingBarManager:UnregisterAllEvents()
146 StatusTrackingBarManager:Hide()
147 end
148
149 if MainMenuBarPerformanceBar then
150 MainMenuBarPerformanceBar:UnregisterAllEvents()
151 MainMenuBarPerformanceBar:Hide()
152 end
153end
154
155function F.UI.Update()
156 if not F.Frame then F.UI.Create() end
157
158 local s = F.State
159 local db = F.DB
160
161 if db and db.barWidth and db.barHeight then
162 local width = db.barWidth
163 if width then
164 local scale = F.Frame:GetEffectiveScale() or 1
165 local stepPixels = math.floor(((width / 20) * scale) + 0.5)
166 width = (stepPixels / scale) * 20
167 end
168 F.Frame:SetSize(width or db.barWidth, db.barHeight)
169 end
170
171 if db then
172 local anchor = db.anchorPoint or "TOP"
173 local x = tonumber(db.offsetX) or 0
174 local y = tonumber(db.offsetY) or 0
175 if anchor == "TOP" then
176 y = -y
177 end
178 F.Frame:ClearAllPoints()
179 F.Frame:SetPoint(anchor, UIParent, anchor, x, y)
180 end
181
182 if F.TickContainer then
183 if db and db.showTicks then
184 local width = F.Frame:GetWidth()
185 local height = F.Frame:GetHeight()
186 if width and width > 0 then
187 local step = width / 20
188 local scale = F.Frame:GetEffectiveScale() or 1
189 local opacity = 0.35
190 if db and type(db.tickOpacity) == "number" then
191 opacity = db.tickOpacity
192 end
193 for i = 1, 19 do
194 local tick = F.TickContainer.ticks[i]
195 if tick then
196 local x = step * i
197 x = math.floor((x * scale) + 0.5) / scale
198 tick:ClearAllPoints()
199 tick:SetColorTexture(0, 0, 0, opacity)
200 tick:SetPoint("TOPLEFT", F.Frame, "TOPLEFT", x, 0)
201 tick:SetPoint("BOTTOMLEFT", F.Frame, "BOTTOMLEFT", x, 0)
202 tick:Show()
203 end
204 end
205 F.TickContainer:Show()
206 else
207 F.TickContainer:Hide()
208 end
209 else
210 F.TickContainer:Hide()
211 end
212 end
213
214 local function resolveColor(color, fallback)
215 if type(color) == "table" then
216 if color.r then
217 return color.r or 1, color.g or 1, color.b or 1, color.a or 1
218 end
219 if color[1] then
220 return color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1
221 end
222 end
223 return fallback[1], fallback[2], fallback[3], fallback[4]
224 end
225
226 if db then
227 local fontPath = db.fontPath or CFG.font
228 F.LevelText:SetFont(fontPath, CFG.barFontSize, "OUTLINE")
229 F.TextCenter:SetFont(fontPath, CFG.barFontSize, "OUTLINE")
230 F.TextRight:SetFont(fontPath, CFG.barFontSize, "OUTLINE")
231 F.InfoText:SetFont(fontPath, CFG.fontSize - 2, "OUTLINE")
232 F.LevelingText:SetFont(fontPath, CFG.fontSize - 2, "OUTLINE")
233 if F.QuestXPText then
234 F.QuestXPText:SetFont(fontPath, CFG.fontSize - 2, "OUTLINE")
235 end
236
237 local mainTexture = db.barTextureMain or CFG.texture
238 local restedTexture = db.barTextureRested or CFG.texture
239 local questTexture = db.barTextureQuest or CFG.texture
240 local questCompleteTexture = db.barTextureQuestComplete or CFG.texture
241 F.BarMain:SetStatusBarTexture(mainTexture)
242 F.BarRested:SetStatusBarTexture(restedTexture)
243 F.BarQuest:SetStatusBarTexture(questTexture)
244 F.BarQuestComplete:SetStatusBarTexture(questCompleteTexture)
245
246 F.BarMain:SetStatusBarColor(resolveColor(db.colorMain, CFG.colorMain))
247 F.BarRested:SetStatusBarColor(resolveColor(db.colorRested, CFG.colorRested))
248 F.BarQuest:SetStatusBarColor(resolveColor(db.colorQuest, CFG.colorQuest))
249 F.BarQuestComplete:SetStatusBarColor(resolveColor(db.colorQuestComplete, CFG.colorQuestComplete))
250 end
251
252 if s.isMaxLevel and not db.showAtMaxLevel then
253 F.Frame:Hide()
254 return
255 else
256 F.Frame:Show()
257 end
258
259 if s.isMaxLevel then
260 F.BarMain:Hide()
261 F.BarQuest:Hide()
262 F.BarRested:Hide()
263 else
264 F.BarMain:Show()
265 end
266
267 local maxXP = (s.maxXP > 0) and s.maxXP or 1
268
269 F.BarMain:SetMinMaxValues(0, maxXP)
270 F.BarMain:SetValue(math.min(s.currentXP, maxXP))
271
272 local projectedComplete = s.currentXP + (s.completeXP or 0)
273 local projectedQuest = projectedComplete + (db.showIncompleteQuestBar and (s.incompleteXP or 0) or 0)
274
275 if s.maxXP > 0 and projectedComplete > s.currentXP then
276 F.BarQuestComplete:Show()
277 F.BarQuestComplete:SetMinMaxValues(0, maxXP)
278 F.BarQuestComplete:SetValue(math.min(projectedComplete, s.maxXP))
279 else
280 F.BarQuestComplete:Hide()
281 end
282
283 if s.maxXP > 0 and projectedQuest > projectedComplete and projectedComplete < s.maxXP then
284 F.BarQuest:Show()
285 F.BarQuest:SetMinMaxValues(0, maxXP)
286 F.BarQuest:SetValue(math.min(projectedQuest, s.maxXP))
287 else
288 F.BarQuest:Hide()
289 end
290
291 local projectedRested = s.currentXP + (s.restedXP or 0)
292 if s.maxXP > 0 and (s.restedXP or 0) > 0 and s.currentXP < s.maxXP then
293 F.BarRested:Show()
294 F.BarRested:SetMinMaxValues(0, maxXP)
295 F.BarRested:SetValue(math.min(projectedRested, s.maxXP))
296 else
297 F.BarRested:Hide()
298 end
299
300 local hourlyXP, timeToLevel, sessionTime = F.State:GetSessionStats()
301 local percentXP = (s.maxXP > 0) and (s.currentXP / s.maxXP * 100) or 0
302 local percentRested = (s.maxXP > 0) and (s.restedXP / s.maxXP * 100) or 0
303
304 if db.showLevelText then
305 F.LevelText:SetText("Level " .. s.level)
306 F.LevelText:Show()
307 else
308 F.LevelText:SetText("")
309 F.LevelText:Hide()
310 end
311
312 if db.showXPText then
313 if s.isMaxLevel then
314 F.TextCenter:SetText("Level " .. s.level .. " (Max)")
315 else
316 F.TextCenter:SetText(string.format("%s / %s", FormatLargeNumber(s.currentXP), FormatLargeNumber(s.maxXP)))
317 end
318 F.TextCenter:Show()
319 else
320 F.TextCenter:SetText("")
321 F.TextCenter:Hide()
322 end
323
324 local percentStr = ""
325 if not s.isMaxLevel and s.maxXP > 0 then
326 local completedXP = (s.completeXP or 0)
327 local percentCompleted = (s.maxXP > 0) and (completedXP / s.maxXP * 100) or 0
328 local percentWithComplete = percentXP + percentCompleted
329
330 if completedXP > 0 then
331 percentStr = string.format("%.1f%% (%.1f%%)", percentXP, math.min(100, percentWithComplete))
332 elseif (s.restedXP or 0) > 0 and db.showQuestRestedText then
333 local percentWithRest = percentXP + percentRested
334 percentStr = string.format("%.1f%% (%.1f%%)", percentXP, math.min(100, percentWithRest))
335 else
336 percentStr = string.format("%.1f%%", percentXP)
337 end
338 end
339
340 if db.showPercentText and percentStr ~= "" and s.maxXP > 0 then
341 F.TextRight:SetText(percentStr)
342 F.TextRight:Show()
343 else
344 F.TextRight:SetText("")
345 F.TextRight:Hide()
346 end
347
348 do
349 local infoParts = {}
350
351 if db.showQuestRestedText and s.maxXP > 0 then
352 if db.questTrackingEnabled ~= false and (s.completeXP or 0) > 0 then
353 table.insert(infoParts, string.format("Completed Quests: %.1f%%", ((s.completeXP or 0) / s.maxXP * 100)))
354 end
355 if (s.restedXP or 0) > 0 then
356 table.insert(infoParts, string.format("Rested Experience: %.1f%%", ((s.restedXP or 0) / s.maxXP * 100)))
357 end
358 end
359
360 local levelingStr = ""
361 if db.showXPHourText and not s.isMaxLevel and hourlyXP > 0 then
362 local xpRateText = hourlyXP > 10000 and (F.Utils.Round(hourlyXP / 1000, 1) .. "K") or FormatLargeNumber(hourlyXP)
363 local timerText = timeToLevel > 0 and F.Utils.FormatTime(timeToLevel) or "--"
364 levelingStr = string.format("Leveling in: %s (%s XP/Hr)", timerText, xpRateText)
365 end
366
367 if db.showQuestRestedText and #infoParts > 0 then
368 F.InfoText:SetText(table.concat(infoParts, " - "))
369 F.InfoText:Show()
370 else
371 F.InfoText:SetText("")
372 F.InfoText:Hide()
373 end
374
375 if db.showLevelingText and levelingStr ~= "" then
376 F.LevelingText:SetText(levelingStr)
377 F.LevelingText:Show()
378 else
379 F.LevelingText:SetText("")
380 F.LevelingText:Hide()
381 end
382 end
383
384 do
385 local xpParts = {}
386 if db.questTrackingEnabled ~= false then
387 if db.showIncompleteQuestXPText and (s.incompleteXP or 0) > 0 then
388 table.insert(xpParts, string.format("Uncompleted Quest XP: %s", FormatLargeNumber(s.incompleteXP or 0)))
389 end
390 if db.showCompletedQuestXPText and (s.completeXP or 0) > 0 then
391 table.insert(xpParts, string.format("Completed Quest XP: %s", FormatLargeNumber(s.completeXP or 0)))
392 end
393 end
394 if db.showRestedXPText and (s.restedXP or 0) > 0 then
395 table.insert(xpParts, string.format("Rested XP: %s", FormatLargeNumber(s.restedXP or 0)))
396 end
397
398 if #xpParts > 0 then
399 F.QuestXPText:SetText(table.concat(xpParts, " - "))
400 F.QuestXPText:Show()
401 else
402 F.QuestXPText:SetText("")
403 F.QuestXPText:Hide()
404 end
405 end
406end