With 5.0 a new C_PetJournal API was introduced, and unfortunately the old GetCompanionInfo API no longer works properly (it does not, for example, know anything about non-combat pets obtained before the account-wide merge, and you also can no longer summon non-combat pets using it).
During the beta I wrote a library to help deal with this new API, but unfortunately it had issues, so I've since rewritten the library to simplify its scope a little bit. The library itself only retrieves a list of pet ids and species ids, which can be used with C_PetJournal.GetPetInfoByPetID() and C_PetJournal.GetPetInfoBySpeciesID().
It primarily deals with two problems with scanning the pet journal:
Filters: The Pet Journal filters are cleared and then restored, as they affect the return values given from C_PetJournal.GetPetInfoByIndex()
Updating in response to event: Manipulating the Pet Journal filters causes PET_JOURNAL_LIST_UPDATE to fire, which is also the only event that always fires when you lose or gain a pet. Filter manipulation is detected and we never try to rescan pets in response. When this is not the case, a CallbackHandler event is fired which can be used to safely detect changes to the pet list.
A really silly example (you should never actually use petName for checking for a particular pet):
local LibPetJournal = LibStub("LibPetJournal-2.0")
local function ScanPets()
for i,petid in LibPetJournal:IteratePetIDs() do
local speciesID, customName, level, xp, maxXp, displayID, name, icon,
petType, creatureID, sourceText, description, isWild, canBattle,
tradable, unique = C_PetJournal.GetPetInfoByPetID(petid)
if name == "Feline Familiar" then
print("Player has a cat in a hat.")
end
end
end
ScanPets()
-- watch for when the player gets new pets too
LibPetJournal:RegisterCallback("PetListUpdated", ScanPets)
Its so lame that an addon attempting to determine whether the number of pets owned has changed, in response to a PET_JOURNAL_LIST_UPDATE event, may need to change the filtering of the PetJournal to do it triggering further PET_JOURNAL_LIST_UPDATE events ...
That's going to be fun if a user has multiple addons monitoring and reacting to that event. Even if each is written to temporarily ignore that event whenever it is twiddling the filters to make a pass at the list, which they pretty much have to be if they don't want to worry about infinite loops, you'll still have each addon triggering numerous spurious events for the others to process ...
For my addon, I don't think I'm even going to bother registering for the PET_JOURNAL_LIST_UPDATE event. I've already stuck the data behind an empty table and an __index metamethod to ensure that I always validate and update if necessary before using it. I think the only gain I get from the event is when my UI is up at the same time as a pet is added; it lets me know I need to fire off a LibStub("AceConfigRegistry-3.0"):NotifyChange.
What they really need is a PET_JOURNAL_PET_ADDED event to complement the PET_JOURNAL_PET_DELETED they already have.
I'm currently implementing this in Collectinator 2.0. I should be rewritten in about a year.
I can confirm that NumPets() works.
This might be out of scope (probably is) but in an addon it'd be nice to use the same API calls for mounts/pets, so for example NumCollectibles("MOUNT") or something like that. Just throwing out for discussion.
Yeah, I don't want to throw too much crap in, and the primary focus is in gathering pet/species IDs.
Maybe it'd be worthwhile to write a separate library that unifies the PetJournal (using LibPetJournal-2.0) and Companion APIs? I don't really know how much use it would get.
Here's a few things I discovered or made use of updating Pokedex. I wanted to remain responsive the changes in the number of pets while trying to avoid getting entangled in PET_JOURNAL_LIST_UPDATE games of tag.
I added pets by purchasing and using a vendor item as well as capturing some wild pets via pet battle; I removed pets via cage and releasing. I did not see any COMPANION_LEARNED or COMPANION_UNLEARNED events as a result.
PET_JOURNAL_PET_DELETED is nice in that in also provides the pid of the pet removed and is received after the pet has been removed from the list. However, this event does not appear to fire if you release a pet, only when you cage one.
To catch when a pet is released you can SecureHook(C_PetJournal, "ReleasePetByID"). The pid will again be passed as a param and your hook called after removal from the list. Oddly, hooking CagePetByID is not as useful as the DELETED event because the hook gets called before the removal has actually happened.
For additions, I'm looking for CHAT_MSG_SYSTEM events and then trying to match the message to BATTLE_PET_NEW_PET minus the %s part. Of course, you can't assume that there isn't a locale where the %s is in the middle of fixed text, so I might have to break it into multiple substrings. This paste has my approach for that http://www.wowace.com/paste/6185/. This system message fires for pets bought from a vendor, readded from pets I caged or trapped in the wild; not tested against additions from achievements or any other form of acquisition. The one problem with this approach is that you will get the system message before the actual addition. So, what I do in that case is register for the list update event and then as soon as I get it I unregister again. Not really ideal, but I can't find a better method.
I also noticed some odd behavior if I try to access the C_PetJournal before the first LIST_UPDATE event has fired. If no calls have been made against any of the filtering functions, even if they would not result in the filter setting changing, then the PetJournal would actually return the correct counts from GetNumPets but every pet would return as not being summonable (like a horde balloon on an alliance character). Since the count wouldn't change, I wouldn't rebuild the list and would think the user had no summonable pets. If you call a filter function, even to set it to its current value, the PetJournal instead will return 0,0 for GetNumPets until after the first LIST_UPDATE. I touch a filter during OnInitialize now to try to make sure the pet journal is in that better state. I suppose the only reason I could even get into this state is that I wrote my filter handling code so that if the filters are already in the exact config I want then I won't make any calls; my way of trying to avoid generating unecessary LIST_UPDATE spam. So, the scenario also required the filters to be in the config I use for my list checking.
Okay, stencil and I were talking about it on IRC, and he suggested an easy strategy for handling PET_JOURNAL_LIST_UPDATE while not hitting filters. Essentially you check for changes to the owned component of C_PetJournal.GetNumPets(false) because that will decrease or increase as you lose or gain pets, and is not affected by filters.
If you don't feel like using my library, for whatever reason, it works something like this:
local last_owned_count = 0
function event_handler:PET_JOURNAL_LIST_UPDATE()
local _, owned = C_PetJournal.GetNumPets(false)
if last_owned_count ~= owned then
event_handler:UnregisterEvent("PET_JOURNAL_LIST_UPDATE")
-- clear some filters
-- run any code pertaining to new or lost pets
-- restore some filters
event_handler:RegisterEvent("PET_JOURNAL_LIST_UPDATE")
last_owned_count = owned
end
-- code pertaining some other pet changing maybe
end
*sigh* Okay, I fixed a bunch of bugs where I wasn't even able to follow my own example code that I posted.
Also there was strange issue where I was getting PJLU despite having unregistered it.
I hit some weird reentrance/recursion type issues with my filters as well. I thought I'd written in a way to prevent that and never did track down exactly how I got in those states. I have my suspicions, but I just rewrote a couple bits to hopefully handle the situation correctly.
For PJLU I'm using a counting scheme. Calls to Suspend reduce the count by 1 while calls to Resume increase the count by 1. We only call RegisterEvent and UnregisterEvent when the count becomes 1 or drops from 1 respectively. This way, should we find ourselves filtering inside a filter, the first Resume call won't re-register an the Event the first filter wants suspended. Here's the little class I made for that http://www.wowace.com/paste/6194/.
any chance you could wait for C_PetJournal.IsJournalUnlocked( ) == true before setting the lib as loaded and sending the first callback?
until the journal is unlocked theres some functions that return all nils, which is a pain when youre scanning.
pet renames arent being caught (same owned count so i presume the update gets ignored). you could possibly hook C_PetJournal.SetCustomName for that purpose
pet level ups also not caught
you may need to add more checking of other events, not just rely on the pet count changing, to know when to rescan.
Okay, we now check for C_PetJournal.IsJournalUnlocked(), and also IsLoggedIn() too (since C_PetJournal.IsJournalUnlocked() is true very early during login) and don't load pets or fire events before this point.
Also, I fixed an issue where species IDs weren't being properly loaded on an empty cache.
Now that I think about it a bit, C_PetJournal.IsJournalUnlocked() is not an appropriate function to check, since it's primary purpose is actually detection of players with multiple accounts online in the same b.net account.
Until I investigate further, I am going to just check for IsLoggedIn(). This might be sufficient to prevent pulling data early.
any chance you could wait for C_PetJournal.IsJournalUnlocked( ) == true before setting the lib as loaded and sending the first callback?
until the journal is unlocked theres some functions that return all nils, which is a pain when youre scanning.
Okay, I think this is finally fixed!
It looks like the problem is just that .GetPetInfoByIndex works before .GetPetInfoByPetID does.
pet renames arent being caught (same owned count so i presume the update gets ignored). you could possibly hook C_PetJournal.SetCustomName for that purpose
Sorry, I didn't see this before. LibPetJournal-2.0 doesn't track anything but pet ids/species ids, so it doesn't really care that much.
There's the PetsUpdated event, but that fires in response to practically every PJLU event (but always after pet ids have been updated), as opposed to PetListUpdated, which is just the list of pet ids changing. This does mean it fires a lot though, so I might at some point make it not respond to filter changes again.
Sorry, I didn't see this before. LibPetJournal-2.0 doesn't track anything but pet ids/species ids, so it doesn't really care that much.
There's the PetsUpdated event, but that fires in response to practically every PJLU event (but always after pet ids have been updated), as opposed to PetListUpdated, which is just the list of pet ids changing. This does mean it fires a lot though, so I might at some point make it not respond to filter changes again.
didnt realise wed gone to another page so adding this here, pet level ups also not being caught but seeing as the lib seems to just be for basic do i or dont i have a pet i may have to write my own. oh well
Rollback Post to RevisionRollBack
To post a comment, please login or register a new account.
During the beta I wrote a library to help deal with this new API, but unfortunately it had issues, so I've since rewritten the library to simplify its scope a little bit. The library itself only retrieves a list of pet ids and species ids, which can be used with C_PetJournal.GetPetInfoByPetID() and C_PetJournal.GetPetInfoBySpeciesID().
It primarily deals with two problems with scanning the pet journal:
A really silly example (you should never actually use petName for checking for a particular pet):
Getting Started
API Documentation
Project Page
So here I am, actually trying to get people to look at it. Any feedback would be appreciated.
That's going to be fun if a user has multiple addons monitoring and reacting to that event. Even if each is written to temporarily ignore that event whenever it is twiddling the filters to make a pass at the list, which they pretty much have to be if they don't want to worry about infinite loops, you'll still have each addon triggering numerous spurious events for the others to process ...
For my addon, I don't think I'm even going to bother registering for the PET_JOURNAL_LIST_UPDATE event. I've already stuck the data behind an empty table and an __index metamethod to ensure that I always validate and update if necessary before using it. I think the only gain I get from the event is when my UI is up at the same time as a pet is added; it lets me know I need to fire off a LibStub("AceConfigRegistry-3.0"):NotifyChange.
What they really need is a PET_JOURNAL_PET_ADDED event to complement the PET_JOURNAL_PET_DELETED they already have.
I can confirm that NumPets() works.
This might be out of scope (probably is) but in an addon it'd be nice to use the same API calls for mounts/pets, so for example NumCollectibles("MOUNT") or something like that. Just throwing out for discussion.
Maybe it'd be worthwhile to write a separate library that unifies the PetJournal (using LibPetJournal-2.0) and Companion APIs? I don't really know how much use it would get.
I added pets by purchasing and using a vendor item as well as capturing some wild pets via pet battle; I removed pets via cage and releasing. I did not see any COMPANION_LEARNED or COMPANION_UNLEARNED events as a result.
PET_JOURNAL_PET_DELETED is nice in that in also provides the pid of the pet removed and is received after the pet has been removed from the list. However, this event does not appear to fire if you release a pet, only when you cage one.
To catch when a pet is released you can SecureHook(C_PetJournal, "ReleasePetByID"). The pid will again be passed as a param and your hook called after removal from the list. Oddly, hooking CagePetByID is not as useful as the DELETED event because the hook gets called before the removal has actually happened.
For additions, I'm looking for CHAT_MSG_SYSTEM events and then trying to match the message to BATTLE_PET_NEW_PET minus the %s part. Of course, you can't assume that there isn't a locale where the %s is in the middle of fixed text, so I might have to break it into multiple substrings. This paste has my approach for that http://www.wowace.com/paste/6185/. This system message fires for pets bought from a vendor, readded from pets I caged or trapped in the wild; not tested against additions from achievements or any other form of acquisition. The one problem with this approach is that you will get the system message before the actual addition. So, what I do in that case is register for the list update event and then as soon as I get it I unregister again. Not really ideal, but I can't find a better method.
I also noticed some odd behavior if I try to access the C_PetJournal before the first LIST_UPDATE event has fired. If no calls have been made against any of the filtering functions, even if they would not result in the filter setting changing, then the PetJournal would actually return the correct counts from GetNumPets but every pet would return as not being summonable (like a horde balloon on an alliance character). Since the count wouldn't change, I wouldn't rebuild the list and would think the user had no summonable pets. If you call a filter function, even to set it to its current value, the PetJournal instead will return 0,0 for GetNumPets until after the first LIST_UPDATE. I touch a filter during OnInitialize now to try to make sure the pet journal is in that better state. I suppose the only reason I could even get into this state is that I wrote my filter handling code so that if the filters are already in the exact config I want then I won't make any calls; my way of trying to avoid generating unecessary LIST_UPDATE spam. So, the scenario also required the filters to be in the config I use for my list checking.
If you don't feel like using my library, for whatever reason, it works something like this:
Also there was strange issue where I was getting PJLU despite having unregistered it.
For PJLU I'm using a counting scheme. Calls to Suspend reduce the count by 1 while calls to Resume increase the count by 1. We only call RegisterEvent and UnregisterEvent when the count becomes 1 or drops from 1 respectively. This way, should we find ourselves filtering inside a filter, the first Resume call won't re-register an the Event the first filter wants suspended. Here's the little class I made for that http://www.wowace.com/paste/6194/.
until the journal is unlocked theres some functions that return all nils, which is a pain when youre scanning.
pet renames arent being caught (same owned count so i presume the update gets ignored). you could possibly hook C_PetJournal.SetCustomName for that purpose
pet level ups also not caught
you may need to add more checking of other events, not just rely on the pet count changing, to know when to rescan.
Also, I fixed an issue where species IDs weren't being properly loaded on an empty cache.
Until I investigate further, I am going to just check for IsLoggedIn(). This might be sufficient to prevent pulling data early.
Okay, I think this is finally fixed!
It looks like the problem is just that .GetPetInfoByIndex works before .GetPetInfoByPetID does.
Sorry, I didn't see this before. LibPetJournal-2.0 doesn't track anything but pet ids/species ids, so it doesn't really care that much.
There's the PetsUpdated event, but that fires in response to practically every PJLU event (but always after pet ids have been updated), as opposed to PetListUpdated, which is just the list of pet ids changing. This does mean it fires a lot though, so I might at some point make it not respond to filter changes again.
didnt realise wed gone to another page so adding this here, pet level ups also not being caught but seeing as the lib seems to just be for basic do i or dont i have a pet i may have to write my own. oh well