Skip to content

Developer Guide

Markus Bordihn edited this page Jun 16, 2026 · 3 revisions

Developer Integration

This page is for mod developers who want to render their own entities or block entities through Easy Model Entities. EME handles profile loading, model baking, basic animation, and rendering; your mod keeps ownership of its gameplay logic.

📦 Dependency

Artifacts are split by loader. Use the artifact that matches your Minecraft and EME version.

Common:

compileOnly "de.markusbordihn.easymodelentities:easy_model_entities-common-${minecraft_version}:1.0.0"

Fabric:

modImplementation "de.markusbordihn.easymodelentities:easy_model_entities-fabric-${minecraft_version}:1.0.0"

Forge:

implementation fg.deobf("de.markusbordihn.easymodelentities:easy_model_entities-forge-${minecraft_version}:1.0.0")

🧩 Renderable Objects

Custom entities or block entities implement EasyModelRenderable.

public class MyBlockEntity extends BlockEntity implements EasyModelRenderable {

  private static final ResourceLocation PROFILE_ID =
    new ResourceLocation("my_mod", "disguised_chest");

  @Override
  public ResourceLocation getEasyModelProfileId() {
    return PROFILE_ID;
  }
}

If the server profile and render profile use the same ID, getEasyModelProfileId() is enough. Override getEasyModelRenderProfileId(), getEasyModelVersion(), or getEasyModelAnimationState() only when the runtime state differs from the server profile.

🧍 Entity Renderer

Use the entity render delegate for custom entity renderers:

public class MyEntityRenderer extends EntityRenderer<MyEntity> {

  private final EasyModelEntityRenderDelegate<MyEntity> delegate =
    EasyModelEntitiesClientApi.createRenderDelegate();

  @Override
  public void render(
    MyEntity entity,
    float entityYaw,
    float partialTick,
    PoseStack poseStack,
    MultiBufferSource buffer,
    int packedLight) {
    this.delegate.render(entity, entityYaw, partialTick, poseStack, buffer, packedLight);
    super.render(entity, entityYaw, partialTick, poseStack, buffer, packedLight);
  }

  @Override
  public ResourceLocation getTextureLocation(MyEntity entity) {
    return this.delegate.getTextureLocation(entity);
  }
}

Pack-side setup and command examples are in Entities.

Current EME host entities only provide static and generic ground behavior. Mods that need swimming, flying, projectiles, vehicles, or custom AI should keep their own entity class and use EME as the render/profile layer.

🧱 Block Entity Renderer

Use the block entity render delegate for custom block entity renderers:

public class MyBlockEntityRenderer implements BlockEntityRenderer<MyBlockEntity> {

  private final EasyModelBlockEntityRenderDelegate<MyBlockEntity> delegate =
    EasyModelEntitiesClientApi.createBlockEntityRenderDelegate();

  @Override
  public void render(
    MyBlockEntity blockEntity,
    float partialTick,
    PoseStack poseStack,
    MultiBufferSource buffer,
    int packedLight,
    int packedOverlay) {
    EasyModelBlockEntityRenderOptions options =
      EasyModelBlockEntityRenderOptions.DEFAULT.withYawDegrees(180.0f);

    this.delegate.render(blockEntity, partialTick, poseStack, buffer, packedLight, options);
  }
}

Pack-side setup and command examples are in Block Entities.

🎞️ Random Idle

For EME host block entities, use animated_randomly in the server profile:

{
  "model_type": "block_entity",
  "preset_type": "animated_randomly",
  "client": {
    "render_profile": "my_mod:shrine"
  }
}

For render-only integrations, use random_idle in the render profile:

{
  "preset_type": "static",
  "model": "my_mod:easy_model_entities/models/disguised_chest",
  "texture": "my_mod:textures/block/disguised_chest.png",
  "animation": {
    "mode": "random_idle"
  }
}

The block entity delegate schedules the idle animation client-side when animation.mode is random_idle and no explicit animationTicks override is provided.

⚙️ Custom Part Animation

Mods can return transforms per model part. The baked model is unchanged; the transform is applied only for the current render call.

EasyModelPartAnimator animator =
  context -> {
    if ("crystal".equals(context.partName())) {
      float spin = context.ageInTicks() * 0.04f;
      return new EasyModelPartTransform(0.0f, spin, 0.0f);
    }

    return EasyModelPartTransform.NONE;
  };

EasyModelBlockEntityRenderOptions options =
  EasyModelBlockEntityRenderOptions.DEFAULT.withPartAnimator(animator);

this.delegate.render(blockEntity, partialTick, poseStack, buffer, packedLight, options);

The animator receives an EasyModelPartAnimationContext with part name, body type, limbSwing, limbSwingAmount, ageInTicks, and automaticTransform. Use automaticTransform when your transform should build on EME's automatic animation.

➕ Add Or Replace

EasyModelPartAnimationMode.ADD is the default. The animator result is added to the automatic EME transform.

EasyModelBlockEntityRenderOptions options =
  EasyModelBlockEntityRenderOptions.DEFAULT
    .withPartAnimator(animator)
    .withPartAnimationMode(EasyModelPartAnimationMode.ADD);

EasyModelPartAnimationMode.REPLACE replaces the automatic transform for that part. context.automaticTransform() is still available if the animator wants to reuse it manually.

EasyModelBlockEntityRenderOptions options =
  EasyModelBlockEntityRenderOptions.DEFAULT
    .withPartAnimator(animator)
    .withPartAnimationMode(EasyModelPartAnimationMode.REPLACE);

For entities, use the same animator through EasyModelEntityRenderOptions:

EasyModelEntityRenderOptions options =
  EasyModelEntityRenderOptions.DEFAULT
    .withPartAnimator(animator)
    .withPartAnimationMode(EasyModelPartAnimationMode.REPLACE);

this.delegate.render(entity, entityYaw, partialTick, poseStack, buffer, packedLight, options);

Set animation.mode to none in the render profile when EME should not create automatic transforms at all.

🔎 Reading Model Parts

Renderers can read the public, read-only part structure:

List<EasyModelPartDefinition> roots = this.delegate.rootModelParts(blockEntity);
List<EasyModelPartDefinition> parts = this.delegate.modelParts(blockEntity);

Entity delegates expose the same methods for entity models. Each part exposes name, offset, rotation, and children. Cube and renderer internals stay hidden.

🧰 Server API

Server-side helpers:

boolean known = EasyModelEntitiesApi.hasProfile(profileId);
Optional<EasyModelEntityProfile> profile = EasyModelEntitiesApi.getProfile(profileId);
Optional<Entity> entity = EasyModelEntitiesApi.createEntity(level, profileId, position);

Optional<ResourceLocation> currentProfile = EasyModelBlockEntitiesApi.getProfileId(blockEntity);
boolean changed = EasyModelBlockEntitiesApi.setProfileId(blockEntity, profileId);

For custom entities and block entities, EasyModelRenderable plus the render delegate is usually the only integration needed.

external_owner and several non-ground presets are currently WIP from a host behavior perspective. They are useful for schemas, render defaults, and mod-owned integrations, but should not be documented as complete built-in mob behavior yet.

Clone this wiki locally