package net.minecraft.world.entity.ai.goal; import java.util.EnumSet; import net.minecraft.core.BlockPos; import net.minecraft.world.entity.PathfinderMob; import net.minecraft.world.level.LevelReader; public abstract class MoveToBlockGoal extends Goal { private static final int GIVE_UP_TICKS = 1200; private static final int STAY_TICKS = 1200; private static final int INTERVAL_TICKS = 200; protected final PathfinderMob mob; public final double speedModifier; /** * Controls task execution delay */ protected int nextStartTick; protected int tryTicks; private int maxStayTicks; /** * Block to move to */ protected BlockPos blockPos = BlockPos.ZERO; private boolean reachedTarget; private final int searchRange; private final int verticalSearchRange; protected int verticalSearchStart; public MoveToBlockGoal(PathfinderMob mob, double speedModifier, int searchRange) { this(mob, speedModifier, searchRange, 1); } public MoveToBlockGoal(PathfinderMob mob, double speedModifier, int searchRange, int verticalSearchRange) { this.mob = mob; this.speedModifier = speedModifier; this.searchRange = searchRange; this.verticalSearchStart = 0; this.verticalSearchRange = verticalSearchRange; this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.JUMP)); } @Override public boolean canUse() { if (this.nextStartTick > 0) { this.nextStartTick--; return false; } else { this.nextStartTick = this.nextStartTick(this.mob); return this.findNearestBlock(); } } protected int nextStartTick(PathfinderMob creature) { return reducedTickDelay(200 + creature.getRandom().nextInt(200)); } @Override public boolean canContinueToUse() { return this.tryTicks >= -this.maxStayTicks && this.tryTicks <= 1200 && this.isValidTarget(this.mob.level(), this.blockPos); } @Override public void start() { this.moveMobToBlock(); this.tryTicks = 0; this.maxStayTicks = this.mob.getRandom().nextInt(this.mob.getRandom().nextInt(1200) + 1200) + 1200; } protected void moveMobToBlock() { this.mob.getNavigation().moveTo(this.blockPos.getX() + 0.5, this.blockPos.getY() + 1, this.blockPos.getZ() + 0.5, this.speedModifier); } public double acceptedDistance() { return 1.0; } protected BlockPos getMoveToTarget() { return this.blockPos.above(); } @Override public boolean requiresUpdateEveryTick() { return true; } @Override public void tick() { BlockPos blockPos = this.getMoveToTarget(); if (!blockPos.closerToCenterThan(this.mob.position(), this.acceptedDistance())) { this.reachedTarget = false; this.tryTicks++; if (this.shouldRecalculatePath()) { this.mob.getNavigation().moveTo(blockPos.getX() + 0.5, blockPos.getY(), blockPos.getZ() + 0.5, this.speedModifier); } } else { this.reachedTarget = true; this.tryTicks--; } } public boolean shouldRecalculatePath() { return this.tryTicks % 40 == 0; } protected boolean isReachedTarget() { return this.reachedTarget; } /** * Searches and sets new destination block and returns true if a suitable block (specified in {@link #isValidTarget(net.minecraft.world.level.LevelReader, net.minecraft.core.BlockPos)}) can be found. */ protected boolean findNearestBlock() { int i = this.searchRange; int j = this.verticalSearchRange; BlockPos blockPos = this.mob.blockPosition(); BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); for (int k = this.verticalSearchStart; k <= j; k = k > 0 ? -k : 1 - k) { for (int l = 0; l < i; l++) { for (int m = 0; m <= l; m = m > 0 ? -m : 1 - m) { for (int n = m < l && m > -l ? l : 0; n <= l; n = n > 0 ? -n : 1 - n) { mutableBlockPos.setWithOffset(blockPos, m, k - 1, n); if (this.mob.isWithinRestriction(mutableBlockPos) && this.isValidTarget(this.mob.level(), mutableBlockPos)) { this.blockPos = mutableBlockPos; return true; } } } } } return false; } /** * Return {@code true} to set given position as destination */ protected abstract boolean isValidTarget(LevelReader level, BlockPos pos); }