Merge branch 'master' of git://cams.pavlovian.net/questhelper
[QuestHelper.git] / custom.lua
blob4d37e5d2b0e5626981f703e6adf339848a5b5f1a
1 QuestHelper_File["custom.lua"] = "Development Version"
2 QuestHelper_Loadtime["custom.lua"] = GetTime()
4 -- This next bit of stuff is for fuzzy string comarisons.
7 local row, prow = {}, {}
9 local difftable = {}
11 for i = 65,90 do
12 local a = {}
13 difftable[i-64] = a
14 for j = 65,90 do
15 a[j-64] = i==j and 0 or 1
16 end
17 end
19 local function setgroup(a, w)
20 for i = 1,string.len(a)-1 do
21 for j = i+1,string.len(a) do
22 local c1, c2 = string.byte(a,i)-64, string.byte(a,j)-64
24 difftable[c1][c2] = math.min(w, difftable[c1][c2])
25 difftable[c2][c1] = math.min(w, difftable[c2][c1])
26 end
27 end
28 end
30 -- Characters that sound similar. At least in my opinion.
31 setgroup("BCDFGHJKLMNPQRSTVWXZ", 0.9)
32 setgroup("AEIOUY", 0.6)
33 setgroup("TD", 0.6)
34 setgroup("CKQ", 0.4)
35 setgroup("MN", 0.4)
36 setgroup("EIY", 0.3)
37 setgroup("UO", 0.2)
38 setgroup("SZ", 0.6)
40 local function diffness(a, b)
41 if a >= 65 and a <=90 then
42 if b >= 65 and b <= 90 then
43 return difftable[a-64][b-64]
44 else
45 return 1
46 end
47 elseif b >= 65 and b <= 90 then
48 return 1
49 else
50 return 0
51 end
52 end
54 local function fuzzyCompare(a, b)
55 local m, n = string.len(a), string.len(b)
57 if n == 0 or m == 0 then
58 return n == m and 0 or 1
59 end
61 for j = 1,n+1 do
62 row[j] = j-1
63 end
65 for i = 1,m do
66 row, prow = prow, row
67 row[1] = i
69 for j = 1,n do
70 row[j+1] = math.min(prow[j+1]+1, row[j]+.4, prow[j]+diffness(string.byte(a,i), string.byte(b,j)))
71 end
72 end
74 return row[n+1]/math.max(n,m)
75 end
77 local search_frame = CreateFrame("Button", nil, UIParent)
78 search_frame.text = search_frame:CreateFontString()
79 search_frame.text:SetFont(QuestHelper.font.sans, 15)
80 search_frame.text:SetTextColor(1, 1, 1)
81 search_frame.text:SetJustifyH("CENTER")
82 search_frame.text:SetJustifyV("MIDDLE")
83 search_frame.text:SetDrawLayer("OVERLAY")
84 search_frame.text:SetAllPoints()
85 search_frame.text:Show()
86 search_frame.background = search_frame:CreateTexture()
87 search_frame.background:SetTexture(0, 0, 0, 0.5)
88 search_frame.background:SetDrawLayer("BACKGROUND")
89 search_frame.background:SetAllPoints()
90 search_frame.background:Show()
91 search_frame:SetPoint("CENTER", UIParent, "CENTER")
92 search_frame:Hide()
94 search_frame.results = {}
96 function search_frame:SetText(text)
97 self.text:SetText(text)
98 self:SetWidth(self.text:GetWidth()+10)
99 self:SetHeight(self.text:GetHeight()+10)
102 function search_frame:OnUpdate()
103 if self.routine and coroutine.status(self.routine) ~= "dead" then
104 local no_error, display = coroutine.resume(self.routine, self, self.query)
105 if no_error then
106 self:SetText(display)
107 else
108 QuestHelper:TextOut("Searching co-routine just exploded: "..display)
110 else
111 self:ShowResults()
112 self.routine = nil
113 QH_Hook(self, "OnUpdate", nil)
114 self:Hide()
118 function QuestHelper:ToggleUserObjective(cat, what)
119 local objective = self:GetObjective(cat, what)
121 if self.user_objectives[objective] then
122 self:TextOut(QHFormat("REMOVED_OBJ", self.user_objectives[objective]))
123 self:RemoveObjectiveWatch(objective, self.user_objectives[objective])
124 self.user_objectives[objective] = nil
125 elseif objective:Known() then
126 local name
127 if cat == "loc" then
128 local _, _, i, x, y = string.find(what, "^(%d+),([%d%.]+),([%d%.]+)$")
129 name = QHFormat("USER_OBJ", self:HighlightText(QuestHelper_NameLookup[tonumber(i)])..": "..self:HighlightText(x*100)..", "..self:HighlightText(y*100))
130 else
131 name = QHFormat("USER_OBJ", self:HighlightText(string.gsub(cat, "^(.)", string.upper))..": "..self:HighlightText(what))
134 objective.priority = 1
135 self.user_objectives[objective] = name
136 self:AddObjectiveWatch(objective, name)
138 self:TextOut(QHFormat("CREATED_OBJ", name))
139 else
140 self:TextOut(QHText("UNKNOWN_OBJ"))
144 function search_frame:CreateResultItem(r, menu)
145 local item
147 if r.cat == "loc" then
148 local _, _, i, x, y = string.find(r.what, "^(%d+),([%d%.]+),([%d%.]+)$")
149 item = QuestHelper:CreateMenuItem(menu, QuestHelper_NameLookup[tonumber(i)]..": "..(x*100)..", "..(y*100).." ["..QuestHelper:PercentString(1-r.w).."]")
150 item:AddTexture(QuestHelper:CreateIconTexture(item, 6), true)
151 else
152 item = QuestHelper:CreateMenuItem(menu, r.what .. " ["..QuestHelper:PercentString(1-r.w).."]")
153 item:AddTexture(QuestHelper:CreateIconTexture(item, (r.cat == "monster" and 1) or 2), true)
156 item:SetFunction(QuestHelper.ToggleUserObjective, QuestHelper, r.cat, r.what)
158 return item
161 function search_frame:ShowResults()
162 local menu = QuestHelper:CreateMenu()
163 QuestHelper:CreateMenuTitle(menu, QHText("RESULTS_TITLE"))
165 if #self.results == 0 then
166 QuestHelper:CreateMenuItem(menu, QHText("NO_RESULTS"))
167 else
168 for i, r in ipairs(self.results) do
169 self:CreateResultItem(r, menu)
173 menu:ShowAtCursor()
174 self:ClearResults()
177 function search_frame:ClearResults()
178 while #self.results > 0 do
179 QuestHelper:ReleaseTable(table.remove(self.results))
183 function search_frame:AddResult(cat, what, w)
184 local r = self.results
185 local mn, mx = 1, #r+1
187 while mn ~= mx do
188 local m = math.floor((mn+mx)*0.5)
190 if r[m].w < w then
191 mn = m+1
192 else
193 mx = m
197 if mn <= 20 then
198 if r[mn] and r[mn].cat == cat and r[mn].what == what then
199 -- Don't add the same item twice.
200 -- Might miss it if multiple items have the same score. Dont care.
201 return
204 if #r >= 20 then
205 QuestHelper:ReleaseTable(table.remove(r, 20))
208 local obj = QuestHelper:CreateTable()
209 obj.cat = cat
210 obj.what = what
211 obj.w = w
212 table.insert(r, mn, obj)
216 function search_frame:SearchRoutine(input)
217 if input == "" then
218 for obj in pairs(QuestHelper.user_objectives) do
219 self:AddResult(obj.cat, obj.obj, 0)
221 return
224 input = string.upper(input)
225 local _, _, command, argument = string.find(input, "^%s*([^%s]-)%s+(.-)%s*$")
227 local search_item, search_npc, search_loc = false, false, false
229 if command and argument then
230 if command == "ITEM" then
231 search_item, input = true, argument
232 elseif command == "NPC" or command == "MONSTER" then
233 search_npc, input = true, argument
234 elseif command == "LOCATION" or command == "LOC" then
235 search_loc, input = true, argument
236 else
237 search_item, search_npc, search_loc = true, true, true
239 else
240 search_item, search_npc, search_loc = true, true, true
243 local yield_countdown_max = math.max(1, math.floor(2000/string.len(input)+0.5))
244 local yield_countdown = yield_countdown_max
246 if search_item then
247 local list = QuestHelper_Objectives_Local["item"]
248 if list then for n in pairs(list) do
249 self:AddResult("item", n, fuzzyCompare(input, string.upper(n)))
250 yield_countdown = yield_countdown - 1
251 if yield_countdown == 0 then
252 yield_countdown = yield_countdown_max
253 coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_LOCAL", QHText("SEARCHING_ITEMS"))))
255 end end
257 list = QuestHelper_StaticData[QuestHelper.locale].objective
258 list = list and list.item
259 if list then for n in pairs(list) do
260 self:AddResult("item", n, fuzzyCompare(input, string.upper(n)))
261 yield_countdown = yield_countdown - 1
262 if yield_countdown == 0 then
263 yield_countdown = yield_countdown_max
264 coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_STATIC", QHText("SEARCHING_ITEMS"))))
266 end end
269 if search_npc then
270 local list = QuestHelper_Objectives_Local["monster"]
271 if list then for n in pairs(list) do
272 self:AddResult("monster", n, fuzzyCompare(input, string.upper(n)))
273 yield_countdown = yield_countdown - 1
274 if yield_countdown == 0 then
275 yield_countdown = yield_countdown_max
276 coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_LOCAL", QHText("SEARCHING_NPCS"))))
278 end end
280 list = QuestHelper_StaticData[QuestHelper.locale].objective
281 list = list and list.monster
282 if list then for n in pairs(list) do
283 self:AddResult("monster", n, fuzzyCompare(input, string.upper(n)))
284 yield_countdown = yield_countdown - 1
285 if yield_countdown == 0 then
286 yield_countdown = yield_countdown_max
287 coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_STATIC", QHText("SEARCHING_NPCS"))))
289 end end
292 if search_loc then
293 local _, _, region, x, y = string.find(input, "^%s*([^%d%.]-)%s*([%d%.]+)%s*[,;:]?%s*([%d%.]+)%s*$")
295 if region then
296 x, y = tonumber(x), tonumber(y)
297 if x and y then
298 x, y = x*0.01, y*0.01
300 if region == "" then
301 self:AddResult("loc", string.format("%d,%.3f,%.3f", QuestHelper.i, x, y), 0)
302 else
303 for i, name in pairs(QuestHelper_NameLookup) do
304 self:AddResult("loc", string.format("%d,%.3f,%.3f", i, x, y), fuzzyCompare(region, string.upper(name)))
305 yield_countdown = yield_countdown - 1
306 if yield_countdown == 0 then
307 yield_countdown = yield_countdown_max
308 coroutine.yield(QHFormat("SEARCHING_STATE", QHText("SEARCHING_ZONES")))
316 return QHText("SEARCHING_DONE")
319 local function ReturnArgument(x)
320 return x
323 function search_frame:PerformSearch(input)
324 QuestHelper:TextOut("/qh find is currently disabled. Sorry! I'll get it back in once I can.")
325 do return end
326 if not self.routine then
327 self.query = string.gsub(input, "|c.-|H.-|h%[(.-)%]|h|r", ReturnArgument)
328 self.routine = coroutine.create(self.SearchRoutine)
329 self:Show()
330 QH_Hook(self, "OnUpdate", self.OnUpdate)
334 function QuestHelper:PerformSearch(query)
335 search_frame:PerformSearch(query)
338 function QuestHelper:PerformCustomSearch(func)
339 if not search_frame:GetScript("OnUpdate") then
340 search_frame:Show()
341 QH_Hook(search_frame, "OnUpdate", func)
345 function QuestHelper:StopCustomSearch()
346 if not search_frame.routine then
347 search_frame:Hide()
348 QH_Hook(search_frame, "OnUpdate", nil)
352 SLASH_QuestHelperFind1 = "/qhfind"
353 SLASH_QuestHelperFind2 = "/find"
354 SlashCmdList["QuestHelperFind"] = function (text) QuestHelper:PerformSearch(text) end