| name | add-custom-mob |
| description | Use when adding new mobs or entities. Covers entity class, model, renderer, client registration, spawn config, loot tables. |
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)
Note: 1.21.3 uses 1.21.2 modules (no separate directory)
Registration (per version)
Platform-Specific Registration (CRITICAL - Common source of crashes)
Resource Files (per version)
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() {
}
}
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, );
}
{
.setupAnim(state);
}
}
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;
public class MobNameModel extends EntityModel<MobNameEntity> {
public MobNameModel(ModelPart root) {
this.body = root.getChild("body");
}
@Override
public void setupAnim(MobNameEntity entity, float limbSwing, float limbSwingAmount,
float ageInTicks, float netHeadYaw, float headPitch) {
}
@Override
public void renderToBuffer(PoseStack poseStack, VertexConsumer buffer,
int packedLight, int packedOverlay, int color) {
body.render(poseStack, buffer, packedLight, packedOverlay, color);
}
@Override
public void renderToBuffer(PoseStack poseStack, VertexConsumer buffer,
packedLight, packedOverlay,
red, green, blue, 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
public class MobNameRenderer extends MobRenderer<MobNameEntity, MobNameModel> {
@Override
public ResourceLocation getTextureLocation(MobNameEntity entity) {
return TEXTURE;
}
}
4. RenderState Class (1.21.2 Only)
Create in common-1.21.2/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 {
}
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)
.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
FabricDefaultAttributeRegistry.register(ModEntities.MOB_NAME.get(), MobNameEntity.createAttributes());
SpawnPlacements.register(
ModEntities.MOB_NAME.get(),
SpawnPlacementTypes.ON_GROUND,
Heightmap.Types.MOTION_BLOCKING_NO_LEAVES,
Monster::checkMonsterSpawnRules
);
SpawnPlacements.register(
ModEntities.MOB_NAME.get(),
SpawnPlacements.Type.ON_GROUND,
Heightmap.Types.MOTION_BLOCKING_NO_LEAVES,
Monster::checkMonsterSpawnRules
);
Fabric - ChronoDawnClientFabric.java
IMPORTANT: Don't forget to add imports at the top of the file!
import com.chronodawn.client.model.MobNameModel;
import com.chronodawn.client.renderer.mobs.MobNameRenderer;
EntityModelLayerRegistry.registerModelLayer(MobNameModel.LAYER_LOCATION, MobNameModel::createBodyLayer);
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
event.put(ModEntities.MOB_NAME.get(), MobNameEntity.createAttributes().build());
event.register(
ModEntities.MOB_NAME.get(),
SpawnPlacementTypes.ON_GROUND,
Heightmap.Types.MOTION_BLOCKING_NO_LEAVES,
Monster::checkMonsterSpawnRules,
SpawnPlacementRegisterEvent.Operation.AND
);
NeoForge - ChronoDawnClientNeoForge.java
event.registerEntityRenderer(ModEntities.MOB_NAME.get(), MobNameRenderer::new);
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"
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
Version API Differences Summary
| Feature | 1.20.1 | 1.21.1 | 1.21.2/1.21.3 |
|---|
| Spawn type enum | MobSpawnType | MobSpawnType | EntitySpawnReason |
| SynchedEntityData | defineSynchedData() no params | defineSynchedData(Builder) | defineSynchedData(Builder) |
| EntityModel type param | EntityModel<Entity> | EntityModel<Entity> | EntityModel<RenderState> |
| MobRenderer type params | 2 (Entity, Model) | 2 (Entity, Model) | 3 (Entity, RenderState, Model) |
| renderToBuffer color | float r,g,b,a | int color | N/A (handled by RenderState) |
| EntityType.Builder.build() | build("name") | build(ResourceKey) | build(ResourceKey) |
| SpawnPlacements.Type | SpawnPlacements.Type | SpawnPlacementTypes | SpawnPlacementTypes |
| Loot table directory | loot_tables/ | loot_table/ | loot_table/ |
| Looting enchant function | looting_enchant | enchanted_count_increase | enchanted_count_increase |
| RenderState class | Not needed | Not needed | Required |
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" |