package net.minecraft.world.entity.ai.goal; import com.google.common.collect.Lists; import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.function.BooleanSupplier; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.PoiTypeTags; import net.minecraft.world.entity.PathfinderMob; import net.minecraft.world.entity.ai.goal.Goal.Flag; import net.minecraft.world.entity.ai.navigation.GroundPathNavigation; import net.minecraft.world.entity.ai.util.DefaultRandomPos; import net.minecraft.world.entity.ai.util.GoalUtils; import net.minecraft.world.entity.ai.util.LandRandomPos; import net.minecraft.world.entity.ai.village.poi.PoiManager.Occupancy; import net.minecraft.world.level.block.DoorBlock; import net.minecraft.world.level.pathfinder.Node; import net.minecraft.world.level.pathfinder.Path; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; public class MoveThroughVillageGoal extends Goal { protected final PathfinderMob mob; private final double speedModifier; @Nullable private Path path; private BlockPos poiPos; private final boolean onlyAtNight; private final List visited = Lists.newArrayList(); private final int distanceToPoi; private final BooleanSupplier canDealWithDoors; public MoveThroughVillageGoal(PathfinderMob mob, double speedModifier, boolean onlyAtNight, int distanceToPoi, BooleanSupplier canDealWithDoors) { this.mob = mob; this.speedModifier = speedModifier; this.onlyAtNight = onlyAtNight; this.distanceToPoi = distanceToPoi; this.canDealWithDoors = canDealWithDoors; this.setFlags(EnumSet.of(Flag.MOVE)); if (!GoalUtils.hasGroundPathNavigation(mob)) { throw new IllegalArgumentException("Unsupported mob for MoveThroughVillageGoal"); } } @Override public boolean canUse() { if (!GoalUtils.hasGroundPathNavigation(this.mob)) { return false; } else { this.updateVisited(); if (this.onlyAtNight && this.mob.level().isBrightOutside()) { return false; } else { ServerLevel serverLevel = (ServerLevel)this.mob.level(); BlockPos blockPos = this.mob.blockPosition(); if (!serverLevel.isCloseToVillage(blockPos, 6)) { return false; } else { Vec3 vec3 = LandRandomPos.getPos( this.mob, 15, 7, blockPos2x -> { if (!serverLevel.isVillage(blockPos2x)) { return Double.NEGATIVE_INFINITY; } else { Optional optionalx = serverLevel.getPoiManager() .find(holder -> holder.is(PoiTypeTags.VILLAGE), this::hasNotVisited, blockPos2x, 10, Occupancy.IS_OCCUPIED); return (Double)optionalx.map(blockPos2xx -> -blockPos2xx.distSqr(blockPos)).orElse(Double.NEGATIVE_INFINITY); } } ); if (vec3 == null) { return false; } else { Optional optional = serverLevel.getPoiManager() .find(holder -> holder.is(PoiTypeTags.VILLAGE), this::hasNotVisited, BlockPos.containing(vec3), 10, Occupancy.IS_OCCUPIED); if (optional.isEmpty()) { return false; } else { this.poiPos = ((BlockPos)optional.get()).immutable(); GroundPathNavigation groundPathNavigation = (GroundPathNavigation)this.mob.getNavigation(); groundPathNavigation.setCanOpenDoors(this.canDealWithDoors.getAsBoolean()); this.path = groundPathNavigation.createPath(this.poiPos, 0); groundPathNavigation.setCanOpenDoors(true); if (this.path == null) { Vec3 vec32 = DefaultRandomPos.getPosTowards(this.mob, 10, 7, Vec3.atBottomCenterOf(this.poiPos), (float) (Math.PI / 2)); if (vec32 == null) { return false; } groundPathNavigation.setCanOpenDoors(this.canDealWithDoors.getAsBoolean()); this.path = this.mob.getNavigation().createPath(vec32.x, vec32.y, vec32.z, 0); groundPathNavigation.setCanOpenDoors(true); if (this.path == null) { return false; } } for (int i = 0; i < this.path.getNodeCount(); i++) { Node node = this.path.getNode(i); BlockPos blockPos2 = new BlockPos(node.x, node.y + 1, node.z); if (DoorBlock.isWoodenDoor(this.mob.level(), blockPos2)) { this.path = this.mob.getNavigation().createPath(node.x, node.y, node.z, 0); break; } } return this.path != null; } } } } } } @Override public boolean canContinueToUse() { return this.mob.getNavigation().isDone() ? false : !this.poiPos.closerToCenterThan(this.mob.position(), this.mob.getBbWidth() + this.distanceToPoi); } @Override public void start() { this.mob.getNavigation().moveTo(this.path, this.speedModifier); } @Override public void stop() { if (this.mob.getNavigation().isDone() || this.poiPos.closerToCenterThan(this.mob.position(), this.distanceToPoi)) { this.visited.add(this.poiPos); } } private boolean hasNotVisited(BlockPos pos) { for (BlockPos blockPos : this.visited) { if (Objects.equals(pos, blockPos)) { return false; } } return true; } private void updateVisited() { if (this.visited.size() > 15) { this.visited.remove(0); } } }