You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
627 lines
23 KiB
627 lines
23 KiB
|
5 years ago
|
-- This is an extension to Roster and only deals with populating the filtered list of pets.
|
||
|
|
|
||
|
|
-- 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 roster = Rematch.Roster
|
||
|
|
local settings
|
||
|
|
|
||
|
|
local GetFilter = roster.GetFilter -- since this is called so often, made local for speed
|
||
|
|
|
||
|
|
roster.petList = {} -- filtered list of petIDs/speciesIDs to list in the pet panel
|
||
|
|
|
||
|
|
local activeFilters = {} -- list of filter groups that are active
|
||
|
|
-- this is the order that filter groups are processed (simplest to most complex)
|
||
|
|
local priority = { "Favorite", "Collected", "Types", "Rarity", "Sources", "Tough",
|
||
|
|
"Level", "Other", "Expansion", "Breed", "Strong", "Similar", "Moveset", "Script" }
|
||
|
|
|
||
|
|
local petInfo -- this will be rematch.petInfo as each new pet is looked at
|
||
|
|
local filterFuncs = {} -- each filter group has a filterFuncs entry to process the petInfo
|
||
|
|
|
||
|
|
local sortReference = { "speciesName", "level", "rarity", "petType", "maxHealth", "power", "speed" }
|
||
|
|
local sortStatsTable -- will be the SortStats tempTable
|
||
|
|
local sortFavoritesTable -- will be the Favorites tempTable (improves speed 57% over using PetIsFavorite)
|
||
|
|
local sortRelevanceTable -- will be the SearchRelevance tempTable
|
||
|
|
local sortLowToHigh -- will be whether sort lists in ascending or descending stat order
|
||
|
|
local sortByNickname -- will be whether settings.SortByNickname is enabled
|
||
|
|
|
||
|
|
rematch:InitModule(function()
|
||
|
|
settings = RematchSettings
|
||
|
|
rematch:RegisterTempTable("SearchSpecies") -- cache for searching text in a species
|
||
|
|
rematch:RegisterTempTable("SearchAbilities") -- cache for searching text in an ability
|
||
|
|
rematch:RegisterTempTable("SortStats") -- table of the primary sort stat of each pet
|
||
|
|
rematch:RegisterTempTable("Favorites") -- table containing favorite petIDs for sort optimization
|
||
|
|
rematch:RegisterTempTable("SearchRelevance") -- table of relevance values from search results
|
||
|
|
end)
|
||
|
|
|
||
|
|
-- UpdatePetList is the only function called outside this module.
|
||
|
|
-- It populates roster.petList with the sorted list of pets to display.
|
||
|
|
function roster:UpdatePetList()
|
||
|
|
|
||
|
|
if not roster.petListNeedsUpdated then
|
||
|
|
return -- don't do anything if list doesn't need updated
|
||
|
|
end
|
||
|
|
roster.petListNeedsUpdated = nil
|
||
|
|
|
||
|
|
local oldPetListSize = #roster.petList
|
||
|
|
|
||
|
|
-- start with a clean slate
|
||
|
|
wipe(roster.petList)
|
||
|
|
|
||
|
|
-- setup which filters are going to run
|
||
|
|
wipe(activeFilters)
|
||
|
|
for i=1,#priority do
|
||
|
|
local group = priority[i]
|
||
|
|
if roster:IsFilterUsed(group) then
|
||
|
|
tinsert(activeFilters,group)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- special cases to run before pulling data
|
||
|
|
-- for CurrentZone capture and desensitize the user's current zone
|
||
|
|
roster.currentZone = GetFilter(self,"Other","CurrentZone") and rematch:DesensitizeText(GetRealZoneText() or "") or nil
|
||
|
|
-- if script filter used, setup its environment
|
||
|
|
if GetFilter(self,"Script","code") then
|
||
|
|
rematch:SetupScriptEnvironment()
|
||
|
|
end
|
||
|
|
|
||
|
|
-- setup sort temp tables to hold primary sort stat
|
||
|
|
sortStatsTable = rematch:GetTempTable("SortStats")
|
||
|
|
sortFavoritesTable = rematch:GetTempTable("Favorites")
|
||
|
|
sortRelevanceTable = rematch:GetTempTable("SearchRelevance")
|
||
|
|
sortByNickname = settings.SortByNickname
|
||
|
|
local favoritesFirst = settings.Sort.FavoritesFirst -- not used outside this scope; ok to be local here
|
||
|
|
|
||
|
|
-- now go through each petID and add ones that should be listed to petList
|
||
|
|
for petID in roster:AllPets() do
|
||
|
|
petInfo = rematch.petInfo:Fetch(petID)
|
||
|
|
if roster:FilterPetByPetID(petID) then -- if pet is to be added
|
||
|
|
if favoritesFirst and petInfo.isFavorite then
|
||
|
|
sortFavoritesTable[petID] = true -- note favorites
|
||
|
|
end
|
||
|
|
sortStatsTable[petID] = roster:GetSortStat()
|
||
|
|
tinsert(roster.petList,petID)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- here all pets to be listed are gathered. now sort them
|
||
|
|
local order = settings.Sort.Order
|
||
|
|
local reversed = settings.Sort.Reverse
|
||
|
|
if (order==1 or order==4) then -- sort by name and type are ascending
|
||
|
|
sortLowToHigh = not reversed
|
||
|
|
else -- sort by level, rarity, health, power and speed is descending
|
||
|
|
sortLowToHigh = reversed
|
||
|
|
end
|
||
|
|
table.sort(roster.petList,roster.SortPets)
|
||
|
|
|
||
|
|
-- once in a great while there may be wrapped pets; not handling them in
|
||
|
|
-- main filters/sorts because of how infrequenlty it will happen
|
||
|
|
local numWrapped = C_PetJournal.GetNumPetsNeedingFanfare()
|
||
|
|
if numWrapped>0 then
|
||
|
|
local numRelocated = 0
|
||
|
|
while numWrapped>0 do
|
||
|
|
-- move backwards down list and relocate wrapped pets to start
|
||
|
|
for i=#roster.petList,2,-1 do
|
||
|
|
local petID = roster.petList[i]
|
||
|
|
-- if petID is an owned pet, needs fanfare, and one before it does not need fanfare, move it
|
||
|
|
if type(petID)=="string" and C_PetJournal.PetNeedsFanfare(petID) and i>numRelocated then
|
||
|
|
tremove(roster.petList,i) -- move the petID to the start of the list
|
||
|
|
tinsert(roster.petList,1,petID)
|
||
|
|
numRelocated = numRelocated + 1
|
||
|
|
break
|
||
|
|
end
|
||
|
|
end
|
||
|
|
-- sometimes pets needing fanfare will be filtered out so this may run more than
|
||
|
|
-- necessary; which is ok. this is happening very rarely only when at least
|
||
|
|
-- one pet needs a fanfare/unwrapped
|
||
|
|
numWrapped = numWrapped-1
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- if the number of pets listed has changed, then do an extra PetPanel update
|
||
|
|
if #roster.petList ~= oldPetListSize then
|
||
|
|
rematch.PetPanel:Update() -- this fixes the potential scroll offset bug when pet list changes length
|
||
|
|
end
|
||
|
|
|
||
|
|
-- whew! all done. now the cleanup
|
||
|
|
rematch:WipeTempTables()
|
||
|
|
if roster:GetFilter("Script","code") then
|
||
|
|
rematch:CleanupScriptEnvironment()
|
||
|
|
end
|
||
|
|
|
||
|
|
end
|
||
|
|
|
||
|
|
-- run the petID through each active filter and return false at first failure
|
||
|
|
-- if true returned, pet should be listed
|
||
|
|
function roster:FilterPetByPetID(petID)
|
||
|
|
|
||
|
|
-- -- gather information about the pet into petInfo
|
||
|
|
-- if type(petID)=="string" then -- if this is an owned pet
|
||
|
|
-- petInfo.owned = true
|
||
|
|
-- petInfo.speciesID, petInfo.customName, petInfo.level, petInfo.xp, petInfo.maxXp, petInfo.displayID, petInfo.isFavorite, petInfo.name, petInfo.icon, petInfo.petType, petInfo.creatureID, petInfo.sourceText, petInfo.description, petInfo.isWild, petInfo.canBattle, petInfo.tradable, petInfo.unique, petInfo.obtainable = C_PetJournal.GetPetInfoByPetID(petID)
|
||
|
|
-- petInfo.petID = petID
|
||
|
|
-- if not petInfo.canBattle then
|
||
|
|
-- petInfo.level = 0
|
||
|
|
-- end
|
||
|
|
-- elseif type(petID)=="number" then -- if this a pet user doesn't own
|
||
|
|
-- petInfo.owned = false
|
||
|
|
-- petInfo.name, petInfo.icon, petInfo.petType, petInfo.creatureID, petInfo.sourceText, petInfo.description, petInfo.isWild, petInfo.canBattle, petInfo.tradable, petInfo.unique, petInfo.obtainable, petInfo.displayID = C_PetJournal.GetPetInfoBySpeciesID(petID)
|
||
|
|
-- petInfo.speciesID = petID
|
||
|
|
-- petInfo.petID, petInfo.customName, petInfo.level, petInfo.xp, petInfo.maxXp, petInfo.isFavorite = nil, nil, nil, nil, nil, nil
|
||
|
|
-- end
|
||
|
|
|
||
|
|
if not petInfo.name then
|
||
|
|
return -- if the pet no longer exists (was released)
|
||
|
|
end
|
||
|
|
|
||
|
|
if settings.HideNonBattlePets and not petInfo.canBattle then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
|
||
|
|
-- hidden pets get special treatment outside of active filters
|
||
|
|
local hidden = settings.HiddenPets
|
||
|
|
if GetFilter(self,"Other","Hidden") then
|
||
|
|
if not hidden or not hidden[petInfo.speciesID] then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
elseif hidden and hidden[petInfo.speciesID] then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
|
||
|
|
-- the first active filter to return false will fail this pet candidate
|
||
|
|
for i=1,#activeFilters do
|
||
|
|
if filterFuncs[activeFilters[i]]()==false then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- now for search stuff, offloading it to IsSearchMatch() later in this module
|
||
|
|
local mask = roster.searchMask
|
||
|
|
if mask then
|
||
|
|
local relevance = roster:RunSearchMatch(mask)
|
||
|
|
if not relevance then
|
||
|
|
return false
|
||
|
|
else
|
||
|
|
rematch:GetTempTable("SearchRelevance")[petID] = relevance
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- and finally if any stat searches (ie speed>300) then run its thing
|
||
|
|
local range = roster.searchStatRanges
|
||
|
|
if next(range) and not roster:RunStatMatch(range) then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
|
||
|
|
-- if we reached here, all filters passed, pet is ok to list
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
|
||
|
|
--[[ filterFuncs
|
||
|
|
|
||
|
|
These are the individual functions that run for each active filter group.
|
||
|
|
If they return false then the pet is to NOT be listed.
|
||
|
|
If they return nil or true then the pet is to be listed.
|
||
|
|
|
||
|
|
]]
|
||
|
|
|
||
|
|
function filterFuncs.Favorite()
|
||
|
|
return petInfo.isFavorite or false
|
||
|
|
end
|
||
|
|
|
||
|
|
function filterFuncs.Collected()
|
||
|
|
local owned = petInfo.owned
|
||
|
|
if owned and GetFilter(self,"Collected","Owned") then
|
||
|
|
return false
|
||
|
|
elseif not owned and GetFilter(self,"Collected","Missing") then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function filterFuncs.Types()
|
||
|
|
return GetFilter(self,"Types",petInfo.petType) or false
|
||
|
|
end
|
||
|
|
|
||
|
|
function filterFuncs.Rarity()
|
||
|
|
if not petInfo.owned then
|
||
|
|
return false -- pets not owned have no rarity
|
||
|
|
else
|
||
|
|
local _,_,_,_,rarity = rematch:GetPetStats(petInfo.petID)
|
||
|
|
return GetFilter(self,"Rarity",rarity) or false
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function filterFuncs.Sources()
|
||
|
|
return GetFilter(self,"Sources",roster:GetSpeciesSource(petInfo.speciesID)) or false
|
||
|
|
end
|
||
|
|
|
||
|
|
function filterFuncs.Tough()
|
||
|
|
return GetFilter(self,"Tough",rematch.hintsDefense[petInfo.petType][2]) or false
|
||
|
|
end
|
||
|
|
|
||
|
|
function filterFuncs.Level()
|
||
|
|
if petInfo.canBattle then
|
||
|
|
if GetFilter(self,"Level","Without25s") then
|
||
|
|
return not rematch.speciesAt25[petInfo.speciesID]
|
||
|
|
elseif GetFilter(self,"Level","MovesetNot25") then
|
||
|
|
return not roster:IsMovesetAt25(petInfo.speciesID)
|
||
|
|
elseif petInfo.owned then
|
||
|
|
local level = petInfo.level or 0
|
||
|
|
if GetFilter(self,"Level",1) and level>0 and level<8 then
|
||
|
|
return true
|
||
|
|
elseif GetFilter(self,"Level",2) and level>7 and level<15 then
|
||
|
|
return true
|
||
|
|
elseif GetFilter(self,"Level",3) and level>14 and level<25 then
|
||
|
|
return true
|
||
|
|
elseif GetFilter(self,"Level",4) and level==25 then
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
|
||
|
|
function filterFuncs.Other()
|
||
|
|
-- Other -> Leveling/Not Leveling
|
||
|
|
local isLeveling = rematch:IsPetLeveling(petInfo.petID)
|
||
|
|
if GetFilter(self,"Other","Leveling") and not isLeveling then
|
||
|
|
return false
|
||
|
|
elseif GetFilter(self,"Other","NotLeveling") and isLeveling then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
-- Other -> Tradable/Not Tradable
|
||
|
|
if GetFilter(self,"Other","Tradable") and not petInfo.isTradable then
|
||
|
|
return false
|
||
|
|
elseif GetFilter(self,"Other","NotTradable") and petInfo.isTradable then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
-- Other -> Can Battle/Can't Battle
|
||
|
|
if GetFilter(self,"Other","Battle") and not petInfo.canBattle then
|
||
|
|
return false
|
||
|
|
elseif GetFilter(self,"Other","NotBattle") and petInfo.canBattle then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
-- Other -> In A Team/Not In A Team
|
||
|
|
local candidate = petInfo.petID or petInfo.speciesID
|
||
|
|
if GetFilter(self,"Other","InTeam") and not roster:IsPetInTeam(candidate) then
|
||
|
|
return false
|
||
|
|
elseif GetFilter(self,"Other","NotInTeam") and roster:IsPetInTeam(candidate) then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
-- Other -> Unique Moveset (excludes non-battle pets)
|
||
|
|
if GetFilter(self,"Other","UniqueMoveset") and (not rematch.uniqueMovesets[rematch.movesetsBySpecies[petInfo.speciesID]] or not petInfo.canBattle) then
|
||
|
|
return false
|
||
|
|
elseif GetFilter(self,"Other","SharedMoveset") and (rematch.uniqueMovesets[rematch.movesetsBySpecies[petInfo.speciesID]] or not petInfo.canBattle) then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
-- Other -> Has Notes
|
||
|
|
if GetFilter(self,"Other","HasNotes") and not settings.PetNotes[petInfo.speciesID] then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
-- Other -> Current Zone
|
||
|
|
if GetFilter(self,"Other","CurrentZone") then
|
||
|
|
local source = petInfo.sourceText
|
||
|
|
if not source or not rematch:match(source,roster.currentZone) then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
end
|
||
|
|
-- Other -> One Copy/Two+ Copies/Three+ Copies
|
||
|
|
local qty = GetFilter(self,"Other","Qty1") and 1 or GetFilter(self,"Other","Qty2") and 2 or GetFilter(self,"Other","Qty3") and 3
|
||
|
|
if qty then
|
||
|
|
local count = C_PetJournal.GetNumCollectedInfo(petInfo.speciesID)
|
||
|
|
if not count then
|
||
|
|
return false
|
||
|
|
elseif qty==3 and count<3 then
|
||
|
|
return false
|
||
|
|
elseif qty==2 and count<2 then
|
||
|
|
return false
|
||
|
|
elseif qty==1 and count~=1 then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function filterFuncs.Breed()
|
||
|
|
return petInfo.owned and GetFilter(self,"Breed",petInfo.breedID) or false
|
||
|
|
end
|
||
|
|
|
||
|
|
function filterFuncs.Strong()
|
||
|
|
-- return roster:IsStrong(petInfo.speciesID) or false
|
||
|
|
|
||
|
|
if roster:IsStrong(petInfo.speciesID) then -- the species is strong vs the chosen filters
|
||
|
|
|
||
|
|
-- if 'Use Level In Strong Vs Filter' is checked, strong abilities need to meet level requirement too
|
||
|
|
if settings.StrongVsLevel then
|
||
|
|
if not petInfo.level then
|
||
|
|
return false -- unowned pets automatically fail
|
||
|
|
elseif petInfo.level<20 then -- if pet is under 20, it can potentially fail level requirement
|
||
|
|
-- this repeats some work from roster:IsStrong(speciesID) to confirm level (can't use cache for individual pets)
|
||
|
|
-- fortunately the impact as not huge since the species cache limits how often this part runs
|
||
|
|
local abilities,levels = rematch:GetAbilities(petInfo.speciesID)
|
||
|
|
local lookup = rematch.info
|
||
|
|
wipe(lookup)
|
||
|
|
-- fill lookup table with abilityType of attacks this species has
|
||
|
|
for index,abilityID in ipairs(abilities) do
|
||
|
|
local _,_,_,_,_,_,abilityType,noHints = C_PetBattles.GetAbilityInfoByID(abilityID)
|
||
|
|
if not noHints and petInfo.level>=levels[index] 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
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- if we reached here, either pet met level requirements or there were no level requirements
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
-- if we reached here, species wasn't strong vs the chosen filters
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
|
||
|
|
function filterFuncs.Similar()
|
||
|
|
local abilities = rematch:GetAbilities(petInfo.speciesID)
|
||
|
|
local matches = 0
|
||
|
|
for i=1,#abilities do
|
||
|
|
local abilityID = abilities[i]
|
||
|
|
if GetFilter(self,"Similar",abilityID) then
|
||
|
|
matches = matches + 1
|
||
|
|
end
|
||
|
|
-- if pet has at least 3 of the abilities in the Similar filter, return true (list pet)
|
||
|
|
if matches>2 then
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
-- if we reached here, pet didn't have 3 abilities in the Similar filter, return false (don't list)
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
|
||
|
|
function filterFuncs.Moveset()
|
||
|
|
-- the 1 is the index into the settings.Filters.Moveset table (all filter groups are tables,
|
||
|
|
-- and this filter only has one value to store which is stored in [1] index)
|
||
|
|
return rematch.movesetsBySpecies[petInfo.speciesID]==GetFilter(self,"Moveset",1)
|
||
|
|
end
|
||
|
|
|
||
|
|
function filterFuncs.Script()
|
||
|
|
local abilityList, levelList = rematch:GetAbilities(petInfo.speciesID)
|
||
|
|
-- TODO: use petInfo for scripts instead of ridiculous number of args?
|
||
|
|
if not rematch:RunScriptFilter(petInfo.owned, petInfo.petID, petInfo.speciesID, petInfo.customName, petInfo.level, petInfo.xp, petInfo.maxXp, petInfo.displayID, petInfo.isFavorite, petInfo.name, petInfo.icon, petInfo.petType, petInfo.creatureID, petInfo.sourceText, petInfo.description, petInfo.isWild, petInfo.canBattle, petInfo.isTradable, petInfo.isUnique, petInfo.isObtainable, abilityList, levelList) then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function filterFuncs.Expansion()
|
||
|
|
local expansionID = petInfo.expansionID
|
||
|
|
return GetFilter(self,"Expansion",expansionID) or false
|
||
|
|
end
|
||
|
|
|
||
|
|
--[[ Searches ]]
|
||
|
|
|
||
|
|
-- returns relevance if the pet defined in petInfo matches the passed search mask.
|
||
|
|
-- the desensitized mask is matched to the name, source and notes of a pet,
|
||
|
|
-- and then the names and description of its abilities.
|
||
|
|
-- due to the weighty nature of this search, two caches via tempTables are used
|
||
|
|
-- to dramatically speed up processing.
|
||
|
|
|
||
|
|
-- relevance: 1=custom name exact match; 2=custom name match; 3=species name exact match;
|
||
|
|
-- 4=species name match; 5=notes exact match; 6=notes match; 7=ability exact match;
|
||
|
|
-- 8=ability match; 9=source exact match; 10=source match
|
||
|
|
|
||
|
|
local speciesCache -- will be the SearchSpecies TempTable
|
||
|
|
|
||
|
|
-- for use in RunSearchMatch; does a match of mask to candidate and returns the given
|
||
|
|
-- relevanceStart if there's an exact match and the relevanceStart+1 if just a regular
|
||
|
|
-- match and nil if there is no match
|
||
|
|
local function matchRelevance(speciesID,mask,candidate,relevanceStart)
|
||
|
|
if candidate and rematch:match(candidate,mask) then
|
||
|
|
local relevance = candidate:match("^"..mask.."$") and relevanceStart or relevanceStart+1
|
||
|
|
speciesCache[speciesID] = relevance
|
||
|
|
return relevance
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function roster:RunSearchMatch(mask)
|
||
|
|
|
||
|
|
-- search for custom name first since that's potentially unique among all pets
|
||
|
|
-- customName match has a relevance of 1/2
|
||
|
|
local customName = petInfo.customName
|
||
|
|
if customName and rematch:match(customName,mask) then
|
||
|
|
return customName:match("^"..mask.."$") and 1 or 2
|
||
|
|
end
|
||
|
|
|
||
|
|
-- the rest of the searches have the same result across the species, so we're going
|
||
|
|
-- to use a cache for the species to speed up processing.
|
||
|
|
local speciesID = petInfo.speciesID
|
||
|
|
speciesCache = rematch:GetTempTable("SearchSpecies")
|
||
|
|
|
||
|
|
-- check if we examined this speciesID before and return earlier result if so
|
||
|
|
if speciesCache[speciesID]~=nil then
|
||
|
|
return speciesCache[speciesID]
|
||
|
|
end
|
||
|
|
|
||
|
|
local relevance
|
||
|
|
|
||
|
|
-- match species name; relevance 3/4
|
||
|
|
relevance = matchRelevance(speciesID,mask,petInfo.speciesName,3)
|
||
|
|
if relevance then
|
||
|
|
return relevance
|
||
|
|
end
|
||
|
|
|
||
|
|
-- match notes; relevance 5/6
|
||
|
|
relevance = matchRelevance(speciesID,mask,settings.PetNotes[speciesID],5)
|
||
|
|
if relevance then
|
||
|
|
return relevance
|
||
|
|
end
|
||
|
|
|
||
|
|
-- if no hits yet, look through abilities, which has another cache since many
|
||
|
|
-- abilities are shared across many pets.
|
||
|
|
local abilityCache = rematch:GetTempTable("SearchAbilities")
|
||
|
|
local abilities = rematch:GetAbilities(speciesID)
|
||
|
|
for i=1,#abilities do
|
||
|
|
local abilityID = abilities[i]
|
||
|
|
-- first see if we've cached a result for this ability
|
||
|
|
local cached = abilityCache[abilityID]
|
||
|
|
if cached~=nil then -- if we've examined this ability already
|
||
|
|
if cached then -- and it was a successful match
|
||
|
|
speciesCache[speciesID] = cached -- mark species as a success too
|
||
|
|
return cached
|
||
|
|
end
|
||
|
|
-- if cached is false, we know this ability doesn't match the mask;
|
||
|
|
-- but another ability may return a hit. just ignore this ability.
|
||
|
|
elseif not cached and abilityID then -- we haven't examined this ability yet
|
||
|
|
local _,name,_,_,description = C_PetBattles.GetAbilityInfoByID(abilityID)
|
||
|
|
relevance = matchRelevance(speciesID,mask,name,7) or matchRelevance(speciesID,mask,description,7)
|
||
|
|
if relevance then
|
||
|
|
abilityCache[abilityID] = relevance
|
||
|
|
return relevance
|
||
|
|
else
|
||
|
|
abilityCache[abilityID] = false -- no match, cache its result
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- match sourceText; relevance 9/10
|
||
|
|
relevance = matchRelevance(speciesID,mask,petInfo.sourceText,9)
|
||
|
|
if relevance then
|
||
|
|
return relevance
|
||
|
|
end
|
||
|
|
|
||
|
|
-- if we've reached here, there was no match :( cache it as such and leave
|
||
|
|
speciesCache[speciesID] = false
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
|
||
|
|
-- roster.searchStatRanges (passed as ranges) has the limits of stats in a small table
|
||
|
|
-- this returns true if the petInfo pet meets all the criterea of those ranges
|
||
|
|
function roster:RunStatMatch(ranges)
|
||
|
|
if not petInfo.owned or not petInfo.canBattle then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
local _,maxHealth,power,speed = rematch:GetPetStats(petInfo.petID)
|
||
|
|
for stat,values in pairs(ranges) do
|
||
|
|
local liveStat = stat=="Level" and petInfo.level or stat=="Health" and maxHealth or stat=="Power" and power or stat=="Speed" and speed
|
||
|
|
if liveStat<values[1] or liveStat>values[2] then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
|
||
|
|
-- returns the sort stat of the current petInfo pet, depending on settings.Sort.Order
|
||
|
|
-- sort orders: 1=name, 2=level, 3=rarity, 4=type, 5=health, 6=power, 7=speed
|
||
|
|
function roster:GetSortStat()
|
||
|
|
local sortOrder = settings.Sort.Order
|
||
|
|
local owned = petInfo.owned
|
||
|
|
local sortStat,_
|
||
|
|
-- for rarity, health, power or speed, we need to fetch those stats if they haven't been fetched
|
||
|
|
if (sortOrder==3 or sortOrder>4) then
|
||
|
|
if owned then
|
||
|
|
_, petInfo.maxHealth, petInfo.power, petInfo.speed, petInfo.rarity = rematch:GetPetStats(petInfo.petID)
|
||
|
|
else -- for missing pets, health, power, speed and rarity are nil
|
||
|
|
petInfo.maxHealth, petInfo.power, petInfo.speed, petInfo.rarity = nil, nil, nil, nil
|
||
|
|
petInfo.maxHealth, petInfo.power, petInfo.speed, petInfo.rarity = 0, 0, 0, 0
|
||
|
|
end
|
||
|
|
end
|
||
|
|
if owned then -- for owned pets
|
||
|
|
if sortOrder==1 and sortByNickname then -- sortStat is customName if SortByNickname enabled
|
||
|
|
return petInfo.customName or petInfo.speciesName -- returning customName or real name early
|
||
|
|
end
|
||
|
|
elseif sortOrder==2 then -- for missing pets when level is the sort order
|
||
|
|
petInfo.level = 0 -- give it a fixed value so the sort is stable
|
||
|
|
end
|
||
|
|
return petInfo[sortReference[sortOrder]] -- and finally return the appropriate sort stat
|
||
|
|
end
|
||
|
|
|
||
|
|
|
||
|
|
-- table.sort function to sort pets so:
|
||
|
|
-- owned (petID instead of speciesID) always list first
|
||
|
|
-- then favorites list before non-favorites (if FavoritesFirst true)
|
||
|
|
-- then by sort stat filled during filter
|
||
|
|
-- then by name pulled on demand (compromise between memory vs speed)
|
||
|
|
-- and lastly by the raw petID so order remains fixed
|
||
|
|
function roster.SortPets(pet1,pet2)
|
||
|
|
|
||
|
|
-- first sort by the existence of a pet
|
||
|
|
if pet1 and not pet2 then return true
|
||
|
|
elseif pet2 and not pet1 then return false
|
||
|
|
elseif not pet1 and not pet2 then return false
|
||
|
|
end
|
||
|
|
|
||
|
|
local o1,o2 = type(pet1)=="string", type(pet2)=="string" -- owned pets always list first
|
||
|
|
if o1 and not o2 then return true
|
||
|
|
elseif o2 and not o1 then return false
|
||
|
|
end
|
||
|
|
|
||
|
|
-- relevance only matters if there's a search happening
|
||
|
|
if roster.searchMask and not settings.DontSortByRelevance then
|
||
|
|
local r1,r2 = sortRelevanceTable[pet1], sortRelevanceTable[pet2] -- relevance
|
||
|
|
if r1 and not r2 then return true -- relevant pets first
|
||
|
|
elseif r2 and not r1 then return false
|
||
|
|
elseif r1~=r2 then
|
||
|
|
return r1<r2
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
local f1,f2 = sortFavoritesTable[pet1], sortFavoritesTable[pet2] -- favorites
|
||
|
|
if f1 and not f2 then return true -- f1/f2 only true if FavoritesFirst true
|
||
|
|
elseif f2 and not f1 then return false
|
||
|
|
end
|
||
|
|
|
||
|
|
local s1,s2 = sortStatsTable[pet1], sortStatsTable[pet2] -- chosen sort stat existence
|
||
|
|
if s1 and not s2 then return true
|
||
|
|
elseif s2 and not s1 then return false
|
||
|
|
elseif not s1 and not s2 then return false
|
||
|
|
end
|
||
|
|
|
||
|
|
-- both pets have a sort stat here; sort them depending on sortLowToHigh
|
||
|
|
if sortLowToHigh then
|
||
|
|
if s1<s2 then return true
|
||
|
|
elseif s2<s1 then return false
|
||
|
|
end
|
||
|
|
else -- level, rarity, health, power and speed sort in descending value (25-1)
|
||
|
|
if s1>s2 then return true
|
||
|
|
elseif s2>s1 then return false
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- if we're still here, the two pets have the same sort stat
|
||
|
|
|
||
|
|
-- gather name and level of the two pets
|
||
|
|
local n1,n2,l1,l2,c1,c2,_
|
||
|
|
if o1 then -- these are owned pets
|
||
|
|
_,c1,l1,_,_,_,_,n1 = C_PetJournal.GetPetInfoByPetID(pet1)
|
||
|
|
_,c2,l2,_,_,_,_,n2 = C_PetJournal.GetPetInfoByPetID(pet2)
|
||
|
|
else -- unowned pets (0 level)
|
||
|
|
l1,n1 = 0,C_PetJournal.GetPetInfoBySpeciesID(pet1)
|
||
|
|
l2,n2 = 0,C_PetJournal.GetPetInfoBySpeciesID(pet2)
|
||
|
|
end
|
||
|
|
|
||
|
|
-- if SortByNickname enabled, use customName instead of actual name
|
||
|
|
if sortByNickname then
|
||
|
|
n1 = c1 or n1
|
||
|
|
n2 = c2 or n2
|
||
|
|
end
|
||
|
|
|
||
|
|
-- if sorting by rarity, then do a secondary sort by level
|
||
|
|
if settings.Sort.Order==3 then
|
||
|
|
if l1~=l2 then
|
||
|
|
return l1>l2 -- always descending level
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- sort stats are identical, sort by name first
|
||
|
|
|
||
|
|
-- final sort: by name (or by petID if name is identical)
|
||
|
|
if n1==n2 then
|
||
|
|
return pet1<pet2 -- names identical; sort by petID so sort order remains fixed
|
||
|
|
else
|
||
|
|
return n1<n2 -- names are different; sort by name
|
||
|
|
end
|
||
|
|
end
|