after covering the basics, we're ready to start developing our own brain instance, for that we'll have to create a new class where we'll store our AI
From GoatAI
// initializes the cooldown ticks for LONG JUMPING and RAMMING using a random value
// OPTIONAL!!! you won't need this if your entity doesn't initialize with behaviors with cooldown or similar.
protected static void initMemories(Goat goat, Random random) {
// gets the brain and sets a value to the memory, sampling a random value (IntProvider / FloatProvider)
goat.getBrain().setMemory(MemoryModuleType.LONG_JUMP_COOLDOWN_TICKS, TIME_BETWEEN_LONG_JUMPS.sample(random));
goat.getBrain().setMemory(MemoryModuleType.RAM_COOLDOWN_TICKS, TIME_BETWEEN_RAMS.sample(random));
}
// initializes the brain for the entity
protected static Brain<?> makeBrain(Brain<Goat> brain) {
// determine behaviors for each activity
initCoreActivity(brain);
initIdleActivity(brain);
initLongJumpActivity(brain);
initRamActivity(brain);
// determines the core activities, which are going to be always run
brain.setCoreActivities(ImmutableSet.of(Activity.CORE));
// determines and executes the activity that's going to be running after spawn.
brain.setDefaultActivity(Activity.IDLE);
brain.useDefaultActivity();
return brain;
}
// initializes the behaviors for the CORE activity
private static void initCoreActivity(Brain<Goat> brain) {
// determines the behaviors for a specific activity, selects a priority to start running each behavior in order.
brain.addActivity(
// Activity
Activity.CORE,
// Priority Start, priorities just determine which behaviors have preference over the others
0,
// Behaviors / Tasks
ImmutableList.of(
// priority: 0
new Swim(0.8F),
// priority: 1
new AnimalPanic(2.0F),
// priority: 2
new LookAtTargetSink(45, 90),
// priority: 3
new MoveToTargetSink(),
// priority: 4
new CountDownCooldownTicks(MemoryModuleType.TEMPTATION_COOLDOWN_TICKS),
// priority: 5
new CountDownCooldownTicks(MemoryModuleType.LONG_JUMP_COOLDOWN_TICKS),
// priority: 6
new CountDownCooldownTicks(MemoryModuleType.RAM_COOLDOWN_TICKS)
)
);
}
// initializes the behaviors for the IDLE activity
private static void initIdleActivity(Brain<Goat> brain) {
// determines the behaviors for a specific activity, takes conditions required to run this activity.
brain.addActivityWithConditions(
// Activity
Activity.IDLE,
// Behaviors / Tasks
ImmutableList.of(
// Pair of { Priority, Behavior }
// each pair counts as a behavior that runs with a certain priority
// RunSometimes will take a behavior and execute at certain interval determined by an UniformInt
Pair.of(0, new RunSometimes<>(new SetEntityLookTarget(EntityType.PLAYER, 6.0F), UniformInt.of(30, 60))),
Pair.of(0, new AnimalMakeLove(EntityTypes.GOAT, 1.0F)),
Pair.of(1, new FollowTemptation(entity -> 1.25F)),
Pair.of(2, new BabyFollowAdult<>(ADULT_FOLLOW_RANGE, 1.25F)),
// RunOne will take one of many behaviors and execute it.
Pair.of(3, new RunOne<>(ImmutableList.of(
// Pair of { Behavior, Priority }
Pair.of(new RandomStroll(1.0F), 2),
Pair.of(new SetWalkTargetFromLookTarget(1.0F, 3), 2),
// DoNothing will prevent the entity from executing any task and idle for a certain amount of time
Pair.of(new DoNothing(30, 60), 1))))
),
// Conditions, if these aren't met then the activity won't be used.
ImmutableSet.of(
Pair.of(MemoryModuleType.RAM_TARGET, MemoryStatus.VALUE_ABSENT),
Pair.of(MemoryModuleType.LONG_JUMP_MID_JUMP, MemoryStatus.VALUE_ABSENT)
)
);
}
// Determines the activity that's going to be used if valid
// conditional activities must meet the requirements to be valid
public static void updateActivity(Goat goat) {
goat.getBrain().setActiveActivityToFirstValid(ImmutableList.of(Activity.RAM, Activity.LONG_JUMP, Activity.IDLE));
}
we've created so far our brain instance, but we must initialize it in our entity class in order to use it.
From Goat
// determines the list of sensors for the entities to execute
protected static final ImmutableList<SensorType<? extends Sensor<? super Goat>>> SENSORS = ImmutableList.of(
SensorType.NEAREST_LIVING_ENTITIES,
SensorType.NEAREST_PLAYERS,
SensorType.NEAREST_ITEMS,
SensorType.NEAREST_ADULT,
SensorType.HURT_BY,
SensorType.GOAT_TEMPTATIONS
);
// determines the list of memories for the entities to execute
protected static final ImmutableList<MemoryModuleType<?>> MEMORIES = ImmutableList.of(
MemoryModuleType.LOOK_TARGET,
MemoryModuleType.VISIBLE_LIVING_ENTITIES,
MemoryModuleType.WALK_TARGET,
MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE,
MemoryModuleType.PATH,
MemoryModuleType.ATE_RECENTLY,
MemoryModuleType.BREED_TARGET,
MemoryModuleType.LONG_JUMP_COOLDOWN_TICKS,
MemoryModuleType.LONG_JUMP_MID_JUMP,
MemoryModuleType.TEMPTING_PLAYER,
MemoryModuleType.NEAREST_VISIBLE_ADULT,
MemoryModuleType.TEMPTATION_COOLDOWN_TICKS,
MemoryModuleType.IS_TEMPTED,
MemoryModuleType.RAM_TARGET,
MemoryModuleType.IS_PANICKING
);
// creates a specific brain provider that gathers the MEMORIES and SENSORS.
@Override
protected Brain.Provider<Goat> brainProvider() {
return Brain.provider(MEMORIES, SENSORS);
}
// creates the brain instance for the entity, using the class that we just created for it
@Override
protected Brain<?> makeBrain(Dynamic<?> dynamic) {
return GoatAi.makeBrain(this.brainProvider().makeBrain(dynamic));
}
// casts the brain to be used properly
@Override
public Brain<Goat> getBrain() {
return (Brain<Goat>)super.getBrain();
}
@Override
protected void customServerAiStep() {
this.level.getProfiler().push("goatBrain");
// start ticking the brain! just like turning on the engine.
this.getBrain().tick((ServerLevel)this.level, this);
this.level.getProfiler().popPush("goatActivityUpdate");
// update the activities depending on their validation.
GoatAi.updateActivity(this);
this.level.getProfiler().pop();
super.customServerAiStep();
}
@Override
public SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType reason, @Nullable SpawnGroupData spawnData, @Nullable CompoundTag dataTag) {
// initialize the cooldown tick memories for RAMMING and LONG JUMPING after spawn, remember that this is OPTIONAL
GoatAi.initMemories(this, random);
...
}
@Nullable @Override
public AgableMob getBreedOffspring(ServerLevel level, AgableMob mate) {
Goat goat = EntityTypes.GOAT.create(level);
if (goat != null) {
// initialize the cooldown tick memories for RAMMING and LONG JUMPING after spawn, remember that this is OPTIONAL
GoatAi.initMemories(goat, level.getRandom());
...
}
// If you are wondering that's this used for, it's for gametesting purposes, mojang developers use this to debug their entities
// but for default users, the method is empty so there's not really an use to us on using this...
@Override
protected void sendDebugPackets() {
super.sendDebugPackets();
DebugPackets.sendEntityBrain(this);
}
also if you're wondering, here's a video on how mojang developers debug their entities with brain like Piglins, not relevant but still cool to watch: https://www.youtube.com/watch?v=unMIhRivDWQ
piglins show above their head the current activities that they are running, CORE is constant while AVOID, FIGHT and IDLE change depending on the conditions.
now it's your turn!