Mannequins and other entities
PAL does not animate players specifically — it animates avatars.
Avatar is the vanilla type that both the player and the mannequin are, so on Minecraft versions that have mannequins your animations work on them without any extra code.
What you get for free
Layers registered through PlayerAnimationFactory.ANIMATION_DATA_FACTORY.registerFactory are created for every avatar, mannequins included, and so is PlayerAnimationAccess.REGISTER_ANIMATION_EVENT.
Getting a layer works the same way:
Avatar avatar = ...; // a player or a mannequin
PlayerAnimationController controller = (PlayerAnimationController) PlayerAnimationAccess.getPlayerAnimationLayer(
avatar, ANIMATION_LAYER_ID);
controller.triggerAnimation(animationID);
PlayerAnimationController.getAvatar() gives you the Avatar back, so if you need something that only players have, check the type first:
if (controller.getAvatar() instanceof AbstractClientPlayer player) {
// player-only logic
}
Your factory runs for mannequins too, so a controller written back when only players existed will get one. If its state handler, its modifiers or its MoLang queries assume the avatar is a player and cast to one, that is a crash rather than a missing animation — do the check above rather than casting.
MoLang on mannequins
The built-in MoLang queries run on the avatar, so they work on mannequins too, and the player-specific ones fall back to a sensible default:
has_capereads the cape of both players and mannequins.player_levelreturns0on a mannequin.is_first_personis only ever true for the local player, so it stays0.
Anything that isn't an avatar
Automatic wiring stops at avatars — mobs, block entities and your own models never get an AvatarAnimManager.
The animation engine itself doesn't care, though: the whole core module has no Minecraft classes in it at all, and neither AnimationController nor HumanoidAnimationController wants an entity — a state handler and a MoLang engine is the whole requirement.
So you can animate anything, as long as you do the three things PAL normally does for you:
- Construct the controller yourself and keep it wherever your entity or block entity data lives.
HumanoidAnimationControllerif your model has the player bones, orAnimationControllerwith your ownregisterBonesif it doesn't. - Drive it — call
tickonce per game tick andsetupAnimonce per frame with anAnimationDatayou build from your own velocity and partial tick. - Apply the bones to your model the same way custom bones describes:
get3DTransformto read a bone out of the controller, andRenderUtil.copyVanillaPartandRenderUtil.translatePartToBoneto move a model part with it.
Don't hand it the default MoLang engine. The built-in queries cast the controller to PlayerAnimationController when they're read, so an animation using something like q.is_on_fire on a non-player controller throws a ClassCastException at evaluation time.
The controller constructor takes the engine factory, so pass one built with MolangLoader.createBaseEngine and register whatever queries make sense for your thing.
Testing
PAL registers a client command for playing an animation on a mannequin:
/testMannequin <animationID> <mannequin UUID>
There are matching commands for the local player — /testPlayerAnimation <animationID> — and for round-tripping an animation through the binary format: /testAnimationBinary <animationID> <version> and /testLegacyAnimationBinary <animationID> <version>.
On Fabric these commands only exist in a development environment or when running a dev build of PAL.