Files
AdapativeMobs/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveEndermanTacticsGoal.java
T

218 lines
10 KiB
Java

package net.fretux.adaptivemobs.ai.goals;
import net.fretux.adaptivemobs.ai.AdaptiveAIGoalUtils;
import net.fretux.adaptivemobs.ai.AdaptivePositioningUtils;
import net.fretux.adaptivemobs.ai.AdaptiveTargetingUtils;
import net.fretux.adaptivemobs.config.AMConfig;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.FluidTags;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.monster.EnderMan;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import java.util.EnumSet;
import java.util.function.IntSupplier;
public class AdaptiveEndermanTacticsGoal extends Goal {
private static final int ANTI_PILLAR_BUILD_COOLDOWN_MIN = 4;
private static final int ANTI_PILLAR_BUILD_COOLDOWN_RANDOM = 4;
private static final BlockState[] OVERWORLD_SCAFFOLD_BLOCKS = {
Blocks.DIRT.defaultBlockState(),
Blocks.GRASS_BLOCK.defaultBlockState(),
Blocks.COARSE_DIRT.defaultBlockState(),
Blocks.ROOTED_DIRT.defaultBlockState(),
Blocks.GRAVEL.defaultBlockState(),
Blocks.SAND.defaultBlockState(),
Blocks.RED_SAND.defaultBlockState(),
Blocks.COBBLESTONE.defaultBlockState(),
Blocks.STONE.defaultBlockState()
};
private static final BlockState[] END_SCAFFOLD_BLOCKS = {
Blocks.END_STONE.defaultBlockState(),
Blocks.OBSIDIAN.defaultBlockState(),
Blocks.COBBLESTONE.defaultBlockState()
};
private final EnderMan enderman;
private final IntSupplier tierSupplier;
private int cooldown;
private BlockPos rampTargetFeet;
private Direction rampApproach;
private int rampRise;
private int rampBaseY;
public AdaptiveEndermanTacticsGoal(EnderMan enderman, IntSupplier tierSupplier) {
this.enderman = enderman;
this.tierSupplier = tierSupplier;
setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
}
@Override
public boolean canUse() {
return AMConfig.ENABLE_ENDERMAN_AI.get()
&& tierSupplier.getAsInt() >= 2
&& AdaptiveAIGoalUtils.isValidAdaptiveTarget(enderman.getTarget())
&& --cooldown <= 0;
}
@Override
public void start() {
LivingEntity target = enderman.getTarget();
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
return;
}
int tier = tierSupplier.getAsInt();
boolean inEnd = enderman.level().dimension() == Level.END;
cooldown = inEnd && tier >= 5 ? 35 + enderman.getRandom().nextInt(45) : 60 + enderman.getRandom().nextInt(80);
double dist = Math.sqrt(enderman.distanceToSqr(target));
boolean waterNearby = enderman.level().getFluidState(enderman.blockPosition()).is(FluidTags.WATER);
boolean strongMelee = tier >= 5 && dist < 4.0D && !AdaptiveAIGoalUtils.isUsingRangedWeapon(target);
if (inEnd && tier >= 4) {
double radius = tier >= 5 ? 18.0D : 12.0D;
AdaptiveTargetingUtils.nearbyHostiles(enderman, radius,
other -> other instanceof EnderMan
&& (other.getTarget() == null || other.getTarget() == target)
&& other.canAttack(target))
.forEach(other -> other.setTarget(target));
AdaptiveAIGoalUtils.debug(enderman, tierSupplier, "enderman coordinated target");
}
if (tier >= 4 && AMConfig.ENABLE_ANTI_CHEESE_AI.get() && isToweredTarget(target)) {
cooldown = ANTI_PILLAR_BUILD_COOLDOWN_MIN + enderman.getRandom().nextInt(ANTI_PILLAR_BUILD_COOLDOWN_RANDOM);
if (tryPlaceMobStep(target, inEnd)) {
return;
}
}
if (tier >= 3 || waterNearby || strongMelee) {
Vec3 side = AdaptivePositioningUtils.sidePosition(enderman, target, strongMelee ? 8.0D : (inEnd && tier >= 5 ? 5.0D : 3.0D),
enderman.getRandom().nextBoolean() ? 1.0D : -1.0D, 0.0D);
int attempts = inEnd && tier >= 5 ? 6 : 4;
for (int i = 0; i < attempts; i++) {
Vec3 candidate = side.add(enderman.getRandom().nextInt(5) - 2, 0.0D, enderman.getRandom().nextInt(5) - 2);
BlockPos pos = BlockPos.containing(candidate);
if (!enderman.level().getFluidState(pos).is(FluidTags.WATER)
&& AdaptivePositioningUtils.isReasonableGround(enderman, candidate)
&& enderman.randomTeleport(candidate.x, candidate.y, candidate.z, true)) {
AdaptiveAIGoalUtils.debug(enderman, tierSupplier, "enderman angular teleport");
return;
}
}
}
}
private boolean isToweredTarget(LivingEntity target) {
double vertical = target.getY() - enderman.getY();
double horizontal = Math.sqrt((target.getX() - enderman.getX()) * (target.getX() - enderman.getX())
+ (target.getZ() - enderman.getZ()) * (target.getZ() - enderman.getZ()));
return vertical > 2.5D && vertical <= 16.0D && horizontal < 9.0D;
}
private boolean tryPlaceMobStep(LivingEntity target, boolean inEnd) {
if (!(enderman.level() instanceof ServerLevel level)) {
return false;
}
BlockState scaffoldState = randomScaffoldState(inEnd);
prepareScaffoldBlock(level, scaffoldState);
BlockPos targetFeet = BlockPos.containing(target.getX(), target.getY(), target.getZ());
if (!targetFeet.equals(rampTargetFeet)) {
rampTargetFeet = targetFeet;
rampApproach = sharedRampDirection(targetFeet);
rampRise = Math.min(8, Math.max(1, (int) Math.floor(target.getY() - enderman.getY())));
rampBaseY = targetFeet.getY() - rampRise;
}
for (int step = 0; step < rampRise; step++) {
int distance = Math.max(1, rampRise - step);
BlockPos pos = new BlockPos(targetFeet.getX(), rampBaseY + step, targetFeet.getZ()).relative(rampApproach, distance);
if (canPlaceRampStep(level, pos, rampApproach, step == 0)) {
BlockState carried = enderman.getCarriedBlock();
level.setBlock(pos, isUsefulScaffoldBlock(carried) ? carried : scaffoldState, 3);
enderman.setCarriedBlock(null);
enderman.randomTeleport(pos.getX() + 0.5D, pos.getY() + 1.0D, pos.getZ() + 0.5D, true);
AdaptiveAIGoalUtils.debug(enderman, tierSupplier, "enderman placed anti-pillar scaffold");
return true;
}
}
return false;
}
private BlockState randomScaffoldState(boolean inEnd) {
BlockState[] options = inEnd ? END_SCAFFOLD_BLOCKS : OVERWORLD_SCAFFOLD_BLOCKS;
return options[enderman.getRandom().nextInt(options.length)];
}
private void prepareScaffoldBlock(ServerLevel level, BlockState scaffoldState) {
BlockState carried = enderman.getCarriedBlock();
if (isUsefulScaffoldBlock(carried)) {
return;
}
if (carried != null) {
if (!tryPlaceDiscardedBlock(level, carried)) {
enderman.spawnAtLocation(new ItemStack(carried.getBlock()));
}
}
enderman.setCarriedBlock(scaffoldState);
AdaptiveAIGoalUtils.debug(enderman, tierSupplier, "enderman swapped carried block for scaffold");
}
private boolean isUsefulScaffoldBlock(BlockState state) {
return state != null && !state.liquid()
&& !state.getCollisionShape(enderman.level(), enderman.blockPosition()).isEmpty();
}
private boolean tryPlaceDiscardedBlock(ServerLevel level, BlockState carried) {
BlockPos origin = enderman.blockPosition();
BlockPos[] candidates = {
origin,
origin.relative(Direction.NORTH),
origin.relative(Direction.EAST),
origin.relative(Direction.SOUTH),
origin.relative(Direction.WEST)
};
for (BlockPos pos : candidates) {
if (canPlaceDiscardedBlock(level, carried, pos)) {
level.setBlock(pos, carried, 3);
return true;
}
}
return false;
}
private boolean canPlaceDiscardedBlock(ServerLevel level, BlockState carried, BlockPos pos) {
return level.getWorldBorder().isWithinBounds(pos)
&& level.getBlockState(pos).isAir()
&& !level.getBlockState(pos.below()).getCollisionShape(level, pos.below()).isEmpty()
&& carried.canSurvive(level, pos);
}
private Direction sharedRampDirection(BlockPos targetFeet) {
Direction[] directions = {Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST};
int index = Math.floorMod(targetFeet.getX() * 31 + targetFeet.getZ() * 17, directions.length);
return directions[index];
}
private boolean canPlaceRampStep(ServerLevel level, BlockPos pos, Direction approach, boolean firstStep) {
return level.getWorldBorder().isWithinBounds(pos)
&& level.getBlockState(pos).isAir()
&& !level.getFluidState(pos).is(FluidTags.WATER)
&& !level.getFluidState(pos).is(FluidTags.LAVA)
&& (firstStep ? hasGroundSupport(level, pos) : hasPreviousRampStep(level, pos, approach));
}
private boolean hasPreviousRampStep(ServerLevel level, BlockPos pos, Direction approach) {
BlockPos previousStep = pos.relative(approach).below();
return !level.getBlockState(previousStep).getCollisionShape(level, previousStep).isEmpty();
}
private boolean hasGroundSupport(ServerLevel level, BlockPos pos) {
BlockPos below = pos.below();
return !level.getBlockState(below).getCollisionShape(level, below).isEmpty();
}
}