A World of Warcraft Experience Bar addon
worldofwarcraft wow addon midnight

Clean up comments and docs Clean up comments and docs

Simplify and remove redundant comments across Core, State, UI, and
Utils.
Clarify terse notes (ticker, quest XP handling, UI setup) and remove
outdated
or verbose documentation. No functional changes.

bdbch 2ff6a961 9f201d8c

+17 -103
+2 -4
Core.lua
··· 24 24 if event == "PLAYER_ENTERING_WORLD" then 25 25 F.State:UpdatePlayerXP() 26 26 F.State:UpdateQuestXP() 27 - -- Ensure Blizzard bars stay hidden after entering world (in case something re-enabled them) 27 + -- Hide Blizzard bars after entering world (in case something re-enabled them) 28 28 if F.UI and F.UI.HideBlizzardBars then 29 29 F.UI.HideBlizzardBars() 30 30 end ··· 50 50 F.UpdateUI() 51 51 end) 52 52 53 - -- 2. The Ticker 54 - -- Updates every second for time-based texts (Session time, etc) 53 + -- ticker that updates the UI each second 55 54 C_Timer.NewTicker(1, function() 56 55 F.UpdateUI() 57 56 end) 58 57 59 - -- 3. UI Update (The "Main" function) 60 58 function F.UpdateUI() 61 59 if F.UI and F.UI.Update then 62 60 F.UI.Update()
+3 -14
State.lua
··· 23 23 } 24 24 } 25 25 26 - --- Updates basic player XP data 27 26 function F.State:UpdatePlayerXP() 28 27 self.level = UnitLevel("player") 29 28 self.currentXP = UnitXP("player") ··· 36 35 self.isMaxLevel = self.level >= maxLevel 37 36 end 38 37 39 - --- Updates Quest XP (Iterates Quest Log) 40 - --- Optimized to define API calls locally for performance 41 38 function F.State:UpdateQuestXP() 42 39 local numEntries = C_QuestLog.GetNumQuestLogEntries() 43 40 local qXP, cXP, iXP = 0, 0, 0 44 41 45 42 for i = 1, numEntries do 46 43 local info = C_QuestLog.GetInfo(i) 47 - -- Skip header / hidden entries (they are not selectable quests) 44 + -- this skips the XP header for a category / campaign as they should not be respected 48 45 if info and not info.isHeader and not info.isHidden and info.questID > 0 then 49 - -- Use the quest log index when querying the reward XP (API expects the index) 46 + -- Use the quest log index when querying the reward XP 50 47 local rewardXP = GetQuestLogRewardXP(i) or 0 51 48 if rewardXP > 0 then 52 49 qXP = qXP + rewardXP ··· 64 61 self.incompleteXP = iXP 65 62 end 66 63 67 - --- Debug helper: dump quest entries and computed quest XP totals 68 - --- Call with: F.State:DebugDumpQuests() or use the slash command /nxpdebug 69 64 function F.State:DebugDumpQuests() 70 65 local numEntries = C_QuestLog.GetNumQuestLogEntries() 71 66 print("NixxnuxXPBar: DebugDumpQuests - numEntries =", numEntries) ··· 120 115 F.State:DebugDumpQuests() 121 116 end 122 117 123 - --- Calculates XP per hour and Time to Level 124 - --- @return number hourlyXP 125 - --- @return number timeToLevel (seconds) 126 118 function F.State:GetSessionStats() 127 - local currentTime = GetTime() -- Using GetTime for session delta usually safer than time() 128 - -- Note: Original WA used time(), stick to time() if syncing with real world clock is needed, 129 - -- but for session duration GetTime() is monotonic. 130 - -- Sticking to time() to match WA logic for now: 119 + local currentTime = GetTime() 131 120 local now = time() 132 121 133 122 local sessionTime = now - self.session.startTime
+11 -68
UI.lua
··· 2 2 3 3 F.UI = {} 4 4 5 - -- Visual Configuration 6 5 local CFG = { 7 6 width = 600, 8 7 height = 24, 9 - texture = "Interface\\Buttons\\WHITE8x8", -- Flat, clean texture 8 + texture = "Interface\\Buttons\\WHITE8x8", 10 9 font = "Fonts\\FRIZQT__.TTF", 11 10 fontSize = 14, 12 11 barFontSize = 12, ··· 21 20 colorQuestComplete = {0.86, 0.52, 0.01, 1}, -- Completed Quest (darker) 22 21 } 23 22 24 - --- Helper to create consistent status bars 25 - --- @param parent table The parent frame 26 - --- @param layer string The draw layer (BACKGROUND, BORDER, ARTWORK) 27 - --- @param subLevel number The sub-level for fine-grained stacking 28 - --- @param color table {r, g, b, a} 29 23 function F.UI.CreateStatusBar(parent, layer, subLevel, color) 30 24 local bar = CreateFrame("StatusBar", nil, parent) 31 25 bar:SetAllPoints() 32 26 bar:SetStatusBarTexture(CFG.texture) 33 27 bar:SetStatusBarColor(unpack(color)) 34 - -- SetDrawLayer allows us to stack bars on top of each other explicitly 35 28 bar:GetStatusBarTexture():SetDrawLayer(layer, subLevel) 36 29 return bar 37 30 end 38 31 39 - --- Creates the main frames and status bars 40 32 function F.UI.Create() 41 - -- 1. Main Container (Movable Anchor) 42 33 local f = CreateFrame("Frame", "NXP_Frame", UIParent) 43 34 f:SetSize(CFG.width, CFG.height) 44 35 f:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, 260) -- Default Position 45 36 f:SetFrameStrata("LOW") 46 37 47 - -- Enable dragging (only while Shift is held). Also add snap-to-center when dropped near the middle. 38 + -- Enable dragging (only while Shift is held) 48 39 f:SetMovable(true) 49 40 f:EnableMouse(false) 50 41 f:RegisterForDrag("LeftButton") ··· 54 45 self:StopMovingOrSizing() 55 46 self.__nxb_moving = nil 56 47 57 - -- Attempt to snap to center if dropped within a radial threshold of the UIParent center 58 48 local fx, fy = self:GetCenter() 59 49 if fx and fy then 60 50 local cx, cy = UIParent:GetCenter() ··· 69 59 end 70 60 end 71 61 72 - -- ensure any update watcher is removed when we stop moving 73 62 self:SetScript("OnUpdate", nil) 74 63 end 75 64 end ··· 77 66 f:SetScript("OnDragStart", function(self) 78 67 if IsShiftKeyDown() then 79 68 self:StartMoving() 80 - -- mark as moving so we don't rely on IsMoving API (may be nil in some environments) 81 69 self.__nxb_moving = true 82 70 83 - -- Fallback watcher: if SHIFT is released or mouse is no longer down, stop dragging 84 71 self:SetScript("OnUpdate", function(self, elapsed) 85 72 if not IsShiftKeyDown() or not IsMouseButtonDown("LeftButton") then 86 73 stopAndMaybeSnap(self) ··· 91 78 92 79 f:SetScript("OnDragStop", stopAndMaybeSnap) 93 80 94 - -- Also only respond to direct clicks for moving when Shift is held 95 81 f:SetScript("OnMouseDown", function(self, button) 96 82 if button == "LeftButton" and IsShiftKeyDown() then 97 83 self:StartMoving() 98 - -- mark as moving so we can detect/release reliably 99 84 self.__nxb_moving = true 100 85 101 - -- Fallback watcher: if SHIFT is released or mouse is no longer down, stop dragging 102 86 self:SetScript("OnUpdate", function(self, elapsed) 103 87 if not IsShiftKeyDown() or not IsMouseButtonDown("LeftButton") then 104 88 stopAndMaybeSnap(self) ··· 113 97 end 114 98 end) 115 99 116 - -- Stop dragging if SHIFT is released while dragging (listen to modifier changes) 117 100 f:RegisterEvent("MODIFIER_STATE_CHANGED") 118 101 f:SetScript("OnEvent", function(self, event, key, state) 119 102 if event == "MODIFIER_STATE_CHANGED" then 120 103 -- Enable/disable clicks based on Shift state 121 104 self:EnableMouse(IsShiftKeyDown()) 122 - -- If Shift is no longer held while we are moving, stop and snap 123 105 if not IsShiftKeyDown() and self.__nxb_moving then 124 106 stopAndMaybeSnap(self) 125 107 end ··· 134 116 bg:SetTexture(CFG.texture) 135 117 bg:SetVertexColor(unpack(CFG.colorBack)) 136 118 137 - 138 - 139 - -- 3. The Status Bars (Layered Bottom to Top) 140 - 141 - -- Rested XP Bar (BACKGROUND layer, visible only if extending past Main) 119 + -- Rested XP Bar 142 120 F.BarRested = F.UI.CreateStatusBar(f, "BACKGROUND", 2, CFG.colorRested) 143 121 144 - -- Quest XP Bar (BORDER layer, sits on top of Rested, below Main) 122 + -- Quest XP Bar 145 123 F.BarQuest = F.UI.CreateStatusBar(f, "BORDER", 1, CFG.colorQuest) 146 124 147 - -- Completed Quest Bar (BORDER layer, drawn above Quest; shows completed quest XP) 125 + -- Completed Quest Bar 148 126 F.BarQuestComplete = F.UI.CreateStatusBar(f, "BORDER", 2, CFG.colorQuestComplete) 149 127 150 - -- Main XP Bar (ARTWORK layer, sits on top of everything) 128 + -- Main XP Bar 151 129 F.BarMain = F.UI.CreateStatusBar(f, "ARTWORK", 1, CFG.colorMain) 152 130 153 - -- 4. Text Labels (all placed on an overlay so they always render above the bars) 154 131 local overlay = CreateFrame("Frame", nil, f) 155 132 overlay:SetAllPoints() 156 133 overlay:SetFrameLevel(f:GetFrameLevel() + 10) 157 134 F.Overlay = overlay 158 135 159 - -- Left: simple level label 136 + -- level label 160 137 F.LevelText = overlay:CreateFontString(nil, "OVERLAY") 161 138 F.LevelText:SetFont(CFG.font, CFG.barFontSize, "OUTLINE") 162 139 F.LevelText:SetPoint("LEFT", overlay, "LEFT", 8, 0) ··· 164 141 F.LevelText:SetDrawLayer("OVERLAY", 2) 165 142 F.LevelText:SetTextColor(1, 1, 1, 1) 166 143 167 - -- Center: current / max XP shown in the middle of the bar 144 + -- current / max XP 168 145 F.TextCenter = overlay:CreateFontString(nil, "OVERLAY") 169 146 F.TextCenter:SetFont(CFG.font, CFG.barFontSize, "OUTLINE") 170 147 F.TextCenter:SetPoint("CENTER", overlay, "CENTER", 0, 0) ··· 172 149 F.TextCenter:SetDrawLayer("OVERLAY", 2) 173 150 F.TextCenter:SetTextColor(1, 1, 1, 1) 174 151 175 - -- Right: percent text (plain right-aligned fontstring, no solid background) 152 + -- percent text 176 153 F.TextRight = overlay:CreateFontString(nil, "OVERLAY") 177 154 F.TextRight:SetFont(CFG.font, CFG.barFontSize, "OUTLINE") 178 155 F.TextRight:SetPoint("RIGHT", overlay, "RIGHT", -8, 0) ··· 180 157 F.TextRight:SetDrawLayer("OVERLAY", 2) 181 158 F.TextRight:SetTextColor(1, 1, 1, 1) 182 159 183 - -- Small informational text below the bar (completed quests/rested) 160 + -- text below the bar (completed quests/rested) 184 161 F.InfoText = overlay:CreateFontString(nil, "OVERLAY") 185 162 F.InfoText:SetFont(CFG.font, CFG.fontSize - 2, "OUTLINE") 186 163 F.InfoText:SetPoint("TOP", overlay, "BOTTOM", 0, -8) ··· 188 165 F.InfoText:SetDrawLayer("OVERLAY", 2) 189 166 F.InfoText:SetTextColor(1, 1, 1, 1) 190 167 191 - -- Leveling info shown above the bar on the top-left 168 + -- Leveling info 192 169 F.LevelingText = overlay:CreateFontString(nil, "OVERLAY") 193 170 F.LevelingText:SetFont(CFG.font, CFG.fontSize - 2, "OUTLINE") 194 171 F.LevelingText:SetPoint("BOTTOMLEFT", overlay, "TOPLEFT", 0, 6) ··· 196 173 F.LevelingText:SetDrawLayer("OVERLAY", 2) 197 174 F.LevelingText:SetTextColor(1, 1, 1, 1) 198 175 199 - -- Perform an initial update to set values 200 176 F.UI.Update() 201 177 202 - -- Hide default Blizzard bars if the option is enabled (guarded in the function) 203 178 F.UI.HideBlizzardBars() 204 179 end 205 180 206 - -- Utility: Hide Blizzard's default experience and reputation bars (if configured) 207 - -- This is safe even if the global Blizzard frames don't exist (we check for them). 208 181 function F.UI.HideBlizzardBars() 209 182 if not (F and F.DB and F.DB.hideBlizzardXPBar) then return end 210 183 211 - -- Hide the main experience bar if present 212 184 if MainMenuExpBar then 213 185 MainMenuExpBar:UnregisterAllEvents() 214 186 MainMenuExpBar:Hide() 215 187 end 216 188 217 - -- Hide the reputation watch bar (classic name) 218 189 if ReputationWatchBar then 219 190 ReputationWatchBar:UnregisterAllEvents() 220 191 ReputationWatchBar:Hide() 221 192 end 222 193 223 - -- Hide the status-tracking manager (modern clients) 224 194 if StatusTrackingBarManager then 225 195 StatusTrackingBarManager:UnregisterAllEvents() 226 196 StatusTrackingBarManager:Hide() 227 197 end 228 198 229 - -- Some clients expose a performance/progress bar container; hide if present 230 199 if MainMenuBarPerformanceBar then 231 200 MainMenuBarPerformanceBar:UnregisterAllEvents() 232 201 MainMenuBarPerformanceBar:Hide() 233 202 end 234 203 end 235 204 236 - --- The Main Draw Function (Called from Core.lua) 237 205 function F.UI.Update() 238 - -- Ensure frame exists 239 206 if not F.Frame then F.UI.Create() end 240 207 241 208 local s = F.State 242 209 local db = F.DB 243 210 244 - -- 1. Visibility Check 245 - -- If max level and option is set to hide, hide and return early 246 211 if s.isMaxLevel and not db.showAtMaxLevel then 247 212 F.Frame:Hide() 248 213 return ··· 250 215 F.Frame:Show() 251 216 end 252 217 253 - -- If we're at max level but configured to still show the frame, there is no XP to display 254 - -- so hide the bars and only show textual information. 255 218 if s.isMaxLevel then 256 219 F.BarMain:Hide() 257 220 F.BarQuest:Hide() ··· 260 223 F.BarMain:Show() 261 224 end 262 225 263 - -- 2. Update Bar Ranges & Values 264 - -- Guard against a zero maxXP (can happen briefly on login or if at max level) 265 226 local maxXP = (s.maxXP > 0) and s.maxXP or 1 266 227 267 - -- Main Bar: Actual current XP (clamped to the computed range) 268 228 F.BarMain:SetMinMaxValues(0, maxXP) 269 229 F.BarMain:SetValue(math.min(s.currentXP, maxXP)) 270 230 271 - -- Quest Bar: Visualizes both completed and total quest XP prediction 272 231 local projectedComplete = s.currentXP + (s.completeXP or 0) 273 - -- Total projection includes completed and optionally incomplete (config) 274 232 local projectedQuest = projectedComplete + (db.showIncompleteQuestBar and (s.incompleteXP or 0) or 0) 275 233 276 - -- Completed quest bar (darker): show only if it extends past current XP 277 234 if s.maxXP > 0 and projectedComplete > s.currentXP then 278 235 F.BarQuestComplete:Show() 279 236 F.BarQuestComplete:SetMinMaxValues(0, maxXP) ··· 282 239 F.BarQuestComplete:Hide() 283 240 end 284 241 285 - -- Total quest bar (lighter): shows only the delta beyond completed quests (if configured) 286 242 if s.maxXP > 0 and projectedQuest > projectedComplete and projectedComplete < s.maxXP then 287 243 F.BarQuest:Show() 288 - -- Restrict this bar's range so it represents only the incomplete delta beyond completed quests 289 244 F.BarQuest:SetMinMaxValues(projectedComplete, maxXP) 290 245 F.BarQuest:SetValue(math.min(projectedQuest, s.maxXP)) 291 246 else 292 247 F.BarQuest:Hide() 293 248 end 294 249 295 - -- Rested Bar: Visualizes "Potential" (Current + Rested) 296 250 local projectedRested = s.currentXP + (s.restedXP or 0) 297 251 if s.maxXP > 0 and (s.restedXP or 0) > 0 and s.currentXP < s.maxXP then 298 252 F.BarRested:Show() ··· 302 256 F.BarRested:Hide() 303 257 end 304 258 305 - -- 3. Text Updates 306 - -- Calculate derived stats for display 307 259 local hourlyXP, timeToLevel, sessionTime = F.State:GetSessionStats() 308 260 local percentXP = (s.maxXP > 0) and (s.currentXP / s.maxXP * 100) or 0 309 261 local percentRested = (s.maxXP > 0) and (s.restedXP / s.maxXP * 100) or 0 310 262 311 - -- Left / Center text updates 312 263 F.LevelText:SetText("Level " .. s.level) 313 264 314 - -- Center Text: show current / max XP in the bar center 315 265 if s.isMaxLevel then 316 266 F.TextCenter:SetText("Level " .. s.level .. " (Max)") 317 267 else 318 268 F.TextCenter:SetText(string.format("%s / %s", FormatLargeNumber(s.currentXP), FormatLargeNumber(s.maxXP))) 319 269 end 320 270 321 - -- Build percent string (e.g., "65.8% (68.2%)") and put it in the right-side percent box 322 - -- The parentheses now show current% + COMPLETED quest XP only (not incomplete). 323 271 local percentStr = "" 324 272 if not s.isMaxLevel and s.maxXP > 0 then 325 - -- Use only completed quest XP for the parentheses prediction 326 273 local completedXP = (s.completeXP or 0) 327 274 local percentCompleted = (s.maxXP > 0) and (completedXP / s.maxXP * 100) or 0 328 275 local percentWithComplete = percentXP + percentCompleted ··· 343 290 F.TextRight:SetText("") 344 291 end 345 292 346 - -- Small info below the bar: e.g. Completed Quests / Rested Experience 347 - -- Combined info line: Completed Quests / Rested Experience plus optional leveling info 348 293 do 349 294 local infoParts = {} 350 295 ··· 376 321 F.LevelingText:SetText("") 377 322 end 378 323 end 379 - 380 - -- Leveling/time info is now merged into `F.InfoText` above; no separate TextLeft usage 381 324 end
+1 -17
Utils.lua
··· 2 2 3 3 F.Utils = {} 4 4 5 - --- Rounds a number to specific decimal places 6 - --- @param num number 7 - --- @param decimals number 8 - --- @return number 9 5 function F.Utils.Round(num, decimals) 10 6 local mult = 10^(decimals or 0) 11 7 return math.floor(num * mult + 0.5) / mult 12 8 end 13 9 14 - --- Formats large numbers into readable strings (adds commas or uses K/M suffixes) 15 - --- Examples: 16 - --- 950 -> "950" 17 - --- 10500 -> "10,500" 18 - --- 200500 -> "200.5K" 19 - --- 1500000 -> "1.5M" 20 - --- @param n number 21 - --- @return string 10 + -- Format large numbers into readable strings (commas or K/M suffixes) 22 11 function F.Utils.FormatLargeNumber(n) 23 12 n = tonumber(n) or 0 24 13 ··· 37 26 end 38 27 end 39 28 40 - -- Backwards-compatible global alias (existing UI code calls FormatLargeNumber directly) 41 29 FormatLargeNumber = function(n) return F.Utils.FormatLargeNumber(n) end 42 30 43 - --- Formats seconds into readable time string 44 - --- @param time number Seconds 45 - --- @param formatString? string Optional format override 46 - --- @return string 47 31 function F.Utils.FormatTime(time, formatString) 48 32 if time <= 59 then return "< 1m" end 49 33