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>
352 lines
12 KiB
Lua
352 lines
12 KiB
Lua
-- Flight master map (TaxiFrame) with camelot's art (blizzard_uipanels_game/shared/taxiframe).
|
|
-- Camelot uses TaxiFrame for its flight maps 1463 (Eastern Kingdoms) and 1464 (Kalimdor). The
|
|
-- client's TaxiRouteMap is suppressed and its TaxiMap hidden (its texture name gives the map);
|
|
-- buttons, lines and map image are ours. Outland and Northrend keep the 3.3.5 image, and so
|
|
-- does a map whose current or reachable node lies in a Burning Crusade zone camelot lacks.
|
|
-- The frame is camelot's bronze metal without portrait (ButtonFrameTemplateNoPortrait) on the
|
|
-- rock and stripes of PortraitFrameTemplate, instead of its grey BasicFrameTemplateWithInset.
|
|
|
|
local ForeverUI = ForeverUI or {}
|
|
_G.ForeverUI = ForeverUI
|
|
|
|
local Tpl = ForeverUI.Templates
|
|
local L = ForeverUI.L
|
|
|
|
local V = {}
|
|
ForeverUI.FlightMaster = V
|
|
|
|
local SEP = string.char(92)
|
|
local FOLDER = "Interface" .. SEP .. "ForeverUI" .. SEP .. "taxiframe" .. SEP
|
|
|
|
local N = {
|
|
window = { 590, 608 },
|
|
map = { 4, -24, -6, 4 }, -- InsetBg: 580 x 580
|
|
side = 580, -- camelot TAXI_MAP_WIDTH / HEIGHT
|
|
button = 16, glow = 32,
|
|
minGap = 18, -- TAXI_BUTTON_MIN_DIST
|
|
line = 32,
|
|
background = { 2, -21, -2, 2 }, -- Bg of PortraitFrameTemplate
|
|
stripes = { 6, -21, -2, -21, 43 },
|
|
titleFrame = { 0, -1, 20, -5 }, -- TitleContainer without portrait, text at -5
|
|
levels = { routes = 1, metal = 20, title = 21, closeButton = 22 },
|
|
}
|
|
|
|
-- camelot TaxiButtonTypes, keyed by the 3.3.5 node types
|
|
local TYPES = {
|
|
CURRENT = { file = FOLDER .. "ui-taxi-icon-green", glow = 0 },
|
|
REACHABLE = { file = FOLDER .. "ui-taxi-icon-white", glow = 1 },
|
|
DISTANT = { file = FOLDER .. "ui-taxi-icon-nub", glow = 0 },
|
|
}
|
|
|
|
-- Flight maps camelot knows: image, 3.3.5 world square (WorldMapContinent.dbc: minX, minY,
|
|
-- maxX, maxY), camelot world square (UiMapAssignment), Burning Crusade zone area
|
|
-- (WorldMapTransforms.dbc: region + offset)
|
|
local CAMELOT = {
|
|
[0] = {
|
|
image = FOLDER .. "taximap-1463",
|
|
taxi = { -16530, -16530, 12270, 12270 },
|
|
camelot = { -15980, -11880, 5817, 9917 },
|
|
bc = { 2400, -7733.3330078125, 13600, -266.666748046875 },
|
|
},
|
|
[1] = {
|
|
image = FOLDER .. "taximap1-classic",
|
|
taxi = { -11870, -13370, 12470, 10970 },
|
|
camelot = { -11870, -13370, 12470, 10970 },
|
|
bc = { 3199.99951171875, 1600, 10666.666320800781, 9600 },
|
|
},
|
|
}
|
|
|
|
local function place(r, ...)
|
|
r:ClearAllPoints()
|
|
r:SetPoint(...)
|
|
end
|
|
|
|
-- ------------------------------------------------------------ Positions
|
|
|
|
-- A 3.3.5 flight map position (u from the left, v from the bottom) on camelot's image
|
|
-- (fractions, y from the BOTTOM), and whether it falls in a Burning Crusade zone
|
|
local function toCamelot(C, u, v)
|
|
local T, K, B = C.taxi, C.camelot, C.bc
|
|
local worldY = T[4] - u * (T[4] - T[2])
|
|
local worldX = T[1] + v * (T[3] - T[1])
|
|
local cu = (K[4] - worldY) / (K[4] - K[2])
|
|
local cv = (worldX - K[1]) / (K[3] - K[1])
|
|
local bc = worldX >= B[1] and worldX <= B[3] and worldY >= B[2] and worldY <= B[4]
|
|
return cu, cv, bc
|
|
end
|
|
|
|
local function inside(cu, cv)
|
|
return cu >= 0 and cu <= 1 and cv >= 0 and cv <= 1
|
|
end
|
|
|
|
-- ------------------------------------------------------------ Route lines
|
|
-- The client's DrawRouteLine is camelot's DrawLine (same math, same 32 / 30 factor).
|
|
|
|
-- Route line texture i, created on first use
|
|
local function line(i)
|
|
local h = V.skin
|
|
local t = h.lines[i]
|
|
if not t then
|
|
t = h.routes:CreateTexture(nil, "BACKGROUND")
|
|
t:SetTexture(FOLDER .. "ui-taxi-line")
|
|
h.lines[i] = t
|
|
end
|
|
return t
|
|
end
|
|
|
|
-- The button at this 3.3.5 position: 3.3.5 has no TaxiGetNodeSlot, so route legs are only
|
|
-- given by their positions
|
|
local function buttonAt(x, y)
|
|
local best, gap
|
|
for _, b in pairs(V.skin.buttons) do
|
|
if b.active then
|
|
local d = math.abs(b.u - x) + math.abs(b.v - y)
|
|
if not gap or d < gap then best, gap = b, d end
|
|
end
|
|
end
|
|
if gap and gap < 0.001 then return best end
|
|
end
|
|
|
|
-- Origin and destination buttons of leg r of the route to node i
|
|
local function leg(i, r)
|
|
return buttonAt(TaxiGetSrcX(i, r), TaxiGetSrcY(i, r)), buttonAt(TaxiGetDestX(i, r), TaxiGetDestY(i, r))
|
|
end
|
|
|
|
local function connect(t, origin, destination)
|
|
DrawRouteLine(t, V.skin.routes, origin.x, origin.y, destination.x, destination.y, N.line)
|
|
t:Show()
|
|
end
|
|
|
|
-- DrawOneHopLines: direct flights from the current node, i.e. one-leg routes (3.3.5 has no
|
|
-- TaxiIsDirectFlight). ERR_TAXINOPATHS stays with the client's own DrawOneHopLines.
|
|
function V.DirectFlights()
|
|
local h = V.skin
|
|
local n = 0
|
|
for i = 1, NumTaxiNodes() do
|
|
local b = h.buttons[i]
|
|
if b and b.active then
|
|
if b.kind == "REACHABLE" and GetNumRoutes(i) == 1 then
|
|
local origin, destination = leg(i, 1)
|
|
if origin and destination then
|
|
n = n + 1
|
|
connect(line(n), origin, destination)
|
|
end
|
|
elseif b.kind == "DISTANT" then
|
|
b:Hide()
|
|
end
|
|
end
|
|
end
|
|
for i = n + 1, #h.lines do h.lines[i]:Hide() end
|
|
end
|
|
|
|
-- ------------------------------------------------------------ Buttons
|
|
|
|
-- Tooltip and route of a node. A reachable node also calls TaxiNodeSetCurrent, as the
|
|
-- 3.3.5 TaxiNodeOnButtonEnter does.
|
|
local function onEnter(b)
|
|
local h = V.skin
|
|
local i = b:GetID()
|
|
GameTooltip:SetOwner(b, "ANCHOR_RIGHT")
|
|
GameTooltip:AddLine(TaxiNodeName(i), nil, nil, nil, true)
|
|
if b.kind ~= "DISTANT" then
|
|
-- Hide distant nodes first
|
|
for _, a in pairs(h.buttons) do
|
|
if a.active and a.kind == "DISTANT" then a:Hide() end
|
|
end
|
|
end
|
|
if b.kind == "REACHABLE" then
|
|
SetTooltipMoney(GameTooltip, TaxiNodeCost(i))
|
|
TaxiNodeSetCurrent(i)
|
|
local n = GetNumRoutes(i)
|
|
for r = 1, math.max(n, #h.lines) do
|
|
if r <= n then
|
|
local origin, destination = leg(i, r)
|
|
if origin and destination then
|
|
connect(line(r), origin, destination)
|
|
if destination.kind == "DISTANT" then destination:Show() end
|
|
elseif h.lines[r] then
|
|
h.lines[r]:Hide()
|
|
end
|
|
else
|
|
h.lines[r]:Hide()
|
|
end
|
|
end
|
|
elseif b.kind == "CURRENT" then
|
|
GameTooltip:AddLine(TAXINODEYOUAREHERE, 1, 1, 1, true)
|
|
V.DirectFlights()
|
|
end
|
|
GameTooltip:Show()
|
|
end
|
|
|
|
local function onLeave()
|
|
GameTooltip:Hide()
|
|
end
|
|
|
|
local function createButton(i)
|
|
local h = V.skin
|
|
local b = CreateFrame("Button", "ForeverUITaxiButton" .. i, h.routes)
|
|
b:SetWidth(N.button)
|
|
b:SetHeight(N.button)
|
|
b:SetNormalTexture(TYPES.REACHABLE.file)
|
|
b:SetHighlightTexture(FOLDER .. "ui-taxi-icon-highlight")
|
|
local l = b:GetHighlightTexture()
|
|
l:ClearAllPoints()
|
|
l:SetWidth(N.glow)
|
|
l:SetHeight(N.glow)
|
|
l:SetPoint("CENTER", b, "CENTER", 0, 0)
|
|
b:SetScript("OnEnter", onEnter)
|
|
b:SetScript("OnLeave", onLeave)
|
|
b:SetScript("OnClick", function(self) TakeTaxiNode(self:GetID()) end)
|
|
h.buttons[i] = b
|
|
return b
|
|
end
|
|
|
|
-- ------------------------------------------------------------ Opening
|
|
|
|
-- Runs AFTER the client's OnEvent (TAXIMAP_OPENED): image, buttons, direct flights
|
|
function V.Open()
|
|
local h = V.skin
|
|
if not h then return end
|
|
local path = TaxiMap:GetTexture()
|
|
local map = type(path) == "string" and tonumber(string.match(string.lower(path), "taximap(%d+)"))
|
|
local C = map and CAMELOT[map]
|
|
-- Nodes, and the image they allow
|
|
local points = {}
|
|
for i = 1, NumTaxiNodes() do
|
|
local kind = TaxiNodeGetType(i)
|
|
if TYPES[kind] then
|
|
local u, v = TaxiNodePosition(i)
|
|
local p = { kind = kind, u = u, v = v }
|
|
if C then
|
|
p.cu, p.cv, p.bc = toCamelot(C, u, v)
|
|
if kind ~= "DISTANT" and (p.bc or not inside(p.cu, p.cv)) then C = nil end
|
|
end
|
|
points[i] = p
|
|
end
|
|
end
|
|
V.camelotImage = C and true or false
|
|
h.map:SetTexCoord(0, 1, 0, 1)
|
|
h.map:SetTexture(C and C.image or path)
|
|
for _, b in pairs(h.buttons) do
|
|
b.active = false
|
|
b:Hide()
|
|
end
|
|
-- Buttons, pushed TAXI_BUTTON_MIN_DIST (18) away from a previous one; a distant node
|
|
-- never pushes a non-distant one
|
|
local slots = {}
|
|
for i = 1, NumTaxiNodes() do
|
|
local p = points[i]
|
|
local showable = p and (not C or (not p.bc and inside(p.cu, p.cv)))
|
|
if showable then
|
|
local x, y
|
|
if C then
|
|
x, y = p.cu * N.side, p.cv * N.side
|
|
else
|
|
x, y = p.u * N.side, p.v * N.side
|
|
end
|
|
for j = 1, i - 1 do
|
|
local q = slots[j]
|
|
if q and (p.kind == "DISTANT" or q.kind ~= "DISTANT") then
|
|
local dx, dy = x - q.x, y - q.y
|
|
local d2 = dx * dx + dy * dy
|
|
if d2 < N.minGap * N.minGap then
|
|
local k = N.minGap
|
|
if d2 > 0 then k = N.minGap / math.sqrt(d2) end
|
|
x, y = q.x + dx * k, q.y + dy * k
|
|
end
|
|
end
|
|
end
|
|
slots[i] = { x = x, y = y, kind = p.kind }
|
|
local b = h.buttons[i] or createButton(i)
|
|
b:SetID(i)
|
|
b.active, b.kind, b.u, b.v, b.x, b.y = true, p.kind, p.u, p.v, x, y
|
|
place(b, "CENTER", h.map, "BOTTOMLEFT", math.floor(x + 0.5), math.floor(y + 0.5))
|
|
local T = TYPES[p.kind]
|
|
b:SetNormalTexture(T.file)
|
|
b:GetHighlightTexture():SetAlpha(T.glow)
|
|
if p.kind == "DISTANT" then b:Hide() else b:Show() end
|
|
end
|
|
end
|
|
V.DirectFlights()
|
|
end
|
|
|
|
function V.AfterEvent(self, event)
|
|
if event == "TAXIMAP_OPENED" then V.Open() end
|
|
end
|
|
|
|
-- ------------------------------------------------------------ Window
|
|
|
|
function V.Skin()
|
|
local f = TaxiFrame
|
|
if not f or f.foreverSkin then return end
|
|
-- 3.3.5 art (the four unnamed UI-TaxiFrame-* textures), portrait and merchant name:
|
|
-- camelot has none of them. The client's map is hidden and its TaxiRouteMap suppressed.
|
|
for _, r in ipairs({ f:GetRegions() }) do
|
|
if r:GetObjectType() == "Texture" then
|
|
local t = r:GetTexture()
|
|
if type(t) == "string" and string.find(string.lower(t), "ui-taxiframe", 1, true) then
|
|
r:SetAlpha(0)
|
|
end
|
|
end
|
|
end
|
|
TaxiPortrait:SetAlpha(0)
|
|
TaxiMerchant:SetAlpha(0)
|
|
TaxiMap:SetAlpha(0)
|
|
ForeverUI.Suppress(TaxiRouteMap)
|
|
f:SetWidth(N.window[1])
|
|
f:SetHeight(N.window[2])
|
|
local skin = {}
|
|
f.foreverSkin = skin
|
|
V.skin = skin
|
|
-- Rock background and stripes (PortraitFrameTemplate)
|
|
local F, S = N.background, N.stripes
|
|
local rock = f:CreateTexture(nil, "BACKGROUND")
|
|
rock:SetTexture("interface" .. SEP .. "ForeverUI" .. SEP .. "framegeneral" .. SEP .. "ui-background-rock", true)
|
|
if rock.SetHorizTile then rock:SetHorizTile(true) rock:SetVertTile(true) end
|
|
rock:SetPoint("TOPLEFT", f, "TOPLEFT", F[1], F[2])
|
|
rock:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", F[3], F[4])
|
|
local stripes = f:CreateTexture(nil, "BACKGROUND")
|
|
ForeverUI.SetAtlas(stripes, "_ui-frame-toptilestreaks", true)
|
|
stripes:SetHeight(S[5])
|
|
stripes:SetPoint("TOPLEFT", f, "TOPLEFT", S[1], S[2])
|
|
stripes:SetPoint("TOPRIGHT", f, "TOPRIGHT", S[3], S[4])
|
|
skin.rock, skin.stripes = rock, stripes
|
|
-- Bronze metal without portrait, above everything else
|
|
local metal = CreateFrame("Frame", nil, f)
|
|
metal:SetAllPoints(f)
|
|
metal:SetFrameLevel(f:GetFrameLevel() + N.levels.metal)
|
|
skin.metal = Tpl.NineSlice(metal, "ButtonFrameTemplateNoPortrait", f)
|
|
-- Map in BORDER: under the trim (ARTWORK) and the edges (OVERLAY)
|
|
local C = N.map
|
|
local map = f:CreateTexture(nil, "BORDER")
|
|
map:SetPoint("TOPLEFT", f, "TOPLEFT", C[1], C[2])
|
|
map:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", C[3], C[4])
|
|
skin.map = map
|
|
skin.trim = Tpl.NineSlice(f, "InsetFrameTemplate", map, "ARTWORK")
|
|
-- Camelot's TaxiRouteMap: lines and buttons over the map
|
|
local routes = CreateFrame("Frame", "ForeverUITaxiRouteMap", f)
|
|
routes:SetAllPoints(map)
|
|
routes:SetFrameLevel(f:GetFrameLevel() + N.levels.routes)
|
|
skin.routes, skin.lines, skin.buttons = routes, {}, {}
|
|
-- Title (TitleContainer without portrait), above the metal
|
|
local T = N.titleFrame
|
|
local container = CreateFrame("Frame", nil, f)
|
|
container:SetHeight(T[3])
|
|
container:SetPoint("TOPLEFT", f, "TOPLEFT", T[1], T[2])
|
|
container:SetPoint("TOPRIGHT", f, "TOPRIGHT", -T[1], T[2])
|
|
container:SetFrameLevel(f:GetFrameLevel() + N.levels.title)
|
|
local title = container:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
|
title:SetPoint("TOP", container, "TOP", 0, T[4])
|
|
title:SetPoint("LEFT", container, "LEFT")
|
|
title:SetPoint("RIGHT", container, "RIGHT")
|
|
title:SetText(L.TAXI_TITLE)
|
|
skin.title = title
|
|
Tpl.CloseButton(TaxiCloseButton, f)
|
|
TaxiCloseButton:SetFrameLevel(f:GetFrameLevel() + N.levels.closeButton)
|
|
-- The XML binds OnEvent to TaxiFrame_OnEvent itself (function=), so a hook on the global
|
|
-- would never run; hook the script instead
|
|
f:HookScript("OnEvent", V.AfterEvent)
|
|
end
|
|
|
|
V.Skin()
|