update changes
[QuestHelper.git] / tracker.lua
blobb9a1bc168389c269428f7b6e63cb79bd86eeee86
1 QuestHelper_File["tracker.lua"] = "Development Version"
3 local tracker = CreateFrame("Frame", "QuestHelperQuestWatchFrame", UIParent)
4 local minbutton = CreateFrame("Button", "QuestHelperQuestWatchFrameMinimizeButton", UIParent)
6 QuestHelper.tracker = tracker
8 tracker:Hide()
9 tracker:SetWidth(200)
10 tracker:SetHeight(100)
11 tracker.dw, tracker.dh = 200, 100
13 local in_tracker = 0
15 minbutton:SetFrameStrata("DIALOG")
16 minbutton:Hide()
17 minbutton:SetPoint("CENTER", QuestWatchFrame) -- We default to a different location to make it more likely to display the right item.
18 minbutton:SetMovable(true)
19 minbutton:SetUserPlaced(true)
20 minbutton:SetWidth(10)
21 minbutton:SetHeight(5)
22 local minbutton_tex = minbutton:CreateTexture()
23 minbutton_tex:SetAllPoints()
24 minbutton_tex:SetTexture(.8, .8, .8)
26 tracker:SetPoint("CENTER", minbutton)
28 function minbutton:moved()
29 local x, y = self:GetCenter()
30 local w, h = UIParent:GetWidth(), UIParent:GetHeight()
31 local anchor = (y < h*.45 and "BOTTOM" or y > h*.55 and "TOP" or "")..(x < w*.45 and "LEFT" or x > w*.55 and "RIGHT" or "")
33 tracker:ClearAllPoints()
34 tracker:SetPoint("CENTER", self)
36 if anchor ~= "" then
37 tracker:SetPoint(anchor, self)
38 end
39 end
41 function QuestHelper:ResetTrackerPosition(cmd)
42 minbutton:ClearAllPoints()
43 if string.find(cmd, "center") then
44 minbutton:SetPoint("CENTER", nil, "CENTER", 100, 100)
45 else
46 minbutton:SetPoint("RIGHT", nil, "RIGHT", -20, 230)
47 end
48 minbutton:moved()
49 self:TextOut("Quest tracker postion reset.")
50 end
52 minbutton:SetScript("OnEvent", minbutton.moved)
53 minbutton:RegisterEvent("DISPLAY_SIZE_CHANGED")
54 minbutton:RegisterEvent("PLAYER_ENTERING_WORLD")
56 minbutton:SetScript("OnClick", function ()
57 QuestHelper_Pref.track_minimized = not QuestHelper_Pref.track_minimized
58 if QuestHelper_Pref.track_minimized then
59 tracker:Hide()
60 else
61 tracker:Show()
62 end
63 end)
65 minbutton:RegisterForDrag("LeftButton")
67 minbutton:SetScript("OnDragStart", function(self)
68 if self:IsVisible() then
69 self:StartMoving()
70 self:SetScript("OnUpdate", self.moved)
71 end
72 end)
74 minbutton:SetScript("OnDragStop", function(self)
75 self:SetScript("OnUpdate", nil)
76 self:StopMovingOrSizing()
77 self:moved()
78 end)
80 minbutton:SetScript("OnEnter", function (self)
81 self:SetAlpha(1)
82 end)
84 minbutton:SetScript("OnLeave", function (self)
85 self:SetAlpha(QuestHelper_Pref.track_minimized and .3 or .5)
86 end)
88 local unused_items = {}
89 local used_items = {}
91 local function itemupdate(item, delta)
92 local done = true
94 local a = item:GetAlpha()
95 a = a + delta
97 if a < 1 then
98 item:SetAlpha(a)
99 done = false
100 else
101 item:SetAlpha(1)
104 local t = item.t + delta
106 if t < 1 then
107 item.t = t
108 local it = 1-t
109 local sp = math.sqrt(t-t*t)
110 item.x, item.y = item.sx*it+item.dx*t+(item.sy-item.dy)*sp, item.sy*it+item.dy*t+(item.dx-item.sx)*sp
111 done = false
112 else
113 item.t = 1
114 item.x, item.y = item.dx, item.dy
117 item:ClearAllPoints()
118 item:SetPoint("TOPLEFT", tracker, "TOPLEFT", item.x, item.y)
120 if done then
121 item:SetScript("OnUpdate", nil)
125 local function itemfadeout(item, delta)
126 local a = item:GetAlpha()
127 a = a - delta
129 if a > 0 then
130 item:SetAlpha(a)
131 else
132 item:SetAlpha(1)
133 item:Hide()
134 item:SetScript("OnUpdate", nil)
135 return
138 local t = item.t + delta
140 if t < 1 then
141 item.t = t
142 local it = 1-t
143 local sp = math.sqrt(t-t*t)
144 item.x, item.y = item.sx*it+item.dx*t+(item.sy-item.dy)*sp, item.sy*it+item.dy*t+(item.dx-item.sx)*sp
145 else
146 item.t = 1
147 item.x, item.y = item.dx, item.dy
150 item:ClearAllPoints()
151 item:SetPoint("TOPLEFT", tracker, "TOPLEFT", item.x, item.y)
154 function QH_ToggleQuestLog() -- This seems to be gone in 3.0, so I'm adding it here.
155 if (QuestLogFrame:IsShown()) then
156 HideUIPanel(QuestLogFrame);
157 else
158 ShowUIPanel(QuestLogFrame);
163 local function itemclick(item, button)
164 if button == "RightButton" then
165 local quest = item.quest
166 local index = 1
167 while true do
168 local title = GetQuestLogTitle(index)
169 if not title then break end
171 if title == quest then
172 if UberQuest then
173 -- UberQuest needs a little extra effort to work properly.
175 if UberQuest_List:IsShown() and GetQuestLogSelection() == index then
176 QH_ToggleQuestLog()
177 else
178 QuestLog_SetSelection(index)
180 -- By hiding the list, the replaced ToggleQuestLog function should try to reshow it
181 -- and in the process update the frames to reflect the selected quest.
182 UberQuest_List:Hide()
183 UberQuest_Details:Show()
184 QH_ToggleQuestLog()
186 else
187 -- This code seems to work properly with the builtin questlog, as well as bEQL and DoubleWide.
189 if QuestLogFrame:IsShown() and GetQuestLogSelection() == index then
190 -- If the selected quest is already being shown, hide it.
191 QH_ToggleQuestLog()
192 else
193 -- Otherwise, select it and show it.
194 QuestLog_SetSelection(index)
196 if not QuestLogFrame:IsShown() then
197 QH_ToggleQuestLog()
202 return
205 index = index + 1
210 local function addItem(name, quest, obj, y, qname)
211 local x = qname and 4 or 20
212 local item = used_items[quest] and used_items[quest][obj]
213 if not item then
214 item = next(unused_items)
215 if item then
216 unused_items[item] = nil
217 else
218 item = CreateFrame("Frame", nil, tracker)
219 item.text = item:CreateFontString()
220 item.text:SetShadowColor(0, 0, 0, .8)
221 item.text:SetShadowOffset(1, -1)
222 item.text:SetPoint("TOPLEFT", item)
225 if qname then
226 item.text:SetFont(QuestHelper.font.serif, 12)
227 item.text:SetTextColor(.82, .65, 0)
228 else
229 item.text:SetFont(QuestHelper.font.sans, 12)
230 item.text:SetTextColor(.82, .82, .82)
233 if not used_items[quest] then used_items[quest] = {} end
235 used_items[quest][obj] = item
236 item.sx, item.sy, item.x, item.y, item.dx, item.dy, item.t = x+30, y, x, y, x, y, 0
237 item:SetScript("OnUpdate", itemupdate)
238 item:SetAlpha(0)
239 item:Show()
242 item.used = true
244 item.quest = qname
245 item.text:SetText(name)
246 local w, h = item.text:GetWidth(), item.text:GetHeight()
247 item:SetWidth(w)
248 item:SetHeight(h)
250 if qname then
251 item:SetScript("OnMouseDown", itemclick)
252 item:EnableMouse(true)
255 if item.dx ~= x or item.dy ~= y then
256 item.sx, item.sy, item.dx, item.dy = item.x, item.y, x, y
257 item.t = 0
258 item:SetScript("OnUpdate", itemupdate)
261 return w+x+4, h
264 local function ccode(r1, g1, b1, r2, g2, b2, p)
265 local ip
266 p, ip = p*255, 255-p*255
267 return string.format("|cff%02x%02x%02x", r1*ip+r2*p, g1*ip+g2*p, b1*ip+b2*p)
270 local function qname(title, level)
271 if QuestHelper_Pref.track_level then
272 title = string.format("[%d] %s", level, title)
275 if QuestHelper_Pref.track_qcolour then
276 local player_level = QuestHelper.player_level
277 local delta = level - player_level
279 local colour
281 if delta >= 5 then
282 colour = "|cffff0000"
283 elseif delta >= 0 then
284 colour = ccode(1, 1, 0, 1, 0, 0, delta/5)
285 else
286 local grey
288 if player_level >= 60 then grey = player_level - 9
289 elseif player_level >= 40 then grey = player_level - math.floor(player_level/5) - 1
290 elseif player_level >= 6 then grey = player_level - math.floor(player_level/10) - 5
291 else grey = 0 end
293 if level > grey then
294 colour = ccode(0, 1, 0, 1, 1, 0, (grey-level)/(grey-player_level))
295 else
296 colour = ccode(.4, .4, .4, .2, .8, .2, (1-level)/(1-grey))
300 title = string.format("%s%s", colour, title)
303 return title
306 local function oname(text, pct)
307 if QuestHelper_Pref.track_ocolour then
308 text = string.format("%s%s", pct < 0.5 and ccode(1, 0, 0, 1, 1, 0, pct*2) or ccode(1, 1, 0, 0, 1, 0, pct*2-1), text)
311 return text
314 local function removeUnusedItem(quest, obj, item)
315 unused_items[item] = true
316 used_items[quest][obj] = nil
317 if not next(used_items[quest]) then used_items[quest] = nil end
318 item.used = false
319 item.t = 0
320 item.sx, item.sy = item.x, item.y
321 item.dx, item.dy = item.x+30, item.y
322 item:SetScript("OnMouseDown", nil)
323 item:EnableMouse(false)
324 item:SetScript("OnUpdate", itemfadeout)
327 local resizing = false
328 local check_delay = 4
329 local seen = {}
330 local obj_list = {}
331 local obj_index_lookup = {}
332 local quest_lookup = {}
333 local was_inside = false
335 local function watched_filter(obj)
336 return obj:IsWatched()
339 local function objlist_sort(a, b)
340 return (obj_index_lookup[a] or 0) < (obj_index_lookup[b] or 0)
343 function tracker:reset()
344 for quest, objs in pairs(used_items) do
345 for obj, item in pairs(objs) do
346 removeUnusedItem(quest, obj, item)
347 check_delay = 1e99
352 local function addobj(objective, seen, obj_index_lookup, filter, x, y, gap)
353 local count = 0
354 local quest
356 if objective.cat == "quest" then
357 quest = objective
358 else
359 quest = objective.quest
362 if quest and quest.watched and not seen[quest] and (not filter or filter(quest)) then
363 seen[quest] = true
365 local level, name = string.match(quest.obj, "^(%d+)/%d*/(.*)$")
367 if not level then
368 level, name = string.match(quest.obj, "^(%d+)/(.*)$")
369 if not level then
370 level, name = 1, quest.obj
374 level = tonumber(level) or 1
376 count = count + 1
377 local w, h = addItem(qname(name, level), true, quest, -(y+gap), name)
378 x = math.max(x, w)
379 y = y + h + gap
380 gap = 2
382 for obj in pairs(quest.swap_after or quest.after) do
383 if obj.progress then
384 table.insert(obj_list, obj)
388 table.sort(obj_list, objlist_sort)
390 for i, obj in ipairs(obj_list) do
391 local pct, text = 0, obj.obj
392 local seen_sum, seen_max = 0, 0
394 if obj.progress then
395 local seen_have, seen_need = QuestHelper:CreateTable(), QuestHelper:CreateTable()
397 for user, progress in pairs(obj.progress) do
398 seen_sum = seen_sum + progress[3]
399 seen_max = seen_max + 1
400 seen_have[progress[1]] = true
401 seen_need[progress[2]] = true
404 if seen_max > 0 then
405 pct = seen_sum / seen_max
406 local list = QuestHelper:CreateTable()
408 for val in pairs(seen_have) do
409 table.insert(list, val)
412 table.sort(list)
414 local have = table.concat(list, ", ")
416 for i = #list,1,-1 do
417 list[i] = nil
420 for val in pairs(seen_need) do
421 table.insert(list, val)
424 if #list ~= 1 or list[1] ~= 1 then
425 -- If there is only one thing needed, ignore the progress, it's redundant.
426 -- It's either shown or it isn't.
428 table.sort(list)
430 local need = table.concat(list, ", ")
432 text = string.format((tonumber(have) and tonumber(need) and QUEST_ITEMS_NEEDED) or QUEST_FACTION_NEEDED,
433 text, have, need)
436 QuestHelper:ReleaseTable(list)
439 QuestHelper:ReleaseTable(seen_have)
440 QuestHelper:ReleaseTable(seen_need)
443 if seen_sum ~= seen_max then
444 count = count + 1
445 w, h = addItem(oname(text, pct), quest, obj, -y)
446 x = math.max(x, w)
447 y = y + h
451 for i = #obj_list, 1, -1 do obj_list[i] = nil end
454 return x, y, gap, count
457 function tracker:update(delta)
458 if not delta then
459 -- This is called without a value when the questlog is updated.
460 -- We'll make sure we update the display on the next update.
461 check_delay = 1e99
462 return
465 if resizing then
466 local t = self.t+delta
468 if t > 1 then
469 self:SetWidth(self.dw)
470 self:SetHeight(self.dh)
471 resizing = false
472 else
473 self.t = t
474 local it = 1-t
475 self:SetWidth(self.sw*it+self.dw*t)
476 self:SetHeight(self.sh*it+self.dh*t)
480 -- Manually checking if the mouse is in the frame, because if I used on OnEnter, i'd have to enable mouse input,
481 -- and if I did that, it would prevent the player from using the mouse to change the view if they clicked inside
482 -- the tracker.
483 local x, y = GetCursorPosition()
484 local s = 1/self:GetEffectiveScale()
485 x, y = x*s, y*s
487 local inside = x >= self:GetLeft() and y >= self:GetBottom() and x < self:GetRight() and y < self:GetTop()
488 if inside ~= was_inside then
489 was_inside = inside
490 if inside then
491 minbutton:SetAlpha(.7)
492 elseif not QuestHelper_Pref.track_minimized then
493 minbutton:SetAlpha(0)
497 check_delay = check_delay + delta
498 if check_delay > 5 then
499 check_delay = 0
501 local quests = QuestHelper.quest_log
502 local added = 0
503 local x, y = 4, 4
504 local gap = 0
505 local track_size = QuestHelper_Pref.track_size
507 for quest, objs in pairs(used_items) do
508 for obj, item in pairs(objs) do
509 item.used = false
513 for i, objective in pairs(QuestHelper.route) do
514 if objective.watched then
515 obj_index_lookup[objective] = i
519 for q, data in pairs(QuestHelper.quest_log) do
520 quest_lookup[data.index] = q
523 -- Add Quests that are watched but not in the route.
524 if UberQuest then
525 local uq_settings = UberQuest_Config[UnitName("player")]
526 if uq_settings then
527 local list = uq_settings.selected
528 if list then
529 local i = 1
530 while true do
531 local name = GetQuestLogTitle(i)
532 if not name then break end
533 quest_lookup[name] = quest_lookup[i]
534 i = i + 1
536 for name in pairs(list) do
537 local q = quest_lookup[name]
538 if q and not obj_index_lookup[q] then
539 local count
540 x, y, gap, count = addobj(q, seen, obj_index_lookup, nil, x, y, gap)
541 added = added + count
546 else
547 for i = 1,GetNumQuestWatches() do
548 local q = quest_lookup[GetQuestIndexForWatch(i)]
549 if q and not obj_index_lookup[q] then
550 local count
551 x, y, gap, count = addobj(q, seen, obj_index_lookup, nil, x, y, gap)
552 added = added + count
557 -- Add Quests that are watched and are in the route.
558 for i, objective in ipairs(QuestHelper.route) do
559 local count
560 x, y, gap, count = addobj(objective, seen, obj_index_lookup, watched_filter, x, y, gap)
561 added = added + count
564 -- Add an extra large gap to seperate the watched objectives from the automatic objectives.
565 gap = gap * 5
567 -- Add Quests that aren't watched and are in the route.
568 if added <= track_size then
569 for i, objective in ipairs(QuestHelper.route) do
570 local count
571 x, y, gap, count = addobj(objective, seen, obj_index_lookup, nil, x, y, gap)
572 added = added + count
574 if added > track_size then
575 break
580 for obj in pairs(obj_index_lookup) do
581 obj_index_lookup[obj] = nil
584 for key in pairs(quest_lookup) do
585 quest_lookup[key] = nil
588 for quest, objs in pairs(used_items) do
589 for obj, item in pairs(objs) do
590 if not item.used then
591 removeUnusedItem(quest, obj, item)
596 for key in pairs(seen) do
597 seen[key] = nil
600 y = y+4
602 if x ~= tracker.dw or y ~= tracker.dy then
603 tracker.t = 0
604 tracker.sw, tracker.sh = tracker:GetWidth(), tracker:GetHeight()
605 tracker.dw, tracker.dh = x, y
606 resizing = true
609 added = 0
613 tracker:SetScript("OnUpdate", tracker.update)
615 -- Some hooks to update the tracker when quests are added or removed.
616 local orig_AddQuestWatch, orig_RemoveQuestWatch = AddQuestWatch, RemoveQuestWatch
618 function AddQuestWatch(...)
619 tracker:update()
620 return orig_AddQuestWatch(...)
623 function RemoveQuestWatch(...)
624 tracker:update()
625 return orig_RemoveQuestWatch(...)
628 -------------------------------------------------------------------------------------------------
629 -- This batch of stuff is to make sure the original tracker (and any modifications) stay hidden
631 local orig_TrackerOnShow = QuestWatchFrame:GetScript("OnShow")
632 local orig_TrackerBackdropOnShow -- bEQL (and perhaps other mods) add a backdrop to the tracker
633 local TrackerBackdropFound = false
635 local function TrackerBackdropOnShow(self, ...)
636 if QuestHelper_Pref.track and not QuestHelper_Pref.hide then
637 TrackerBackdropFound:Hide()
640 if orig_TrackerBackdropOnShow then
641 return orig_TrackerBackdropOnShow(self, ...)
645 function tracker:HideDefaultTracker()
646 -- The easy part: hide the original tracker
647 QuestWatchFrame:Hide()
649 -- The harder part: check if a known backdrop is present (but we don't already know about it).
650 -- If it is, make sure it's hidden, and hook its OnShow to make sure it stays that way.
651 -- Unfortunately, I can't figure out a good time to check for this once, so we'll just have
652 -- to keep checking. Hopefully, this won't happen too often.
653 if not TrackerBackdropFound then
654 if QuestWatchFrameBackdrop then
655 -- Found bEQL's QuestWatchFrameBackdrop...
656 TrackerBackdropFound = QuestWatchFrameBackdrop
659 if TrackerBackdropFound then
660 -- OK, we found something - so hide it, and make sure it doesn't rear its ugly head again
661 TrackerBackdropFound:Hide()
663 orig_TrackerBackdropOnShow = TrackerBackdropFound:GetScript("OnShow")
664 TrackerBackdropFound:SetScript("OnShow", TrackerBackdropOnShow)
669 function tracker:ShowDefaultTracker()
670 QuestWatchFrame:Show()
672 -- Make sure the default tracker is up to date on what what's being watched and what isn't.
673 QuestWatch_Update()
675 if TrackerBackdropFound then
676 TrackerBackdropFound:Show()
680 local function QuestWatchFrameOnShow(self, ...)
681 if QuestHelper_Pref.track and not QuestHelper_Pref.hide then
682 tracker:HideDefaultTracker()
685 if orig_TrackerOnShow then
686 return orig_TrackerOnShow(self, ...)
690 QuestWatchFrame:SetScript("OnShow", QuestWatchFrameOnShow)
692 function QuestHelper:ShowTracker()
693 tracker:HideDefaultTracker()
694 minbutton:Show()
696 if QuestHelper_Pref.track_minimized then
697 minbutton:SetAlpha(.3)
698 else
699 minbutton:SetAlpha(0)
700 tracker:Show()
704 function QuestHelper:HideTracker()
705 tracker:ShowDefaultTracker()
706 tracker:Hide()
707 minbutton:Hide()