Files

929 lines
32 KiB
Lua

-- Initialize all static variables
local loc = GetLocale()
local dbs = { "items", "quests", "quests-itemreq", "objects", "units", "zones", "professions", "areatrigger", "refloot" }
local noloc = { "items", "quests", "objects", "units" }
-- Patch databases to merge TurtleWoW data
local function patchtable(base, diff)
for k, v in pairs(diff) do
if type(v) == "string" and v == "_" then
base[k] = nil
else
base[k] = v
end
end
end
-- Detect a typo from old clients and re-apply the typo to the zones table
-- This is a workaround which is required until all clients are updated
for id, name in pairs({GetMapZones(2)}) do
if name == "Northwind " then
pfDB["zones"]["enUS-turtle"][5581] = "Northwind "
end
end
local loc_core, loc_update
for _, db in pairs(dbs) do
if pfDB[db]["data-turtle"] then
patchtable(pfDB[db]["data"], pfDB[db]["data-turtle"])
end
for loc, _ in pairs(pfDB.locales) do
if pfDB[db][loc] and pfDB[db][loc.."-turtle"] then
loc_update = pfDB[db][loc.."-turtle"] or pfDB[db]["enUS-turtle"]
patchtable(pfDB[db][loc], loc_update)
end
end
end
loc_core = pfDB["professions"][loc] or pfDB["professions"]["enUS"]
loc_update = pfDB["professions"][loc.."-turtle"] or pfDB["professions"]["enUS-turtle"]
if loc_update then patchtable(loc_core, loc_update) end
if pfDB["minimap-turtle"] then patchtable(pfDB["minimap"], pfDB["minimap-turtle"]) end
if pfDB["meta-turtle"] then patchtable(pfDB["meta"], pfDB["meta-turtle"]) end
-- The legacy SQL importer represented some reference-loot pointers as though
-- the reference ID itself were an item drop. Reference IDs often overlap a
-- real item ID, so this produces impossible drops in the loot panel (for
-- example, Lost Soul -> Onyxian Drake). A leaked pointer has an exact copy of
-- its reference pool's source list; mark only those exact copies for filtering.
local function SameSourceSet(first, second)
if not first or not second then return false end
local count = 0
for id in pairs(first) do
count = count + 1
if not second[id] then return false end
end
if count == 0 then return false end
for id in pairs(second) do
if not first[id] then return false end
end
return true
end
local function IsReferenceToken(item, reference)
local hasSources = false
for _, sourceType in pairs({ "U", "O" }) do
if item[sourceType] or reference[sourceType] then
if not SameSourceSet(item[sourceType], reference[sourceType]) then
return false
end
hasSources = true
end
end
return hasSources
end
for id, reference in pairs(pfDB["refloot"]["data"]) do
local item = pfDB["items"]["data"][id]
if item and IsReferenceToken(item, reference) then
item["reference-token"] = true
end
end
-- Detect german client patch and switch some databases
if TURTLE_DE_PATCH then
pfDB["zones"]["loc"] = pfDB["zones"]["deDE"] or pfDB["zones"]["enUS"]
pfDB["professions"]["loc"] = pfDB["professions"]["deDE"] or pfDB["professions"]["enUS"]
end
-- Update bitmasks to include custom races
if pfDB.bitraces then
pfDB.bitraces[256] = "Goblin"
pfDB.bitraces[512] = "BloodElf"
end
-- Use turtle-wow database url
pfQuest.dburl = "https://database.turtle-wow.org/?quest="
-- Disable Minimap in custom dungeon maps
function pfMap:HasMinimap(map_id)
-- disable dungeon minimap
local has_minimap = not IsInInstance()
-- enable dungeon minimap if continent is less then 3 (e.g AV)
if IsInInstance() and GetCurrentMapContinent() < 3 then
has_minimap = true
end
return has_minimap
end
-- Reload all pfQuest internal database shortcuts
pfDatabase:Reload()
local function strsplit(delimiter, subject)
if not subject then return nil end
local delimiter, fields = delimiter or ":", {}
local pattern = string.format("([^%s]+)", delimiter)
string.gsub(subject, pattern, function(c) fields[table.getn(fields)+1] = c end)
return unpack(fields)
end
-- Complete quest id including all pre quests
local function complete(history, qid)
-- ignore empty or broken questid
if not qid or not tonumber(qid) then return end
-- mark quest as complete
local time = pfQuest_history[qid] and pfQuest_history[qid][1] or 0
local level = pfQuest_history[qid] and pfQuest_history[qid][2] or 0
history[qid] = { time, level }
-- complete all quests that are closed by the selcted one
local close = pfDB["quests"]["data"][qid] and pfDB["quests"]["data"][qid]["close"]
if close then
for _, qid in pairs(close) do
if not history[qid] then complete(history, qid) end
end
end
-- make sure all prequests are marked as done aswell
local prequests = pfDB["quests"]["data"][qid] and pfDB["quests"]["data"][qid]["pre"]
if prequests then
for _, qid in pairs(prequests) do
if not history[qid] then complete(history, qid) end
end
end
end
-- Temporary workaround for a faction group translation error
-- Add function to query for quest completion
local query = CreateFrame("Frame")
query:Hide()
query:SetScript("OnEvent", function()
if arg1 == "TWQUEST" then
for _, qid in pairs({strsplit(" ", arg2)}) do
complete(this.history, tonumber(qid))
end
end
end)
query:SetScript("OnShow", function()
this.history = {}
this.time = GetTime()
this:RegisterEvent("CHAT_MSG_ADDON")
SendChatMessage(".queststatus", "GUILD")
end)
query:SetScript("OnHide", function()
this:UnregisterEvent("CHAT_MSG_ADDON")
local count = 0
for qid in pairs(this.history) do count = count + 1 end
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest|r: A total of " .. count .. " quests have been marked as completed.")
pfQuest_history = this.history
this.history = nil
pfQuest:ResetAll()
end)
query:SetScript("OnUpdate", function()
if GetTime() > this.time + 3 then this:Hide() end
end)
function pfDatabase:QueryServer()
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest|r: Receiving quest data from server...")
query:Show()
end
-- Automatically clear quest cache if new turtle quests have been found
local updatecheck = CreateFrame("Frame")
updatecheck:RegisterEvent("PLAYER_ENTERING_WORLD")
updatecheck:SetScript("OnEvent", function()
if pfDB["quests"]["data-turtle"] then
-- count all known turtle-wow quests
local count = 0
for k, v in pairs(pfDB["quests"]["data-turtle"]) do
count = count + 1
end
pfQuest:Debug("TurtleWoW loaded with |cff33ffcc" .. count .. "|r quests.")
-- check if the last count differs to the current amount of quests
if not pfQuest_turtlecount or pfQuest_turtlecount ~= count then
-- remove quest cache to force reinitialisation of all quests.
pfQuest:Debug("New quests found. Reloading |cff33ffccCache|r")
pfQuest_questcache = {}
end
-- write current count to the saved variable
pfQuest_turtlecount = count
end
end)
local function GetGrayLevel(charLevel)
if charLevel <= 5 then
return 0
elseif charLevel <= 49 then
return charLevel - math.floor(charLevel / 10) - 5
elseif charLevel == 50 then
return 40
elseif charLevel <= 59 then
return charLevel - math.floor(charLevel / 5) - 1
else
return charLevel - 9
end
end
local RANK_INFO = {
["1"] = { text = "Elite", r = 1, g = 0.5, b = 0 },
["2"] = { text = "Rare Elite", r = 1, g = 0.42, b = 0.71 },
["3"] = { text = "Boss", r = 1, g = 0, b = 0 },
["4"] = { text = "Rare", r = 1, g = 1, b = 0 },
}
local function AddRankLine(tooltip, spawnid, spawntype)
-- Unit and object IDs share the same number space. Looking up an object ID
-- in the unit table can therefore show an unrelated creature rank.
if not spawnid or spawntype ~= pfQuest_Loc["Unit"] then return end
local units = pfDB["units"] and pfDB["units"]["data"]
local unit = units and units[spawnid]
local info = unit and unit["rnk"] and RANK_INFO[tostring(unit["rnk"])]
if not info then return end
tooltip:AddDoubleLine((pfQuest_Loc["Rank"] or "Rank") .. ":", info.text, .8, .8, .8, info.r, info.g, info.b)
end
-- A single map pin can represent several creatures at the same coordinate.
-- Keep the pin compact, but make every represented creature visible in its
-- tooltip so players know the location is shared rather than incomplete.
local function AddSharedSpawnLine(tooltip, sharedspawns, primary)
if not sharedspawns then return end
local names = {}
for name in pairs(sharedspawns) do
if name and name ~= primary then
table.insert(names, name)
end
end
if table.getn(names) == 0 then return end
table.sort(names)
tooltip:AddLine("Shared with: " .. table.concat(names, ", "), .7, .7, .7, true)
end
-- Helper to gather item drop source info for a quest-starting item
local function GetItemDropSources(item, items, units, objects, refloot)
local drop_sources = {}
local sources_with_levels = {}
if items[item]["U"] then
for unit, chance in pairs(items[item]["U"]) do
local unit_name = pfDB["units"]["loc"][unit]
if unit_name and not drop_sources[unit_name] then
drop_sources[unit_name] = true
local unit_level = units[unit] and units[unit]["lvl"] or "?"
sources_with_levels[unit_name] = {level = unit_level, chance = chance}
end
end
end
if items[item]["O"] then
for object, chance in pairs(items[item]["O"]) do
local obj_name = pfDB["objects"]["loc"][object]
if obj_name and not drop_sources[obj_name] then
drop_sources[obj_name] = true
sources_with_levels[obj_name] = {level = "Object", chance = chance}
end
end
end
if items[item]["R"] then
for ref, chance in pairs(items[item]["R"]) do
if refloot[ref] then
if refloot[ref]["U"] then
for unit in pairs(refloot[ref]["U"]) do
local unit_name = pfDB["units"]["loc"][unit]
if unit_name and not drop_sources[unit_name] then
drop_sources[unit_name] = true
local unit_level = units[unit] and units[unit]["lvl"] or "?"
sources_with_levels[unit_name] = {level = unit_level, chance = chance}
end
end
end
if refloot[ref]["O"] then
for object in pairs(refloot[ref]["O"]) do
local obj_name = pfDB["objects"]["loc"][object]
if obj_name and not drop_sources[obj_name] then
drop_sources[obj_name] = true
sources_with_levels[obj_name] = {level = "Object", chance = chance}
end
end
end
end
end
end
-- Build display text (up to 3 sources)
local sources_text = ""
local source_count = 0
local display_count = 0
for _ in pairs(drop_sources) do
source_count = source_count + 1
end
for source_name in pairs(drop_sources) do
display_count = display_count + 1
if display_count > 1 then sources_text = sources_text .. ", " end
sources_text = sources_text .. source_name
if display_count >= 3 then
if source_count > 3 then
sources_text = sources_text .. " (+" .. (source_count - 3) .. " more)"
end
break
end
end
return drop_sources, sources_with_levels, sources_text
end
-- Helper to find the best map source (most spawn points) for an item
local function GetBestItemSource(item, items, units, objects, refloot)
local best_source = nil
local best_count = 0
if items[item]["U"] then
for unit, chance in pairs(items[item]["U"]) do
if units[unit] and units[unit]["coords"] then
local count = table.getn(units[unit]["coords"])
if count > best_count then
best_count = count
best_source = {type = "unit", id = unit}
end
end
end
end
if items[item]["O"] then
for object, chance in pairs(items[item]["O"]) do
if objects[object] and objects[object]["coords"] then
local count = table.getn(objects[object]["coords"])
if count > best_count then
best_count = count
best_source = {type = "object", id = object}
end
end
end
end
if items[item]["R"] then
for ref, chance in pairs(items[item]["R"]) do
if refloot[ref] then
if refloot[ref]["U"] then
for unit in pairs(refloot[ref]["U"]) do
if units[unit] and units[unit]["coords"] then
local count = table.getn(units[unit]["coords"])
if count > best_count then
best_count = count
best_source = {type = "unit", id = unit}
end
end
end
end
if refloot[ref]["O"] then
for object in pairs(refloot[ref]["O"]) do
if objects[object] and objects[object]["coords"] then
local count = table.getn(objects[object]["coords"])
if count > best_count then
best_count = count
best_source = {type = "object", id = object}
end
end
end
end
end
end
end
return best_source
end
-- Helper to add item drop map nodes for a given quest/item
local function AddItemDropNodes(id, item, meta, maps, quests, items, units, objects, refloot, sources_text, sources_with_levels)
local best_source = GetBestItemSource(item, items, units, objects, refloot)
if not best_source then return maps end
local coords_table = best_source.type == "unit" and units[best_source.id]["coords"] or objects[best_source.id]["coords"]
local zones_coords = {}
for _, data in pairs(coords_table) do
local x, y, zone = unpack(data)
if zone > 0 and not zones_coords[zone] then
zones_coords[zone] = {x, y}
end
end
for zone, coords in pairs(zones_coords) do
local item_meta = {}
for k, v in pairs(meta or {}) do item_meta[k] = v end
item_meta["QTYPE"] = "ITEM_START"
item_meta["layer"] = 4
item_meta["texture"] = pfQuestConfig.path.."\\img\\available"
local plevel = UnitLevel("player")
if quests[id]["min"] and quests[id]["min"] > plevel then
item_meta["vertex"] = { 1, .6, .6 }
item_meta["layer"] = 2
elseif quests[id]["lvl"] and quests[id]["lvl"] <= GetGrayLevel(plevel) then
item_meta["vertex"] = { 1, 1, 1 }
item_meta["layer"] = 2
elseif quests[id]["event"] then
item_meta["vertex"] = { .2, .8, 1 }
item_meta["layer"] = 2
end
if pfDB.quests.repeatable and pfDB.quests.repeatable[id] then
item_meta["vertex"] = { .2, .8, 1 }
item_meta["layer"] = 2
end
item_meta["spawn"] = pfDB["items"]["loc"][item] or UNKNOWN
item_meta["spawnid"] = item
item_meta["item"] = pfDB["items"]["loc"][item]
item_meta["dropsources"] = sources_text
item_meta["dropsources_levels"] = sources_with_levels
item_meta["title"] = item_meta["quest"] or item_meta["spawn"]
item_meta["zone"] = zone
item_meta["x"] = coords[1]
item_meta["y"] = coords[2]
item_meta["level"] = pfQuest_Loc["N/A"]
item_meta["spawntype"] = "Item Drop"
item_meta["respawn"] = pfQuest_Loc["N/A"]
item_meta["qlvl"] = quests[id]["lvl"]
item_meta["qmin"] = quests[id]["min"]
item_meta["description"] = pfDatabase:BuildQuestDescription(item_meta)
maps = maps or {}
maps[zone] = maps[zone] and maps[zone] + 1 or 1
pfMap:AddNode(item_meta)
end
return maps
end
-- Item Drop System: Override SearchQuestID to show item-start quests for current quest givers
local originalSearchQuestID = pfDatabase.SearchQuestID
pfDatabase.SearchQuestID = function(self, id, meta, maps)
maps = originalSearchQuestID(self, id, meta, maps)
local quests = pfDB["quests"]["data"]
local items = pfDB["items"]["data"]
local units = pfDB["units"]["data"]
local objects = pfDB["objects"]["data"]
local refloot = pfDB["refloot"]["data"]
if not quests[id] then return maps end
if pfQuest_config["currentquestgivers"] == "1" then
if quests[id]["start"] and not meta["qlogid"] then
if quests[id]["start"]["I"] then
for _, item in pairs(quests[id]["start"]["I"]) do
if items[item] then
local _, sources_with_levels, sources_text = GetItemDropSources(item, items, units, objects, refloot)
maps = AddItemDropNodes(id, item, meta, maps, quests, items, units, objects, refloot, sources_text, sources_with_levels)
end
end
end
end
end
return maps
end
local function ItemDropQuestFilter(id, plevel, pclass, prace)
local quests = pfDB["quests"]["data"]
-- hide active quest
if pfQuest.questlog[id] then return end
local repeatable = pfDB.quests.repeatable and pfDB.quests.repeatable[id]
if repeatable and pfQuest_config["showrepeatable"] ~= "1" then return end
-- Repeatable quests remain available after a prior turn-in.
if pfQuest_history[id] and not repeatable then return end
-- hide broken quests without names
if not pfDB.quests.loc[id] or not pfDB.quests.loc[id].T then return end
local quest = quests[id]
if not quest then return end
-- hide missing pre-quests
if quest["pre"] then
local one_complete = nil
for _, prequest in pairs(quest["pre"]) do
if pfQuest_history[prequest] then
one_complete = true
end
end
if not one_complete then return end
end
-- hide non-available quests for your race
if quest["race"] and not ( bit.band(quest["race"], prace) == prace ) then return end
-- hide non-available quests for your class
if quest["class"] and not ( bit.band(quest["class"], pclass) == pclass ) then return end
-- hide non-available quests for your profession
if quest["skill"] and not pfDatabase:GetPlayerSkill(quest["skill"]) then return end
local levelRange = pfQuest_config["questpinlevelrange"] or "off"
if levelRange == "all" then levelRange = "off" end
if levelRange ~= "off" and quest["lvl"] then
local color = pfQuestCompat.GetDifficultyColor(tonumber(quest["lvl"]))
local rank
if color.r > .9 and color.g < .15 then
rank = 5
elseif color.r > .9 and color.g < .9 then
rank = 4
elseif color.r > .9 then
rank = 3
elseif color.g > color.r then
rank = 2
else
rank = 1
end
local threshold = ({ red = 5, orange = 4, yellow = 3, green = 2, gray = 1 })[levelRange]
local direction = pfQuest_config["questpinleveldirection"] == "higher" and "higher" or "lower"
if threshold and direction == "lower" and rank > threshold then return end
if threshold and direction == "higher" and rank < threshold then return end
elseif quest["min"] and quest["min"] > plevel + (pfQuest_config["showhighlevel"] == "1" and 3 or 0) then
-- Match base pfQuest: the extra three required levels are optional.
return
end
-- Item-start quests are added after base SearchQuests and therefore must
-- enforce the normal low-level preference themselves. Keep this setting
-- authoritative in both optional Level Range directions, matching base
-- quest-giver filtering.
if quest["lvl"] and quest["lvl"] < plevel - 4 and pfQuest_config["showlowlevel"] == "0" then
return
end
return true
end
local originalSearchQuests = pfDatabase.SearchQuests
pfDatabase.SearchQuests = function(self, meta, maps)
maps = originalSearchQuests(self, meta, maps)
local quests = pfDB["quests"]["data"]
local items = pfDB["items"]["data"]
local units = pfDB["units"]["data"]
local objects = pfDB["objects"]["data"]
local refloot = pfDB["refloot"]["data"]
local plevel = UnitLevel("player")
local pfaction = UnitFactionGroup("player")
pfaction = pfaction == "Horde" and "H" or pfaction == "Alliance" and "A" or "GM"
local _, race = UnitRace("player")
local prace = pfDatabase:GetBitByRace(race)
local _, class = UnitClass("player")
local pclass = pfDatabase:GetBitByClass(class)
for id in pairs(quests) do
if quests[id]["start"] and quests[id]["start"]["I"] and ItemDropQuestFilter(id, plevel, pclass, prace) then
-- Additional faction check for quest enders
local validFaction = true
if quests[id]["end"] and quests[id]["end"]["U"] then
validFaction = false
for _, unit in pairs(quests[id]["end"]["U"]) do
local unitData = units[unit]
if pfDatabase:IsFriendly(unit) or (unitData and not unitData["fac"]) then
validFaction = true
break
end
end
end
if validFaction then
for _, item in pairs(quests[id]["start"]["I"]) do
if items[item] then
local _, sources_with_levels, sources_text = GetItemDropSources(item, items, units, objects, refloot)
local quest_meta = {}
for k, v in pairs(meta or {}) do quest_meta[k] = v end
quest_meta["quest"] = pfDB["quests"]["loc"][id] and pfDB["quests"]["loc"][id].T or UNKNOWN
quest_meta["questid"] = id
maps = AddItemDropNodes(id, item, quest_meta, maps, quests, items, units, objects, refloot, sources_text, sources_with_levels)
end
end
end
end
end
return maps
end
-- Override BuildQuestDescription to handle ITEM_START quest type
local originalBuildQuestDescription = pfDatabase.BuildQuestDescription
pfDatabase.BuildQuestDescription = function(self, meta)
if meta and meta.QTYPE == "ITEM_START" then
if not meta.title or not meta.quest then return meta.description end
if meta.dropsources and meta.dropsources ~= "" then
return string.format("Loot |cff33ffcc[%s]|r from |cff33ffcc%s|r to obtain |cff66ff66[!]|cff33ffcc %s|r", (meta.spawn or UNKNOWN), meta.dropsources, (meta.quest or UNKNOWN))
else
return string.format("Loot |cff33ffcc[%s]|r to obtain |cff66ff66[!]|cff33ffcc %s|r", (meta.spawn or UNKNOWN), (meta.quest or UNKNOWN))
end
end
return originalBuildQuestDescription(self, meta)
end
-- Extend QuestFilter with a few title-based quest hide options (settings
-- added in pfQuest-worldmap.lua's "Quest Filters" section)
local originalQuestFilter = pfDatabase.QuestFilter
pfDatabase.QuestFilter = function(self, id, plevel, pclass, prace)
if not originalQuestFilter(self, id, plevel, pclass, prace) then
return
end
local title = pfDB.quests.loc[id] and pfDB.quests.loc[id].T
if title then
-- hide PvP/battleground quests
if pfQuest_config["hidePvPQuests"] == "1" and not string.find(title, "Alteraci Shilling") and (
string.find(title, "Warsong") or
string.find(title, "Arathi") or
string.find(title, "Alterac") or
string.find(title, "Battleground") or
string.find(title, "Call to Skirmish")
) then
return
end
-- hide cloth donation quests
if pfQuest_config["hideDonationQuests"] == "1" and (
string.find(title, "A Donation of Wool") or
string.find(title, "A Donation of Silk") or
string.find(title, "A Donation of Mageweave") or
string.find(title, "A Donation of Runecloth") or
string.find(title, "Additional Runecloth")
) then
return
end
-- hide chicken quests
if pfQuest_config["hideChickenQuests"] == "1" and string.find(title, "CLUCK!") then
return
end
-- hide Felwood corrupted flower quests
if pfQuest_config["hideFelwoodFlowers"] == "1" and (
title == "Corrupted Windblossom" or
title == "Corrupted Whipper Root" or
title == "Corrupted Songflower" or
title == "Corrupted Night Dragon"
) then
return
end
end
return true
end
-- Override NodeEnter to show custom tooltips for ITEM_START nodes
local originalNodeEnter = pfMap.NodeEnter
local function AddConfiguredClickModifier(text, tooltip)
if pfQuest_config["continentClickThrough"] == "1" and tooltip == WorldMapTooltip then
text = string.gsub(text, "<Shift>%-Click", "<Ctrl>%-<Shift>%-Click")
text = string.gsub(text, "Remove Nodes", "Hide Nodes")
text = string.gsub(text, "<Alt>%-Click", "<Ctrl>%-<Alt>%-Click")
text = string.gsub(text, "Alt%-Click", "Ctrl%-Alt%-Click")
text = string.gsub(text, "^Click", "<Ctrl>%-Click")
end
return text
end
local function AddWorldMapHideHint(tooltip)
if pfQuest_config["continentClickThrough"] == "1" and tooltip == WorldMapTooltip then
tooltip:AddLine(pfQuest_Loc["<Ctrl>-<Shift>-Click To Hide Nodes"], .6, .6, .6)
end
end
pfMap.NodeEnter = function()
if not this or not this.node then
if originalNodeEnter then originalNodeEnter() end
return
end
if pfMap.BeginNodeTooltip then pfMap:BeginNodeTooltip(this) end
local hasItemStart = false
local itemStartMeta = nil
for title, meta in pairs(this.node) do
if meta.QTYPE == "ITEM_START" and meta.dropsources_levels then
hasItemStart = true
itemStartMeta = meta
break
end
end
if hasItemStart and itemStartMeta then
local tooltip = this.worldmap and WorldMapTooltip or GameTooltip
tooltip:SetOwner(this, "ANCHOR_LEFT")
this.spawn = this.spawn or UNKNOWN
tooltip:SetText(this.spawn..(pfQuest_config.showids == "1" and " |cffcccccc("..this.spawnid..")|r" or ""), .3, 1, .8)
tooltip:AddDoubleLine(pfQuest_Loc["Type"] .. ":", (this.spawntype or UNKNOWN), .8,.8,.8, 1,1,1)
AddRankLine(tooltip, this.spawnid, this.spawntype)
AddSharedSpawnLine(tooltip, this.sharedspawns, this.spawn)
if itemStartMeta.dropsources_levels then
tooltip:AddLine(" ")
tooltip:AddLine("Drops from:", .8,.8,.8)
local sorted_sources = {}
for source_name, data in pairs(itemStartMeta.dropsources_levels) do
table.insert(sorted_sources, {name = source_name, level = data.level, chance = data.chance or 0})
end
table.sort(sorted_sources, function(a, b)
return a.chance > b.chance
end)
local count = 0
for _, source in ipairs(sorted_sources) do
count = count + 1
if count <= 5 then
tooltip:AddLine(" " .. source.name .. " (" .. source.level .. ") - " .. source.chance .. "%", 1,1,1)
end
end
if table.getn(sorted_sources) > 5 then
tooltip:AddLine(" (+" .. (table.getn(sorted_sources) - 5) .. " more)", .7,.7,.7)
end
end
tooltip:AddLine(" ")
for title, meta in pairs(this.node) do
pfMap:ShowTooltip(meta, tooltip)
end
if pfQuest_config["tooltiphelp"] == "1" then
local text = string.gsub(pfQuest_Loc["Use <Shift>-Click To Mark Quest As Done"], "^Use ", "")
text = AddConfiguredClickModifier(text, tooltip)
tooltip:AddLine(text, .6, .6, .6)
if tooltip == GameTooltip then
tooltip:AddLine(pfQuest_Loc["Hold <Ctrl> To Hide Minimap Nodes"], .6, .6, .6)
end
AddWorldMapHideHint(tooltip)
tooltip:Show()
end
pfMap.highlight = pfQuest_config["mouseover"] == "1" and this.title
else
if pfQuestCompat and pfQuestCompat.client and pfQuestCompat.client >= 30300 then
WorldMapPOIFrame.allowBlobTooltip = false
end
local tooltip = this.worldmap and WorldMapTooltip or GameTooltip
tooltip:SetOwner(this, "ANCHOR_LEFT")
this.spawn = this.spawn or UNKNOWN
tooltip:SetText(this.spawn..(pfQuest_config.showids == "1" and " |cffcccccc("..this.spawnid..")|r" or ""), .3, 1, .8)
tooltip:AddDoubleLine(pfQuest_Loc["Level"] .. ":", (this.level or UNKNOWN), .8,.8,.8, 1,1,1)
tooltip:AddDoubleLine(pfQuest_Loc["Type"] .. ":", (this.spawntype or UNKNOWN), .8,.8,.8, 1,1,1)
AddRankLine(tooltip, this.spawnid, this.spawntype)
AddSharedSpawnLine(tooltip, this.sharedspawns, this.spawn)
tooltip:AddDoubleLine(pfQuest_Loc["Respawn"] .. ":", (this.respawn or UNKNOWN), .8,.8,.8, 1,1,1)
for title, meta in pairs(this.node) do
pfMap:ShowTooltip(meta, tooltip)
end
if pfQuest_config["tooltiphelp"] == "1" then
local text = string.gsub(pfQuest_Loc["Use <Shift>-Click To Remove Nodes"], "^Use ", "")
local shifttext
if this.cluster then
text = pfQuest_Loc["Hold <Ctrl> To Hide Cluster"]
elseif tooltip == GameTooltip then
text = pfQuest_Loc["Hold <Ctrl> To Hide Minimap Nodes"]
shifttext = this.questid and this.texture and this.layer < 5
and string.gsub(pfQuest_Loc["Use <Shift>-Click To Mark Quest As Done"], "^Use ", "")
or string.gsub(pfQuest_Loc["Use <Shift>-Click To Remove Nodes"], "^Use ", "")
elseif not this.texture then
text = pfQuest_Loc["Click Node To Change Color"]
elseif this.questid and this.texture and this.layer < 5 then
text = string.gsub(pfQuest_Loc["Use <Shift>-Click To Mark Quest As Done"], "^Use ", "")
end
text = AddConfiguredClickModifier(text, tooltip)
tooltip:AddLine(text, .6, .6, .6)
if shifttext then tooltip:AddLine(shifttext, .6, .6, .6) end
AddWorldMapHideHint(tooltip)
if this.spawnid and pfQuestLoot and pfQuestLoot.HasDrops and pfQuestLoot.HasDrops(this.spawnid) then
tooltip:AddLine(AddConfiguredClickModifier(pfQuest_Loc["Alt-Click To Show Item Drops"] or "Alt-Click To Show Item Drops", tooltip), .6, .6, .6)
end
tooltip:Show()
end
pfMap.highlight = pfQuest_config["mouseover"] == "1" and this.title
end
end
local originalNodeClick = pfMap.NodeClick
pfMap.NodeClick = function()
if pfQuestLoot then
pfQuestLoot.lastClickTrace = "map alt=" .. tostring(IsAltKeyDown()) .. " spawn=" .. tostring(this and this.spawnid)
end
if IsAltKeyDown() and this.spawnid then
if pfQuestLoot and pfQuestLoot.HasDrops and pfQuestLoot.ShowPinned and pfQuestLoot.HasDrops(this.spawnid) then
local ok, err = pcall(pfQuestLoot.ShowPinned, this)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[pfQuest-turtle]|r ShowPinned error: " .. tostring(err))
end
return
end
end
if originalNodeClick then originalNodeClick() end
end
-- Minimap pins are recycled. A recycled pin can keep the core click handler
-- that was installed before the Turtle wrapper, which makes Alt-click work on
-- the world map but not on that minimap pin. Wrap the pin's current handler
-- after every minimap update and retain it for ordinary clicks.
local function MinimapLootNodeClick()
local altClick = IsAltKeyDown() or this.lootPanelAltMouseDown
if pfQuestLoot then
pfQuestLoot.lastClickTrace = "pin alt=" .. tostring(altClick) .. " spawn=" .. tostring(this.spawnid)
pfQuestLoot.lastClickedNode = this
end
this.lootPanelAltMouseDown = nil
if altClick and this.spawnid then
if pfQuestLoot and pfQuestLoot.HasDrops and pfQuestLoot.ShowPinned and pfQuestLoot.HasDrops(this.spawnid) then
local ok, err = pcall(pfQuestLoot.ShowPinned, this)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[pfQuest-turtle]|r ShowPinned error: " .. tostring(err))
end
return
end
end
if this.lootPanelOriginalClick then
this.lootPanelOriginalClick()
end
end
local originalUpdateNode = pfMap.UpdateNode
pfMap.UpdateNode = function(self, frame, node, color, obj, distance)
originalUpdateNode(self, frame, node, color, obj, distance)
-- Older clients bind node clicks directly to the pin frame, so wrap every
-- spawn pin rather than relying on the map-level click callback.
if frame.spawnid and frame:GetScript("OnClick") ~= MinimapLootNodeClick then
frame.lootPanelOriginalClick = frame:GetScript("OnClick")
frame:SetScript("OnClick", MinimapLootNodeClick)
frame:SetScript("OnMouseDown", function()
this.lootPanelAltMouseDown = IsAltKeyDown() and true or nil
end)
end
end
local function RebindUnitResultTooltips()
local unitTab = pfBrowser and pfBrowser.tabs and pfBrowser.tabs["units"]
if not unitTab or not unitTab.buttons then return end
for _, button in pairs(unitTab.buttons) do
if not button.rankTooltipHooked then
local originalOnEnter = button:GetScript("OnEnter")
button:SetScript("OnEnter", function()
if originalOnEnter then originalOnEnter() end
AddRankLine(GameTooltip, this.id, pfQuest_Loc["Unit"])
GameTooltip:Show()
end)
button.rankTooltipHooked = true
end
end
end
local rankTooltipRebind = CreateFrame("Frame")
rankTooltipRebind:Hide()
rankTooltipRebind:SetScript("OnUpdate", function()
this.delay = (this.delay or 0) - arg1
if this.delay <= 0 then
this:Hide()
RebindUnitResultTooltips()
end
end)
if pfBrowser and pfBrowser.input then
local previous = pfBrowser.input:GetScript("OnTextChanged")
pfBrowser.input:SetScript("OnTextChanged", function()
if previous then previous() end
rankTooltipRebind.delay = 0.7
rankTooltipRebind:Show()
end)
end