A modular entity AI library for Minestom servers.
- Priority-based behavior system
- Sensor system — scan the environment independently from behavior logic
- Type-safe memory with
MemoryType<T> - 2D A* pathfinding for ground entities, 3D A* for flying/swimming entities
- Weighted random behavior selection
- Vanilla registry attributes — entities use Minecraft's default stats automatically
- Java & Kotlin compatible
- Extensible — create your own sensors, memory types, executors, and evaluators
It depends on what you're building.
MobMind is designed for projects that need complex AI or AI that behaves similarly to vanilla Minecraft. If you're creating many custom entities, want reusable behaviors, or need more advanced decision-making, MobMind can save a lot of time in the long run.
However, MobMind is not the right choice for every project.
If your server only needs a few simple entities with straightforward AI, writing the logic directly inside your entity classes will usually be simpler. In that case, you may even want to look for a alternative (Stommobs, etc).
MobMind introduces concepts such as behaviors, sensors, memory, and controllers, so there is a learning curve before its benefits become apparent.
api/ ← Interfaces and factories. Only touch this.
core/ ← Implementations. Never import directly.
vanilla/ ← Vanilla entity implementations.
example/ ← Example Minestom server.
repositories {
maven("https://jitpack.io")
}
dependencies {
implementation("com.github.Kanelucky.MobMind:api:0.2.0")
implementation("com.github.Kanelucky.MobMind:core:0.2.0")
}MobMind.INSTANCE.init(new CoreInitializer());Stats are loaded from Minecraft's entity registry automatically:
public class MyZombie extends HostileMob {
public MyZombie() { super(EntityType.ZOMBIE); }
@Override public Key getMobKey() { return Key.key("myplugin", "my_zombie"); }
@Override public SoundEvent getHurtSound() { return SoundEvent.ENTITY_ZOMBIE_HURT; }
// Override only if you want custom values:
// @Override protected double getBaseHealth() { return 200.0; }
}public class MyZombie extends IntelligentEntity {
private final BehaviorGroup behaviorGroup;
public MyZombie() {
super(EntityType.ZOMBIE);
this.behaviorGroup = buildBehaviorGroup().withEntity(this);
}
@Override public Key getMobKey() { return Key.key("myplugin", "my_zombie"); }
@Override public BehaviorGroup getBehaviorGroup() { return behaviorGroup; }
private BehaviorGroup buildBehaviorGroup() {
return BehaviorGroup.builder()
.sensor(Sensors.nearestPlayer())
.sensor(Sensors.hurtBy())
.behavior(
BehaviorImpl.builder()
.executor(Executors.meleeAttack(MemoryTypes.HURT_BY))
.evaluator(Evaluators.hasMemory(MemoryTypes.HURT_BY))
.priority(4).period(1).build()
)
.behavior(
BehaviorImpl.builder()
.executor(Executors.meleeAttack(MemoryTypes.NEAREST_PLAYER))
.evaluator(Evaluators.hasMemory(MemoryTypes.NEAREST_PLAYER))
.priority(3).period(1).build()
)
.behavior(
BehaviorImpl.builder()
.executor(Executors.roamBuilder().speed(0.1).maxRange(8).build())
.evaluator(entity -> true)
.priority(1).period(1).build()
)
.controller(Controllers.walk())
.controller(Controllers.look())
.build();
}
}The AI brain. Manages behaviors, sensors, memory, and controllers.
BehaviorGroup.builder()
.sensor(...)
.behavior(...)
.controller(Controllers.walk())
.controller(Controllers.look())
.build()
.withEntity(this); // bind entity in one lineA unit of AI logic. Has an executor (what to do) and an evaluator (when to activate).
BehaviorImpl.builder()
.executor(Executors.idle(20, 60))
.evaluator(entity -> true)
.priority(1)
.period(20)
.build()Type-safe key-value storage per entity.
// Built-in types
MemoryTypes.NEAREST_PLAYER // Player
MemoryTypes.NEAREST_FEEDING_PLAYER // Player
MemoryTypes.ATTACK_TARGET // LivingEntity
MemoryTypes.MOVE_TARGET // Point
MemoryTypes.LOOK_TARGET // Point
MemoryTypes.PANIC_TICKS // Integer
MemoryTypes.IS_IN_LOVE // Boolean
MemoryTypes.HURT_BY // LivingEntity
MemoryTypes.HURT_BY_TICKS // Integer
// Custom types
public static final MemoryType<LivingEntity> AGGRO_TARGET =
new MemoryType<>("myplugin:aggro_target");Sensors.nearestPlayer()
Sensors.nearestPlayer(24.0, 0.0, 10)
Sensors.nearestFeedingPlayer()
Sensors.hurtBy()
Sensors.hurtBy(200)
Sensors.nearestEntity(MY_TARGET, EntityCreature.class)
Sensors.nearestEntity(MY_TARGET, EntityCreature.class, 16.0, 0.0, 20,
e -> e.getEntityType() == EntityType.WOLF)Tracks the last entity that damaged this entity. Stores it in MemoryTypes.HURT_BY for a configurable duration:
.sensor(Sensors.hurtBy(100))
// Retaliate against attacker
.behavior(
BehaviorImpl.builder()
.executor(Executors.meleeAttack(MemoryTypes.HURT_BY))
.evaluator(Evaluators.hasMemory(MemoryTypes.HURT_BY))
.priority(4).period(1).build()
)Finds the nearest entity of any type — not limited to players:
public static final MemoryType<EntityCreature> NEAREST_WOLF =
new MemoryType<>("myplugin:nearest_wolf");
.sensor(Sensors.nearestEntity(NEAREST_WOLF, EntityCreature.class, 16.0, 0.0, 20,
e -> e.getEntityType() == EntityType.WOLF))
.behavior(
BehaviorImpl.builder()
.executor(Executors.flee(NEAREST_WOLF))
.evaluator(Evaluators.hasMemory(NEAREST_WOLF))
.priority(5).period(1).build()
)public class LowHealthSensor implements Sensor {
@Override public int getPeriod() { return 5; }
@Override
public void sense(EntityCreature entity) {
if (!(entity instanceof IntelligentEntity e)) return;
boolean isLow = entity.getHealth() / entity.getAttributeValue(Attribute.MAX_HEALTH) < 0.3;
e.getBehaviorGroup().getMemoryStorage().set(MyMemoryTypes.IS_LOW_HEALTH, isLow);
}
}| Method | Description |
|---|---|
Evaluators.panic() |
True when PANIC_TICKS > 0 |
Evaluators.inLove() |
True when IS_IN_LOVE and ready to breed |
Evaluators.probability(chance, outOf) |
Random chance |
Evaluators.hasMemory(type) |
True when memory type has a value |
Evaluators.lacksMemory(type) |
True when memory type is empty |
Evaluators.hasAllMemory(types...) |
True when all memory types have values |
Evaluators.hasAnyMemory(types...) |
True when any memory type has a value |
// Before
.evaluator(entity -> {
if (!(entity instanceof IntelligentEntity e)) return false;
return e.getBehaviorGroup().getMemoryStorage().get(MemoryTypes.NEAREST_PLAYER) != null;
})
// After
.evaluator(Evaluators.hasMemory(MemoryTypes.NEAREST_PLAYER))// Lambda
.evaluator(entity -> entity.getHealth() < 5)
// Class
public class IsLowHealthEvaluator implements BehaviorEvaluator {
@Override
public boolean evaluate(EntityCreature entity) {
if (!(entity instanceof IntelligentEntity e)) return false;
return Boolean.TRUE.equals(
e.getBehaviorGroup().getMemoryStorage().get(MyMemoryTypes.IS_LOW_HEALTH)
);
}
}| Method | Description |
|---|---|
Executors.idle(min, max) |
Stand still |
Executors.roam(...) |
Wander randomly |
Executors.wander(radius) |
Move to one random position |
Executors.lookAround(min, max) |
Look in a random direction |
Executors.followEntity(memory, ...) |
Follow an entity |
Executors.lookAtEntity(memory, duration) |
Look at an entity |
Executors.meleeAttack(memory, ...) |
Chase and attack |
Executors.beamAttack(memory, ...) |
Delayed beam attack |
Executors.shootProjectile(memory, ..., supplier) |
Shoot a projectile |
Executors.jump(...) |
Jump periodically |
Executors.panic(...) |
Flee in a random direction |
Executors.flee(memory, ...) |
Run away from a specific entity |
Executors.breeding(...) |
Find a mate and spawn offspring |
Executors.eatGrass(duration, callback) |
Eat grass |
Executors.moveToTarget(memory, ...) |
Move to a position |
For executors with many parameters, builders are available:
// Verbose
Executors.roam(0.1, 0.1, 8, 20, false, 100, false, 10)
// Builder
Executors.roamBuilder()
.speed(0.1)
.maxRange(8)
.avoidWater(false)
.build()
Executors.meleeAttackBuilder(MemoryTypes.NEAREST_PLAYER)
.speed(0.1)
.attackCooldown(15)
.onAttack((attacker, target) -> {
if (attacker instanceof LivingEntity le) le.swingMainHand();
})
.build()
Executors.fleeBuilder(MY_THREAT)
.fleeSpeed(0.2)
.maxFleeRangeSq(400.0)
.build()public class SummonLightningExecutor implements BehaviorExecutor {
private int cooldown = 0;
@Override
public boolean execute(EntityCreature entity) {
if (cooldown-- > 0) return true;
// custom logic here
cooldown = 60;
return true;
}
@Override public void onStart(EntityCreature entity) { cooldown = 0; }
@Override public void onStop(EntityCreature entity) {}
}Behaviors.weighted(
Set.<Behavior>of(
BehaviorImpl.builder()
.executor(Executors.idle(20, 60))
.evaluator(entity -> true)
.priority(1).weight(1).period(1).build(),
BehaviorImpl.builder()
.executor(Executors.roam())
.evaluator(entity -> true)
.priority(1).weight(3).period(1).build()
),
1, // priority
40 // period
)public class MySheep extends IntelligentEntity
implements Breedable, Feedable, Offspring {
private boolean baby = false;
private int breedCooldown = 0;
@Override public boolean isBaby() { return baby; }
@Override public void setBaby(boolean baby) { this.baby = baby; }
@Override public int getBreedCooldown() { return breedCooldown; }
@Override public void setBreedCooldown(int cooldown) { this.breedCooldown = cooldown; }
@Override
public boolean isBreedingItem(ItemStack item) {
return item.material() == Material.WHEAT;
}
@Override
public EntityCreature createOffspring() {
MySheep lamb = new MySheep();
lamb.setBaby(true);
return lamb;
}
}This project is licensed under Apache License. Please see the LICENSE file for details.