update changes again
[QuestHelper.git] / tracker.lua
blobf3bdf71e2dd4a1e2ffa803162fbe898ae97f527e
1 QuestHelper_File["tracker.lua"] = "Development Version"
2 QuestHelper_Loadtime["tracker.lua"] = GetTime()
4 local tracker = CreateFrame("Frame", "QuestHelperQuestWatchFrame", UIParent)
5 local minbutton = CreateFrame("Button", "QuestHelperQuestWatchFrameMinimizeButton", UIParent)
7 QuestHelper.tracker = tracker
9 tracker:Hide()
10 tracker:SetWidth(200)
11 tracker:SetHeight(100)
12 tracker.dw, tracker.dh = 200, 100
14 local in_tracker = 0
16 minbutton:SetFrameStrata("DIALOG")
17 minbutton:Hide()
18 minbutton:SetPoint("CENTER", QuestWatchFrame) -- We default to a different location to make it more likely to display the right item.
19 minbutton:SetMovable(true)
20 minbutton:SetUserPlaced(true)
21 minbutton:SetWidth(10)
22 minbutton:SetHeight(5)
23 local minbutton_tex = minbutton:CreateTexture()
24 minbutton_tex:SetAllPoints()
25 minbutton_tex:SetTexture(.8, .8, .8)
27 tracker:SetPoint("CENTER", minbutton)
29 function minbutton:moved()
30 local x, y = self:GetCenter()
31 local w, h = UIParent:GetWidth(), UIParent:GetHeight()
32 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 "")
34 tracker:ClearAllPoints()
35 tracker:SetPoint("CENTER", self)
37 if anchor ~= "" then
38 tracker:SetPoint(anchor, self)
39 end
40 end
42 function QuestHelper:ResetTrackerPosition(cmd)
43 minbutton:ClearAllPoints()
44 if cmd and string.find(cmd, "center") then
45 minbutton:SetPoint("CENTER", nil, "CENTER", 100, 100)
46 else
47 minbutton:SetPoint("RIGHT", nil, "RIGHT", -20, 230)
48 end
49 minbutton:moved()
50 QuestHelper_Pref.track_minimized = false
51 tracker:Show()
52 self:TextOut("Quest tracker postion reset.")
53 end
55 minbutton:SetScript("OnEvent", minbutton.moved)
56 minbutton:RegisterEvent("DISPLAY_SIZE_CHANGED")
57 minbutton:RegisterEvent("PLAYER_ENTERING_WORLD")
59 minbutton:SetScript("OnClick", function ()
60 QuestHelper_Pref.track_minimized = not QuestHelper_Pref.track_minimized
61 if QuestHelper_Pref.track_minimized then
62 tracker:Hide()
63 else
64 tracker:Show()
65 end
66 end)
68 minbutton:RegisterForDrag("LeftButton")
70 minbutton:SetScript("OnDragStart", function(self)
71 if self:IsVisible() then
72 self:StartMoving()
73 self:SetScript("OnUpdate", self.moved)
74 end
75 end)
77 minbutton:SetScript("OnDragStop", function(self)
78 self:SetScript("OnUpdate", nil)
79 self:StopMovingOrSizing()
80 self:moved()
81 end)
83 minbutton:SetScript("OnEnter", function (self)
84 self:SetAlpha(1)
85 end)
87 minbutton:SetScript("OnLeave", function (self)
88 self:SetAlpha(QuestHelper_Pref.track_minimized and .3 or .5)
89 end)
91 local unused_items = {}
92 local used_items = {}
94 local function itemupdate(item, delta)
95 local done = true
97 local a = item:GetAlpha()
98 a = a + delta
100 if a < 1 then
101 item:SetAlpha(a)
102 done = false
103 else
104 item:SetAlpha(1)
107 local t = item.t + delta
109 if t < 1 then
110 item.t = t
111 local it = 1-t
112 local sp = math.sqrt(t-t*t)
113 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
114 done = false
115 else
116 item.t = 1
117 item.x, item.y = item.dx, item.dy
120 item:ClearAllPoints()
121 item:SetPoint("TOPLEFT", tracker, "TOPLEFT", item.x, item.y)
123 if done then
124 item:SetScript("OnUpdate", nil)
128 local function itemfadeout(item, delta)
129 local a = item:GetAlpha()
130 a = a - delta
132 if a > 0 then
133 item:SetAlpha(a)
134 else
135 item:SetAlpha(1)
136 item:Hide()
137 item:SetScript("OnUpdate", nil)
138 return
141 local t = item.t + delta
143 if t < 1 then
144 item.t = t
145 local it = 1-t
146 local sp = math.sqrt(t-t*t)
147 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
148 else
149 item.t = 1
150 item.x, item.y = item.dx, item.dy
153 item:ClearAllPoints()
154 item:SetPoint("TOPLEFT", tracker, "TOPLEFT", item.x, item.y)
157 function QH_ToggleQuestLog() -- This seems to be gone in 3.0, so I'm adding it here.
158 if (QuestLogFrame:IsShown()) then
159 HideUIPanel(QuestLogFrame);
160 else
161 ShowUIPanel(QuestLogFrame);
166 local function itemclick(item, button)
167 if button == "RightButton" then
168 local quest = item.quest
169 local index = 1
170 while true do
171 local title = GetQuestLogTitle(index)
172 if not title then break end
174 if title == quest then
175 if UberQuest then
176 -- UberQuest needs a little extra effort to work properly.
178 if UberQuest_List:IsShown() and GetQuestLogSelection() == index then
179 QH_ToggleQuestLog()
180 else
181 QuestLog_SetSelection(index)
183 -- By hiding the list, the replaced ToggleQuestLog function should try to reshow it
184 -- and in the process update the frames to reflect the selected quest.
185 UberQuest_List:Hide()
186 UberQuest_Details:Show()
187 QH_ToggleQuestLog()
189 else
190 -- This code seems to work properly with the builtin questlog, as well as bEQL and DoubleWide.
192 if QuestLogFrame:IsShown() and GetQuestLogSelection() == index then
193 -- If the selected quest is already being shown, hide it.
194 QH_ToggleQuestLog()
195 else
196 -- Otherwise, select it and show it.
197 QuestLog_SetSelection(index)
199 if not QuestLogFrame:IsShown() then
200 QH_ToggleQuestLog()
205 return
208 index = index + 1
213 local function addItem(name, quest, obj, y, qname)
214 local x = qname and 4 or 20
215 local item = used_items[quest] and used_items[quest][obj]
216 if not item then
217 item = next(unused_items)
218 if item then
219 unused_items[item] = nil
220 else
221 item = CreateFrame("Frame", nil, tracker)
222 item.text = item:CreateFontString()
223 item.text:SetShadowColor(0, 0, 0, .8)
224 item.text:SetShadowOffset(1, -1)
225 item.text:SetPoint("TOPLEFT", item)
228 if qname then
229 item.text:SetFont(QuestHelper.font.serif, 12)
230 item.text:SetTextColor(.82, .65, 0)
231 else
232 item.text:SetFont(QuestHelper.font.sans, 12)
233 item.text:SetTextColor(.82, .82, .82)
236 if not used_items[quest] then used_items[quest] = {} end
238 used_items[quest][obj] = item
239 item.sx, item.sy, item.x, item.y, item.dx, item.dy, item.t = x+30, y, x, y, x, y, 0
240 item:SetScript("OnUpdate", itemupdate)
241 item:SetAlpha(0)
242 item:Show()
245 item.used = true
247 item.quest = qname
248 item.text:SetText(name)
249 local w, h = item.text:GetWidth(), item.text:GetHeight()
250 item:SetWidth(w)
251 item:SetHeight(h)
253 if qname then
254 item:SetScript("OnMouseDown", itemclick)
255 item:EnableMouse(true)
258 if item.dx ~= x or item.dy ~= y then
259 item.sx, item.sy, item.dx, item.dy = item.x, item.y, x, y
260 item.t = 0
261 item:SetScript("OnUpdate", itemupdate)
264 return w+x+4, h
267 local function ccode(r1, g1, b1, r2, g2, b2, p)
268 local ip
269 p, ip = p*255, 255-p*255
270 return string.format("|cff%02x%02x%02x", r1*ip+r2*p, g1*ip+g2*p, b1*ip+b2*p)
273 local function qname(title, level)
274 if QuestHelper_Pref.track_level and level ~= 7777 and level ~= 7778 then
275 title = string.format("[%d] %s", level, title)
278 if level == 7778 then
279 level = -7778
282 if QuestHelper_Pref.track_qcolour then
283 local player_level = QuestHelper.player_level
284 local delta = level - player_level
286 local colour
288 if delta >= 5 then
289 colour = "|cffff0000"
290 elseif delta >= 0 then
291 colour = ccode(1, 1, 0, 1, 0, 0, delta/5)
292 else
293 local grey
295 if player_level >= 60 then grey = player_level - 9
296 elseif player_level >= 40 then grey = player_level - math.floor(player_level/5) - 1
297 elseif player_level >= 6 then grey = player_level - math.floor(player_level/10) - 5
298 else grey = 0 end
300 if level == -7778 then
301 colour = "|cff808080"
302 elseif level > grey then
303 colour = ccode(0, 1, 0, 1, 1, 0, (grey-level)/(grey-player_level))
304 else
305 colour = ccode(.4, .4, .4, .2, .8, .2, (1-level)/(1-grey))
309 title = string.format("%s%s", colour, title)
312 return title
315 local function oname(text, pct)
316 if QuestHelper_Pref.track_ocolour then
317 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)
320 return text
323 local function removeUnusedItem(quest, obj, item)
324 unused_items[item] = true
325 used_items[quest][obj] = nil
326 if not next(used_items[quest]) then used_items[quest] = nil end
327 item.used = false
328 item.t = 0
329 item.sx, item.sy = item.x, item.y
330 item.dx, item.dy = item.x+30, item.y
331 item:SetScript("OnMouseDown", nil)
332 item:EnableMouse(false)
333 item:SetScript("OnUpdate", itemfadeout)
336 local resizing = false
337 local check_delay = 4
338 local seen = {}
339 local obj_list = {}
340 local obj_index_lookup = {}
341 local quest_lookup = {}
342 local was_inside = false
344 local function watched_filter(obj)
345 return obj:IsWatched()
348 local function objlist_sort(a, b)
349 return (obj_index_lookup[a] or 0) < (obj_index_lookup[b] or 0)
352 function tracker:reset()
353 for quest, objs in pairs(used_items) do
354 for obj, item in pairs(objs) do
355 removeUnusedItem(quest, obj, item)
356 check_delay = 1e99
361 local function addobj(objective, seen, obj_index_lookup, filter, x, y, gap)
362 local count = 0
363 local quest
365 if objective.cat == "quest" then
366 quest = objective
367 else
368 quest = objective.quest
371 if quest and quest.watched and not seen[quest] and (not filter or filter(quest)) then
372 seen[quest] = true
374 local level, name = string.match(quest.obj, "^(%d+)/%d*/(.*)$")
376 if not level then
377 level, name = string.match(quest.obj, "^(%d+)/(.*)$")
378 if not level then
379 level, name = 1, quest.obj
383 level = tonumber(level) or 1
385 count = count + 1
386 local w, h = addItem(qname(name, level), true, quest, -(y+gap), name)
387 x = math.max(x, w)
388 y = y + h + gap
389 gap = 2
391 for obj in pairs(quest.swap_after or quest.after) do
392 if obj.progress then
393 table.insert(obj_list, obj)
397 table.sort(obj_list, objlist_sort)
399 for i, obj in ipairs(obj_list) do
400 local pct, text = 0, obj.obj
401 local seen_sum, seen_max = 0, 0
403 if obj.progress then
404 local seen_have, seen_need = QuestHelper:CreateTable(), QuestHelper:CreateTable()
406 for user, progress in pairs(obj.progress) do
407 seen_sum = seen_sum + progress[3]
408 seen_max = seen_max + 1
409 seen_have[progress[1]] = true
410 seen_need[progress[2]] = true
413 if seen_max > 0 then
414 pct = seen_sum / seen_max
415 local list = QuestHelper:CreateTable()
417 for val in pairs(seen_have) do
418 table.insert(list, val)
421 table.sort(list)
423 local have = table.concat(list, ", ")
425 for i = #list,1,-1 do
426 list[i] = nil
429 for val in pairs(seen_need) do
430 table.insert(list, val)
433 if #list ~= 1 or list[1] ~= 1 then
434 -- If there is only one thing needed, ignore the progress, it's redundant.
435 -- It's either shown or it isn't.
437 table.sort(list)
439 local need = table.concat(list, ", ")
441 text = string.format((tonumber(have) and tonumber(need) and QUEST_ITEMS_NEEDED) or QUEST_FACTION_NEEDED,
442 text, have, need)
445 QuestHelper:ReleaseTable(list)
448 QuestHelper:ReleaseTable(seen_have)
449 QuestHelper:ReleaseTable(seen_need)
452 if seen_sum ~= seen_max then
453 count = count + 1
454 w, h = addItem(oname(text, pct), quest, obj, -y)
455 x = math.max(x, w)
456 y = y + h
460 for i = #obj_list, 1, -1 do obj_list[i] = nil end
463 return x, y, gap, count
466 local loading_vquest = { cat = "quest", obj = "7777/" .. QHFormat("QH_LOADING", "0"), after = {}, watched = true }
467 local hidden_vquest1 = { cat = "quest", obj = "7778/" .. QHText("QUESTS_HIDDEN_1"), after = {}, watched = true }
468 local hidden_vquest2 = { cat = "quest", obj = "7778/ " .. QHText("QUESTS_HIDDEN_2"), after = {}, watched = true }
470 function tracker:update(delta)
471 if not delta then
472 -- This is called without a value when the questlog is updated.
473 -- We'll make sure we update the display on the next update.
474 check_delay = 1e99
475 return
478 if resizing then
479 local t = self.t+delta
481 if t > 1 then
482 self:SetWidth(self.dw)
483 self:SetHeight(self.dh)
484 resizing = false
485 else
486 self.t = t
487 local it = 1-t
488 self:SetWidth(self.sw*it+self.dw*t)
489 self:SetHeight(self.sh*it+self.dh*t)
493 -- Manually checking if the mouse is in the frame, because if I used on OnEnter, i'd have to enable mouse input,
494 -- and if I did that, it would prevent the player from using the mouse to change the view if they clicked inside
495 -- the tracker.
496 local x, y = GetCursorPosition()
497 local s = 1/self:GetEffectiveScale()
498 x, y = x*s, y*s
500 QuestHelper: Assert(x)
501 QuestHelper: Assert(y)
502 --[[ QuestHelper: Assert(self:GetLeft())
503 QuestHelper: Assert(self:GetBottom())
504 QuestHelper: Assert(self:GetRight())
505 QuestHelper: Assert(self:GetTop())]]
507 -- Sometimes it just doesn't know its own coordinates. Not sure why. Maybe this will fix it.
508 local inside = (self:GetLeft() and (x >= self:GetLeft() and y >= self:GetBottom() and x < self:GetRight() and y < self:GetTop()))
509 if inside ~= was_inside then
510 was_inside = inside
511 if inside then
512 minbutton:SetAlpha(.7)
513 elseif not QuestHelper_Pref.track_minimized then
514 minbutton:SetAlpha(0)
518 check_delay = check_delay + delta
519 if check_delay > 5 or (not QuestHelper.Routing.map_walker and check_delay > 0.5) then
520 check_delay = 0
522 local quests = QuestHelper.quest_log
523 local added = 0
524 local x, y = 4, 4
525 local gap = 0
526 local track_size = QuestHelper_Pref.track_size
527 local quests_added = {}
529 for quest, objs in pairs(used_items) do
530 for obj, item in pairs(objs) do
531 item.used = false
535 for i, objective in pairs(QuestHelper.route) do
536 if objective.watched then
537 obj_index_lookup[objective] = i
541 for q, data in pairs(QuestHelper.quest_log) do
542 quest_lookup[data.index] = q
545 -- Add our little "not yet loaded" notification
546 local loadedshow = false
547 if not QuestHelper.Routing.map_walker then
548 local count
549 x, y, gap, count = addobj(loading_vquest, seen, nil, nil, x, y, gap)
550 added = added + count
551 loadedshow = true
553 if QuestHelper_Flight_Updates and QuestHelper_Flight_Updates_Current and QuestHelper_Flight_Updates > 0 and QuestHelper_Flight_Updates_Current < QuestHelper_Flight_Updates then
554 loading_vquest.obj = string.format("7777/" .. QHFormat("QH_LOADING", string.format("%d", QuestHelper_Flight_Updates_Current * 100 / QuestHelper_Flight_Updates)))
558 -- Add an extra large gap to seperate the notification from everything else
559 gap = gap * 5
561 -- Add Quests that are watched but not in the route.
562 if UberQuest then
563 local uq_settings = UberQuest_Config[UnitName("player")]
564 if uq_settings then
565 local list = uq_settings.selected
566 if list then
567 local i = 1
568 while true do
569 local name = GetQuestLogTitle(i)
570 if not name then break end
571 quest_lookup[name] = quest_lookup[i]
572 i = i + 1
574 for name in pairs(list) do
575 local q = quest_lookup[name]
576 if q and not obj_index_lookup[q] then
577 local count
578 x, y, gap, count = addobj(q, seen, obj_index_lookup, nil, x, y, gap)
579 added = added + count
580 quests_added[q] = true
585 else
586 for i = 1,GetNumQuestWatches() do
587 local q = quest_lookup[GetQuestIndexForWatch(i)]
588 if q and not obj_index_lookup[q] then
589 local count
590 x, y, gap, count = addobj(q, seen, obj_index_lookup, nil, x, y, gap)
591 added = added + count
596 -- Add Quests that are watched and are in the route.
597 for i, objective in ipairs(QuestHelper.route) do
598 local count
599 x, y, gap, count = addobj(objective, seen, obj_index_lookup, watched_filter, x, y, gap)
600 added = added + count
601 quests_added[objective] = true
604 -- Add an extra large gap to seperate the watched objectives from the automatic objectives.
605 gap = gap * 5
607 -- Add Quests that aren't watched and are in the route.
608 if added <= track_size then
609 for i, objective in ipairs(QuestHelper.route) do
610 local count
611 x, y, gap, count = addobj(objective, seen, obj_index_lookup, nil, x, y, gap)
612 added = added + count
613 quests_added[objective] = true
615 if added > track_size then
616 break
621 if not loadedshow and added < track_size and not QuestHelper_Pref.filter_done and not QuestHelper_Pref.filter_zone and not QuestHelper_Pref.filter_watched then
622 local added = 0
623 local notadded = 0
624 for k, v in pairs(quest_lookup) do
625 if not quests_added[v] then
626 notadded = notadded + 1
627 else
628 added = added + 1
632 if notadded > 0 then
633 gap = gap * 10
634 x, y, gap, count = addobj(hidden_vquest1, seen, nil, nil, x, y, gap)
635 added = added + count
636 x, y, gap, count = addobj(hidden_vquest2, seen, nil, nil, x, y, gap)
637 added = added + count
641 for obj in pairs(obj_index_lookup) do
642 obj_index_lookup[obj] = nil
645 for key in pairs(quest_lookup) do
646 quest_lookup[key] = nil
649 for quest, objs in pairs(used_items) do
650 for obj, item in pairs(objs) do
651 if not item.used then
652 removeUnusedItem(quest, obj, item)
657 for key in pairs(seen) do
658 seen[key] = nil
661 y = y+4
663 if x ~= tracker.dw or y ~= tracker.dy then
664 tracker.t = 0
665 tracker.sw, tracker.sh = tracker:GetWidth(), tracker:GetHeight()
666 tracker.dw, tracker.dh = x, y
667 resizing = true
670 added = 0
674 tracker:SetScript("OnUpdate", tracker.update)
676 -- Some hooks to update the tracker when quests are added or removed.
677 local orig_AddQuestWatch, orig_RemoveQuestWatch = AddQuestWatch, RemoveQuestWatch
679 function AddQuestWatch(...)
680 tracker:update()
681 return orig_AddQuestWatch(...)
684 function RemoveQuestWatch(...)
685 tracker:update()
686 return orig_RemoveQuestWatch(...)
689 -------------------------------------------------------------------------------------------------
690 -- This batch of stuff is to make sure the original tracker (and any modifications) stay hidden
692 local orig_TrackerOnShow
693 if QuestWatchFrame then -- 3.1 hackery
694 orig_TrackerOnShow = QuestWatchFrame:GetScript("OnShow")
696 local orig_TrackerBackdropOnShow -- bEQL (and perhaps other mods) add a backdrop to the tracker
697 local TrackerBackdropFound = false
699 local function TrackerBackdropOnShow(self, ...)
700 if QuestHelper_Pref.track and not QuestHelper_Pref.hide then
701 TrackerBackdropFound:Hide()
704 if orig_TrackerBackdropOnShow then
705 return orig_TrackerBackdropOnShow(self, ...)
709 function tracker:HideDefaultTracker()
710 -- The easy part: hide the original tracker
711 if QuestWatchFrame then -- 3.1 hackery
712 QuestWatchFrame:Hide()
713 else
714 WatchFrame_RemoveObjectiveHandler(WatchFrame_DisplayTrackedQuests)
715 WatchFrame_ClearDisplay()
716 WatchFrame_Update()
719 -- The harder part: check if a known backdrop is present (but we don't already know about it).
720 -- If it is, make sure it's hidden, and hook its OnShow to make sure it stays that way.
721 -- Unfortunately, I can't figure out a good time to check for this once, so we'll just have
722 -- to keep checking. Hopefully, this won't happen too often.
723 if not TrackerBackdropFound then
724 if QuestWatchFrameBackdrop then
725 -- Found bEQL's QuestWatchFrameBackdrop...
726 TrackerBackdropFound = QuestWatchFrameBackdrop
729 if TrackerBackdropFound then
730 -- OK, we found something - so hide it, and make sure it doesn't rear its ugly head again
731 TrackerBackdropFound:Hide()
733 orig_TrackerBackdropOnShow = TrackerBackdropFound:GetScript("OnShow")
734 TrackerBackdropFound:SetScript("OnShow", TrackerBackdropOnShow)
739 function tracker:ShowDefaultTracker()
740 if QuestWatchFrame then -- 3.1 hackery
741 QuestWatchFrame:Show()
742 -- Make sure the default tracker is up to date on what what's being watched and what isn't.
743 QuestWatch_Update()
744 else
745 -- I like how there's code explicitly to allow us to do this without checking if it's already added
746 WatchFrame_AddObjectiveHandler(WatchFrame_DisplayTrackedQuests)
747 -- Make sure the default tracker is up to date on what what's being watched and what isn't.
748 WatchFrame_Update()
751 if TrackerBackdropFound then
752 TrackerBackdropFound:Show()
756 if QuestWatchFrame then -- 3.1 hackery
757 local function QuestWatchFrameOnShow(self, ...)
758 if QuestHelper_Pref.track and not QuestHelper_Pref.hide then
759 tracker:HideDefaultTracker()
762 if orig_TrackerOnShow then
763 return orig_TrackerOnShow(self, ...)
767 QuestWatchFrame:SetScript("OnShow", QuestWatchFrameOnShow)
770 function QuestHelper:ShowTracker()
771 tracker:HideDefaultTracker()
772 minbutton:Show()
774 if QuestHelper_Pref.track_minimized then
775 minbutton:SetAlpha(.3)
776 else
777 minbutton:SetAlpha(0)
778 tracker:Show()
782 function QuestHelper:HideTracker()
783 tracker:ShowDefaultTracker()
784 tracker:Hide()
785 minbutton:Hide()