Merge branch 'master' of git://cams.pavlovian.net/questhelper
[QuestHelper.git] / director_achievement.lua
blob391c9635a864bcd4d5153f327a8c43a920b0ec05
1 QuestHelper_File["director_achievement.lua"] = "Development Version"
2 QuestHelper_Loadtime["director_achievement.lua"] = GetTime()
4 local debug_output = false
5 if QuestHelper_File["director_achievement.lua"] == "Development Version" then debug_output = true end
7 local achieveable = {}
9 local function IsDoable(id)
10 if achieveable[id] == nil then
11 -- First we just see if we have a DB entry for it
12 -- This can be made *much* more efficient.
13 if not DB_Ready() then
14 print("DB not yet ready, please wait")
15 return false
16 end
18 if DB_HasItem("achievement", id) then
19 --print(id, "achieveable via db")
20 achieveable[id] = true
21 return true
22 end
24 local crit = GetAchievementNumCriteria(id)
26 -- Whenever I write "crit" as a variable name it always slightly worries me.
28 -- Y'see, several years ago I competed in a programming competition called Topcoder. At the end of a big tournament, if you did well, they flew you to a casino or a hotel and you competed against a bunch of other geeks, with your code posted on monitors. This was uncommonly boring unless you were a geek, but luckily, we were, so it was actually kind of exciting.
29 -- So I'm up there competing, and I need a count, so I make a variable called "cont". "count", y'see, is a function, so I can't use that. But then I need another one. I can't go with "cout" because that's actually a global variable in C++, which is what I'm using. And, well, I want to keep the first and last letters preserved, because that way it reminds me it's a count.
31 -- So I start typing the first thing that comes to mind that fulfills all the requirements.
33 -- Luckily, I stop myself in time, and write "cnt" instead.
35 -- Once or twice in QuestHelper I've needed a few variables about criteria. And there's . . . something . . . which is only one letter off from "crit", and is something I should probably not be typing in publicly accessible sourcecode.
37 -- So now you know. Back to the code.
39 -- (although let's be honest with the amount of profanity scattered throughout this codebase I'm not quite sure why I care buuuuuuuuuut here we are anyway)
41 if crit > 0 then
42 for i = 1, crit do
43 local _, typ, _, _, _, _, _, asset, _, cid = GetAchievementCriteriaInfo(id, i)
44 if typ == 0 then
45 -- Monster kill. We're good! We can do these.
46 elseif typ == 8 then
47 -- Achievement chain
48 if not IsDoable(asset) then
49 achieveable[id] = false
50 break
51 end
52 else
53 achieveable[id] = false
54 break
55 end
56 end
58 if achieveable[id] == nil then
59 --print(id, "achieveable via occlusion")
60 achieveable[id] = true
61 end
62 else
63 --print(id, "not achieveable due to wizard casting what the fuck")
64 achieveable[id] = false
65 end
66 end
68 return achieveable[id]
69 end
71 local function GetListOfAchievements(category)
72 local ct = GetCategoryNumAchievements(category)
74 local available_achieves = {}
76 for i = 1, ct do
77 local id, _, _, complete = GetAchievementInfo(category, i)
78 if not complete and IsDoable(id) then
79 table.insert(available_achieves, i)
80 end
81 end
83 return available_achieves
84 end
86 local function FilterFunction(category)
87 local aa = GetListOfAchievements(category)
89 return #aa, 0, 0
90 end
92 local function SetQHVis(button, ach, _, _, complete)
93 button.qh_checkbox:Hide()
94 if complete or not IsDoable(ach) then
95 button.qh_checkbox:Hide()
96 else
97 button.qh_checkbox:Show()
98 end
99 end
101 local ABDA_permit
102 local ABDA
103 local function ABDA_Replacement(button, category, achievement, selectionID)
104 -- hee hee hee
105 -- i am sneaky like fish
106 -- *sneaky* fish
107 -- ^__^
109 if ABDA_permit and ACHIEVEMENTUI_SELECTEDFILTER == FilterFunction then
110 local aa = GetListOfAchievements(category)
111 local ach = aa[achievement]
112 ABDA(button, category, ach, selectionID)
113 SetQHVis(button, GetAchievementInfo(category, ach))
114 else
115 ABDA(button, category, achievement, selectionID)
116 SetQHVis(button, GetAchievementInfo(category, achievement))
120 local AFAU
121 local function AFAU_Replacement(...)
122 ABDA_permit = true
123 AFAU(...)
124 ABDA_permit = false
127 local TrackedAchievements = {}
128 local MetaAchievements = {}
129 local Update_Objectives
131 local function MarkAchieveable(id, setto)
132 TrackedAchievements[id] = setto
134 local crit = GetAchievementNumCriteria(id)
135 for i = 1, crit do
136 local _, typ, _, _, _, _, _, asset, _, cid = GetAchievementCriteriaInfo(id, i)
137 if typ == 8 then
138 MetaAchievements[id] = true
139 MarkAchieveable(asset, setto)
144 local check_onshow
146 function QH_COC_GIX(id)
147 MarkAchieveable(id, true)
148 Update_Objectives()
151 local function check_onclick(self)
152 if self:GetChecked() then
153 MarkAchieveable(self:GetParent().id, true)
154 else
155 MarkAchieveable(self:GetParent().id, nil)
157 Update_Objectives()
159 for i = 1, #AchievementFrameAchievements.buttons do
160 check_onshow(AchievementFrameAchievements.buttons[i].qh_checkbox)
164 local function check_onenter(self)
165 GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
166 GameTooltip:SetText(QHText("ACHIEVEMENT_CHECKBOX"))
168 local function check_onleave(self)
169 GameTooltip:Hide()
171 function check_onshow(self)
172 self:SetChecked(TrackedAchievements[self:GetParent().id])
175 local AAFOC = AchievementAlertFrame_OnClick
176 function AAF_onclick(self)
177 if not self.id then return end
179 local _, _, _, complete = GetAchievementInfo(self.id);
180 if complete and ACHIEVEMENTUI_SELECTEDFILTER == FilterFunction then -- yipyipyipyipyipyipyip
181 AchievementFrame_SetFilter(ACHIEVEMENT_FILTER_ALL) -- uh-huh, uh-huh
184 AAFOC(self)
186 AchievementAlertFrame_OnClick = AAF_onclick
188 QH_Event("ADDON_LOADED", function (addonid)
189 if addonid == "Blizzard_AchievementUI" then
190 -- yyyyyyoink
191 table.insert(AchievementFrameFilters, {text="QuestHelpable", func=FilterFunction})
193 ABDA = AchievementButton_DisplayAchievement
194 AchievementButton_DisplayAchievement = ABDA_Replacement
196 AFAU = AchievementFrameAchievements_Update
197 AchievementFrameAchievements_Update = AFAU_Replacement
198 AchievementFrameAchievementsContainer.update = AFAU_Replacement
199 ACHIEVEMENT_FUNCTIONS.updateFunc = AFAU_Replacement
201 for i = 1, #AchievementFrameAchievements.buttons do
202 local framix = CreateFrame("CheckButton", "qh_arglbargl_" .. i, AchievementFrameAchievements.buttons[i], "AchievementCheckButtonTemplate")
203 framix:SetPoint("BOTTOMRIGHT", AchievementFrameAchievements.buttons[i], "BOTTOMRIGHT", -22, 7.5)
204 framix:SetScript("OnEnter", check_onenter)
205 framix:SetScript("OnLeave", check_onleave)
207 framix:SetScript("OnShow", check_onshow)
208 framix:SetScript("OnClick", check_onclick)
210 _G["qh_arglbargl_" .. i .. "Text"]:Hide() -- no
212 local sigil = framix:CreateTexture("BACKGROUND")
213 sigil:SetHeight(24)
214 sigil:SetWidth(24)
215 sigil:SetTexture("Interface\\AddOns\\QuestHelper\\sigil")
216 sigil:SetPoint("RIGHT", framix, "LEFT", -1, 0)
217 sigil:SetVertexColor(0.6, 0.6, 0.6)
219 AchievementFrameAchievements.buttons[i].qh_checkbox = framix
222 end)
225 local function horribledupe(from)
226 if not from then return nil end
228 local rv = {}
229 for k, v in pairs(from) do
230 if k == "__owner" then
231 elseif type(v) == "table" then
232 rv[k] = horribledupe(v)
233 else
234 rv[k] = v
237 return rv
240 local achievement_list = setmetatable({}, {__mode="k"})
241 function GetAchievementMetaObjective(achievement)
242 if achievement_list[achievement] then return achievement_list[achievement] end
244 local db = DB_GetItem("achievement", achievement, true)
246 local ite = {}
247 ite.desc = select(2, GetAchievementInfo(achievement))
248 ite.tracker_desc = ite.desc
249 ite.tracker_split = true
251 local itlist = {}
252 if db and db.achieved then
253 table.insert(itlist, {cid = "achieved", chunk = db.achieved})
254 else
255 local crit = GetAchievementNumCriteria(achievement)
257 for i = 1, crit do
258 local name, typ, _, _, _, _, _, asset, _, cid = GetAchievementCriteriaInfo(achievement, i)
260 assert(cid)
262 local chunk
263 local split
264 if typ == 0 then
265 chunk = DB_GetItem("monster", asset)
266 split = true
267 else
268 assert(db)
269 chunk = db[cid]
272 if split then
273 local soldupe = horribledupe(chunk.solid)
274 for i = 1, #chunk.loc do
275 table.insert(itlist, {cid = cid, solid = soldupe, loc = chunk.loc[i], name = name})
277 else
278 table.insert(itlist, {cid = cid, chunk = chunk, name = name})
283 for _, data in pairs(itlist) do
284 local ttx = {}
286 if data.chunk then
287 ttx.solid = horribledupe(data.chunk.solid)
288 if data.chunk.loc then for _, v in ipairs(data.chunk.loc) do
289 table.insert(ttx, {loc = {x = v.x, y = v.y, c = QuestHelper_ParentLookup[v.p], p = v.p}})
290 end end
293 if data.loc then
294 ttx.solid = data.solid
295 table.insert(ttx, {loc = {x = data.loc.x, y = data.loc.y, c = QuestHelper_ParentLookup[data.loc.p], p = data.loc.p}})
298 if #ttx == 0 then
299 table.insert(ttx, {loc = {x = 5000, y = 5000, c = 0, p = 2}, icon_id = 7, type_quest_unknown = true}) -- this is Ashenvale, for no particularly good reason
300 ttx.type_achievement_unknown = true
303 for _, v in ipairs(ttx) do
304 v.map_desc = {ite.desc, data.name}
305 v.tracker_desc = data.name or ite.desc
306 v.tracker_hide_dupes = true
307 v.hidden_desc = data.name and string.format("%s: %s", ite.desc, data.name) or ite.desc
308 v.arrow_desc = v.hidden_desc
309 if not data.name then v.tracker_hidden = true end
310 v.cluster = ttx
311 v.why = ite
312 v.icon_id = 17
315 if not ite[data.cid] then
316 ite[data.cid] = {}
319 table.insert(ite[data.cid], ttx)
322 achievement_list[achievement] = ite
324 return achievement_list[achievement]
327 local function achievement_is_unified(ach)
328 return GetAchievementMetaObjective(ach).achieved
332 local current_aches = {}
333 local next_aches = {}
335 local ach_locational = {}
337 local function AchUpdateStart()
338 next_aches = {}
340 local function AchUpdateAdd(ach, crit)
341 if not next_aches[ach] then next_aches[ach] = {} end
342 next_aches[ach][crit] = true
344 local function AchUpdateEnd()
345 --print("aue")
346 for k, v in pairs(current_aches) do
347 for c in pairs(v) do
348 if not next_aches[k] or not next_aches[k][c] then
349 local meta = GetAchievementMetaObjective(k)
351 for i = 1, #meta[c] do
352 QH_Route_ClusterRemove(meta[c][i])
354 for _, nod in ipairs(meta[c][i]) do
355 ach_locational[nod.loc] = nil
362 for k, v in pairs(next_aches) do
363 for c in pairs(v) do
364 local kacey = nil
365 if not current_aches[k] or not current_aches[k][c] then
366 local meta = GetAchievementMetaObjective(k)
368 for i = 1, #meta[c] do
369 QH_Route_ClusterAdd(meta[c][i])
371 for _, nod in ipairs(meta[c][i]) do
372 if not ach_locational[nod.loc] then
373 if not kacey then
374 kacey = QuestHelper:CreateTable("ach_locational")
375 kacey.k = k
376 kacey.c = c
378 --print(nod.loc.p, nod.loc.x, nod.loc.y)
379 ach_locational[nod.loc] = kacey
387 current_aches = next_aches -- yaaaaaaaay
390 function qh_critupd()
391 --print("critup")
393 local c, z, x, y = QuestHelper.routing_c, QuestHelper.routing_z, QuestHelper.routing_ax, QuestHelper.routing_ay
394 --print(c, z, x, y)
395 if not c or not z or not x or not y then return end
397 local p = QuestHelper_IndexLookup[c][z]
398 --print(p)
399 if not p then return end
401 local closest_dist = 1000000000000
402 local closest_item = nil
403 for k, v in pairs(ach_locational) do
404 if k.p == p then
405 local dx, dy = x - k.x, y - k.y
406 dx, dy = dx * dx, dy * dy
407 local dd = dx + dy
408 if dd < closest_dist then
409 closest_dist = dd
410 closest_item = v
415 --print(closest_item)
416 if not closest_item then return end -- bort bort bort
417 local k, c = closest_item.k, closest_item.c
418 --print(k, c)
419 if not (type(k) == "number" and type(c) == "number") then return end
421 local _, _, crit_complete = GetAchievementCriteriaInfo(c)
422 --print(crit_complete)
423 if not crit_complete then return end -- welp
425 --print("desplicing", k, c)
426 assert(current_aches[k][c])
427 -- hey we found it gee jay
428 current_aches[k][c] = nil
429 local meta = GetAchievementMetaObjective(k)
431 for i = 1, #meta[c] do
432 QH_Route_ClusterRemove(meta[c][i])
433 for _, nod in ipairs(meta[c][i]) do
434 ach_locational[nod.loc] = nil
438 QH_Event("CRITERIA_UPDATE", qh_critupd)
442 local db
443 function Update_Objectives(_, new)
444 if not new then new = db end -- sometimes we're just told to update thanks to a change in checkmarks, and this is the easiest way to keep a DB around
445 db = new
446 --print("uobj", new)
447 if not new then QH_AchievementManagerRegister_Poke() return end
449 AchUpdateStart()
451 local oblit = {}
452 for k in pairs(TrackedAchievements) do
453 --print("updating achievement", k)
455 if not MetaAchievements[k] then
456 local achid = new.achievements[k]
457 if not achid then print(k) end
459 if achid.complete then
460 oblit[k] = true
461 else
462 if achievement_is_unified(k) then
463 AchUpdateAdd(k, "achieved")
464 else
465 local critcount = GetAchievementNumCriteria(k)
466 for i = 1, critcount do
467 local _, _, _, _, _, _, _, _, _, crit = GetAchievementCriteriaInfo(k, i)
469 if not new.criteria[crit].complete then
470 AchUpdateAdd(k, crit)
478 for k in pairs(oblit) do
479 TrackedAchievements[k] = nil
482 AchUpdateEnd()
485 QH_AchievementManagerRegister(Update_Objectives)
486 QH_AchievementManagerRegister_Poke()