-
Notifications
You must be signed in to change notification settings - Fork 0
Entity Lifecycle and AI
Package: dev.goshi.omnimixin.api.event.entity
Ticking, despawning, spawn rules, and mob AI goal registration/targeting.
Generic per-entity lifecycle hooks — these fire for every entity, so keep
your listeners cheap, especially PRE_TICK/POST_TICK, which run every
tick for every entity in every loaded chunk.
// A custom "frozen in time" zone that stops entities from ticking at all.
PlayEntity.PRE_TICK.register(ctx -> {
// Note: PRE_TICK/POST_TICK don't carry a Cancellable - see below.
if (isInFrozenZone(ctx.entity())) {
freezeInPlace(ctx.entity());
}
});
// Custom despawn message for named/tamed entities.
PlayEntity.DESPAWN.register(ctx -> {
if (ctx.entity() instanceof TameableEntity pet && pet.hasCustomName()) {
ctx.cancel().cancel(); // keep the player's named pet from despawning
}
});
TickContext(Entity entity) — PRE_TICK / POST_TICK
Note there's no Cancellable on TickContext — you can't skip an entity's
tick entirely through this event (that would risk leaving the entity in an
inconsistent physics/AI state); use it for observing or applying your own
side effects around the tick, not for suppressing it.
DespawnContext(Entity entity, Cancellable cancel) — DESPAWN
Three events across an entity's decision to spawn into the world.
// Reduce hostile mob spawn rates near a player-built town.
PlaySpawn.CHECK_RULES.register(ctx -> {
if (isHostile(ctx.entity()) && isNearPlayerTown(ctx.entity())) {
return false; // veto this spawn
}
return true;
});
// Custom spawn-in effect (particles, sound) for any natural spawn.
PlaySpawn.POST_SPAWN.register((ctx, spawned) -> {
if (spawned && ctx.entity() instanceof HostileEntity) {
playSpawnFlash(ctx.world(), ctx.entity());
}
});
SpawnRulesContext(MobEntity entity, WorldView world, SpawnReason spawnReason) — CHECK_RULES
CHECK_RULES returns boolean — return false from any listener to block
the spawn; if every listener returns true, vanilla's own rules still apply
on top.
SpawnContext(Entity entity, ServerWorld world, Cancellable cancel) — PRE_SPAWN / POST_SPAWN
PRE_SPAWN's cancel actually stops the entity from being added to the
world. POST_SPAWN fires after, with a second spawned boolean telling you
whether it actually happened — its own Cancellable has no effect at that
point.
Mob AI hooks — registering new goals onto a mob's GoalSelector, and
redirecting what a mob is targeting.
// Prevent a specific goal type from ever being added to skeletons.
PlayGoal.REGISTER_GOALS.register(ctx -> {
if (ctx.goal() instanceof MeleeAttackGoal) {
ctx.cancel().cancel();
}
});
// Make guard mobs prioritize whichever nearby player has a bounty.
PlayGoal.MODIFY_TARGET.register(ctx -> {
findBountyPlayerNearby(ctx.entity()).ifPresent(ctx.target()::set);
});
GoalRegistrationContext(GoalSelector selector, int priority, Goal goal, Cancellable cancel)
REGISTER_GOALS deviates a little from what you might expect: vanilla mobs
each wire up their AI goals inline in their own constructors rather than
through one shared "init goals" method, so this hooks the shared
GoalSelector.add(...) call instead. That means the context can't tell
you which entity, or which selector (the mob's normal goal selector vs. its
target selector), a given addition belongs to — you're filtering purely
on the Goal instance and priority being added. If you need per-entity
control, you'll generally want to check the goal's own type/fields rather
than trying to infer the owning entity.
TargetContext(MobEntity entity, LivingEntity oldTarget, MutableReference<LivingEntity> target, Cancellable cancel)
target is a MutableReference<LivingEntity> — call .set(...) to
redirect who the mob is targeting, or .set(null) to clear its target
entirely. Cancel to keep oldTarget in place instead.