5842eb7a2b
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>
684 lines
26 KiB
Lua
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)
|