Fixed nag command, made nag command only nag for local faction unless "all" appears...
[QuestHelper.git] / main.lua
blobbd3dae43d18cc4ab482073a5dccb91849701d854
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 = {}
12 QuestHelper_Pref =
15 QuestHelper_DefaultPref =
17 filter_level=true,
18 filter_zone=false,
19 filter_done=false,
20 share = true,
21 scale = 1,
22 solo = false,
23 comm = false,
24 show_ants = true,
25 level = 2,
26 hide = false,
27 cart_wp = true,
28 locale = GetLocale() -- This variable is used for display purposes, and has nothing to do with the collected data.
31 QuestHelper_FlightInstructors = {}
32 QuestHelper_FlightLinks = {}
33 QuestHelper_FlightRoutes = {}
34 QuestHelper_KnownFlightRoutes = {}
36 QuestHelper.tooltip = CreateFrame("GameTooltip", "QuestHelperTooltip", nil, "GameTooltipTemplate")
37 QuestHelper.objective_objects = {}
38 QuestHelper.user_objectives = {}
39 QuestHelper.quest_objects = {}
40 QuestHelper.player_level = 1
41 QuestHelper.locale = QuestHelper_Locale
42 QuestHelper.faction = (UnitFactionGroup("player") == FACTION_ALLIANCE and 1) or
43 (UnitFactionGroup("player") == FACTION_HORDE and 2)
44 QuestHelper.route = {}
45 QuestHelper.to_add = {}
46 QuestHelper.to_remove = {}
47 QuestHelper.quest_log = {}
48 QuestHelper.pos = {nil, {}, 0, 0, 1, "You are here.", 0}
49 QuestHelper.sharing = false -- Will be set to true when sharing with at least one user.
51 function QuestHelper.tooltip:GetPrevLines() -- Just a helper to make life easier.
52 local last = self:NumLines()
53 local name = self:GetName()
54 return _G[name.."TextLeft"..last], _G[name.."TextRight"..last]
55 end
57 function QuestHelper:SetTargetLocation(i, x, y, toffset)
58 -- Informs QuestHelper that you're going to be at some location in toffset seconds.
59 local c, z = unpack(QuestHelper_ZoneLookup[i])
61 self.target = self:CreateTable()
62 self.target[2] = self:CreateTable()
64 self.target_time = time()+(toffset or 0)
66 x, y = self.Astrolabe:TranslateWorldMapPosition(c, z, x, y, c, 0)
67 self.target[1] = self.zone_nodes[i]
68 self.target[3] = x * self.continent_scales_x[c]
69 self.target[4] = y * self.continent_scales_y[c]
71 for i, n in ipairs(self.target[1]) do
72 local a, b = n.x-self.target[3], n.y-self.target[4]
73 self.target[2][i] = math.sqrt(a*a+b*b)
74 end
75 end
77 function QuestHelper:UnsetTargetLocation()
78 -- Unsets the target set above.
79 if self.target then
80 self:ReleaseTable(self.target[2])
81 self:ReleaseTable(self.target)
82 self.target = nil
83 self.target_time = nil
84 end
85 end
87 function QuestHelper:OnEvent(event)
88 if event == "VARIABLES_LOADED" then
89 QHFormatSetLocale(QuestHelper_Pref.locale or GetLocale())
90 if not QuestHelper_UID then
91 QuestHelper_UID = self:CreateUID()
92 end
93 QuestHelper_SaveDate = time()
95 QuestHelper_BuildZoneLookup()
97 if QuestHelper_Locale ~= GetLocale() then
98 self:TextOut(QHText("LOCALE_ERROR"))
99 return
102 self.Astrolabe = DongleStub("Astrolabe-0.4")
104 if not self:ZoneSanity() then
105 self:TextOut(QHText("ZONE_LAYOUT_ERROR"))
106 message("QuestHelper: "..QHText("ZONE_LAYOUT_ERROR"))
107 return
110 QuestHelper_UpgradeDatabase(_G)
111 QuestHelper_UpgradeComplete()
113 if QuestHelper_SaveVersion ~= 7 then
114 self:TextOut(QHText("DOWNGRADE_ERROR"))
115 return
118 self:ResetPathing()
120 self:UnregisterEvent("VARIABLES_LOADED")
121 self:RegisterEvent("PLAYER_TARGET_CHANGED")
122 self:RegisterEvent("LOOT_OPENED")
123 self:RegisterEvent("QUEST_COMPLETE")
124 self:RegisterEvent("QUEST_LOG_UPDATE")
125 self:RegisterEvent("QUEST_PROGRESS")
126 self:RegisterEvent("MERCHANT_SHOW")
127 self:RegisterEvent("QUEST_DETAIL")
128 self:RegisterEvent("TAXIMAP_OPENED")
129 self:RegisterEvent("PLAYER_CONTROL_GAINED")
130 self:RegisterEvent("PLAYER_CONTROL_LOST")
131 self:RegisterEvent("PLAYER_LEVEL_UP")
132 self:RegisterEvent("PARTY_MEMBERS_CHANGED")
133 self:RegisterEvent("CHAT_MSG_ADDON")
134 self:RegisterEvent("CHAT_MSG_SYSTEM")
136 self:SetScript("OnUpdate", self.OnUpdate)
138 for key, def in pairs(QuestHelper_DefaultPref) do
139 if QuestHelper_Pref[key] == nil then
140 QuestHelper_Pref[key] = def
144 self.player_level = UnitLevel("player")
146 if QuestHelper_Pref.share and not QuestHelper_Pref.solo then
147 self:EnableSharing()
150 if QuestHelper_Pref.hide then
151 self.map_overlay:Hide()
154 self:HandlePartyChange()
155 self:Nag("all")
157 for locale in pairs(QuestHelper_StaticData) do
158 if locale ~= self.locale then
159 -- Will delete references to locales you don't use.
160 QuestHelper_StaticData[locale] = nil
164 local static = QuestHelper_StaticData[self.locale]
166 if static then
167 if static.flight_instructors then for faction in pairs(static.flight_instructors) do
168 if faction ~= self.faction then
169 -- Will delete references to flight instructors that don't belong to your faction.
170 static.flight_instructors[faction] = nil
172 end end
174 if static.quest then for faction in pairs(static.quest) do
175 if faction ~= self.faction then
176 -- Will delete references to quests that don't belong to your faction.
177 static.quest[faction] = nil
179 end end
182 -- Adding QuestHelper_CharVersion, so I know if I've already converted this characters saved data.
183 if not QuestHelper_CharVersion then
184 -- Changing per-character flight routes, now only storing the flight points they have,
185 -- will attempt to guess the routes from this.
186 local routes = {}
188 for i, l in pairs(QuestHelper_KnownFlightRoutes) do
189 for key in pairs(l) do
190 routes[key] = true
194 QuestHelper_KnownFlightRoutes = routes
196 -- Deleting the player's home again.
197 -- But using the new CharVersion variable I'm adding is cleaner that what I was doing, so I'll go with it.
198 QuestHelper_Home = nil
199 QuestHelper_CharVersion = 1
202 if not QuestHelper_Home then
203 -- Not going to bother complaining about the player's home not being set, uncomment this when the home is used in routing.
204 -- self:TextOut(QHText("HOME_NOT_KNOWN"))
207 collectgarbage("collect") -- Free everything we aren't using.
209 if self.debug_objectives then
210 for name, data in pairs(self.debug_objectives) do
211 self:LoadDebugObjective(name, data)
216 if event == "PLAYER_TARGET_CHANGED" then
217 if UnitExists("target") and UnitIsVisible("target") and UnitCreatureType("target") ~= "Critter" and not UnitIsPlayer("target") and not UnitPlayerControlled("target") then
218 local index, x, y = self:UnitPosition("target")
220 if index then -- Might not have a position if inside an instance.
221 local w = 0.1
223 -- Modify the weight based on how far they are from us.
224 -- We don't know the exact location (using our own location), so the farther, the less sure we are that it's correct.
225 if CheckInteractDistance("target", 3) then w = 1
226 elseif CheckInteractDistance("target", 2) then w = 0.89
227 elseif CheckInteractDistance("target", 1) or CheckInteractDistance("target", 4) then w = 0.33 end
229 local monster_objective = self:GetObjective("monster", UnitName("target"))
230 self:AppendObjectivePosition(monster_objective, index, x, y, w)
231 monster_objective.o.faction = UnitFactionGroup("target")
233 local level = UnitLevel("target")
234 if level and level >= 1 then
235 local w = monster_objective.o.levelw or 0
236 monster_objective.o.level = ((monster_objective.o.level or 0)*w+level)/(w+1)
237 monster_objective.o.levelw = w+1
243 if event == "LOOT_OPENED" then
244 local target = UnitName("target")
245 if target and UnitIsDead("target") and UnitCreatureType("target") ~= "Critter" and not UnitIsPlayer("target") and not UnitPlayerControlled("target") then
246 local index, x, y = self:UnitPosition("target")
248 local monster_objective = self:GetObjective("monster", target)
249 monster_objective.o.looted = (monster_objective.o.looted or 0) + 1
251 if index then -- Might not have a position if inside an instance.
252 self:AppendObjectivePosition(monster_objective, index, x, y)
255 for i = 1, GetNumLootItems() do
256 local icon, name, number, rarity = GetLootSlotInfo(i)
257 if name then
258 if number and number >= 1 then
259 self:AppendItemObjectiveDrop(self:GetObjective("item", name), name, target, number)
260 else
261 local total = 0
262 local _, _, amount = string.find(name, "(%d+) "..COPPER)
263 if amount then total = total + amount end
264 _, _, amount = string.find(name, "(%d+) "..SILVER)
265 if amount then total = total + amount * 100 end
266 _, _, amount = string.find(name, "(%d+) "..GOLD)
267 if amount then total = total + amount * 10000 end
269 if total > 0 then
270 self:AppendObjectiveDrop(self:GetObjective("item", "money"), target, total)
275 else
276 local container = nil
278 -- Go through the players inventory and look for a locked item, we're probably looting it.
279 for bag = 0,NUM_BAG_SLOTS do
280 for slot = 1,GetContainerNumSlots(bag) do
281 local link = GetContainerItemLink(bag, slot)
282 if link and select(3, GetContainerItemInfo(bag, slot)) then
283 if container == nil then
284 -- Found a locked item and haven't previously assigned to container, assign its name, or false if we fail to parse it.
285 container = select(3, string.find(link, "|h%[(.+)%]|h|r")) or false
286 else
287 -- Already tried to assign to a container. If there are multiple locked items, we give up.
288 container = false
294 if container then
295 local container_objective = self:GetObjective("item", container)
296 container_objective.o.opened = (container_objective.o.opened or 0) + 1
298 for i = 1, GetNumLootItems() do
299 local icon, name, number, rarity = GetLootSlotInfo(i)
300 if name and number >= 1 then
301 self:AppendItemObjectiveContainer(self:GetObjective("item", name), container, number)
304 else
305 -- No idea where the items came from.
306 local index, x, y = self:PlayerPosition()
308 if index then
309 for i = 1, GetNumLootItems() do
310 local icon, name, number, rarity = GetLootSlotInfo(i)
311 if name and number >= 1 then
312 self:AppendItemObjectivePosition(self:GetObjective("item", name), name, index, x, y)
320 if event == "CHAT_MSG_SYSTEM" then
321 local home_name = self:convertPattern(ERR_DEATHBIND_SUCCESS_S)(arg1)
322 if home_name then
323 if self.i then
324 self:TextOut(QHText("HOME_CHANGED"))
325 self:TextOut(QHText("WILL_RESET_PATH"))
327 local home = QuestHelper_Home
328 if not home then
329 home = {}
330 QuestHelper_Home = home
333 home[1], home[2], home[3], home[4] = self.i, self.x, self.y, home_name
334 self.defered_graph_reset = true
339 if event == "CHAT_MSG_ADDON" then
340 if arg1 == "QHpr" and (arg3 == "PARTY" or arg3 == "WHISPER") and arg4 ~= UnitName("player") then
341 self:HandleRemoteData(arg2, arg4)
345 if event == "PARTY_MEMBERS_CHANGED" then
346 self:HandlePartyChange()
349 if event == "QUEST_LOG_UPDATE" or
350 event == "PLAYER_LEVEL_UP" or
351 event == "PARTY_MEMBERS_CHANGED" then
352 self.defered_quest_scan = true
355 if event == "QUEST_DETAIL" then
356 if not self.quest_giver then self.quest_giver = {} end
357 local npc = UnitName("npc")
358 if npc then
359 -- Some NPCs aren't actually creatures, and so their positions might not be marked by PLAYER_TARGET_CHANGED.
360 local index, x, y = self:UnitPosition("npc")
362 if index then -- Might not have a position if inside an instance.
363 local npc_objective = self:GetObjective("monster", npc)
364 self:AppendObjectivePosition(npc_objective, index, x, y)
365 self.quest_giver[GetTitleText()] = npc
370 if event == "QUEST_COMPLETE" or event == "QUEST_PROGRESS" then
371 local quest = GetTitleText()
372 if quest then
373 local level, hash = self:GetQuestLevel(quest)
374 if not level or level < 1 then
375 --self:TextOut("Don't know quest level for ".. quest.."!")
376 return
378 local q = self:GetQuest(quest, level, hash)
380 if q.need_hash then
381 q.o.hash = hash
384 local unit = UnitName("npc")
385 if unit then
386 q.o.finish = unit
387 q.o.pos = nil
389 -- Some NPCs aren't actually creatures, and so their positions might not be marked by PLAYER_TARGET_CHANGED.
390 local index, x, y = self:UnitPosition("npc")
391 if index then -- Might not have a position if inside an instance.
392 local npc_objective = self:GetObjective("monster", unit)
393 self:AppendObjectivePosition(npc_objective, index, x, y)
395 elseif not q.o.finish then
396 local index, x, y = self:PlayerPosition()
397 if index then -- Might not have a position if inside an instance.
398 self:AppendObjectivePosition(q, index, x, y)
404 if event == "MERCHANT_SHOW" then
405 local npc_name = UnitName("npc")
406 if npc_name then
407 local npc_objective = self:GetObjective("monster", npc_name)
408 local index = 1
409 while true do
410 local item_name = GetMerchantItemInfo(index)
411 if item_name then
412 index = index + 1
413 local item_objective = self:GetObjective("item", item_name)
414 if not item_objective.o.vendor then
415 item_objective.o.vendor = {npc_name}
416 else
417 local known = false
418 for i, vendor in ipairs(item_objective.o.vendor) do
419 if npc_name == vendor then
420 known = true
421 break
424 if not known then
425 table.insert(item_objective.o.vendor, npc_name)
428 else
429 break
435 if event == "TAXIMAP_OPENED" then
436 self:taxiMapOpened()
439 if event == "PLAYER_CONTROL_GAINED" then
440 self:flightEnded()
443 if event == "PLAYER_CONTROL_LOST" then
444 self:flightBegan()
448 local map_shown_decay = 0
449 local delayed_action = 100
451 function QuestHelper:OnUpdate()
452 if self.Astrolabe.WorldMapVisible then
453 -- We won't trust that the zone returned by Astrolabe is correct until map_shown_decay is 0.
454 map_shown_decay = 2
455 elseif map_shown_decay > 0 then
456 map_shown_decay = map_shown_decay - 1
457 else
458 SetMapToCurrentZone()
461 delayed_action = delayed_action - 1
462 if delayed_action <= 0 then
463 delayed_action = 100
464 self:HandlePartyChange()
468 local nc, nz, nx, ny = self.Astrolabe:GetCurrentPlayerPosition()
470 if nc and nc == self.c and map_shown_decay > 0 and self.z > 0 and self.z ~= nz then
471 -- There's a chance Astrolable will return the wrong zone if you're messing with the world map, if you can
472 -- be seen in that zone but aren't in it.
473 local nnx, nny = self.Astrolabe:TranslateWorldMapPosition(nc, nz, nx, ny, nc, self.z)
474 if nnx > 0 and nny > 0 and nnx < 1 and nny < 1 then
475 nz, nx, ny = self.z, nnx, nny
479 if nc and nc > 0 and nz == 0 and nc == self.c and self.z > 0 then
480 nx, ny = self.Astrolabe:TranslateWorldMapPosition(nc, nz, nx, ny, nc, self.z)
481 if nx and ny and nx > -0.1 and ny > -0.1 and nx < 1.1 and ny < 1.1 then
482 nz = self.z
483 else
484 nc, nz, nx, ny = nil, nil, nil, nil
488 if nc and nz > 0 then
489 if nc > 0 and nz > 0 then
490 self.c, self.z, self.x, self.y = nc or self.c, nz or self.z, nx or self.x, ny or self.y
491 self.i = QuestHelper_IndexLookup[self.c][self.z]
493 if not self.target then
494 self.pos[3], self.pos[4] = self.Astrolabe:TranslateWorldMapPosition(self.c, self.z, self.x, self.y, self.c, 0)
495 assert(self.pos[3])
496 assert(self.pos[4])
497 self.pos[1] = self.zone_nodes[self.i]
498 self.pos[3] = self.pos[3] * self.continent_scales_x[self.c]
499 self.pos[4] = self.pos[4] * self.continent_scales_y[self.c]
500 for i, n in ipairs(self.pos[1]) do
501 if not n.x then
502 for i, j in pairs(n) do self:TextOut("[%q]=%s %s", i, type(j), tostring(j) or "???") end
503 assert(false)
505 local a, b = n.x-self.pos[3], n.y-self.pos[4]
506 self.pos[2][i] = math.sqrt(a*a+b*b)
512 if self.target then
513 self.pos[1], self.pos[3], self.pos[4] = self.target[1], self.target[3], self.target[4]
514 local extra_time = math.max(0, self.target_time-time())
515 for i in ipairs(self.pos[1]) do
516 self.pos[2][i] = self.target[2][i]+extra_time
520 if self.pos[1] then
521 if self.defered_quest_scan then
522 self.defered_quest_scan = false
523 self:ScanQuestLog()
526 if not self.hide and coroutine.status(self.update_route) ~= "dead" then
527 local state, err = coroutine.resume(self.update_route, self)
528 if not state then self:TextOut("|cffff0000The routing co-routine just exploded|r: |cffffff77"..err.."|r") end
532 local level = UnitLevel("player")
533 if level >= 58 and self.player_level < 58 then
534 self.defered_graph_reset = true
536 self.player_level = level
538 self:PumpCommMessages()
541 QuestHelper:RegisterEvent("VARIABLES_LOADED")
542 QuestHelper:SetScript("OnEvent", QuestHelper.OnEvent)