SFrames.FloatingTooltip = {} -------------------------------------------------------------------------------- -- Class colors for tooltip unit-name coloring -------------------------------------------------------------------------------- local TT_CLASS_COLORS = { ["WARRIOR"] = { 0.78, 0.61, 0.43 }, ["MAGE"] = { 0.41, 0.80, 0.94 }, ["ROGUE"] = { 1.00, 0.96, 0.41 }, ["DRUID"] = { 1.00, 0.49, 0.04 }, ["HUNTER"] = { 0.67, 0.83, 0.45 }, ["SHAMAN"] = { 0.14, 0.35, 1.00 }, ["PRIEST"] = { 1.00, 1.00, 1.00 }, ["WARLOCK"] = { 0.58, 0.51, 0.79 }, ["PALADIN"] = { 0.96, 0.55, 0.73 }, } local TT_CLASS_REVERSE = {} local ttClassReverseDone = false local function TT_BuildClassReverse() if ttClassReverseDone then return end ttClassReverseDone = true if LOCALIZED_CLASS_NAMES_MALE then for en, loc in pairs(LOCALIZED_CLASS_NAMES_MALE) do TT_CLASS_REVERSE[loc] = en end end if LOCALIZED_CLASS_NAMES_FEMALE then for en, loc in pairs(LOCALIZED_CLASS_NAMES_FEMALE) do TT_CLASS_REVERSE[loc] = en end end local zh = { ["战士"]="WARRIOR", ["法师"]="MAGE", ["盗贼"]="ROGUE", ["德鲁伊"]="DRUID", ["猎人"]="HUNTER", ["萨满祭司"]="SHAMAN", ["牧师"]="PRIEST", ["术士"]="WARLOCK", ["圣骑士"]="PALADIN", } for loc, en in pairs(zh) do if not TT_CLASS_REVERSE[loc] then TT_CLASS_REVERSE[loc] = en end end for en, _ in pairs(TT_CLASS_COLORS) do TT_CLASS_REVERSE[en] = en TT_CLASS_REVERSE[string.lower(en)] = en end end local function TT_GetClassToken(unit) if not UnitExists(unit) then return nil end local className, classEN = UnitClass(unit) if classEN and classEN ~= "" then return string.upper(classEN) end if className then TT_BuildClassReverse() return TT_CLASS_REVERSE[className] end return nil end local function TT_GetClassColor(classToken) if not classToken then return nil, nil, nil end if CUSTOM_CLASS_COLORS and CUSTOM_CLASS_COLORS[classToken] then local c = CUSTOM_CLASS_COLORS[classToken] return c.r, c.g, c.b end if RAID_CLASS_COLORS and RAID_CLASS_COLORS[classToken] then local c = RAID_CLASS_COLORS[classToken] return c.r, c.g, c.b end local c = TT_CLASS_COLORS[classToken] if c then return c[1], c[2], c[3] end return nil, nil, nil end local function TT_ClassHex(classToken) local r, g, b = TT_GetClassColor(classToken) if not r then return "|cffffffff" end return string.format("|cff%02x%02x%02x", r * 255, g * 255, b * 255) end -------------------------------------------------------------------------------- -- Backdrop helper (applied once, not every frame) -------------------------------------------------------------------------------- local TT_BACKDROP = { bgFile = "Interface\\Buttons\\WHITE8X8", insets = { left = 0, right = 0, top = 0, bottom = 0 }, } local function TT_ApplyBackdrop(frame) frame:SetBackdrop(TT_BACKDROP) local _A = SFrames.ActiveTheme if _A and _A.panelBg then frame:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], 0.95) else frame:SetBackdropColor(0.08, 0.08, 0.08, 0.95) end end -------------------------------------------------------------------------------- -- Level difficulty color (fallback if GetDifficultyColor is unavailable) -------------------------------------------------------------------------------- local function TT_DifficultyColor(unitLevel) local playerLevel = UnitLevel("player") or 1 if unitLevel < 0 then return 1, 0, 0 end local diff = unitLevel - playerLevel if diff >= 5 then return 1, 0.1, 0.1 elseif diff >= 3 then return 1, 0.5, 0.25 elseif diff >= -2 then return 1, 1, 0 elseif diff >= -8 then return 0.25, 0.75, 0.25 else return 0.5, 0.5, 0.5 end end -------------------------------------------------------------------------------- -- Initialize -------------------------------------------------------------------------------- function SFrames.FloatingTooltip:Initialize() TT_ApplyBackdrop(GameTooltip) -------------------------------------------------------------------------- -- Standalone backdrop frame: a SIBLING of GameTooltip (parented to -- UIParent, not GameTooltip) at the same TOOLTIP strata but one frame- -- level below. This guarantees it renders directly behind the tooltip -- text and is completely immune to C++ SetBackdrop resets. -------------------------------------------------------------------------- if not GameTooltip._nanamiBG then GameTooltip._nanamiBG = true local bgFrame = CreateFrame("Frame", "NanamiTooltipBG", UIParent) bgFrame:SetFrameStrata("TOOLTIP") bgFrame:SetFrameLevel(math.max(1, GameTooltip:GetFrameLevel()) - 1) bgFrame:Hide() GameTooltip._nanamiBGFrame = bgFrame local bg = bgFrame:CreateTexture(nil, "ARTWORK") bg:SetTexture("Interface\\Buttons\\WHITE8X8") local _Abg = SFrames.ActiveTheme if _Abg and _Abg.panelBg then bg:SetVertexColor(_Abg.panelBg[1], _Abg.panelBg[2], _Abg.panelBg[3], 0.95) else bg:SetVertexColor(0.08, 0.08, 0.08, 0.95) end bg:SetAllPoints(bgFrame) GameTooltip._nanamiBGTex = bg end -------------------------------------------------------------------------- -- Health bar: position, backdrop, texture, health text -------------------------------------------------------------------------- local barFont = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF" GameTooltipStatusBar:SetHeight(10) GameTooltipStatusBar:ClearAllPoints() GameTooltipStatusBar:SetPoint("BOTTOMLEFT", GameTooltip, "TOPLEFT", 4, 2) GameTooltipStatusBar:SetPoint("BOTTOMRIGHT", GameTooltip, "TOPRIGHT", -4, 2) GameTooltipStatusBar:SetStatusBarTexture(SFrames:GetTexture()) GameTooltipStatusBar.bg = GameTooltipStatusBar.bg or GameTooltipStatusBar:CreateTexture(nil, "BACKGROUND") GameTooltipStatusBar.bg:SetTexture("Interface\\TARGETINGFRAME\\UI-StatusBar") GameTooltipStatusBar.bg:SetVertexColor(.1, .1, 0, .8) GameTooltipStatusBar.bg:SetAllPoints(true) if not GameTooltipStatusBar.backdrop then local bd = CreateFrame("Frame", "SFramesTooltipStatusBarBD", GameTooltipStatusBar) bd:SetPoint("TOPLEFT", GameTooltipStatusBar, "TOPLEFT", -3, 3) bd:SetPoint("BOTTOMRIGHT", GameTooltipStatusBar, "BOTTOMRIGHT", 3, -3) bd:SetBackdrop({ edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 8, edgeSize = 12, insets = { left = 3, right = 3, top = 3, bottom = 3 }, }) bd:SetBackdropBorderColor(.8, .8, .8, 1) GameTooltipStatusBar.backdrop = bd end if not GameTooltipStatusBar.healthText then local ht = GameTooltipStatusBar.backdrop:CreateFontString(nil, "DIALOG", "GameFontWhite") ht:SetFont(barFont, 12, "OUTLINE") ht:SetPoint("TOP", 0, 4) ht:SetNonSpaceWrap(false) GameTooltipStatusBar.healthText = ht end if not GameTooltipStatusBar._origSetColor then GameTooltipStatusBar._origSetColor = GameTooltipStatusBar.SetStatusBarColor GameTooltipStatusBar.SetStatusBarColor = function() return end end -------------------------------------------------------------------------- -- Track mouseover name/level for health estimation -------------------------------------------------------------------------- local ttMouseName, ttMouseLevel local barEvents = CreateFrame("Frame", nil, GameTooltipStatusBar) barEvents:RegisterEvent("UPDATE_MOUSEOVER_UNIT") barEvents:SetScript("OnEvent", function() ttMouseName = UnitName("mouseover") ttMouseLevel = UnitLevel("mouseover") end) local function TT_Abbreviate(val) if val >= 1000000 then return string.format("%.1fM", val / 1000000) elseif val >= 10000 then return string.format("%.1fK", val / 1000) end return tostring(math.floor(val)) end local barThrottle = 0 barEvents:SetScript("OnUpdate", function() barThrottle = barThrottle + arg1 if barThrottle < 0.1 then return end barThrottle = 0 local hp = GameTooltipStatusBar:GetValue() local _, hpmax = GameTooltipStatusBar:GetMinMaxValues() if hpmax and hpmax > 0 then if hpmax > 100 then GameTooltipStatusBar.healthText:SetText( TT_Abbreviate(hp) .. " / " .. TT_Abbreviate(hpmax)) else GameTooltipStatusBar.healthText:SetText( string.format("%d%%", math.ceil(hp / hpmax * 100))) end else GameTooltipStatusBar.healthText:SetText("") end end) local linesFormatted = false -------------------------------------------------------------------------- -- Track tooltip owner to skip styling for world-map / pfQuest markers -------------------------------------------------------------------------- local ttOwner = nil local orig_SetOwner = GameTooltip.SetOwner GameTooltip.SetOwner = function(self, owner, anchor, xOff, yOff) ttOwner = owner return orig_SetOwner(self, owner, anchor, xOff, yOff) end local function TT_IsMapMarkerTooltip() if not ttOwner then return false end if WorldMapFrame and WorldMapFrame:IsShown() then local name = ttOwner.GetName and ttOwner:GetName() if name and (string.find(name, "^pf") or string.find(name, "^WorldMap")) then return true end local parent = ttOwner.GetParent and ttOwner:GetParent() while parent do if parent == WorldMapFrame or parent == WorldMapButton then return true end parent = parent.GetParent and parent:GetParent() end end return false end local function TT_ShowBar(show) if not GameTooltipStatusBar then return end GameTooltipStatusBar:SetStatusBarTexture(SFrames:GetTexture()) if show then GameTooltipStatusBar:SetAlpha(1) GameTooltipStatusBar:Show() if GameTooltipStatusBar.backdrop then GameTooltipStatusBar.backdrop:SetAlpha(1) GameTooltipStatusBar.backdrop:Show() end else GameTooltipStatusBar:SetAlpha(0) if GameTooltipStatusBar.backdrop then GameTooltipStatusBar.backdrop:SetAlpha(0) end if GameTooltipStatusBar.healthText then GameTooltipStatusBar.healthText:SetText("") end end end -- Sync the standalone bg frame to match GameTooltip's position/size local function TT_SyncBGFrame() local bf = GameTooltip._nanamiBGFrame if not bf then return end local w = GameTooltip:GetWidth() local h = GameTooltip:GetHeight() if w and h and w > 0 and h > 0 then bf:SetWidth(w) bf:SetHeight(h) bf:ClearAllPoints() bf:SetPoint("TOPLEFT", GameTooltip, "TOPLEFT", 0, 0) bf:SetFrameLevel(math.max(1, GameTooltip:GetFrameLevel()) - 1) bf:Show() end end -- OnShow: apply backdrop, sync bg frame, reset formatting flag local orig_OnShow = GameTooltip:GetScript("OnShow") local ttIsMapMarker = false GameTooltip:SetScript("OnShow", function() if orig_OnShow then orig_OnShow() end linesFormatted = false ttIsMapMarker = TT_IsMapMarkerTooltip() TT_ApplyBackdrop(this) TT_SyncBGFrame() if not ttIsMapMarker then TT_ShowBar(UnitExists("mouseover")) end end) -- OnUpdate: throttled backdrop/bar refresh and cursor tracking local orig_OnUpdate = GameTooltip:GetScript("OnUpdate") local ttThrottle = 0 GameTooltip:SetScript("OnUpdate", function() if orig_OnUpdate then orig_OnUpdate() end ttThrottle = ttThrottle + arg1 if ttThrottle < 0.05 then return end ttThrottle = 0 TT_ApplyBackdrop(this) TT_SyncBGFrame() if not ttIsMapMarker then TT_ShowBar(UnitExists("mouseover")) end if not linesFormatted then linesFormatted = true if not ttIsMapMarker and UnitExists("mouseover") then SFrames.FloatingTooltip:FormatLines(this) end end if SFramesDB and SFramesDB.tooltipMode == "CURSOR" and not ttIsMapMarker then local x, y = GetCursorPosition() local scale = UIParent:GetEffectiveScale() if scale and scale > 0 then this:ClearAllPoints() this:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", (x / scale) + 16, (y / scale) - 16) end end end) -- OnHide: hide bg frame, reset flag and owner tracking local orig_OnHide = GameTooltip:GetScript("OnHide") GameTooltip:SetScript("OnHide", function() linesFormatted = false ttOwner = nil if GameTooltip._nanamiBGFrame then GameTooltip._nanamiBGFrame:Hide() end if orig_OnHide then orig_OnHide() end end) -- Tooltip Positioning logic if not SFrames.FloatingTooltip.anchor then local anchor = CreateFrame("Button", "SFramesTooltipAnchor", UIParent) anchor:SetWidth(180) anchor:SetHeight(60) anchor:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", -20, 100) anchor:SetFrameStrata("HIGH") anchor:EnableMouse(true) anchor:SetMovable(true) anchor:RegisterForDrag("LeftButton") anchor:SetScript("OnDragStart", function() this:StartMoving() end) anchor:SetScript("OnDragStop", function() this:StopMovingOrSizing() if SFramesDB then local _, _, _, x, y = this:GetPoint() SFramesDB.tooltipX = x SFramesDB.tooltipY = y end end) anchor:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Buttons\\WHITE8x8", edgeSize = 1, insets = {left=1, right=1, top=1, bottom=1} }) anchor:SetBackdropColor(0.08, 0.08, 0.08, 0.95) anchor:SetBackdropBorderColor(0.4, 0.8, 0.4, 1) local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF" anchor.title = anchor:CreateFontString(nil, "OVERLAY") anchor.title:SetFont(font, 13, "OUTLINE") anchor.title:SetPoint("TOPLEFT", anchor, "TOPLEFT", 8, -8) anchor.title:SetText("Tooltip 模拟位置") anchor.title:SetTextColor(1, 1, 1) anchor.desc = anchor:CreateFontString(nil, "OVERLAY") anchor.desc:SetFont(font, 11, "OUTLINE") anchor.desc:SetPoint("TOPLEFT", anchor.title, "BOTTOMLEFT", 0, -4) anchor.desc:SetText("拖拽我以调整[自定义]锚点。") anchor.desc:SetTextColor(1, 0.82, 0) anchor.desc:SetJustifyH("LEFT") anchor:Hide() SFrames.FloatingTooltip.anchor = anchor end if not SFrames.FloatingTooltip.hookedAnchor then SFrames.FloatingTooltip.hookedAnchor = true local orig_GameTooltip_SetDefaultAnchor = GameTooltip_SetDefaultAnchor function GameTooltip_SetDefaultAnchor(tooltip, parent) if SFramesDB and SFramesDB.tooltipMode == "CURSOR" then tooltip:SetOwner(parent, "ANCHOR_NONE") elseif SFramesDB and SFramesDB.tooltipMode == "CUSTOM" then tooltip:SetOwner(parent, "ANCHOR_NONE") tooltip:ClearAllPoints() tooltip:SetPoint("BOTTOMRIGHT", "SFramesTooltipAnchor", "BOTTOMRIGHT", 0, 0) else orig_GameTooltip_SetDefaultAnchor(tooltip, parent) end end end SFrames.FloatingTooltip:ApplyConfig() -- WorldMapTooltip: raw textures on a child frame (SetBackdrop is unreliable) if WorldMapTooltip and not WorldMapTooltip._nanamiBG then WorldMapTooltip._nanamiBG = true local wmtBgFrame = CreateFrame("Frame", nil, WorldMapTooltip) wmtBgFrame:SetAllPoints(WorldMapTooltip) wmtBgFrame:SetFrameLevel(math.max(0, WorldMapTooltip:GetFrameLevel())) local bg = wmtBgFrame:CreateTexture(nil, "BACKGROUND") bg:SetTexture("Interface\\Buttons\\WHITE8X8") bg:SetVertexColor(0.05, 0.05, 0.05, 1) bg:SetAllPoints(wmtBgFrame) local function WMTEdge(p1, r1, p2, r2, w, h) local t = wmtBgFrame:CreateTexture(nil, "BORDER") t:SetTexture("Interface\\Buttons\\WHITE8X8") t:SetVertexColor(0.20, 0.20, 0.20, 1) t:SetPoint(p1, WorldMapTooltip, r1) t:SetPoint(p2, WorldMapTooltip, r2) if w then t:SetWidth(w) end if h then t:SetHeight(h) end end WMTEdge("TOPLEFT","TOPLEFT","TOPRIGHT","TOPRIGHT", nil, 1) WMTEdge("BOTTOMLEFT","BOTTOMLEFT","BOTTOMRIGHT","BOTTOMRIGHT", nil, 1) WMTEdge("TOPLEFT","TOPLEFT","BOTTOMLEFT","BOTTOMLEFT", 1, nil) WMTEdge("TOPRIGHT","TOPRIGHT","BOTTOMRIGHT","BOTTOMRIGHT", 1, nil) end if SFrames.ItemCompare and SFrames.ItemCompare.HookTooltips then SFrames.ItemCompare:HookTooltips() end end function SFrames.FloatingTooltip:ApplyConfig() if SFramesDB and SFramesDB.tooltipX and SFramesDB.tooltipY and self.anchor then self.anchor:ClearAllPoints() self.anchor:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", SFramesDB.tooltipX, SFramesDB.tooltipY) end end function SFrames.FloatingTooltip:ToggleAnchor(show) if not self.anchor then return end if show then self.anchor:Show() else self.anchor:Hide() end end function SFrames.FloatingTooltip:FormatLines(tooltip) local unit = "mouseover" if not UnitExists(unit) then return end local nameLine = GameTooltipTextLeft1 if not nameLine then return end local nameText = nameLine:GetText() if nameText and string.find(nameText, "\n") then nameText = string.gsub(nameText, "\n", " ") nameLine:SetText(nameText) end -------------------------------------------------------------------------- -- Player units -------------------------------------------------------------------------- if UnitIsPlayer(unit) then local classToken = TT_GetClassToken(unit) -- Class-color the name if classToken then local r, g, b = TT_GetClassColor(classToken) if r then nameLine:SetTextColor(r, g, b) if GameTooltipStatusBar and GameTooltipStatusBar._origSetColor then GameTooltipStatusBar._origSetColor(GameTooltipStatusBar, r, g, b) end end end -- Fetch guild info for this player local ttGuild, ttRankStr, ttRankIdx if GetGuildInfo then ttGuild, ttRankStr, ttRankIdx = GetGuildInfo(unit) end -- Iterate existing lines: enhance guild line + color level local numLines = tooltip:NumLines() for i = 2, numLines do local left = getglobal("GameTooltipTextLeft" .. i) if left then local txt = left:GetText() if txt then -- Guild line — append rank if string.find(txt, "^<.*>$") then if ttRankStr and ttRankStr ~= "" then left:SetText(txt .. " - " .. ttRankStr) end left:SetTextColor(0.30, 0.90, 0.30) end -- Level line local _, _, lvlStr = string.find(txt, "^Level (%d+)") if not lvlStr then _, _, lvlStr = string.find(txt, "^等级 (%d+)") end local lvlNum = tonumber(lvlStr) if lvlNum then local lr, lg, lb = TT_DifficultyColor(lvlNum) left:SetTextColor(lr, lg, lb) end end end end ----------------------------------------------------------------------- -- Extra info lines (appended below existing content) ----------------------------------------------------------------------- -- Guild info (if player is in a guild but Blizzard didn't show it) if ttGuild and ttRankStr then local foundGuild = false for i = 2, numLines do local left = getglobal("GameTooltipTextLeft" .. i) if left then local txt = left:GetText() or "" if string.find(txt, "^<.*>") then foundGuild = true break end end end if not foundGuild then tooltip:AddLine("<" .. ttGuild .. "> - " .. ttRankStr, 0.30, 0.90, 0.30) end end -- PvP Rank (text only; |T...|t inline textures not supported in 1.12) if UnitPVPRank and GetPVPRankInfo then local rank = UnitPVPRank(unit) if rank and rank > 0 then local rankName = GetPVPRankInfo(rank) if rankName and rankName ~= "" then tooltip:AddLine("军衔: " .. rankName, 1, 0.85, 0.35) end end end -- PvP flag + faction if UnitIsPVP and UnitIsPVP(unit) then local fTag = UnitFactionGroup and UnitFactionGroup(unit) if fTag then local pvpHex = (fTag == "Horde") and "|cffff4444" or "|cff44bbff" tooltip:AddLine(pvpHex .. "PvP " .. fTag .. "|r") end end -- Turtle WoW challenge modes (defensive checks) if IsHardcore and IsHardcore(unit) then tooltip:AddLine("|cffff3333[Hardcore]|r") end if C_TurtleWoW then if C_TurtleWoW.IsHardcore and C_TurtleWoW.IsHardcore(unit) then tooltip:AddLine("|cffff3333[Hardcore]|r") end if C_TurtleWoW.IsSurvivalist and C_TurtleWoW.IsSurvivalist(unit) then tooltip:AddLine("|cff33ff33[Survivalist]|r") end if C_TurtleWoW.IsIronMan and C_TurtleWoW.IsIronMan(unit) then tooltip:AddLine("|cffaaaaaa[Iron Man]|r") end end if UnitIsTrivial and UnitIsTrivial(unit) then tooltip:AddLine("|cff888888[休闲模式]|r") end -------------------------------------------------------------------------- -- NPC / Creature units: color health bar by reaction -------------------------------------------------------------------------- else local reaction = UnitReaction(unit, "player") if reaction then local color = UnitReactionColor and UnitReactionColor[reaction] if color and GameTooltipStatusBar and GameTooltipStatusBar._origSetColor then GameTooltipStatusBar._origSetColor(GameTooltipStatusBar, color.r, color.g, color.b) end end end -------------------------------------------------------------------------- -- Target of mouseover (all units) -------------------------------------------------------------------------- local tgtUnit = unit .. "target" if UnitExists(tgtUnit) then local tgtName = UnitName(tgtUnit) if tgtName then local line if UnitIsUnit(tgtUnit, "player") then local _At = SFrames.ActiveTheme local _accentHex = "ff6699" if _At and _At.accentHex then _accentHex = _At.accentHex end line = "|cffffd700目标: |r|cff" .. _accentHex .. ">> 你 <<|r" elseif UnitIsPlayer(tgtUnit) then local hex = TT_ClassHex(TT_GetClassToken(tgtUnit)) line = "|cffffd700目标: |r" .. hex .. tgtName .. "|r" else line = "|cffffd700目标: |r|cffffffff" .. tgtName .. "|r" end tooltip:AddLine(line) end end tooltip:Show() end -------------------------------------------------------------------------------- -- Item Compare: show stat differences vs currently equipped item -------------------------------------------------------------------------------- SFrames.ItemCompare = {} local IC = SFrames.ItemCompare local IC_STAT_ORDER = { "STR","AGI","STA","INT","SPI", "CRIT","TOHIT","RANGEDCRIT", "SPELLCRIT","SPELLTOHIT", "ATTACKPOWER","RANGEDATTACKPOWER", "DMG","HEAL", "DEFENSE","DODGE","PARRY","BLOCK","BLOCKVALUE", "ARMOR", "HEALTHREG","MANAREG", "HEALTH","MANA", } local IC_STAT_NAMES = { STR="力量", AGI="敏捷", STA="耐力", INT="智力", SPI="精神", CRIT="暴击%", TOHIT="命中%", RANGEDCRIT="远程暴击%", SPELLCRIT="法术暴击%", SPELLTOHIT="法术命中%", ATTACKPOWER="攻强", RANGEDATTACKPOWER="远程攻强", DMG="法伤", HEAL="治疗", DEFENSE="防御", DODGE="闪避%", PARRY="招架%", BLOCK="格挡%", BLOCKVALUE="格挡值", ARMOR="护甲", HEALTHREG="生命/5秒", MANAREG="法力/5秒", HEALTH="生命值", MANA="法力值", } local IC_EQUIP_LOC_TO_SLOT = { INVTYPE_HEAD = 1, INVTYPE_NECK = 2, INVTYPE_SHOULDER = 3, INVTYPE_BODY = 4, INVTYPE_CHEST = 5, INVTYPE_ROBE = 5, INVTYPE_WAIST = 6, INVTYPE_LEGS = 7, INVTYPE_FEET = 8, INVTYPE_WRIST = 9, INVTYPE_HAND = 10, INVTYPE_FINGER = 11, INVTYPE_TRINKET = 13, INVTYPE_CLOAK = 15, INVTYPE_WEAPON = 16, INVTYPE_2HWEAPON = 16, INVTYPE_WEAPONMAINHAND = 16, INVTYPE_SHIELD = 17, INVTYPE_WEAPONOFFHAND = 17, INVTYPE_HOLDABLE = 17, INVTYPE_RANGED = 18, INVTYPE_RANGEDRIGHT = 18, INVTYPE_THROWN = 18, INVTYPE_RELIC = 18, INVTYPE_TABARD = 19, } local function IC_GetLib() if AceLibrary and AceLibrary.HasInstance and AceLibrary:HasInstance("ItemBonusLib-1.0") then return AceLibrary("ItemBonusLib-1.0") end return nil end local function IC_GetEquipSlot(link) if not link then return nil end local _, _, _, _, _, _, _, equipLoc = GetItemInfo(link) if not equipLoc or equipLoc == "" then return nil end return IC_EQUIP_LOC_TO_SLOT[equipLoc] end local function IC_ScanBonuses(lib, link) if not lib or not link then return nil end local ok, result = pcall(function() return lib:ScanItem(link, true) end) if ok and result then return result end return nil end local function IC_AppendCompare(tooltip, newBonuses, oldBonuses) if not newBonuses then return end local hasAny = false local lines = {} for _, key in ipairs(IC_STAT_ORDER) do local nv = newBonuses[key] or 0 local ov = (oldBonuses and oldBonuses[key]) or 0 local diff = nv - ov if diff ~= 0 then local name = IC_STAT_NAMES[key] or key local text if diff > 0 then text = "|cff00ff00+" .. diff .. " " .. name .. "|r" else text = "|cffff4444" .. diff .. " " .. name .. "|r" end table.insert(lines, text) hasAny = true end end if hasAny then tooltip:AddLine(" ") tooltip:AddLine("与当前装备对比:", 0.6, 0.8, 1) for _, l in ipairs(lines) do tooltip:AddLine(l) end tooltip:Show() end end local function IC_GetItemLevel(link) if not link or not LibItem_Level then return nil end local _, _, itemId = string.find(link, "item:(%d+)") if itemId then return LibItem_Level[tonumber(itemId)] end return nil end local function IC_AppendItemLevel(tooltip, link) if SFramesDB and SFramesDB.showItemLevel == false then return end local ilvl = IC_GetItemLevel(link) if ilvl and ilvl > 0 then tooltip:AddLine("物品等级: " .. ilvl, 1, 0.82, 0) end end -------------------------------------------------------------------------------- -- Sell Price Infrastructure -------------------------------------------------------------------------------- local function IC_GetItemIdFromLink(link) if not link then return nil end local _, _, id = string.find(link, "item:(%d+)") if id then return tonumber(id) end return nil end local function IC_CacheSellPrice(itemId, copper) if not itemId or not copper or copper <= 0 then return end if not SFramesGlobalDB then SFramesGlobalDB = {} end if not SFramesGlobalDB.sellPriceCache then SFramesGlobalDB.sellPriceCache = {} end SFramesGlobalDB.sellPriceCache[itemId] = copper end local function IC_GetSellPrice(itemId) if not itemId then return nil end if NanamiSellPriceDB then local price = NanamiSellPriceDB[itemId] if price and price > 0 then return price end end if ShaguTweaks and ShaguTweaks.SellValueDB then local price = ShaguTweaks.SellValueDB[itemId] if price and price > 0 then return price end end if SFramesGlobalDB and SFramesGlobalDB.sellPriceCache then local price = SFramesGlobalDB.sellPriceCache[itemId] if price and price > 0 then return price end end return nil end local function IC_TryGetItemInfoSellPrice(link) if not link then return nil end local r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11 = GetItemInfo(link) if r11 and type(r11) == "number" and r11 > 0 then return r11 end return nil end local function IC_ExtractLinkFromTooltipName(tooltip) if not tooltip then return nil end local left1 = _G[tooltip:GetName() .. "TextLeft1"] if not left1 then return nil end local name = left1:GetText() if not name or name == "" then return nil end if GetItemLinkByName then local link = GetItemLinkByName(name) if link then return link end end if ShaguTweaks and ShaguTweaks.GetItemLinkByName then local link = ShaguTweaks.GetItemLinkByName(name) if link then return link end end return nil end local function IC_AddSellPrice(tooltip, link, count) if not link then return end if tooltip._nanamiSellPriceAdded then return end if MerchantFrame and MerchantFrame:IsShown() then return end local itemId = IC_GetItemIdFromLink(link) local price = IC_GetSellPrice(itemId) if not price then price = IC_TryGetItemInfoSellPrice(link) if price and itemId then IC_CacheSellPrice(itemId, price) end end if price and price > 0 then count = count or 1 if count < 1 then count = 1 end tooltip._nanamiSellPriceAdded = true SetTooltipMoney(tooltip, price * count) tooltip:Show() end end -------------------------------------------------------------------------------- local function IC_EnhanceTooltip(tooltip, link, count, skipSellPrice) if not link then return end IC_AppendItemLevel(tooltip, link) if not SFramesDB or SFramesDB.itemCompare ~= false then local lib = IC_GetLib() if lib then local eslot = IC_GetEquipSlot(link) if eslot then local newB = IC_ScanBonuses(lib, link) local eqLink = GetInventoryItemLink("player", eslot) local oldB = IC_ScanBonuses(lib, eqLink) IC_AppendCompare(tooltip, newB, oldB) end end end if SFrames.GearScore and SFrames.GearScore.AddScoreToTooltip then pcall(function() SFrames.GearScore:AddScoreToTooltip(tooltip, link) end) end if not skipSellPrice then IC_AddSellPrice(tooltip, link, count) end tooltip:Show() end function IC:HookTooltips() if self.hooked then return end self.hooked = true --------------------------------------------------------------------------- -- OnHide cleanup: reset sell-price tracking flag --------------------------------------------------------------------------- local orig_OnHide_IC = GameTooltip:GetScript("OnHide") GameTooltip:SetScript("OnHide", function() this._nanamiSellPriceAdded = nil if orig_OnHide_IC then orig_OnHide_IC() end end) --------------------------------------------------------------------------- -- Passive sell-price caching: intercept SetTooltipMoney calls while -- processing a bag item at a merchant so we learn the unit price. --------------------------------------------------------------------------- local IC_PendingBagLink = nil local IC_PendingBagCount = nil local orig_SetTooltipMoney = SetTooltipMoney SetTooltipMoney = function(frame, money, a1, a2, a3) if orig_SetTooltipMoney then orig_SetTooltipMoney(frame, money, a1, a2, a3) end if IC_PendingBagLink and money and money > 0 then if frame == GameTooltip or frame == SFramesScanTooltip then local itemId = IC_GetItemIdFromLink(IC_PendingBagLink) local count = IC_PendingBagCount or 1 if count < 1 then count = 1 end if itemId then IC_CacheSellPrice(itemId, math.floor(money / count)) end end end end --------------------------------------------------------------------------- -- MERCHANT_SHOW: proactively scan all bag items to cache sell prices. --------------------------------------------------------------------------- local scanFrame = CreateFrame("Frame") scanFrame:RegisterEvent("MERCHANT_SHOW") scanFrame:SetScript("OnEvent", function() for bag = 0, 4 do local numSlots = GetContainerNumSlots(bag) or 0 for slot = 1, numSlots do local link = GetContainerItemLink(bag, slot) if link then local itemId = IC_GetItemIdFromLink(link) if itemId and not IC_GetSellPrice(itemId) then local infoPrice = IC_TryGetItemInfoSellPrice(link) if infoPrice then IC_CacheSellPrice(itemId, infoPrice) elseif SFramesScanTooltip then local _, cnt = GetContainerItemInfo(bag, slot) cnt = cnt or 1 IC_PendingBagLink = link IC_PendingBagCount = cnt pcall(function() SFramesScanTooltip:SetOwner(UIParent, "ANCHOR_NONE") SFramesScanTooltip:ClearLines() SFramesScanTooltip:SetBagItem(bag, slot) SFramesScanTooltip:Hide() end) IC_PendingBagLink = nil IC_PendingBagCount = nil end end end end end end) --------------------------------------------------------------------------- -- GameTooltip.SetBagItem --------------------------------------------------------------------------- local orig_SetBagItem = GameTooltip.SetBagItem GameTooltip.SetBagItem = function(self, bag, slot) self._nanamiSellPriceAdded = nil local link = GetContainerItemLink(bag, slot) local _, cnt = GetContainerItemInfo(bag, slot) IC_PendingBagLink = link IC_PendingBagCount = cnt or 1 local hasItem, hasCooldown, repairCost = orig_SetBagItem(self, bag, slot) IC_PendingBagLink = nil IC_PendingBagCount = nil local moneyAlreadyShown = self.hasMoney pcall(function() if link then IC_EnhanceTooltip(GameTooltip, link, cnt, moneyAlreadyShown) end end) return hasItem, hasCooldown, repairCost end --------------------------------------------------------------------------- -- GameTooltip.SetInventoryItem --------------------------------------------------------------------------- local orig_SetInvItem = GameTooltip.SetInventoryItem GameTooltip.SetInventoryItem = function(self, unit, slotId) self._nanamiSellPriceAdded = nil local hasItem, hasCooldown, repairCost = orig_SetInvItem(self, unit, slotId) local moneyAlreadyShown = self.hasMoney pcall(function() if unit == "player" and slotId then local link = GetInventoryItemLink("player", slotId) if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end end end) return hasItem, hasCooldown, repairCost end --------------------------------------------------------------------------- -- GameTooltip.SetMerchantItem --------------------------------------------------------------------------- local orig_SetMerchantItem = GameTooltip.SetMerchantItem if orig_SetMerchantItem then GameTooltip.SetMerchantItem = function(self, idx) self._nanamiSellPriceAdded = nil orig_SetMerchantItem(self, idx) pcall(function() local link = GetMerchantItemLink and GetMerchantItemLink(idx) if link then IC_EnhanceTooltip(GameTooltip, link) end end) end end --------------------------------------------------------------------------- -- GameTooltip.SetQuestItem — with fallback link extraction --------------------------------------------------------------------------- local orig_SetQuestItem = GameTooltip.SetQuestItem if orig_SetQuestItem then GameTooltip.SetQuestItem = function(self, qtype, idx) self._nanamiSellPriceAdded = nil orig_SetQuestItem(self, qtype, idx) local moneyAlreadyShown = self.hasMoney pcall(function() local link if GetQuestItemLink then link = GetQuestItemLink(qtype, idx) end if not link then link = IC_ExtractLinkFromTooltipName(GameTooltip) end if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end end) end end --------------------------------------------------------------------------- -- GameTooltip.SetQuestLogItem — with fallback link extraction --------------------------------------------------------------------------- local orig_SetQuestLogItem = GameTooltip.SetQuestLogItem if orig_SetQuestLogItem then GameTooltip.SetQuestLogItem = function(self, itype, idx) self._nanamiSellPriceAdded = nil orig_SetQuestLogItem(self, itype, idx) local moneyAlreadyShown = self.hasMoney pcall(function() local link if GetQuestLogItemLink then link = GetQuestLogItemLink(itype, idx) end if not link then link = IC_ExtractLinkFromTooltipName(GameTooltip) end if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end end) end end --------------------------------------------------------------------------- -- GameTooltip.SetLootItem --------------------------------------------------------------------------- local orig_SetLootItem = GameTooltip.SetLootItem if orig_SetLootItem then GameTooltip.SetLootItem = function(self, idx) self._nanamiSellPriceAdded = nil orig_SetLootItem(self, idx) local moneyAlreadyShown = self.hasMoney pcall(function() local link = GetLootSlotLink and GetLootSlotLink(idx) if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end end) end end --------------------------------------------------------------------------- -- GameTooltip.SetLootRollItem --------------------------------------------------------------------------- local orig_SetLootRollItem = GameTooltip.SetLootRollItem if orig_SetLootRollItem then GameTooltip.SetLootRollItem = function(self, rollId) self._nanamiSellPriceAdded = nil orig_SetLootRollItem(self, rollId) local moneyAlreadyShown = self.hasMoney pcall(function() local link = GetLootRollItemLink and GetLootRollItemLink(rollId) if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end end) end end --------------------------------------------------------------------------- -- GameTooltip.SetCraftItem --------------------------------------------------------------------------- local orig_SetCraftItem = GameTooltip.SetCraftItem if orig_SetCraftItem then GameTooltip.SetCraftItem = function(self, skill, slot) self._nanamiSellPriceAdded = nil orig_SetCraftItem(self, skill, slot) local moneyAlreadyShown = self.hasMoney pcall(function() local link = GetCraftReagentItemLink and GetCraftReagentItemLink(skill, slot) if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end end) end end --------------------------------------------------------------------------- -- GameTooltip.SetTradeSkillItem --------------------------------------------------------------------------- local orig_SetTradeSkillItem = GameTooltip.SetTradeSkillItem if orig_SetTradeSkillItem then GameTooltip.SetTradeSkillItem = function(self, skillIndex, reagentIndex) self._nanamiSellPriceAdded = nil orig_SetTradeSkillItem(self, skillIndex, reagentIndex) local moneyAlreadyShown = self.hasMoney pcall(function() local link if reagentIndex then if GetTradeSkillReagentItemLink then link = GetTradeSkillReagentItemLink(skillIndex, reagentIndex) end else if GetTradeSkillItemLink then link = GetTradeSkillItemLink(skillIndex) end end if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end end) end end --------------------------------------------------------------------------- -- GameTooltip.SetAuctionItem --------------------------------------------------------------------------- local orig_SetAuctionItem = GameTooltip.SetAuctionItem if orig_SetAuctionItem then GameTooltip.SetAuctionItem = function(self, atype, idx) self._nanamiSellPriceAdded = nil orig_SetAuctionItem(self, atype, idx) local moneyAlreadyShown = self.hasMoney pcall(function() local _, _, cnt = GetAuctionItemInfo and GetAuctionItemInfo(atype, idx) local link = GetAuctionItemLink and GetAuctionItemLink(atype, idx) if link then IC_EnhanceTooltip(GameTooltip, link, cnt, moneyAlreadyShown) end end) end end --------------------------------------------------------------------------- -- GameTooltip.SetTradePlayerItem --------------------------------------------------------------------------- local orig_SetTradePlayerItem = GameTooltip.SetTradePlayerItem if orig_SetTradePlayerItem then GameTooltip.SetTradePlayerItem = function(self, idx) self._nanamiSellPriceAdded = nil orig_SetTradePlayerItem(self, idx) local moneyAlreadyShown = self.hasMoney pcall(function() local link = GetTradePlayerItemLink and GetTradePlayerItemLink(idx) if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end end) end end --------------------------------------------------------------------------- -- GameTooltip.SetTradeTargetItem --------------------------------------------------------------------------- local orig_SetTradeTargetItem = GameTooltip.SetTradeTargetItem if orig_SetTradeTargetItem then GameTooltip.SetTradeTargetItem = function(self, idx) self._nanamiSellPriceAdded = nil orig_SetTradeTargetItem(self, idx) local moneyAlreadyShown = self.hasMoney pcall(function() local link = GetTradeTargetItemLink and GetTradeTargetItemLink(idx) if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end end) end end --------------------------------------------------------------------------- -- GameTooltip.SetInboxItem --------------------------------------------------------------------------- local orig_SetInboxItem = GameTooltip.SetInboxItem if orig_SetInboxItem then GameTooltip.SetInboxItem = function(self, mailID, attachIdx) self._nanamiSellPriceAdded = nil orig_SetInboxItem(self, mailID, attachIdx) local moneyAlreadyShown = self.hasMoney pcall(function() local link = IC_ExtractLinkFromTooltipName(GameTooltip) if link then IC_EnhanceTooltip(GameTooltip, link, nil, moneyAlreadyShown) end end) end end --------------------------------------------------------------------------- -- SetItemRef (chat item links) --------------------------------------------------------------------------- local orig_SetItemRef = SetItemRef if orig_SetItemRef then SetItemRef = function(link, text, button) orig_SetItemRef(link, text, button) if IsAltKeyDown() or IsShiftKeyDown() or IsControlKeyDown() then return end pcall(function() local _, _, itemStr = string.find(link or "", "(item:[%-?%d:]+)") if itemStr then ItemRefTooltip._nanamiSellPriceAdded = nil local itemId = IC_GetItemIdFromLink(itemStr) local price = IC_GetSellPrice(itemId) if not price then price = IC_TryGetItemInfoSellPrice(itemStr) if price and itemId then IC_CacheSellPrice(itemId, price) end end if price and price > 0 and not ItemRefTooltip.hasMoney then SetTooltipMoney(ItemRefTooltip, price) ItemRefTooltip:Show() end end end) end end end