-- Sort lib local _, app = ...; -- Concepts: -- Encapsulates the functionality for performing sort logic against sets of ATT groups -- Global locals local ipairs, tostring, type, table_sort, pcall = ipairs, tostring, type, table.sort, pcall; -- Module locals -- Sorting Logic local sortA, sortB; local function calculateSourceQuestDepth(group, text) if group.sourceQuestDepth then return group.sourceQuestDepth; end if group.sourceQuests then local maxDepth, results, depth = 0, nil, nil; for i,sourceQuestID in ipairs(group.sourceQuests) do results = app.SearchForField("questID", sourceQuestID); if results and #results > 0 then depth = calculateSourceQuestDepth(results[1]) + 1; else depth = 1; end if maxDepth < depth then maxDepth = depth; end end group.sourceQuestDepth = maxDepth; return maxDepth; end return 0; end local function toLowerString(value) ---@diagnostic disable-next-line: undefined-field return tostring(value):lower(); end local function calculateAccessibility(source) return source.AccessibilityScore or 10000000; end local function defaultComparison(a,b) -- If either object doesn't exist if a then if not b then return true; end elseif b then return false; else -- neither a or b exists, equality returns false return false; end -- If comparing non-tables if type(a) ~= "table" or type(b) ~= "table" then return a < b; end local acomp, bcomp; -- Maps acomp = a.mapID; bcomp = b.mapID; if acomp then if not bcomp then return true; end elseif bcomp then return false; end -- Raids/Encounter acomp = a.isRaid; bcomp = b.isRaid; if acomp then if not bcomp then return true; end elseif bcomp then return false; end -- Headers/Filters/AchievementCategories (or other Types which are used as Headers) acomp = a.headerID or a.filterID or a.achievementCategoryID or a.isHeader bcomp = b.headerID or b.filterID or b.achievementCategoryID or b.isHeader if acomp then if not bcomp then return true; end elseif bcomp then return false; end -- Quests acomp = a.questID; bcomp = b.questID; if acomp then if not bcomp then return true; end elseif bcomp then return false; end -- Items acomp = a.itemID; bcomp = b.itemID; if acomp then if not bcomp then return true; end elseif bcomp then return false; end -- Any two similar-type groups via name acomp = toLowerString(a.name); bcomp = toLowerString(b.name); return acomp < bcomp; end local function GetGroupSortValue(group) -- sub-groups on top -- >= 1 if group.g then local total = group.total; if total then local progress = group.progress; -- completed groups at the very top, ordered by their own total if total == progress then -- 3 <= p return 2 + total; -- partially completed next elseif progress and progress > 0 then -- 1 < p <= 2 return 1 + (progress / total); -- no completion, ordered by their own total in reverse -- 0 < p <= 1 else return (1 / total); end end -- collectibles next -- >= 0 elseif group.collectible then -- = 0.5 if group.collected then return 0.5; else -- 0 <= p < 0.5 return (group.sortProgress or 0) / 2; end -- trackables next -- -1 <= p <= -0.5 elseif group.trackable then if group.saved then return -0.5; else return -1; end -- remaining last -- = -2 else return -2; end end local function stringComparison(a,b) -- If either object doesn't exist if a then if not b then return true; end elseif b then return false; else -- neither a or b exists, equality returns false return false; end -- Any two similar-type groups with text a = toLowerString(a); b = toLowerString(b); return a < b; end app.SortDefaults = setmetatable({ -- Naming Convention: Capital = Special Sort Function, lowercase = field on an object Global = defaultComparison, Strings = stringComparison, Values = function(a,b) -- If either object doesn't exist if a then if not b then return true; end elseif b then return false; else -- neither a or b exists, equality returns false return false; end return a < b; end, -- Sorts objects first by whether they do not have sub-groups [.g] defined Hierarchy = function(a,b) -- If either object doesn't exist if a then if not b then return true; end elseif b then return false; else -- neither a or b exists, equality returns false return false; end local acomp, bcomp; acomp = a.g bcomp = b.g return (acomp and #acomp or 0) < (bcomp and #bcomp or 0); end, -- Sorts objects first by how many total collectibles they contain Total = function(a,b) -- If either object doesn't exist if a then if not b then return true; end elseif b then return false; else -- neither a or b exists, equality returns false return false; end local acomp, bcomp; acomp = a.total or 0; bcomp = b.total or 0; return acomp < bcomp; end, -- Sorts objects first by their nextEvent.Start EventStart = function(a,b) -- If either object doesn't exist if a then if not b then return true; end elseif b then return false; else -- neither a or b exists, equality returns false return false; end local acomp, bcomp; acomp = a.nextEvent; acomp = acomp and acomp.start or 0; bcomp = b.nextEvent; bcomp = bcomp and bcomp.start or 0; return acomp < bcomp; end, ClassicQuestOrder = function(a,b) -- If either object doesn't exist if a then if not b then return true; end elseif b then return false; else -- neither a or b exists, equality returns false return false; end if a.isRaid then if not b.isRaid then return true; end elseif b.isRaid then return false; end if a.isBreadcrumb then if not b.isBreadcrumb then return true; end elseif b.isBreadcrumb then return false; end -- Any two similar-type groups with text sortA = toLowerString(a.text); sortB = toLowerString(b.text); if sortA == sortB and sortA then return calculateSourceQuestDepth(a, sortA) < calculateSourceQuestDepth(b, sortB); end return sortA < sortB; end, Accessibility = function(a, b) return calculateAccessibility(a) < calculateAccessibility(b); end, name = function(a,b) -- If either object doesn't exist if a then if not b then return true; end elseif b then return false; else -- neither a or b exists, equality returns false return false; end -- Any two similar-type groups with text a = toLowerString(a.name); b = toLowerString(b.name); return a < b; end, text = function(a, b) -- If either object doesn't exist if a then if not b then return true; end elseif b then return false; else -- neither a or b exists, equality returns false return false; end -- Any two similar-type groups with text a = toLowerString(a.text); b = toLowerString(b.text); return a < b; end, textAndLvl = function(a, b) -- If either object doesn't exist if a then if not b then return true; end elseif b then return false; else -- neither a or b exists, equality returns false return false; end sortA = a.lvl or 0; sortB = b.lvl or 0; if sortA < sortB then return false; elseif sortA == sortB then -- Any two similar-type groups with text a = toLowerString(a.name or a.text); b = toLowerString(b.name or b.text); return a < b; else return true; end end, progress = function(a, b) return GetGroupSortValue(a) > GetGroupSortValue(b); end, IndexOneStrings = function(a,b) return stringComparison(a[1], b[1]); end, }, { __index = function(t, sortType) if type(sortType) == "function" then return sortType; end local method = function(a, b) -- If either object doesn't exist if a then if not b then return true; end elseif b then return false; else -- neither a or b exists, equality returns false return false; end return a[sortType] < b[sortType]; end; rawset(t, sortType, method); return method; end, }); local function Sort(t, compare, nested) if t then table_sort(t, compare); if nested then for i=#t,1,-1 do Sort(t[i].g, compare, nested); end end end end -- Safely-sorts a table using a provided comparison function and whether to propogate to nested groups -- Wrapping in a pcall since sometimes the sorted values are able to change while being within the sort method. This causes the 'invalid sort order function' error app.Sort = function(t, compare, nested) return pcall(Sort, t, compare or defaultComparison, nested); end -- Sorts a group using the provided sortType, whether to recurse through nested groups, and whether sorting should only take place given the group having a conditional field local function SortGroup(group, sortType) -- app.PrintDebug("SortGroup", group.parent and group.parent.text, group.text, sortType); if group.g then -- either sort visible groups or by conditional if group.visible then -- app.PrintDebug("sorting",group.hash,"by",sortType) local status,err = app.Sort(group.g, app.SortDefaults[sortType]); if status then -- Setting this to false instead of nil causes the field to also -- ignore inherited settings, such as from its base class. if group.SortType and not group.PersistSortType then group.SortType = false; end else -- Uncomment this to debug errors in your sort functions -- app.PrintDebug("Error in sort", err); end end end end app.SortGroup = SortGroup;