Files
moon-well-ui/data/addon/ForeverUI/Taxi.lua
T
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

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()