1 QuestHelper
= CreateFrame("Frame", "QuestHelper", nil)
3 -- Just to make sure it's always 'seen' (there's nothing that can be seen, but still...), and therefore always updating.
4 QuestHelper
:SetFrameStrata("TOOLTIP")
6 QuestHelper_SaveVersion
= 7
7 QuestHelper_CharVersion
= 1
8 QuestHelper_Locale
= GetLocale() -- This variable is used only for the collected data, and has nothing to do with displayed text.
9 QuestHelper_Quests
= {}
10 QuestHelper_Objectives
= {}
15 QuestHelper_DefaultPref
=
29 locale
= GetLocale(), -- This variable is used for display purposes, and has nothing to do with the collected data.
30 perf_scale
= 1 -- How much background processing can the current machine handle? Higher means more load, lower means better performance.
33 QuestHelper_FlightInstructors
= {}
34 QuestHelper_FlightLinks
= {}
35 QuestHelper_FlightRoutes
= {}
36 QuestHelper_KnownFlightRoutes
= {}
38 QuestHelper
.tooltip
= CreateFrame("GameTooltip", "QuestHelperTooltip", nil, "GameTooltipTemplate")
39 QuestHelper
.objective_objects
= {}
40 QuestHelper
.user_objectives
= {}
41 QuestHelper
.quest_objects
= {}
42 QuestHelper
.player_level
= 1
43 QuestHelper
.locale
= QuestHelper_Locale
45 QuestHelper
.faction
= (UnitFactionGroup("player") == "Alliance" and 1) or
46 (UnitFactionGroup("player") == "Horde" and 2)
48 assert(QuestHelper
.faction
)
50 QuestHelper
.font
= {serif
=GameFontNormal
:GetFont(), sans
=ChatFontNormal
:GetFont(), fancy
=QuestTitleFont
:GetFont()}
52 QuestHelper
.route
= {}
53 QuestHelper
.to_add
= {}
54 QuestHelper
.to_remove
= {}
55 QuestHelper
.quest_log
= {}
56 QuestHelper
.pos
= {nil, {}, 0, 0, 1, "You are here.", 0}
57 QuestHelper
.sharing
= false -- Will be set to true when sharing with at least one user.
59 function QuestHelper
.tooltip
:GetPrevLines() -- Just a helper to make life easier.
60 local last
= self
:NumLines()
61 local name
= self
:GetName()
62 return _G
[name
.."TextLeft"..last
], _G
[name
.."TextRight"..last
]
65 function QuestHelper
:SetTargetLocation(i
, x
, y
, toffset
)
66 -- Informs QuestHelper that you're going to be at some location in toffset seconds.
67 local c
, z
= unpack(QuestHelper_ZoneLookup
[i
])
69 self
.target
= self
:CreateTable()
70 self
.target
[2] = self
:CreateTable()
72 self
.target_time
= time()+(toffset
or 0)
74 x
, y
= self
.Astrolabe
:TranslateWorldMapPosition(c
, z
, x
, y
, c
, 0)
75 self
.target
[1] = self
.zone_nodes
[i
]
76 self
.target
[3] = x
* self
.continent_scales_x
[c
]
77 self
.target
[4] = y
* self
.continent_scales_y
[c
]
79 for i
, n
in ipairs(self
.target
[1]) do
80 local a
, b
= n
.x
-self
.target
[3], n
.y
-self
.target
[4]
81 self
.target
[2][i
] = math
.sqrt(a
*a
+b
*b
)
85 function QuestHelper
:UnsetTargetLocation()
86 -- Unsets the target set above.
88 self
:ReleaseTable(self
.target
[2])
89 self
:ReleaseTable(self
.target
)
91 self
.target_time
= nil
95 function QuestHelper
:OnEvent(event
)
96 if event
== "VARIABLES_LOADED" then
97 QHFormatSetLocale(QuestHelper_Pref
.locale
or GetLocale())
98 if not QuestHelper_UID
then
99 QuestHelper_UID
= self
:CreateUID()
101 QuestHelper_SaveDate
= time()
103 QuestHelper_BuildZoneLookup()
105 if QuestHelper_Locale
~= GetLocale() then
106 self
:TextOut(QHText("LOCALE_ERROR"))
110 self
.Astrolabe
= DongleStub("Astrolabe-0.4")
112 if not self
:ZoneSanity() then
113 self
:TextOut(QHText("ZONE_LAYOUT_ERROR"))
114 message("QuestHelper: "..QHText("ZONE_LAYOUT_ERROR"))
118 QuestHelper_UpgradeDatabase(_G
)
119 QuestHelper_UpgradeComplete()
121 if QuestHelper_SaveVersion
~= 7 then
122 self
:TextOut(QHText("DOWNGRADE_ERROR"))
126 self
.player_level
= UnitLevel("player")
130 self
:UnregisterEvent("VARIABLES_LOADED")
131 self
:RegisterEvent("PLAYER_TARGET_CHANGED")
132 self
:RegisterEvent("LOOT_OPENED")
133 self
:RegisterEvent("QUEST_COMPLETE")
134 self
:RegisterEvent("QUEST_LOG_UPDATE")
135 self
:RegisterEvent("QUEST_PROGRESS")
136 self
:RegisterEvent("MERCHANT_SHOW")
137 self
:RegisterEvent("QUEST_DETAIL")
138 self
:RegisterEvent("TAXIMAP_OPENED")
139 self
:RegisterEvent("PLAYER_CONTROL_GAINED")
140 self
:RegisterEvent("PLAYER_CONTROL_LOST")
141 self
:RegisterEvent("PLAYER_LEVEL_UP")
142 self
:RegisterEvent("PARTY_MEMBERS_CHANGED")
143 self
:RegisterEvent("CHAT_MSG_ADDON")
144 self
:RegisterEvent("CHAT_MSG_SYSTEM")
145 self
:RegisterEvent("BAG_UPDATE")
146 self
:RegisterEvent("GOSSIP_SHOW")
148 self
:SetScript("OnUpdate", self
.OnUpdate
)
150 for key
, def
in pairs(QuestHelper_DefaultPref
) do
151 if QuestHelper_Pref
[key
] == nil then
152 QuestHelper_Pref
[key
] = def
156 if QuestHelper_Pref
.share
and not QuestHelper_Pref
.solo
then
160 if QuestHelper_Pref
.hide
then
161 self
.map_overlay
:Hide()
164 self
:HandlePartyChange()
167 for locale
in pairs(QuestHelper_StaticData
) do
168 if locale
~= self
.locale
then
169 -- Will delete references to locales you don't use.
170 QuestHelper_StaticData
[locale
] = nil
174 local static
= QuestHelper_StaticData
[self
.locale
]
177 if static
.flight_instructors
then for faction
in pairs(static
.flight_instructors
) do
178 if faction
~= self
.faction
then
179 -- Will delete references to flight instructors that don't belong to your faction.
180 static
.flight_instructors
[faction
] = nil
184 if static
.quest
then for faction
in pairs(static
.quest
) do
185 if faction
~= self
.faction
then
186 -- Will delete references to quests that don't belong to your faction.
187 static
.quest
[faction
] = nil
192 -- Adding QuestHelper_CharVersion, so I know if I've already converted this characters saved data.
193 if not QuestHelper_CharVersion
then
194 -- Changing per-character flight routes, now only storing the flight points they have,
195 -- will attempt to guess the routes from this.
198 for i
, l
in pairs(QuestHelper_KnownFlightRoutes
) do
199 for key
in pairs(l
) do
204 QuestHelper_KnownFlightRoutes
= routes
206 -- Deleting the player's home again.
207 -- But using the new CharVersion variable I'm adding is cleaner that what I was doing, so I'll go with it.
208 QuestHelper_Home
= nil
209 QuestHelper_CharVersion
= 1
212 if not QuestHelper_Home
then
213 -- Not going to bother complaining about the player's home not being set, uncomment this when the home is used in routing.
214 -- self:TextOut(QHText("HOME_NOT_KNOWN"))
217 QuestHelper_InitMapButton()
219 collectgarbage("collect") -- Free everything we aren't using.
221 if self
.debug_objectives
then
222 for name
, data
in pairs(self
.debug_objectives
) do
223 self
:LoadDebugObjective(name
, data
)
228 if event
== "GOSSIP_SHOW" then
229 local name
, id
= UnitName("npc"), self
:GetUnitID("npc")
231 self
:GetObjective("monster", name
).o
.id
= id
232 --self:TextOut("NPC: "..name.." = "..id)
236 if event
== "PLAYER_TARGET_CHANGED" then
237 local name
, id
= UnitName("target"), self
:GetUnitID("target")
239 self
:GetObjective("monster", name
).o
.id
= id
240 --self:TextOut("Target: "..name.." = "..id)
243 if UnitExists("target") and UnitIsVisible("target") and UnitCreatureType("target") ~= "Critter" and not UnitIsPlayer("target") and not UnitPlayerControlled("target") then
244 local index
, x
, y
= self
:UnitPosition("target")
246 if index
then -- Might not have a position if inside an instance.
249 -- Modify the weight based on how far they are from us.
250 -- We don't know the exact location (using our own location), so the farther, the less sure we are that it's correct.
251 if CheckInteractDistance("target", 3) then w
= 1
252 elseif CheckInteractDistance("target", 2) then w
= 0.89
253 elseif CheckInteractDistance("target", 1) or CheckInteractDistance("target", 4) then w
= 0.33 end
255 local monster_objective
= self
:GetObjective("monster", UnitName("target"))
256 self
:AppendObjectivePosition(monster_objective
, index
, x
, y
, w
)
257 monster_objective
.o
.faction
= UnitFactionGroup("target")
259 local level
= UnitLevel("target")
260 if level
and level
>= 1 then
261 local w
= monster_objective
.o
.levelw
or 0
262 monster_objective
.o
.level
= ((monster_objective
.o
.level
or 0)*w
+level
)/(w
+1)
263 monster_objective
.o
.levelw
= w
+1
269 if event
== "LOOT_OPENED" then
270 local target
= UnitName("target")
271 if target
and UnitIsDead("target") and UnitCreatureType("target") ~= "Critter" and not UnitIsPlayer("target") and not UnitPlayerControlled("target") then
272 local index
, x
, y
= self
:UnitPosition("target")
274 local monster_objective
= self
:GetObjective("monster", target
)
275 monster_objective
.o
.looted
= (monster_objective
.o
.looted
or 0) + 1
277 if index
then -- Might not have a position if inside an instance.
278 self
:AppendObjectivePosition(monster_objective
, index
, x
, y
)
281 for i
= 1, GetNumLootItems() do
282 local icon
, name
, number, rarity
= GetLootSlotInfo(i
)
284 if number and number >= 1 then
285 self
:AppendItemObjectiveDrop(self
:GetObjective("item", name
), name
, target
, number)
288 local _
, _
, amount
= string.find(name
, "(%d+) "..COPPER
)
289 if amount
then total
= total
+ amount
end
290 _
, _
, amount
= string.find(name
, "(%d+) "..SILVER
)
291 if amount
then total
= total
+ amount
* 100 end
292 _
, _
, amount
= string.find(name
, "(%d+) "..GOLD
)
293 if amount
then total
= total
+ amount
* 10000 end
296 self
:AppendObjectiveDrop(self
:GetObjective("item", "money"), target
, total
)
302 local container
= nil
304 -- Go through the players inventory and look for a locked item, we're probably looting it.
305 for bag
= 0,NUM_BAG_SLOTS
do
306 for slot
= 1,GetContainerNumSlots(bag
) do
307 local link
= GetContainerItemLink(bag
, slot
)
308 if link
and select(3, GetContainerItemInfo(bag
, slot
)) then
309 if container
== nil then
310 -- Found a locked item and haven't previously assigned to container, assign its name, or false if we fail to parse it.
311 container
= select(3, string.find(link
, "|h%[(.+)%]|h|r")) or false
313 -- Already tried to assign to a container. If there are multiple locked items, we give up.
321 local container_objective
= self
:GetObjective("item", container
)
322 container_objective
.o
.opened
= (container_objective
.o
.opened
or 0) + 1
324 for i
= 1, GetNumLootItems() do
325 local icon
, name
, number, rarity
= GetLootSlotInfo(i
)
326 if name
and number >= 1 then
327 self
:AppendItemObjectiveContainer(self
:GetObjective("item", name
), container
, number)
331 -- No idea where the items came from.
332 local index
, x
, y
= self
:PlayerPosition()
335 for i
= 1, GetNumLootItems() do
336 local icon
, name
, number, rarity
= GetLootSlotInfo(i
)
337 if name
and number >= 1 then
338 self
:AppendItemObjectivePosition(self
:GetObjective("item", name
), name
, index
, x
, y
)
346 if event
== "CHAT_MSG_SYSTEM" then
347 local home_name
= self
:convertPattern(ERR_DEATHBIND_SUCCESS_S
)(arg1
)
350 self
:TextOut(QHText("HOME_CHANGED"))
351 self
:TextOut(QHText("WILL_RESET_PATH"))
353 local home
= QuestHelper_Home
356 QuestHelper_Home
= home
359 home
[1], home
[2], home
[3], home
[4] = self
.i
, self
.x
, self
.y
, home_name
360 self
.defered_graph_reset
= true
365 if event
== "CHAT_MSG_ADDON" then
366 if arg1
== "QHpr" and (arg3
== "PARTY" or arg3
== "WHISPER") and arg4
~= UnitName("player") then
367 self
:HandleRemoteData(arg2
, arg4
)
371 if event
== "PARTY_MEMBERS_CHANGED" then
372 self
:HandlePartyChange()
375 if event
== "QUEST_LOG_UPDATE" or
376 event
== "PLAYER_LEVEL_UP" or
377 event
== "PARTY_MEMBERS_CHANGED" then
378 self
.defered_quest_scan
= true
381 if event
== "QUEST_DETAIL" then
382 if not self
.quest_giver
then self
.quest_giver
= {} end
383 local npc
= UnitName("npc")
385 -- Some NPCs aren't actually creatures, and so their positions might not be marked by PLAYER_TARGET_CHANGED.
386 local index
, x
, y
= self
:UnitPosition("npc")
388 if index
then -- Might not have a position if inside an instance.
389 local npc_objective
= self
:GetObjective("monster", npc
)
390 self
:AppendObjectivePosition(npc_objective
, index
, x
, y
)
391 self
.quest_giver
[GetTitleText()] = npc
396 if event
== "QUEST_COMPLETE" or event
== "QUEST_PROGRESS" then
397 local quest
= GetTitleText()
399 local level
, hash
= self
:GetQuestLevel(quest
)
400 if not level
or level
< 1 then
401 --self:TextOut("Don't know quest level for ".. quest.."!")
404 local q
= self
:GetQuest(quest
, level
, hash
)
410 local unit
= UnitName("npc")
415 -- Some NPCs aren't actually creatures, and so their positions might not be marked by PLAYER_TARGET_CHANGED.
416 local index
, x
, y
= self
:UnitPosition("npc")
417 if index
then -- Might not have a position if inside an instance.
418 local npc_objective
= self
:GetObjective("monster", unit
)
419 self
:AppendObjectivePosition(npc_objective
, index
, x
, y
)
421 elseif not q
.o
.finish
then
422 local index
, x
, y
= self
:PlayerPosition()
423 if index
then -- Might not have a position if inside an instance.
424 self
:AppendObjectivePosition(q
, index
, x
, y
)
430 if event
== "MERCHANT_SHOW" then
431 local npc_name
= UnitName("npc")
433 local npc_objective
= self
:GetObjective("monster", npc_name
)
436 local item_name
= GetMerchantItemInfo(index
)
439 local item_objective
= self
:GetObjective("item", item_name
)
440 if not item_objective
.o
.vendor
then
441 item_objective
.o
.vendor
= {npc_name
}
444 for i
, vendor
in ipairs(item_objective
.o
.vendor
) do
445 if npc_name
== vendor
then
451 table.insert(item_objective
.o
.vendor
, npc_name
)
461 if event
== "TAXIMAP_OPENED" then
465 if event
== "PLAYER_CONTROL_GAINED" then
469 if event
== "PLAYER_CONTROL_LOST" then
473 if event
== "BAG_UPDATE" then
474 for slot
= 1,GetContainerNumSlots(arg1
) do
475 local link
= GetContainerItemLink(arg1
, slot
)
477 local id
, name
= select(3, string.find(link
, "|Hitem:(%d+):.-|h%[(.-)%]|h"))
479 self
:GetObjective("item", name
).o
.id
= tonumber(id
)
486 local map_shown_decay
= 0
487 local delayed_action
= 100
488 local update_count
= 0
490 function QuestHelper
:OnUpdate()
492 update_count
= update_count
- 1
494 if update_count
<= 0 then
496 -- Reset the update count for next time around; this will make sure the body executes every time
497 -- when perf_scale >= 1, and down to 1 in 10 iterations when perf_scale < 1, or when hidden.
498 update_count
= update_count
+ (QuestHelper_Pref
.hide
and 10 or 1/QuestHelper_Pref
.perf_scale
)
500 if update_count
< 0 then
501 -- Make sure the count doesn't go perpetually negative; don't know what will happen if it underflows.
505 if self
.Astrolabe
.WorldMapVisible
then
506 -- We won't trust that the zone returned by Astrolabe is correct until map_shown_decay is 0.
508 elseif map_shown_decay
> 0 then
509 map_shown_decay
= map_shown_decay
- 1
511 SetMapToCurrentZone()
514 delayed_action
= delayed_action
- 1
515 if delayed_action
<= 0 then
517 self
:HandlePartyChange()
521 local nc
, nz
, nx
, ny
= self
.Astrolabe
:GetCurrentPlayerPosition()
523 if nc
and nc
== self
.c
and map_shown_decay
> 0 and self
.z
> 0 and self
.z
~= nz
then
524 -- There's a chance Astrolable will return the wrong zone if you're messing with the world map, if you can
525 -- be seen in that zone but aren't in it.
526 local nnx
, nny
= self
.Astrolabe
:TranslateWorldMapPosition(nc
, nz
, nx
, ny
, nc
, self
.z
)
527 if nnx
> 0 and nny
> 0 and nnx
< 1 and nny
< 1 then
528 nz
, nx
, ny
= self
.z
, nnx
, nny
532 if nc
and nc
> 0 and nz
== 0 and nc
== self
.c
and self
.z
> 0 then
533 nx
, ny
= self
.Astrolabe
:TranslateWorldMapPosition(nc
, nz
, nx
, ny
, nc
, self
.z
)
534 if nx
and ny
and nx
> -0.1 and ny
> -0.1 and nx
< 1.1 and ny
< 1.1 then
537 nc
, nz
, nx
, ny
= nil, nil, nil, nil
541 if nc
and nz
> 0 then
542 if nc
> 0 and nz
> 0 then
543 self
.c
, self
.z
, self
.x
, self
.y
= nc
or self
.c
, nz
or self
.z
, nx
or self
.x
, ny
or self
.y
544 self
.i
= QuestHelper_IndexLookup
[self
.c
][self
.z
]
546 if not self
.target
then
547 self
.pos
[3], self
.pos
[4] = self
.Astrolabe
:TranslateWorldMapPosition(self
.c
, self
.z
, self
.x
, self
.y
, self
.c
, 0)
550 self
.pos
[1] = self
.zone_nodes
[self
.i
]
551 self
.pos
[3] = self
.pos
[3] * self
.continent_scales_x
[self
.c
]
552 self
.pos
[4] = self
.pos
[4] * self
.continent_scales_y
[self
.c
]
553 for i
, n
in ipairs(self
.pos
[1]) do
555 for i
, j
in pairs(n
) do self
:TextOut("[%q]=%s %s", i
, type(j
), tostring(j
) or "???") end
558 local a
, b
= n
.x
-self
.pos
[3], n
.y
-self
.pos
[4]
559 self
.pos
[2][i
] = math
.sqrt(a
*a
+b
*b
)
566 self
.pos
[1], self
.pos
[3], self
.pos
[4] = self
.target
[1], self
.target
[3], self
.target
[4]
567 local extra_time
= math
.max(0, self
.target_time
-time())
568 for i
in ipairs(self
.pos
[1]) do
569 self
.pos
[2][i
] = self
.target
[2][i
]+extra_time
574 if self
.defered_quest_scan
then
575 self
.defered_quest_scan
= false
579 if coroutine
.status(self
.update_route
) ~= "dead" then
580 local state
, err
= coroutine
.resume(self
.update_route
, self
)
581 if not state
then self
:TextOut("|cffff0000The routing co-routine just exploded|r: |cffffff77"..err
.."|r") end
585 local level
= UnitLevel("player")
586 if level
>= 58 and self
.player_level
< 58 then
587 self
.defered_graph_reset
= true
589 self
.player_level
= level
591 self
:PumpCommMessages()
595 QuestHelper
:RegisterEvent("VARIABLES_LOADED")
596 QuestHelper
:SetScript("OnEvent", QuestHelper
.OnEvent
)