The cat and dog animation-ready bases are both ported from the Bunny. I was interested in the way bunnies can load additional animations contained in the breedfile. Porting this to cats/dogs would give hexers the ability to override certain animations for specific breeds - for example, instead of having show dogs with static addball angled legs, show breeds could override the pose animation to put the dog into an angled-legs position only while posing.
PF Magic originally promised pigs and ferrets as new downloadable species in Petz 3. In the end, Petz 4 had pigs and bunnies as "special" breeds. Bunnies were a port from Babyz. You can read more about pigs on Starlight.
Bunnies and pigs have several interesting properties:
Breedfiles are PEs (portable executables). Basically they're DLLs. Every breedfile exports GetSprite
and imports a bunch of stuff from Petz 4.exe.
Each breedfile defines its own class (TabbySprite, DalmatianSprite, BulldogSprite, etc) which inherits from CatSprite/DogSprite (or, for the pig, PetSprite). GetSprite
initialises and returns a breed-specific Sprite.
Each breed Sprite class overrides InitBreedSprite
, ConstructGenome
, and MapBreedSpecificAction
. This is where pretty much all the breed-specific behaviour is defined (in addition to the SCP).
InitBreedSprite
- in most breeds this just specifies the name of the SCP and LNZ files to load, then defers to PetSprite::InitPetSprite. Called every time you bring the pet out. In bunnies this also calls LoadBreedFrames
- more on this later. Bunnies and pigs register new SCP goals/plans here.
ConstructGenome
- calls PetSprite::ConstructGenome to get a base cat/dog genome (some of which is pulled from lnz), then overrides some alleles for breed-specificity. This is where personality traits (and favourites?) are set. Called once when pet is first created. InitPersonalityBiorhythms
is also sometimes overridden and called at creation time but I'm not sure what it does.
MapBreedSpecificAction
- there is a data structure in each breedfile called [BreedName]UActionMapping
that maps certain SCP actions to other SCP actions. This seems to get called when the pet is locomoting+vocalising, and layers the returned SCP action on top of the action currently playing. This is probably used for those little 'flourishes' pets do like somersaulting while running.
Some breeds also override GetPostureLayerToDo
, which controls the gait of the breed (e.g. bulldog walking), and EyesNormal
, which probably sets default angry/worried eyelids like oshies and B+W shorthairs. The default PetSprite::GetPostureLayerToDo
seems to look at personality values and some other stuff to determine the gait.
Every pet is one breed class. That is, even your mixed pets are a TabbySprite or a BWShorthairSprite or whatever. Any functions in the breedfile which run on taking out the pet or while the pet is out (InitBreedSprite
, GetPostureLayerToDo
, MapBreedSpecificAction
) are controlled by whichever Sprite class your pet has.
The bunny is the only breedfile which imports and calls LoadBreedFrames
in InitBreedSprite
. It is therefore the only breed which will load additional BHD/BDT files. You can add BHD/BDT files to other breeds but they'll never be loaded and cannot be called from the SCP.
The bunny inherits from CatSprite, but both the bunny and pig override GetIsDogz
and GetIsCatz
to return 0, telling the game that they are neither cats nor dogs. This has knock-on effects for genomes and breeding; only cats and dogs have their look alleles populated from lnz, and pigs/bunnies cannot breed with either cats or dogs.
Given that a bunny is a CatSprite and is based on the cat lnz/scp, it's fairly easy to make it into a normal breedable cat by just making GetIsCatz
return 1. (You can also change the various genome/action/posture/eyelid info as below to perfectly match an existing cat breed.) Then we have a cat that looks like a bunny and which can play additional animations.
But what about dogs? Initially I wanted to import and call LoadBreedFrames
in a dog. It is possible to add extra imports to a PE, but I couldn't figure it out, so I ended up hacking the Bunny to be a dog instead.
I used IDA Freeware, HxD hex editor, LnzPro and Resource Hacker for this.
I made a new breed with a new ID in LnzPro.
I edited GetIsDogz
so that it always returns 1. This makes the breed list alongside other dogs in the adoption center, makes the full genome populate, and lets it breed with other dogs.
I changed the 'tag' of the breedfile to 1 to match standard cats/dogs. (See instructions for Carolyn's breedable pigs - this tag somehow controls whether the pets are autospayed on creation. I'm not sure why it's 0x19 specifically in pigs/bunnies.)
I swapped the base SCP in InitBreedSprite
to dog.scp. I replaced the SCP file with the Dalmatian SCP (keeping the same name).
I replaced the bn/bnkit LNZ with Dalmatian LNZ (keeping the same name) and updated any necessary details like the [Little one]
path.
I changed the sound path and added the wavs from the Dalmatian.
I swapped the CatSprite import with a DogSprite import, so that BunnySprite would inherit from DogSprite. I'm not sure this was necessary, since the game seems to rely on GetIsDogz
to determine the species, but I figured this might have some effect on the genome. (Swapping an import is much easier than adding a new one.)
I edited BunnySprite::ConstructGenome to match DalmatianSprite::ConstructGenome.
I no-op'd GetPostureLayerToDo
to remove the special gait that bunnies have (without doing this, the bunnydali has a bulldog walk). I suspect I should properly have set this to return 0.
I replaced the bunny UActionMapping structure with the Dalmatian UActionMapping structure.
I replaced the star bmp and case FLH/FLM with the base dog ones from one of the core Petz resource DLLs.
There was no need to zero out any of the bunny state machine stuff, since the bunny goal's Filter always returns 0 and so the goal/plan never seem to trigger.
At this point, the Bunnydali has exactly the same genome, personality and behaviour as a basegame dali. (I should have edited the EyesNormal
too, but meh...)
BUT unlike the normal dali, it has the ability to load and play custom animations.
You need to know the BHD/BDT and SCP formats inside-out in order to do anything with these bases. The community will probably develop tooling for this in future. At the moment we also don't have a good idea which SCP actions correspond to what behaviours or animations, so the whole endeavour is tricky.
The basic requirements for displaying a custom animation are:
The difficulty is in generating these files and identifying the correct actions to change. You're on your own there for now!
For the DalmatianPoser breedfile with the replaced pose:
As you can see, this is a pile of work and you need to already understand all these file formats. If you're not comfortable doing this yourself, wait for tooling!