Merge branch 'master' into flightpoint-rewrite
[QuestHelper.git] / custom.lua
blobcacdaf6d770acfe222d68c1864fbb39e76e395fd
1 -- This next bit of stuff is for fuzzy string comarisons.
3 local row, prow = {}, {}
5 local difftable = {}
7 for i = 65,90 do
8 local a = {}
9 difftable[i-64] = a
10 for j = 65,90 do
11 a[j-64] = i==j and 0 or 1
12 end
13 end
15 local function setgroup(a, w)
16 for i = 1,string.len(a)-1 do
17 for j = i+1,string.len(a) do
18 local c1, c2 = string.byte(a,i)-64, string.byte(a,j)-64
20 difftable[c1][c2] = math.min(w, difftable[c1][c2])
21 difftable[c2][c1] = math.min(w, difftable[c2][c1])
22 end
23 end
24 end
26 -- Characters that sound similar. At least in my opinion.
27 setgroup("BCDFGHJKLMNPQRSTVWXZ", 0.9)
28 setgroup("AEIOUY", 0.6)
29 setgroup("TD", 0.6)
30 setgroup("CKQ", 0.4)
31 setgroup("MN", 0.4)
32 setgroup("EIY", 0.3)
33 setgroup("UO", 0.2)
34 setgroup("SZ", 0.6)
36 local function diffness(a, b)
37 if a >= 65 and a <=90 then
38 if b >= 65 and b <= 90 then
39 return difftable[a-64][b-64]
40 else
41 return 1
42 end
43 elseif b >= 65 and b <= 90 then
44 return 1
45 else
46 return 0
47 end
48 end
50 local function fuzzyCompare(a, b)
51 local m, n = string.len(a), string.len(b)
53 if n == 0 or m == 0 then
54 return n == m and 0 or 1
55 end
57 for j = 1,n+1 do
58 row[j] = j-1
59 end
61 for i = 1,m do
62 row, prow = prow, row
63 row[1] = i
65 for j = 1,n do
66 row[j+1] = math.min(prow[j+1]+1, row[j]+.4, prow[j]+diffness(string.byte(a,i), string.byte(b,j)))
67 end
68 end
70 return row[n+1]/math.max(n,m)
71 end
73 local search_frame = CreateFrame("Button", nil, UIParent)
74 search_frame.text = search_frame:CreateFontString()
75 search_frame.text:SetFont("Fonts\\ARIALN.TTF", 15)
76 search_frame.text:SetTextColor(1, 1, 1)
77 search_frame.text:SetJustifyH("CENTER")
78 search_frame.text:SetJustifyV("MIDDLE")
79 search_frame.text:SetDrawLayer("OVERLAY")
80 search_frame.text:SetAllPoints()
81 search_frame.text:Show()
82 search_frame.background = search_frame:CreateTexture()
83 search_frame.background:SetTexture(0, 0, 0, 0.5)
84 search_frame.background:SetDrawLayer("BACKGROUND")
85 search_frame.background:SetAllPoints()
86 search_frame.background:Show()
87 search_frame:SetPoint("CENTER", UIParent, "CENTER")
88 search_frame:Hide()
90 search_frame.results = {}
92 function search_frame:SetText(text)
93 self.text:SetText(text)
94 self:SetWidth(self.text:GetWidth()+10)
95 self:SetHeight(self.text:GetHeight()+10)
96 end
98 function search_frame:OnUpdate()
99 if self.routine and coroutine.status(self.routine) ~= "dead" then
100 local no_error, display = coroutine.resume(self.routine, self, self.query)
101 if no_error then
102 self:SetText(display)
103 else
104 QuestHelper:TextOut("Searching co-routine just exploded: "..display)
106 else
107 self:ShowResults()
108 self.routine = nil
109 self:SetScript("OnUpdate", nil)
110 self:Hide()
114 function QuestHelper:ToggleUserObjective(cat, what)
115 local objective = self:GetObjective(cat, what)
117 if self.user_objectives[objective] then
118 self:TextOut(QHFormat("REMOVED_OBJ", self.user_objectives[objective]))
119 self:RemoveObjectiveWatch(objective, self.user_objectives[objective])
120 self.user_objectives[objective] = nil
121 elseif objective:Known() then
122 local name
123 if cat == "loc" then
124 local _, _, i, x, y = string.find(what, "^(%d+),([%d%.]+),([%d%.]+)$")
125 name = QHFormat("USER_OBJ", self:HighlightText(QuestHelper_NameLookup[tonumber(i)])..": "..self:HighlightText(x*100)..", "..self:HighlightText(y*100))
126 else
127 name = QHFormat("USER_OBJ", self:HighlightText(string.gsub(cat, "^(.)", string.upper))..": "..self:HighlightText(what))
130 objective.priority = 1
131 self.user_objectives[objective] = name
132 self:AddObjectiveWatch(objective, name)
134 self:TextOut(QHFormat("CREATED_OBJ", name))
135 else
136 self:TextOut(QHText("UNKNOWN_OBJ"))
140 function search_frame:CreateResultItem(r, menu)
141 local item
143 if r.cat == "loc" then
144 local _, _, i, x, y = string.find(r.what, "^(%d+),([%d%.]+),([%d%.]+)$")
145 item = QuestHelper:CreateMenuItem(menu, QuestHelper_NameLookup[tonumber(i)]..": "..(x*100)..", "..(y*100).." ["..QuestHelper:PercentString(1-r.w).."]")
146 item:AddTexture(QuestHelper:CreateIconTexture(item, 6), true)
147 else
148 item = QuestHelper:CreateMenuItem(menu, r.what .. " ["..QuestHelper:PercentString(1-r.w).."]")
149 item:AddTexture(QuestHelper:CreateIconTexture(item, (r.cat == "monster" and 1) or 2), true)
152 item:SetFunction(QuestHelper.ToggleUserObjective, QuestHelper, r.cat, r.what)
154 return item
157 function search_frame:ShowResults()
158 local menu = QuestHelper:CreateMenu()
159 QuestHelper:CreateMenuTitle(menu, QHText("RESULTS_TITLE"))
161 if #self.results == 0 then
162 QuestHelper:CreateMenuItem(menu, "NO_RESULTS")
163 else
164 for i, r in ipairs(self.results) do
165 self:CreateResultItem(r, menu)
169 menu:ShowAtCursor()
170 self:ClearResults()
173 function search_frame:ClearResults()
174 while #self.results > 0 do
175 QuestHelper:ReleaseTable(table.remove(self.results))
179 function search_frame:AddResult(cat, what, w)
180 local r = self.results
181 local mn, mx = 1, #r+1
183 while mn ~= mx do
184 local m = math.floor((mn+mx)*0.5)
186 if r[m].w < w then
187 mn = m+1
188 else
189 mx = m
193 if mn <= 20 then
194 if r[mn] and r[mn].cat == cat and r[mn].what == what then
195 -- Don't add the same item twice.
196 -- Might miss it if multiple items have the same score. Dont care.
197 return
200 if #r >= 20 then
201 QuestHelper:ReleaseTable(table.remove(r, 20))
204 local obj = QuestHelper:CreateTable()
205 obj.cat = cat
206 obj.what = what
207 obj.w = w
208 table.insert(r, mn, obj)
212 function search_frame:SearchRoutine(input)
213 if input == "" then
214 for obj in pairs(QuestHelper.user_objectives) do
215 self:AddResult(obj.cat, obj.obj, 0)
217 return
220 input = string.upper(input)
221 local _, _, command, argument = string.find(input, "^%s*([^%s]-)%s+(.-)%s*$")
223 local search_item, search_npc, search_loc = false, false, false
225 if command and argument then
226 if command == "ITEM" then
227 search_item, input = true, argument
228 elseif command == "NPC" or command == "MONSTER" then
229 search_npc, input = true, argument
230 elseif command == "LOCATION" or command == "LOC" then
231 search_loc, input = true, argument
232 else
233 search_item, search_npc, search_loc = true, true, true
235 else
236 search_item, search_npc, search_loc = true, true, true
239 local yield_countdown_max = math.max(1, math.floor(2000/string.len(input)+0.5))
240 local yield_countdown = yield_countdown_max
242 if search_item then
243 local list = QuestHelper_Objectives["item"]
244 if list then for n in pairs(list) do
245 self:AddResult("item", n, fuzzyCompare(input, string.upper(n)))
246 yield_countdown = yield_countdown - 1
247 if yield_countdown == 0 then
248 yield_countdown = yield_countdown_max
249 coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_LOCAL", QHText("SEARCHING_ITEMS"))))
251 end end
253 list = QuestHelper_StaticData[QuestHelper.locale].objective
254 list = list and list.item
255 if list then for n in pairs(list) do
256 self:AddResult("item", n, fuzzyCompare(input, string.upper(n)))
257 yield_countdown = yield_countdown - 1
258 if yield_countdown == 0 then
259 yield_countdown = yield_countdown_max
260 coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_STATIC", QHText("SEARCHING_ITEMS"))))
262 end end
265 if search_npc then
266 local list = QuestHelper_Objectives["monster"]
267 if list then for n in pairs(list) do
268 self:AddResult("monster", n, fuzzyCompare(input, string.upper(n)))
269 yield_countdown = yield_countdown - 1
270 if yield_countdown == 0 then
271 yield_countdown = yield_countdown_max
272 coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_LOCAL", QHText("SEARCHING_NPCS"))))
274 end end
276 list = QuestHelper_StaticData[QuestHelper.locale].objective
277 list = list and list.monster
278 if list then for n in pairs(list) do
279 self:AddResult("monster", n, fuzzyCompare(input, string.upper(n)))
280 yield_countdown = yield_countdown - 1
281 if yield_countdown == 0 then
282 yield_countdown = yield_countdown_max
283 coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_STATIC", QHText("SEARCHING_NPCS"))))
285 end end
288 if search_loc then
289 local _, _, region, x, y = string.find(input, "^%s*([^%d%.]-)%s*([%d%.]+)%s*[,;:]?%s*([%d%.]+)%s*$")
291 if region then
292 x, y = tonumber(x), tonumber(y)
293 if x and y then
294 x, y = x*0.01, y*0.01
296 if region == "" then
297 self:AddResult("loc", string.format("%d,%.3f,%.3f", QuestHelper.i, x, y), 0)
298 else
299 for i, name in pairs(QuestHelper_NameLookup) do
300 self:AddResult("loc", string.format("%d,%.3f,%.3f", i, x, y), fuzzyCompare(region, string.upper(name)))
301 yield_countdown = yield_countdown - 1
302 if yield_countdown == 0 then
303 yield_countdown = yield_countdown_max
304 coroutine.yield(QHFormat("SEARCHING_STATE", QHText("SEARCHING_ZONES")))
312 return QHText("SEARCHING_DONE")
315 local function ReturnArgument(x)
316 return x
319 function search_frame:PerformSearch(input)
320 if not self.routine then
321 self.query = string.gsub(input, "|c.-|H.-|h%[(.-)%]|h|r", ReturnArgument)
322 self.routine = coroutine.create(self.SearchRoutine)
323 self:Show()
324 self:SetScript("OnUpdate", self.OnUpdate)
328 function QuestHelper:PerformSearch(query)
329 search_frame:PerformSearch(query)
332 function QuestHelper:PerformCustomSearch(func)
333 if not search_frame:GetScript("OnUpdate") then
334 search_frame:Show()
335 search_frame:SetScript("OnUpdate", func)
339 function QuestHelper:StopCustomSearch()
340 if not search_frame.routine then
341 search_frame:Hide()
342 search_frame:SetScript("OnUpdate", nil)
346 SLASH_QuestHelperFind1 = "/qhfind"
347 SLASH_QuestHelperFind2 = "/find"
348 SlashCmdList["QuestHelperFind"] = function (text) QuestHelper:PerformSearch(text) end