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
11 tracker
:SetHeight(100)
12 tracker
.dw
, tracker
.dh
= 200, 100
16 minbutton
:SetFrameStrata("DIALOG")
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
)
38 tracker
:SetPoint(anchor
, self
)
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)
47 minbutton
:SetPoint("RIGHT", nil, "RIGHT", -20, 230)
50 self
:TextOut("Quest tracker postion reset.")
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
66 minbutton
:RegisterForDrag("LeftButton")
68 minbutton
:SetScript("OnDragStart", function(self
)
69 if self
:IsVisible() then
71 self
:SetScript("OnUpdate", self
.moved
)
75 minbutton
:SetScript("OnDragStop", function(self
)
76 self
:SetScript("OnUpdate", nil)
77 self
:StopMovingOrSizing()
81 minbutton
:SetScript("OnEnter", function (self
)
85 minbutton
:SetScript("OnLeave", function (self
)
86 self
:SetAlpha(QuestHelper_Pref
.track_minimized
and .3 or .5)
89 local unused_items
= {}
92 local function itemupdate(item
, delta
)
95 local a
= item
:GetAlpha()
105 local t
= item
.t
+ delta
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
115 item
.x
, item
.y
= item
.dx
, item
.dy
118 item
:ClearAllPoints()
119 item
:SetPoint("TOPLEFT", tracker
, "TOPLEFT", item
.x
, item
.y
)
122 item
:SetScript("OnUpdate", nil)
126 local function itemfadeout(item
, delta
)
127 local a
= item
:GetAlpha()
135 item
:SetScript("OnUpdate", nil)
139 local t
= item
.t
+ delta
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
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
);
159 ShowUIPanel(QuestLogFrame
);
164 local function itemclick(item
, button
)
165 if button
== "RightButton" then
166 local quest
= item
.quest
169 local title
= GetQuestLogTitle(index
)
170 if not title
then break end
172 if title
== quest
then
174 -- UberQuest needs a little extra effort to work properly.
176 if UberQuest_List
:IsShown() and GetQuestLogSelection() == index
then
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()
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.
194 -- Otherwise, select it and show it.
195 QuestLog_SetSelection(index
)
197 if not QuestLogFrame
:IsShown() then
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
]
215 item
= next(unused_items
)
217 unused_items
[item
] = nil
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
)
227 item
.text
:SetFont(QuestHelper
.font
.serif
, 12)
228 item
.text
:SetTextColor(.82, .65, 0)
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
)
246 item
.text
:SetText(name
)
247 local w
, h
= item
.text
:GetWidth(), item
.text
:GetHeight()
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
259 item
:SetScript("OnUpdate", itemupdate
)
265 local function ccode(r1
, g1
, b1
, r2
, g2
, b2
, p
)
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
283 colour
= "|cffff0000"
284 elseif delta
>= 0 then
285 colour
= ccode(1, 1, 0, 1, 0, 0, delta
/5)
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
295 colour
= ccode(0, 1, 0, 1, 1, 0, (grey
-level
)/(grey
-player_level
))
297 colour
= ccode(.4, .4, .4, .2, .8, .2, (1-level
)/(1-grey
))
301 title
= string.format("%s%s", colour
, 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
)
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
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
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
)
353 local function addobj(objective
, seen
, obj_index_lookup
, filter
, x
, y
, gap
)
357 if objective
.cat
== "quest" then
360 quest
= objective
.quest
363 if quest
and quest
.watched
and not seen
[quest
] and (not filter
or filter(quest
)) then
366 local level
, name
= string.match(quest
.obj
, "^(%d+)/%d*/(.*)$")
369 level
, name
= string.match(quest
.obj
, "^(%d+)/(.*)$")
371 level
, name
= 1, quest
.obj
375 level
= tonumber(level
) or 1
378 local w
, h
= addItem(qname(name
, level
), true, quest
, -(y
+gap
), name
)
383 for obj
in pairs(quest
.swap_after
or quest
.after
) do
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
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
406 pct
= seen_sum
/ seen_max
407 local list
= QuestHelper
:CreateTable()
409 for val
in pairs(seen_have
) do
410 table.insert(list
, val
)
415 local have
= table.concat(list
, ", ")
417 for i
= #list
,1,-1 do
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.
431 local need
= table.concat(list
, ", ")
433 text
= string.format((tonumber(have
) and tonumber(need
) and QUEST_ITEMS_NEEDED
) or QUEST_FACTION_NEEDED
,
437 QuestHelper
:ReleaseTable(list
)
440 QuestHelper
:ReleaseTable(seen_have
)
441 QuestHelper
:ReleaseTable(seen_need
)
444 if seen_sum
~= seen_max
then
446 w
, h
= addItem(oname(text
, pct
), quest
, obj
, -y
)
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
)
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.
469 local t
= self
.t
+delta
472 self
:SetWidth(self
.dw
)
473 self
:SetHeight(self
.dh
)
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
486 local x
, y
= GetCursorPosition()
487 local s
= 1/self
:GetEffectiveScale()
490 local inside
= x
>= self
:GetLeft() and y
>= self
:GetBottom() and x
< self
:GetRight() and y
< self
:GetTop()
491 if inside
~= was_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
504 local quests
= QuestHelper
.quest_log
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
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
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
540 -- Add Quests that are watched but not in the route.
542 local uq_settings
= UberQuest_Config
[UnitName("player")]
544 local list
= uq_settings
.selected
548 local name
= GetQuestLogTitle(i
)
549 if not name
then break end
550 quest_lookup
[name
] = quest_lookup
[i
]
553 for name
in pairs(list
) do
554 local q
= quest_lookup
[name
]
555 if q
and not obj_index_lookup
[q
] then
557 x
, y
, gap
, count
= addobj(q
, seen
, obj_index_lookup
, nil, x
, y
, gap
)
558 added
= added
+ count
564 for i
= 1,GetNumQuestWatches() do
565 local q
= quest_lookup
[GetQuestIndexForWatch(i
)]
566 if q
and not obj_index_lookup
[q
] then
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
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.
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
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
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
619 if x
~= tracker
.dw
or y
~= tracker
.dy
then
621 tracker
.sw
, tracker
.sh
= tracker
:GetWidth(), tracker
:GetHeight()
622 tracker
.dw
, tracker
.dh
= x
, y
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(...)
637 return orig_AddQuestWatch(...)
640 function RemoveQuestWatch(...)
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.
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()
713 if QuestHelper_Pref
.track_minimized
then
714 minbutton
:SetAlpha(.3)
716 minbutton
:SetAlpha(0)
721 function QuestHelper
:HideTracker()
722 tracker
:ShowDefaultTracker()