Skip to content

Coding an entity

In this section, we’ll walk through creating a custom Capybara entity, just like the one in the Bestium Example plugin.

Minecraft has a deep and confusing entity class hierarchy, and picking the correct base class can be challenging.

Unlike traditional approaches that force you to extend a concrete class and work around AI structures, Bestium gives you full control. You can design your entity from scratch, almost like Mojang does internally.

The real power of Bestium lies in choice: you’re free to select the base class that best fits your entity’s behavior. The ideal way to determine this is to:

  • Decompile a similar vanilla entity and check which class it extends.
  • Open net.minecraft.world.entity.Entity in your IDE and explore the class hierarchy.

Here are some frequently used abstract or base classes you might consider extending:

  • Animal for passive creatures like cows, axolotls or capybaras
  • Monster for hostile mobs like zombies or skeletons
  • AbstractVillager for villagers and their variants
  • AbstractGolem for golems like iron golems or snow golems
  • AbstractFish for fish entities like pufferfish or tadpoles
  • AbstractSchoolingFish for schooling fish like cod or salmon
  • AgeableWaterCreature for water creatues with baby forms like dolphins or squids

Your class must

  • Extend the appropriate base class
  • Implement all abstract methods
  • Provide a public super-matching constructor

Your initial code should look something like this:

Capybara.kt
package com.example.myplugin
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.AgeableMob
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.animal.Animal
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
class Capybara(entityType: EntityType<out Capybara>, level: Level) : Animal(entityType, level) {
override fun isFood(stack: ItemStack): Boolean {
TODO("Not yet implemented")
}
override fun getBreedOffspring(
serverLevel: ServerLevel,
otherParent: AgeableMob
): AgeableMob? {
TODO("Not yet implemented")
}
}

If your entity extends LivingEntity it needs a set of default attributes, like health, speed, or attack damage, to function properly in the game. These are defined in a static method, typically named createAttributes(). Some attributes are required (like max health and movement speed), you will usually find out, because the server will crash 🙂.

Use the appropriate base method provided by your parent for your entity type:

  • createAnimalAttributes() for animals
  • createMonsterAttributes() for monsters

etc.

Capybara.kt
companion object {
fun createAttributes() = createAnimalAttributes()
.add(Attributes.MAX_HEALTH, 10.0)
.add(Attributes.MOVEMENT_SPEED, .25)
}

Goals power your entity’s AI, how it moves, reacts, or interacts with the world. To add goals, override the registerGoals() method in your custom entity. Use:

goalSelector.addGoal(PRIORITY, GOAL)
  • PRIORITY: The importance of this goal over others, lower numbers run first
  • GOAL: An instance of an existing goal (check the net.minecraft.world.entity.ai.goal package) or your own custom goal

Here’s an example for our capybara:

Capybara.kt
override fun registerGoals() {
goalSelector.addGoal(0, FloatGoal(this))
goalSelector.addGoal(1, PanicGoal(this, 1.25))
goalSelector.addGoal(3, BreedGoal(this, 1.0))
goalSelector.addGoal(4, TemptGoal(this, 1.2, ::isFood, false))
goalSelector.addGoal(5, FollowParentGoal(this, 1.1))
goalSelector.addGoal(6, WaterAvoidingRandomStrollGoal(this, 1.0))
goalSelector.addGoal(7, LookAtPlayerGoal(this, Player::class.java, 6.0F))
goalSelector.addGoal(8, RandomLookAroundGoal(this))
}

Now, complete your entity class by implementing the required methods:

  • isFood(ItemStack) defines what items your entity considers food.
  • getBreedOffspring() handles breeding behavior and returns the baby entity.

Your finished class will look something like this:

Capybara.kt
package com.example.myplugin
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.AgeableMob
import net.minecraft.world.entity.EntitySpawnReason
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.ai.goal.*
import net.minecraft.world.entity.animal.Animal
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.Level
open class Capybara(entityType: EntityType<out Capybara>, level: Level) : Animal(entityType, level) {
companion object {
fun createAttributes(): AttributeSupplier.Builder {
return createAnimalAttributes()
.add(Attributes.MAX_HEALTH, 10.0)
.add(Attributes.MOVEMENT_SPEED, 0.3)
}
}
override fun registerGoals() {
goalSelector.addGoal(0, FloatGoal(this))
goalSelector.addGoal(1, PanicGoal(this, 1.25))
goalSelector.addGoal(2, FollowParentGoal(this, 1.1))
goalSelector.addGoal(3, WaterAvoidingRandomStrollGoal(this, 1.0))
goalSelector.addGoal(4, LookAtPlayerGoal(this, Player::class.java, 6.0f))
goalSelector.addGoal(5, RandomLookAroundGoal(this))
}
override fun isFood(itemStack: ItemStack) = when (itemStack.item) {
Items.SEAGRASS, Items.MELON_SLICE -> true
else -> false
}
override fun getBreedOffspring(level: ServerLevel, otherParent: AgeableMob): AgeableMob? {
return type.create(level, EntitySpawnReason.BREEDING) as Capybara?
}
}