-- Roster.lua handles the collection of pets and created API to get information about pets. -- Filters.lua handles the definition of filters and API to manipulate those filters. -- PetList.lua applies the filters to the roster to form the filtered pet list to display. local _,L = ... local rematch = Rematch local settings local roster = CreateFrame("Frame") -- this module will handle its own events rematch.Roster = roster -- pets is the master list of petIDs/speciesIDs (the "unfiltered" journal list) local allPets = {} roster.allPets = allPets -- speciesIDs is the master list of speciesIDs local allSpeciesIDs = {} -- list of all speciesIDs roster.speciesIDs = allSpeciesIDs -- (delete this line before pushing; need to really avoid direct access) roster.speciesSources = {} -- source indexes of each species, filled only if GetSpeciesSource ever used -- place to backup and restore journal settings roster.journalBackup = { search="", collected=nil, notCollected=nil, types={}, sources={} } roster.uniquePets = 0 -- number of unique pets owned, updated when number of owned pets changes roster.ownedPets = nil -- number of owned pets in the journal; UpdateOwned() runs when this number changes roster.updatingSelf = nil -- becomes true when roster is updating itself (to prevent reacting to own events) roster.ownedNeedsUpdated = true -- becomes true when we need to expand filters to grab all pets rematch:InitModule(function() settings = RematchSettings roster:RegisterEvent("UPDATE_SUMMONPETS_ACTION") roster:SetScript("OnEvent",function(self,event,...) if roster[event] then roster[event](self,...) end end) roster.isStrongCache = rematch:CreateODTable() -- used by IsStrong() function end) function roster:GetNumUniquePets() roster:UpdateOwned() return roster.uniquePets end -- this should be the standard way of updating the roster; wait a frame before doing actual update function rematch:UpdateRoster() roster.petListNeedsUpdated = true rematch:StartTimer("RosterUpdate",0,rematch.UpdateUI) end --[[ As of 10.0.2, using UPDATE_SUMMONPETS_ACTION to test whether pets are loaded on login ]] -- using this to know when pets are loaded function roster:UPDATE_SUMMONPETS_ACTION() roster:UnregisterEvent("UPDATE_SUMMONPETS_ACTION") roster:RegisterEvent("PET_JOURNAL_LIST_UPDATE") roster:RegisterEvent("NEW_PET_ADDED") roster:RegisterEvent("PET_JOURNAL_PET_DELETED") roster:PET_JOURNAL_LIST_UPDATE() -- start first check end --[[ PET_JOURNAL_LIST_UPDATE Just about everything that changes pets (added/removed, renamed, stoned for level/rarity, etc) triggers this event. As of 7.0.3: On a cold login if this fires once: 1: numPets is valid, numOwned is valid, not sure on first loadout (not observed lately) On a cold login if this fires twice: 1: numPets is 0, numOwned is 0, first loadout is nil 2: numPets is valid, numOwned is valid, first loadout is valid On a /reload if this fires once (first /reload after a cold login): 1: numPets is valid, numOwned is valid, first loadout is valid On a /reload if this fires twice: 1: numPets is 0, numOwned is valid, first loadout is nil (pets are not properly loaded!) 2: numPets is valid, numOwned is valid, first loadout is valid Notes: - numPets,numOwned are the returns of C_PetJournal.GetNumPets() - numPets can be 0 and still be valid! When the default journal has all pets filtered off. - numOwned can be 0 and still be valid too! When the user has no pets. - On the first event of a /reload when it fires twice, petIDs are not yet valid. - In the past on a cold login it could take a good while for this event to fire (and it would fire once) - As of 4.7.3, PET_JOURNAL_LIST_UPDATE does not fire for the roster until the journal is unlocked ]] function roster:PET_JOURNAL_LIST_UPDATE() if not rematch.inWorld then return end local numPets,owned = C_PetJournal.GetNumPets() -- if number of owned pets changed, pets were added/removed; flag for an update to happen if owned ~= roster.ownedPets then roster.ownedPets = owned roster.ownedNeedsUpdated = true end -- if pets not loaded yet, simply leave if not owned or owned==0 then return end rematch.queueNeedsProcessed = true if owned>0 and settings.ShowOnLogin and not InCombatLockdown() then rematch.Frame:Show() end settings.ShowOnLogin = nil rematch.speciesAt25:Invalidate() roster.petListNeedsUpdated = true rematch:UpdateUI() end roster.NEW_PET_ADDED = roster.PET_JOURNAL_LIST_UPDATE roster.PET_JOURNAL_PET_DELETED = roster.PET_JOURNAL_LIST_UPDATE -- this is only registered while "Other","CurrentZone" filter enabled function roster:ZONE_CHANGED_NEW_AREA() if roster:GetFilter("Other","CurrentZone") then rematch:StartTimer("CurrentZone",0.75,rematch.UpdateUI) end end -- this is safe to call at any time; only when the number of owned pets changes will -- roster.ownedNeedsUpdated be true. function roster:UpdateOwned() if roster.ownedNeedsUpdated then -- first go through and mark all sanctuary pets as unknown local sanctuary = settings.Sanctuary for _,pet in pairs(sanctuary) do pet[2] = nil end -- in case user caged the gcd pet, reset gcd pet rematch.GCDPetID = nil -- next expand filters roster:ExpandJournalFilters() local numPets,owned = C_PetJournal.GetNumPets() -- get this AFTER expanding local fillSpecies = #allSpeciesIDs==0 -- if we haven't gathered species yet -- wipe existing pets local uniqueOwned = rematch.info -- will collect species owned in this table to count uniques wipe(uniqueOwned) wipe(allPets) -- and go through and gather them for i=1,numPets do local petID,speciesID,_,_,level = C_PetJournal.GetPetInfoByIndex(i) -- add the petID to allPets if it's one we own, or its speciesID otherwise if petID then tinsert(allPets,petID) uniqueOwned[speciesID] = true if sanctuary[petID] then -- if the pet is in the sanctuary sanctuary[petID][2] = true -- flag it as known to exist end else tinsert(allPets,speciesID) end -- if we need to gather species and this one isn't already in the allSpeciesIDs table if fillSpecies and not tContains(allSpeciesIDs,speciesID) then tinsert(allSpeciesIDs,speciesID) end end -- total unique pets roster.uniquePets = 0 for k,v in pairs(uniqueOwned) do roster.uniquePets = roster.uniquePets + 1 end wipe(uniqueOwned) -- put everything back how it was roster:RestoreJournalFilters() roster.ownedNeedsUpdated = owned==0 -- if pets not loaded we still need updating! rematch.sanctuaryNeedsUpdated = owned>0 -- and don't touch sanctuary until then! end end --[[ Journal Extraction: getting all pets from the journal requires expanding filters and clearing search, getting the list of pets, then restoring filters/search ]] -- since there is no C_PetJournal.GetSearchFilter, we need to watch for it changing hooksecurefunc(C_PetJournal,"SetSearchFilter",function(search) if not roster.updatingSelf then roster.journalBackup.search = search or "" end end) hooksecurefunc(C_PetJournal,"ClearSearchFilter",function() if not roster.updatingSelf then roster.journalBackup.search = "" end end) -- backs up filters and expands journal to show all pets function roster:ExpandJournalFilters() -- if no filters are used, no need to expand journal if not roster:IsAnyFilterUsed() then roster.noFiltersUsed = true return end roster.updatingSelf = true -- we're going to be triggering a PJLU event, expand disables event; restore turns it on after a delay roster:UnregisterEvent("PET_JOURNAL_LIST_UPDATE") -- backup all filters local backup = roster.journalBackup backup.collected = C_PetJournal.IsFilterChecked(LE_PET_JOURNAL_FILTER_COLLECTED) backup.notCollected = C_PetJournal.IsFilterChecked(LE_PET_JOURNAL_FILTER_NOT_COLLECTED) for i=1,C_PetJournal.GetNumPetTypes() do backup.types[i] = C_PetJournal.IsPetTypeChecked(i) end for i=1,C_PetJournal.GetNumPetSources() do backup.sources[i] = C_PetJournal.IsPetSourceChecked(i) end C_PetJournal.ClearSearchFilter() C_PetJournal.SetFilterChecked(LE_PET_JOURNAL_FILTER_COLLECTED,true) C_PetJournal.SetFilterChecked(LE_PET_JOURNAL_FILTER_NOT_COLLECTED,true) C_PetJournal.SetAllPetSourcesChecked(true) C_PetJournal.SetAllPetTypesChecked(true) end -- restores journal filters to the state in roster.journalBackup function roster:RestoreJournalFilters() -- if this was set in the ExpandJournalFilters, then nothing was backed up to restore if roster.noFiltersUsed then roster.noFiltersUsed = nil return end local backup = roster.journalBackup C_PetJournal.SetFilterChecked(LE_PET_JOURNAL_FILTER_COLLECTED,backup.collected) C_PetJournal.SetFilterChecked(LE_PET_JOURNAL_FILTER_NOT_COLLECTED,backup.notCollected) for i=1,C_PetJournal.GetNumPetSources() do C_PetJournal.SetPetSourceChecked(i,backup.sources[i]) end for i=1,C_PetJournal.GetNumPetTypes() do C_PetJournal.SetPetTypeFilter(i,backup.types[i]) end C_PetJournal.SetSearchFilter(backup.search) roster.updatingSelf = nil -- we're done messing with PJLU-triggering stuff, start event again next frame C_Timer.After(0,function() roster:RegisterEvent("PET_JOURNAL_LIST_UPDATE") end) end -- returns true if any journal filters are used, since there's no need to expand/restore if it's already expanded function roster:IsAnyFilterUsed() if roster.journalBackup.search~="" then return true -- search is used end if not C_PetJournal.IsFilterChecked(LE_PET_JOURNAL_FILTER_COLLECTED) then return true -- collected is unchecked end if not C_PetJournal.IsFilterChecked(LE_PET_JOURNAL_FILTER_NOT_COLLECTED) then return true -- not collected is unchecked end for i=1,C_PetJournal.GetNumPetTypes() do if not C_PetJournal.IsPetTypeChecked(i) then return true -- a pet type is unchecked end end for i=1,C_PetJournal.GetNumPetSources() do if not C_PetJournal.IsPetSourceChecked(i) then return true -- a pet source is unchecked end end return false end --[[ Iterator functions will go over all pets, running UpdateOwned if needed ]] -- this is all pets, owned petIDs and missing speciesIDs function roster:AllPets() roster:UpdateOwned() local i=0 return function() i=i+1 if i<=#allPets then return allPets[i] end end end -- this is all owned petIDs function roster:AllOwnedPets() roster:UpdateOwned() local i=0 return function() i=i+1 if i<=#allPets then local petID = allPets[i] if type(petID)=="string" then return petID end end end end -- this is all unique speciesIDs function roster:AllSpecies() roster:UpdateOwned() local i=0 return function() i=i+1 if i<=#allSpeciesIDs then return allSpeciesIDs[i] end end end --[[ On-demand stuff ]] -- returns the source (as a number 1-10; Drop, Quest, Vendor, etc) of a speciesID. -- unfortunately the only way to get this number is by setting the journal filters and -- looking at what's left. this caches first encounter so later use doesn't touch filters. function roster:GetSpeciesSource(speciesID) local sources = roster.speciesSources if not next(sources) then -- haven't collected sources this session roster:ExpandJournalFilters() for source=1,C_PetJournal.GetNumPetSources() do -- going through all pet sources -- set source filter to single category for i=1,C_PetJournal.GetNumPetSources() do C_PetJournal.SetPetSourceChecked(i,i==source) end -- fill speciesSources with the results for i=1,C_PetJournal.GetNumPets() do local _,speciesID = C_PetJournal.GetPetInfoByIndex(i) if not sources[speciesID] then sources[speciesID] = source end end end roster:RestoreJournalFilters() -- put everything back how it was end -- after the first time sources are gathered, we just return the cached source index if speciesID then return sources[speciesID] end end -- returns true if the moveset of a species is at 25 for any species function roster:IsMovesetAt25(speciesID) return rematch.movesetsAt25[rematch.movesetsBySpecies[speciesID]] end -- counts the number of teams that a petID belongs to -- (remove this once PetList converted to real petInfo) function roster:IsPetInTeam(petID) return rematch.altInfo:Fetch(petID).inTeams end function roster:PopulatePetsInTeams(data) for _,team in pairs(RematchSaved) do for i=1,3 do local petID = team[i][1] if petID then if not data[petID] then data[petID] = 0 end data[petID] = data[petID] + 1 end end end end -- returns true if the given speciesID is strong to the filtered pet types function roster:IsStrong(speciesID) -- to save work, use an ondemand table with cached results of this species local cache = roster.isStrongCache:Activate() if cache[speciesID]~=nil then return cache[speciesID] end local abilities = rematch:GetAbilities(speciesID) local lookup = rematch.info wipe(lookup) -- fill lookup table with abilityType of attacks this species has for _,abilityID in ipairs(abilities) do local _,_,_,_,_,_,abilityType,noHints = C_PetBattles.GetAbilityInfoByID(abilityID) if not noHints then lookup[abilityType] = true end end -- then go through each filter and make sure each Strong Vs type are accounted for for attackType in pairs(settings.Filters.Strong) do if not lookup[rematch.hintsDefense[attackType][1]] then cache[speciesID] = false -- a filtered Strong Vs is not among the pet's attacks; mark it false return false end end -- if we reached here, all Strong Vs types are accounted for in the pet's attacks cache[speciesID] = true return true end