4823261f84
camelot ExpBarOverrides: rested (GetRestState 1), the earned fill is Fill-Rested and the rested bonus Fill-Prediction; otherwise the earned fill is Fill-Experience. The bonus was drawn with Fill-Rested. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
283 lines
10 KiB
Lua
283 lines
10 KiB
Lua
-- Bottom status bars: experience and reputation.
|
|
-- mainline/StatusTrackingBarTemplate.xml: both bars are the same object (background, fill,
|
|
-- frame over it, centered text), so both are built the same way.
|
|
-- camelot/StatusTrackingBarConstants.lua: the containers are one bar height apart, so the
|
|
-- bars touch; camelot's image is 13 high. The game shows reputation above experience,
|
|
-- although the priorities (Experience 0, Reputation 2) sorted descending suggest the reverse.
|
|
-- Reputation fill color by standing (shared/ReputationBar.lua), same ranges as 3.3.5
|
|
-- FACTION_BAR_COLORS.
|
|
-- The experience bar hides at max level and reputation moves down to its place (camelot
|
|
-- StatusTrackingBarManager fills containers from the bottom). Reputation hides when no
|
|
-- faction is watched.
|
|
-- Both bars span the bottom row (action bar, micro menu, bags) and sit on its top, measured
|
|
-- by BottomBar (ForeverUI.BottomRow). Each stays movable. Both carry camelot's 19 dividers.
|
|
|
|
local HEIGHT = 13 -- camelot image height (1020 x 13)
|
|
|
|
-- Dividers (camelot StatusTrackingBarContainerMixin:UpdateDividers, unchanged in 70450):
|
|
-- STATUS_BAR_NUM_SEGMENTS = 20 segments, so 19 StatusBarDividerTemplate frames (3 x 10,
|
|
-- ui-hud-experiencebar-divider, OVERLAY) at LEFT (width / 20 * i, 0), above the fill.
|
|
-- Rested tick (ExhaustionTick, UI-HUD-ExperienceBar-Frame-Pip, 10 x 14): centered on the right
|
|
-- end of the rested fill, shown while rested experience remains. The rested fill
|
|
-- (ExhaustionLevelFillBar) is UI-HUD-ExperienceBar-Fill-Prediction; while rested, the earned
|
|
-- fill is UI-HUD-ExperienceBar-Fill-Rested (blue) instead of -Fill-Experience (purple).
|
|
local SEGMENTS = 20
|
|
local DIVIDER_W, DIVIDER_H = 3, 10
|
|
local L = ForeverUI.L
|
|
|
|
local ATLAS_REPUTATION = {
|
|
"ui-hud-experiencebar-fill-reputation-faction-red-camelot", -- hated
|
|
"ui-hud-experiencebar-fill-reputation-faction-red-camelot", -- hostile
|
|
"ui-hud-experiencebar-fill-reputation-faction-orange-camelot", -- unfriendly
|
|
"ui-hud-experiencebar-fill-reputation-faction-yellow-camelot", -- neutral
|
|
"ui-hud-experiencebar-fill-reputation-faction-green-camelot", -- friendly
|
|
"ui-hud-experiencebar-fill-reputation-faction-green-camelot", -- honored
|
|
"ui-hud-experiencebar-fill-reputation-faction-green-camelot", -- revered
|
|
"ui-hud-experiencebar-fill-reputation-faction-green-camelot", -- exalted
|
|
}
|
|
|
|
-- Crops a fill to the fraction. The image has rounded ends: cutting it on the right gives
|
|
-- the straight edge of a partial fill. width: bar width at 100 %.
|
|
local function populate(texture, atlas, fraction, width)
|
|
local e = atlas and ForeverUI.AtlasEntry(atlas)
|
|
if not e or not fraction or fraction ~= fraction or fraction <= 0 then
|
|
texture:Hide()
|
|
return
|
|
end
|
|
|
|
if fraction > 1 then
|
|
fraction = 1
|
|
end
|
|
|
|
local w = width * fraction
|
|
if w < 1 then
|
|
texture:Hide()
|
|
return
|
|
end
|
|
|
|
texture:SetTexture(e[1])
|
|
texture:SetTexCoord(e[2], e[2] + (e[3] - e[2]) * fraction, e[4], e[5])
|
|
texture:SetWidth(w)
|
|
texture:Show()
|
|
end
|
|
|
|
local function createBar(name)
|
|
local bar = CreateFrame("Frame", name, UIParent)
|
|
bar:SetHeight(HEIGHT)
|
|
bar:EnableMouse(true)
|
|
|
|
local background = bar:CreateTexture(nil, "BACKGROUND")
|
|
ForeverUI.SetAtlas(background, "ui-hud-experiencebar-background-camelot", true)
|
|
background:SetAllPoints(bar)
|
|
|
|
-- Rested part shows behind the earned part: lower layer, same left edge.
|
|
local rested = bar:CreateTexture(nil, "BORDER")
|
|
rested:SetPoint("LEFT", bar, "LEFT", 0, 0)
|
|
rested:SetHeight(HEIGHT)
|
|
rested:Hide()
|
|
|
|
local fill = bar:CreateTexture(nil, "ARTWORK")
|
|
fill:SetPoint("LEFT", bar, "LEFT", 0, 0)
|
|
fill:SetHeight(HEIGHT)
|
|
fill:Hide()
|
|
|
|
local frame = bar:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(frame, "ui-hud-experiencebar-frame-camelot", true)
|
|
frame:SetAllPoints(bar)
|
|
|
|
local text = bar:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
|
|
text:SetPoint("CENTER")
|
|
text:Hide()
|
|
|
|
bar.background, bar.rested, bar.fill, bar.frame, bar.text =
|
|
background, rested, fill, frame, text
|
|
|
|
-- Dividers and rested tick, in a child frame above the fill and the frame art
|
|
local marks = CreateFrame("Frame", nil, bar)
|
|
marks:SetAllPoints(bar)
|
|
marks:SetFrameLevel(bar:GetFrameLevel() + 1)
|
|
bar.dividers = {}
|
|
for i = 1, SEGMENTS - 1 do
|
|
local divider = marks:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(divider, "ui-hud-experiencebar-divider", true)
|
|
divider:SetWidth(DIVIDER_W)
|
|
divider:SetHeight(DIVIDER_H)
|
|
bar.dividers[i] = divider
|
|
end
|
|
local tick = marks:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(tick, "ui-hud-experiencebar-frame-pip-camelot")
|
|
tick:Hide()
|
|
bar.marks, bar.tick = marks, tick
|
|
|
|
bar:SetScript("OnEnter", function(self)
|
|
self.text:Show()
|
|
end)
|
|
bar:SetScript("OnLeave", function(self)
|
|
self.text:Hide()
|
|
end)
|
|
|
|
return bar
|
|
end
|
|
|
|
-- Places the dividers on the bar's current width (camelot: on each layout change)
|
|
local function placeDividers(bar)
|
|
local width = bar:GetWidth() or 0
|
|
for i, divider in ipairs(bar.dividers) do
|
|
divider:ClearAllPoints()
|
|
divider:SetPoint("LEFT", bar, "LEFT", math.floor(width / SEGMENTS * i + 0.5), 0)
|
|
end
|
|
end
|
|
|
|
local experience = createBar("ForeverUIExperienceBar")
|
|
local reputation = createBar("ForeverUIReputationBar")
|
|
|
|
-- Reputation: just above experience, or in its place when experience is hidden (max level)
|
|
local function placeReputation()
|
|
local rowLine = ForeverUI.BottomRow
|
|
if not rowLine then
|
|
return
|
|
end
|
|
local center = (rowLine.left + rowLine.right) / 2
|
|
local y = experience:IsShown() and rowLine.top + HEIGHT or rowLine.top
|
|
ForeverUI.Layout.SetDefaults("reputationbar", "BOTTOM", "BOTTOM", center, y)
|
|
end
|
|
|
|
-- ---------- Experience
|
|
local function updateExperience()
|
|
local maximum = UnitXPMax("player")
|
|
local level = UnitLevel("player")
|
|
local maxLevel = MAX_PLAYER_LEVEL or 80
|
|
|
|
-- hidden too while a vehicle bar replaces the player's bars (Vehicle.lua)
|
|
if ForeverUI.VehicleArt or not maximum or maximum <= 0 or (level and level >= maxLevel) then
|
|
experience:Hide()
|
|
placeReputation()
|
|
return
|
|
end
|
|
|
|
experience:Show()
|
|
placeReputation()
|
|
|
|
local width = experience:GetWidth()
|
|
local earned = UnitXP("player")
|
|
local rested = (GetXPExhaustion and GetXPExhaustion()) or 0
|
|
local fraction = earned / maximum
|
|
|
|
populate(experience.rested, "ui-hud-experiencebar-fill-prediction-camelot",
|
|
(earned + rested) / maximum, width)
|
|
-- rested tick at the end of the rested part
|
|
if rested > 0 then
|
|
local x = math.min(1, (earned + rested) / maximum) * width
|
|
experience.tick:ClearAllPoints()
|
|
experience.tick:SetPoint("CENTER", experience, "LEFT", x, 0)
|
|
experience.tick:Show()
|
|
else
|
|
experience.tick:Hide()
|
|
end
|
|
-- rested (GetRestState 1): the earned part turns blue (ExpBarOverrides UpdateStatusBarTextures)
|
|
local isRested = GetRestState and GetRestState() == 1
|
|
populate(experience.fill, isRested and "ui-hud-experiencebar-fill-rested-camelot"
|
|
or "ui-hud-experiencebar-fill-experience-camelot", fraction, width)
|
|
|
|
experience.text:SetText(string.format("%d / %d (%d%%)", earned, maximum,
|
|
math.floor(fraction * 100)))
|
|
end
|
|
|
|
-- ---------- Reputation
|
|
local function updateReputation()
|
|
local name, standingId, minimum, maximum, value = GetWatchedFactionInfo()
|
|
if ForeverUI.VehicleArt or not name or not maximum or maximum <= minimum then
|
|
reputation:Hide()
|
|
return
|
|
end
|
|
|
|
reputation:Show()
|
|
|
|
local span = maximum - minimum
|
|
local earned = value - minimum
|
|
local fraction = earned / span
|
|
|
|
populate(reputation.fill, ATLAS_REPUTATION[standingId] or ATLAS_REPUTATION[4],
|
|
fraction, reputation:GetWidth())
|
|
|
|
reputation.text:SetText(string.format("%s %d / %d", name, earned, span))
|
|
end
|
|
|
|
-- ---------- Placement
|
|
-- Both bars span the row (action bar, micro menu, bags) and touch: experience on the row,
|
|
-- reputation just above (or on the row without experience).
|
|
local function place()
|
|
local rowLine = ForeverUI.BottomRow
|
|
if not rowLine then
|
|
return
|
|
end
|
|
|
|
local width = rowLine.right - rowLine.left
|
|
local center = (rowLine.left + rowLine.right) / 2
|
|
|
|
experience:SetWidth(width)
|
|
reputation:SetWidth(width)
|
|
placeDividers(experience)
|
|
placeDividers(reputation)
|
|
|
|
ForeverUI.Layout.SetDefaults("experiencebar", "BOTTOM", "BOTTOM", center, rowLine.top)
|
|
placeReputation()
|
|
end
|
|
|
|
local listener = CreateFrame("Frame")
|
|
listener:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
listener:RegisterEvent("PLAYER_XP_UPDATE")
|
|
listener:RegisterEvent("PLAYER_LEVEL_UP")
|
|
listener:RegisterEvent("UPDATE_EXHAUSTION")
|
|
listener:RegisterEvent("PLAYER_UPDATE_RESTING")
|
|
listener:RegisterEvent("UPDATE_FACTION")
|
|
listener:SetScript("OnEvent", function(_self, event)
|
|
if event == "PLAYER_ENTERING_WORLD" then
|
|
-- The client's bars: experience, rested tick, and reputation, which re-places itself on
|
|
-- every update.
|
|
ForeverUI.Suppress(MainMenuExpBar)
|
|
ForeverUI.Suppress(ExhaustionTick)
|
|
ForeverUI.Suppress(ReputationWatchBar)
|
|
place()
|
|
end
|
|
updateExperience()
|
|
updateReputation()
|
|
end)
|
|
|
|
ForeverUI.Layout.Register(experience, "experiencebar", L.STATUSBARS_EDIT_LABEL_EXPERIENCE, "BOTTOM", "BOTTOM", 0, 54)
|
|
ForeverUI.Layout.Register(reputation, "reputationbar", L.STATUSBARS_EDIT_LABEL_REPUTATION, "BOTTOM", "BOTTOM", 0, 67)
|
|
place()
|
|
updateExperience()
|
|
updateReputation()
|
|
|
|
ForeverUI.ReputationBar = reputation
|
|
ForeverUI.StatusBarsUpdate = function()
|
|
updateExperience()
|
|
updateReputation()
|
|
end
|
|
-- the row changed width (BottomBar.lua: a micro button was added)
|
|
ForeverUI.StatusBarsLayout = function()
|
|
place()
|
|
updateExperience()
|
|
updateReputation()
|
|
end
|
|
|
|
ForeverUI.StatusBarsDebug = function()
|
|
local rowLine = ForeverUI.BottomRow or {}
|
|
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
|
"|cff66ccffForeverUI|r " .. L.STATUSBARS_DEBUG,
|
|
rowLine.left or 0, rowLine.right or 0, tostring(rowLine.top),
|
|
experience:GetWidth(),
|
|
tostring(experience:IsShown()), tostring(select(5, experience:GetPoint(1))),
|
|
tostring(reputation:IsShown()), tostring(select(5, reputation:GetPoint(1)))))
|
|
-- what decides the reputation's place: level, the client's max level, XP to earn, default
|
|
-- place, saved place
|
|
local system = ForeverUI.Layout.systems["reputationbar"]
|
|
local saved = ForeverUIDB and ForeverUIDB.positions and ForeverUIDB.positions["reputationbar"]
|
|
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
|
"|cff66ccffForeverUI|r " .. L.STATUSBARS_DEBUG_STATE,
|
|
tostring(UnitLevel("player")), tostring(MAX_PLAYER_LEVEL), tostring(UnitXPMax("player")),
|
|
tostring(system and system.defaults and system.defaults.y), tostring(saved and saved.y),
|
|
tostring(reputation.IsUserPlaced and reputation:IsUserPlaced())))
|
|
end
|