Entities
What a Strut entity is, what it does not need to declare, and how its files are found.
The class
Any entity can be a Strut entity: implement StrutEntity.
public class HeroEntity extends PathAwareEntity implements StrutEntity {
public HeroEntity(EntityType<? extends HeroEntity> type, World world) {
super(type, world);
}
}
Everything StrutEntity asks of you has a default:
| Was required | Now |
|---|---|
| A field holding the animation cache, and its getter | Held in a slot Strut adds to every entity, created on first use. Declare your own and yours wins. |
getTick | The entity's age in ticks. Override it for a non-ticking entity. |
registerControllers | Optional. When the server plays an animation on a controller name that has none, one is created on the client. Register controllers only to tune one (transition length, triggerable animations) or to animate on the client. |
| A model class, a renderer class, and a registration call | One line: StrutClient.entity(type). |
Registering the renderer
StrutClient is Strut's client entry point. Call these from your own client initialiser, once per entity type.
StrutClient.entity(AVA);
This uses the convention below. StrutClient.entity has three forms:
| Call | Use it when |
|---|---|
entity(type) | The files follow the convention. |
entity(type, model) | You need to change which model, texture or animation file is used. |
entity(type, factory) | You want a renderer of your own: a subclass of StrutEntityRenderer, or one with withScale and render layers. |
The convention
An entity type with the registry id namespace:path is rendered from:
| File | Location |
|---|---|
| Model | assets/namespace/strut/models/entity/path.strutmodel (or assets/namespace/strut/models/entity/path.strutmodel: both folders are read, and a model asks for the id of the file's full path) |
| Texture | assets/namespace/textures/entity/path.png |
| Animations | assets/namespace/strut/animations/entity/path.strutanim (or strut/animations/entity/path.strutanim; a data pack's server timelines are read from the same two folders under data/namespace/) |
A nested id nests the files: mymod:creatures/ava reads strut/models/entity/creatures/ava.strutmodel.
Changing what differs
Start from the conventional model and replace one part. withAltModel, withAltTexture and withAltAnimations take an id and apply the same folder convention to it, so several entities can share a texture:
StrutClient.entity(NIA, StrutModel.<HeroEntity>entity(Identifier.of("mymod", "nia")).withAltTexture(shared));
When the files do not follow the convention at all, build the model from ids, or from functions of the entity so the model can change with its state:
return StrutModel.<HeroEntity>builder()
.model(creature -> Identifier.of("mymod", "strut/models/forested/" + creature.getName().getString() + ".strutmodel"))
.texture(creature -> Identifier.of("mymod", "textures/entities/" + creature.getName().getString() + ".png"))
.animation(Identifier.of("mymod", "strut/animations/creature.strutanim"))
.build();
The builder refuses to build() unless the model, texture and animation are all set, and names the one that is missing.
A renderer of your own
Pass a factory when you need more than a model, for example a scale:
StrutClient.entity(AVA, context -> new StrutEntityRenderer<>(context, AVA).withScale(1.2f));
Server-driven by default
A StrutEntity's controllers are server-authoritative: the server decides what plays, the client only mirrors it. That is what makes late joiners, lag and events reliable. A controller can be opted out to decide locally (AuthorityMode.CLIENT_COSMETIC), which is right for purely visual flourishes. Everything is in Playing animations.
Formats. The convention points at the binary formats. Legacy .geo.json and .animation.json files can still be loaded by naming them explicitly, but nothing new (events, the compact format, the Blockbench plugin) is built for them.