-- this module handles all of the queue mechanics (QueuePanel handles the UI side) -- this is one of the few modules that needs to run even when the panels aren't up local _,L = ... local rematch = Rematch local settings local queue -- the actual leveling queue (settings.LevelingQueue) local levelingPets = {} -- indexed by petID, lookup table of leveling pets (created in ProcessQueue) rematch.topPicks = {} -- list of petIDs in the order they should be slotted rematch.skippedPicks = {} -- list of petIDs skipped due to preferences (for queue panel update) local alwaysSkip = {} -- lookup table of speciesIDs to always skip in the queue (horde/alliance pets) rematch:InitModule(function() settings = RematchSettings queue = settings.LevelingQueue -- hard-coding pets that can't be slotted or fight in wrong faction if UnitFactionGroup("player")=="Alliance" then alwaysSkip[2444] = true -- Lil' War Machine can't battle while Alliance alwaysSkip[298] = true -- Horde version of Moonkin Hatchling can't slot while Alliance else alwaysSkip[2443] = true -- Lil' Siege Tower can't battle while Horde alwaysSkip[296] = true -- Alliance version of Moonkin Hatchling can't slot while Horde end end) -- returns true (and the precise level) of a petID if it can level function rematch:PetCanLevel(petID) local petInfo = rematch.petInfo:Fetch(petID) if petInfo.canLevel then return true,petInfo.level+(petInfo.xp/petInfo.maxXp) end -- if rematch:GetIDType(petID)=="pet" then -- local _,_,level,xp,maxXp,_,_,_,_,_,_,_,_,_,canBattle = C_PetJournal.GetPetInfoByPetID(petID) -- if level and level<25 and canBattle then -- return true,level+(xp/maxXp) -- end -- end end function rematch:IsPetLeveling(petID) return levelingPets[petID] and true end -- direction is -2 for top, -1 for up, 1 for down, 2 for bottom function rematch:MovePetInQueue(petID,direction) if direction==-2 then rematch:InsertPetToQueue(1,petID) elseif direction==2 then rematch:InsertPetToQueue(#queue+1,petID) else -- we're moving up or down one position local index -- first find its position for i=1,#queue do if queue[i]==petID then index = i break end end if direction==-1 and index>1 then rematch:InsertPetToQueue(index-1,petID) elseif direction==1 and index<#queue then rematch:InsertPetToQueue(index+2,petID) end end rematch:ShowQueue(petID) end function rematch:UpdateQueue() rematch.queueNeedsProcessed = true rematch:UpdateRoster() rematch:UpdateUI() end -- as a part of UpdateUI, this is where the queue updates: it removes any pets in the queue -- that can't level (due to hitting 25 or no longer existing), it slots the most -- preferred pet to any active leveling slots, and toasts when the leveling pet changes -- NOTE: all places that used to call ProcessQueue directly should now call UpdateQueue!! local firstProcessQueueTimeout = 5 -- when this has a value, then the queue is still waiting for first run function rematch:ProcessQueue() if not rematch.inWorld then return -- not logged in or in a loading screen, don't do anything end -- if pets not loaded, come back in half a second to try again local numPets,owned = C_PetJournal.GetNumPets() local petLoaded = C_PetJournal.GetPetLoadOutInfo(1) if (owned==0 or not petLoaded) and firstProcessQueueTimeout then -- special case: if pets in journal loaded but no pets slotted, maybe all slots are empty. hard to say. -- check back every couple seconds until timeout reached; this use case is only going to happen for new -- players who haven't slotted a pet and edge cases like server transfers where slots are emptied if owned > 0 and not petLoaded then C_Timer.After(2,function() if firstProcessQueueTimeout and firstProcessQueueTimeout > 0 then firstProcessQueueTimeout = firstProcessQueueTimeout - 1 rematch:ProcessQueue() end end) end if firstProcessQueueTimeout > 0 then return -- if pets aren't loaded and timeout hasn't run out, then don't do anything yet end end -- first time getting this far, confirm pets in the queue are valid (and replace with sanctuary where possible if not) if firstProcessQueueTimeout then rematch:ValidateQueue() end rematch.queueNeedsProcessed = nil firstProcessQueueTimeout = nil local oldTopPetID = rematch.topPicks[1] -- note top-most pet in queue -- remove any pets that can't level (missing or level 25) and fill levelingPets with those that can wipe(levelingPets) for i=#queue,1,-1 do local petID = queue[i] local canLevel,level = rematch:PetCanLevel(petID) if canLevel and not levelingPets[petID] then levelingPets[petID] = level else tremove(queue,i) -- remove pets that can't level (or that are already in queue) end end -- sort leveling pets if active sort enabled if settings.QueueActiveSort then rematch:SortQueue() end -- populate topLevelingPicks wipe(rematch.topPicks) wipe(rematch.skippedPicks) -- pick out preferred pets first and save which pets are skipped for _,petID in ipairs(queue) do if rematch:IsPetPickable(petID) then if #rematch.topPicks<3 then tinsert(rematch.topPicks,petID) end else rematch.skippedPicks[petID] = true end end -- if top 3 picks not made, get top-most that aren't already picked for _,petID in ipairs(queue) do if #rematch.topPicks<3 then if not tContains(rematch.topPicks,petID) then local isSummonable,reason = C_PetJournal.GetPetSummonInfo(petID) if isSummonable or reason==Enum.PetJournalError.PetIsDead then tinsert(rematch.topPicks,petID) end end else break -- don't need to run through rest of queue if top 3 chosen end end local levelingPetSlotted -- will become true if new leveling pets are loaded -- if we can swap pets, swap if any needed if not (InCombatLockdown() or C_PetBattles.IsInBattle() or C_PetBattles.GetPVPMatchmakingInfo()) then -- load the top picks into the slots specially marked for leveling local pickIndex = 1 for i=1,3 do if rematch:GetSpecialSlot(i)==0 then local petID = C_PetJournal.GetPetLoadOutInfo(i) if petID then -- going to not attempt to slot empty slots local pickID = rematch.topPicks[pickIndex] if pickID and pickID~=petID then levelingPetSlotted = true rematch:SlotPet(i,pickID) end pickIndex = pickIndex + 1 end end end else -- if we can't swap pets, that's ok, re-process queue when we can rematch.queueNeedsProcessed = true end -- toast leveling pet if it changed and toast passed if oldTopPetID and rematch.topPicks[1]~=oldTopPetID then rematch:ToastNextLevelingPet(rematch.topPicks[1]) end if rematch.Roster:GetFilter("Other","Leveling") or rematch.Roster:GetFilter("Other","NotLeveling") then -- if pets are filtered for leveling pets, update the list with every ProcessQueue rematch.Roster.needsUpdated = true end end -- returns true if petID meets the conditions of the current preferences function rematch:IsPetPickable(petID) -- if pet's species is in alwaysSkip then never pick it local petInfo = rematch.petInfo:Fetch(petID) if petInfo.speciesID and alwaysSkip[petInfo.speciesID] then return false end local health,maxHealth = C_PetJournal.GetPetStats(petID) local isSummonable,reason = C_PetJournal.GetPetSummonInfo(petID) if isSummonable or reason==Enum.PetJournalError.PetIsDead then -- if C_PetJournal.PetIsSummonable(petID) or (health and health<1) then -- passed first test, pet is summonable (or dead) if health and settings.QueueSkipDead then if health<1 then return false -- Prefer Living Pets checked, don't want dead pets end if settings.QueuePreferFullHP and health=maxHP then return false end end local maxXP = rematch:GetPrefStatValue("maxXP",nil,nil,true) if maxXP then local roundedXP = floor(maxXP) if roundedXP==maxXP and floor(levelingPets[petID])>roundedXP then return false -- if maxXP is a whole number, limit levels by whole numbers elseif roundedXP~=maxXP and levelingPets[petID]>maxXP then return false -- if maxXP has a partial level, limit levels by the same amount end end local minXP = rematch:GetPrefStatValue("minXP",nil,nil,true) if minXP then local roundedXP = floor(minXP) if roundedXP==minXP and floor(levelingPets[petID])0 then tinsert(queue,index,petID) -- insert pet into given index else tinsert(queue,petID) -- -1 index adds to end of queue end for i=#queue,1,-1 do if queue[i]==0 then tremove(queue,i) -- and remove where it existed before (if it existed before) end end if isNew and oldQueueSize<3 then -- queue was near empty, see if this new pet should be slotted for ProcessQueue to control rematch:MaybeSlotNewLevelingPet(petID) end rematch:UpdateQueue() end function rematch:RemovePetFromQueue(petID) for i=#queue,1,-1 do if queue[i]==petID then tremove(queue,i) end end rematch.outgoingQueuedPetID = petID -- note this pet for ProcessQueue within the UpdateUI rematch:UpdateQueue() end -- this should be the only place to add many pets to the end of the queue -- petID is a list of petIDs to add {"BattlePet-0-etc","BattlePet-0-etc",etc} function rematch:InsertManyPetsToQueue(petTable) local hasNew local oldQueueSize = #queue for _,petID in ipairs(petTable) do if not tContains(queue) then tinsert(queue,petID) hasNew = true end end if hasNew and oldQueueSize<3 then rematch:MaybeSlotNewLevelingPet(petTable[1],petTable[2],petTable[3]) end rematch:UpdateQueue() end -- this is to be called in InsertPetToQueue and InsertManyPetsToQueue right before -- ProcessQueue. if a loaded team has leveling pets assigned but no leveling pets -- in their slots (for instance if queue is empty), then just before processing -- the queue this will move the incoming petID(s) into a slot that doesn't already -- have a petID function rematch:MaybeSlotNewLevelingPet(...) for slot=1,select("#",...) do local petID = select(slot,...) local team = RematchSaved[settings.loadedTeam] if team and petID and (not C_PetJournal.PetIsSlotted(petID)) and (team[1][1]==0 or team[2][1]==0 or team[3][1]==0) then for i=1,3 do local loadedID = C_PetJournal.GetPetLoadOutInfo(i) if team[i][1]==0 and not rematch:IsPetLeveling(loadedID) then rematch:SlotPet(i,petID) return end end end end end -- called in ProcessQueue for ActiveSort and from queue menu, performs a sort function rematch:SortQueue() local newTopPetID = queue[1] -- note top-most pet in case we want to move it back to top table.sort(queue,rematch.SortQueueTable) if settings.KeepCurrentOnSort and petID then rematch:InsertPetToQueue(1,petID) -- move old leveling pet back to top if KeepCurrentOnSort enabled end end -- table.sort where e1 and e2 are petIDs -- Levels sort by level, then name, then petID. -- Type sorts by type, then ascending level, then name, then petID. function rematch.SortQueueTable(e1,e2) local order = settings.QueueSortOrder local name1, name2, type1, type2 if order==4 then -- for type sort name1,_,type1 = select(8,C_PetJournal.GetPetInfoByPetID(e1)) name2,_,type2 = select(8,C_PetJournal.GetPetInfoByPetID(e2)) if type1 < type2 then return true elseif type1 > type2 then return false end -- if petType1 == petType2, continue to sort by level end if settings.QueueActiveSort then if settings.QueueSortFavoritesFirst then local fav1 = C_PetJournal.PetIsFavorite(e1) local fav2 = C_PetJournal.PetIsFavorite(e2) if fav1~=fav2 then return fav1 end end if settings.QueueSortRaresFirst and e1 and e2 then local _,_,_,_,rarity1 = C_PetJournal.GetPetStats(e1) local _,_,_,_,rarity2 = C_PetJournal.GetPetStats(e2) if (rarity1==4 or rarity2==4) and rarity1~=rarity2 then return rarity1==4 end end end local level1 = levelingPets[e1] local level2 = levelingPets[e2] if order==3 then -- for median sort, levels are distance from 10.5 level1 = abs(level1-10.5) level2 = abs(level2-10.5) end if level1==level2 then -- if levels are the same, sort by their name next if not name1 then name1 = select(8,C_PetJournal.GetPetInfoByPetID(e1)) name2 = select(8,C_PetJournal.GetPetInfoByPetID(e2)) end if name1==name2 then -- if names are the same, sort by the petID for a stable sort return e1level2 else -- if ascending or median, lower levels (or distance from 10.5) first return level1