QuestieOcto.Tooltips = QuestieOcto.Tooltips or {} local T = QuestieOcto.Tooltips -- World/target tooltip augmentation uses the already-resolved canonical node -- graph instead of searching the database on every mouse movement. This is the -- same separation used by pfQuest: map preparation establishes which quest -- relationships exist, while GameTooltip hover only performs indexed lookups. T.hoverIndex=T.hoverIndex or { unitByName={}, objectByID={}, itemByID={} } T.hoverIndexReady=T.hoverIndexReady or false T.hoverIndexPending=T.hoverIndexPending or false T.worldTooltipState=T.worldTooltipState or { signature=nil, afterLines=0 } T.initialized=T.initialized or false local function Settings() return QuestieOcto.MinimapSettings end -- Keep the stock Blizzard GameTooltip path for normal clients. Questie-Octo -- historically appended its map/minimap information to that native tooltip, -- which preserves the normal Vanilla frame sizing and appearance. -- -- pfUI is the exception: it attaches child OnShow handlers to GameTooltip and -- mutates/re-shows that frame as part of its tooltip lifecycle. For pfUI only, -- use a private GameTooltipTemplate so pin hovers cannot recurse through pfUI. -- That private tooltip is then skinned through pfUI's own backdrop helpers. local function GetPfUI() local ui=nil if getglobal then ui=getglobal("pfUI") end if not ui then ui=pfUI end if type(ui)=="table" then return ui end return nil end local function ApplyPfUISkin(tooltip) if not tooltip or tooltip.questieOctoPfUISkinned then return end local ui=GetPfUI() if type(ui)~="table" or type(ui.api)~="table" then return end if type(ui.api.CreateBackdrop)~="function" then return end local config=nil if getglobal then config=getglobal("pfUI_config") end if not config then config=pfUI_config end local alpha=nil if type(config)=="table" and type(config.tooltip)=="table" then alpha=tonumber(config.tooltip.alpha) end -- Match pfUI's own skins/blizzard/tooltips.lua path. pcall keeps pfUI an -- optional compatibility integration: a pfUI fork/API mismatch must never -- prevent Questie-Octo's tooltip from working with its default appearance. local ok=pcall(ui.api.CreateBackdrop,tooltip,nil,nil,alpha) if not ok then return end if type(ui.api.CreateBackdropShadow)=="function" then pcall(ui.api.CreateBackdropShadow,tooltip) end tooltip.questieOctoPfUISkinned=true end local function MapTooltip() -- Non-pfUI users keep the exact native Blizzard tooltip frame. This also -- means other ordinary tooltip skins/addons can style GameTooltip normally. if not GetPfUI() then return GameTooltip end if T.mapTooltip then -- Re-check until successful so addon load order cannot leave an early-created -- Questie-Octo tooltip permanently unskinned when pfUI becomes available. ApplyPfUISkin(T.mapTooltip) return T.mapTooltip end if CreateFrame then local ok,tooltip=pcall( CreateFrame, "GameTooltip", "QuestieOctoMapTooltip", UIParent, "GameTooltipTemplate" ) if ok and tooltip then if tooltip.SetClampedToScreen then tooltip:SetClampedToScreen(true) end T.mapTooltip=tooltip ApplyPfUISkin(tooltip) return tooltip end end -- Extremely defensive fallback for clients where GameTooltipTemplate cannot -- be created. ClassicAPI/pfUI themselves create this template successfully. return GameTooltip end function T:Hide(pin) local tooltip=nil if GetPfUI() then tooltip=self.mapTooltip end if not tooltip then tooltip=GameTooltip end if not tooltip then return end if not pin or not tooltip.GetOwner or tooltip:GetOwner()==pin then tooltip:Hide() end end local function QuestTitle(q) local title=tostring(q.title or ("Quest "..tostring(q.id or ""))) if Settings():Get("enableTooltipsQuestLevel") then title="["..tostring(q.level or 0).."] "..title end if Settings():Get("enableTooltipsQuestID") then title=title.." ("..tostring(q.id or 0)..")" end return title end local function FormatDropRate(rate) rate=tonumber(rate) if not rate then return nil end if rate>=10 then return string.format("%.0f",rate) end if rate>=2 then return string.format("%.1f",rate) end if rate>=0.01 then return string.format("%.2f",rate) end return string.format("%.3f",rate) end local function ItemStartAreaDropRateText(area) local minimum=nil local maximum=nil for _,source in pairs((area and area.sourceList) or {}) do local chance=tonumber(source.chance) if chance and chance>0 then if not minimum or chancemaximum then maximum=chance end end end if not minimum or not maximum then return nil end local minText=FormatDropRate(minimum) local maxText=FormatDropRate(maximum) if not minText or not maxText then return nil end if minText==maxText then return minText.."%" end return minText.."%~"..maxText.."%" end local function DifficultyColor(level,questID) -- Use the same authority as the native Quest Log. Octo/Turtle modifies the -- low-level/easy band, so reproducing stock Classic thresholds makes map -- tooltips disagree with the Quest Log (green there, gray here). if QuestieOcto.GetNativeQuestDifficultyColor then local r,g,b=QuestieOcto:GetNativeQuestDifficultyColor(level,questID) if r then return r,g,b end end return 1,1,0 end local function RoleText(role) if role=="available" or role=="itemStart" then return "(Available)" end if role=="turnin" then return "(Complete)" end return "(Active)" end local function RareRankText(rank) rank=tonumber(rank) if rank==4 then return "Rare" end if rank==2 then return "Rare Elite" end return nil end local function RespawnText(seconds) seconds=tonumber(seconds) if not seconds or seconds<=0 then return nil end seconds=math.floor(seconds) if seconds<60 then return "~"..tostring(seconds).."s" end local minutes=math.floor(seconds/60) -- Roll long respawns up into hours so 18000s reads "~5h", not "~300 min". if minutes>=60 then local hours=math.floor(minutes/60) local mins=math.mod(minutes,60) if mins==0 then return "~"..tostring(hours).."h" end return "~"..tostring(hours).."h "..tostring(mins).."m" end local secs=math.mod(seconds,60) if secs==0 then return "~"..tostring(minutes).." min" end return "~"..tostring(minutes).."m "..tostring(secs).."s" end local function SourceDisplayName(source) local name=tostring(source.name or "Creature") local rank=RareRankText(source.rank) if rank then name=name.." ["..rank.."]" end if Settings():Get("enableTooltipsNPCID") and source.id then name=name.." (NPC "..tostring(source.id)..")" end return name end local function NormalizeFirstRowFont(tooltip) -- WoW 1.12 gives a GameTooltipTemplate's first left row a larger title font. -- Copy the normal second-row font onto line 1 for item-start source lists. tooltip=tooltip or GameTooltip local name=tooltip and tooltip.GetName and tooltip:GetName() or "GameTooltip" local first=getglobal and getglobal(tostring(name).."TextLeft1") or nil local normal=getglobal and getglobal(tostring(name).."TextLeft2") or nil if first and normal and normal.GetFont and first.SetFont then local font,size,flags=normal:GetFont() if font and size then first:SetFont(font,size,flags) end end end -- Map/minimap node geometry intentionally ignores numeric objective progress -- (for example 3/10 -> 4/10) so a simple kill/loot counter does not rebuild -- thousands of pins. Tooltips must therefore resolve the current quest-log row -- at hover time instead of trusting the objective text snapshot stored on the -- node when its geometry was last published. local function LiveObjectiveText(node) local questID=tonumber(node and node.questID) local objectiveIndex=tonumber(node and node.objectiveIndex) local state=questID and QuestieOcto.QuestLog and QuestieOcto.QuestLog.active and QuestieOcto.QuestLog.active[questID] or nil if state and objectiveIndex then for _,row in pairs(state.objectives or {}) do if tonumber(row.index)==objectiveIndex then local text=row.text or row.rawText or node.objectiveText or "" -- When the row text is only the entity name (its live objective ID did -- not resolve, so LocalizeObjectiveRows left off the counter) append the -- x/y progress from the row so the tooltip still shows it. local current=tonumber(row.current) local required=tonumber(row.required) if current and required and required>0 and not string.find(text,"%d+%s*/%s*%d+") then if text~="" then text=text.." "..tostring(current).."/"..tostring(required) else text=tostring(current).."/"..tostring(required) end end return text end end end return node and node.objectiveText or nil end local function Extra(node) if node.role=="available" and node.conditionalOffer then return tostring(node.conditionalOffer) end if node.role=="itemStart" and node.itemName then local text if node.vendor then text="Sells ["..tostring(node.itemName).."]" else text="Drops ["..tostring(node.itemName).."]" end if node.chance and Settings():Get("enableTooltipDroprates") then text=text.." ("..(FormatDropRate(node.chance) or tostring(node.chance)).."%)" end return text.." - item starts quest" end if node.role=="objectiveCreature" or node.role=="objectiveObject" or node.role=="objectiveItemSource" or node.role=="objectiveArea" then local liveText=LiveObjectiveText(node) if liveText and liveText~="" then local text=liveText if node.role=="objectiveItemSource" and node.chance and Settings():Get("enableTooltipDroprates") then local rate=FormatDropRate(node.chance) if rate then text=text.." |cff999999["..rate.."%]|r" end end return text end if node.role=="objectiveItemSource" and node.itemName then local text="["..tostring(node.itemName).."]" if node.vendor then text=text.." (Vendor)" end if node.chance and Settings():Get("enableTooltipDroprates") then text=text.." ("..(FormatDropRate(node.chance) or tostring(node.chance)).."%)" end return text end return nil end return nil end local function AddSourceIDLine(pin,tooltip) tooltip=tooltip or MapTooltip() if pin.sourceKind=="creature" and pin.sourceID and Settings():Get("enableTooltipsNPCID") then tooltip:AddLine("NPC ID: "..tostring(pin.sourceID),.65,.65,.65) end end local function SortedEntries(pin) local rows={} for _,entry in pairs(pin.entries or {}) do rows[table.getn(rows)+1]=entry end table.sort(rows,function(a,b) local qa=QuestieOcto.QuestModel:Get(a.node.questID) local qb=QuestieOcto.QuestModel:Get(b.node.questID) local la=qa and qa.level or 0 local lb=qb and qb.level or 0 if la==lb then return tonumber(a.node.questID or 0)1 then title=title.." |cffaaaaaa("..tostring(table.getn(pins)).." nearby)|r" end tooltip:AddLine(title,.2,1,.35) AddSourceIDLine(pin,tooltip) AddRareSourceRespawn(pin,tooltip) AddFullNodeQuestEntries(pins,tooltip) NormalizeFirstRowFont(tooltip) tooltip:Show() end local function ShowCombinedNearbyQuestTooltip(pin,pins) local tooltip=MapTooltip() tooltip:SetOwner(pin,"ANCHOR_CURSOR") tooltip:ClearLines() local seen={} local first=true local _,near for _,near in pairs(pins) do if near.itemStartArea then -- Existing item-start area formatting stays self-contained. Nearby -- ordinary quest markers are still included below it. local area=near.itemStartArea if not first then tooltip:AddLine(" ",1,1,1) end first=false tooltip:AddLine(tostring(area.displayName or "Item-start source"),.2,1,.35) local q=QuestieOcto.QuestModel:Get(area.questID) if q then local key="itemstart:"..tostring(area.questID) if not seen[key] then seen[key]=true local r,g,b=DifficultyColor(q.level,q.id) tooltip:AddDoubleLine(QuestTitle(q),"(Available)",r,g,b,1,.82,0) end end elseif near.entries and next(near.entries) then if not first then tooltip:AddLine(" ",1,1,1) end first=false local title=QuestSourceTitle(near) if near.clusterCount and near.clusterCount>1 then title=title.." |cffaaaaaa("..tostring(near.clusterCount).." nearby spawns)|r" end tooltip:AddLine(title,.2,1,.35) AddSourceIDLine(near,tooltip) AddRareSourceRespawn(near,tooltip) AddQuestEntries(near,seen,tooltip) end end NormalizeFirstRowFont(tooltip) tooltip:Show() end local function IsQuestHoverRole(role) return role=="available" or role=="itemStart" or role=="turnin" or role=="objectiveCreature" or role=="objectiveObject" or role=="objectiveItemSource" end local function AppendIndexed(index,key,node) if key==nil then return end index[key]=index[key] or {} table.insert(index[key],node) end local function UnitNameKey(name) name=tostring(name or "") if name=="" then return nil end return string.lower(name) end function T:RebuildHoverIndex() local nextIndex={ unitByName={}, objectByID={}, itemByID={} } for _,node in pairs((QuestieOcto.Nodes and QuestieOcto.Nodes.nodes) or {}) do local questID=tonumber(node.questID) if questID and questID>0 and IsQuestHoverRole(node.role) then if node.sourceKind=="creature" and node.sourceName then AppendIndexed(nextIndex.unitByName,UnitNameKey(node.sourceName),node) elseif node.sourceKind=="gameObject" and tonumber(node.sourceID) then AppendIndexed(nextIndex.objectByID,tonumber(node.sourceID),node) end end end -- Item hover should not depend on whether a map source survived source-rate -- filtering. Index the active item objective itself, then let creature/object -- hover use the source nodes above. This also avoids one item tooltip entry -- per possible drop source. for questID,resolved in pairs((QuestieOcto.Objectives and QuestieOcto.Objectives.byQuest) or {}) do if QuestieOcto.QuestLog.active[questID] then for _,item in pairs(resolved.item or {}) do local itemID=tonumber(item.itemID) if itemID then AppendIndexed(nextIndex.itemByID,itemID,{ questID=questID, role="objectiveItemSource", itemID=itemID, itemName=item.name, objectiveIndex=item.objectiveIndex, objectiveText=item.objectiveText, current=item.current, required=item.required, objectiveComplete=item.complete and true or false, }) end end end end -- Available item-start quests likewise belong on the starter item's own -- tooltip even if the item came from a source outside our current map data. for questID,resolved in pairs((QuestieOcto.ItemStarts and QuestieOcto.ItemStarts.byQuest) or {}) do if QuestieOcto.AvailableQuests and QuestieOcto.AvailableQuests.available[questID] then for _,item in pairs(resolved.items or {}) do local itemID=tonumber(item.itemID) if itemID then AppendIndexed(nextIndex.itemByID,itemID,{ questID=questID, role="itemStart", itemID=itemID, itemName=item.name, }) end end end end self.hoverIndex=nextIndex self.hoverIndexReady=true self.hoverIndexPending=false end function T:ScheduleHoverIndex() if self.hoverIndexPending then return end self.hoverIndexPending=true QuestieOcto.Scheduler:After(0.05,function() T:RebuildHoverIndex() end,"tooltip-hover-index") end local function HoverExtra(node,subjectKind) if not node then return nil end if node.role=="itemStart" then if subjectKind=="item" then return "Item starts quest" end local text if node.vendor then text="Sells ["..tostring(node.itemName or "Quest item").."]" else text="Drops ["..tostring(node.itemName or "Quest item").."]" end if node.chance and Settings():Get("enableTooltipDroprates") then local rate=FormatDropRate(node.chance) if rate then text=text.." ("..rate.."%)" end end return text.." - item starts quest" end if node.role=="objectiveCreature" or node.role=="objectiveObject" or node.role=="objectiveItemSource" then local text=LiveObjectiveText(node) if not text or text=="" then if node.itemName then text="["..tostring(node.itemName).."]" end end -- A source-specific drop rate is useful while hovering the creature/object -- that drops the item, but it would be misleading on the item's own tooltip -- because the item can have many sources with different rates. if text and subjectKind~="item" and node.role=="objectiveItemSource" and node.chance and Settings():Get("enableTooltipDroprates") then local rate=FormatDropRate(node.chance) if rate then text=text.." |cff999999["..rate.."%]|r" end end return text end return nil end local function HoverStatusPriority(role) if role=="turnin" then return 3 end if role=="available" or role=="itemStart" then return 2 end return 1 end local function HoverStatusText(priority) if priority==3 then return "(Complete)" end if priority==2 then return "(Available)" end return "(Active)" end local function BuildHoverGroups(nodes,subjectKind) local groups={} for _,node in pairs(nodes or {}) do local questID=tonumber(node.questID) local q=questID and QuestieOcto.QuestModel:Get(questID) or nil if q then local group=groups[questID] if not group then group={ quest=q, status=0, lines={}, seenLines={}, sourceIDs={} } groups[questID]=group end local priority=HoverStatusPriority(node.role) if priority>group.status then group.status=priority end local extra=HoverExtra(node,subjectKind) if extra and extra~="" and not group.seenLines[extra] then group.seenLines[extra]=true table.insert(group.lines,extra) end if subjectKind=="unit" and tonumber(node.sourceID) then group.sourceIDs[tonumber(node.sourceID)]=true end end end local ordered={} for _,group in pairs(groups) do table.insert(ordered,group) end table.sort(ordered,function(a,b) local la=tonumber(a.quest.level) or 0 local lb=tonumber(b.quest.level) or 0 if la==lb then return tonumber(a.quest.id or 0)1 then return nil end end end return only end local function TooltipLineCount() if GameTooltip and type(GameTooltip.NumLines)=="function" then local count=tonumber(GameTooltip:NumLines()) if count then return count end end -- Vanilla fallback for replacement UIs that do not expose NumLines cleanly. local count=0 if getglobal then for i=1,128 do local left=getglobal("GameTooltipTextLeft"..tostring(i)) local right=getglobal("GameTooltipTextRight"..tostring(i)) local leftText=left and left.GetText and left:GetText() or nil local rightText=right and right.GetText and right:GetText() or nil if leftText or rightText then count=i end end end return count end local function WorldTooltipSignature(subjectKind,subjectID) return tostring(subjectKind or "")..":"..tostring(subjectID or "") end local function ShouldAppendWorldTooltip(signature) local state=T.worldTooltipState or {} local lines=TooltipLineCount() -- pfUI and other replacement interfaces may hide/show or restyle the same -- GameTooltip several times for one hover. If our previously appended block -- is still present, never append it again. If the tooltip was rebuilt/cleared, -- its line count drops below our recorded end and it is safe to append anew. if state.signature==signature and tonumber(state.afterLines or 0)>0 and lines>=tonumber(state.afterLines or 0) then return false end return true end local function MarkWorldTooltipAppended(signature) T.worldTooltipState=T.worldTooltipState or {} T.worldTooltipState.signature=signature T.worldTooltipState.afterLines=TooltipLineCount() end -- pfUI owns the global GameTooltip and performs its normal size/layout pass -- from its own OnShow lifecycle. Questie-Octo cannot safely call Show() again -- after appending world-hover quest lines because that can re-enter pfUI and -- duplicate tooltip content. Instead, when pfUI is present, resize the already -- visible GameTooltip directly from its rendered FontStrings. pfUI's backdrop -- is anchored to the tooltip edges, so it follows this size change immediately. local function ResizePfUIWorldTooltip(beforeLines,beforeWidth,beforeHeight,beforeBottomPadding) if not GetPfUI() or not GameTooltip then return end local afterLines=TooltipLineCount() if afterLines<=tonumber(beforeLines or 0) then return end local currentWidth=tonumber(GameTooltip.GetWidth and GameTooltip:GetWidth()) or tonumber(beforeWidth) or 0 local desiredWidth=math.max(currentWidth,tonumber(beforeWidth) or 0) local maxWidth=desiredWidth -- Preserve pfUI's existing width unless the new Questie rows actually need -- more room. AddDoubleLine rows need both left and right strings plus a gap; -- wrapped objective rows report their rendered line width on this client. if getglobal then for i=1,afterLines do local left=getglobal("GameTooltipTextLeft"..tostring(i)) local right=getglobal("GameTooltipTextRight"..tostring(i)) local leftText=left and left.GetText and left:GetText() or nil local rightText=right and right.GetText and right:GetText() or nil local leftWidth=0 local rightWidth=0 if leftText and left.GetStringWidth then leftWidth=tonumber(left:GetStringWidth()) or 0 end if rightText and right.GetStringWidth then rightWidth=tonumber(right:GetStringWidth()) or 0 end local rowWidth=leftWidth if rightText and rightText~="" then rowWidth=leftWidth+rightWidth+18 end if rowWidth+20>maxWidth then maxWidth=rowWidth+20 end end end -- Keep world-hover tooltips readable without allowing a very long localized -- quest string to turn them into a screen-wide panel. Objective text already -- wraps, so 420px is a presentation ceiling rather than a data truncation. desiredWidth=math.min(math.max(desiredWidth,maxWidth),420) if GameTooltip.SetWidth and desiredWidth>currentWidth+0.5 then GameTooltip:SetWidth(desiredWidth) end local tooltipTop=GameTooltip.GetTop and tonumber(GameTooltip:GetTop()) or nil local lowestBottom=nil if getglobal then for i=1,afterLines do local left=getglobal("GameTooltipTextLeft"..tostring(i)) local right=getglobal("GameTooltipTextRight"..tostring(i)) local leftText=left and left.GetText and left:GetText() or nil local rightText=right and right.GetText and right:GetText() or nil if leftText and left.GetBottom then local bottom=tonumber(left:GetBottom()) if bottom and (not lowestBottom or bottom24 then padding=24 end desiredHeight=tooltipTop-lowestBottom+padding else -- Defensive Vanilla fallback if a replacement client does not expose -- FontString screen coordinates. This still encloses every appended row. local added=afterLines-tonumber(beforeLines or 0) desiredHeight=math.max(currentHeight,(tonumber(beforeHeight) or currentHeight)+(added*14)) end if GameTooltip.SetHeight and desiredHeight>currentHeight+0.5 then GameTooltip:SetHeight(desiredHeight) end end -- pfUI world/unit tooltips often start intentionally compact. Measure the -- Questie quest header before appending it so the single-row pfUI compatibility -- layout has enough room in normal cases; very long localized text can still -- wrap naturally. Native clients continue to use Blizzard AddDoubleLine rows. local function PfUIWorldTooltipTextWidth(text) if not GetPfUI() or not UIParent then return 0 end if not T.pfUIWorldMeasureText then local measure=UIParent:CreateFontString(nil,"ARTWORK","GameTooltipText") if not measure then return 0 end measure:Hide() T.pfUIWorldMeasureText=measure end local measure=T.pfUIWorldMeasureText measure:SetText(tostring(text or "")) if measure.GetStringWidth then return tonumber(measure:GetStringWidth()) or 0 end return 0 end local function PreSizePfUIWorldTooltip(groups) if not GetPfUI() or not GameTooltip or not GameTooltip.SetWidth then return end local currentWidth=tonumber(GameTooltip.GetWidth and GameTooltip:GetWidth()) or 0 local desiredWidth=currentWidth for _,group in pairs(groups or {}) do local q=group.quest local leftWidth=PfUIWorldTooltipTextWidth(QuestTitle(q)) local rightWidth=PfUIWorldTooltipTextWidth(HoverStatusText(group.status)) local rowWidth=leftWidth+rightWidth+38 if rowWidth>desiredWidth then desiredWidth=rowWidth end end -- Keep the same presentation ceiling used by the post-append resize pass. -- This establishes a comfortable width before pfUI's single-row quest header -- is added; objective text remains wrapped and no quest information is lost. if desiredWidth>420 then desiredWidth=420 end if desiredWidth>currentWidth+0.5 then GameTooltip:SetWidth(desiredWidth) end end local function AddWorldHoverEntries(nodes,subjectKind,subjectID,signature) local groups=BuildHoverGroups(nodes,subjectKind) if table.getn(groups)==0 then return false end local beforeLines=TooltipLineCount() local beforeWidth=GameTooltip and GameTooltip.GetWidth and tonumber(GameTooltip:GetWidth()) or 0 local beforeHeight=GameTooltip and GameTooltip.GetHeight and tonumber(GameTooltip:GetHeight()) or 0 local beforeBottomPadding=nil -- pfUI is widened before Questie rows are added so its single-row compatibility -- header normally fits without wrapping; the post-pass still encloses all rows. PreSizePfUIWorldTooltip(groups) if GetPfUI() and GameTooltip and GameTooltip.GetBottom and getglobal then local tooltipBottom=tonumber(GameTooltip:GetBottom()) local lowestBottom=nil for i=1,beforeLines do local left=getglobal("GameTooltipTextLeft"..tostring(i)) local right=getglobal("GameTooltipTextRight"..tostring(i)) local leftText=left and left.GetText and left:GetText() or nil local rightText=right and right.GetText and right:GetText() or nil if leftText and left.GetBottom then local bottom=tonumber(left:GetBottom()) if bottom and (not lowestBottom or bottom1 then ShowCombinedNearbyFullNodeTooltip(pin,related) return end elseif table.getn(nearby)>1 then -- Clustered/item-start presentation keeps its established behavior. ShowCombinedNearbyQuestTooltip(pin,nearby) return end end tooltip:SetOwner(pin,"ANCHOR_CURSOR") tooltip:ClearLines() if pin.itemStartArea then local area=pin.itemStartArea if area.zoneWideRare then -- A zone-wide representative exists specifically to make enormous -- world-drop source sets readable. Do not undo that simplification by -- dumping every represented creature into the tooltip (Pendant of -- Myzrael can have dozens of source types in a single zone). Keep the -- full source list in the data and present only a compact aggregate. local sourceCount=table.getn(area.sourceList or {}) local spawnCount=tonumber(area.n) or 0 tooltip:SetText("Rare item-start sources",.2,1,.35) tooltip:AddLine( tostring(sourceCount).." creature types, "..tostring(spawnCount).." zone spawns", .65,.65,.65 ) else -- Ordinary clustered item-start areas still list their nearby sources. -- Use AddDoubleLine with an empty right column for every source row so -- the first source does not inherit the tooltip's title-sized font. for _,source in pairs(area.sourceList or {}) do local sourceText=SourceDisplayName(source).." ("..tostring(source.count).." nearby spawns)" tooltip:AddDoubleLine( sourceText, "", .2,1,.35, .2,1,.35 ) local rank=RareRankText(source.rank) local respawn=rank and RespawnText(source.respawnSeconds) or nil if respawn then local rareTag=RareRankText(source.rank) or "Rare" tooltip:AddLine("["..rareTag.."] Respawn: "..respawn,.75,.75,.75) end end end local q=QuestieOcto.QuestModel:Get(area.questID) if q then local r,g,b=DifficultyColor(q.level,q.id) tooltip:AddDoubleLine( QuestTitle(q), "(Available)", r,g,b, 1,.82,0 ) end local dropRateText=ItemStartAreaDropRateText(area) local itemLabel=tostring(area.itemName or ("Item "..tostring(area.itemID))) if Settings():Get("enableTooltipsItemID") and area.itemID then itemLabel=itemLabel.." (Item "..tostring(area.itemID)..")" end local line="Drops ["..itemLabel.."]" if dropRateText and Settings():Get("enableTooltipDroprates") then line=line.." ("..dropRateText..")" end tooltip:AddLine(line.." - item starts quest",.82,.82,.82,true) if not area.zoneWideRare then NormalizeFirstRowFont(tooltip) end tooltip:Show() return end if not pin.entries then return end local title=QuestSourceTitle(pin) if pin.clusterCount and pin.clusterCount>1 then title=title.." |cffaaaaaa("..tostring(pin.clusterCount).." nearby spawns)|r" end tooltip:SetText(title,.2,1,.35) AddSourceIDLine(pin,tooltip) AddRareSourceRespawn(pin,tooltip) if pin.fullNode then AddFullNodeQuestEntries({pin},tooltip) else AddQuestEntries(pin,{},tooltip) end tooltip:Show() end