修复拾取界面点击无效问题 修复宠物训练界面不显示训练点问题 新增天赋分享到聊天界面 修复飞行界面无法关闭问题 修复术士宠物的显示问题 为天赋界面添加默认数据库支持 框架现在也可自主选择是否启用 玩家框架和目标框架可取消显示3D头像以及透明度修改 背包和银行也添加透明度自定义支持 优化tooltip性能和背包物品显示方式 修复布局模式在ui缩放后不能正常定位的问题 添加硬核模式危险和死亡的工会通报 添加拾取和已拾取的框体 等等
1288 lines
47 KiB
Lua
1288 lines
47 KiB
Lua
--------------------------------------------------------------------------------
|
|
-- Nanami-UI: Flight Map (FlightMap.lua)
|
|
-- Skins TaxiFrame with Nanami-UI theme, destination list, in-flight timer
|
|
--------------------------------------------------------------------------------
|
|
|
|
SFrames = SFrames or {}
|
|
SFrames.FlightMap = {}
|
|
local FM = SFrames.FlightMap
|
|
SFramesDB = SFramesDB or {}
|
|
SFramesGlobalDB = SFramesGlobalDB or {}
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Theme (Pink Cat-Paw)
|
|
--------------------------------------------------------------------------------
|
|
local T = SFrames.Theme:Extend({
|
|
currentText = { 0.40, 1.0, 0.40 },
|
|
moneyGold = { 1, 0.84, 0.0 },
|
|
moneySilver = { 0.78, 0.78, 0.78 },
|
|
moneyCopper = { 0.71, 0.43, 0.18 },
|
|
arrivedText = { 0.40, 1.0, 0.40 },
|
|
})
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Layout
|
|
--------------------------------------------------------------------------------
|
|
local DEST_PANEL_W = 220
|
|
local DEST_ROW_H = 22
|
|
local HEADER_H = 34
|
|
local SIDE_PAD = 10
|
|
local MAX_DEST_ROWS = 30
|
|
|
|
local BAR_W = 160
|
|
local TRACK_H = 170
|
|
local TRACK_X = 20
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- State
|
|
--------------------------------------------------------------------------------
|
|
local skinApplied = false
|
|
local DestPanel = nil
|
|
local DestRows = {}
|
|
local FlightBar = nil
|
|
|
|
local flightState = {
|
|
pendingFlight = false,
|
|
inFlight = false,
|
|
source = "",
|
|
dest = "",
|
|
startTime = 0,
|
|
estimated = 0,
|
|
lingerTime = 0,
|
|
}
|
|
|
|
FM.currentSource = ""
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Helpers
|
|
--------------------------------------------------------------------------------
|
|
local function GetFont()
|
|
if SFrames and SFrames.GetFont then return SFrames:GetFont() end
|
|
return "Fonts\\ARIALN.TTF"
|
|
end
|
|
|
|
local function FormatMoney(copper)
|
|
if not copper or copper <= 0 then return 0, 0, 0 end
|
|
local g = math.floor(copper / 10000)
|
|
local s = math.floor(math.mod(copper, 10000) / 100)
|
|
local c = math.mod(copper, 100)
|
|
return g, s, c
|
|
end
|
|
|
|
local function FormatTime(seconds)
|
|
if not seconds or seconds < 0 then seconds = 0 end
|
|
local m = math.floor(seconds / 60)
|
|
local s = math.floor(math.mod(seconds, 60))
|
|
return string.format("%d:%02d", m, s)
|
|
end
|
|
|
|
local function StripTextures(frame)
|
|
if not frame or not frame.GetRegions then return end
|
|
local regions = { frame:GetRegions() }
|
|
for i = 1, table.getn(regions) do
|
|
local region = regions[i]
|
|
if region and region.SetTexture and not region._nanamiKeep then
|
|
local drawLayer = region.GetDrawLayer and region:GetDrawLayer()
|
|
if drawLayer == "BACKGROUND" or drawLayer == "BORDER" or drawLayer == "ARTWORK" then
|
|
region:SetTexture(nil)
|
|
region:SetAlpha(0)
|
|
region:Hide()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function CreateShadow(parent)
|
|
local s = CreateFrame("Frame", nil, parent)
|
|
s:SetPoint("TOPLEFT", parent, "TOPLEFT", -4, 4)
|
|
s:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 4, -4)
|
|
s:SetBackdrop({
|
|
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 16,
|
|
insets = { left = 4, right = 4, top = 4, bottom = 4 },
|
|
})
|
|
s:SetBackdropColor(0, 0, 0, 0.45)
|
|
s:SetBackdropBorderColor(0, 0, 0, 0.6)
|
|
s:SetFrameLevel(math.max(0, parent:GetFrameLevel() - 1))
|
|
return s
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Flight Time Database (self-learning, stored in SFramesGlobalDB)
|
|
--------------------------------------------------------------------------------
|
|
local function GetFlightDB()
|
|
if not SFramesGlobalDB then SFramesGlobalDB = {} end
|
|
if not SFramesGlobalDB.flightTimes then SFramesGlobalDB.flightTimes = {} end
|
|
return SFramesGlobalDB.flightTimes
|
|
end
|
|
|
|
local function GetRouteKey(src, dst)
|
|
return (src or "") .. "->" .. (dst or "")
|
|
end
|
|
|
|
local function GetTaxiNodeHash(nodeIndex)
|
|
local x, y = TaxiNodePosition(nodeIndex)
|
|
if not x then return nil end
|
|
return tostring(math.floor(x * 100000000))
|
|
end
|
|
|
|
local function GetPlayerFaction()
|
|
local faction = UnitFactionGroup("player")
|
|
if faction == "Alliance" then return "Alliance" end
|
|
return "Horde"
|
|
end
|
|
|
|
-- Hash-based index built when taxi map opens: hash -> nodeIndex
|
|
local hashToIndex = {}
|
|
local indexToHash = {}
|
|
local hashCorrection = {} -- actualHash -> ftcHash (fuzzy match)
|
|
|
|
local function BuildHashIndex()
|
|
hashToIndex = {}
|
|
indexToHash = {}
|
|
hashCorrection = {}
|
|
local numNodes = NumTaxiNodes()
|
|
local faction = GetPlayerFaction()
|
|
local factionData = FTCData and FTCData[faction]
|
|
|
|
-- Collect all hashes used in FTCData for fuzzy matching
|
|
local ftcHashes = {}
|
|
if factionData then
|
|
for srcH, routes in pairs(factionData) do
|
|
ftcHashes[srcH] = true
|
|
for dstH in pairs(routes) do
|
|
ftcHashes[dstH] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
for i = 1, numNodes do
|
|
local h = GetTaxiNodeHash(i)
|
|
if h then
|
|
hashToIndex[h] = i
|
|
indexToHash[i] = h
|
|
if ftcHashes[h] then
|
|
hashCorrection[h] = h
|
|
else
|
|
-- Turtle WoW coords may differ slightly from Classic; try ±10
|
|
local hNum = tonumber(h)
|
|
if hNum then
|
|
for delta = -10, 10 do
|
|
local candidate = tostring(hNum + delta)
|
|
if ftcHashes[candidate] then
|
|
hashCorrection[h] = candidate
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function LookupFTCData(srcHash, dstHash)
|
|
if not srcHash or not dstHash then return nil end
|
|
local faction = GetPlayerFaction()
|
|
-- Priority 1: self-learned hash DB (exact hash, no correction needed)
|
|
local hdb = SFramesGlobalDB and SFramesGlobalDB.flightTimesHash
|
|
if hdb and hdb[faction] then
|
|
local lr = hdb[faction][srcHash]
|
|
if lr and lr[dstHash] then return lr[dstHash] end
|
|
end
|
|
-- Priority 2: FTCData pre-recorded (with fuzzy hash correction)
|
|
if not FTCData then return nil end
|
|
local factionData = FTCData[faction]
|
|
if not factionData then return nil end
|
|
local corrSrc = hashCorrection[srcHash] or srcHash
|
|
local corrDst = hashCorrection[dstHash] or dstHash
|
|
local srcRoutes = factionData[corrSrc]
|
|
if not srcRoutes then return nil end
|
|
return srcRoutes[corrDst]
|
|
end
|
|
|
|
local function GetEstimatedTime(src, dst)
|
|
local routeKey = GetRouteKey(src, dst)
|
|
-- Priority 1: per-account learned database (SavedVariables)
|
|
local db = GetFlightDB()
|
|
local learned = db[routeKey]
|
|
if learned then return learned end
|
|
|
|
-- Priority 2: shared learned database (FlightData.lua, cross-account)
|
|
if NanamiLearnedFlights and NanamiLearnedFlights[routeKey] then
|
|
return NanamiLearnedFlights[routeKey]
|
|
end
|
|
|
|
-- Priority 3: FTCData pre-recorded database (by hash)
|
|
if FM.currentSourceHash and dst then
|
|
local numNodes = NumTaxiNodes()
|
|
for i = 1, numNodes do
|
|
if TaxiNodeName(i) == dst then
|
|
local dstHash = indexToHash[i] or GetTaxiNodeHash(i)
|
|
if dstHash then
|
|
local ftcTime = LookupFTCData(FM.currentSourceHash, dstHash)
|
|
if ftcTime then return ftcTime end
|
|
end
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
local function GetEstimatedTimeByHash(srcHash, dstHash)
|
|
-- Check learned DB by resolving hash to names
|
|
local srcIdx = hashToIndex[srcHash]
|
|
local dstIdx = hashToIndex[dstHash]
|
|
if srcIdx and dstIdx then
|
|
local db = GetFlightDB()
|
|
local learned = db[GetRouteKey(TaxiNodeName(srcIdx), TaxiNodeName(dstIdx))]
|
|
if learned then return learned end
|
|
end
|
|
return LookupFTCData(srcHash, dstHash)
|
|
end
|
|
|
|
local function GetFlightHashDB()
|
|
if not SFramesGlobalDB then SFramesGlobalDB = {} end
|
|
if not SFramesGlobalDB.flightTimesHash then SFramesGlobalDB.flightTimesHash = {} end
|
|
return SFramesGlobalDB.flightTimesHash
|
|
end
|
|
|
|
local function SaveFlightTime(src, dst, duration, srcHash, dstHash)
|
|
if not src or src == "" or not dst or dst == "" then return end
|
|
if duration < 5 then return end
|
|
local secs = math.floor(duration + 0.5)
|
|
local db = GetFlightDB()
|
|
db[GetRouteKey(src, dst)] = secs
|
|
-- Also save in hash format for cross-account export
|
|
if srcHash and dstHash then
|
|
local faction = GetPlayerFaction()
|
|
local hdb = GetFlightHashDB()
|
|
if not hdb[faction] then hdb[faction] = {} end
|
|
if not hdb[faction][srcHash] then hdb[faction][srcHash] = {} end
|
|
hdb[faction][srcHash][dstHash] = secs
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Money Layout (icon-based, like Merchant.lua)
|
|
--------------------------------------------------------------------------------
|
|
local function LayoutRowMoney(row, copper)
|
|
row.gTxt:Hide(); row.gTex:Hide()
|
|
row.sTxt:Hide(); row.sTex:Hide()
|
|
row.cTxt:Hide(); row.cTex:Hide()
|
|
|
|
if not copper or copper <= 0 then
|
|
row.gTxt:SetText("--")
|
|
row.gTxt:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
row.gTxt:ClearAllPoints()
|
|
row.gTxt:SetPoint("RIGHT", row, "RIGHT", -6, 0)
|
|
row.gTxt:Show()
|
|
return
|
|
end
|
|
|
|
local vG, vS, vC = FormatMoney(copper)
|
|
local anchor = nil
|
|
|
|
local function AttachPair(txt, tex, val, cr, cg, cb)
|
|
txt:SetText(val)
|
|
txt:SetTextColor(cr, cg, cb)
|
|
txt:ClearAllPoints()
|
|
if not anchor then
|
|
txt:SetPoint("RIGHT", row, "RIGHT", -6, 0)
|
|
else
|
|
txt:SetPoint("RIGHT", anchor, "LEFT", -3, 0)
|
|
end
|
|
txt:Show()
|
|
tex:ClearAllPoints()
|
|
tex:SetPoint("RIGHT", txt, "LEFT", -1, 0)
|
|
tex:Show()
|
|
anchor = tex
|
|
end
|
|
|
|
if vC > 0 then AttachPair(row.cTxt, row.cTex, vC, T.moneyCopper[1], T.moneyCopper[2], T.moneyCopper[3]) end
|
|
if vS > 0 then AttachPair(row.sTxt, row.sTex, vS, T.moneySilver[1], T.moneySilver[2], T.moneySilver[3]) end
|
|
if vG > 0 then AttachPair(row.gTxt, row.gTex, vG, T.moneyGold[1], T.moneyGold[2], T.moneyGold[3]) end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Destination Row Factory
|
|
--------------------------------------------------------------------------------
|
|
local function CreateDestRow(parent, index)
|
|
local row = CreateFrame("Button", "NanamiFlightDest" .. index, parent)
|
|
row:SetHeight(DEST_ROW_H)
|
|
|
|
local font = GetFont()
|
|
|
|
local dot = row:CreateTexture(nil, "ARTWORK")
|
|
dot:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
dot:SetWidth(6)
|
|
dot:SetHeight(6)
|
|
dot:SetPoint("LEFT", row, "LEFT", 6, 0)
|
|
row.dot = dot
|
|
|
|
local nameFS = row:CreateFontString(nil, "OVERLAY")
|
|
nameFS:SetFont(font, 10, "OUTLINE")
|
|
nameFS:SetPoint("LEFT", dot, "RIGHT", 5, 0)
|
|
nameFS:SetPoint("RIGHT", row, "RIGHT", -112, 0)
|
|
nameFS:SetJustifyH("LEFT")
|
|
row.nameFS = nameFS
|
|
|
|
local timeFS = row:CreateFontString(nil, "OVERLAY")
|
|
timeFS:SetFont(font, 9, "OUTLINE")
|
|
timeFS:SetPoint("RIGHT", row, "RIGHT", -78, 0)
|
|
timeFS:SetJustifyH("RIGHT")
|
|
timeFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
row.timeFS = timeFS
|
|
|
|
row.gTxt = row:CreateFontString(nil, "OVERLAY")
|
|
row.gTxt:SetFont(font, 9, "OUTLINE")
|
|
row.gTex = row:CreateTexture(nil, "ARTWORK")
|
|
row.gTex:SetTexture("Interface\\MoneyFrame\\UI-MoneyIcons")
|
|
row.gTex:SetTexCoord(0, 0.25, 0, 1)
|
|
row.gTex:SetWidth(10); row.gTex:SetHeight(10)
|
|
|
|
row.sTxt = row:CreateFontString(nil, "OVERLAY")
|
|
row.sTxt:SetFont(font, 9, "OUTLINE")
|
|
row.sTex = row:CreateTexture(nil, "ARTWORK")
|
|
row.sTex:SetTexture("Interface\\MoneyFrame\\UI-MoneyIcons")
|
|
row.sTex:SetTexCoord(0.25, 0.5, 0, 1)
|
|
row.sTex:SetWidth(10); row.sTex:SetHeight(10)
|
|
|
|
row.cTxt = row:CreateFontString(nil, "OVERLAY")
|
|
row.cTxt:SetFont(font, 9, "OUTLINE")
|
|
row.cTex = row:CreateTexture(nil, "ARTWORK")
|
|
row.cTex:SetTexture("Interface\\MoneyFrame\\UI-MoneyIcons")
|
|
row.cTex:SetTexCoord(0.5, 0.75, 0, 1)
|
|
row.cTex:SetWidth(10); row.cTex:SetHeight(10)
|
|
|
|
local hl = row:CreateTexture(nil, "HIGHLIGHT")
|
|
hl:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
|
|
hl:SetBlendMode("ADD")
|
|
hl:SetAllPoints(row)
|
|
hl:SetAlpha(0.15)
|
|
|
|
row.nodeIndex = nil
|
|
row.nodeType = nil
|
|
|
|
row:SetScript("OnEnter", function()
|
|
if this.nodeType == "REACHABLE" and this.nodeIndex then
|
|
GameTooltip:SetOwner(this, "ANCHOR_LEFT")
|
|
GameTooltip:AddLine(TaxiNodeName(this.nodeIndex), 1, 1, 1)
|
|
local cost = TaxiNodeCost(this.nodeIndex)
|
|
if cost and cost > 0 then
|
|
SetTooltipMoney(GameTooltip, cost)
|
|
end
|
|
local est = GetEstimatedTime(FM.currentSource, TaxiNodeName(this.nodeIndex))
|
|
if est then
|
|
GameTooltip:AddLine(" ")
|
|
GameTooltip:AddLine("预计飞行: " .. FormatTime(est), 0.6, 0.8, 1.0)
|
|
end
|
|
GameTooltip:AddLine(" ")
|
|
GameTooltip:AddLine("点击飞往此处", T.dimText[1], T.dimText[2], T.dimText[3])
|
|
GameTooltip:Show()
|
|
end
|
|
end)
|
|
|
|
row:SetScript("OnLeave", function()
|
|
GameTooltip:Hide()
|
|
end)
|
|
|
|
row:SetScript("OnClick", function()
|
|
if this.nodeType == "REACHABLE" and this.nodeIndex then
|
|
TakeTaxiNode(this.nodeIndex)
|
|
end
|
|
end)
|
|
|
|
return row
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Skin TaxiFrame
|
|
--------------------------------------------------------------------------------
|
|
function FM:ApplySkin()
|
|
if skinApplied then return end
|
|
if not TaxiFrame then return end
|
|
skinApplied = true
|
|
|
|
local font = GetFont()
|
|
|
|
StripTextures(TaxiFrame)
|
|
|
|
if TaxiPortrait then TaxiPortrait:Hide(); TaxiPortrait:SetAlpha(0) end
|
|
if TaxiTitleText then TaxiTitleText:Hide() end
|
|
|
|
local origClose = TaxiCloseButton
|
|
if origClose then
|
|
origClose:Hide()
|
|
origClose:SetAlpha(0)
|
|
origClose.Show = function() end
|
|
end
|
|
|
|
TaxiFrame:SetBackdrop({
|
|
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 14,
|
|
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
|
})
|
|
TaxiFrame:SetBackdropColor(T.panelBg[1], T.panelBg[2], T.panelBg[3], T.panelBg[4])
|
|
TaxiFrame:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], T.panelBorder[4])
|
|
|
|
CreateShadow(TaxiFrame)
|
|
|
|
-- Trim empty space at the bottom (TaxiMap anchored to top, safe to shrink from bottom)
|
|
if TaxiMap then
|
|
local mapBottom = TaxiMap:GetBottom()
|
|
local frameBottom = TaxiFrame:GetBottom()
|
|
if mapBottom and frameBottom and mapBottom > frameBottom then
|
|
local excess = mapBottom - frameBottom - 10
|
|
if excess > 10 then
|
|
TaxiFrame:SetHeight(TaxiFrame:GetHeight() - excess)
|
|
end
|
|
end
|
|
end
|
|
|
|
TaxiFrame:SetMovable(true)
|
|
TaxiFrame:EnableMouse(true)
|
|
TaxiFrame:RegisterForDrag("LeftButton")
|
|
TaxiFrame:SetScript("OnDragStart", function() this:StartMoving() end)
|
|
TaxiFrame:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
|
|
|
local header = CreateFrame("Frame", nil, TaxiFrame)
|
|
header:SetPoint("TOPLEFT", TaxiFrame, "TOPLEFT", 0, 0)
|
|
header:SetPoint("TOPRIGHT", TaxiFrame, "TOPRIGHT", 0, 0)
|
|
header:SetHeight(HEADER_H)
|
|
header:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" })
|
|
header:SetBackdropColor(T.headerBg[1], T.headerBg[2], T.headerBg[3], T.headerBg[4])
|
|
header:SetFrameLevel(TaxiFrame:GetFrameLevel() + 5)
|
|
|
|
local titleIco = SFrames:CreateIcon(header, "mount", 16)
|
|
titleIco:SetDrawLayer("OVERLAY")
|
|
titleIco:SetPoint("LEFT", header, "LEFT", SIDE_PAD, 0)
|
|
titleIco:SetVertexColor(T.gold[1], T.gold[2], T.gold[3])
|
|
|
|
local titleFS = header:CreateFontString(nil, "OVERLAY")
|
|
titleFS:SetFont(font, 14, "OUTLINE")
|
|
titleFS:SetPoint("LEFT", titleIco, "RIGHT", 5, 0)
|
|
titleFS:SetPoint("RIGHT", header, "RIGHT", -30, 0)
|
|
titleFS:SetJustifyH("LEFT")
|
|
titleFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
|
|
FM.titleFS = titleFS
|
|
|
|
local closeBtn = CreateFrame("Button", nil, header)
|
|
closeBtn:SetWidth(20)
|
|
closeBtn:SetHeight(20)
|
|
closeBtn:SetPoint("RIGHT", header, "RIGHT", -8, 0)
|
|
closeBtn:SetFrameLevel(header:GetFrameLevel() + 1)
|
|
local closeTex = closeBtn:CreateTexture(nil, "ARTWORK")
|
|
closeTex:SetTexture("Interface\\AddOns\\Nanami-UI\\img\\icon")
|
|
closeTex:SetTexCoord(0.25, 0.375, 0, 0.125)
|
|
closeTex:SetAllPoints()
|
|
closeTex:SetVertexColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
closeBtn:SetScript("OnClick", function() CloseTaxiMap() end)
|
|
closeBtn:SetScript("OnEnter", function() closeTex:SetVertexColor(1, 0.6, 0.7) end)
|
|
closeBtn:SetScript("OnLeave", function() closeTex:SetVertexColor(T.dimText[1], T.dimText[2], T.dimText[3]) end)
|
|
|
|
local headerSep = TaxiFrame:CreateTexture(nil, "OVERLAY")
|
|
headerSep:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
headerSep:SetHeight(1)
|
|
headerSep:SetPoint("TOPLEFT", TaxiFrame, "TOPLEFT", 4, -HEADER_H)
|
|
headerSep:SetPoint("TOPRIGHT", TaxiFrame, "TOPRIGHT", -4, -HEADER_H)
|
|
headerSep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
|
|
headerSep._nanamiKeep = true
|
|
|
|
if TaxiMap then
|
|
local mapBorder = CreateFrame("Frame", nil, TaxiFrame)
|
|
mapBorder:SetPoint("TOPLEFT", TaxiMap, "TOPLEFT", -3, 3)
|
|
mapBorder:SetPoint("BOTTOMRIGHT", TaxiMap, "BOTTOMRIGHT", 3, -3)
|
|
mapBorder:SetBackdrop({
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
edgeSize = 12,
|
|
insets = { left = 2, right = 2, top = 2, bottom = 2 },
|
|
})
|
|
mapBorder:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], T.panelBorder[4])
|
|
mapBorder:SetFrameLevel(TaxiFrame:GetFrameLevel() + 3)
|
|
end
|
|
|
|
FM:CreateDestPanel()
|
|
|
|
local origOnHide = TaxiFrame:GetScript("OnHide")
|
|
TaxiFrame:SetScript("OnHide", function()
|
|
if origOnHide then origOnHide() end
|
|
if DestPanel then DestPanel:Hide() end
|
|
end)
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Destination Panel
|
|
--------------------------------------------------------------------------------
|
|
function FM:CreateDestPanel()
|
|
local font = GetFont()
|
|
|
|
DestPanel = CreateFrame("Frame", "NanamiFlightDestPanel", UIParent)
|
|
DestPanel:SetWidth(DEST_PANEL_W)
|
|
DestPanel:SetPoint("TOPLEFT", TaxiFrame, "TOPRIGHT", 4, 0)
|
|
DestPanel:SetPoint("BOTTOMLEFT", TaxiFrame, "BOTTOMRIGHT", 4, 0)
|
|
DestPanel:SetFrameStrata(TaxiFrame:GetFrameStrata())
|
|
DestPanel:SetFrameLevel(TaxiFrame:GetFrameLevel() + 1)
|
|
DestPanel:SetBackdrop({
|
|
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 14,
|
|
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
|
})
|
|
DestPanel:SetBackdropColor(T.listBg[1], T.listBg[2], T.listBg[3], T.listBg[4])
|
|
DestPanel:SetBackdropBorderColor(T.listBorder[1], T.listBorder[2], T.listBorder[3], T.listBorder[4])
|
|
|
|
CreateShadow(DestPanel)
|
|
|
|
local panelTitle = DestPanel:CreateFontString(nil, "OVERLAY")
|
|
panelTitle:SetFont(font, 12, "OUTLINE")
|
|
panelTitle:SetPoint("TOP", DestPanel, "TOP", 0, -8)
|
|
panelTitle:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
|
|
panelTitle:SetText("目的地列表")
|
|
|
|
FM.currentLabel = DestPanel:CreateFontString(nil, "OVERLAY")
|
|
FM.currentLabel:SetFont(font, 10, "OUTLINE")
|
|
FM.currentLabel:SetPoint("TOPLEFT", DestPanel, "TOPLEFT", 8, -28)
|
|
FM.currentLabel:SetPoint("RIGHT", DestPanel, "RIGHT", -8, 0)
|
|
FM.currentLabel:SetJustifyH("LEFT")
|
|
FM.currentLabel:SetTextColor(T.currentText[1], T.currentText[2], T.currentText[3])
|
|
|
|
local sepLine = DestPanel:CreateTexture(nil, "OVERLAY")
|
|
sepLine:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
sepLine:SetHeight(1)
|
|
sepLine:SetPoint("TOPLEFT", DestPanel, "TOPLEFT", 8, -46)
|
|
sepLine:SetPoint("TOPRIGHT", DestPanel, "TOPRIGHT", -8, -46)
|
|
sepLine:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
|
|
|
|
FM.noDestLabel = DestPanel:CreateFontString(nil, "OVERLAY")
|
|
FM.noDestLabel:SetFont(font, 10, "OUTLINE")
|
|
FM.noDestLabel:SetPoint("TOP", DestPanel, "TOP", 0, -60)
|
|
FM.noDestLabel:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
FM.noDestLabel:SetText("暂无可用航线")
|
|
FM.noDestLabel:Hide()
|
|
|
|
local scrollFrame = CreateFrame("ScrollFrame", "NanamiFlightDestScroll", DestPanel, "UIPanelScrollFrameTemplate")
|
|
scrollFrame:SetPoint("TOPLEFT", DestPanel, "TOPLEFT", 2, -50)
|
|
scrollFrame:SetPoint("BOTTOMRIGHT", DestPanel, "BOTTOMRIGHT", -20, 6)
|
|
|
|
local scrollChild = CreateFrame("Frame", "NanamiFlightDestScrollChild", scrollFrame)
|
|
scrollChild:SetWidth(DEST_PANEL_W - 24)
|
|
scrollChild:SetHeight(MAX_DEST_ROWS * DEST_ROW_H + 20)
|
|
scrollFrame:SetScrollChild(scrollChild)
|
|
|
|
local scrollBar = getglobal("NanamiFlightDestScrollScrollBar")
|
|
if scrollBar then
|
|
scrollBar:SetWidth(12)
|
|
local regions = { scrollBar:GetRegions() }
|
|
for i = 1, table.getn(regions) do
|
|
local region = regions[i]
|
|
if region and region.GetObjectType and region:GetObjectType() == "Texture" then
|
|
region:SetTexture(nil)
|
|
region:SetAlpha(0)
|
|
end
|
|
end
|
|
local thumb = scrollBar:GetThumbTexture()
|
|
if thumb then
|
|
thumb:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
thumb:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], 0.6)
|
|
thumb:SetWidth(10)
|
|
thumb:SetHeight(40)
|
|
end
|
|
end
|
|
local scrollUp = getglobal("NanamiFlightDestScrollScrollBarScrollUpButton")
|
|
if scrollUp then scrollUp:SetAlpha(0); scrollUp:SetWidth(1); scrollUp:SetHeight(1) end
|
|
local scrollDown = getglobal("NanamiFlightDestScrollScrollBarScrollDownButton")
|
|
if scrollDown then scrollDown:SetAlpha(0); scrollDown:SetWidth(1); scrollDown:SetHeight(1) end
|
|
|
|
for i = 1, MAX_DEST_ROWS do
|
|
local row = CreateDestRow(scrollChild, i)
|
|
row:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 0, -((i - 1) * DEST_ROW_H))
|
|
row:SetPoint("RIGHT", scrollChild, "RIGHT", 0, 0)
|
|
row:Hide()
|
|
DestRows[i] = row
|
|
end
|
|
|
|
DestPanel:Hide()
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Update Destinations
|
|
--------------------------------------------------------------------------------
|
|
function FM:UpdateDestinations()
|
|
if not DestPanel then return end
|
|
|
|
local numNodes = NumTaxiNodes()
|
|
local currentName = ""
|
|
local reachable = {}
|
|
local unreachableCount = 0
|
|
|
|
for i = 1, numNodes do
|
|
local name = TaxiNodeName(i)
|
|
local ntype = TaxiNodeGetType(i)
|
|
local cost = TaxiNodeCost(i)
|
|
|
|
if ntype == "CURRENT" then
|
|
currentName = name
|
|
elseif ntype == "REACHABLE" then
|
|
table.insert(reachable, { index = i, name = name, cost = cost })
|
|
elseif ntype == "NONE" then
|
|
unreachableCount = unreachableCount + 1
|
|
end
|
|
end
|
|
|
|
FM.currentSource = currentName
|
|
|
|
-- Build hash index for FTCData lookup
|
|
BuildHashIndex()
|
|
FM.currentSourceHash = nil
|
|
for i = 1, numNodes do
|
|
if TaxiNodeGetType(i) == "CURRENT" then
|
|
FM.currentSourceHash = indexToHash[i] or GetTaxiNodeHash(i)
|
|
break
|
|
end
|
|
end
|
|
|
|
table.sort(reachable, function(a, b) return a.cost < b.cost end)
|
|
|
|
if table.getn(reachable) == 0 then
|
|
CloseTaxiMap()
|
|
return
|
|
end
|
|
|
|
local npcName = UnitName("NPC") or "飞行管理员"
|
|
FM.titleFS:SetText(npcName .. " - 飞行路线")
|
|
|
|
if currentName ~= "" then
|
|
FM.currentLabel:SetText("|cFF66E666*|r " .. currentName)
|
|
else
|
|
FM.currentLabel:SetText("|cFF66E666*|r 未知")
|
|
end
|
|
|
|
local rowIdx = 0
|
|
|
|
for _, dest in ipairs(reachable) do
|
|
rowIdx = rowIdx + 1
|
|
if rowIdx <= MAX_DEST_ROWS then
|
|
local row = DestRows[rowIdx]
|
|
row.nodeIndex = dest.index
|
|
row.nodeType = "REACHABLE"
|
|
row.dot:SetVertexColor(T.nameText[1], T.nameText[2], T.nameText[3])
|
|
row.nameFS:SetText(dest.name)
|
|
row.nameFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
|
|
LayoutRowMoney(row, dest.cost)
|
|
local est = GetEstimatedTime(currentName, dest.name)
|
|
if not est and FM.currentSourceHash then
|
|
local dstHash = indexToHash[dest.index] or GetTaxiNodeHash(dest.index)
|
|
if dstHash then est = LookupFTCData(FM.currentSourceHash, dstHash) end
|
|
end
|
|
if est then
|
|
row.timeFS:SetText(FormatTime(est))
|
|
row.timeFS:Show()
|
|
else
|
|
row.timeFS:SetText("")
|
|
row.timeFS:Hide()
|
|
end
|
|
row:Show()
|
|
end
|
|
end
|
|
|
|
if unreachableCount > 0 then
|
|
rowIdx = rowIdx + 1
|
|
if rowIdx <= MAX_DEST_ROWS then
|
|
local row = DestRows[rowIdx]
|
|
row.nodeIndex = nil
|
|
row.nodeType = "NONE"
|
|
row.dot:SetVertexColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
row.nameFS:SetText("(" .. unreachableCount .. " 个未发现)")
|
|
row.nameFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
row.gTxt:Hide(); row.gTex:Hide()
|
|
row.sTxt:Hide(); row.sTex:Hide()
|
|
row.cTxt:Hide(); row.cTex:Hide()
|
|
row.timeFS:SetText(""); row.timeFS:Hide()
|
|
row:Show()
|
|
end
|
|
end
|
|
|
|
for i = rowIdx + 1, MAX_DEST_ROWS do
|
|
DestRows[i]:Hide()
|
|
end
|
|
|
|
if rowIdx == 0 then
|
|
FM.noDestLabel:Show()
|
|
else
|
|
FM.noDestLabel:Hide()
|
|
end
|
|
|
|
local child = getglobal("NanamiFlightDestScrollChild")
|
|
if child then
|
|
child:SetHeight(math.max(rowIdx * DEST_ROW_H + 10, 50))
|
|
end
|
|
|
|
DestPanel:Show()
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Hook Node Buttons (tooltip only)
|
|
--------------------------------------------------------------------------------
|
|
function FM:HookNodeButtons()
|
|
local numNodes = NumTaxiNodes()
|
|
for i = 1, numNodes do
|
|
local btn = getglobal("TaxiButton" .. i)
|
|
if btn and not btn._nanamiHooked then
|
|
btn._nanamiHooked = true
|
|
|
|
local origEnter = btn:GetScript("OnEnter")
|
|
btn:SetScript("OnEnter", function()
|
|
if origEnter then origEnter() end
|
|
local id = this:GetID()
|
|
if TaxiNodeGetType(id) == "REACHABLE" then
|
|
local cost = TaxiNodeCost(id)
|
|
if cost and cost > 0 then
|
|
GameTooltip:AddLine(" ")
|
|
SetTooltipMoney(GameTooltip, cost)
|
|
end
|
|
local est = GetEstimatedTime(FM.currentSource, TaxiNodeName(id))
|
|
if est then
|
|
GameTooltip:AddLine("预计飞行: " .. FormatTime(est), 0.6, 0.8, 1.0)
|
|
end
|
|
GameTooltip:Show()
|
|
end
|
|
for _, row in ipairs(DestRows) do
|
|
if row:IsShown() and row.nodeIndex == id then
|
|
row.nameFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
|
|
end
|
|
end
|
|
end)
|
|
|
|
local origLeave = btn:GetScript("OnLeave")
|
|
btn:SetScript("OnLeave", function()
|
|
if origLeave then origLeave() end
|
|
for _, row in ipairs(DestRows) do
|
|
if row:IsShown() and row.nodeType == "REACHABLE" then
|
|
row.nameFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Flight Progress Bar (vertical axis: departure → destination with countdown)
|
|
--------------------------------------------------------------------------------
|
|
local COLLAPSED_H = 34
|
|
local barCollapsed = false
|
|
|
|
local function SetBarCollapsed(collapsed)
|
|
barCollapsed = collapsed
|
|
if not FlightBar then return end
|
|
local bar = FlightBar
|
|
if collapsed then
|
|
bar:SetHeight(COLLAPSED_H)
|
|
for _, el in ipairs(bar.expandElements) do el:Hide() end
|
|
bar.collapseBtn.label:SetText("+")
|
|
-- Vertically center all visible elements
|
|
bar.titleFS:ClearAllPoints()
|
|
bar.titleFS:SetPoint("LEFT", bar, "LEFT", 10, 0)
|
|
bar.compactFS:ClearAllPoints()
|
|
bar.compactFS:SetPoint("RIGHT", bar, "RIGHT", -28, 0)
|
|
bar.collapseBtn:ClearAllPoints()
|
|
bar.collapseBtn:SetPoint("RIGHT", bar, "RIGHT", -6, 0)
|
|
bar.compactFS:Show()
|
|
else
|
|
bar:SetHeight(bar.expandedH)
|
|
for _, el in ipairs(bar.expandElements) do el:Show() end
|
|
bar.collapseBtn.label:SetText("-")
|
|
-- Restore top-aligned positions
|
|
bar.titleFS:ClearAllPoints()
|
|
bar.titleFS:SetPoint("TOPLEFT", bar, "TOPLEFT", 10, -8)
|
|
bar.compactFS:ClearAllPoints()
|
|
bar.compactFS:SetPoint("RIGHT", bar, "RIGHT", -28, -9)
|
|
bar.collapseBtn:ClearAllPoints()
|
|
bar.collapseBtn:SetPoint("TOPRIGHT", bar, "TOPRIGHT", -6, -6)
|
|
bar.compactFS:Hide()
|
|
end
|
|
end
|
|
|
|
local function CreateFlightBar()
|
|
if FlightBar then return end
|
|
|
|
local font = GetFont()
|
|
local panelH = 8 + 18 + 6 + 12 + 6 + TRACK_H + 6 + 12 + 10 + 14 + 4 + 14 + 4 + 14 + 10
|
|
|
|
local bar = CreateFrame("Frame", "NanamiFlightBar", UIParent)
|
|
bar:SetWidth(BAR_W)
|
|
bar:SetHeight(panelH)
|
|
bar.expandedH = panelH
|
|
bar:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", -80, -300)
|
|
bar:SetFrameStrata("HIGH")
|
|
bar:SetMovable(true)
|
|
bar:EnableMouse(true)
|
|
bar:RegisterForDrag("LeftButton")
|
|
bar:SetScript("OnDragStart", function() this:StartMoving() end)
|
|
bar:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
|
bar:SetBackdrop({
|
|
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 14,
|
|
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
|
})
|
|
bar:SetBackdropColor(T.panelBg[1], T.panelBg[2], T.panelBg[3], T.panelBg[4])
|
|
bar:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], T.panelBorder[4])
|
|
CreateShadow(bar)
|
|
|
|
bar.expandElements = {}
|
|
local yOff = -8
|
|
|
|
-- Title (always visible)
|
|
bar.titleFS = bar:CreateFontString(nil, "OVERLAY")
|
|
bar.titleFS:SetFont(font, 13, "OUTLINE")
|
|
bar.titleFS:SetPoint("TOPLEFT", bar, "TOPLEFT", 10, yOff)
|
|
bar.titleFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
|
|
bar.titleFS:SetText("飞行中...")
|
|
|
|
-- Compact remaining time (shown only when collapsed, next to title)
|
|
bar.compactFS = bar:CreateFontString(nil, "OVERLAY")
|
|
bar.compactFS:SetFont(font, 13, "OUTLINE")
|
|
bar.compactFS:SetPoint("RIGHT", bar, "RIGHT", -28, yOff - 1)
|
|
bar.compactFS:SetJustifyH("RIGHT")
|
|
bar.compactFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
|
|
bar.compactFS:Hide()
|
|
|
|
-- Collapse / expand button
|
|
local colBtn = CreateFrame("Button", nil, bar)
|
|
colBtn:SetWidth(18)
|
|
colBtn:SetHeight(18)
|
|
colBtn:SetPoint("TOPRIGHT", bar, "TOPRIGHT", -6, -6)
|
|
colBtn:SetFrameLevel(bar:GetFrameLevel() + 3)
|
|
local colLabel = colBtn:CreateFontString(nil, "OVERLAY")
|
|
colLabel:SetFont(font, 14, "OUTLINE")
|
|
colLabel:SetPoint("CENTER", 0, 1)
|
|
colLabel:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
colLabel:SetText("-")
|
|
colBtn.label = colLabel
|
|
colBtn:SetScript("OnClick", function()
|
|
SetBarCollapsed(not barCollapsed)
|
|
end)
|
|
colBtn:SetScript("OnEnter", function()
|
|
colLabel:SetTextColor(1, 0.7, 0.85)
|
|
end)
|
|
colBtn:SetScript("OnLeave", function()
|
|
colLabel:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
end)
|
|
bar.collapseBtn = colBtn
|
|
|
|
yOff = yOff - 18
|
|
|
|
-- Separator
|
|
local sep1 = bar:CreateTexture(nil, "OVERLAY")
|
|
sep1:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
sep1:SetHeight(1)
|
|
sep1:SetPoint("TOPLEFT", bar, "TOPLEFT", 8, yOff - 2)
|
|
sep1:SetPoint("TOPRIGHT", bar, "TOPRIGHT", -8, yOff - 2)
|
|
sep1:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
|
|
table.insert(bar.expandElements, sep1)
|
|
yOff = yOff - 6
|
|
|
|
-- Source dot + name
|
|
local srcDot = bar:CreateTexture(nil, "OVERLAY")
|
|
srcDot:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
srcDot:SetWidth(9)
|
|
srcDot:SetHeight(9)
|
|
srcDot:SetPoint("TOPLEFT", bar, "TOPLEFT", TRACK_X, yOff)
|
|
srcDot:SetVertexColor(T.currentText[1], T.currentText[2], T.currentText[3])
|
|
bar.srcDot = srcDot
|
|
table.insert(bar.expandElements, srcDot)
|
|
|
|
bar.srcNameFS = bar:CreateFontString(nil, "OVERLAY")
|
|
bar.srcNameFS:SetFont(font, 10, "OUTLINE")
|
|
bar.srcNameFS:SetPoint("LEFT", srcDot, "RIGHT", 6, 0)
|
|
bar.srcNameFS:SetPoint("RIGHT", bar, "RIGHT", -8, 0)
|
|
bar.srcNameFS:SetJustifyH("LEFT")
|
|
bar.srcNameFS:SetTextColor(T.currentText[1], T.currentText[2], T.currentText[3])
|
|
table.insert(bar.expandElements, bar.srcNameFS)
|
|
|
|
yOff = yOff - 14
|
|
|
|
-- Vertical track area
|
|
local trackTop = yOff
|
|
local trackBg = bar:CreateTexture(nil, "BACKGROUND")
|
|
trackBg:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
trackBg:SetWidth(3)
|
|
trackBg:SetHeight(TRACK_H)
|
|
trackBg:SetPoint("TOPLEFT", bar, "TOPLEFT", TRACK_X + 3, trackTop)
|
|
trackBg:SetVertexColor(T.sectionBg[1], T.sectionBg[2], T.sectionBg[3], T.sectionBg[4])
|
|
bar.trackBg = trackBg
|
|
table.insert(bar.expandElements, trackBg)
|
|
|
|
-- Track fill (grows from top downward)
|
|
local trackFillFrame = CreateFrame("Frame", nil, bar)
|
|
trackFillFrame:SetPoint("TOPLEFT", trackBg, "TOPLEFT", 0, 0)
|
|
trackFillFrame:SetWidth(3)
|
|
trackFillFrame:SetHeight(1)
|
|
local trackFill = trackFillFrame:CreateTexture(nil, "ARTWORK")
|
|
trackFill:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
trackFill:SetAllPoints(trackFillFrame)
|
|
trackFill:SetVertexColor(T.progressFill[1], T.progressFill[2], T.progressFill[3], T.progressFill[4])
|
|
bar.trackFillFrame = trackFillFrame
|
|
bar.trackFill = trackFill
|
|
table.insert(bar.expandElements, trackFillFrame)
|
|
|
|
-- Progress indicator
|
|
local progDot = bar:CreateTexture(nil, "OVERLAY")
|
|
progDot:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
progDot:SetWidth(13)
|
|
progDot:SetHeight(5)
|
|
progDot:SetPoint("CENTER", trackBg, "TOP", 0, 0)
|
|
progDot:SetVertexColor(T.accent[1], T.accent[2], T.accent[3], T.accent[4])
|
|
bar.progDot = progDot
|
|
table.insert(bar.expandElements, progDot)
|
|
|
|
-- Progress glow
|
|
local progGlow = bar:CreateTexture(nil, "ARTWORK")
|
|
progGlow:SetTexture("Interface\\Buttons\\UI-ActionButton-Border")
|
|
progGlow:SetBlendMode("ADD")
|
|
progGlow:SetWidth(28)
|
|
progGlow:SetHeight(28)
|
|
progGlow:SetPoint("CENTER", progDot, "CENTER", 0, 0)
|
|
progGlow:SetVertexColor(T.progressFill[1], T.progressFill[2], T.progressFill[3], 0.4)
|
|
bar.progGlow = progGlow
|
|
table.insert(bar.expandElements, progGlow)
|
|
|
|
-- Elapsed time
|
|
bar.elapsedFS = bar:CreateFontString(nil, "OVERLAY")
|
|
bar.elapsedFS:SetFont(font, 11, "OUTLINE")
|
|
bar.elapsedFS:SetPoint("LEFT", trackBg, "RIGHT", 12, 0)
|
|
bar.elapsedFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
|
|
table.insert(bar.expandElements, bar.elapsedFS)
|
|
|
|
-- Remaining time (follows progress dot)
|
|
bar.remainFS = bar:CreateFontString(nil, "OVERLAY")
|
|
bar.remainFS:SetFont(font, 14, "OUTLINE")
|
|
bar.remainFS:SetPoint("LEFT", progDot, "RIGHT", 10, 0)
|
|
bar.remainFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
|
|
table.insert(bar.expandElements, bar.remainFS)
|
|
|
|
yOff = trackTop - TRACK_H
|
|
|
|
-- Destination dot + name
|
|
local dstDot = bar:CreateTexture(nil, "OVERLAY")
|
|
dstDot:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
dstDot:SetWidth(9)
|
|
dstDot:SetHeight(9)
|
|
dstDot:SetPoint("TOPLEFT", bar, "TOPLEFT", TRACK_X, yOff - 4)
|
|
dstDot:SetVertexColor(T.nameText[1], T.nameText[2], T.nameText[3])
|
|
bar.dstDot = dstDot
|
|
table.insert(bar.expandElements, dstDot)
|
|
|
|
bar.dstNameFS = bar:CreateFontString(nil, "OVERLAY")
|
|
bar.dstNameFS:SetFont(font, 10, "OUTLINE")
|
|
bar.dstNameFS:SetPoint("LEFT", dstDot, "RIGHT", 6, 0)
|
|
bar.dstNameFS:SetPoint("RIGHT", bar, "RIGHT", -8, 0)
|
|
bar.dstNameFS:SetJustifyH("LEFT")
|
|
bar.dstNameFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
|
|
table.insert(bar.expandElements, bar.dstNameFS)
|
|
|
|
yOff = yOff - 18
|
|
|
|
-- Bottom info
|
|
local sep2 = bar:CreateTexture(nil, "OVERLAY")
|
|
sep2:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
sep2:SetHeight(1)
|
|
sep2:SetPoint("TOPLEFT", bar, "TOPLEFT", 8, yOff)
|
|
sep2:SetPoint("TOPRIGHT", bar, "TOPRIGHT", -8, yOff)
|
|
sep2:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
|
|
table.insert(bar.expandElements, sep2)
|
|
|
|
yOff = yOff - 6
|
|
|
|
bar.totalFS = bar:CreateFontString(nil, "OVERLAY")
|
|
bar.totalFS:SetFont(font, 10, "OUTLINE")
|
|
bar.totalFS:SetPoint("TOPLEFT", bar, "TOPLEFT", 12, yOff)
|
|
bar.totalFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
table.insert(bar.expandElements, bar.totalFS)
|
|
|
|
bar:Hide()
|
|
FlightBar = bar
|
|
end
|
|
|
|
local function ShowFlightBar()
|
|
CreateFlightBar()
|
|
local bar = FlightBar
|
|
|
|
bar.srcNameFS:SetText(flightState.source)
|
|
bar.dstNameFS:SetText(flightState.dest)
|
|
|
|
if flightState.estimated > 0 then
|
|
bar.totalFS:SetText("预计: " .. FormatTime(flightState.estimated))
|
|
else
|
|
bar.totalFS:SetText("首次飞行 - 记录中...")
|
|
end
|
|
|
|
bar.titleFS:SetText("飞行中...")
|
|
bar.titleFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
|
|
bar:SetAlpha(1)
|
|
bar.compactFS:SetText("")
|
|
|
|
-- Reset track fill
|
|
bar.trackFillFrame:SetHeight(1)
|
|
bar.progDot:ClearAllPoints()
|
|
bar.progDot:SetPoint("CENTER", bar.trackBg, "TOP", 0, 0)
|
|
bar.progGlow:ClearAllPoints()
|
|
bar.progGlow:SetPoint("CENTER", bar.progDot, "CENTER", 0, 0)
|
|
bar.elapsedFS:SetText("0:00")
|
|
bar.remainFS:SetText("")
|
|
|
|
-- Restore collapse state
|
|
SetBarCollapsed(barCollapsed)
|
|
|
|
bar:Show()
|
|
end
|
|
|
|
local function UpdateFlightBar()
|
|
if not FlightBar or not FlightBar:IsShown() then return end
|
|
local bar = FlightBar
|
|
|
|
local elapsed = GetTime() - flightState.startTime
|
|
local estimated = flightState.estimated
|
|
local progress = 0
|
|
local compactText = FormatTime(elapsed)
|
|
|
|
if estimated > 0 then
|
|
progress = math.min(1, elapsed / estimated)
|
|
local remain = math.max(0, estimated - elapsed)
|
|
bar.remainFS:SetText(FormatTime(remain))
|
|
compactText = FormatTime(remain)
|
|
if remain <= 0 then
|
|
bar.remainFS:SetText("即将到达")
|
|
bar.remainFS:SetTextColor(T.arrivedText[1], T.arrivedText[2], T.arrivedText[3])
|
|
compactText = "即将到达"
|
|
else
|
|
bar.remainFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
|
|
end
|
|
else
|
|
bar.remainFS:SetText("记录中...")
|
|
bar.remainFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
progress = math.mod(elapsed / 10, 1) * 0.8
|
|
end
|
|
|
|
bar.compactFS:SetText(compactText)
|
|
bar.elapsedFS:SetText("已飞行 " .. FormatTime(elapsed))
|
|
|
|
if not barCollapsed then
|
|
local fillH = math.max(1, progress * TRACK_H)
|
|
bar.trackFillFrame:SetHeight(fillH)
|
|
bar.progDot:ClearAllPoints()
|
|
bar.progDot:SetPoint("CENTER", bar.trackBg, "TOP", 0, -fillH)
|
|
bar.progGlow:ClearAllPoints()
|
|
bar.progGlow:SetPoint("CENTER", bar.progDot, "CENTER", 0, 0)
|
|
end
|
|
end
|
|
|
|
local function OnFlightArrived()
|
|
if not FlightBar then return end
|
|
local bar = FlightBar
|
|
|
|
local elapsed = GetTime() - flightState.startTime
|
|
SaveFlightTime(flightState.source, flightState.dest, elapsed, flightState.srcHash, flightState.dstHash)
|
|
|
|
bar.titleFS:SetText("已到达!")
|
|
bar.titleFS:SetTextColor(T.arrivedText[1], T.arrivedText[2], T.arrivedText[3])
|
|
bar.remainFS:SetText("")
|
|
bar.elapsedFS:SetText("飞行用时 " .. FormatTime(elapsed))
|
|
bar.totalFS:SetText("")
|
|
|
|
-- Fill track to 100%
|
|
bar.trackFillFrame:SetHeight(TRACK_H)
|
|
bar.progDot:ClearAllPoints()
|
|
bar.progDot:SetPoint("CENTER", bar.trackBg, "BOTTOM", 0, 0)
|
|
bar.progGlow:ClearAllPoints()
|
|
bar.progGlow:SetPoint("CENTER", bar.progDot, "CENTER", 0, 0)
|
|
bar.progDot:SetVertexColor(T.arrivedText[1], T.arrivedText[2], T.arrivedText[3])
|
|
|
|
flightState.lingerTime = 4
|
|
end
|
|
|
|
local function HideFlightBar()
|
|
if FlightBar then
|
|
FlightBar:Hide()
|
|
FlightBar.progDot:SetVertexColor(T.accent[1], T.accent[2], T.accent[3], T.accent[4])
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Initialize
|
|
--------------------------------------------------------------------------------
|
|
function FM:Initialize()
|
|
-- TaxiMap skin updater
|
|
local updater = CreateFrame("Frame")
|
|
updater:RegisterEvent("TAXIMAP_OPENED")
|
|
updater.needsUpdate = false
|
|
|
|
updater:SetScript("OnEvent", function()
|
|
if event == "TAXIMAP_OPENED" then
|
|
this.needsUpdate = true
|
|
end
|
|
end)
|
|
|
|
updater:SetScript("OnUpdate", function()
|
|
if this.needsUpdate then
|
|
this.needsUpdate = false
|
|
FM:ApplySkin()
|
|
FM:UpdateDestinations()
|
|
FM:HookNodeButtons()
|
|
end
|
|
end)
|
|
|
|
-- Hook TakeTaxiNode to capture route info before flight starts
|
|
local origTakeTaxiNode = TakeTaxiNode
|
|
TakeTaxiNode = function(index)
|
|
local numNodes = NumTaxiNodes()
|
|
local srcName = ""
|
|
local srcHash = nil
|
|
for i = 1, numNodes do
|
|
if TaxiNodeGetType(i) == "CURRENT" then
|
|
srcName = TaxiNodeName(i)
|
|
srcHash = indexToHash[i] or GetTaxiNodeHash(i)
|
|
break
|
|
end
|
|
end
|
|
local dstName = TaxiNodeName(index) or ""
|
|
local dstHash = indexToHash[index] or GetTaxiNodeHash(index)
|
|
|
|
flightState.source = srcName
|
|
flightState.dest = dstName
|
|
flightState.srcHash = srcHash
|
|
flightState.dstHash = dstHash
|
|
flightState.pendingFlight = true
|
|
flightState.inFlight = false
|
|
flightState.lingerTime = 0
|
|
|
|
-- Lookup estimated time: learned DB first, then FTCData
|
|
local est = GetEstimatedTime(srcName, dstName)
|
|
if not est and srcHash and dstHash then
|
|
est = LookupFTCData(srcHash, dstHash)
|
|
end
|
|
flightState.estimated = est or 0
|
|
|
|
origTakeTaxiNode(index)
|
|
end
|
|
|
|
-- Flight state monitor
|
|
local monitor = CreateFrame("Frame")
|
|
monitor:SetScript("OnUpdate", function()
|
|
local dt = arg1 or 0
|
|
local onTaxi = UnitOnTaxi("player")
|
|
|
|
if flightState.pendingFlight and onTaxi then
|
|
flightState.pendingFlight = false
|
|
flightState.inFlight = true
|
|
flightState.startTime = GetTime()
|
|
ShowFlightBar()
|
|
end
|
|
|
|
if flightState.inFlight then
|
|
if not onTaxi then
|
|
flightState.inFlight = false
|
|
OnFlightArrived()
|
|
else
|
|
UpdateFlightBar()
|
|
end
|
|
end
|
|
|
|
-- Linger after arrival then hide
|
|
if flightState.lingerTime > 0 then
|
|
flightState.lingerTime = flightState.lingerTime - dt
|
|
if flightState.lingerTime <= 1 and FlightBar then
|
|
FlightBar:SetAlpha(math.max(0, flightState.lingerTime))
|
|
end
|
|
if flightState.lingerTime <= 0 then
|
|
flightState.lingerTime = 0
|
|
HideFlightBar()
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Bootstrap
|
|
--------------------------------------------------------------------------------
|
|
local bootstrap = CreateFrame("Frame")
|
|
bootstrap:RegisterEvent("PLAYER_LOGIN")
|
|
bootstrap:SetScript("OnEvent", function()
|
|
if event == "PLAYER_LOGIN" then
|
|
if SFramesDB.enableFlightMap == nil then
|
|
SFramesDB.enableFlightMap = true
|
|
end
|
|
if SFramesDB.enableFlightMap ~= false then
|
|
FM:Initialize()
|
|
end
|
|
end
|
|
end)
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Debug: /ftcdebug (open taxi map first, then type this command)
|
|
--------------------------------------------------------------------------------
|
|
SLASH_FTCDEBUG1 = "/ftcdebug"
|
|
SlashCmdList["FTCDEBUG"] = function()
|
|
local faction = GetPlayerFaction()
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cFF00FF00[NanamiFlightDebug]|r faction=" .. tostring(faction) .. " FTCData=" .. tostring(FTCData ~= nil))
|
|
if FTCData then
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cFF00FF00[NanamiFlightDebug]|r FTCData[" .. faction .. "]=" .. tostring(FTCData[faction] ~= nil))
|
|
end
|
|
local numNodes = NumTaxiNodes()
|
|
if not numNodes or numNodes == 0 then
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[NanamiFlightDebug]|r Taxi map not open!")
|
|
return
|
|
end
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cFF00FF00[NanamiFlightDebug]|r Nodes=" .. numNodes .. " srcHash=" .. tostring(FM.currentSourceHash))
|
|
for i = 1, numNodes do
|
|
local name = TaxiNodeName(i)
|
|
local ntype = TaxiNodeGetType(i)
|
|
local x, y = TaxiNodePosition(i)
|
|
local h = tostring(math.floor(x * 100000000))
|
|
local corr = hashCorrection[h]
|
|
local corrTag = ""
|
|
if corr and corr ~= h then
|
|
corrTag = " |cFFFFFF00->fuzzy:" .. corr .. "|r"
|
|
elseif corr then
|
|
corrTag = " |cFF00FF00exact|r"
|
|
else
|
|
corrTag = " |cFFFF6666NO_MATCH|r"
|
|
end
|
|
local tag = ""
|
|
if ntype == "CURRENT" then tag = " |cFF66E666<< YOU|r" end
|
|
DEFAULT_CHAT_FRAME:AddMessage(" #" .. i .. " [" .. ntype .. "] " .. name .. " h=" .. h .. corrTag .. tag)
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Export: /ftcexport (prints learned flight times in FlightData.lua format)
|
|
--------------------------------------------------------------------------------
|
|
SLASH_FTCEXPORT1 = "/ftcexport"
|
|
SlashCmdList["FTCEXPORT"] = function()
|
|
local hdb = SFramesGlobalDB and SFramesGlobalDB.flightTimesHash
|
|
if not hdb then
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[NanamiFlightExport]|r No learned flight times yet.")
|
|
return
|
|
end
|
|
local count = 0
|
|
for faction, sources in pairs(hdb) do
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cFF00FF00[NanamiFlightExport]|r -- " .. faction .. " learned routes:")
|
|
for srcHash, dests in pairs(sources) do
|
|
for dstHash, secs in pairs(dests) do
|
|
DEFAULT_CHAT_FRAME:AddMessage(" FTCData." .. faction .. "['" .. srcHash .. "']['" .. dstHash .. "'] = " .. secs)
|
|
count = count + 1
|
|
end
|
|
end
|
|
end
|
|
if count == 0 then
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cFFFF6666[NanamiFlightExport]|r No learned flight times yet. Fly some routes first!")
|
|
else
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cFF00FF00[NanamiFlightExport]|r Total: " .. count .. " routes. Copy lines above into FlightData.lua for permanent cross-account sharing.")
|
|
end
|
|
end
|