Automated update from: http://smariot.hopto.org/translate
[QuestHelper.git] / tracker.lua
blob30204d5e03ca57994f37f4e2a24cb619ee6c1c2d
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 and level ~= 7778 then
273 title = string.format("[%d] %s", level, title)
276 if level == 7778 then
277 level = -7778
280 if QuestHelper_Pref.track_qcolour then
281 local player_level = QuestHelper.player_level
282 local delta = level - player_level
284 local colour
286 if delta >= 5 then
287 colour = "|cffff0000"
288 elseif delta >= 0 then
289 colour = ccode(1, 1, 0, 1, 0, 0, delta/5)
290 else
291 local grey
293 if player_level >= 60 then grey = player_level - 9
294 elseif player_level >= 40 then grey = player_level - math.floor(player_level/5) - 1
295 elseif player_level >= 6 then grey = player_level - math.floor(player_level/10) - 5
296 else grey = 0 end
298 if level == -7778 then
299 colour = "|cff808080"
300 elseif level > grey then
301 colour = ccode(0, 1, 0, 1, 1, 0, (grey-level)/(grey-player_level))
302 else
303 colour = ccode(.4, .4, .4, .2, .8, .2, (1-level)/(1-grey))
307 title = string.format("%s%s", colour, title)
310 return title
313 local function oname(text, pct)
314 if QuestHelper_Pref.track_ocolour then
315 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)
318 return text
321 local function removeUnusedItem(quest, obj, item)
322 unused_items[item] = true
323 used_items[quest][obj] = nil
324 if not next(used_items[quest]) then used_items[quest] = nil end
325 item.used = false
326 item.t = 0
327 item.sx, item.sy = item.x, item.y
328 item.dx, item.dy = item.x+30, item.y
329 item:SetScript("OnMouseDown", nil)
330 item:EnableMouse(false)
331 item:SetScript("OnUpdate", itemfadeout)
334 local resizing = false
335 local check_delay = 4
336 local seen = {}
337 local obj_list = {}
338 local obj_index_lookup = {}
339 local quest_lookup = {}
340 local was_inside = false
342 local function watched_filter(obj)
343 return obj:IsWatched()
346 local function objlist_sort(a, b)
347 return (obj_index_lookup[a] or 0) < (obj_index_lookup[b] or 0)
350 function tracker:reset()
351 for quest, objs in pairs(used_items) do
352 for obj, item in pairs(objs) do
353 removeUnusedItem(quest, obj, item)
354 check_delay = 1e99
359 local function addobj(objective, seen, obj_index_lookup, filter, x, y, gap)
360 local count = 0
361 local quest
363 if objective.cat == "quest" then
364 quest = objective
365 else
366 quest = objective.quest
369 if quest and quest.watched and not seen[quest] and (not filter or filter(quest)) then
370 seen[quest] = true
372 local level, name = string.match(quest.obj, "^(%d+)/%d*/(.*)$")
374 if not level then
375 level, name = string.match(quest.obj, "^(%d+)/(.*)$")
376 if not level then
377 level, name = 1, quest.obj
381 level = tonumber(level) or 1
383 count = count + 1
384 local w, h = addItem(qname(name, level), true, quest, -(y+gap), name)
385 x = math.max(x, w)
386 y = y + h + gap
387 gap = 2
389 for obj in pairs(quest.swap_after or quest.after) do
390 if obj.progress then
391 table.insert(obj_list, obj)
395 table.sort(obj_list, objlist_sort)
397 for i, obj in ipairs(obj_list) do
398 local pct, text = 0, obj.obj
399 local seen_sum, seen_max = 0, 0
401 if obj.progress then
402 local seen_have, seen_need = QuestHelper:CreateTable(), QuestHelper:CreateTable()
404 for user, progress in pairs(obj.progress) do
405 seen_sum = seen_sum + progress[3]
406 seen_max = seen_max + 1
407 seen_have[progress[1]] = true
408 seen_need[progress[2]] = true
411 if seen_max > 0 then
412 pct = seen_sum / seen_max
413 local list = QuestHelper:CreateTable()
415 for val in pairs(seen_have) do
416 table.insert(list, val)
419 table.sort(list)
421 local have = table.concat(list, ", ")
423 for i = #list,1,-1 do
424 list[i] = nil
427 for val in pairs(seen_need) do
428 table.insert(list, val)
431 if #list ~= 1 or list[1] ~= 1 then
432 -- If there is only one thing needed, ignore the progress, it's redundant.
433 -- It's either shown or it isn't.
435 table.sort(list)
437 local need = table.concat(list, ", ")
439 text = string.format((tonumber(have) and tonumber(need) and QUEST_ITEMS_NEEDED) or QUEST_FACTION_NEEDED,
440 text, have, need)
443 QuestHelper:ReleaseTable(list)
446 QuestHelper:ReleaseTable(seen_have)
447 QuestHelper:ReleaseTable(seen_need)
450 if seen_sum ~= seen_max then
451 count = count + 1
452 w, h = addItem(oname(text, pct), quest, obj, -y)
453 x = math.max(x, w)
454 y = y + h
458 for i = #obj_list, 1, -1 do obj_list[i] = nil end
461 return x, y, gap, count
464 local loading_vquest = { cat = "quest", obj = "7777/" .. QHFormat("QH_LOADING", "0"), after = {}, watched = true }
465 local hidden_vquest1 = { cat = "quest", obj = "7778/" .. QHText("QUESTS_HIDDEN_1"), after = {}, watched = true }
466 local hidden_vquest2 = { cat = "quest", obj = "7778/ " .. QHText("QUESTS_HIDDEN_2"), after = {}, watched = true }
468 function tracker:update(delta)
469 if not delta then
470 -- This is called without a value when the questlog is updated.
471 -- We'll make sure we update the display on the next update.
472 check_delay = 1e99
473 return
476 if resizing then
477 local t = self.t+delta
479 if t > 1 then
480 self:SetWidth(self.dw)
481 self:SetHeight(self.dh)
482 resizing = false
483 else
484 self.t = t
485 local it = 1-t
486 self:SetWidth(self.sw*it+self.dw*t)
487 self:SetHeight(self.sh*it+self.dh*t)
491 -- Manually checking if the mouse is in the frame, because if I used on OnEnter, i'd have to enable mouse input,
492 -- and if I did that, it would prevent the player from using the mouse to change the view if they clicked inside
493 -- the tracker.
494 local x, y = GetCursorPosition()
495 local s = 1/self:GetEffectiveScale()
496 x, y = x*s, y*s
498 QuestHelper: Assert(x)
499 QuestHelper: Assert(y)
500 --[[ QuestHelper: Assert(self:GetLeft())
501 QuestHelper: Assert(self:GetBottom())
502 QuestHelper: Assert(self:GetRight())
503 QuestHelper: Assert(self:GetTop())]]
505 -- Sometimes it just doesn't know its own coordinates. Not sure why. Maybe this will fix it.
506 local inside = (self:GetLeft() and (x >= self:GetLeft() and y >= self:GetBottom() and x < self:GetRight() and y < self:GetTop()))
507 if inside ~= was_inside then
508 was_inside = inside
509 if inside then
510 minbutton:SetAlpha(.7)
511 elseif not QuestHelper_Pref.track_minimized then
512 minbutton:SetAlpha(0)
516 check_delay = check_delay + delta
517 if check_delay > 5 or (not QuestHelper.Routing.map_walker and check_delay > 0.5) then
518 check_delay = 0
520 local quests = QuestHelper.quest_log
521 local added = 0
522 local x, y = 4, 4
523 local gap = 0
524 local track_size = QuestHelper_Pref.track_size
525 local quests_added = {}
527 for quest, objs in pairs(used_items) do
528 for obj, item in pairs(objs) do
529 item.used = false
533 for i, objective in pairs(QuestHelper.route) do
534 if objective.watched then
535 obj_index_lookup[objective] = i
539 for q, data in pairs(QuestHelper.quest_log) do
540 quest_lookup[data.index] = q
543 -- Add our little "not yet loaded" notification
544 local loadedshow = false
545 if not QuestHelper.Routing.map_walker then
546 local count
547 x, y, gap, count = addobj(loading_vquest, seen, nil, nil, x, y, gap)
548 added = added + count
549 loadedshow = true
551 if QuestHelper_Flight_Updates and QuestHelper_Flight_Updates_Current and QuestHelper_Flight_Updates > 0 and QuestHelper_Flight_Updates_Current < QuestHelper_Flight_Updates then
552 loading_vquest.obj = string.format("7777/" .. QHFormat("QH_LOADING", string.format("%d", QuestHelper_Flight_Updates_Current * 100 / QuestHelper_Flight_Updates)))
556 -- Add an extra large gap to seperate the notification from everything else
557 gap = gap * 5
559 -- Add Quests that are watched but not in the route.
560 if UberQuest then
561 local uq_settings = UberQuest_Config[UnitName("player")]
562 if uq_settings then
563 local list = uq_settings.selected
564 if list then
565 local i = 1
566 while true do
567 local name = GetQuestLogTitle(i)
568 if not name then break end
569 quest_lookup[name] = quest_lookup[i]
570 i = i + 1
572 for name in pairs(list) do
573 local q = quest_lookup[name]
574 if q and not obj_index_lookup[q] then
575 local count
576 x, y, gap, count = addobj(q, seen, obj_index_lookup, nil, x, y, gap)
577 added = added + count
578 quests_added[q] = true
583 else
584 for i = 1,GetNumQuestWatches() do
585 local q = quest_lookup[GetQuestIndexForWatch(i)]
586 if q and not obj_index_lookup[q] then
587 local count
588 x, y, gap, count = addobj(q, seen, obj_index_lookup, nil, x, y, gap)
589 added = added + count
594 -- Add Quests that are watched and are in the route.
595 for i, objective in ipairs(QuestHelper.route) do
596 local count
597 x, y, gap, count = addobj(objective, seen, obj_index_lookup, watched_filter, x, y, gap)
598 added = added + count
599 quests_added[objective] = true
602 -- Add an extra large gap to seperate the watched objectives from the automatic objectives.
603 gap = gap * 5
605 -- Add Quests that aren't watched and are in the route.
606 if added <= track_size then
607 for i, objective in ipairs(QuestHelper.route) do
608 local count
609 x, y, gap, count = addobj(objective, seen, obj_index_lookup, nil, x, y, gap)
610 added = added + count
611 quests_added[objective] = true
613 if added > track_size then
614 break
619 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
620 local added = 0
621 local notadded = 0
622 for k, v in pairs(quest_lookup) do
623 if not quests_added[v] then
624 notadded = notadded + 1
625 else
626 added = added + 1
630 if notadded > 0 then
631 gap = gap * 10
632 x, y, gap, count = addobj(hidden_vquest1, seen, nil, nil, x, y, gap)
633 added = added + count
634 x, y, gap, count = addobj(hidden_vquest2, seen, nil, nil, x, y, gap)
635 added = added + count
639 for obj in pairs(obj_index_lookup) do
640 obj_index_lookup[obj] = nil
643 for key in pairs(quest_lookup) do
644 quest_lookup[key] = nil
647 for quest, objs in pairs(used_items) do
648 for obj, item in pairs(objs) do
649 if not item.used then
650 removeUnusedItem(quest, obj, item)
655 for key in pairs(seen) do
656 seen[key] = nil
659 y = y+4
661 if x ~= tracker.dw or y ~= tracker.dy then
662 tracker.t = 0
663 tracker.sw, tracker.sh = tracker:GetWidth(), tracker:GetHeight()
664 tracker.dw, tracker.dh = x, y
665 resizing = true
668 added = 0
672 tracker:SetScript("OnUpdate", tracker.update)
674 -- Some hooks to update the tracker when quests are added or removed.
675 local orig_AddQuestWatch, orig_RemoveQuestWatch = AddQuestWatch, RemoveQuestWatch
677 function AddQuestWatch(...)
678 tracker:update()
679 return orig_AddQuestWatch(...)
682 function RemoveQuestWatch(...)
683 tracker:update()
684 return orig_RemoveQuestWatch(...)
687 -------------------------------------------------------------------------------------------------
688 -- This batch of stuff is to make sure the original tracker (and any modifications) stay hidden
690 local orig_TrackerOnShow = QuestWatchFrame:GetScript("OnShow")
691 local orig_TrackerBackdropOnShow -- bEQL (and perhaps other mods) add a backdrop to the tracker
692 local TrackerBackdropFound = false
694 local function TrackerBackdropOnShow(self, ...)
695 if QuestHelper_Pref.track and not QuestHelper_Pref.hide then
696 TrackerBackdropFound:Hide()
699 if orig_TrackerBackdropOnShow then
700 return orig_TrackerBackdropOnShow(self, ...)
704 function tracker:HideDefaultTracker()
705 -- The easy part: hide the original tracker
706 QuestWatchFrame:Hide()
708 -- The harder part: check if a known backdrop is present (but we don't already know about it).
709 -- If it is, make sure it's hidden, and hook its OnShow to make sure it stays that way.
710 -- Unfortunately, I can't figure out a good time to check for this once, so we'll just have
711 -- to keep checking. Hopefully, this won't happen too often.
712 if not TrackerBackdropFound then
713 if QuestWatchFrameBackdrop then
714 -- Found bEQL's QuestWatchFrameBackdrop...
715 TrackerBackdropFound = QuestWatchFrameBackdrop
718 if TrackerBackdropFound then
719 -- OK, we found something - so hide it, and make sure it doesn't rear its ugly head again
720 TrackerBackdropFound:Hide()
722 orig_TrackerBackdropOnShow = TrackerBackdropFound:GetScript("OnShow")
723 TrackerBackdropFound:SetScript("OnShow", TrackerBackdropOnShow)
728 function tracker:ShowDefaultTracker()
729 QuestWatchFrame:Show()
731 -- Make sure the default tracker is up to date on what what's being watched and what isn't.
732 QuestWatch_Update()
734 if TrackerBackdropFound then
735 TrackerBackdropFound:Show()
739 local function QuestWatchFrameOnShow(self, ...)
740 if QuestHelper_Pref.track and not QuestHelper_Pref.hide then
741 tracker:HideDefaultTracker()
744 if orig_TrackerOnShow then
745 return orig_TrackerOnShow(self, ...)
749 QuestWatchFrame:SetScript("OnShow", QuestWatchFrameOnShow)
751 function QuestHelper:ShowTracker()
752 tracker:HideDefaultTracker()
753 minbutton:Show()
755 if QuestHelper_Pref.track_minimized then
756 minbutton:SetAlpha(.3)
757 else
758 minbutton:SetAlpha(0)
759 tracker:Show()
763 function QuestHelper:HideTracker()
764 tracker:ShowDefaultTracker()
765 tracker:Hide()
766 minbutton:Hide()