--- 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, 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) - [ ] `ModEntities.java` - Entity type registration - [ ] `ModItems.java` - Spawn egg registration (see [custom-mob-spawn-egg skill](#spawn-egg)) ### 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 ```java 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 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 ```java @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) ```java 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 { 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) ```java 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 { 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 ```java 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 { 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 ```java // MobRenderer has 2 type parameters: public class MobNameRenderer extends MobRenderer { @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/` ```java 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) ```java public static final RegistrySupplier> 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 ```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! ```java // 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 ```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 ```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`): ```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 ```json "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`: ```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` | `EntityModel` | `EntityModel` | `EntityModel` | | 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"` |