package net.minecraft.world.entity.ai.behavior; import java.util.Map; import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.DamageTypeTags; import net.minecraft.tags.FluidTags; import net.minecraft.tags.TagKey; import net.minecraft.util.Mth; import net.minecraft.world.damagesource.DamageType; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.PathfinderMob; import net.minecraft.world.entity.ai.Brain; import net.minecraft.world.entity.ai.memory.MemoryModuleType; import net.minecraft.world.entity.ai.memory.MemoryStatus; import net.minecraft.world.entity.ai.memory.WalkTarget; import net.minecraft.world.entity.ai.util.LandRandomPos; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; public class AnimalPanic extends Behavior { private static final int PANIC_MIN_DURATION = 100; private static final int PANIC_MAX_DURATION = 120; private static final int PANIC_DISTANCE_HORIZONTAL = 5; private static final int PANIC_DISTANCE_VERTICAL = 4; private final float speedMultiplier; private final Function> panicCausingDamageTypes; public AnimalPanic(float speedMultiplier) { this(speedMultiplier, pathfinderMob -> DamageTypeTags.PANIC_CAUSES); } public AnimalPanic(float speedMultiplier, Function> panicCausingDamageTypes) { super(Map.of(MemoryModuleType.IS_PANICKING, MemoryStatus.REGISTERED, MemoryModuleType.HURT_BY, MemoryStatus.REGISTERED), 100, 120); this.speedMultiplier = speedMultiplier; this.panicCausingDamageTypes = panicCausingDamageTypes; } protected boolean checkExtraStartConditions(ServerLevel serverLevel, E pathfinderMob) { return (Boolean)pathfinderMob.getBrain() .getMemory(MemoryModuleType.HURT_BY) .map(damageSource -> damageSource.is((TagKey)this.panicCausingDamageTypes.apply(pathfinderMob))) .orElse(false) || pathfinderMob.getBrain().hasMemoryValue(MemoryModuleType.IS_PANICKING); } protected boolean canStillUse(ServerLevel serverLevel, E pathfinderMob, long l) { return true; } protected void start(ServerLevel serverLevel, E pathfinderMob, long l) { pathfinderMob.getBrain().setMemory(MemoryModuleType.IS_PANICKING, true); pathfinderMob.getBrain().eraseMemory(MemoryModuleType.WALK_TARGET); } protected void stop(ServerLevel serverLevel, E pathfinderMob, long l) { Brain brain = pathfinderMob.getBrain(); brain.eraseMemory(MemoryModuleType.IS_PANICKING); } protected void tick(ServerLevel serverLevel, E pathfinderMob, long l) { if (pathfinderMob.getNavigation().isDone()) { Vec3 vec3 = this.getPanicPos(pathfinderMob, serverLevel); if (vec3 != null) { pathfinderMob.getBrain().setMemory(MemoryModuleType.WALK_TARGET, new WalkTarget(vec3, this.speedMultiplier, 0)); } } } @Nullable private Vec3 getPanicPos(E pathfinder, ServerLevel level) { if (pathfinder.isOnFire()) { Optional optional = this.lookForWater(level, pathfinder).map(Vec3::atBottomCenterOf); if (optional.isPresent()) { return (Vec3)optional.get(); } } return LandRandomPos.getPos(pathfinder, 5, 4); } private Optional lookForWater(BlockGetter level, Entity entity) { BlockPos blockPos = entity.blockPosition(); if (!level.getBlockState(blockPos).getCollisionShape(level, blockPos).isEmpty()) { return Optional.empty(); } else { Predicate predicate; if (Mth.ceil(entity.getBbWidth()) == 2) { predicate = blockPosx -> BlockPos.squareOutSouthEast(blockPosx).allMatch(blockPosxx -> level.getFluidState(blockPosxx).is(FluidTags.WATER)); } else { predicate = blockPosx -> level.getFluidState(blockPosx).is(FluidTags.WATER); } return BlockPos.findClosestMatch(blockPos, 5, 1, predicate); } } }