Back to skills
extension
Category: Development & EngineeringNo API key required

add-custom-mob

Use when adding new mobs or entities. Covers entity class, model, renderer, client registration, spawn config, loot tables.

personAuthor: jakexiaohubgithub

Add Custom Mob to ChronoDawn

Purpose: Complete guide for adding a new mob entity to ChronoDawn with full multi-version and multi-loader support.


Quick Checklist

When adding a new mob, complete ALL of the following:

Java Classes (per module: 1.20.1, 1.21.1, 1.21.2, 1.21.4)

Note: 1.21.3 uses 1.21.2 modules (no separate directory)

  • [ ] Entity class (entities/mobs/MobNameEntity.java)
  • [ ] Model class (client/model/MobNameModel.java)
  • [ ] Renderer class (client/renderer/mobs/MobNameRenderer.java)
  • [ ] RenderState class (client/renderer/mobs/MobNameRenderState.java) - 1.21.2 and 1.21.4 only

Registration (per version)

Platform-Specific Registration (CRITICAL - Common source of crashes)

  • [ ] Fabric ChronoDawnFabric.java - Attribute & spawn placement registration
  • [ ] Fabric ChronoDawnClientFabric.java - Import statements for Model and Renderer
  • [ ] Fabric ChronoDawnClientFabric.java - Model layer registration (EntityModelLayerRegistry.registerModelLayer)
  • [ ] Fabric ChronoDawnClientFabric.java - Renderer registration (EntityRendererRegistry.register)
  • [ ] NeoForge ChronoDawnNeoForge.java - Attribute & spawn placement registration
  • [ ] NeoForge ChronoDawnClientNeoForge.java - Model layer & renderer registration
  • [ ] NeoForge ChronoDawnClientNeoForge.java - Spawn egg color handler

Resource Files (per version)

  • [ ] Texture file (textures/entity/mobs/mob_name.png)
  • [ ] Loot table (loot_table/entities/mob_name.json or loot_tables/ for 1.20.1)
  • [ ] Spawn egg model (models/item/mob_name_spawn_egg.json)
  • [ ] Spawn egg item definition (items/mob_name_spawn_egg.json) - 1.21.4 only
  • [ ] Language files (lang/en_us.json, lang/ja_jp.json)
  • [ ] Biome spawn configuration (if replacing vanilla mob)

Vanilla Mob Reference Parameters

When creating a mob based on a vanilla mob, use these reference values:

| Base Mob | sized(w, h) | Health | Attack | Speed | Other Attributes | |----------|-------------|--------|--------|-------|------------------| | Iron Golem | 1.4f, 2.7f | 100 | 7.5-21 | 0.25 | Knockback Resistance 1.0 | | Enderman | 0.6f, 2.9f | 40 | 7 | 0.3 | Follow Range 64 | | Zombie | 0.6f, 1.95f | 20 | 3 | 0.23 | - | | Spider | 1.4f, 0.9f | 16 | 2 | 0.3 | Wall climbing | | Skeleton | 0.6f, 1.99f | 20 | - | 0.25 | Bow attack | | Chicken | 0.4f, 0.7f | 4 | - | 0.25 | - | | Pig | 0.9f, 0.9f | 10 | - | 0.25 | - | | Rabbit | 0.4f, 0.5f | 3 | - | 0.3 | - |

Tip: For hostile versions of friendly mobs, consider increasing Health and adding Attack damage.


1. Entity Class

Create in common-{version}/src/main/java/com/chronodawn/entities/mobs/

1.21.2 Template

package com.chronodawn.entities.mobs;

import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.level.Level;

public class MobNameEntity extends Monster {

    public MobNameEntity(EntityType<? extends MobNameEntity> entityType, Level level) {
        super(entityType, level);
    }

    public static AttributeSupplier.Builder createAttributes() {
        return Monster.createMonsterAttributes()
                .add(Attributes.MAX_HEALTH, 20.0D)
                .add(Attributes.ATTACK_DAMAGE, 3.0D)
                .add(Attributes.MOVEMENT_SPEED, 0.3D)
                .add(Attributes.FOLLOW_RANGE, 35.0D);
    }

    @Override
    protected void registerGoals() {
        // Add AI goals here
    }

    // 1.21.2: Use EntitySpawnReason, SynchedEntityData.Builder
    // Example for synched data:
    // @Override
    // protected void defineSynchedData(SynchedEntityData.Builder builder) {
    //     super.defineSynchedData(builder);
    //     builder.define(DATA_FLAGS, (byte)0);
    // }
}

1.21.1 Template

Key differences from 1.21.2:

  • Use MobSpawnType instead of EntitySpawnReason
  • SynchedEntityData.Builder pattern same as 1.21.2

1.20.1 Template

Key differences:

  • Use MobSpawnType instead of EntitySpawnReason
  • defineSynchedData() has no parameters, use this.entityData.define() directly
@Override
protected void defineSynchedData() {
    super.defineSynchedData();
    this.entityData.define(DATA_FLAGS, (byte)0);
}

2. Model Class

Create in common-{version}/src/main/java/com/chronodawn/client/model/

1.21.2 Template (RenderState pattern)

package com.chronodawn.client.model;

import com.chronodawn.client.renderer.mobs.MobNameRenderState;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.model.geom.builders.*;

public class MobNameModel extends EntityModel<MobNameRenderState> {

    private final ModelPart body;

    public MobNameModel(ModelPart root) {
        super(root);
        this.body = root.getChild("body");
    }

    public static LayerDefinition createBodyLayer() {
        MeshDefinition meshdefinition = new MeshDefinition();
        PartDefinition partdefinition = meshdefinition.getRoot();

        partdefinition.addOrReplaceChild("body",
            CubeListBuilder.create()
                .texOffs(0, 0)
                .addBox(-4.0F, -8.0F, -4.0F, 8.0F, 8.0F, 8.0F),
            PartPose.offset(0.0F, 24.0F, 0.0F));

        return LayerDefinition.create(meshdefinition, 64, 32);
    }

    @Override
    public void setupAnim(MobNameRenderState state) {
        super.setupAnim(state);
        // Animation logic using state.walkAnimationPos, state.walkAnimationSpeed
    }
}

1.21.1 / 1.20.1 Template (Entity pattern)

package com.chronodawn.client.model;

import com.chronodawn.entities.mobs.MobNameEntity;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.model.geom.ModelPart;
// ... other imports

public class MobNameModel extends EntityModel<MobNameEntity> {

    public MobNameModel(ModelPart root) {
        // 1.21.1/1.20.1: No super(root) call
        this.body = root.getChild("body");
    }

    // Same createBodyLayer() as 1.21.2

    @Override
    public void setupAnim(MobNameEntity entity, float limbSwing, float limbSwingAmount,
                          float ageInTicks, float netHeadYaw, float headPitch) {
        // Animation logic
    }

    // 1.21.1: renderToBuffer with int color
    @Override
    public void renderToBuffer(PoseStack poseStack, VertexConsumer buffer,
                               int packedLight, int packedOverlay, int color) {
        body.render(poseStack, buffer, packedLight, packedOverlay, color);
    }

    // 1.20.1: renderToBuffer with float RGBA
    @Override
    public void renderToBuffer(PoseStack poseStack, VertexConsumer buffer,
                               int packedLight, int packedOverlay,
                               float red, float green, float blue, float alpha) {
        body.render(poseStack, buffer, packedLight, packedOverlay, red, green, blue, alpha);
    }
}

3. Renderer Class

Create in common-{version}/src/main/java/com/chronodawn/client/renderer/mobs/

1.21.2 Template

package com.chronodawn.client.renderer.mobs;

import com.chronodawn.ChronoDawn;
import com.chronodawn.client.model.MobNameModel;
import com.chronodawn.entities.mobs.MobNameEntity;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.MobRenderer;
import net.minecraft.resources.ResourceLocation;

public class MobNameRenderer extends MobRenderer<MobNameEntity, MobNameRenderState, MobNameModel> {

    private static final ResourceLocation TEXTURE =
        ResourceLocation.fromNamespaceAndPath(ChronoDawn.MOD_ID, "textures/entity/mobs/mob_name.png");

    public MobNameRenderer(EntityRendererProvider.Context context) {
        super(context, new MobNameModel(context.bakeLayer(MobNameModel.LAYER_LOCATION)), 0.5f);
    }

    @Override
    public ResourceLocation getTextureLocation(MobNameRenderState state) {
        return TEXTURE;
    }

    @Override
    public MobNameRenderState createRenderState() {
        return new MobNameRenderState();
    }
}

1.21.1 / 1.20.1 Template

// MobRenderer has 2 type parameters: <Entity, Model>
public class MobNameRenderer extends MobRenderer<MobNameEntity, MobNameModel> {

    @Override
    public ResourceLocation getTextureLocation(MobNameEntity entity) {
        return TEXTURE;
    }

    // No createRenderState() method needed
}

4. RenderState Class (1.21.2 and 1.21.4)

Create in common-1.21.2/ and common-1.21.4/ under src/main/java/com/chronodawn/client/renderer/mobs/

package com.chronodawn.client.renderer.mobs;

import net.minecraft.client.renderer.entity.state.LivingEntityRenderState;

public class MobNameRenderState extends LivingEntityRenderState {
    // Add custom state fields if needed for animations
}

5. Entity Registration (ModEntities.java)

public static final RegistrySupplier<EntityType<MobNameEntity>> MOB_NAME = ENTITIES.register(
    "mob_name",
    () -> EntityType.Builder.of(MobNameEntity::new, MobCategory.MONSTER)
        .sized(0.6f, 1.8f)  // width, height
        .clientTrackingRange(8)
        .updateInterval(3)
        .build(ResourceKey.create(Registries.ENTITY_TYPE,
            CompatResourceLocation.create(ChronoDawn.MOD_ID, "mob_name")))
);

1.20.1 difference: Use .build("mob_name") instead of .build(ResourceKey...)


6. Platform-Specific Registration

Fabric - ChronoDawnFabric.java

// Attributes
FabricDefaultAttributeRegistry.register(ModEntities.MOB_NAME.get(), MobNameEntity.createAttributes());

// Spawn placement (1.21.1/1.21.2)
SpawnPlacements.register(
    ModEntities.MOB_NAME.get(),
    SpawnPlacementTypes.ON_GROUND,
    Heightmap.Types.MOTION_BLOCKING_NO_LEAVES,
    Monster::checkMonsterSpawnRules
);

// Spawn placement (1.20.1)
SpawnPlacements.register(
    ModEntities.MOB_NAME.get(),
    SpawnPlacements.Type.ON_GROUND,  // Note: different enum
    Heightmap.Types.MOTION_BLOCKING_NO_LEAVES,
    Monster::checkMonsterSpawnRules
);

Fabric - ChronoDawnClientFabric.java

IMPORTANT: Don't forget to add imports at the top of the file!

// Required imports (add to import section)
import com.chronodawn.client.model.MobNameModel;
import com.chronodawn.client.renderer.mobs.MobNameRenderer;

// In registerEntityModelLayers() method - Model layer registration
EntityModelLayerRegistry.registerModelLayer(MobNameModel.LAYER_LOCATION, MobNameModel::createBodyLayer);

// In registerEntityRenderers() method - Renderer registration
EntityRendererRegistry.register(ModEntities.MOB_NAME.get(), MobNameRenderer::new);

Common Mistake: Forgetting either the imports OR the registrations will cause a NullPointerException crash when spawning the entity.

NeoForge - ChronoDawnNeoForge.java

// In onEntityAttributeCreation event
event.put(ModEntities.MOB_NAME.get(), MobNameEntity.createAttributes().build());

// In onSpawnPlacementRegister event
event.register(
    ModEntities.MOB_NAME.get(),
    SpawnPlacementTypes.ON_GROUND,
    Heightmap.Types.MOTION_BLOCKING_NO_LEAVES,
    Monster::checkMonsterSpawnRules,
    SpawnPlacementRegisterEvent.Operation.AND
);

NeoForge - ChronoDawnClientNeoForge.java

// In onRegisterEntityRenderers event
event.registerEntityRenderer(ModEntities.MOB_NAME.get(), MobNameRenderer::new);

// In onRegisterLayerDefinitions event
event.registerLayerDefinition(MobNameModel.LAYER_LOCATION, MobNameModel::createBodyLayer);

7. Resource Files

Loot Table

1.21.1 / 1.21.2 / 1.21.3 (data/chronodawn/loot_table/entities/mob_name.json):

{
  "type": "minecraft:entity",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:item",
          "name": "minecraft:bone",
          "functions": [
            {
              "function": "minecraft:set_count",
              "count": { "min": 0, "max": 2, "type": "minecraft:uniform" }
            },
            {
              "function": "minecraft:enchanted_count_increase",
              "enchantment": "minecraft:looting",
              "count": { "type": "minecraft:uniform", "min": 0, "max": 1 }
            }
          ]
        }
      ]
    }
  ]
}

1.20.1 (data/chronodawn/loot_tables/entities/mob_name.json):

  • Directory is loot_tables (plural)
  • Use "function": "minecraft:looting_enchant" instead of enchanted_count_increase

Language Files

"entity.chronodawn.mob_name": "Mob Name",
"item.chronodawn.mob_name_spawn_egg": "Mob Name Spawn Egg"

Biome Spawn Configuration

To replace a vanilla mob in ChronoDawn biomes, edit worldgen/biome/chronodawn_*.json:

{
  "spawners": {
    "monster": [
      { "type": "chronodawn:mob_name", "weight": 100, "minCount": 4, "maxCount": 4 }
    ]
  }
}

8. Spawn Egg

IMPORTANT: See the custom-mob-spawn-egg skill for complete spawn egg implementation checklist.

Quick reminder:

  • Register in ModItems.java
  • Add to initializeSpawnEggs() and populateCreativeTab()
  • Create models/item/mob_name_spawn_egg.json with "parent": "item/template_spawn_egg"
  • Add to NeoForge color handler in ChronoDawnClientNeoForge.java
  • 1.21.4 only: Create items/mob_name_spawn_egg.json with tints array (colors in decimal)

Version API Differences Summary

| Feature | 1.20.1 | 1.21.1 | 1.21.2/1.21.3 | 1.21.4 | |---------|--------|--------|---------------|--------| | Spawn type enum | MobSpawnType | MobSpawnType | EntitySpawnReason | EntitySpawnReason | | SynchedEntityData | defineSynchedData() no params | defineSynchedData(Builder) | defineSynchedData(Builder) | defineSynchedData(Builder) | | EntityModel type param | EntityModel<Entity> | EntityModel<Entity> | EntityModel<RenderState> | EntityModel<RenderState> | | MobRenderer type params | 2 (Entity, Model) | 2 (Entity, Model) | 3 (Entity, RenderState, Model) | 3 (Entity, RenderState, Model) | | renderToBuffer color | float r,g,b,a | int color | N/A (handled by RenderState) | N/A (handled by RenderState) | | EntityType.Builder.build() | build("name") | build(ResourceKey) | build(ResourceKey) | build(ResourceKey) | | SpawnPlacements.Type | SpawnPlacements.Type | SpawnPlacementTypes | SpawnPlacementTypes | SpawnPlacementTypes | | Loot table directory | loot_tables/ | loot_table/ | loot_table/ | loot_table/ | | Looting enchant function | looting_enchant | enchanted_count_increase | enchanted_count_increase | enchanted_count_increase | | RenderState class | Not needed | Not needed | Required | Required | | Damage handling | hurt(DamageSource, float) | hurt(DamageSource, float) | hurtServer(ServerLevel, DamageSource, float) | hurtServer(ServerLevel, DamageSource, float) | | Y coord minimum | getMinBuildHeight() | getMinBuildHeight() | getMinY() | getMinY() | | Spawn egg item def | Not needed | Not needed | Not needed | items/*.json with tints |

Note: 1.21.3 is a hotfix release that shares modules with 1.21.2. No separate code changes are needed.


Common Issues

CRITICAL: The most common mistakes are missing Fabric client registrations. Always verify both model layer AND renderer are registered in ChronoDawnClientFabric.java.

| Issue | Cause | Solution | |-------|-------|----------| | Entity not spawning | Missing attribute registration | Add to Fabric/NeoForge attribute events | | Model not rendering | Missing model layer registration | Add EntityModelLayerRegistry.registerModelLayer() in ChronoDawnClientFabric.java | | Crash on entity spawn | Wrong API for version | Check version differences table above | | NullPointerException on entityRenderer | Missing Fabric renderer registration | Add EntityRendererRegistry.register() in ChronoDawnClientFabric.java | | Entity invisible | Missing renderer registration | Add to EntityRendererRegistry in BOTH Fabric and NeoForge client classes | | Spawn egg crash | Missing spawn egg color handler | See custom-mob-spawn-egg skill | | Loot table error (1.21.2) | Using old looting_enchant function | Use enchanted_count_increase with "enchantment": "minecraft:looting" |