Merge branch 'master' of git://cams.pavlovian.net/questhelper
[QuestHelper.git] / main.lua
blobc872b81453ac507ad94be346777c41681937cc0f
1 QuestHelper_File["main.lua"] = "Development Version"
2 QuestHelper_Loadtime["main.lua"] = GetTime()
4 local version_string = QuestHelper_File["main.lua"] -- we pretty much save this only so we can inform the user that they're using a beta version
6 -- Just to make sure it's always 'seen' (there's nothing that can be seen, but still...), and therefore always updating.
7 QuestHelper:SetFrameStrata("TOOLTIP")
9 QuestHelper_SaveVersion = 10
10 QuestHelper_CharVersion = 1
11 QuestHelper_Locale = GetLocale() -- This variable is used only for the collected data, and has nothing to do with displayed text.
12 QuestHelper_Quests = {}
13 QuestHelper_Objectives = {}
15 QuestHelper_Pref =
18 QuestHelper_DefaultPref =
20 filter_level=true,
21 filter_zone=false,
22 filter_done=false,
23 filter_blocked=false, -- Hides blocked objectives, such as quest turn-ins for incomplete quests
24 filter_watched=false, -- Limits to Watched objectives
25 filter_group=true,
26 filter_group_param=2,
27 filter_wintergrasp=true,
28 track=true,
29 track_minimized=false,
30 track_scale=1,
31 track_level=true,
32 track_qcolour=true,
33 track_ocolour=true,
34 track_size=10,
35 tooltip=true,
36 share = true,
37 scale = 1,
38 solo = false,
39 comm = false,
40 show_ants = true,
41 level = 3,
42 hide = false,
43 cart_wp_new = false,
44 tomtom_wp_new = false,
45 arrow = true,
46 arrow_locked = false,
47 arrow_arrowsize = 1,
48 arrow_textsize = 1,
49 metric = (QuestHelper_Locale ~= "enUS" and QuestHelper_Locale ~= "esMX"),
50 flight_time = true,
51 locale = GetLocale(), -- This variable is used for display purposes, and has nothing to do with the collected data.
52 perf_scale_2 = 1, -- How much background processing can the current machine handle? Higher means more load, lower means better performance.
53 perfload_scale = 1, -- Performance scale to use on startup
54 map_button = true,
57 -- We do it here also in case things decide they care about preferences before the init function is called. Shouldn't happen, but maybe does.
58 setmetatable(QuestHelper_Pref, {__index=QuestHelper_DefaultPref})
60 QuestHelper_FlightInstructors = {}
61 QuestHelper_FlightLinks = {}
62 QuestHelper_FlightRoutes = {}
63 QuestHelper_KnownFlightRoutes = {}
64 QuestHelper_SeenRealms = {}
66 QuestHelper.tooltip = CreateFrame("GameTooltip", "QuestHelperTooltip", nil, "GameTooltipTemplate")
67 QuestHelper.objective_objects = {}
68 QuestHelper.user_objectives = {}
69 QuestHelper.quest_objects = {}
70 QuestHelper.player_level = 1
71 QuestHelper.locale = QuestHelper_Locale
73 QuestHelper.faction = (UnitFactionGroup("player") == "Alliance" and 1) or
74 (UnitFactionGroup("player") == "Horde" and 2)
76 assert(QuestHelper.faction)
78 QuestHelper.font = {serif=GameFontNormal:GetFont(), sans=ChatFontNormal:GetFont(), fancy=QuestTitleFont:GetFont()}
80 function QuestHelper:GetFontPath(list_string, font)
81 if list_string then
82 for name in string.gmatch(list_string, "[^;]+") do
83 if font:SetFont(name, 10) then
84 return name
85 elseif font:SetFont("Interface\\AddOns\\QuestHelper\\Fonts\\"..name, 10) then
86 return "Interface\\AddOns\\QuestHelper\\Fonts\\"..name
87 end
88 end
89 end
90 end
92 function QuestHelper:SetLocaleFonts()
93 self.font.sans = nil
94 self.font.serif = nil
95 self.font.fancy = nil
97 local font = self:CreateText(self)
99 if QuestHelper_Locale ~= QuestHelper_Pref.locale then
100 -- Only use alternate fonts if using a language the client wasn't intended for.
101 local replacements = QuestHelper_SubstituteFonts[QuestHelper_Pref.locale]
102 if replacements then
103 self.font.sans = self:GetFontPath(replacements.sans, font)
104 self.font.serif = self:GetFontPath(replacements.serif, font)
105 self.font.fancy = self:GetFontPath(replacements.fancy, font)
109 self.font.sans = self.font.sans or self:GetFontPath(QuestHelper_Pref.locale.."_sans.ttf", font)
110 self.font.serif = self.font.serif or self:GetFontPath(QuestHelper_Pref.locale.."_serif.ttf", font) or self.font.sans
111 self.font.fancy = self.font.fancy or self:GetFontPath(QuestHelper_Pref.locale.."_fancy.ttf", font) or self.font.serif
113 self:ReleaseText(font)
115 self.font.sans = self.font.sans or ChatFontNormal:GetFont()
116 self.font.serif = self.font.serif or GameFontNormal:GetFont()
117 self.font.fancy = self.font.fancy or QuestTitleFont:GetFont()
119 -- Need to change the font of the chat frame, for any messages that QuestHelper displays.
120 -- This should do nothing if not using an alternate font.
121 DEFAULT_CHAT_FRAME:SetFont(self.font.sans, select(2, DEFAULT_CHAT_FRAME:GetFont()))
124 QuestHelper.route = {}
125 QuestHelper.to_add = {}
126 QuestHelper.to_remove = {}
127 QuestHelper.quest_log = {}
128 QuestHelper.pos = {nil, {}, 0, 0, 1, "You are here.", 0}
129 QuestHelper.sharing = false -- Will be set to true when sharing with at least one user.
131 function QuestHelper.tooltip:GetPrevLines() -- Just a helper to make life easier.
132 local last = self:NumLines()
133 local name = self:GetName()
134 return _G[name.."TextLeft"..last], _G[name.."TextRight"..last]
137 function QuestHelper:SetTargetLocation(i, x, y, toffset)
138 -- Informs QuestHelper that you're going to be at some location in toffset seconds.
139 local c, z = unpack(QuestHelper_ZoneLookup[i])
141 self.target = self:CreateTable()
142 self.target[2] = self:CreateTable()
144 self.target_time = time()+(toffset or 0)
146 x, y = self.Astrolabe:TranslateWorldMapPosition(c, z, x, y, c, 0)
147 self.target[1] = self.zone_nodes[i]
148 self.target[3] = x * self.continent_scales_x[c]
149 self.target[4] = y * self.continent_scales_y[c]
151 self:SetTargetLocationRecalculate()
154 function QuestHelper:SetTargetLocationRecalculate()
155 if self.target then
156 for i, n in ipairs(self.target[1]) do
157 local a, b = n.x-self.target[3], n.y-self.target[4]
158 self.target[2][i] = math.sqrt(a*a+b*b)
163 function QuestHelper:UnsetTargetLocation()
164 -- Unsets the target set above.
165 if self.target then
166 self:ReleaseTable(self.target[2])
167 self:ReleaseTable(self.target)
168 self.target = nil
169 self.target_time = nil
173 local interruptcount = 0 -- counts how many "played gained control" messages we recieve, used for flight paths
174 local init_cartographer_later = false
176 QH_Event("ADDON_LOADED", function (addonid)
177 if addonid ~= "QuestHelper" then return end
178 local self = QuestHelper -- whee hack hack hack
180 QuestHelper_Loadtime["init_start"] = GetTime()
182 -- Use DefaultPref as fallback for unset preference keys.
183 setmetatable(QuestHelper_Pref, {__index=QuestHelper_DefaultPref})
185 local file_problem = false
186 local expected_version = GetAddOnMetadata("QuestHelper", "Version")
188 local expected_files =
190 ["bst_pre.lua"] = true,
191 ["bst_post.lua"] = true,
192 ["bst_astrolabe.lua"] = true,
193 ["bst_ctl.lua"] = true,
194 ["bst_libaboutpanel.lua"] = true,
196 ["manager_event.lua"] = true,
198 ["upgrade.lua"] = true,
199 ["main.lua"] = true,
200 ["recycle.lua"] = true,
201 ["objective.lua"] = true,
202 ["quest.lua"] = true,
203 ["utility.lua"] = true,
204 ["dodads.lua"] = true,
205 ["teleport.lua"] = true,
206 ["pathfinding.lua"] = true,
207 ["routing.lua"] = true,
208 ["custom.lua"] = true,
209 ["menu.lua"] = true,
210 ["nag.lua"] = true,
211 ["comm.lua"] = true,
212 ["mapbutton.lua"] = true,
213 ["help.lua"] = true,
214 ["pattern.lua"] = true,
215 ["flightpath.lua"] = true,
216 ["tracker.lua"] = true,
217 ["objtips.lua"] = true,
218 ["cartographer.lua"] = true,
219 ["cartographer_is_terrible.lua"] = true,
220 ["tomtom.lua"] = true,
221 ["textviewer.lua"] = true,
222 ["error.lua"] = true,
223 ["timeslice.lua"] = true,
224 ["lang.lua"] = true,
225 ["core.lua"] = true,
226 ["tooltip.lua"] = true,
227 ["arrow.lua"] = true,
229 ["static.lua"] = true,
230 ["static_1.lua"] = true,
231 ["static_2.lua"] = true,
232 ["static_deDE.lua"] = true,
233 ["static_deDE_1.lua"] = true,
234 ["static_deDE_2.lua"] = true,
235 ["static_enUS.lua"] = true,
236 ["static_enUS_1.lua"] = true,
237 ["static_enUS_2.lua"] = true,
238 ["static_esES.lua"] = true,
239 ["static_esES_1.lua"] = true,
240 ["static_esES_2.lua"] = true,
241 ["static_esMX.lua"] = true,
242 ["static_esMX_1.lua"] = true,
243 ["static_esMX_2.lua"] = true,
244 ["static_frFR.lua"] = true,
245 ["static_frFR_1.lua"] = true,
246 ["static_frFR_2.lua"] = true,
247 ["static_koKR.lua"] = true,
248 ["static_koKR_1.lua"] = true,
249 ["static_koKR_2.lua"] = true,
250 ["static_ruRU.lua"] = true,
251 ["static_ruRU_1.lua"] = true,
252 ["static_ruRU_2.lua"] = true,
253 ["static_zhTW.lua"] = true,
254 ["static_zhTW_1.lua"] = true,
255 ["static_zhTW_2.lua"] = true,
257 ["collect.lua"] = true,
258 ["collect_achievement.lua"] = true,
259 ["collect_lzw.lua"] = true,
260 ["collect_traveled.lua"] = true,
261 ["collect_zone.lua"] = true,
262 ["collect_location.lua"] = true,
263 ["collect_merger.lua"] = true,
264 ["collect_monster.lua"] = true,
265 ["collect_item.lua"] = true,
266 ["collect_object.lua"] = true,
267 ["collect_loot.lua"] = true,
268 ["collect_patterns.lua"] = true,
269 ["collect_flight.lua"] = true,
270 ["collect_util.lua"] = true,
271 ["collect_quest.lua"] = true,
272 ["collect_equip.lua"] = true,
273 ["collect_notifier.lua"] = true,
274 ["collect_bitstream.lua"] = true,
275 ["collect_spec.lua"] = true,
276 ["collect_upgrade.lua"] = true,
277 ["collect_merchant.lua"] = true,
278 ["collect_warp.lua"] = true,
280 ["filter_core.lua"] = true,
281 ["filter_base.lua"] = true,
283 ["routing_debug.lua"] = true,
284 ["routing_loc.lua"] = true,
285 ["routing_route.lua"] = true,
286 ["routing_core.lua"] = true,
287 ["routing_controller.lua"] = true,
288 ["routing_hidden.lua"] = true,
290 ["director_quest.lua"] = true,
291 ["director_achievement.lua"] = true,
293 ["db_get.lua"] = true,
295 ["graph_core.lua"] = true,
296 ["graph_flightpath.lua"] = true,
299 local uninstallederr = ""
301 for file, version in pairs(QuestHelper_File) do
302 if not expected_files[file] then
303 local errmsg = "Unexpected QuestHelper file: "..file
304 DEFAULT_CHAT_FRAME:AddMessage(errmsg)
305 uninstallederr = uninstallederr .. " " .. errmsg .. "\n"
306 file_problem = true
307 elseif version ~= expected_version then
308 local errmsg = "Wrong version of QuestHelper file: "..file.." (found '"..version.."', should be '"..expected_version.."')"
309 DEFAULT_CHAT_FRAME:AddMessage(errmsg)
310 uninstallederr = uninstallederr .. " " .. errmsg .. "\n"
311 if version ~= "Development Version" and expected_version ~= "Development Version" then
312 -- Developers are allowed to mix dev versions with release versions
313 file_problem = true
318 for file in pairs(expected_files) do
319 if not QuestHelper_File[file] then
320 local errmsg = "Missing QuestHelper file: "..file
321 DEFAULT_CHAT_FRAME:AddMessage(errmsg)
322 uninstallederr = uninstallederr .. " " .. errmsg .. "\n"
323 if not (expected_version == "Development Version" and file:match("static.*")) then file_problem = true end
327 -- Don't need this table anymore.
328 QuestHelper_File = nil
330 if QuestHelper_StaticData and not QuestHelper_StaticData[GetLocale()] then
331 local errmsg = "Static data does not seem to exist"
332 DEFAULT_CHAT_FRAME:AddMessage(errmsg)
334 -- TODO: Are you sure this should be an error? Shouldn't we let people we don't have data for collect their own?
335 uninstallederr = uninstallederr .. " " .. errmsg .. "\n"
336 file_problem = true
339 if file_problem then
340 message(QHText("PLEASE_RESTART"))
341 QuestHelper_ErrorCatcher_ExplicitError(true, "not-installed-properly" .. "\n" .. uninstallederr)
342 QuestHelper = nil -- Just in case anybody else is checking for us, we're not home
343 return
346 if not GetCategoryList or not GetQuestLogSpecialItemInfo or not WatchFrame_RemoveObjectiveHandler then
347 message(QHText("PRIVATE_SERVER"))
348 QuestHelper_ErrorCatcher_ExplicitError(true, "error id cakbep ten T")
349 QuestHelper = nil
350 return
353 if not DongleStub then
354 message(QHText("NOT_UNZIPPED_CORRECTLY"))
355 QuestHelper_ErrorCatcher_ExplicitError(true, "not-unzipped-properly")
356 QuestHelper = nil -- Just in case anybody else is checking for us, we're not home
357 return
360 QuestHelper_ErrorCatcher_CompletelyStarted()
362 if not QuestHelper_StaticData then
363 -- If there is no static data for some mysterious reason, create an empty table so that
364 -- other parts of the code can carry on as usual, using locally collected data if it exists.
365 QuestHelper_StaticData = {}
368 QHFormatSetLocale(QuestHelper_Pref.locale or GetLocale())
370 if not QuestHelper_UID then
371 QuestHelper_UID = self:CreateUID()
373 QuestHelper_SaveDate = time()
375 QuestHelper_BuildZoneLookup()
376 QH_Graph_Init()
377 load_graph_links()
379 if QuestHelper_Locale ~= GetLocale() then
380 self:TextOut(QHText("LOCALE_ERROR"))
381 return
384 if not self:ZoneSanity() then
385 self:TextOut(QHText("ZONE_LAYOUT_ERROR"))
386 message("QuestHelper: "..QHText("ZONE_LAYOUT_ERROR"))
387 return
390 QuestHelper_UpgradeDatabase(_G)
391 QuestHelper_UpgradeComplete()
393 if QuestHelper_SaveVersion ~= 10 then
394 self:TextOut(QHText("DOWNGRADE_ERROR"))
395 return
398 if QuestHelper_IsPolluted(_G) then
399 self:TextOut(QHFormat("NAG_POLLUTED"))
400 self:Purge(nil, true, true)
403 local signature = expected_version .. " on " .. GetBuildInfo()
404 QuestHelper_Quests[signature] = QuestHelper_Quests[signature] or {}
405 QuestHelper_Objectives[signature] = QuestHelper_Objectives[signature] or {}
406 QuestHelper_FlightInstructors[signature] = QuestHelper_FlightInstructors[signature] or {}
407 QuestHelper_FlightRoutes[signature] = QuestHelper_FlightRoutes[signature] or {}
409 QuestHelper_Quests_Local = QuestHelper_Quests[signature]
410 QuestHelper_Objectives_Local = QuestHelper_Objectives[signature]
411 QuestHelper_FlightInstructors_Local = QuestHelper_FlightInstructors[signature]
412 QuestHelper_FlightRoutes_Local = QuestHelper_FlightRoutes[signature]
414 QuestHelper_SeenRealms[GetRealmName()] = true -- some attempt at tracking private servers
416 QH_Collector_Init()
417 DB_Init()
419 self.player_level = UnitLevel("player")
421 self:SetLocaleFonts()
423 if QuestHelper_Pref.share and not QuestHelper_Pref.solo then
424 self:EnableSharing()
427 if QuestHelper_Pref.hide then
428 self.map_overlay:Hide()
431 self:HandlePartyChange()
433 self:Nag("all")
435 for locale in pairs(QuestHelper_StaticData) do
436 if locale ~= self.locale then
437 -- Will delete references to locales you don't use.
438 QuestHelper_StaticData[locale] = nil
439 _G["QuestHelper_StaticData_" .. locale] = nil
443 local static = QuestHelper_StaticData[self.locale]
445 if static then
446 if static.flight_instructors then for faction in pairs(static.flight_instructors) do
447 if faction ~= self.faction then
448 -- Will delete references to flight instructors that don't belong to your faction.
449 static.flight_instructors[faction] = nil
451 end end
453 if static.quest then for faction in pairs(static.quest) do
454 if faction ~= self.faction then
455 -- Will delete references to quests that don't belong to your faction.
456 static.quest[faction] = nil
458 end end
461 -- Adding QuestHelper_CharVersion, so I know if I've already converted this characters saved data.
462 if not QuestHelper_CharVersion then
463 -- Changing per-character flight routes, now only storing the flight points they have,
464 -- will attempt to guess the routes from this.
465 local routes = {}
467 for i, l in pairs(QuestHelper_KnownFlightRoutes) do
468 for key in pairs(l) do
469 routes[key] = true
473 QuestHelper_KnownFlightRoutes = routes
475 -- Deleting the player's home again.
476 -- But using the new CharVersion variable I'm adding is cleaner that what I was doing, so I'll go with it.
477 QuestHelper_Home = nil
478 QuestHelper_CharVersion = 1
481 if not QuestHelper_Home then
482 -- Not going to bother complaining about the player's home not being set, uncomment this when the home is used in routing.
483 -- self:TextOut(QHText("HOME_NOT_KNOWN"))
486 if QuestHelper_Pref.map_button then
487 QuestHelper:InitMapButton()
490 if QuestHelper_Pref.cart_wp_new then
491 init_cartographer_later = true
494 if QuestHelper_Pref.tomtom_wp_new then
495 self:EnableTomTom()
498 self.tracker:SetScale(QuestHelper_Pref.track_scale)
500 if QuestHelper_Pref.track and not QuestHelper_Pref.hide then
501 self:ShowTracker()
504 local version = GetAddOnMetadata("QuestHelper", "Version") or "Unknown"
506 local major, minor = (version_string or ""):match("^(%d+)%.(%d+)")
507 major, minor = tonumber(major), tonumber(minor)
509 -- For versions before 0.82, we're changing the default level offset to 3.
510 if major == 0 and minor and minor < 82 and QuestHelper_Pref.level == 2 then
511 QuestHelper_Pref.level = nil
514 -- For versions before 0.84...
515 if major == 0 and minor and minor < 84 then
516 -- remove all keys that match their default setting.
517 for key, val in pairs(QuestHelper_DefaultPref) do
518 if QuestHelper_Pref[key] == val then
519 QuestHelper_Pref[key] = nil
524 QH_Hook(self, "OnUpdate", self.OnUpdate)
526 -- Seems to do its own garbage collection pass before fully loading, so I'll just rely on that
527 --collectgarbage("collect") -- Free everything we aren't using.
529 --[[
530 if self.debug_objectives then
531 for name, data in pairs(self.debug_objectives) do
532 self:LoadDebugObjective(name, data)
534 end]]
536 -- wellllp
537 QH_Arrow_SetScale()
538 QH_Arrow_SetTextScale()
540 --[[
541 QH_Timeslice_Add(function ()
542 self:ResetPathing()
543 self.Routing:Initialize() -- Set up the routing task
544 end, "init")]] -- FUCK YOU BOXBOT
546 --[[ -- This is just an example of how the WoW profiler biases its profiles heavily.
547 function C()
550 function A()
551 q = 0
552 for x = 0, 130000000, 1 do
556 function B()
557 q = 0
558 for x = 0, 12000000, 1 do
563 function B2()
564 q = 0
565 for x = 0, 1200000, 1 do
566 --q = q + x
571 debugprofilestart()
573 local ta = debugprofilestop()
575 local tb = debugprofilestop()
577 local tc = debugprofilestop()
579 QuestHelper:TextOut(string.format("%d %d %d", ta, tb - ta, tc - tb))
580 QuestHelper:TextOut(string.format("%d %d", GetFunctionCPUUsage(A), GetFunctionCPUUsage(B)))
582 --/script SetCVar("scriptProfile", value)]]
584 LibStub("LibAboutPanelQH").new(nil, "QuestHelper")
586 QuestHelper_Loadtime["init_end"] = GetTime()
588 QuestHelper.loading_main = QuestHelper.CreateLoadingCounter()
590 QuestHelper.loading_flightpath = QuestHelper.loading_main:MakeSubcategory(1)
591 QuestHelper.loading_preroll = QuestHelper.loading_main:MakeSubcategory(1)
593 QH_Event("CHAT_MSG_ADDON", function (...)
594 if arg1 == "QHpr" and arg4 ~= UnitName("player") then
595 QH_Questcomm_Msg(arg2, arg4)
597 end)
599 QH_Event({"PARTY_MEMBERS_CHANGED", "UNIT_LEVEL", "RAID_ROSTER_UPDATE"}, function ()
600 QH_Filter_Group_Sync()
601 QH_Route_Filter_Rescan("filter_quest_level")
602 QH_Route_Filter_Rescan("filter_quest_group")
603 end)
605 QH_Event({"PARTY_MEMBERS_CHANGED", "RAID_ROSTER_UPDATE"}, function ()
606 QH_Questcomm_Sync()
607 end)
609 QH_Event("PLAYER_LEVEL_UP", function ()
610 self.player_level = arg1
611 QH_Route_Filter_Rescan("filter_quest_level")
612 end)
614 QH_Event("TAXIMAP_OPENED", function ()
615 self:taxiMapOpened()
616 end)
618 QH_Event({"ZONE_CHANGED", "ZONE_CHANGED_INDOORS", "ZONE_CHANGED_NEW_AREA"}, function()
619 QH_Route_Filter_Rescan()
620 end)
622 end)
624 local startup_time
625 local please_donate_enabled = false
626 local please_donate_initted = false
628 --[==[
629 function QuestHelper:OnEvent(event)
630 local tstart = GetTime()
632 --[[
633 if event == "GOSSIP_SHOW" then
634 local name, id = UnitName("npc"), self:GetUnitID("npc")
635 if name and id then
636 self:GetObjective("monster", name).o.id = id
637 --self:TextOut("NPC: "..name.." = "..id)
639 end]]
641 --[[if event == "PLAYER_TARGET_CHANGED" then
642 local name, id = UnitName("target"), self:GetUnitID("target")
643 if name and id then
644 self:GetObjective("monster", name).o.id = id
645 --self:TextOut("Target: "..name.." = "..id)
648 if UnitExists("target") and UnitIsVisible("target") and UnitCreatureType("target") ~= "Critter" and not UnitIsPlayer("target") and not UnitPlayerControlled("target") then
649 local index, x, y = self:UnitPosition("target")
651 if index then -- Might not have a position if inside an instance.
652 local w = 0.1
654 -- Modify the weight based on how far they are from us.
655 -- We don't know the exact location (using our own location), so the farther, the less sure we are that it's correct.
656 if CheckInteractDistance("target", 3) then w = 1
657 elseif CheckInteractDistance("target", 2) then w = 0.89
658 elseif CheckInteractDistance("target", 1) or CheckInteractDistance("target", 4) then w = 0.33 end
660 local monster_objective = self:GetObjective("monster", UnitName("target"))
661 self:AppendObjectivePosition(monster_objective, index, x, y, w)
663 monster_objective.o.faction = (UnitFactionGroup("target") == "Alliance" and 1) or
664 (UnitFactionGroup("target") == "Horde" and 2) or nil
666 local level = UnitLevel("target")
667 if level and level >= 1 then
668 local w = monster_objective.o.levelw or 0
669 monster_objective.o.level = ((monster_objective.o.level or 0)*w+level)/(w+1)
670 monster_objective.o.levelw = w+1
674 end]]
676 --[[if event == "LOOT_OPENED" then
677 local target = UnitName("target")
678 if target and UnitIsDead("target") and UnitCreatureType("target") ~= "Critter" and not UnitIsPlayer("target") and not UnitPlayerControlled("target") then
679 local index, x, y = self:UnitPosition("target")
681 local monster_objective = self:GetObjective("monster", target)
682 monster_objective.o.looted = (monster_objective.o.looted or 0) + 1
684 if index then -- Might not have a position if inside an instance.
685 self:AppendObjectivePosition(monster_objective, index, x, y)
688 for i = 1, GetNumLootItems() do
689 local icon, name, number, rarity = GetLootSlotInfo(i)
690 if name then
691 if number and number >= 1 then
692 self:AppendItemObjectiveDrop(self:GetObjective("item", name), name, target, number)
693 else
694 local total = (name:match(COPPER_AMOUNT:gsub("%%d", "%(%%d+%)")) or 0) +
695 (name:match(SILVER_AMOUNT:gsub("%%d", "%(%%d+%)")) or 0) * 100 +
696 (name:match(GOLD_AMOUNT:gsub("%%d", "%(%%d+%)")) or 0) * 10000
698 if total > 0 then
699 self:AppendObjectiveDrop(self:GetObjective("item", "money"), target, total)
704 else
705 local container = nil
707 -- Go through the players inventory and look for a locked item, we're probably looting it.
708 for bag = 0,NUM_BAG_SLOTS do
709 for slot = 1,GetContainerNumSlots(bag) do
710 local link = GetContainerItemLink(bag, slot)
711 if link and select(3, GetContainerItemInfo(bag, slot)) then
712 if container == nil then
713 -- Found a locked item and haven't previously assigned to container, assign its name, or false if we fail to parse it.
714 container = select(3, string.find(link, "|h%[(.+)%]|h|r")) or false
715 else
716 -- Already tried to assign to a container. If there are multiple locked items, we give up.
717 container = false
723 if container then
724 local container_objective = self:GetObjective("item", container)
725 container_objective.o.opened = (container_objective.o.opened or 0) + 1
727 for i = 1, GetNumLootItems() do
728 local icon, name, number, rarity = GetLootSlotInfo(i)
729 if name and number >= 1 then
730 self:AppendItemObjectiveContainer(self:GetObjective("item", name), container, number)
733 else
734 -- No idea where the items came from.
735 local index, x, y = self:PlayerPosition()
737 if index then
738 for i = 1, GetNumLootItems() do
739 local icon, name, number, rarity = GetLootSlotInfo(i)
740 if name and number >= 1 then
741 self:AppendItemObjectivePosition(self:GetObjective("item", name), name, index, x, y)
747 end]]
749 --[[if event == "CHAT_MSG_SYSTEM" then
750 local home_name = self:convertPattern(ERR_DEATHBIND_SUCCESS_S)(arg1)
751 if home_name then
752 if self.i then
753 self:TextOut(QHText("HOME_CHANGED"))
754 self:TextOut(QHText("WILL_RESET_PATH"))
756 local home = QuestHelper_Home
757 if not home then
758 home = {}
759 QuestHelper_Home = home
762 home[1], home[2], home[3], home[4] = self.i, self.x, self.y, home_name
763 self.defered_graph_reset = true
766 end]]
771 --[[if event == "QUEST_DETAIL" then
772 if not self.quest_giver then self.quest_giver = {} end
773 local npc = UnitName("npc")
774 if npc then
775 -- Some NPCs aren't actually creatures, and so their positions might not be marked by PLAYER_TARGET_CHANGED.
776 local index, x, y = self:UnitPosition("npc")
778 if index then -- Might not have a position if inside an instance.
779 local npc_objective = self:GetObjective("monster", npc)
780 self:AppendObjectivePosition(npc_objective, index, x, y)
781 self.quest_giver[GetTitleText()] = npc
784 end]]
786 --[[if event == "QUEST_COMPLETE" or event == "QUEST_PROGRESS" then
787 local quest = GetTitleText()
788 if quest then
789 local level, hash = self:GetQuestLevel(quest)
790 if not level or level < 1 then
791 --self:TextOut("Don't know quest level for ".. quest.."!")
792 return
794 local q = self:GetQuest(quest, level, hash)
796 if q.need_hash then
797 q.o.hash = hash
800 local unit = UnitName("npc")
801 if unit then
802 q.o.finish = unit
803 q.o.pos = nil
805 -- Some NPCs aren't actually creatures, and so their positions might not be marked by PLAYER_TARGET_CHANGED.
806 local index, x, y = self:UnitPosition("npc")
807 if index then -- Might not have a position if inside an instance.
808 local npc_objective = self:GetObjective("monster", unit)
809 self:AppendObjectivePosition(npc_objective, index, x, y)
811 elseif not q.o.finish then
812 local index, x, y = self:PlayerPosition()
813 if index then -- Might not have a position if inside an instance.
814 self:AppendObjectivePosition(q, index, x, y)
818 end]]
820 --[[if event == "MERCHANT_SHOW" then
821 local npc_name = UnitName("npc")
822 if npc_name then
823 local npc_objective = self:GetObjective("monster", npc_name)
824 local index = 1
825 while true do
826 local item_name = GetMerchantItemInfo(index)
827 if item_name then
828 index = index + 1
829 local item_objective = self:GetObjective("item", item_name)
830 if not item_objective.o.vendor then
831 item_objective.o.vendor = {npc_name}
832 else
833 local known = false
834 for i, vendor in ipairs(item_objective.o.vendor) do
835 if npc_name == vendor then
836 known = true
837 break
840 if not known then
841 table.insert(item_objective.o.vendor, npc_name)
844 else
845 break
849 end]]
851 if event == "TAXIMAP_OPENED" then
852 self:taxiMapOpened()
855 --[[if event == "PLAYER_CONTROL_GAINED" then
856 interruptcount = interruptcount + 1
857 end]]
859 --[[if event == "BAG_UPDATE" then
860 for slot = 1,GetContainerNumSlots(arg1) do
861 local link = GetContainerItemLink(arg1, slot)
862 if link then
863 local id, name = select(3, string.find(link, "|Hitem:(%d+):.-|h%[(.-)%]|h"))
864 if name then
865 self:GetObjective("item", name).o.id = tonumber(id)
869 end]]
871 if event == "CHAT_MSG_CHANNEL_NOTICE" and please_donate_enabled and not please_donate_initted then
872 please_donate_enabled = QHNagInit()
873 startup_time = GetTime()
874 please_donate_initted = true
876 QHUpdateNagInit()
879 if event == "ZONE_CHANGED" or event == "ZONE_CHANGED_INDOORS" or event == "ZONE_CHANGED_NEW_AREA" then
880 QH_Route_Filter_Rescan()
883 QH_Timeslice_Increment(GetTime() - tstart, "event")
884 end]==]
886 local map_shown_decay = 0
887 local delayed_action = 100
888 --local update_count = 0
889 local ontaxi = false
890 local frams = 0
892 QH_OnUpdate_High(function ()
893 local self = QuestHelper -- hoorj
894 local tstart = GetTime()
895 frams = frams + 1
897 if not QuestHelper_Loadtime["onupdate"] then QuestHelper_Loadtime["onupdate"] = GetTime() end
899 if false and frams == 60 then
900 self:ShowText([[
901 This is a |cffff8000beta of QuestHelper|r. Be warned: It may crash. It may lock up. It may give bad advice. It may spew errors. It shouldn't spam people, delete your hard-won epics, or make your computer catch on fire, but technically I'm giving no guarantees. |cffff8000If you want a polished, functioning product, close WoW, download the official QH release from curse.com, and use that.|r
903 Known bugs and issues include:
905 |cff40bbffNo support for "/qh find"|r
907 |cff40bbffNo support for in-party quest synchronization|r
909 These may not be fixed before the official 1.0 release - I'm hoping to get them all finished up in time for 1.1.
911 If you encounter any issue besides the ones listed here, please please please report it, if you're reading this you know how to get in contact with me anyway.
913 Thanks for testing!]], "QuestHelper " .. version_string, 500, 20, 10)
916 if frams == 250 then please_donate_enabled = false end -- TOOK TOO LONG >:(
917 if please_donate_enabled and startup_time and startup_time + 1 < GetTime() then
918 QuestHelper:TextOut(QHText("PLEASE_DONATE"))
919 startup_time = nil
920 please_donate_enabled = false
922 QHUpdateNagTick() -- These probably shouldn't be in OnUpdate. Eventually I'll move them somewhere cleaner.
924 if init_cartographer_later and Cartographer_Waypoints then -- there has to be a better way to do this
925 init_cartographer_later = false
926 if QuestHelper_Pref.cart_wp_new then
927 self:EnableCartographer()
931 if not ontaxi and UnitOnTaxi("player") then
932 self:flightBegan()
933 interruptcount = 0
934 elseif ontaxi and not UnitOnTaxi("player") then
935 self:flightEnded(interruptcount > 1)
937 ontaxi = UnitOnTaxi("player")
939 -- For now I'm ripping out the update_count code
940 --update_count = update_count - 1
941 --if update_count <= 0 then
943 -- Reset the update count for next time around; this will make sure the body executes every time
944 -- when perf_scale_2 >= 1, and down to 1 in 10 iterations when perf_scale_2 < 1, or when hidden.
945 --update_count = update_count + (QuestHelper_Pref.hide and 10 or 1/QuestHelper_Pref.perf_scale_2)
947 --if update_count < 0 then
948 -- Make sure the count doesn't go perpetually negative; don't know what will happen if it underflows.
949 --update_count = 0
950 --end
952 if self.Astrolabe.WorldMapVisible then
953 -- We won't trust that the zone returned by Astrolabe is correct until map_shown_decay is 0.
954 map_shown_decay = 2
955 elseif map_shown_decay > 0 then
956 map_shown_decay = map_shown_decay - 1
957 else
958 --SetMapToCurrentZone() -- not sure why this existed
961 --[[delayed_action = delayed_action - 1
962 if delayed_action <= 0 then
963 delayed_action = 100
964 self:HandlePartyChange()
965 end]]
967 local nc, nz, nx, ny = self.Astrolabe:GetCurrentPlayerPosition()
968 local tc, tx, ty
970 if nc and nc ~= -1 then -- We just want the raw data here, before we've done anything clever.
971 tc, tx, ty = self.Astrolabe:GetAbsoluteContinentPosition(nc, nz, nx, ny)
972 QuestHelper: Assert(tc and tx and ty) -- is it true? nobody knows! :D
975 if nc and nc == self.c and map_shown_decay > 0 and self.z > 0 and self.z ~= nz then
976 -- There's a chance Astrolable will return the wrong zone if you're messing with the world map, if you can
977 -- be seen in that zone but aren't in it.
978 local nnx, nny = self.Astrolabe:TranslateWorldMapPosition(nc, nz, nx, ny, nc, self.z)
979 if nnx > 0 and nny > 0 and nnx < 1 and nny < 1 then
980 nz, nx, ny = self.z, nnx, nny
984 if nc and nc > 0 and nz == 0 and nc == self.c and self.z > 0 then
985 nx, ny = self.Astrolabe:TranslateWorldMapPosition(nc, nz, nx, ny, nc, self.z)
986 if nx and ny --[[and nx > -0.1 and ny > -0.1 and nx < 1.1 and ny < 1.1]] then -- removing the conditional because I think I can use the data even when it's a little wonky
987 nz = self.z
988 else
989 nc, nz, nx, ny = nil, nil, nil, nil
993 if nc and nz > 0 then
994 self.c, self.z, self.x, self.y = nc, nz, nx, ny
995 local upd_zone = false
996 if self.i ~= QuestHelper_IndexLookup[nc][nz] then upd_zone = true end
997 self.i = QuestHelper_IndexLookup[nc][nz]
998 if upd_zone then QH_Route_Filter_Rescan("filter_zone") end
1001 if nc and nz and nx and ny and tc and tx and ty then
1002 self.collect_rc, self.collect_rz, self.collect_rx, self.collect_ry = nc, nz, nx, ny
1003 self.collect_ac, self.collect_ax, self.collect_ay = tc, tx, ty
1004 self.collect_delayed = false
1006 local ibi = self.InBrokenInstance
1007 if nc < -77 then self.InBrokenInstance = true else self.InBrokenInstance = false end
1009 if ibi and not self.InBrokenInstance then self.minimap_marker:OnUpdate(0) end -- poke
1010 else
1011 self.collect_delayed = true
1012 self.InBrokenInstance = true
1015 if not UnitOnTaxi("player") and not UnitIsDeadOrGhost("player") then
1016 QuestHelper.routing_ac, QuestHelper.routing_ax, QuestHelper.routing_ay, QuestHelper.routing_c, QuestHelper.routing_z = QuestHelper.collect_ac, QuestHelper.collect_ax, QuestHelper.collect_ay, QuestHelper.c, QuestHelper.z
1019 QH_Timeslice_Toggle("routing", not not self.c)
1021 self:PumpCommMessages()
1022 --end
1023 end)
1025 -- Some or all of these may be nil. c,x,y should be enough for a location - c is the pure continent (currently either 0 or 3 for Azeroth or Outland, or -77 for the DK starting zone) and x,y are the coordinates within that continent.
1026 -- rc and rz are the continent and zone that Questhelper thinks it's within. For various reasons, this isn't perfect. TODO: Base it off the map zone name identifiers instead of the map itself?
1027 function QuestHelper:Location_RawRetrieve()
1028 return self.collect_delayed, self.collect_rc, self.collect_rz, self.collect_rx, self.collect_ry
1030 function QuestHelper:Location_AbsoluteRetrieve()
1031 return self.collect_delayed, self.collect_ac, self.collect_ax, self.collect_ay
1034 --QH_Hook(QuestHelper, "OnEvent", QuestHelper.OnEvent)