diff --git a/server/session/world.go b/server/session/world.go index 586294c56..e4628bb13 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -976,9 +976,12 @@ func (s *Session) ViewEntityState(e world.Entity) { } // ViewEntityAnimation ... -func (s *Session) ViewEntityAnimation(e world.Entity, animationName string) { +func (s *Session) ViewEntityAnimation(e world.Entity, a world.EntityAnimation) { s.writePacket(&packet.AnimateEntity{ - Animation: animationName, + Animation: a.Name(), + NextState: a.NextState(), + StopCondition: a.StopCondition(), + Controller: a.Controller(), EntityRuntimeIDs: []uint64{ s.entityRuntimeID(e), }, diff --git a/server/world/entity_animation.go b/server/world/entity_animation.go new file mode 100644 index 000000000..b9f459c33 --- /dev/null +++ b/server/world/entity_animation.go @@ -0,0 +1,63 @@ +package world + +// EntityAnimation represents an animation that may be played on an entity from an active resource pack on +// the client. +type EntityAnimation struct { + name string + nextState string + controller string + stopCondition string +} + +// NewEntityAnimation returns a new animation that can be played on an entity. If no controller or stop +// condition is set, the animation will play for its full duration, including looping. Controllers can be set +// to manage multiple states of animations. It is also possible to use vanilla animations/controllers if they +// work for your entity, i.e. "animation.pig.baby_transform". +func NewEntityAnimation(name string) EntityAnimation { + return EntityAnimation{name: name} +} + +// Name returns the name of the animation to be played. +func (a EntityAnimation) Name() string { + return a.name +} + +// Controller returns the name of the controller to be used for the animation. +func (a EntityAnimation) Controller() string { + return a.controller +} + +// WithController returns a copy of the EntityAnimation with the provided animation controller. An animation +// controller with the same name must be defined in a resource pack for it to work. +func (a EntityAnimation) WithController(controller string) EntityAnimation { + a.controller = controller + return a +} + +// NextState returns the state to transition to after the animation has finished playing within the +// animation controller. +func (a EntityAnimation) NextState() string { + return a.nextState +} + +// WithNextState returns a copy of the EntityAnimation with the provided state to transition to after the +// animation has finished playing within the animation controller. +func (a EntityAnimation) WithNextState(state string) EntityAnimation { + a.nextState = state + return a +} + +// StopCondition returns the condition that must be met for the animation to stop playing. This is often +// a Molang expression that can be used to query various entity properties to determine when the animation +// should stop playing. +func (a EntityAnimation) StopCondition() string { + return a.stopCondition +} + +// WithStopCondition returns a copy of the EntityAnimation with the provided stop condition. The stop condition +// is a Molang expression that can be used to query various entity properties to determine when the animation +// should stop playing. +func (a EntityAnimation) WithStopCondition(condition string) EntityAnimation { + a.stopCondition = condition + return a +} diff --git a/server/world/tx.go b/server/world/tx.go index d82ed12af..75944ef80 100644 --- a/server/world/tx.go +++ b/server/world/tx.go @@ -158,6 +158,14 @@ func (tx *Tx) AddParticle(pos mgl64.Vec3, p Particle) { tx.World().addParticle(pos, p) } +// PlayEntityAnimation plays an animation on an entity in the World. The animation is played for all viewers +// of the entity. +func (tx *Tx) PlayEntityAnimation(e Entity, a EntityAnimation) { + for _, viewer := range tx.World().viewersOf(e.Position()) { + viewer.ViewEntityAnimation(e, a) + } +} + // PlaySound plays a sound at a specific position in the World. Viewers of that // position will be able to hear the sound if they are close enough. func (tx *Tx) PlaySound(pos mgl64.Vec3, s Sound) { diff --git a/server/world/viewer.go b/server/world/viewer.go index eab45c57f..7ffbbbebf 100644 --- a/server/world/viewer.go +++ b/server/world/viewer.go @@ -49,8 +49,8 @@ type Viewer interface { // ViewEntityState views the current state of an Entity. It is called whenever an Entity changes its // physical appearance, for example when sprinting. ViewEntityState(e Entity) - // ViewEntityAnimation starts viewing an animation performed by an Entity. The animation has to be from a resource pack. - ViewEntityAnimation(e Entity, animationName string) + // ViewEntityAnimation starts viewing an animation performed by an Entity. + ViewEntityAnimation(e Entity, a EntityAnimation) // ViewParticle views a particle spawned at a given position in the world. It is called when a particle, // for example a block breaking particle, is spawned near the player. ViewParticle(pos mgl64.Vec3, p Particle) @@ -91,7 +91,7 @@ func (NopViewer) ViewEntityItems(Entity) func (NopViewer) ViewEntityArmour(Entity) {} func (NopViewer) ViewEntityAction(Entity, EntityAction) {} func (NopViewer) ViewEntityState(Entity) {} -func (NopViewer) ViewEntityAnimation(Entity, string) {} +func (NopViewer) ViewEntityAnimation(Entity, EntityAnimation) {} func (NopViewer) ViewParticle(mgl64.Vec3, Particle) {} func (NopViewer) ViewSound(mgl64.Vec3, Sound) {} func (NopViewer) ViewBlockUpdate(cube.Pos, Block, int) {}