Files
Adrestie 5842eb7a2b ForeverUI : code en anglais, commentaires resserres, code mort retire
Traduction
- Identifiants en anglais dans tout le module : addon, ecrans d'accueil,
  outils Python, banc d'essai et patcheur C#.
- Fichiers au nom francais renommes (Gabarits.lua -> Templates.lua,
  Textes_*.lua -> Texts_*.lua, deployer.py -> deploy.py,
  tools/patcheur -> tools/patcher, etc.).
- Commandes /fui et options de deploy.py en anglais (--check, --server,
  --remove-dbc).
- Cles des variables sauvegardees et de data/dbc/statistics.json
  traduites, sans reprise des anciennes cles.
- API de la micro-barre : ForeverUI.AddMicroButton(def) avec les champs
  name, atlasSet, after, tooltip, onClick, ready, et
  ForeverUI.UpdateMicro(name, isOpen).
- Commentaires reecrits en anglais, courts, sans dates ni historique ;
  les fonctions au nom peu parlant et leurs parametres sont expliques.

Code mort
- Exports, fonctions et variables jamais lus retires.
- Retires a la demande des modules : ForeverUI.PvPJauge, les exports
  FondEntete / TuilerEntete / Polices de TradeSkill, l'art legacy-up-c60,
  honorfill(half)blue et inv_misc_book_09, leurs entrees d'atlas et les
  parties d'outils correspondantes.

Verifications : liaisons des noms comparees avant et apres, banc vert,
chemins de textures inchanges, generateurs reproduisant les fichiers,
patcheur identique octet pour octet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 09:15:52 +02:00

684 lines
26 KiB
Lua

-- ForeverUI: the inspect window (InspectFrame, load-on-demand Blizzard_InspectUI) in camelot's
-- style. The client frames, slots, model and logic stay; they are moved and re-skinned.
-- Side tabs: character and PvP (camelot has no PvP page; this one uses the sheet's PvP art).
-- No guild tab: 3.3.5 has no inspect guild data. camelot's Talents button opens the talent
-- window on the inspected unit (Talents.inspect).
local ForeverUI = ForeverUI or {}
_G.ForeverUI = ForeverUI
local Tpl = ForeverUI.Templates
local I = {}
ForeverUI.Inspection = I
local SEP = string.char(92)
local TABS = "Interface" .. SEP .. "ForeverUI" .. SEP .. "TabIcons" .. SEP
local PARTS = "Interface" .. SEP .. "ForeverUI" .. SEP .. "characterframe" .. SEP .. "char-paperdoll-"
local PVP = "Interface" .. SEP .. "PVPFrame" .. SEP
local N = {
window = { 338, 424 },
-- 48, centered on the ring hole: (-13 + 38 - 24, 16 - 38.5 + 24); larger, the disc goes past
-- the opaque metal
portrait = { side = 48, x = 1, y = 1.5 },
inset = { 4, -60, -6, 4 },
tabs = { x = 1, y = -30, l = 64, h = 384, side = 55, gap = -2, icon = 50, iconX = -3, crop = 0.03125 },
level = { y = -27, l = 220 },
talents = { l = 102, h = 20, y = -39, minLevel = 10 },
model = { x = 52, y = -66, l = 231, h = 320, arrowsY = -2, arrowsGap = 4 },
background = { l = 212, h = 245, right = 19, down = 128, veil = 52,
uLeft = { 0.171875, 1 }, uRight = { 0, 0.296875 }, vTop = 0.0392156862745098 },
edges = { corner = 7, rule = 5, x = 46, xRight = -47, y = -4, yBottom = 31 },
slot = 37, gap = 4, left = { 4, -2 }, right = { -4, -2 },
weapons = { 116, 16 }, weaponsGap = 5,
}
-- Char-Paperdoll-* (camelot/characterframe.xml): file, u1, u2, v1, v2
local PIECES = {
cornerTL = { "parts", 0.40625, 0.43359375, 0.8046875, 0.859375 },
topRightCorner = { "parts", 0.40625, 0.43359375, 0.734375, 0.7890625 },
bottomLeftCorner = { "parts", 0.40625, 0.43359375, 0.6640625, 0.71875 },
cornerBR = { "parts", 0.40625, 0.43359375, 0.59375, 0.6484375 },
left = { "vertical", 0.0625, 0.375, 0, 1 },
right = { "vertical", 0.5, 0.8125, 0, 1 },
top = { "horizontal", 0, 1, 0.5, 0.8125 },
down = { "horizontal", 0, 1, 0.0625, 0.375 },
}
local LEFT_COLUMN = { "Head", "Neck", "Shoulder", "Back", "Chest", "Shirt", "Tabard", "Wrist" }
local RIGHT_COLUMN = { "Hands", "Waist", "Legs", "Feet", "Finger0", "Finger1", "Trinket0", "Trinket1" }
local WEAPONS = { "MainHand", "SecondaryHand", "Ranged" }
local function place(r, ...)
r:ClearAllPoints()
r:SetPoint(...)
end
-- ------------------------------------------------------------ Side tabs
-- LargeSideTabButtonTemplate, as on the character sheet: common-sidetab background, cropped
-- icon 50 at (-3, 0), common-sidetab-selected when open, common-sidetab-hover on mouse over.
-- bar: parent bar; id: tab index for InspectSwitchTabs; tooltip: tooltip text
local function createTab(bar, id, tooltip)
local O = N.tabs
local b = CreateFrame("Button", "ForeverUIInspectTab" .. id, bar)
b:SetWidth(O.side)
b:SetHeight(O.side)
b:SetID(id)
local background = b:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(background, "common-sidetab", true)
background:SetAllPoints(b)
local icon = b:CreateTexture(nil, "ARTWORK")
icon:SetWidth(O.icon)
icon:SetHeight(O.icon)
icon:SetPoint("CENTER", b, "CENTER", O.iconX, 0)
icon:SetTexCoord(O.crop, 1 - O.crop, O.crop, 1 - O.crop)
local selected = b:CreateTexture(nil, "OVERLAY")
ForeverUI.SetAtlas(selected, "common-sidetab-selected", true)
selected:SetAllPoints(b)
selected:Hide()
local hover = b:CreateTexture(nil, "HIGHLIGHT")
ForeverUI.SetAtlas(hover, "common-sidetab-hover", true)
hover:SetAllPoints(b)
b.icon, b.selected = icon, selected
b:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(tooltip, 1.0, 1.0, 1.0)
GameTooltip:Show()
end)
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
b:SetScript("OnClick", function(self)
PlaySound("igCharacterInfoTab")
InspectSwitchTabs(self:GetID())
end)
return b
end
function I.UpdateTabs()
local selected = PanelTemplates_GetSelectedTab(InspectFrame)
for id, b in ipairs(I.tabs or {}) do
Tpl.SetShown(b.selected, id == selected)
end
end
-- ------------------------------------------------------------ Character page
-- 3.3.5 DressUpTexturePath for another unit: same fallback for the missing backgrounds
-- (gnome, troll)
local function backgroundPath(unit)
local _, file = UnitRace(unit)
local top = string.upper(file or "")
if top == "GNOME" then
file = "Dwarf"
elseif top == "TROLL" then
file = "Orc"
end
return "Interface" .. SEP .. "DressUpFrame" .. SEP .. "DressUpBackground-" .. (file or "Orc")
end
local function createPiece(parent, overlay, key)
local d = PIECES[key]
local t = parent:CreateTexture(nil, overlay)
t:SetTexture(PARTS .. d[1])
t:SetTexCoord(d[2], d[3], d[4], d[5])
return t
end
-- Resize an inspect slot to 37 and put the gear slot art behind it.
local function skinSlot(name)
local b = _G["Inspect" .. name .. "Slot"]
if not b or b.foreverFrame then return b end
b:SetWidth(N.slot)
b:SetHeight(N.slot)
local frame = b:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(frame, "ui-character-info-gearslot")
frame:SetPoint("CENTER", b, "CENTER", 0, 0)
b.foreverFrame = frame
return b
end
-- Character page: level, talents button, model, race background, frame and slots.
local function skinCharacter(inset)
local p = InspectPaperDollFrame
for _, r in ipairs({ p:GetRegions() }) do
if r:GetObjectType() == "Texture" and not r:GetName() then r:SetAlpha(0) end
end
place(InspectLevelText, "TOP", p, "TOP", 0, N.level.y)
InspectLevelText:SetWidth(N.level.l)
-- talents button, enabled from level 10 (InspectFrame_UpdateTalentTab)
local TB = N.talents
local b = ForeverUI.CreatePanelButton(p, TALENTS, TB.l, TB.h, "ForeverUIInspectTalentsButton", "GameFontNormal")
b:SetPoint("TOP", p, "TOP", 0, TB.y)
b:SetMotionScriptsWhileDisabled(true)
b:SetScript("OnClick", function()
PlaySound("igCharacterInfoTab")
if ForeverUI.Talents and InspectFrame.unit then ForeverUI.Talents.inspect(InspectFrame.unit) end
end)
b:SetScript("OnEnter", function(self)
if Tpl.Truthy(self:IsEnabled()) then return end
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(UNAVAILABLE, 1.0, 0.125, 0.125)
GameTooltip:Show()
end)
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
I.talents = b
-- model: 3.3.5 has no model controls, so the two rotate arrows sit at TOP (0, -2), 4 apart
local M = N.model
local model = InspectModelFrame
place(model, "TOPLEFT", p, "TOPLEFT", M.x, M.y)
model:SetWidth(M.l)
model:SetHeight(M.h)
local left, right = InspectModelRotateLeftButton, InspectModelRotateRightButton
local half = left:GetWidth() / 2 + M.arrowsGap / 2
place(left, "TOP", model, "TOP", -half, M.arrowsY)
place(right, "TOP", model, "TOP", half, M.arrowsY)
ForeverUI.RotateWithMouse(model)
-- race background under the model (page regions), then the black veil
local F = N.background
local background = {}
local function piece(u, v1, v2, l, h)
local t = p:CreateTexture(nil, "BACKGROUND")
t:SetTexCoord(u[1], u[2], v1, v2)
t:SetWidth(l)
t:SetHeight(h)
t:SetDesaturated(true)
background[#background + 1] = t
return t
end
-- bottom pieces are cut at the inset bottom (109 of 128); whole, they leave the window
local visibleBottom = (N.window[2] - N.inset[4]) - (-M.y + F.h)
local topLeft = piece(F.uLeft, F.vTop, 1, F.l, F.h)
topLeft:SetPoint("TOPLEFT", model, "TOPLEFT", 0, 0)
local topRight = piece(F.uRight, F.vTop, 1, F.right, F.h)
topRight:SetPoint("TOPLEFT", topLeft, "TOPRIGHT", 0, 0)
local bottomLeft = piece(F.uLeft, 0, visibleBottom / F.down, F.l, visibleBottom)
bottomLeft:SetPoint("TOPLEFT", topLeft, "BOTTOMLEFT", 0, 0)
local bottomRight = piece(F.uRight, 0, visibleBottom / F.down, F.right, visibleBottom)
bottomRight:SetPoint("TOPLEFT", topLeft, "BOTTOMRIGHT", 0, 0)
local veil = p:CreateTexture(nil, "BORDER")
veil:SetTexture(0, 0, 0, 1)
veil:SetPoint("TOPLEFT", topLeft, "TOPLEFT", 0, 0)
veil:SetPoint("BOTTOMRIGHT", topRight, "BOTTOMRIGHT", 0, -(F.down - F.veil))
I.background, I.veil = background, veil
-- Char-Paperdoll frame above the model. camelot's second bottom rule (BorderBottom2) is not
-- drawn: it doubles the frame's bottom rule 3 px higher.
local B = N.edges
local frame = CreateFrame("Frame", nil, p)
frame:SetAllPoints(inset)
frame:SetFrameLevel(model:GetFrameLevel() + 1)
local c = {}
for _, d in ipairs({ { "cornerTL", "TOPLEFT", B.x, B.y }, { "topRightCorner", "TOPRIGHT", B.xRight, B.y },
{ "bottomLeftCorner", "BOTTOMLEFT", B.x, B.yBottom }, { "cornerBR", "BOTTOMRIGHT", B.xRight, B.yBottom } }) do
local t = createPiece(frame, "OVERLAY", d[1])
t:SetWidth(B.corner)
t:SetHeight(B.corner)
t:SetPoint(d[2], inset, d[2], d[3], d[4])
c[d[1]] = t
end
local function rule(key, a1, r1, p1, x1, y1, a2, r2, p2, x2, y2)
local t = createPiece(frame, "OVERLAY", key)
t:SetPoint(a1, r1, p1, x1, y1)
t:SetPoint(a2, r2, p2, x2, y2)
return t
end
local g = rule("left", "TOPLEFT", c.cornerTL, "BOTTOMLEFT", -1, 0, "BOTTOMLEFT", c.bottomLeftCorner, "TOPLEFT", -1, 0)
local d = rule("right", "TOPRIGHT", c.topRightCorner, "BOTTOMRIGHT", 1, 0, "BOTTOMRIGHT", c.cornerBR, "TOPRIGHT", 1, 0)
local h = rule("top", "TOPLEFT", c.cornerTL, "TOPRIGHT", 0, 1, "TOPRIGHT", c.topRightCorner, "TOPLEFT", 0, 1)
local down = rule("down", "BOTTOMLEFT", c.bottomLeftCorner, "BOTTOMRIGHT", 0, -1, "BOTTOMRIGHT", c.cornerBR, "BOTTOMLEFT", 0, -1)
g:SetWidth(B.rule)
d:SetWidth(B.rule)
h:SetHeight(B.rule)
down:SetHeight(B.rule)
c.left, c.right, c.top, c.down = g, d, h, down
I.frame = c
-- slots above the frame (camelot: frameLevel 100), so the bottom rule does not cross
-- the weapons
local function skinRaised(name)
local s = skinSlot(name)
s:SetFrameLevel(frame:GetFrameLevel() + 1)
return s
end
local previous
for i, name in ipairs(LEFT_COLUMN) do
local s = skinRaised(name)
if i == 1 then
place(s, "TOPLEFT", inset, "TOPLEFT", N.left[1], N.left[2])
else
place(s, "TOPLEFT", previous, "BOTTOMLEFT", 0, -N.gap)
end
previous = s
end
for i, name in ipairs(RIGHT_COLUMN) do
local s = skinRaised(name)
if i == 1 then
place(s, "TOPRIGHT", inset, "TOPRIGHT", N.right[1], N.right[2])
else
place(s, "TOPLEFT", previous, "BOTTOMLEFT", 0, -N.gap)
end
previous = s
end
for i, name in ipairs(WEAPONS) do
local s = skinRaised(name)
if i == 1 then
place(s, "BOTTOMLEFT", p, "BOTTOMLEFT", N.weapons[1], N.weapons[2])
else
place(s, "TOPLEFT", previous, "TOPRIGHT", N.weaponsGap, 0)
end
previous = s
end
end
-- ------------------------------------------------------------ PvP page
-- Head: the rank name and the sheet's ui-character-info-honor-levelbg line (PvPTab.lua),
-- then the inspected player's faction emblem, at 50 % under the rank badge if any, full
-- otherwise ("Civilian"); badges 36 x 42. Below: honor table (today / yesterday / lifetime,
-- as InspectPVPHonor), the sheet's separator, and the three teams as cards (PvPArena.lua).
-- Heights below count from the bottom of the head.
local J = {
head = { h = 90, nameY = -12, rowY = -10, emblemGap = -4, badgeW = 36, badgeH = 42, underRankAlpha = 0.5 },
top = -12, tagX = 14, columns = { 150, 215, 280 }, rowLines = { -30, -48 },
separator = -64, cardsY = -74, cardX = 8, cardH = 52,
textX = 50, nameY = -7, nameW = 160, ratingX = -14, ratingY = -8, typeY = -30,
tagsY = -24, valueGap = -2, cardColumns = { 145, 209, 273 },
-- banner scaled from 90 to 48 high (PvPArena.lua)
scale = 48 / 90,
}
local SIZES = { 2, 3, 5 }
local function text(parent, template, justify)
local fs = parent:CreateFontString(nil, "ARTWORK", template)
if justify then fs:SetJustifyH(justify) end
return fs
end
-- Column label with its value below; returns the value font string.
local function column(parent, x, y, tag)
local e = text(parent, "GameFontDisableSmall", "CENTER")
e:SetPoint("TOP", parent, "TOPLEFT", x, y)
e:SetText(tag)
local v = text(parent, "GameFontHighlightSmall", "CENTER")
v:SetPoint("TOP", e, "BOTTOM", 0, J.valueGap)
return v
end
-- Arena team card: banner, name, rating and season columns. rank: 1..3 for sizes
-- 2, 3, 5
local function createCard(page, rank, width)
local c = CreateFrame("Frame", "ForeverUIInspectArenaTeam" .. rank, page)
c:SetWidth(width)
c:SetHeight(J.cardH)
c.size = SIZES[rank]
c.plate = ForeverUI.CreateNineSlice(c, "common-button-list-collapseexpand", 12, { 0, 0, 0, 0 }, "BACKGROUND") or {}
local E = J.scale
local bannerFrame = CreateFrame("Frame", nil, c)
bannerFrame:SetAllPoints(c)
c.bannerFrame = bannerFrame
local pole = bannerFrame:CreateTexture(nil, "BACKGROUND")
pole:SetTexture(PVP .. "UI-Character-PVP-Elements")
pole:SetTexCoord(0, 0.099609375, 0.91015625, 0.935546875)
pole:SetWidth(50 * E)
pole:SetHeight(13 * E)
pole:SetPoint("TOPLEFT", c, "TOPLEFT", 8, -2)
local bannerTexture = bannerFrame:CreateTexture(nil, "BORDER")
bannerTexture:SetWidth(45 * E)
bannerTexture:SetHeight(90 * E)
bannerTexture:SetPoint("TOP", pole, "TOP", 5 * E, -2 * E)
local edge = bannerFrame:CreateTexture(nil, "ARTWORK")
edge:SetWidth(45 * E)
edge:SetHeight(90 * E)
edge:SetPoint("CENTER", bannerTexture, "CENTER", 0, 0)
local emblem = bannerFrame:CreateTexture(nil, "OVERLAY")
emblem:SetWidth(24 * E)
emblem:SetHeight(24 * E)
emblem:SetPoint("CENTER", edge, "CENTER", -5 * E, 17 * E)
c.bannerTexture, c.edge, c.emblem = bannerTexture, edge, emblem
local d = CreateFrame("Frame", nil, c)
d:SetAllPoints(c)
c.data = d
d.name = text(d, "GameFontNormal", "LEFT")
d.name:SetWidth(J.nameW)
d.name:SetPoint("TOPLEFT", c, "TOPLEFT", J.textX, J.nameY)
d.side = text(d, "GameFontNormalSmall", "RIGHT")
d.side:SetPoint("TOPRIGHT", c, "TOPRIGHT", J.ratingX, J.ratingY)
d.ratingLabel = text(d, "GameFontDisableSmall", "RIGHT")
d.ratingLabel:SetPoint("RIGHT", d.side, "LEFT", -4, 0)
d.ratingLabel:SetText(ARENA_TEAM_RATING)
d.type = text(d, "GameFontHighlightSmall", "LEFT")
d.type:SetPoint("TOPLEFT", c, "TOPLEFT", J.textX, J.typeY)
d.type:SetText(ARENA_THIS_SEASON)
local K = J.cardColumns
d.games = column(d, K[1], J.tagsY, GAMES)
d.winLoss = column(d, K[2], J.tagsY, WIN_LOSS)
d.character = column(d, K[3], J.tagsY, RATING)
c.empty = c:CreateFontString(nil, "ARTWORK", "GameFontDisableLarge")
c.empty:SetPoint("CENTER", c, "CENTER", 0, 0)
c.empty:Hide()
return c
end
-- One card: the team of that size, or the empty slot, as InspectPVPTeam_Update (season values
-- and the inspected player's rating). id: GetInspectArenaTeamData index, or nil
local function populateCard(c, id)
local d = c.data
if not id then
c:SetAlpha(0.4)
c.bannerTexture:SetTexture(PVP .. "PVP-Banner-" .. c.size)
c.bannerTexture:SetVertexColor(1, 1, 1)
c.bannerFrame:SetAlpha(0.1)
c.edge:Hide()
c.emblem:Hide()
d:Hide()
c.empty:SetText(string.format(PVP_TEAMSIZE, c.size, c.size))
c.empty:Show()
return
end
local name, size, side, played, wins, _, character, fr, fg, fb, emblem, er, eg, eb, edge, br, bgc, bb =
GetInspectArenaTeamData(id)
c:SetAlpha(1)
c.bannerFrame:SetAlpha(1)
d.name:SetText(name)
d.side:SetText(side)
d.games:SetText(played)
d.winLoss:SetText(tostring(wins or 0) .. " - " .. tostring((played or 0) - (wins or 0)))
d.character:SetText(character)
c.bannerTexture:SetTexture(PVP .. "PVP-Banner-" .. tostring(size))
c.bannerTexture:SetVertexColor(fr or 1, fg or 1, fb or 1)
c.edge:SetVertexColor(br or 1, bgc or 1, bb or 1)
c.emblem:SetVertexColor(er or 1, eg or 1, eb or 1)
if edge and edge ~= -1 then
c.edge:SetTexture(PVP .. "PVP-Banner-" .. tostring(size) .. "-Border-" .. tostring(edge))
end
if emblem and emblem ~= -1 then
c.emblem:SetTexture(PVP .. "Icons" .. SEP .. "PVP-Banner-Emblem-" .. tostring(emblem))
end
c.edge:Show()
c.emblem:Show()
d:Show()
c.empty:Hide()
end
-- Rank of the inspected player from lifetime honorable kills at the sheet's thresholds
-- (IsTitleKnown only knows the player's titles). The server sends no honor data for a player
-- out of inspect range or attackable (other faction, duel; MiscHandler.cpp,
-- HandleInspectHonorStatsOpcode): GetInspectHonorData then returns zeros, so "Civilian" and
-- the faction emblem.
local function updateRank(page, wins)
local R = ForeverUI.PvPRanks
local unit = InspectFrame and InspectFrame.unit
local faction = unit and UnitFactionGroup(unit)
if not R or not faction then
page.badge:Hide()
page.insignia:Hide()
page.rank:SetText("")
return
end
-- unknown thresholds (not sent by the server): no rank, the emblem stays alone
local number = R.byWins(wins)
page.rank:SetText(number and R.name(number, faction) or "")
number = number or 0
ForeverUI.SetAtlas(page.badge, string.format(R.badgeFaction, string.lower(faction)), true)
page.badge:Show()
-- a rank: its badge over the emblem, half faded
if number > 0 and ForeverUI.SetAtlas(page.insignia, string.format(R.badgeRank, number), true) then
page.badge:SetAlpha(J.head.underRankAlpha)
page.insignia:Show()
else
page.badge:SetAlpha(1)
page.insignia:Hide()
end
end
function I.UpdatePvP()
local page = I.pvpPage
if not page then return end
local todayHK, todayHonor, yesterdayHK, yesterdayHonor, lifetimeHK = GetInspectHonorData()
updateRank(page, lifetimeHK)
local v = page.values
v[1]:SetText(todayHK)
v[2]:SetText(yesterdayHK)
v[3]:SetText(lifetimeHK)
v[4]:SetText(todayHonor)
v[5]:SetText(yesterdayHonor)
v[6]:SetText("-")
local indices = {}
for i = 1, (MAX_ARENA_TEAMS or 3) do
local _, size = GetInspectArenaTeamData(i)
for rank, t in ipairs(SIZES) do
if size == t then indices[rank] = i end
end
end
for rank, c in ipairs(page.maps) do
populateCard(c, indices[rank])
end
end
-- PvP page rebuilt over the client's InspectPVPFrame.
local function buildPvP(f, inset)
-- the client frame stays shown (it requests the data and gets the events), but invisible
InspectPVPFrame:SetAlpha(0)
InspectPVPFrame:EnableMouse(false)
for i = 1, 3 do
local b = _G["InspectPVPTeam" .. i]
if b then b:EnableMouse(false) end
end
local page = CreateFrame("Frame", "ForeverUIInspectPvP", f)
page:SetAllPoints(inset)
page:SetFrameLevel(InspectPVPFrame:GetFrameLevel() + 10)
page:Hide()
local width = N.window[1] + N.inset[3] - N.inset[1] - 2 * J.cardX
-- head: rank name and line, then emblem and badge
local R = J.head
local head = CreateFrame("Frame", nil, page)
head:SetPoint("TOPLEFT", page, "TOPLEFT", 0, 0)
head:SetPoint("TOPRIGHT", page, "TOPRIGHT", 0, 0)
head:SetHeight(R.h)
local rankName = text(head, "GameFontHighlightLarge", "CENTER")
rankName:SetPoint("TOP", head, "TOP", 0, R.nameY)
local row = head:CreateTexture(nil, "BORDER")
ForeverUI.SetAtlas(row, "ui-character-info-honor-levelbg")
row:SetWidth(width)
row:SetPoint("BOTTOM", rankName, "BOTTOM", 0, R.rowY)
local badge = head:CreateTexture(nil, "ARTWORK")
badge:SetWidth(R.badgeW)
badge:SetHeight(R.badgeH)
badge:SetPoint("TOP", row, "BOTTOM", 0, R.emblemGap)
badge:Hide()
local insignia = head:CreateTexture(nil, "OVERLAY")
insignia:SetWidth(R.badgeW)
insignia:SetHeight(R.badgeH)
insignia:SetPoint("CENTER", badge, "CENTER", 0, 0)
insignia:Hide()
page.head, page.badge, page.insignia, page.rank, page.row = head, badge, insignia, rankName, row
-- honor, under the head
for i, t in ipairs({ HONOR_TODAY, HONOR_YESTERDAY, HONOR_LIFETIME }) do
local e = text(page, "GameFontDisableSmall", "CENTER")
e:SetPoint("TOP", head, "BOTTOMLEFT", J.columns[i], J.top)
e:SetText(t)
end
page.values = {}
for r, t in ipairs({ KILLS, HONOR }) do
local e = text(page, "GameFontDisableSmall", "LEFT")
e:SetPoint("TOPLEFT", head, "BOTTOMLEFT", J.tagX, J.rowLines[r])
e:SetText(t)
for i = 1, 3 do
local v = text(page, "GameFontHighlightSmall", "CENTER")
v:SetPoint("TOP", head, "BOTTOMLEFT", J.columns[i], J.rowLines[r])
page.values[#page.values + 1] = v
end
end
local separator = page:CreateTexture(nil, "ARTWORK")
ForeverUI.SetAtlas(separator, "ui-character-info-scrollline-long")
separator:SetWidth(width)
separator:SetPoint("TOP", head, "BOTTOM", 0, J.separator)
page.separator = separator
page.maps = {}
for rank = 1, #SIZES do
local c = createCard(page, rank, width)
if rank == 1 then
c:SetPoint("TOPLEFT", head, "BOTTOMLEFT", J.cardX, J.cardsY)
else
c:SetPoint("TOPLEFT", page.maps[rank - 1], "BOTTOMLEFT", 0, 0)
end
page.maps[rank] = c
end
I.pvpPage = page
InspectPVPFrame:HookScript("OnShow", function()
page:Show()
I.UpdatePvP()
end)
InspectPVPFrame:HookScript("OnHide", function() page:Hide() end)
end
-- ------------------------------------------------------------ Moving
-- The title bar is the drag handle, as in Achievements.lua. The position is saved in
-- ForeverUIDB.positions (key "inspection", top-center, as WindowStack.lua) and re-applied on
-- show and after UpdateUIPanelPositions, which re-places this "left" panel whenever a panel
-- opens or closes. Nothing here is secure, so moving works in combat. The inspected talent
-- window does not follow.
local KEY = "inspection"
local function positions()
ForeverUIDB = ForeverUIDB or {}
ForeverUIDB.positions = ForeverUIDB.positions or {}
return ForeverUIDB.positions
end
function I.Reposition()
local f = InspectFrame
local p = positions()[KEY]
if not f or not p then return end
f:ClearAllPoints()
f:SetPoint("TOP", UIParent, "TOP", p.x, p.y)
end
-- Top-center of a frame relative to UIParent's (nil while the frame is not placed)
local function topCenter(frame)
local cx, ux = frame:GetCenter(), UIParent:GetCenter()
local top, uiTop = frame:GetTop(), UIParent:GetTop()
if not cx or not ux or not top or not uiTop then return end
return cx - ux, top - uiTop
end
local function makeMovable(f, handle)
f:SetMovable(true)
f:SetClampedToScreen(true)
handle:EnableMouse(true)
handle:RegisterForDrag("LeftButton")
handle:SetScript("OnDragStart", function()
f:StartMoving()
end)
handle:SetScript("OnDragStop", function()
f:StopMovingOrSizing()
local x, y = topCenter(f)
if not x then return end
f:ClearAllPoints()
f:SetPoint("TOP", UIParent, "TOP", x, y)
-- we keep the position: stop the client from saving it too
if f.SetUserPlaced then f:SetUserPlaced(false) end
positions()[KEY] = { x = x, y = y }
end)
f:HookScript("OnShow", I.Reposition)
hooksecurefunc("UpdateUIPanelPositions", function()
if f:IsShown() then I.Reposition() end
end)
end
-- ------------------------------------------------------------ Opening
-- Refresh portraits, PvP tab icon, race background and talents button for the unit.
function I.Update()
local f = InspectFrame
local skin = f and f.foreverSkin
local unit = f and f.unit
if not skin or not unit then return end
SetPortraitTexture(skin.portrait, unit)
local o = I.tabs
-- UpdateCharacterModeTabPortrait: the portrait, cropped again
local R = N.tabs.crop
SetPortraitTexture(o[1].icon, unit)
o[1].icon:SetTexCoord(R, 1 - R, R, 1 - R)
local faction = UnitFactionGroup(unit) or UnitFactionGroup("player") or "Alliance"
o[2].icon:SetTexture(TABS .. "Inv_SideTab_Honor_" .. faction .. "_c60")
local file = backgroundPath(unit)
for i, t in ipairs(I.background) do t:SetTexture(file .. i) end
local level = UnitLevel(unit) or 0
I.talents:Activate(not (level > 0 and level < N.talents.minLevel))
I.UpdateTabs()
end
function I.Skin()
local f = InspectFrame
if not f or f.foreverSkin then return end
f:SetWidth(N.window[1])
f:SetHeight(N.window[2])
f:SetHitRectInsets(0, 0, 0, 0)
InspectFramePortrait:SetAlpha(0)
InspectNameFrame:SetAlpha(0)
local skin = Tpl.PortraitWindow(f, {
portraitSide = N.portrait.side, portraitX = N.portrait.x, portraitY = N.portrait.y,
title = InspectNameText:GetText(),
})
f.foreverSkin = skin
makeMovable(f, skin.banner)
skin.title:SetFontObject(GameFontHighlight)
hooksecurefunc(InspectNameText, "SetText", function()
skin.title:SetText(InspectNameText:GetText() or "")
end)
-- inset, without button bar
local E = N.inset
local inset = CreateFrame("Frame", "ForeverUIInspectInset", f)
inset:SetPoint("TOPLEFT", f, "TOPLEFT", E[1], E[2])
inset:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", E[3], E[4])
local marble = f:CreateTexture(nil, "BACKGROUND")
marble:SetTexture("interface" .. SEP .. "ForeverUI" .. SEP .. "framegeneral" .. SEP .. "ui-background-marble", true)
if marble.SetHorizTile then marble:SetHorizTile(true) marble:SetVertTile(true) end
marble:SetAllPoints(inset)
skin.frameBox = Tpl.NineSlice(f, "InsetFrameTemplate", inset)
skin.marble, skin.inset = marble, inset
Tpl.CloseButton(InspectFrameCloseButton, f)
InspectFrameCloseButton:SetFrameLevel(f:GetFrameLevel() + 22)
-- the bottom tabs go away; the side tabs replace them
for i = 1, 3 do
local t = _G["InspectFrameTab" .. i]
t:SetAlpha(0)
t:EnableMouse(false)
t:Hide()
end
local O = N.tabs
local bar = CreateFrame("Frame", nil, f)
bar:SetWidth(O.l)
bar:SetHeight(O.h)
bar:SetPoint("TOPLEFT", f, "TOPRIGHT", O.x, O.y)
bar:SetFrameLevel(f:GetFrameLevel() + 1)
I.tabs = { createTab(bar, 1, CHARACTER_INFO), createTab(bar, 2, PLAYER_V_PLAYER) }
I.tabs[1]:SetPoint("TOPLEFT", bar, "TOPLEFT", 0, 0)
I.tabs[2]:SetPoint("TOPLEFT", I.tabs[1], "BOTTOMLEFT", 0, O.gap)
skinCharacter(inset)
buildPvP(f, inset)
f:HookScript("OnShow", I.Update)
-- the talent window follows the inspection: it closes with it
f:HookScript("OnHide", function()
if ForeverUI.Talents and ForeverUI.Talents.inspection and PlayerTalentFrame then
HideUIPanel(PlayerTalentFrame)
end
end)
hooksecurefunc("InspectSwitchTabs", I.UpdateTabs)
hooksecurefunc("InspectFrame_UnitChanged", I.Update)
I.Update()
I.Reposition()
end
I.Skin()
local watcher = CreateFrame("Frame")
watcher:RegisterEvent("ADDON_LOADED")
watcher:RegisterEvent("UNIT_PORTRAIT_UPDATE")
watcher:RegisterEvent("INSPECT_HONOR_UPDATE")
watcher:SetScript("OnEvent", function(_, event, arg1)
if event == "ADDON_LOADED" then
if arg1 == "Blizzard_InspectUI" then I.Skin() end
elseif event == "UNIT_PORTRAIT_UPDATE" then
if InspectFrame and InspectFrame:IsShown() and arg1 == InspectFrame.unit then I.Update() end
elseif I.pvpPage and I.pvpPage:IsShown() then
I.UpdatePvP()
end
end)