package net.minecraft.world.entity.ai.behavior; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.stream.Collectors; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundSource; import net.minecraft.util.Mth; import net.minecraft.util.random.WeightedRandom; import net.minecraft.util.valueproviders.UniformInt; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.memory.MemoryModuleType; import net.minecraft.world.entity.ai.memory.MemoryStatus; import net.minecraft.world.entity.ai.navigation.PathNavigation; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.pathfinder.Path; import net.minecraft.world.level.pathfinder.WalkNodeEvaluator; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; public class LongJumpToRandomPos extends Behavior { protected static final int FIND_JUMP_TRIES = 20; private static final int PREPARE_JUMP_DURATION = 40; protected static final int MIN_PATHFIND_DISTANCE_TO_VALID_JUMP = 8; private static final int TIME_OUT_DURATION = 200; private static final List ALLOWED_ANGLES = Lists.newArrayList(65, 70, 75, 80); private final UniformInt timeBetweenLongJumps; protected final int maxLongJumpHeight; protected final int maxLongJumpWidth; protected final float maxJumpVelocityMultiplier; protected List jumpCandidates = Lists.newArrayList(); protected Optional initialPosition = Optional.empty(); @Nullable protected Vec3 chosenJump; protected int findJumpTries; protected long prepareJumpStart; private final Function getJumpSound; private final BiPredicate acceptableLandingSpot; public LongJumpToRandomPos( UniformInt timeBetweenLongJumps, int maxLongJumpHeight, int maxLongJumpWidth, float maxJumpVelocity, Function getJumpSound ) { this(timeBetweenLongJumps, maxLongJumpHeight, maxLongJumpWidth, maxJumpVelocity, getJumpSound, LongJumpToRandomPos::defaultAcceptableLandingSpot); } public static boolean defaultAcceptableLandingSpot(E mob, BlockPos pos) { Level level = mob.level(); BlockPos blockPos = pos.below(); return level.getBlockState(blockPos).isSolidRender() && mob.getPathfindingMalus(WalkNodeEvaluator.getPathTypeStatic(mob, pos)) == 0.0F; } public LongJumpToRandomPos( UniformInt timeBetweenLongJumps, int maxLongJumpHeight, int maxLongJumpWidth, float maxJumpVelocity, Function getJumpSound, BiPredicate acceptableLandingSpot ) { super( ImmutableMap.of( MemoryModuleType.LOOK_TARGET, MemoryStatus.REGISTERED, MemoryModuleType.LONG_JUMP_COOLDOWN_TICKS, MemoryStatus.VALUE_ABSENT, MemoryModuleType.LONG_JUMP_MID_JUMP, MemoryStatus.VALUE_ABSENT ), 200 ); this.timeBetweenLongJumps = timeBetweenLongJumps; this.maxLongJumpHeight = maxLongJumpHeight; this.maxLongJumpWidth = maxLongJumpWidth; this.maxJumpVelocityMultiplier = maxJumpVelocity; this.getJumpSound = getJumpSound; this.acceptableLandingSpot = acceptableLandingSpot; } protected boolean checkExtraStartConditions(ServerLevel serverLevel, Mob mob) { boolean bl = mob.onGround() && !mob.isInWater() && !mob.isInLava() && !serverLevel.getBlockState(mob.blockPosition()).is(Blocks.HONEY_BLOCK); if (!bl) { mob.getBrain().setMemory(MemoryModuleType.LONG_JUMP_COOLDOWN_TICKS, this.timeBetweenLongJumps.sample(serverLevel.random) / 2); } return bl; } protected boolean canStillUse(ServerLevel serverLevel, Mob mob, long l) { boolean bl = this.initialPosition.isPresent() && ((Vec3)this.initialPosition.get()).equals(mob.position()) && this.findJumpTries > 0 && !mob.isInWater() && (this.chosenJump != null || !this.jumpCandidates.isEmpty()); if (!bl && mob.getBrain().getMemory(MemoryModuleType.LONG_JUMP_MID_JUMP).isEmpty()) { mob.getBrain().setMemory(MemoryModuleType.LONG_JUMP_COOLDOWN_TICKS, this.timeBetweenLongJumps.sample(serverLevel.random) / 2); mob.getBrain().eraseMemory(MemoryModuleType.LOOK_TARGET); } return bl; } protected void start(ServerLevel serverLevel, E mob, long l) { this.chosenJump = null; this.findJumpTries = 20; this.initialPosition = Optional.of(mob.position()); BlockPos blockPos = mob.blockPosition(); int i = blockPos.getX(); int j = blockPos.getY(); int k = blockPos.getZ(); this.jumpCandidates = (List)BlockPos.betweenClosedStream( i - this.maxLongJumpWidth, j - this.maxLongJumpHeight, k - this.maxLongJumpWidth, i + this.maxLongJumpWidth, j + this.maxLongJumpHeight, k + this.maxLongJumpWidth ) .filter(blockPos2 -> !blockPos2.equals(blockPos)) .map(blockPos2 -> new LongJumpToRandomPos.PossibleJump(blockPos2.immutable(), Mth.ceil(blockPos.distSqr(blockPos2)))) .collect(Collectors.toCollection(Lists::newArrayList)); } protected void tick(ServerLevel serverLevel, E mob, long l) { if (this.chosenJump != null) { if (l - this.prepareJumpStart >= 40L) { mob.setYRot(mob.yBodyRot); mob.setDiscardFriction(true); double d = this.chosenJump.length(); double e = d + mob.getJumpBoostPower(); mob.setDeltaMovement(this.chosenJump.scale(e / d)); mob.getBrain().setMemory(MemoryModuleType.LONG_JUMP_MID_JUMP, true); serverLevel.playSound(null, mob, (SoundEvent)this.getJumpSound.apply(mob), SoundSource.NEUTRAL, 1.0F, 1.0F); } } else { this.findJumpTries--; this.pickCandidate(serverLevel, mob, l); } } protected void pickCandidate(ServerLevel level, E entity, long prepareJumpStart) { while (!this.jumpCandidates.isEmpty()) { Optional optional = this.getJumpCandidate(level); if (!optional.isEmpty()) { LongJumpToRandomPos.PossibleJump possibleJump = (LongJumpToRandomPos.PossibleJump)optional.get(); BlockPos blockPos = possibleJump.targetPos(); if (this.isAcceptableLandingPosition(level, entity, blockPos)) { Vec3 vec3 = Vec3.atCenterOf(blockPos); Vec3 vec32 = this.calculateOptimalJumpVector(entity, vec3); if (vec32 != null) { entity.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, new BlockPosTracker(blockPos)); PathNavigation pathNavigation = entity.getNavigation(); Path path = pathNavigation.createPath(blockPos, 0, 8); if (path == null || !path.canReach()) { this.chosenJump = vec32; this.prepareJumpStart = prepareJumpStart; return; } } } } } } protected Optional getJumpCandidate(ServerLevel level) { Optional optional = WeightedRandom.getRandomItem( level.random, this.jumpCandidates, LongJumpToRandomPos.PossibleJump::weight ); optional.ifPresent(this.jumpCandidates::remove); return optional; } private boolean isAcceptableLandingPosition(ServerLevel level, E entity, BlockPos pos) { BlockPos blockPos = entity.blockPosition(); int i = blockPos.getX(); int j = blockPos.getZ(); return i == pos.getX() && j == pos.getZ() ? false : this.acceptableLandingSpot.test(entity, pos); } @Nullable protected Vec3 calculateOptimalJumpVector(Mob mob, Vec3 target) { List list = Lists.newArrayList(ALLOWED_ANGLES); Collections.shuffle(list); float f = (float)(mob.getAttributeValue(Attributes.JUMP_STRENGTH) * this.maxJumpVelocityMultiplier); for (int i : list) { Optional optional = LongJumpUtil.calculateJumpVectorForAngle(mob, target, f, i, true); if (optional.isPresent()) { return (Vec3)optional.get(); } } return null; } public record PossibleJump(BlockPos targetPos, int weight) { } }