local _,rematch = ... local L = rematch.localization local C = rematch.constants local settings = rematch.settings --[[ Rematch refers to pets as a single number or string, depending on their context. This reference, called a petID, can be one of nine idTypes: idType example petID value description ---------- --------------------- ----------------------------------- "pet" "Battle-0-0000000etc" a player-owned pet in the journal "species" 42 speciesID species number "leveling" 0 leveling slot "ignored" "ignored" ignored slot "link" "battlepet:42:25:etc" linked pet "battle" "battle:2:1" pet in battle (battle:owner:index) "random" "random:10" random mechanical pet (random:0 for any pet type) "unnotable" "unnotable:npcID" pet of a target not in targetData's notableTargets "empty" nil or "blank" no pet "unknown" (anything else) indecipherable/undefined pet To simplify getting information about these different types of pets, to eliminate the scattered code to independently call the various API through C_PetJournal and C_PetBattles, and to reduce redundant API calls for information already retrieved (or information not used!), this module encapsulates a "front end" for information about pets. To use, fetch a petID to make it the pet of interest (which can be any of the above strings or numbers): petInfo:Fetch(petID) After a pet of interest is fetched, simply index the stat you want: print(petInfo.name,"is level",petInfo.level,"and breed",petInfo.breedName) The stat can be any of these: petID: this is the pet reference Fetched (string, number, link, etc) idType: "pet" "species" "leveling" "ignored" "link" "battle" "random" or "unknown" (string) speciesID: numeric speciesID of the pet (integer) customName: user-renamed pet name (string) speciesName: name of the species (string) name: customName if defined, speciesName otherwise (string) level: whole level 1-25 (integer) xp: amount of xp in current level (integer) maxXp: total xp to reach next level (integer) fullLevel: level+xp/maxXp (float) displayID: id of the pet's skin (integer) isFavorite: whether pet is favorited (bool) icon: fileID of pet's icon or specific filename (integer or string) petType: numeric type of pet 1-10 (integer) creatureID: npcID of summoned pet (integer) sourceText: formatted text about where pet is from (string) loreText: "back of the card" lore (string) isWild: whether the pet is found in the wild (bool) canBattle: whether pet can battle (bool) isTradable: whether pet can be caged (bool) isUnique: whether only one of pet can be learned (bool) isObtainable: whether this pet is in the journal (bool) health: current health of the pet (integer) maxHealth: maximum health of the pet (integer) power: power stat of the pet (integer) speed: speed stat of the pet (integer) rarity: rarity 1-4 of pet (integer) isDead: whether the pet is dead (bool) isInjured: whether the pet has less than max health (bool) isSummonable: whether the pet can be summoned (bool) summonError: the error ID why a pet can't be summoned summonErrorText: the error text why a pet can't be summoned summonShortError: shortened text of why a pet can't be summoned (for pet card stat) isRevoked: whether the pet is revoked (bool) abilityList: table of pet's abilities (table) levelList: table of pet's ability levels (table) valid: whether the petID is valid and petID is not missing (bool) owned: whether the petID is a valid pet owned by the player (bool) count: number of pet the player owns (integer) maxCount: maximum number of this pet the player can own (integer) countColor: hex color code for pet count (white for 0, green for count:::::" ]] -- if an alternate petInfo needs made, use: local altInfo = rematch.petInfo:Create() local GetPetInfoByPetID = C_PetJournal.GetPetInfoByPetID local GetPetInfoBySpeciesID = C_PetJournal.GetPetInfoBySpeciesID -- local functions defined here local fillInfoByPetID, fillInfoBySpeciesID, getIDType, fetch, reset, lookup, create -- each stat comes from a function. this table says what function populates each stat local stats = { speciesID="General", customName="General", level="General", xp="General", maxXp="General", displayID="General", isFavorite="General", speciesName="General", name="General", icon="General", petType="General", creatureID="General", sourceText="General", loreText="General", isWild="General", canBattle="General", isTradable="General", isUnique="General", isObtainable="General", npcID="General", isValid="Valid", isOwned="Valid", health="Stats",maxHealth="Stats", power="Stats", speed="Stats", rarity="Stats", color="Stats", isDead="Dead", isInjured="Dead", shortHealthStatus="Status",longHealthStatus="Status", isRevoked="Other", needsFanfare="Other", isSummonable="SummonInfo", summonError="SummonInfo", summonErrorText="SummonInfo", isSummoned="IsSummoned", abilityList="Abilities", levelList="Abilities", usableAbilities="UsableAbilities", strongVs="StrongVs", toughVs="ToughVs", vulnerableVs="ToughVs", count="Count", maxCount="Count", countColor="Count", fullLevel="FullLevel", suffix="Suffix", petTypeName="Suffix", battleOwner="Battle", battleIndex="Battle", isSlotted="Slotted", breedID="Breed", breedName="Breed", hasBreed="Breed", possibleBreedIDs="PossibleBreeds", possibleBreedNames="PossibleBreeds", numPossibleBreeds="PossibleBreeds", inTeams="Teams", numTeams="Teams", sourceID="Source", moveset="Moveset", isSpeciesAt25="SpeciesAt25", isMovesetAt25="MovesetAt25", notes="Notes", hasNotes="Notes", isLeveling="IsLeveling", expansionID="Expansion", expansionName="Expansion", isSpecialType="Special", passive="Passive", marker="PetMarker", tint="Tint", formattedName="FormattedName", isStickied="Stickied", } -- and each function is a member of this table, called during a lookup to populate the above stats local funcs = {} -- the General func has to deal with 7 idTypes separately, so it has subfuncs broken out into the following table local generalSubFuncs -- indexed by petInfo reference, this contains tables like abilityList and fetchedFuncs reused to reduce garbage local reusedTables = {} --[[ stat-pulling funcs ]] -- the majority of information about a pet will come from the General function, which is broken up -- by idTypes in generalSubFuncs to make lookups a little quicker function funcs:General() local idType = self.idType if idType and generalSubFuncs[idType] then generalSubFuncs[idType](self) else self.name = L["Unknown"] self.icon = C.UNKNOWN_ICON end end -- verifies the pet contains information (not a server-reassigned petID or bad speciesID) and if it's owned function funcs:Valid() local idType = self.idType if idType=="pet" and self.speciesID then -- if speciesID read ok then "pet" petID is functional self.isValid = true self.isOwned = true -- owned is only true if regular petID is a "Battle-0-etc" petID elseif (idType=="species" and self.name) or (idType=="leveling" or idType=="ignored" or idType=="random" or idType=="unnotable") or (idType=="link" and self.name) or (idType=="battle" and self.name) then self.isValid = true self.isOwned = false else self.isValid = false self.isOwned = false end end -- gets rarity, health, power and speed stats if possible function funcs:Stats() local idType = self.idType local rarity,health,maxHealth,power,speed if idType=="pet" then health,maxHealth,power,speed,rarity = C_PetJournal.GetPetStats(self.petID) elseif idType=="link" then rarity,health,power,speed = self.petID:match("battlepet:%d+:%d+:(%d+):(%d+):(%d+):(%d+)") if rarity then rarity = tonumber(rarity)+1 -- links are 0-3 rarity intead of 1-4 health = tonumber(health) maxHealth = tonumber(health) power = tonumber(power) speed = tonumber(speed) end elseif idType=="battle" then -- pets in a battle fetch live stats (but note values cache!) local owner = self.battleOwner local index = self.battleIndex if C_PetBattles.GetPetSpeciesID(owner,index) then rarity = C_PetBattles.GetBreedQuality(owner,index) health = C_PetBattles.GetHealth(owner,index) maxHealth = C_PetBattles.GetMaxHealth(owner,index) power = C_PetBattles.GetPower(owner,index) speed = C_PetBattles.GetSpeed(owner,index) end end if not self.canBattle then -- for non-battle pets, hide all stats except rarity health = nil maxHealth = nil power = nil speed = nil end self.rarity = rarity self.health = health self.maxHealth = maxHealth self.power = power self.speed = speed if rarity then self.color = rematch.utils:GetRarityColor(rarity-1) end end -- whether a pet isDead or isInjured function funcs:Dead() if self.maxHealth and self.maxHealth > 0 then self.isDead = self.health==0 self.isInjured = self.health 0 then local abilityList = self.abilityList local levelList = self.levelList for i=1,6 do local listLevel = levelList[i] if listLevel and listLevel <= level then usableAbilities[abilityList[i]] = true end end end self.usableAbilities = usableAbilities end -- fills petInfo.strongVs table indexed by abilityID and the petType the ability is strong against function funcs:StrongVs() local strongVs = reusedTables[self].strongVs local abilityList = self.abilityList wipe(strongVs) if abilityList then for _,abilityID in ipairs(abilityList) do local _,_,_,_,_,_,abilityType,noHints = C_PetBattles.GetAbilityInfoByID(abilityID) if not noHints then -- skipping self heals and such that don't attack strongVs[abilityID] = C.HINTS_OFFENSE[abilityType][1] end end end self.strongVs = strongVs end -- sets toughVs to the pet type the pet is tough against () function funcs:ToughVs() local hint = C.HINTS_DEFENSE[self.petType] self.toughVs = hint and hint[2] self.vulnerableVs = hint and hint[1] end -- gets the current number of collected versions of the pet and the max allowed copies function funcs:Count() if self.speciesID then local count,maxCount = C_PetJournal.GetNumCollectedInfo(self.speciesID) self.count,self.maxCount = count,maxCount if not count or count==0 then self.countColor = C.HEX_WHITE elseif count==maxCount then self.countColor = C.HEX_RED elseif count 0 self.numTeams = numTeams end -- whether the pet is not an actual pet but a special non-pet type function funcs:Special() local idType = self.idType self.isSpecialType = idType=="leveling" or idType=="random" or idType=="ignored" or idType=="unnotable" end -- the passive or racial text for the pet type function funcs:Passive() local petType = self.petType if petType and not self.isSpecialType then self.passive = select(5,C_PetBattles.GetAbilityInfoByID(PET_BATTLE_PET_TYPE_PASSIVES[petType])):match("^.-\r\n(.-)\r"):gsub("%[percentage.-%]%%","4%%") end end -- the numeric 1-8 marker assigned to the speciesID function funcs:PetMarker() local speciesID = self.speciesID if speciesID then self.marker = settings.PetMarkers[speciesID] end end -- tint is "red" if the pet is unsummonable and owned (revoked or wrong faction), or "grey" if -- otherwise unsummonable (uncollected speciesID) or nil if no tint function funcs:Tint() if not self.isSummonable and not self.isSpecialType then self.tint = self.isOwned and "red" or "grey" end end -- true/false if pet is in the leveling queue -- note: if queue is mid-process, this is unreliable; check the settings.LevelingQueue then function funcs:IsLeveling() self.isLeveling = rematch.queue:IsPetLeveling(self.petID) end -- returns the pet name with color codes function funcs:FormattedName() if settings.ColorPetNames then local color = self.color self.formattedName = format("%s%s\124r",color and color.hex or C.HEX_WHITE,self.name) else self.formattedName = self.name end end function funcs:Stickied() self.isStickied = rematch.sort:IsPetIDStickied(self.petID) end ------------------------------------------------------------------------------------------------ --[[ helper funcs (these are declared local at the top here) ]] -- lookup table of sub-functions to fill General stats depending on the pet's idType generalSubFuncs = { pet = function(self) fillInfoByPetID(self,self.petID) end, species = function(self) fillInfoBySpeciesID(self,self.petID) -- petID here is a number (speciesID) end, leveling = function(self) self.name = L["Leveling Pet"] self.icon = C.LEVELING_ICON self.displayID = "Interface\\Buttons\\talktomequestion_ltblue.m2" end, ignored = function(self) self.name = L["Ignored Pet"] self.icon = C.IGNORED_ICON self.displayID = "Interface\\Buttons\\talktomered.m2" end, link = function(self) local speciesID,level = self.petID:match("battlepet:(%d+):(%d+):") speciesID = tonumber(speciesID) if speciesID then fillInfoBySpeciesID(self,speciesID) self.level = tonumber(level) -- rarity, health, power and speed comes from Stats end end, battle = function(self) local owner = self.battleOwner local index = self.battleIndex if owner==1 then -- for ally battle pets, just use the loaded pet local petID = C_PetJournal.GetPetLoadOutInfo(index) if petID then fillInfoByPetID(self,petID) end elseif owner==2 then local speciesID = C_PetBattles.GetPetSpeciesID(owner,index) if speciesID then fillInfoBySpeciesID(self,speciesID) self.level = C_PetBattles.GetLevel(owner,index) self.displayID = C_PetBattles.GetDisplayID(owner,index) end end end, random = function(self) -- petType always defined as 0-10 for random pets local petType = math.min(10,math.max(0,tonumber(self.petID:match("random:(%d+)")) or 0)) self.petType = petType -- name is "Random Pet" or "Random Humanoid", "Random Dragonkin", etc local suffix = PET_TYPE_SUFFIX[petType] self.name = suffix and format(L["Random %s"],_G["BATTLE_PET_NAME_"..petType]) or L["Random Pet"] self.icon = suffix and format("Interface\\Icons\\Icon_PetFamily_%s",suffix) or "Interface\\Icons\\INV_Misc_Dice_02" self.displayID = "Interface\\Buttons\\talktomequestionmark.m2" end, empty = function(self) self.name = L["Empty Pet Slot"] self.icon = C.EMPTY_ICON end, unnotable = function(self) self.name = L["Not Notable"] self.icon = C.UNNOTABLE_ICON self.npcID = tonumber(self.petID:match("unnotable:(%d+)")) end, } -- used in General functions to gather info by petID (BattlePet-0-000etc) function fillInfoByPetID(self,petID) local speciesID, customName, speciesName -- prevent a __index lookup if petID is invalid or not renamed local canBattle, level, xp, maxXp speciesID,customName,level,xp,maxXp,self.displayID, self.isFavorite,speciesName,self.icon,self.petType,self.creatureID, self.sourceText,self.loreText,self.isWild,canBattle,self.isTradable, self.isUnique,self.isObtainable = GetPetInfoByPetID(petID) self.name = customName or speciesName self.speciesID = speciesID self.customName = customName self.speciesName = speciesName -- canBattle can be false for GetPetInfoByPetID when it can be true for GetPetInfoBySpeciesID (/sigh) canBattle = rematch.speciesInfo:CanBattle(speciesID) if canBattle then -- only define level and xp for pets that can battle self.level = level self.xp = xp self.maxXp = maxXp end self.canBattle = canBattle end -- used in General functions to gather info by speciesID (42) function fillInfoBySpeciesID(self,speciesID) local speciesName,icon -- prevent a __index lookup if speciesID is invalid speciesName,icon,self.petType,self.creatureID,self.sourceText, self.loreText,self.isWild,self.canBattle,self.isTradable,self.isUnique, self.isObtainable,self.displayID = GetPetInfoBySpeciesID(speciesID) -- when speciesID is invalid, it returns the GetPetInfoBySpeciesID function now for some reason if type(speciesName)=="function" then -- nil everything if speciesName = nil icon = nil end self.speciesName = speciesName self.name = speciesName self.speciesID = speciesID self.icon = icon if not self.icon then self.icon = C.REMATCH_ICON end end -- used by fetch, getIDType takes a petID and returns what type of id it is. one of: -- "pet" "species" "leveling" "ignored" "link" "battle" "random" "ignored" or "unknown" function getIDType(petID,forJournal) local idType = type(petID) if forJournal then if idType=="string" then return "pet" elseif idType=="number" then return "species" end elseif idType=="string" then if petID:match("^BattlePet%-%x%-%x%x%x%x%x%x%x%x%x%x%x%x$") then return "pet" elseif petID:match("battlepet:%d+:%d+:%d+:%d+:%d+:%d+") then return "link" elseif petID:match("battle:%d:%d") then return "battle" elseif petID:match("random:%d+") then return "random" elseif petID=="ignored" then return "ignored" elseif petID:match("unnotable:%d+") then return "unnotable" elseif petID=="empty" then return "empty" end elseif idType=="number" then if petID>0 then return "species" elseif petID==0 then return "leveling" end elseif idType=="nil" then return "empty" end return "unknown" -- if we reached here, no idea what this petID is! end -- fetch makes a petID the "pet of interest." it can be a full petID, a speciesID, link, etc. -- if fromJournal is true, then this petID is trusted to be an owned petID or a speciesID. -- the same petID can be fetched multiple times and it will not duplicate the work to get info function fetch(self,petID,fromJournal) if petID~=self.petID or not petID then -- nils are okay (they're unknown; need 'not petID' if nil~=nil) reset(self) self.petID = petID -- note that .petID property is the one used in the fetch and never changes self.idType = getIDType(petID,fromJournal) end return self -- initially, only the given petID and its idType are known end -- resets a petInfo, called when a new pet is fetched. it's also a good idea to call this at the start -- of an update in case any pets changed attributes since the petInfo stat was last fetched. (by design, -- API calls are not re-run for the same pet, it uses the cached value.) function reset(self) wipe(self) for _,tbl in pairs(reusedTables[self]) do wipe(tbl) -- wipes the reusable tables end self.Fetch = fetch -- re-establishing these since we wiped self self.Reset = reset self.Create = create end -- the __index metamethod that returns an already-cached result or fetches the result and returns it function lookup(self,stat) local func = stats[stat] local fetchedFuncs = reusedTables[self].fetchedFuncs if func and not fetchedFuncs[func] and funcs[func] then -- if function for the stat has not been called fetchedFuncs[func] = true -- flag it being used funcs[func](self) -- and run the func to populate the stat end return rawget(self,stat) -- and return the value now, either already cached or just-pulled value end -- creates a new petInfo if needed (rematch.petInfo is the primary one. rematch.petInfo (or any -- petInfo can spawn a new one with petInfo:Create())) function create() local info = {} reusedTables[info] = { fetchedFuncs = {}, -- lookup of functions that have been run abilityList = {}, -- cached result of C_PetJournal.GetPetAbilityList levelList = {}, -- cached result of C_PetJouranl.GetPetAbilityList usableAbilities = {}, -- list of abilities the pet can use at their current level possibleBreedIDs = {}, -- table of breedIDs possible for a speciesID possibleBreedNames = {}, -- table of breedNames possible for a speciesID strongVs = {}, -- table of abilities and the petTypes they are strong against } info.Fetch = fetch -- member functions info.Reset = reset info.Create = create -- any petInfo can spawn new petInfos if needed setmetatable(info,{__index=lookup}) return info end -- create the main petInfo used throughout the addon rematch.petInfo = create() -- creating an alternate one in case we need to fetch two concurrently (comparing others) rematch.altInfo = rematch.petInfo:Create()