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.

261 lines
11 KiB

local _,rematch = ...
local L = rematch.localization
local C = rematch.constants
local settings = rematch.settings
rematch.sort = {}
--[[
RunSort(list) takes a list of petIDs (which can include speciesIDs) and sorts them in the orders
defined in settings.Filters.Sort.
When settings.Filters.Sort is empty, then it will use all defaults sorts, which would be
(if they had values):
Three tiers of sort are:
settings.Filters.Sort[1] = C.SORT_NAME
settings.Filters.Sort[2] = C.SORT_LEVEL
settings.Filters.Sort[3] = C.SORT_RARITY
Each tier can be reversed by having a true value in its negative tier:
settings.Filters.Sort[-1] = false (don't reverse name sort)
settings.Filters.Sort[-2] = false (don't reverse level sort)
settings.Filters.Sort[-3] = false (don't reverse rarity sort)
And extra settings:
settings.Filters.Sort.FavoritesNotFirst = false (list favorites first)
When a sort is applied, the list is sorted in this order:
1. Owned first (BattlePet-0-etc strings before speciesID numbers)
2. Favorites first (if settings.FavoritesNotFirst is false)
2. Relevance if search is used
3. Primary Sort
4. Secondary Sort
5. Tertiary Sort
6. Name sort (if C.SORT_NAME not in primary-tertiary sort)
7. petID
C.SORT_NAME and C.SORT_TYPE default to ascending sort values, and the rest default to sort by
descending values. The table ascendingSorts (set up during PrepareSort()) will be true for ascending
tiers, and false for descending; and will be the opposite value if a sort tier is reversed.
For faster sorts, as each pet is evaluated, ones to list run AddSortStats(petID) and it will
populate sortValues with the value for the three tiers (+name if unused in the three tiers).
Each subtable of sortValues is a lookup table indexed by petID and the value of the stat for that tier.
]]
-- the current sort orders, taken from settings.Filters.Sort
local activeSorts = {C.SORT_NAME,C.SORT_LEVEL,C.SORT_RARITY}
-- whether the sorts are ascending (true) or descending (false)
local ascendingSorts = {true,true,true}
-- which stats to put in the sortValues subtables (named sort will be speciesName unless SortByNickname enabled, when it'd be name)
local petInfoSorts = {"speciesName","level","rarity"}
-- lookup table of petIDs that are favorited (if settings.FavoritesNotFirst is false), filled during AddSortValues()
local favorites = {}
local listFavoritesFirst = false -- defined in PrepareSort, true if settings.Filters.Sort.FavoritesNotFirst is false
-- lookup tables of pets and their relevance when a search is involved, filled during AddSortValues()
local searchRelevance -- this is created during filter evaluation and is a cache indexed by speciesID (except customName relevances index by petID)
local sortRelevance = {} -- this is filled during AddSortValues() and indexed by a petID/speciesID for all pets
-- lookup tables of sort values, with index 1-3 being the primary, secondary and teriary sort values
-- and index 4 for names if it's not used in the other
local sortValues = { {},{},{},{} }
-- each sort type directly relates to a petInfo value. (to add sorts in the future, they need to be a petInfo value)
local petInfoStats = {
[C.SORT_NAME] = "speciesName",
[C.SORT_LEVEL] = "level",
[C.SORT_RARITY] = "rarity",
[C.SORT_TYPE] = "petType",
[C.SORT_HEALTH] = "maxHealth",
[C.SORT_POWER] = "power",
[C.SORT_SPEED] = "speed",
[C.SORT_TEAMS] = "numTeams",
}
local stickiedPetIDs = {} -- indexed by petID, these pets should be moved to the top of the list until rematch is closed
local stickiedStaging = {} -- for a quick stable sort, used as a temp space for swapping
-- call this before starting a filter run. it does pre-sort maintenance, such as wiping tables and filling actualSorts,
-- ascendingSorts and petInfoSorts depending on current settings. It needs to be before a filter run so that
-- AddSortValues() knows which sort values (name, level, rarity, etc.) to keep as pets are evaluated.
function rematch.sort:PrepareSort()
-- first clean up the lookup tables
for _,v in ipairs(sortValues) do
wipe(v)
end
wipe(favorites)
wipe(sortRelevance)
local nameSorted = false -- assume no sort by name is happening until observed otherwise
-- go through each of the three sort levels to set up activeSorts and ascendingSorts
for sortLevel=1,3 do
local sort = rematch.filters:GetSort(sortLevel)
activeSorts[sortLevel] = sort
-- choose whether it's ascending or descending based on the type of sort and the reverse filter setting
if sort==C.SORT_NAME then -- name sorts happen in ascending order by default
nameSorted = true -- note that a sort by name is happening (so don't need a 4th sort)
ascendingSorts[sortLevel] = not settings.Filters.Sort[-sortLevel]
elseif sort==C.SORT_TYPE then
ascendingSorts[sortLevel] = not settings.Filters.Sort[-sortLevel]
else -- all other sorts default to descending, so reverse would be ascending
ascendingSorts[sortLevel] = settings.Filters.Sort[-sortLevel] or false
end
-- fill petInfoSorts with the petInfo stat to use for the sort
if sort==C.SORT_NAME and settings.SortByNickname then
petInfoSorts[sortLevel] = "name"
else
petInfoSorts[sortLevel] = petInfoStats[sort]
end
end
-- if name is not one of the 3 sorts, then add it as a 4th sort
if not nameSorted then
activeSorts[4] = C.SORT_NAME
ascendingSorts[4] = true
petInfoSorts[4] = settings.SortByNickname and "name" or "speciesName"
else
activeSorts[4] = nil
ascendingSorts[4] = nil
petInfoSorts[4] = nil
end
-- create local flag to avoid doing table indexing
listFavoritesFirst = not settings.Filters.Sort.FavoritesNotFirst
-- if search is happening, keep a local reference for quick lookup
searchRelevance = not settings.DontSortByRelevance and rematch.filters:GetSearchRelevance()
end
-- called after a filter run, wipes the sort cache tables
function rematch.sort:Cleanup()
for _,v in ipairs(sortValues) do
wipe(v)
end
end
-- fills sortValues with the 3 values (4 if name needed) of the given petID; called as pets are evaluated in rematch.filter:RunFilters()
function rematch.sort:AddSortValues(petID)
local petInfo = rematch.petInfo:Fetch(petID)
for sortLevel=1,#activeSorts do
sortValues[sortLevel][petID] = petInfo[petInfoSorts[sortLevel]]
end
-- if favorites list first, note which are favorites
if listFavoritesFirst and petInfo.isFavorite then
favorites[petID] = true
end
-- this searchRelevance is largely indexed by speciesID and used as a cache during search filter (except renamed pets use customName and
-- have a petID index). here each pet in the list is getting assigned a relevance, either from its petID or speciesID, for sortRelevance
if searchRelevance then
local relevance = searchRelevance[petID] or searchRelevance[petInfo.speciesID]
if relevance then
sortRelevance[petID] = relevance
end
end
end
-- sorts the given list according to the current sort settings and sort values accumulated during RunFilters()
function rematch.sort:RunSort(list)
table.sort(list,rematch.sort.SortFunc)
-- once an epoch, a wrapped pet should be moved to top temporarily (until rematch closes or filter reset all)
if C_PetJournal.GetNumPetsNeedingFanfare()>0 then
for _,petID in ipairs(list) do
if rematch.petInfo:Fetch(petID).needsFanfare then
rematch.sort:AddStickiedPetID(petID)
end
end
end
if stickiedPetIDs then
rematch.sort:MoveStickiedPetsToTop(list)
end
end
-- if any petIDs are stickied, moves them to top of list with a simple stable sort (assumption: list is already sorted)
function rematch.sort:MoveStickiedPetsToTop(list)
if #list<=1 then
return -- only have 0-1 result, don't bother to sort
end
wipe(stickiedStaging)
-- add stickied pets to the staging list in the order they appear in regular list; and remove from regular list
for i=#list,1,-1 do
if stickiedPetIDs[list[i]] then
tinsert(stickiedStaging,list[i])
tremove(list,i)
end
end
-- now insert stickied pets back to start of regular list
for _,petID in ipairs(stickiedStaging) do
tinsert(list,1,petID)
end
end
-- call when stickied pets should be unstickied (rematch window closes or filter reset all); return true if something was stickied
function rematch.sort:ClearStickiedPetIDs()
local wasStickied = type(stickiedPetIDs)=="table"
stickiedPetIDs = nil
return wasStickied
end
-- adds a petID to be stickied (called in roster too for new pets)
function rematch.sort:AddStickiedPetID(petID)
if not stickiedPetIDs then
stickiedPetIDs = {}
end
stickiedPetIDs[petID] = true
end
function rematch.sort:IsPetIDStickied(petID)
return (stickiedPetIDs and petID and stickiedPetIDs[petID]) and true or false
end
-- When a sort is applied, the list is sorted in this order:
-- 1. Owned first (BattlePet-0-etc strings before speciesID numbers)
-- 2. Favorites first (if settings.Filters.Sort.FavoritesNotFirst is false)
-- 2. Relevance if search is used
-- 3. Primary Sort
-- 4. Secondary Sort
-- 5. Tertiary Sort
-- 6. Name sort (if C.SORT_NAME not in primary-tertiary sort)
-- 7. petID
function rematch.sort.SortFunc(pet1,pet2)
-- always sort owned pets (strings) before uncollected (number) pets
local o1,o2 = type(pet1)=="string", type(pet2)=="string"
if o1 and not o2 then
return true
elseif o2 and not o1 then
return false
end
-- if a search is happening sort by relevance first
if searchRelevance then
local r1,r2 = sortRelevance[pet1],sortRelevance[pet2]
if r1 and not r2 then return true
elseif r2 and not r1 then return false
elseif r1~=r2 then
return r1<r2
end
end
-- if 'Favorites First' is checked then always list favorites first
if listFavoritesFirst then
local f1,f2 = favorites[pet1],favorites[pet2]
if f1 and not f2 then return true
elseif f2 and not f1 then return false
end
end
-- next do the primary, secondary, tertiary (and if needed, name) sort
for sortLevel=1,#activeSorts do
local s1,s2 = sortValues[sortLevel][pet1],sortValues[sortLevel][pet2]
if s1 and not s2 then return true
elseif s2 and not s1 then return false
elseif s1~=s2 then
if ascendingSorts[sortLevel] then
return s1<s2
else
return s1>s2
end
end
end
-- if we reached here, these two pets are identical, order them by petID so the order is stable
return pet1<pet2
end