update changes
[QuestHelper.git] / custom.lua
blob1d127f6dbc75cc4e0e64e0e5d15c44c454a48d5a
1 QuestHelper_File["custom.lua"] = "Development Version"
2 QuestHelper_Loadtime["custom.lua"] = GetTime()
4 -- This next bit of stuff is for fuzzy string comarisons.
6 local row, prow = {}, {}
8 local difftable = {}
10 for i = 65,90 do
11 local a = {}
12 difftable[i-64] = a
13 for j = 65,90 do
14 a[j-64] = i==j and 0 or 1
15 end
16 end
18 local function setgroup(a, w)
19 for i = 1,string.len(a)-1 do
20 for j = i+1,string.len(a) do
21 local c1, c2 = string.byte(a,i)-64, string.byte(a,j)-64
23 difftable[c1][c2] = math.min(w, difftable[c1][c2])
24 difftable[c2][c1] = math.min(w, difftable[c2][c1])
25 end
26 end
27 end
29 -- Characters that sound similar. At least in my opinion.
30 setgroup("BCDFGHJKLMNPQRSTVWXZ", 0.9)
31 setgroup("AEIOUY", 0.6)
32 setgroup("TD", 0.6)
33 setgroup("CKQ", 0.4)
34 setgroup("MN", 0.4)
35 setgroup("EIY", 0.3)
36 setgroup("UO", 0.2)
37 setgroup("SZ", 0.6)
39 local function diffness(a, b)
40 if a >= 65 and a <=90 then
41 if b >= 65 and b <= 90 then
42 return difftable[a-64][b-64]
43 else
44 return 1
45 end
46 elseif b >= 65 and b <= 90 then
47 return 1
48 else
49 return 0
50 end
51 end
53 local function fuzzyCompare(a, b)
54 local m, n = string.len(a), string.len(b)
56 if n == 0 or m == 0 then
57 return n == m and 0 or 1
58 end
60 for j = 1,n+1 do
61 row[j] = j-1
62 end
64 for i = 1,m do
65 row, prow = prow, row
66 row[1] = i
68 for j = 1,n do
69 row[j+1] = math.min(prow[j+1]+1, row[j]+.4, prow[j]+diffness(string.byte(a,i), string.byte(b,j)))
70 end
71 end
73 return row[n+1]/math.max(n,m)
74 end
76 local search_frame = CreateFrame("Button", nil, UIParent)
77 search_frame.text = search_frame:CreateFontString()
78 search_frame.text:SetFont(QuestHelper.font.sans, 15)
79 search_frame.text:SetTextColor(1, 1, 1)
80 search_frame.text:SetJustifyH("CENTER")
81 search_frame.text:SetJustifyV("MIDDLE")
82 search_frame.text:SetDrawLayer("OVERLAY")
83 search_frame.text:SetAllPoints()
84 search_frame.text:Show()
85 search_frame.background = search_frame:CreateTexture()
86 search_frame.background:SetTexture(0, 0, 0, 0.5)
87 search_frame.background:SetDrawLayer("BACKGROUND")
88 search_frame.background:SetAllPoints()
89 search_frame.background:Show()
90 search_frame:SetPoint("CENTER", UIParent, "CENTER")
91 search_frame:Hide()
93 search_frame.results = {}
95 function search_frame:SetText(text)
96 self.text:SetText(text)
97 self:SetWidth(self.text:GetWidth()+10)
98 self:SetHeight(self.text:GetHeight()+10)
99 end
101 function search_frame:OnUpdate()
102 if self.routine and coroutine.status(self.routine) ~= "dead" then
103 local no_error, display = coroutine.resume(self.routine, self, self.query)
104 if no_error then
105 self:SetText(display)
106 else
107 QuestHelper:TextOut("Searching co-routine just exploded: "..display)
109 else
110 self:ShowResults()
111 self.routine = nil
112 self:SetScript("OnUpdate", nil)
113 self:Hide()
117 function QuestHelper:ToggleUserObjective(cat, what)
118 local objective = self:GetObjective(cat, what)
120 if self.user_objectives[objective] then
121 self:TextOut(QHFormat("REMOVED_OBJ", self.user_objectives[objective]))
122 self:RemoveObjectiveWatch(objective, self.user_objectives[objective])
123 self.user_objectives[objective] = nil
124 elseif objective:Known() then
125 local name
126 if cat == "loc" then
127 local _, _, i, x, y = string.find(what, "^(%d+),([%d%.]+),([%d%.]+)$")
128 name = QHFormat("USER_OBJ", self:HighlightText(QuestHelper_NameLookup[tonumber(i)])..": "..self:HighlightText(x*100)..", "..self:HighlightText(y*100))
129 else
130 name = QHFormat("USER_OBJ", self:HighlightText(string.gsub(cat, "^(.)", string.upper))..": "..self:HighlightText(what))
133 objective.priority = 1
134 self.user_objectives[objective] = name
135 self:AddObjectiveWatch(objective, name)
137 self:TextOut(QHFormat("CREATED_OBJ", name))
138 else
139 self:TextOut(QHText("UNKNOWN_OBJ"))
143 function search_frame:CreateResultItem(r, menu)
144 local item
146 if r.cat == "loc" then
147 local _, _, i, x, y = string.find(r.what, "^(%d+),([%d%.]+),([%d%.]+)$")
148 item = QuestHelper:CreateMenuItem(menu, QuestHelper_NameLookup[tonumber(i)]..": "..(x*100)..", "..(y*100).." ["..QuestHelper:PercentString(1-r.w).."]")
149 item:AddTexture(QuestHelper:CreateIconTexture(item, 6), true)
150 else
151 item = QuestHelper:CreateMenuItem(menu, r.what .. " ["..QuestHelper:PercentString(1-r.w).."]")
152 item:AddTexture(QuestHelper:CreateIconTexture(item, (r.cat == "monster" and 1) or 2), true)
155 item:SetFunction(QuestHelper.ToggleUserObjective, QuestHelper, r.cat, r.what)
157 return item
160 function search_frame:ShowResults()
161 local menu = QuestHelper:CreateMenu()
162 QuestHelper:CreateMenuTitle(menu, QHText("RESULTS_TITLE"))
164 if #self.results == 0 then
165 QuestHelper:CreateMenuItem(menu, QHText("NO_RESULTS"))
166 else
167 for i, r in ipairs(self.results) do
168 self:CreateResultItem(r, menu)
172 menu:ShowAtCursor()
173 self:ClearResults()
176 function search_frame:ClearResults()
177 while #self.results > 0 do
178 QuestHelper:ReleaseTable(table.remove(self.results))
182 function search_frame:AddResult(cat, what, w)
183 local r = self.results
184 local mn, mx = 1, #r+1
186 while mn ~= mx do
187 local m = math.floor((mn+mx)*0.5)
189 if r[m].w < w then
190 mn = m+1
191 else
192 mx = m
196 if mn <= 20 then
197 if r[mn] and r[mn].cat == cat and r[mn].what == what then
198 -- Don't add the same item twice.
199 -- Might miss it if multiple items have the same score. Dont care.
200 return
203 if #r >= 20 then
204 QuestHelper:ReleaseTable(table.remove(r, 20))
207 local obj = QuestHelper:CreateTable()
208 obj.cat = cat
209 obj.what = what
210 obj.w = w
211 table.insert(r, mn, obj)
215 function search_frame:SearchRoutine(input)
216 if input == "" then
217 for obj in pairs(QuestHelper.user_objectives) do
218 self:AddResult(obj.cat, obj.obj, 0)
220 return
223 input = string.upper(input)
224 local _, _, command, argument = string.find(input, "^%s*([^%s]-)%s+(.-)%s*$")
226 local search_item, search_npc, search_loc = false, false, false
228 if command and argument then
229 if command == "ITEM" then
230 search_item, input = true, argument
231 elseif command == "NPC" or command == "MONSTER" then
232 search_npc, input = true, argument
233 elseif command == "LOCATION" or command == "LOC" then
234 search_loc, input = true, argument
235 else
236 search_item, search_npc, search_loc = true, true, true
238 else
239 search_item, search_npc, search_loc = true, true, true
242 local yield_countdown_max = math.max(1, math.floor(2000/string.len(input)+0.5))
243 local yield_countdown = yield_countdown_max
245 if search_item then
246 local list = QuestHelper_Objectives_Local["item"]
247 if list then for n in pairs(list) do
248 self:AddResult("item", n, fuzzyCompare(input, string.upper(n)))
249 yield_countdown = yield_countdown - 1
250 if yield_countdown == 0 then
251 yield_countdown = yield_countdown_max
252 coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_LOCAL", QHText("SEARCHING_ITEMS"))))
254 end end
256 list = QuestHelper_StaticData[QuestHelper.locale].objective
257 list = list and list.item
258 if list then for n in pairs(list) do
259 self:AddResult("item", n, fuzzyCompare(input, string.upper(n)))
260 yield_countdown = yield_countdown - 1
261 if yield_countdown == 0 then
262 yield_countdown = yield_countdown_max
263 coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_STATIC", QHText("SEARCHING_ITEMS"))))
265 end end
268 if search_npc then
269 local list = QuestHelper_Objectives_Local["monster"]
270 if list then for n in pairs(list) do
271 self:AddResult("monster", n, fuzzyCompare(input, string.upper(n)))
272 yield_countdown = yield_countdown - 1
273 if yield_countdown == 0 then
274 yield_countdown = yield_countdown_max
275 coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_LOCAL", QHText("SEARCHING_NPCS"))))
277 end end
279 list = QuestHelper_StaticData[QuestHelper.locale].objective
280 list = list and list.monster
281 if list then for n in pairs(list) do
282 self:AddResult("monster", n, fuzzyCompare(input, string.upper(n)))
283 yield_countdown = yield_countdown - 1
284 if yield_countdown == 0 then
285 yield_countdown = yield_countdown_max
286 coroutine.yield(QHFormat("SEARCHING_STATE", QHFormat("SEARCHING_STATIC", QHText("SEARCHING_NPCS"))))
288 end end
291 if search_loc then
292 local _, _, region, x, y = string.find(input, "^%s*([^%d%.]-)%s*([%d%.]+)%s*[,;:]?%s*([%d%.]+)%s*$")
294 if region then
295 x, y = tonumber(x), tonumber(y)
296 if x and y then
297 x, y = x*0.01, y*0.01
299 if region == "" then
300 self:AddResult("loc", string.format("%d,%.3f,%.3f", QuestHelper.i, x, y), 0)
301 else
302 for i, name in pairs(QuestHelper_NameLookup) do
303 self:AddResult("loc", string.format("%d,%.3f,%.3f", i, x, y), fuzzyCompare(region, string.upper(name)))
304 yield_countdown = yield_countdown - 1
305 if yield_countdown == 0 then
306 yield_countdown = yield_countdown_max
307 coroutine.yield(QHFormat("SEARCHING_STATE", QHText("SEARCHING_ZONES")))
315 return QHText("SEARCHING_DONE")
318 local function ReturnArgument(x)
319 return x
322 function search_frame:PerformSearch(input)
323 self:TextOut("/qh find is currently disabled. Sorry! I'll get it back in once I can.")
324 do return end
325 if not self.routine then
326 self.query = string.gsub(input, "|c.-|H.-|h%[(.-)%]|h|r", ReturnArgument)
327 self.routine = coroutine.create(self.SearchRoutine)
328 self:Show()
329 self:SetScript("OnUpdate", self.OnUpdate)
333 function QuestHelper:PerformSearch(query)
334 search_frame:PerformSearch(query)
337 function QuestHelper:PerformCustomSearch(func)
338 if not search_frame:GetScript("OnUpdate") then
339 search_frame:Show()
340 search_frame:SetScript("OnUpdate", func)
344 function QuestHelper:StopCustomSearch()
345 if not search_frame.routine then
346 search_frame:Hide()
347 search_frame:SetScript("OnUpdate", nil)
351 SLASH_QuestHelperFind1 = "/qhfind"
352 SLASH_QuestHelperFind2 = "/find"
353 SlashCmdList["QuestHelperFind"] = function (text) QuestHelper:PerformSearch(text) end