tweak the nag system slightly
[QuestHelper.git] / tracker.lua
blobbe5a418ac59ac1dd3db4c81c4cad3fc47a9cb259
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 self:TextOut("Quest tracker postion reset.")
51 end
53 minbutton:SetScript("OnEvent", minbutton.moved)
54 minbutton:RegisterEvent("DISPLAY_SIZE_CHANGED")
55 minbutton:RegisterEvent("PLAYER_ENTERING_WORLD")
57 minbutton:SetScript("OnClick", function ()
58 QuestHelper_Pref.track_minimized = not QuestHelper_Pref.track_minimized
59 if QuestHelper_Pref.track_minimized then
60 tracker:Hide()
61 else
62 tracker:Show()
63 end
64 end)
66 minbutton:RegisterForDrag("LeftButton")
68 minbutton:SetScript("OnDragStart", function(self)
69 if self:IsVisible() then
70 self:StartMoving()
71 self:SetScript("OnUpdate", self.moved)
72 end
73 end)
75 minbutton:SetScript("OnDragStop", function(self)
76 self:SetScript("OnUpdate", nil)
77 self:StopMovingOrSizing()
78 self:moved()
79 end)
81 minbutton:SetScript("OnEnter", function (self)
82 self:SetAlpha(1)
83 end)
85 minbutton:SetScript("OnLeave", function (self)
86 self:SetAlpha(QuestHelper_Pref.track_minimized and .3 or .5)
87 end)
89 local unused_items = {}
90 local used_items = {}
92 local function itemupdate(item, delta)
93 local done = true
95 local a = item:GetAlpha()
96 a = a + delta
98 if a < 1 then
99 item:SetAlpha(a)
100 done = false
101 else
102 item:SetAlpha(1)
105 local t = item.t + delta
107 if t < 1 then
108 item.t = t
109 local it = 1-t
110 local sp = math.sqrt(t-t*t)
111 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
112 done = false
113 else
114 item.t = 1
115 item.x, item.y = item.dx, item.dy
118 item:ClearAllPoints()
119 item:SetPoint("TOPLEFT", tracker, "TOPLEFT", item.x, item.y)
121 if done then
122 item:SetScript("OnUpdate", nil)
126 local function itemfadeout(item, delta)
127 local a = item:GetAlpha()
128 a = a - delta
130 if a > 0 then
131 item:SetAlpha(a)
132 else
133 item:SetAlpha(1)
134 item:Hide()
135 item:SetScript("OnUpdate", nil)
136 return
139 local t = item.t + delta
141 if t < 1 then
142 item.t = t
143 local it = 1-t
144 local sp = math.sqrt(t-t*t)
145 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
146 else
147 item.t = 1
148 item.x, item.y = item.dx, item.dy
151 item:ClearAllPoints()
152 item:SetPoint("TOPLEFT", tracker, "TOPLEFT", item.x, item.y)
155 function QH_ToggleQuestLog() -- This seems to be gone in 3.0, so I'm adding it here.
156 if (QuestLogFrame:IsShown()) then
157 HideUIPanel(QuestLogFrame);
158 else
159 ShowUIPanel(QuestLogFrame);
164 local function itemclick(item, button)
165 if button == "RightButton" then
166 local quest = item.quest
167 local index = 1
168 while true do
169 local title = GetQuestLogTitle(index)
170 if not title then break end
172 if title == quest then
173 if UberQuest then
174 -- UberQuest needs a little extra effort to work properly.
176 if UberQuest_List:IsShown() and GetQuestLogSelection() == index then
177 QH_ToggleQuestLog()
178 else
179 QuestLog_SetSelection(index)
181 -- By hiding the list, the replaced ToggleQuestLog function should try to reshow it
182 -- and in the process update the frames to reflect the selected quest.
183 UberQuest_List:Hide()
184 UberQuest_Details:Show()
185 QH_ToggleQuestLog()
187 else
188 -- This code seems to work properly with the builtin questlog, as well as bEQL and DoubleWide.
190 if QuestLogFrame:IsShown() and GetQuestLogSelection() == index then
191 -- If the selected quest is already being shown, hide it.
192 QH_ToggleQuestLog()
193 else
194 -- Otherwise, select it and show it.
195 QuestLog_SetSelection(index)
197 if not QuestLogFrame:IsShown() then
198 QH_ToggleQuestLog()
203 return
206 index = index + 1
211 local function addItem(name, quest, obj, y, qname)
212 local x = qname and 4 or 20
213 local item = used_items[quest] and used_items[quest][obj]
214 if not item then
215 item = next(unused_items)
216 if item then
217 unused_items[item] = nil
218 else
219 item = CreateFrame("Frame", nil, tracker)
220 item.text = item:CreateFontString()
221 item.text:SetShadowColor(0, 0, 0, .8)
222 item.text:SetShadowOffset(1, -1)
223 item.text:SetPoint("TOPLEFT", item)
226 if qname then
227 item.text:SetFont(QuestHelper.font.serif, 12)
228 item.text:SetTextColor(.82, .65, 0)
229 else
230 item.text:SetFont(QuestHelper.font.sans, 12)
231 item.text:SetTextColor(.82, .82, .82)
234 if not used_items[quest] then used_items[quest] = {} end
236 used_items[quest][obj] = item
237 item.sx, item.sy, item.x, item.y, item.dx, item.dy, item.t = x+30, y, x, y, x, y, 0
238 item:SetScript("OnUpdate", itemupdate)
239 item:SetAlpha(0)
240 item:Show()
243 item.used = true
245 item.quest = qname
246 item.text:SetText(name)
247 local w, h = item.text:GetWidth(), item.text:GetHeight()
248 item:SetWidth(w)
249 item:SetHeight(h)
251 if qname then
252 item:SetScript("OnMouseDown", itemclick)
253 item:EnableMouse(true)
256 if item.dx ~= x or item.dy ~= y then
257 item.sx, item.sy, item.dx, item.dy = item.x, item.y, x, y
258 item.t = 0
259 item:SetScript("OnUpdate", itemupdate)
262 return w+x+4, h
265 local function ccode(r1, g1, b1, r2, g2, b2, p)
266 local ip
267 p, ip = p*255, 255-p*255
268 return string.format("|cff%02x%02x%02x", r1*ip+r2*p, g1*ip+g2*p, b1*ip+b2*p)
271 local function qname(title, level)
272 if QuestHelper_Pref.track_level and level ~= 7777 then
273 title = string.format("[%d] %s", level, title)
276 if QuestHelper_Pref.track_qcolour then
277 local player_level = QuestHelper.player_level
278 local delta = level - player_level
280 local colour
282 if delta >= 5 then
283 colour = "|cffff0000"
284 elseif delta >= 0 then
285 colour = ccode(1, 1, 0, 1, 0, 0, delta/5)
286 else
287 local grey
289 if player_level >= 60 then grey = player_level - 9
290 elseif player_level >= 40 then grey = player_level - math.floor(player_level/5) - 1
291 elseif player_level >= 6 then grey = player_level - math.floor(player_level/10) - 5
292 else grey = 0 end
294 if level > grey then
295 colour = ccode(0, 1, 0, 1, 1, 0, (grey-level)/(grey-player_level))
296 else
297 colour = ccode(.4, .4, .4, .2, .8, .2, (1-level)/(1-grey))
301 title = string.format("%s%s", colour, title)
304 return title
307 local function oname(text, pct)
308 if QuestHelper_Pref.track_ocolour then
309 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)
312 return text
315 local function removeUnusedItem(quest, obj, item)
316 unused_items[item] = true
317 used_items[quest][obj] = nil
318 if not next(used_items[quest]) then used_items[quest] = nil end
319 item.used = false
320 item.t = 0
321 item.sx, item.sy = item.x, item.y
322 item.dx, item.dy = item.x+30, item.y
323 item:SetScript("OnMouseDown", nil)
324 item:EnableMouse(false)
325 item:SetScript("OnUpdate", itemfadeout)
328 local resizing = false
329 local check_delay = 4
330 local seen = {}
331 local obj_list = {}
332 local obj_index_lookup = {}
333 local quest_lookup = {}
334 local was_inside = false
336 local function watched_filter(obj)
337 return obj:IsWatched()
340 local function objlist_sort(a, b)
341 return (obj_index_lookup[a] or 0) < (obj_index_lookup[b] or 0)
344 function tracker:reset()
345 for quest, objs in pairs(used_items) do
346 for obj, item in pairs(objs) do
347 removeUnusedItem(quest, obj, item)
348 check_delay = 1e99
353 local function addobj(objective, seen, obj_index_lookup, filter, x, y, gap)
354 local count = 0
355 local quest
357 if objective.cat == "quest" then
358 quest = objective
359 else
360 quest = objective.quest
363 if quest and quest.watched and not seen[quest] and (not filter or filter(quest)) then
364 seen[quest] = true
366 local level, name = string.match(quest.obj, "^(%d+)/%d*/(.*)$")
368 if not level then
369 level, name = string.match(quest.obj, "^(%d+)/(.*)$")
370 if not level then
371 level, name = 1, quest.obj
375 level = tonumber(level) or 1
377 count = count + 1
378 local w, h = addItem(qname(name, level), true, quest, -(y+gap), name)
379 x = math.max(x, w)
380 y = y + h + gap
381 gap = 2
383 for obj in pairs(quest.swap_after or quest.after) do
384 if obj.progress then
385 table.insert(obj_list, obj)
389 table.sort(obj_list, objlist_sort)
391 for i, obj in ipairs(obj_list) do
392 local pct, text = 0, obj.obj
393 local seen_sum, seen_max = 0, 0
395 if obj.progress then
396 local seen_have, seen_need = QuestHelper:CreateTable(), QuestHelper:CreateTable()
398 for user, progress in pairs(obj.progress) do
399 seen_sum = seen_sum + progress[3]
400 seen_max = seen_max + 1
401 seen_have[progress[1]] = true
402 seen_need[progress[2]] = true
405 if seen_max > 0 then
406 pct = seen_sum / seen_max
407 local list = QuestHelper:CreateTable()
409 for val in pairs(seen_have) do
410 table.insert(list, val)
413 table.sort(list)
415 local have = table.concat(list, ", ")
417 for i = #list,1,-1 do
418 list[i] = nil
421 for val in pairs(seen_need) do
422 table.insert(list, val)
425 if #list ~= 1 or list[1] ~= 1 then
426 -- If there is only one thing needed, ignore the progress, it's redundant.
427 -- It's either shown or it isn't.
429 table.sort(list)
431 local need = table.concat(list, ", ")
433 text = string.format((tonumber(have) and tonumber(need) and QUEST_ITEMS_NEEDED) or QUEST_FACTION_NEEDED,
434 text, have, need)
437 QuestHelper:ReleaseTable(list)
440 QuestHelper:ReleaseTable(seen_have)
441 QuestHelper:ReleaseTable(seen_need)
444 if seen_sum ~= seen_max then
445 count = count + 1
446 w, h = addItem(oname(text, pct), quest, obj, -y)
447 x = math.max(x, w)
448 y = y + h
452 for i = #obj_list, 1, -1 do obj_list[i] = nil end
455 return x, y, gap, count
458 local loading_vquest = { cat = "quest", obj = "7777/Questhelper is loading...", after = {}, watched = true }
460 function tracker:update(delta)
461 if not delta then
462 -- This is called without a value when the questlog is updated.
463 -- We'll make sure we update the display on the next update.
464 check_delay = 1e99
465 return
468 if resizing then
469 local t = self.t+delta
471 if t > 1 then
472 self:SetWidth(self.dw)
473 self:SetHeight(self.dh)
474 resizing = false
475 else
476 self.t = t
477 local it = 1-t
478 self:SetWidth(self.sw*it+self.dw*t)
479 self:SetHeight(self.sh*it+self.dh*t)
483 -- Manually checking if the mouse is in the frame, because if I used on OnEnter, i'd have to enable mouse input,
484 -- and if I did that, it would prevent the player from using the mouse to change the view if they clicked inside
485 -- the tracker.
486 local x, y = GetCursorPosition()
487 local s = 1/self:GetEffectiveScale()
488 x, y = x*s, y*s
490 local inside = x >= self:GetLeft() and y >= self:GetBottom() and x < self:GetRight() and y < self:GetTop()
491 if inside ~= was_inside then
492 was_inside = inside
493 if inside then
494 minbutton:SetAlpha(.7)
495 elseif not QuestHelper_Pref.track_minimized then
496 minbutton:SetAlpha(0)
500 check_delay = check_delay + delta
501 if check_delay > 5 or (not QuestHelper.Routing.map_walker and check_delay > 0.5) then
502 check_delay = 0
504 local quests = QuestHelper.quest_log
505 local added = 0
506 local x, y = 4, 4
507 local gap = 0
508 local track_size = QuestHelper_Pref.track_size
510 for quest, objs in pairs(used_items) do
511 for obj, item in pairs(objs) do
512 item.used = false
516 for i, objective in pairs(QuestHelper.route) do
517 if objective.watched then
518 obj_index_lookup[objective] = i
522 for q, data in pairs(QuestHelper.quest_log) do
523 quest_lookup[data.index] = q
526 -- Add our little "not yet loaded" notification
527 if not QuestHelper.Routing.map_walker then
528 local count
529 x, y, gap, count = addobj(loading_vquest, seen, nil, nil, x, y, gap)
530 added = added + count
532 if QuestHelper_Flight_Updates and QuestHelper_Flight_Updates_Current and QuestHelper_Flight_Updates > 0 and QuestHelper_Flight_Updates_Current < QuestHelper_Flight_Updates then
533 loading_vquest.obj = string.format("7777/QuestHelper is loading (%2d%%)...", QuestHelper_Flight_Updates_Current * 100 / QuestHelper_Flight_Updates)
537 -- Add an extra large gap to seperate the notification from everything else
538 gap = gap * 5
540 -- Add Quests that are watched but not in the route.
541 if UberQuest then
542 local uq_settings = UberQuest_Config[UnitName("player")]
543 if uq_settings then
544 local list = uq_settings.selected
545 if list then
546 local i = 1
547 while true do
548 local name = GetQuestLogTitle(i)
549 if not name then break end
550 quest_lookup[name] = quest_lookup[i]
551 i = i + 1
553 for name in pairs(list) do
554 local q = quest_lookup[name]
555 if q and not obj_index_lookup[q] then
556 local count
557 x, y, gap, count = addobj(q, seen, obj_index_lookup, nil, x, y, gap)
558 added = added + count
563 else
564 for i = 1,GetNumQuestWatches() do
565 local q = quest_lookup[GetQuestIndexForWatch(i)]
566 if q and not obj_index_lookup[q] then
567 local count
568 x, y, gap, count = addobj(q, seen, obj_index_lookup, nil, x, y, gap)
569 added = added + count
574 -- Add Quests that are watched and are in the route.
575 for i, objective in ipairs(QuestHelper.route) do
576 local count
577 x, y, gap, count = addobj(objective, seen, obj_index_lookup, watched_filter, x, y, gap)
578 added = added + count
581 -- Add an extra large gap to seperate the watched objectives from the automatic objectives.
582 gap = gap * 5
584 -- Add Quests that aren't watched and are in the route.
585 if added <= track_size then
586 for i, objective in ipairs(QuestHelper.route) do
587 local count
588 x, y, gap, count = addobj(objective, seen, obj_index_lookup, nil, x, y, gap)
589 added = added + count
591 if added > track_size then
592 break
597 for obj in pairs(obj_index_lookup) do
598 obj_index_lookup[obj] = nil
601 for key in pairs(quest_lookup) do
602 quest_lookup[key] = nil
605 for quest, objs in pairs(used_items) do
606 for obj, item in pairs(objs) do
607 if not item.used then
608 removeUnusedItem(quest, obj, item)
613 for key in pairs(seen) do
614 seen[key] = nil
617 y = y+4
619 if x ~= tracker.dw or y ~= tracker.dy then
620 tracker.t = 0
621 tracker.sw, tracker.sh = tracker:GetWidth(), tracker:GetHeight()
622 tracker.dw, tracker.dh = x, y
623 resizing = true
626 added = 0
630 tracker:SetScript("OnUpdate", tracker.update)
632 -- Some hooks to update the tracker when quests are added or removed.
633 local orig_AddQuestWatch, orig_RemoveQuestWatch = AddQuestWatch, RemoveQuestWatch
635 function AddQuestWatch(...)
636 tracker:update()
637 return orig_AddQuestWatch(...)
640 function RemoveQuestWatch(...)
641 tracker:update()
642 return orig_RemoveQuestWatch(...)
645 -------------------------------------------------------------------------------------------------
646 -- This batch of stuff is to make sure the original tracker (and any modifications) stay hidden
648 local orig_TrackerOnShow = QuestWatchFrame:GetScript("OnShow")
649 local orig_TrackerBackdropOnShow -- bEQL (and perhaps other mods) add a backdrop to the tracker
650 local TrackerBackdropFound = false
652 local function TrackerBackdropOnShow(self, ...)
653 if QuestHelper_Pref.track and not QuestHelper_Pref.hide then
654 TrackerBackdropFound:Hide()
657 if orig_TrackerBackdropOnShow then
658 return orig_TrackerBackdropOnShow(self, ...)
662 function tracker:HideDefaultTracker()
663 -- The easy part: hide the original tracker
664 QuestWatchFrame:Hide()
666 -- The harder part: check if a known backdrop is present (but we don't already know about it).
667 -- If it is, make sure it's hidden, and hook its OnShow to make sure it stays that way.
668 -- Unfortunately, I can't figure out a good time to check for this once, so we'll just have
669 -- to keep checking. Hopefully, this won't happen too often.
670 if not TrackerBackdropFound then
671 if QuestWatchFrameBackdrop then
672 -- Found bEQL's QuestWatchFrameBackdrop...
673 TrackerBackdropFound = QuestWatchFrameBackdrop
676 if TrackerBackdropFound then
677 -- OK, we found something - so hide it, and make sure it doesn't rear its ugly head again
678 TrackerBackdropFound:Hide()
680 orig_TrackerBackdropOnShow = TrackerBackdropFound:GetScript("OnShow")
681 TrackerBackdropFound:SetScript("OnShow", TrackerBackdropOnShow)
686 function tracker:ShowDefaultTracker()
687 QuestWatchFrame:Show()
689 -- Make sure the default tracker is up to date on what what's being watched and what isn't.
690 QuestWatch_Update()
692 if TrackerBackdropFound then
693 TrackerBackdropFound:Show()
697 local function QuestWatchFrameOnShow(self, ...)
698 if QuestHelper_Pref.track and not QuestHelper_Pref.hide then
699 tracker:HideDefaultTracker()
702 if orig_TrackerOnShow then
703 return orig_TrackerOnShow(self, ...)
707 QuestWatchFrame:SetScript("OnShow", QuestWatchFrameOnShow)
709 function QuestHelper:ShowTracker()
710 tracker:HideDefaultTracker()
711 minbutton:Show()
713 if QuestHelper_Pref.track_minimized then
714 minbutton:SetAlpha(.3)
715 else
716 minbutton:SetAlpha(0)
717 tracker:Show()
721 function QuestHelper:HideTracker()
722 tracker:ShowDefaultTracker()
723 tracker:Hide()
724 minbutton:Hide()