local GetTime = QuestHelper_GetTime QuestHelper_File["director_achievement.lua"] = "4.0.1.$svnversion$" QuestHelper_Loadtime["director_achievement.lua"] = GetTime() local debug_output = false if QuestHelper_File["director_achievement.lua"] == "Development Version" then debug_output = true end local achieveable = {} local function IsDoable(id) if achieveable[id] == nil then -- First we just see if we have a DB entry for it -- This can be made *much* more efficient. if not DB_Ready() then print("DB not yet ready, please wait") return false end if DB_HasItem("achievement", id) then --print(id, "achieveable via db") achieveable[id] = true return true end local crit = GetAchievementNumCriteria(id) -- Whenever I write "crit" as a variable name it always slightly worries me. -- 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. -- 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. -- So I start typing the first thing that comes to mind that fulfills all the requirements. -- Luckily, I stop myself in time, and write "cnt" instead. -- 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. -- So now you know. Back to the code. -- (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) if crit > 0 then for i = 1, crit do local _, typ, _, _, _, _, _, asset, _, cid = GetAchievementCriteriaInfo(id, i) if typ == 0 then -- Monster kill. We're good! We can do these. elseif typ == 8 then -- Achievement chain if not IsDoable(asset) then achieveable[id] = false break end else achieveable[id] = false break end end if achieveable[id] == nil then --print(id, "achieveable via occlusion") achieveable[id] = true end else --print(id, "not achieveable due to wizard casting what the fuck") achieveable[id] = false end end return achieveable[id] end local function GetListOfAchievements(category) local ct = GetCategoryNumAchievements(category) local available_achieves = {} for i = 1, ct do local id, _, _, complete = GetAchievementInfo(category, i) if not complete and IsDoable(id) then table.insert(available_achieves, i) end end return available_achieves end local function FilterFunction(category) local aa = GetListOfAchievements(category) return #aa, 0, 0 end local function SetQHVis(button, ach, _, _, complete) button.qh_checkbox:Hide() if complete or not IsDoable(ach) then button.qh_checkbox:Hide() else button.qh_checkbox:Show() end end local ABDA_permit local ABDA local function ABDA_Replacement(button, category, achievement, selectionID) -- hee hee hee -- i am sneaky like fish -- *sneaky* fish -- ^__^ if ABDA_permit and ACHIEVEMENTUI_SELECTEDFILTER == FilterFunction then local aa = GetListOfAchievements(category) local ach = aa[achievement] ABDA(button, category, ach, selectionID) SetQHVis(button, GetAchievementInfo(category, ach)) else ABDA(button, category, achievement, selectionID) SetQHVis(button, GetAchievementInfo(category, achievement)) end end local AFAU local function AFAU_Replacement(...) ABDA_permit = true AFAU(...) ABDA_permit = false end local TrackedAchievements = {} local MetaAchievements = {} local Update_Objectives local function MarkAchieveable(id, setto) TrackedAchievements[id] = setto local crit = GetAchievementNumCriteria(id) for i = 1, crit do local _, typ, _, _, _, _, _, asset, _, cid = GetAchievementCriteriaInfo(id, i) if typ == 8 then MetaAchievements[id] = true MarkAchieveable(asset, setto) end end end local check_onshow function QH_COC_GIX(id) MarkAchieveable(id, true) Update_Objectives() end local function check_onclick(self) if self:GetChecked() then MarkAchieveable(self:GetParent().id, true) else MarkAchieveable(self:GetParent().id, nil) end Update_Objectives() for i = 1, #AchievementFrameAchievementsContainer.buttons do check_onshow(AchievementFrameAchievementsContainer.buttons[i].qh_checkbox) end end local function check_onenter(self) GameTooltip:SetOwner(self, "ANCHOR_RIGHT") GameTooltip:SetText(QHText("ACHIEVEMENT_CHECKBOX")) end local function check_onleave(self) GameTooltip:Hide() end function check_onshow(self) self:SetChecked(TrackedAchievements[self:GetParent().id]) end local AAFOC = AchievementAlertFrame_OnClick function AAF_onclick(self) if not self.id then return end local _, _, _, complete = GetAchievementInfo(self.id); if complete and ACHIEVEMENTUI_SELECTEDFILTER == FilterFunction then -- yipyipyipyipyipyipyip AchievementFrame_SetFilter(ACHIEVEMENT_FILTER_ALL) -- uh-huh, uh-huh end AAFOC(self) end AchievementAlertFrame_OnClick = AAF_onclick QH_Event("ADDON_LOADED", function (addonid) if addonid == "Blizzard_AchievementUI" then -- yyyyyyoink table.insert(AchievementFrameFilters, {text="QuestHelpable", func=FilterFunction}) ABDA = AchievementButton_DisplayAchievement AchievementButton_DisplayAchievement = ABDA_Replacement AFAU = AchievementFrameAchievements_Update AchievementFrameAchievements_Update = AFAU_Replacement AchievementFrameAchievementsContainer.update = AFAU_Replacement ACHIEVEMENT_FUNCTIONS.updateFunc = AFAU_Replacement for i = 1, #AchievementFrameAchievementsContainer.buttons do local framix = CreateFrame("CheckButton", "qh_arglbargl_" .. i, AchievementFrameAchievementsContainer.buttons[i], "AchievementCheckButtonTemplate") framix:SetPoint("BOTTOMRIGHT", AchievementFrameAchievementsContainer.buttons[i], "BOTTOMRIGHT", -22, 7.5) framix:SetScript("OnEnter", check_onenter) framix:SetScript("OnLeave", check_onleave) framix:SetScript("OnShow", check_onshow) framix:SetScript("OnClick", check_onclick) _G["qh_arglbargl_" .. i .. "Text"]:Hide() -- no local sigil = framix:CreateTexture("BACKGROUND") sigil:SetHeight(24) sigil:SetWidth(24) sigil:SetTexture("Interface\\AddOns\\QuestHelper\\sigil") sigil:SetPoint("RIGHT", framix, "LEFT", -1, 0) sigil:SetVertexColor(0.6, 0.6, 0.6) AchievementFrameAchievementsContainer.buttons[i].qh_checkbox = framix end end end) local function horribledupe(from) if not from then return nil end local rv = {} for k, v in pairs(from) do if k == "__owner" then elseif type(v) == "table" then rv[k] = horribledupe(v) else rv[k] = v end end return rv end local achievement_list = setmetatable({}, {__mode="k"}) function GetAchievementMetaObjective(achievement) if achievement_list[achievement] then return achievement_list[achievement] end local db = DB_GetItem("achievement", achievement, true) local ite = {} ite.desc = select(2, GetAchievementInfo(achievement)) ite.tracker_desc = ite.desc ite.tracker_split = true local itlist = {} if db and db.achieved then table.insert(itlist, {cid = "achieved", chunk = db.achieved}) else local crit = GetAchievementNumCriteria(achievement) for i = 1, crit do local name, typ, _, _, _, _, _, asset, _, cid = GetAchievementCriteriaInfo(achievement, i) --assert(cid) local chunk local split if typ == 0 then chunk = DB_GetItem("monster", asset) split = true else --assert(db) chunk = db[cid] end if split then local soldupe = horribledupe(chunk.solid) for i = 1, #chunk.loc do table.insert(itlist, {cid = cid, solid = soldupe, loc = chunk.loc[i], name = name}) end else table.insert(itlist, {cid = cid, chunk = chunk, name = name}) end end end for _, data in pairs(itlist) do local ttx = {} if data.chunk then ttx.solid = horribledupe(data.chunk.solid) if data.chunk.loc then for _, v in ipairs(data.chunk.loc) do table.insert(ttx, {loc = {x = v.x, y = v.y, c = QuestHelper_ParentLookup[v.p], p = v.p}}) end end end if data.loc then ttx.solid = data.solid table.insert(ttx, {loc = {x = data.loc.x, y = data.loc.y, c = QuestHelper_ParentLookup[data.loc.p], p = data.loc.p}}) end if #ttx == 0 then 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 ttx.type_achievement_unknown = true end for _, v in ipairs(ttx) do v.map_desc = {ite.desc, data.name} v.tracker_desc = data.name or ite.desc v.tracker_hide_dupes = true v.hidden_desc = data.name and string.format("%s: %s", ite.desc, data.name) or ite.desc v.arrow_desc = v.hidden_desc if not data.name then v.tracker_hidden = true end v.cluster = ttx v.why = ite v.icon_id = 17 end if not ite[data.cid] then ite[data.cid] = {} end table.insert(ite[data.cid], ttx) end achievement_list[achievement] = ite return achievement_list[achievement] end local function achievement_is_unified(ach) return GetAchievementMetaObjective(ach).achieved end local current_aches = {} local next_aches = {} local ach_locational = {} local function AchUpdateStart() next_aches = {} end local function AchUpdateAdd(ach, crit) if not next_aches[ach] then next_aches[ach] = {} end next_aches[ach][crit] = true end local function AchUpdateEnd() --print("aue") for k, v in pairs(current_aches) do for c in pairs(v) do if not next_aches[k] or not next_aches[k][c] then local meta = GetAchievementMetaObjective(k) for i = 1, #meta[c] do QH_Route_ClusterRemove(meta[c][i]) for _, nod in ipairs(meta[c][i]) do ach_locational[nod.loc] = nil end end end end end for k, v in pairs(next_aches) do for c in pairs(v) do local kacey = nil if not current_aches[k] or not current_aches[k][c] then local meta = GetAchievementMetaObjective(k) for i = 1, #meta[c] do QH_Route_ClusterAdd(meta[c][i]) for _, nod in ipairs(meta[c][i]) do if not ach_locational[nod.loc] then if not kacey then kacey = QuestHelper:CreateTable("ach_locational") kacey.k = k kacey.c = c end --print(nod.loc.p, nod.loc.x, nod.loc.y) ach_locational[nod.loc] = kacey end end end end end end current_aches = next_aches -- yaaaaaaaay end function qh_critupd() --print("critup") local c, z, x, y = QuestHelper.routing_c, QuestHelper.routing_z, QuestHelper.routing_ax, QuestHelper.routing_ay --print(c, z, x, y) if not c or not z or not x or not y then return end local p = QuestHelper_IndexLookup[c][z] --print(p) if not p then return end local closest_dist = 1000000000000 local closest_item = nil for k, v in pairs(ach_locational) do if k.p == p then local dx, dy = x - k.x, y - k.y dx, dy = dx * dx, dy * dy local dd = dx + dy if dd < closest_dist then closest_dist = dd closest_item = v end end end --print(closest_item) if not closest_item then return end -- bort bort bort local k, c = closest_item.k, closest_item.c --print(k, c) if not (type(k) == "number" and type(c) == "number") then return end local _, _, crit_complete = GetAchievementCriteriaInfo(c) --print(crit_complete) if not crit_complete then return end -- welp --print("desplicing", k, c) --assert(current_aches[k][c]) -- hey we found it gee jay current_aches[k][c] = nil local meta = GetAchievementMetaObjective(k) for i = 1, #meta[c] do QH_Route_ClusterRemove(meta[c][i]) for _, nod in ipairs(meta[c][i]) do ach_locational[nod.loc] = nil end end end QH_Event("CRITERIA_UPDATE", qh_critupd) local db function Update_Objectives(_, new) 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 db = new --print("uobj", new) if not new then QH_AchievementManagerRegister_Poke() return end AchUpdateStart() local oblit = {} for k in pairs(TrackedAchievements) do --print("updating achievement", k) if not MetaAchievements[k] then local achid = new.achievements[k] if not achid then print(k) end if achid.complete then oblit[k] = true else if achievement_is_unified(k) then AchUpdateAdd(k, "achieved") else local critcount = GetAchievementNumCriteria(k) for i = 1, critcount do local _, _, _, _, _, _, _, _, _, crit = GetAchievementCriteriaInfo(k, i) if not new.criteria[crit].complete then AchUpdateAdd(k, crit) end end end end end end for k in pairs(oblit) do TrackedAchievements[k] = nil end AchUpdateEnd() end QH_AchievementManagerRegister(Update_Objectives) QH_AchievementManagerRegister_Poke()