local GetTime = QuestHelper_GetTime QuestHelper_File["custom.lua"] = "4.0.1.$svnversion$" QuestHelper_Loadtime["custom.lua"] = GetTime() local search_frame = CreateFrame("Button", nil, UIParent) search_frame.text = search_frame:CreateFontString() search_frame.text:SetFont(QuestHelper.font.sans, 15) search_frame.text:SetTextColor(1, 1, 1) search_frame.text:SetJustifyH("CENTER") search_frame.text:SetJustifyV("MIDDLE") search_frame.text:SetDrawLayer("OVERLAY") search_frame.text:SetAllPoints() search_frame.text:Show() search_frame.background = search_frame:CreateTexture() search_frame.background:SetTexture(0, 0, 0, 0.5) search_frame.background:SetDrawLayer("BACKGROUND") search_frame.background:SetAllPoints() search_frame.background:Show() search_frame:SetPoint("CENTER", UIParent, "CENTER") search_frame:Hide() search_frame.results = {} function search_frame:SetText(text) self.text:SetText(text) self:SetWidth(self.text:GetWidth()+10) self:SetHeight(self.text:GetHeight()+10) end function search_frame:OnUpdate() if self.routine and coroutine.status(self.routine) ~= "dead" then local no_error, display = coroutine.resume(self.routine, self, self.query) if no_error then self:SetText(display) else QuestHelper:TextOut("Searching co-routine just exploded: "..display) end else self:ShowResults() self.routine = nil QH_Hook(self, "OnUpdate", nil) self:Hide() end end function QuestHelper:PerformCustomSearch(func) if not search_frame:GetScript("OnUpdate") then search_frame:Show() QH_Hook(search_frame, "OnUpdate", func) end end function QuestHelper:StopCustomSearch() if not search_frame.routine then search_frame:Hide() QH_Hook(search_frame, "OnUpdate", nil) end end do return end -- This next bit of stuff is for fuzzy string comarisons. local row, prow = {}, {} local difftable = {} for i = 65,90 do local a = {} difftable[i-64] = a for j = 65,90 do a[j-64] = i==j and 0 or 1 end end local function setgroup(a, w) for i = 1,string.len(a)-1 do for j = i+1,string.len(a) do local c1, c2 = string.byte(a,i)-64, string.byte(a,j)-64 difftable[c1][c2] = math.min(w, difftable[c1][c2]) difftable[c2][c1] = math.min(w, difftable[c2][c1]) end end end -- Characters that sound similar. At least in my opinion. setgroup("BCDFGHJKLMNPQRSTVWXZ", 0.9) setgroup("AEIOUY", 0.6) setgroup("TD", 0.6) setgroup("CKQ", 0.4) setgroup("MN", 0.4) setgroup("EIY", 0.3) setgroup("UO", 0.2) setgroup("SZ", 0.6) local function diffness(a, b) if a >= 65 and a <=90 then if b >= 65 and b <= 90 then return difftable[a-64][b-64] else return 1 end elseif b >= 65 and b <= 90 then return 1 else return 0 end end local function fuzzyCompare(a, b) local m, n = string.len(a), string.len(b) if n == 0 or m == 0 then return n == m and 0 or 1 end for j = 1,n+1 do row[j] = j-1 end for i = 1,m do row, prow = prow, row row[1] = i for j = 1,n do row[j+1] = math.min(prow[j+1]+1, row[j]+.4, prow[j]+diffness(string.byte(a,i), string.byte(b,j))) end end return row[n+1]/math.max(n,m) end function QuestHelper:ToggleUserObjective(cat, what) local objective = self:GetObjective(cat, what) if self.user_objectives[objective] then self:TextOut(QHFormat("REMOVED_OBJ", self.user_objectives[objective])) self:RemoveObjectiveWatch(objective, self.user_objectives[objective]) self.user_objectives[objective] = nil elseif objective:Known() then local name if cat == "loc" then local _, _, i, x, y = string.find(what, "^(%d+),([%d%.]+),([%d%.]+)$") name = QHFormat("USER_OBJ", self:HighlightText(QuestHelper_NameLookup[tonumber(i)])..": "..self:HighlightText(x*100)..", "..self:HighlightText(y*100)) else name = QHFormat("USER_OBJ", self:HighlightText(string.gsub(cat, "^(.)", string.upper))..": "..self:HighlightText(what)) end objective.priority = 1 self.user_objectives[objective] = name self:AddObjectiveWatch(objective, name) self:TextOut(QHFormat("CREATED_OBJ", name)) else self:TextOut(QHText("UNKNOWN_OBJ")) end end function search_frame:CreateResultItem(r, menu) local item if r.cat == "loc" then local _, _, i, x, y = string.find(r.what, "^(%d+),([%d%.]+),([%d%.]+)$") item = QuestHelper:CreateMenuItem(menu, QuestHelper_NameLookup[tonumber(i)]..": "..(x*100)..", "..(y*100).." ["..QuestHelper:PercentString(1-r.w).."]") item:AddTexture(QuestHelper:CreateIconTexture(item, 6), true) else item = QuestHelper:CreateMenuItem(menu, r.what .. " ["..QuestHelper:PercentString(1-r.w).."]") item:AddTexture(QuestHelper:CreateIconTexture(item, (r.cat == "monster" and 1) or 2), true) end item:SetFunction(QuestHelper.ToggleUserObjective, QuestHelper, r.cat, r.what) return item end function search_frame:ShowResults() local menu = QuestHelper:CreateMenu() QuestHelper:CreateMenuTitle(menu, QHText("RESULTS_TITLE")) if #self.results == 0 then QuestHelper:CreateMenuItem(menu, QHText("NO_RESULTS")) else for i, r in ipairs(self.results) do self:CreateResultItem(r, menu) end end menu:ShowAtCursor() self:ClearResults() end function search_frame:ClearResults() while #self.results > 0 do QuestHelper:ReleaseTable(table.remove(self.results)) end end function search_frame:AddResult(cat, what, w) local r = self.results local mn, mx = 1, #r+1 while mn ~= mx do local m = math.floor((mn+mx)*0.5) if r[m].w < w then mn = m+1 else mx = m end end if mn <= 20 then if r[mn] and r[mn].cat == cat and r[mn].what == what then -- Don't add the same item twice. -- Might miss it if multiple items have the same score. Dont care. return end if #r >= 20 then QuestHelper:ReleaseTable(table.remove(r, 20)) end local obj = QuestHelper:CreateTable() obj.cat = cat obj.what = what obj.w = w table.insert(r, mn, obj) end end local function searchNPC(input, yield_countdown, yield_countdown_max) local list = QuestHelper_Objectives_Local["monster"] if list then for n in pairs(list) do self:AddResult("monster", n, fuzzyCompare(input, string.upper(n))) yield_countdown = yield_countdown - 1 if yield_countdown == 0 then yield_countdown = yield_countdown_max coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_LOCAL", QHText("SEARCHING_NPCS")))) end end end list = QuestHelper_StaticData[QuestHelper.locale].objective list = list and list.monster if list then for n in pairs(list) do self:AddResult("monster", n, fuzzyCompare(input, string.upper(n))) yield_countdown = yield_countdown - 1 if yield_countdown == 0 then yield_countdown = yield_countdown_max coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_STATIC", QHText("SEARCHING_NPCS")))) end end end return yield_countdown end local elders = { ["Alliance"] = { ["Bladeswift"] = "Darnassus 39 32", ["Bronzebeard"] = "Ironforge 29 16", ["Hammershout"] = "Elwynn Forest 34 50" }, ["Horde"] = { ["Darkhorn"] = "Orgrimmar 52 60", ["Wheathoof"] = "Thunder Bluff 72 33", ["Darkcore"] = "Undercity 67 38" }, ["Kalimdor"] = { ["Riversong"] = "Ashenvale 35 48", ["Skygleam"] = "Azshara 64 79", ["High Mountain"] = "Southern Barrens 41 47", -- 41.5 47.5 ["Moonwarden"] = "Northern Barrens 48 59", -- 48.5 59.2 ["Windtotem"] = "Northern Barrens 68 69", ["Starweave"] = "Darkshore 49 19", -- 49.5 19.0 ["Runetotem"] = "Durotar 54 44", ["Nightwind"] = "Felwood 37 52", ["Grimtotem"] = "Feralas 76 37", ["Mistwalker"] = "Feralas 62 31", ["Bloodhoof"] = "Mulgore 48 53", ["Bladesing"] = "Silithus 53 35", ["Primestone"] = "Silithus 30 13", -- 30.7 13.3 ["Dreamseer"] = "Tanaris 50 28", ["Ragetotem"] = "Tanaris 36 80", ["Bladeleaf"] = "Teldrassil 57 53", ["Skyseer"] = "Thousand Needles 45 50", ["Morningdew"] = "Thousand Needles 77 75", -- 77.0 75.6 ["Thunderhorn"] = "Un'Goro Crater 51 75", ["Brightspear"] = "Winterspring 53 56", -- 53.0 56.7 ["Stonespire"] = "Winterspring 60 50", -- 60.0 50.0 }, ["Eastern Kingdoms"] = { ["Bellowrage"] = "Blasted Lands 54 49", ["Rumblerock"] = "Burning Steppes 70 45", ["Dawnstrider"] = "Burning Steppes 53 24", ["Goldwell"] = "Dun Morogh 53 49", ["Windrun"] = "Eastern Plaguelands 35 68", ["Snowcrown"] = "Eastern Plaguelands 75 54", ["Stormbrow"] = "Elwynn Forest 40 63", ["Highpeak"] = "Hinterlands 50 48", ["Silvervein"] = "Loch Modan 33 46", ["Ironband"] = "Searing Gorge 21 79", ["Obsidian"] = "Silverpine Forest 45 41", ["Starglade"] = "Northern Stranglethorn 71 34", ["Winterhoof"] = "The Cape of Stranglethorn 39 72", -- Cape 39 72 ["Graveborn"] = "Tirisfal Glades 61 53", ["Moonstrike"] = "Western Plaguelands 69 73", ["Meadowrun"] = "Western Plaguelands 63 36", ["Skychaser"] = "Westfall 56 47" }, ["Northrend"] = { ["Arp"] = "Borean Tundra 57 44", ["Northal"] = "Borean Tundra 34 34", ["Pamuya"] = "Borean Tundra 43 50", ["Sardis"] = "Borean Tundra 59 66", ["Morthie"] = "Dragonblight 30 56", ["Skywarden"] = "Dragonblight 35 48", ["Thoim"] = "Dragonblight 49 78", ["Beldak"] = "Grizzly Hills 61 28", ["Lunaro"] = "Grizzly Hills 81 37", ["Whurain"] = "Grizzly Hills 64 47", ["Bluewolf"] = "Wintergrasp 49 14", ["Sandrene"] = "Sholazar Basin 50 64", ["Wanikaya"] = "Sholazar Basin 64 49", ["Fargal"] = "Storm Peaks 29 74", ["Graymane"] = "Storm Peaks 41 85", ["Muraco"] = "Storm Peaks 64 51", ["Stonebeard"] = "Storm Peaks 31 38", ["Tauros"] = "Zul'Drak 59 56" }, ["Cataclysm"] = { ["Moonlance"] = "Shimmering Expanse 57, 86", -- Biel'aran Ridge, Shimmering Expanse ["Windsong"] = "Mount Hyjal 27 62", -- Sanctuary of Malorne, Hyjal ["Evershade"] = "Mount Hyjal 63 23", -- Nordrassil, Hyjal ["Stonebrand"] = "Deepholm 50 55", -- Temple of Earth, Deepholm ["Deepforge"] = "Deepholm 28 69", -- Stonehearth, Deepholm ["Menkhaf"] = "Uldum 66 19", -- Khartut's Tomb, Uldum ["Sekhemi"] = "Uldum 32 63", -- Ruins of Ammon, Uldum ["Firebeard"] = "Twilight Highlands 51 71", -- Dunward Town Square, Dunward Ruins, Twilight Highlands ["Darkfeather"] = "Twilight Highlands 52 33" -- Thundermar Ruins, Twilight Highlands }, } local function searchItem(input, yield_countdown, yield_countdown_max) local list = QuestHelper_Objectives_Local["item"] if list then for n in pairs(list) do self:AddResult("item", n, fuzzyCompare(input, string.upper(n))) yield_countdown = yield_countdown - 1 if yield_countdown == 0 then yield_countdown = yield_countdown_max coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_LOCAL", QHText("SEARCHING_ITEMS")))) end end end list = QuestHelper_StaticData[QuestHelper.locale].objective list = list and list.item if list then for n in pairs(list) do self:AddResult("item", n, fuzzyCompare(input, string.upper(n))) yield_countdown = yield_countdown - 1 if yield_countdown == 0 then yield_countdown = yield_countdown_max coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_STATIC", QHText("SEARCHING_ITEMS")))) end end end return yield_countdown end local function searchLoc(input, yield_countdown, yield_countdown_max) local _, _, region, x, y = string.find(input, "^%s*([^%d%.]-)%s*([%d%.]+)%s*[,;:]?%s*([%d%.]+)%s*$") if region then x, y = tonumber(x), tonumber(y) if x and y then x, y = x*0.01, y*0.01 if region == "" then self:AddResult("loc", string.format("%d,%.3f,%.3f", QuestHelper.i, x, y), 0) else for i, name in pairs(QuestHelper_NameLookup) do self:AddResult("loc", string.format("%d,%.3f,%.3f", i, x, y), fuzzyCompare(region, string.upper(name))) yield_countdown = yield_countdown - 1 if yield_countdown == 0 then yield_countdown = yield_countdown_max coroutine.yield(QHFormat("SEARCHING_STATE", QHText("SEARCHING_ZONES"))) end end end end end return yield_countdown end local function searchElders(input, yield_countdown, yield_countdown_max) local yc = yield_countdown local achievement_match, elder_match = false, false for achievement, eldrs in pairs(elders) do if input and input ~= "" then if input == string.upper(achievement) then achievement_match = true end end for elder, elder_loc in pairs(eldrs) do if input and input ~= "" then if achievement_match then -- just add it yc = searchLoc(string.upper(elder_loc), yc, yield_countdown_max) elseif input == string.upper(elder) then -- We have input and it's not an achievement, so it must be an elder. elder_match = true yc = searchLoc(string.upper(elder_loc), yc, yield_countdown_max) break -- We've found him or her. end -- No need for else here. We've got input so we either have an achievement or we have an elder. else -- We came in without an input, therefore we add all. yc = searchLoc(string.upper(elder_loc), yc, yield_countdown_max) end end if achievement_match or elder_match then break end -- We've done our match. end return yc end function search_frame:SearchRoutine(input) if input == "" then for obj in pairs(QuestHelper.user_objectives) do self:AddResult(obj.cat, obj.obj, 0) end return end input = string.upper(input) local _, _, command, argument = string.find(input, "^%s*([^%s]-)%s+(.-)%s*$") local search_item, search_npc, search_loc, search_elders = false, false, false, false local yield_countdown_max = math.max(1, math.floor(2000/string.len(input)+0.5)) local yield_countdown = yield_countdown_max if command and argument then if command == "ITEM" then searchItem(argument, yield_countdown, yield_countdown_max) elseif command == "NPC" or command == "MONSTER" then searchNPC(argument, yield_countdown, yield_countdown_max) elseif command == "LOCATION" or command == "LOC" then searchLoc(argument, yield_countdown, yield_countdown_max) elseif command == "ELDER" or command == "ELDERS" then searchElders(argument, yield_countdown, yield_countdown_max) else yield_countdown = searchItem(input, yield_countdown, yield_countdown_max) yield_countdown = searchNPC(input, yield_countdown, yield_countdown_max) searchLoc(input, yield_countdown, yield_countdown_max) end else yield_countdown = searchItem(input, yield_countdown, yield_countdown_max) yield_countdown = searchNPC(input, yield_countdown, yield_countdown_max) searchLoc(input, yield_countdown, yield_countdown_max) end return QHText("SEARCHING_DONE") end local function ReturnArgument(x) return x end function search_frame:PerformSearch(input) QuestHelper:TextOut("/qh find is currently disabled. Sorry! I'll get it back in once I can.") do return end if not self.routine then self.query = string.gsub(input, "|c.-|H.-|h%[(.-)%]|h|r", ReturnArgument) self.routine = coroutine.create(self.SearchRoutine) self:Show() QH_Hook(self, "OnUpdate", self.OnUpdate) end end function QuestHelper:PerformSearch(query) search_frame:PerformSearch(query) end SLASH_QuestHelperFind1 = "/qhfind" SLASH_QuestHelperFind2 = "/find" SlashCmdList["QuestHelperFind"] = function (text) QuestHelper:PerformSearch(text) end