219e749046
release / release (push) Successful in 5s
GetAchievementCriteriaInfo returns nil reqQuantity/quantity for some CoA achievements; 'reqQuantity > 1' crashed. Guarded both.
252 lines
8.3 KiB
Lua
252 lines
8.3 KiB
Lua
--[[ *** DataStore_Achievements ***
|
|
Written by : Thaoky, EU-Marécages de Zangar
|
|
June 21st, 2009
|
|
--]]
|
|
if not DataStore then return end
|
|
|
|
local addonName = "DataStore_Achievements"
|
|
|
|
_G[addonName] = LibStub("AceAddon-3.0"):NewAddon(addonName, "AceConsole-3.0", "AceEvent-3.0")
|
|
|
|
local addon = _G[addonName]
|
|
|
|
local THIS_ACCOUNT = "Default"
|
|
|
|
local AddonDB_Defaults = {
|
|
global = {
|
|
Characters = {
|
|
['*'] = { -- ["Account.Realm.Name"]
|
|
lastUpdate = nil,
|
|
numAchievements = 0,
|
|
numCompletedAchievements = 0,
|
|
numAchievementPoints = 0,
|
|
guid = nil,
|
|
Achievements = {},
|
|
CompletionDates = {},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
-- *** Scanning functions ***
|
|
local CriteriaCache = {}
|
|
|
|
local function ScanSingleAchievement(id, isCompleted, month, day, year)
|
|
addon.ThisCharacter.lastUpdate = time()
|
|
local Achievements = addon.ThisCharacter.Achievements
|
|
|
|
-- a given entry can have 3 types of values:
|
|
-- nil = the achievement has not been started, not even a single criteria
|
|
-- true = the achievement has been completed, so implicitly, all criterias have been completed too
|
|
-- string = the achievement is partially complete, so a string of values describes the state of completion
|
|
|
|
if isCompleted then
|
|
Achievements[id] = true -- true when completed, all criterias are completed thus
|
|
addon.ThisCharacter.CompletionDates[id] = format("%d:%d:%d", month, day, year)
|
|
return
|
|
end
|
|
|
|
wipe(CriteriaCache)
|
|
for j = 1, GetAchievementNumCriteria(id) do
|
|
-- ** calling GetAchievementCriteriaInfo in this loop is what costs the most in terms of cpu time **
|
|
local _, _, critCompleted, quantity, reqQuantity = GetAchievementCriteriaInfo(id, j);
|
|
local currentCrit
|
|
|
|
if critCompleted then
|
|
table.insert(CriteriaCache, tostring(j))
|
|
else
|
|
if (reqQuantity or 0) > 1 then -- CoA: GetAchievementCriteriaInfo can return nil quantities for custom/partial achievements
|
|
table.insert(CriteriaCache, j .. ":" .. (quantity or 0))
|
|
end
|
|
end
|
|
end
|
|
|
|
if #CriteriaCache > 0 then -- if at least one criteria completed, save the entry, do nothing otherwise
|
|
Achievements[id] = table.concat(CriteriaCache, ",")
|
|
end
|
|
end
|
|
|
|
local function ScanAllAchievements()
|
|
wipe(addon.ThisCharacter.Achievements)
|
|
|
|
local cats = GetCategoryList()
|
|
local achievementID, achCompleted
|
|
local prevID
|
|
|
|
for _, categoryID in ipairs(cats) do
|
|
for i = 1, GetCategoryNumAchievements(categoryID) do
|
|
achievementID, _, _, achCompleted, month, day, year = GetAchievementInfo(categoryID, i);
|
|
ScanSingleAchievement(achievementID, achCompleted, month, day, year)
|
|
|
|
-- track previous steps of a progressive achievements
|
|
prevID = GetPreviousAchievement(achievementID)
|
|
|
|
while type(prevID) ~= "nil" do
|
|
achievementID, _, _, achCompleted, month, day, year = GetAchievementInfo(prevID);
|
|
ScanSingleAchievement(achievementID, achCompleted, month, day, year)
|
|
prevID = GetPreviousAchievement(achievementID)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function ScanProgress()
|
|
local char = addon.ThisCharacter
|
|
local total, completed = GetNumCompletedAchievements()
|
|
|
|
char.numAchievements = total
|
|
char.numCompletedAchievements = completed
|
|
char.numAchievementPoints = GetTotalAchievementPoints()
|
|
end
|
|
|
|
|
|
-- ** Mixins **
|
|
local function _GetAchievementInfo(character, achievementID)
|
|
local achievement = character.Achievements[achievementID]
|
|
|
|
if achievement then -- if there's a value ..
|
|
local isStarted = true -- .. the achievement has been worked on
|
|
local isComplete
|
|
if achievement == true then -- ..and might even have been completed
|
|
isComplete = true
|
|
end
|
|
return isStarted, isComplete
|
|
end
|
|
-- implicit return of nil, nil otherwise
|
|
end
|
|
|
|
local function _GetCriteriaInfo(character, achievementID, criteriaIndex)
|
|
local achievement = character.Achievements[achievementID]
|
|
|
|
if type(achievement) == "string" then -- only return criteria info if the DB value is a string
|
|
|
|
for v in achievement:gmatch("([^,]+)") do
|
|
local index, qty = strsplit(":", v)
|
|
|
|
index = tonumber(index)
|
|
qty = tonumber(qty)
|
|
|
|
if criteriaIndex == index then
|
|
local isStarted = true -- .. the criteria has been worked on
|
|
local isComplete
|
|
if not qty then -- ..and might even have been completed (no qty means complete)
|
|
isComplete = true
|
|
end
|
|
|
|
-- this will return :
|
|
-- true, true, nil if the criteria is 100% completed
|
|
-- true, nil, value if the criteria is partially complete
|
|
return isStarted, isComplete, qty
|
|
end
|
|
end
|
|
end
|
|
-- implicit return of nil, nil , nil (not started, not complete)
|
|
end
|
|
|
|
local function _GetNumAchievements(character)
|
|
return character.numAchievements
|
|
end
|
|
|
|
local function _GetNumCompletedAchievements(character)
|
|
return character.numCompletedAchievements
|
|
end
|
|
|
|
local function _GetNumAchievementPoints(character)
|
|
return character.numAchievementPoints
|
|
end
|
|
|
|
local function _GetAchievementLink(character, achievementID)
|
|
-- information sources :
|
|
-- http://www.wowwiki.com/AchievementLink
|
|
-- http://www.wowwiki.com/AchievementString
|
|
if not character.guid then return end
|
|
|
|
local achievement = character.Achievements[achievementID]
|
|
if not achievement then return end -- exit if there's no value ..
|
|
|
|
local link
|
|
local completion -- will contain: finished (0 or 1), month, day, year
|
|
local criterias
|
|
|
|
if achievement == true then -- totally completed
|
|
local completionDate = character.CompletionDates[achievementID]
|
|
if not completionDate then return end -- if there's no data yet for this achievement, the link can't be created, return nil
|
|
|
|
completion = format("1:%s", completionDate) -- ex: 1:12:19:8 1 = finished, on 12/19/2008
|
|
criterias = "4294967295:4294967295:4294967295:4294967295" -- 4294967295 = the highest 32-bit value = 32 bits set to 1
|
|
else -- partially completed
|
|
completion = "0:0:0:-1"
|
|
|
|
local bitset = { 0, 0, 0, 0 } -- a simple array that will contain the 4 values to store into "criterias"
|
|
local isComplete
|
|
local numCriteria = GetAchievementNumCriteria(achievementID)
|
|
|
|
for criteriaIndex = 1, numCriteria do -- browse all criterias
|
|
local index = ceil(criteriaIndex / 32) -- store in bitset[1], [2] ..
|
|
|
|
_, isComplete = _GetCriteriaInfo(character, achievementID, criteriaIndex)
|
|
if isComplete then
|
|
local pos = mod(criteriaIndex, 32) -- pos must be within [1 .. 32]
|
|
pos = (pos == 0) and 32 or pos -- if the modulo leads to 0, change it to 32
|
|
bitset[index] = bitset[index] + (2^(pos-1)) -- I'll change this to use bit functions later on, for the time being, this works fine.
|
|
end
|
|
end
|
|
|
|
criterias = table.concat(bitset, ":")
|
|
end
|
|
|
|
local _, name = GetAchievementInfo(achievementID)
|
|
|
|
return format("|cffffff00|Hachievement:%s:%s:%s:%s|h\[%s\]|h|r", achievementID, character.guid, completion, criterias, name)
|
|
end
|
|
|
|
local PublicMethods = {
|
|
GetAchievementInfo = _GetAchievementInfo,
|
|
GetCriteriaInfo = _GetCriteriaInfo,
|
|
GetNumAchievements = _GetNumAchievements,
|
|
GetNumCompletedAchievements = _GetNumCompletedAchievements,
|
|
GetNumAchievementPoints = _GetNumAchievementPoints,
|
|
GetAchievementLink = _GetAchievementLink,
|
|
}
|
|
|
|
function addon:OnInitialize()
|
|
addon.db = LibStub("AceDB-3.0"):New(addonName .. "DB", AddonDB_Defaults)
|
|
|
|
DataStore:RegisterModule(addonName, addon, PublicMethods)
|
|
DataStore:SetCharacterBasedMethod("GetAchievementInfo")
|
|
DataStore:SetCharacterBasedMethod("GetCriteriaInfo")
|
|
DataStore:SetCharacterBasedMethod("GetNumAchievements")
|
|
DataStore:SetCharacterBasedMethod("GetNumCompletedAchievements")
|
|
DataStore:SetCharacterBasedMethod("GetNumAchievementPoints")
|
|
DataStore:SetCharacterBasedMethod("GetAchievementLink")
|
|
end
|
|
|
|
function addon:OnEnable()
|
|
addon:RegisterEvent("PLAYER_ALIVE")
|
|
addon:RegisterEvent("ACHIEVEMENT_EARNED")
|
|
end
|
|
|
|
function addon:OnDisable()
|
|
addon:UnregisterEvent("PLAYER_ALIVE")
|
|
addon:UnregisterEvent("ACHIEVEMENT_EARNED")
|
|
end
|
|
|
|
|
|
-- *** EVENT HANDLERS ***
|
|
function addon:PLAYER_ALIVE()
|
|
-- print("DataStore_Achievements.lua") -- DEBUG 2025 07 21
|
|
if not UnitIsGhost("player") then return end -- only scan if player released spirit and went to graveyard
|
|
|
|
ScanAllAchievements()
|
|
ScanProgress()
|
|
addon.ThisCharacter.guid = strsub(UnitGUID("player"), 3) -- get rid at the 0x at the beginning of the string
|
|
end
|
|
|
|
function addon:ACHIEVEMENT_EARNED(self, id)
|
|
if id then
|
|
local _, _, _, achCompleted, month, day, year = GetAchievementInfo(id)
|
|
ScanSingleAchievement(id, true, month, day, year)
|
|
ScanProgress()
|
|
end
|
|
end
|