TourGuide
[WoW-TourGuide.git] / TourGuide.lua
blobe0106eb5c314770340d2227646272641398e9138
2 local OptionHouse = LibStub("OptionHouse-1.1")
5 local myfaction = UnitFactionGroup("player")
7 TourGuide = DongleStub("Dongle-1.0"):New("TourGuide")
8 if tekDebug then TourGuide:EnableDebug(10, tekDebug:GetFrame("TourGuide")) end
9 TourGuide.guides = {}
10 TourGuide.guidelist = {}
11 TourGuide.nextzones = {}
14 TourGuide.icons = setmetatable({
15 ACCEPT = "Interface\\GossipFrame\\AvailableQuestIcon",
16 COMPLETE = "Interface\\Icons\\Ability_DualWield",
17 TURNIN = "Interface\\GossipFrame\\ActiveQuestIcon",
18 RUN = "Interface\\Icons\\Ability_Tracking",
19 MAP = "Interface\\Icons\\Ability_Spy",
20 FLY = "Interface\\Icons\\Ability_Druid_FlightForm",
21 TRAIN = "Interface\\GossipFrame\\trainerGossipIcon",
22 SETHEARTH = "Interface\\AddOns\\TourGuide\\resting.tga",
23 HEARTH = "Interface\\Icons\\INV_Misc_Rune_01",
24 NOTE = "Interface\\Icons\\INV_Misc_Note_01",
25 GRIND = "Interface\\Icons\\INV_Stone_GrindingStone_05",
26 USE = "Interface\\Icons\\INV_Misc_Bag_08",
27 BUY = "Interface\\Icons\\INV_Misc_Coin_01",
28 BOAT = "Interface\\Icons\\Spell_Frost_SummonWaterElemental",
29 GETFLIGHTPOINT = "Interface\\Icons\\Ability_Hunter_EagleEye",
30 }, {__index = function() return "Interface\\Icons\\INV_Misc_QuestionMark" end})
33 local actiontypes = {
34 A = "ACCEPT",
35 C = "COMPLETE",
36 T = "TURNIN",
37 R = "RUN",
38 t = "TRAIN",
39 H = "HEARTH",
40 h = "SETHEARTH",
41 G = "GRIND",
42 F = "FLY",
43 f = "GETFLIGHTPOINT",
44 N = "NOTE",
45 B = "BUY",
46 b = "BOAT",
47 U = "USE",
51 function TourGuide:Initialize()
52 self.db = self:InitializeDB("TourGuideAlphaDB", {
53 char = {
54 turnedin = {},
55 cachedturnins = {},
58 self.turnedin = self.db.char.turnedin
59 self.cachedturnins = self.db.char.cachedturnins
61 self.db.char.currentguide = self.db.char.currentguide or self.guidelist[1]
62 self:LoadGuide(self.db.char.currentguide)
63 self:PositionStatusFrame()
64 end
67 function TourGuide:Enable()
68 local _, title = GetAddOnInfo("TourGuide")
69 local author, version = GetAddOnMetadata("TourGuide", "Author"), GetAddOnMetadata("TourGuide", "Version")
70 local oh = OptionHouse:RegisterAddOn("Tour Guide", title, author, version)
71 oh:RegisterCategory("Guides", TourGuide, "CreateGuidesPanel")
72 oh:RegisterCategory("Objectives", TourGuide, "CreateObjectivePanel")
74 for _,event in pairs(self.TrackEvents) do self:RegisterEvent(event) end
75 self:RegisterEvent("QUEST_COMPLETE", "UpdateStatusFrame")
76 self:RegisterEvent("QUEST_DETAIL", "UpdateStatusFrame")
77 self.TrackEvents = nil
78 self:UpdateStatusFrame()
79 end
82 function TourGuide:RegisterGuide(name, nextzone, faction, sequencefunc)
83 if faction ~= myfaction then return end
84 self.guides[name] = sequencefunc
85 self.nextzones[name] = nextzone
86 table.insert(self.guidelist, name)
87 end
90 function TourGuide:LoadNextGuide()
91 local name = self.nextzones[self.db.char.currentguide]
92 if not name then return end
94 for i,quest in ipairs(self.quests) do self.turnedin[quest] = nil end -- Clean out old completed objectives, to avoid conflicts
96 self:LoadGuide(name)
97 self:UpdateGuidesPanel()
98 return true
99 end
102 function TourGuide:GetQuestLogIndexByName(name)
103 name = name or self.quests[self.current]
104 name = name:gsub("%s%(Part %d+%)", "")
105 for i=1,GetNumQuestLogEntries() do
106 if GetQuestLogTitle(i) == name then return i end
110 function TourGuide:GetQuestDetails(name)
111 if not name then return end
113 local i = self:GetQuestLogIndexByName(name)
114 local complete = i and select(7, GetQuestLogTitle(i)) == 1
115 return i, complete
119 function TourGuide:FindBagSlot(itemid)
120 for bag=0,4 do
121 for slot=1,GetContainerNumSlots(bag) do
122 local item = GetContainerItemLink(bag, slot)
123 if item and string.find(item, "item:"..itemid) then return bag, slot end
129 function TourGuide:GetObjectiveInfo(i)
130 i = i or self.current
131 if not self.actions[i] then return end
133 return self.actions[i], self.quests[i]:gsub("@.*@", ""), self.quests[i] -- Action, display name, full name
137 function TourGuide:GetObjectiveStatus(i)
138 i = i or self.current
139 if not self.actions[i] then return end
141 return self.turnedin[self.quests[i]], self:GetQuestDetails(self.quests[i]) -- turnedin, logi, complete
145 function TourGuide:GetObjectiveTag(tag, i)
146 self:Debug(11, "GetObjectiveTag", tag, i)
147 i = i or self.current
148 if tag == "LV" then return self.levels[i]
149 elseif tag == "N" then return self.notes[i]
150 elseif tag == "U" then return self.useitems[i]
151 elseif tag == "O" then return self.optional[i]
152 elseif tag == "Z" then return self.zones[i]
153 elseif tag == "L" then
154 local _, _, lootitem, lootqty = string.find(self.lootitems[i] or "", "(%d+)%s?(%d*)")
155 lootqty = tonumber(lootqty) or 1
157 return lootitem, lootqty
162 local myclass = UnitClass("player")
163 local titlematches = {"For", "A", "The", "Or", "In", "Then", "From", "To"}
164 local function ParseQuests(...)
165 local accepts, turnins, completes = {}, {}, {}
166 local uniqueid = 1
167 local actions, notes, quests, useitems, optionals, lootitems, levels, zones = {}, {}, {}, {}, {}, {}, {}, {}
168 local i, haserrors = 1, false
170 for j=1,select("#", ...) do
171 local text = select(j, ...)
172 local _, _, class = text:find("|C|([^|]+)|")
174 if text ~= "" and (not class or class == myclass) then
175 local _, _, action, quest = text:find("^(%a) ([^|]*)")
176 assert(actiontypes[action], "Unknown action: "..text)
177 quest = quest:trim()
178 if not (action == "A" or action =="C" or action =="T") then
179 quest = quest.."@"..uniqueid.."@"
180 uniqueid = uniqueid + 1
183 actions[i], quests[i] = actiontypes[action], quest
185 local _, _, note = string.find(text, "|N|([^|]+)|")
186 if note then notes[i] = note end
188 local _, _, level = string.find(text, "|LV|(%d+)|")
189 if level then levels[i] = tonumber(level) end
191 local _, _, useitem = string.find(text, "|U|(%d+)|")
192 if useitem then useitems[i] = useitem end
194 local _, _, zone = string.find(text, "|Z|([^|]+)|")
195 if zone then zones[i] = zone end
197 local _, _, lootitem = string.find(text, "|L|(%d+%s?%d*)|")
198 if lootitem then lootitems[i] = lootitem end
200 if string.find(text, "|O|") then optionals[i] = true end
202 i = i + 1
204 -- Debuggery
205 if not string.find(text, "|NODEBUG|") then
206 if action == "A" then accepts[quest] = true
207 elseif action == "T" then turnins[quest] = true
208 elseif action == "C" then completes[quest] = true end
210 if action == "A" or action == "C" or action == "T" then
211 -- Catch bad Title Case
212 for _,word in pairs(titlematches) do
213 if quest:find("[^:]%s"..word.."%s") or quest:find("[^:]%s"..word.."$") or quest:find("[^:]%s"..word.."@") then
214 TourGuide:DebugF(1, "%s %s -- Contains bad title case", action, quest)
215 haserrors = true
219 if quest:find("[“”’]") then
220 TourGuide:DebugF(1, "%s %s -- Contains bad char", action, quest)
221 haserrors = true
225 local _, _, comment = string.find(text, "(|[NLUC]V?|[^|]+)$") or string.find(text, "(|[NLUC]V?|[^|]+) |[NLUC]V?|")
226 if comment then
227 TourGuide:Debug(1, "Unclosed comment: ".. comment)
228 haserrors = true
234 -- More debug
235 for quest in pairs(accepts) do if not turnins[quest] then TourGuide:DebugF(1, "Quest has no 'turnin' objective: %s", quest) end end
236 for quest in pairs(turnins) do if not accepts[quest] then TourGuide:DebugF(1, "Quest has no 'accept' objective: %s", quest) end end
237 for quest in pairs(completes) do if not accepts[quest] and not turnins[quest] then TourGuide:DebugF(1, "Quest has no 'accept' and 'turnin' objectives: %s", quest) end end
239 if haserrors and TourGuide:IsDebugEnabled() then TourGuide:Print("This guide contains errors") end
241 return actions, notes, quests, useitems, optionals, lootitems, levels, zones
245 function TourGuide:LoadGuide(name)
246 if not name then return end
248 self.db.char.currentguide = name
249 if not self.guides[name] then self.db.char.currentguide = self.guidelist[1] end
250 self:DebugF(1, "Loading guide: %s", name)
251 self.guidechanged = true
252 local _, _, zonename = name:find("^(.*) %(.*%)$")
253 self.zonename = zonename
254 self.actions, self.notes, self.quests, self.useitems, self.optional, self.lootitems, self.levels, self.zones = ParseQuests(string.split("\n", self.guides[self.db.char.currentguide]()))
258 function TourGuide:SetTurnedIn(i, value, noupdate)
259 if not i then
260 i = self.current
261 value = true
264 if value then value = true else value = nil end -- Cleanup to minimize savedvar data
266 self.turnedin[self.quests[i]] = value
267 self:DebugF(1, "Set turned in %q = %s", self.quests[i], tostring(value))
268 if not noupdate then self:UpdateStatusFrame()
269 else self.updatedelay = true end
273 function TourGuide:CompleteQuest(name, noupdate)
274 local i = self.current
275 local action, quest
276 repeat
277 action, quest = self:GetObjectiveInfo(i)
278 if action == "TURNIN" and not self:GetObjectiveStatus(i) and name == quest:gsub("%s%(Part %d+%)", "") then
279 self:DebugF(1, "Saving quest turnin %q", quest)
280 return self:SetTurnedIn(i, true, noupdate)
282 i = i + 1
283 until not action
284 self:DebugF(1, "Quest %q not found!", name)