Added colour for the progress of objectives in the quest tracker.
[QuestHelper.git] / custom.lua
bloba840be180d1094193c885d04edff44f1d0f51784
1 QuestHelper_File["custom.lua"] = "Development Version"
3 -- This next bit of stuff is for fuzzy string comarisons.
5 local row, prow = {}, {}
7 local difftable = {}
9 for i = 65,90 do
10 local a = {}
11 difftable[i-64] = a
12 for j = 65,90 do
13 a[j-64] = i==j and 0 or 1
14 end
15 end
17 local function setgroup(a, w)
18 for i = 1,string.len(a)-1 do
19 for j = i+1,string.len(a) do
20 local c1, c2 = string.byte(a,i)-64, string.byte(a,j)-64
22 difftable[c1][c2] = math.min(w, difftable[c1][c2])
23 difftable[c2][c1] = math.min(w, difftable[c2][c1])
24 end
25 end
26 end
28 -- Characters that sound similar. At least in my opinion.
29 setgroup("BCDFGHJKLMNPQRSTVWXZ", 0.9)
30 setgroup("AEIOUY", 0.6)
31 setgroup("TD", 0.6)
32 setgroup("CKQ", 0.4)
33 setgroup("MN", 0.4)
34 setgroup("EIY", 0.3)
35 setgroup("UO", 0.2)
36 setgroup("SZ", 0.6)
38 local function diffness(a, b)
39 if a >= 65 and a <=90 then
40 if b >= 65 and b <= 90 then
41 return difftable[a-64][b-64]
42 else
43 return 1
44 end
45 elseif b >= 65 and b <= 90 then
46 return 1
47 else
48 return 0
49 end
50 end
52 local function fuzzyCompare(a, b)
53 local m, n = string.len(a), string.len(b)
55 if n == 0 or m == 0 then
56 return n == m and 0 or 1
57 end
59 for j = 1,n+1 do
60 row[j] = j-1
61 end
63 for i = 1,m do
64 row, prow = prow, row
65 row[1] = i
67 for j = 1,n do
68 row[j+1] = math.min(prow[j+1]+1, row[j]+.4, prow[j]+diffness(string.byte(a,i), string.byte(b,j)))
69 end
70 end
72 return row[n+1]/math.max(n,m)
73 end
75 local search_frame = CreateFrame("Button", nil, UIParent)
76 search_frame.text = search_frame:CreateFontString()
77 search_frame.text:SetFont(QuestHelper.font.sans, 15)
78 search_frame.text:SetTextColor(1, 1, 1)
79 search_frame.text:SetJustifyH("CENTER")
80 search_frame.text:SetJustifyV("MIDDLE")
81 search_frame.text:SetDrawLayer("OVERLAY")
82 search_frame.text:SetAllPoints()
83 search_frame.text:Show()
84 search_frame.background = search_frame:CreateTexture()
85 search_frame.background:SetTexture(0, 0, 0, 0.5)
86 search_frame.background:SetDrawLayer("BACKGROUND")
87 search_frame.background:SetAllPoints()
88 search_frame.background:Show()
89 search_frame:SetPoint("CENTER", UIParent, "CENTER")
90 search_frame:Hide()
92 search_frame.results = {}
94 function search_frame:SetText(text)
95 self.text:SetText(text)
96 self:SetWidth(self.text:GetWidth()+10)
97 self:SetHeight(self.text:GetHeight()+10)
98 end
100 function search_frame:OnUpdate()
101 if self.routine and coroutine.status(self.routine) ~= "dead" then
102 local no_error, display = coroutine.resume(self.routine, self, self.query)
103 if no_error then
104 self:SetText(display)
105 else
106 QuestHelper:TextOut("Searching co-routine just exploded: "..display)
108 else
109 self:ShowResults()
110 self.routine = nil
111 self:SetScript("OnUpdate", nil)
112 self:Hide()
116 function QuestHelper:ToggleUserObjective(cat, what)
117 local objective = self:GetObjective(cat, what)
119 if self.user_objectives[objective] then
120 self:TextOut(QHFormat("REMOVED_OBJ", self.user_objectives[objective]))
121 self:RemoveObjectiveWatch(objective, self.user_objectives[objective])
122 self.user_objectives[objective] = nil
123 elseif objective:Known() then
124 local name
125 if cat == "loc" then
126 local _, _, i, x, y = string.find(what, "^(%d+),([%d%.]+),([%d%.]+)$")
127 name = QHFormat("USER_OBJ", self:HighlightText(QuestHelper_NameLookup[tonumber(i)])..": "..self:HighlightText(x*100)..", "..self:HighlightText(y*100))
128 else
129 name = QHFormat("USER_OBJ", self:HighlightText(string.gsub(cat, "^(.)", string.upper))..": "..self:HighlightText(what))
132 objective.priority = 1
133 self.user_objectives[objective] = name
134 self:AddObjectiveWatch(objective, name)
136 self:TextOut(QHFormat("CREATED_OBJ", name))
137 else
138 self:TextOut(QHText("UNKNOWN_OBJ"))
142 function search_frame:CreateResultItem(r, menu)
143 local item
145 if r.cat == "loc" then
146 local _, _, i, x, y = string.find(r.what, "^(%d+),([%d%.]+),([%d%.]+)$")
147 item = QuestHelper:CreateMenuItem(menu, QuestHelper_NameLookup[tonumber(i)]..": "..(x*100)..", "..(y*100).." ["..QuestHelper:PercentString(1-r.w).."]")
148 item:AddTexture(QuestHelper:CreateIconTexture(item, 6), true)
149 else
150 item = QuestHelper:CreateMenuItem(menu, r.what .. " ["..QuestHelper:PercentString(1-r.w).."]")
151 item:AddTexture(QuestHelper:CreateIconTexture(item, (r.cat == "monster" and 1) or 2), true)
154 item:SetFunction(QuestHelper.ToggleUserObjective, QuestHelper, r.cat, r.what)
156 return item
159 function search_frame:ShowResults()
160 local menu = QuestHelper:CreateMenu()
161 QuestHelper:CreateMenuTitle(menu, QHText("RESULTS_TITLE"))
163 if #self.results == 0 then
164 QuestHelper:CreateMenuItem(menu, QHText("NO_RESULTS"))
165 else
166 for i, r in ipairs(self.results) do
167 self:CreateResultItem(r, menu)
171 menu:ShowAtCursor()
172 self:ClearResults()
175 function search_frame:ClearResults()
176 while #self.results > 0 do
177 QuestHelper:ReleaseTable(table.remove(self.results))
181 function search_frame:AddResult(cat, what, w)
182 local r = self.results
183 local mn, mx = 1, #r+1
185 while mn ~= mx do
186 local m = math.floor((mn+mx)*0.5)
188 if r[m].w < w then
189 mn = m+1
190 else
191 mx = m
195 if mn <= 20 then
196 if r[mn] and r[mn].cat == cat and r[mn].what == what then
197 -- Don't add the same item twice.
198 -- Might miss it if multiple items have the same score. Dont care.
199 return
202 if #r >= 20 then
203 QuestHelper:ReleaseTable(table.remove(r, 20))
206 local obj = QuestHelper:CreateTable()
207 obj.cat = cat
208 obj.what = what
209 obj.w = w
210 table.insert(r, mn, obj)
214 function search_frame:SearchRoutine(input)
215 if input == "" then
216 for obj in pairs(QuestHelper.user_objectives) do
217 self:AddResult(obj.cat, obj.obj, 0)
219 return
222 input = string.upper(input)
223 local _, _, command, argument = string.find(input, "^%s*([^%s]-)%s+(.-)%s*$")
225 local search_item, search_npc, search_loc = false, false, false
227 if command and argument then
228 if command == "ITEM" then
229 search_item, input = true, argument
230 elseif command == "NPC" or command == "MONSTER" then
231 search_npc, input = true, argument
232 elseif command == "LOCATION" or command == "LOC" then
233 search_loc, input = true, argument
234 else
235 search_item, search_npc, search_loc = true, true, true
237 else
238 search_item, search_npc, search_loc = true, true, true
241 local yield_countdown_max = math.max(1, math.floor(2000/string.len(input)+0.5))
242 local yield_countdown = yield_countdown_max
244 if search_item then
245 local list = QuestHelper_Objectives["item"]
246 if list then for n in pairs(list) do
247 self:AddResult("item", n, fuzzyCompare(input, string.upper(n)))
248 yield_countdown = yield_countdown - 1
249 if yield_countdown == 0 then
250 yield_countdown = yield_countdown_max
251 coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_LOCAL", QHText("SEARCHING_ITEMS"))))
253 end end
255 list = QuestHelper_StaticData[QuestHelper.locale].objective
256 list = list and list.item
257 if list then for n in pairs(list) do
258 self:AddResult("item", n, fuzzyCompare(input, string.upper(n)))
259 yield_countdown = yield_countdown - 1
260 if yield_countdown == 0 then
261 yield_countdown = yield_countdown_max
262 coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_STATIC", QHText("SEARCHING_ITEMS"))))
264 end end
267 if search_npc then
268 local list = QuestHelper_Objectives["monster"]
269 if list then for n in pairs(list) do
270 self:AddResult("monster", n, fuzzyCompare(input, string.upper(n)))
271 yield_countdown = yield_countdown - 1
272 if yield_countdown == 0 then
273 yield_countdown = yield_countdown_max
274 coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_LOCAL", QHText("SEARCHING_NPCS"))))
276 end end
278 list = QuestHelper_StaticData[QuestHelper.locale].objective
279 list = list and list.monster
280 if list then for n in pairs(list) do
281 self:AddResult("monster", n, fuzzyCompare(input, string.upper(n)))
282 yield_countdown = yield_countdown - 1
283 if yield_countdown == 0 then
284 yield_countdown = yield_countdown_max
285 coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_STATIC", QHText("SEARCHING_NPCS"))))
287 end end
290 if search_loc then
291 local _, _, region, x, y = string.find(input, "^%s*([^%d%.]-)%s*([%d%.]+)%s*[,;:]?%s*([%d%.]+)%s*$")
293 if region then
294 x, y = tonumber(x), tonumber(y)
295 if x and y then
296 x, y = x*0.01, y*0.01
298 if region == "" then
299 self:AddResult("loc", string.format("%d,%.3f,%.3f", QuestHelper.i, x, y), 0)
300 else
301 for i, name in pairs(QuestHelper_NameLookup) do
302 self:AddResult("loc", string.format("%d,%.3f,%.3f", i, x, y), fuzzyCompare(region, string.upper(name)))
303 yield_countdown = yield_countdown - 1
304 if yield_countdown == 0 then
305 yield_countdown = yield_countdown_max
306 coroutine.yield(QHFormat("SEARCHING_STATE", QHText("SEARCHING_ZONES")))
314 return QHText("SEARCHING_DONE")
317 local function ReturnArgument(x)
318 return x
321 function search_frame:PerformSearch(input)
322 if not self.routine then
323 self.query = string.gsub(input, "|c.-|H.-|h%[(.-)%]|h|r", ReturnArgument)
324 self.routine = coroutine.create(self.SearchRoutine)
325 self:Show()
326 self:SetScript("OnUpdate", self.OnUpdate)
330 function QuestHelper:PerformSearch(query)
331 search_frame:PerformSearch(query)
334 function QuestHelper:PerformCustomSearch(func)
335 if not search_frame:GetScript("OnUpdate") then
336 search_frame:Show()
337 search_frame:SetScript("OnUpdate", func)
341 function QuestHelper:StopCustomSearch()
342 if not search_frame.routine then
343 search_frame:Hide()
344 search_frame:SetScript("OnUpdate", nil)
348 SLASH_QuestHelperFind1 = "/qhfind"
349 SLASH_QuestHelperFind2 = "/find"
350 SlashCmdList["QuestHelperFind"] = function (text) QuestHelper:PerformSearch(text) end