implement many major AI changes.
This commit is contained in:
@@ -15,6 +15,8 @@ import java.util.function.IntSupplier;
|
||||
|
||||
public final class AdaptiveAIGoalUtils {
|
||||
|
||||
private static final String ADAPTIVE_STACKED_ZOMBIE_KEY = "am_adaptive_stacked_zombie";
|
||||
|
||||
private AdaptiveAIGoalUtils() {
|
||||
}
|
||||
|
||||
@@ -46,6 +48,25 @@ public final class AdaptiveAIGoalUtils {
|
||||
return stack.isEdible();
|
||||
}
|
||||
|
||||
public static boolean isValidAdaptiveTarget(LivingEntity entity) {
|
||||
if (entity == null || !entity.isAlive()) {
|
||||
return false;
|
||||
}
|
||||
return !(entity instanceof Player player) || (!player.isCreative() && !player.isSpectator());
|
||||
}
|
||||
|
||||
public static void markAdaptiveStackedZombie(Mob mob) {
|
||||
mob.getPersistentData().putBoolean(ADAPTIVE_STACKED_ZOMBIE_KEY, true);
|
||||
}
|
||||
|
||||
public static boolean isAdaptiveStackedZombie(Mob mob) {
|
||||
return mob.getPersistentData().getBoolean(ADAPTIVE_STACKED_ZOMBIE_KEY);
|
||||
}
|
||||
|
||||
public static void clearAdaptiveStackedZombie(Mob mob) {
|
||||
mob.getPersistentData().remove(ADAPTIVE_STACKED_ZOMBIE_KEY);
|
||||
}
|
||||
|
||||
public static boolean isLikelyMeleeWeapon(ItemStack stack) {
|
||||
return stack.is(Items.WOODEN_SWORD) || stack.is(Items.STONE_SWORD) || stack.is(Items.IRON_SWORD)
|
||||
|| stack.is(Items.GOLDEN_SWORD) || stack.is(Items.DIAMOND_SWORD) || stack.is(Items.NETHERITE_SWORD)
|
||||
|
||||
@@ -27,6 +27,7 @@ import net.fretux.adaptivemobs.ai.goals.AdaptiveSpiderDisengageGoal;
|
||||
import net.fretux.adaptivemobs.ai.goals.AdaptiveSpiderTacticsGoal;
|
||||
import net.fretux.adaptivemobs.ai.goals.AdaptiveSupportGoal;
|
||||
import net.fretux.adaptivemobs.ai.goals.AdaptiveTargetPriorityGoal;
|
||||
import net.fretux.adaptivemobs.ai.goals.AdaptiveTargetSanitizerGoal;
|
||||
import net.fretux.adaptivemobs.ai.goals.AdaptiveThreatMemoryGoal;
|
||||
import net.fretux.adaptivemobs.ai.goals.AdaptiveWardenTacticsGoal;
|
||||
import net.fretux.adaptivemobs.ai.goals.AdaptiveWitchTacticsGoal;
|
||||
@@ -92,6 +93,7 @@ public final class AdaptiveGoalInjector {
|
||||
}
|
||||
PathfinderMob pathfinder = mob instanceof PathfinderMob pf ? pf : null;
|
||||
IntSupplier tier = () -> AdaptiveDifficultyManager.getMobTier(level, mob.getType());
|
||||
mob.targetSelector.addGoal(0, new AdaptiveTargetSanitizerGoal(mob));
|
||||
|
||||
if (tier.getAsInt() >= 4) {
|
||||
mob.targetSelector.addGoal(2, new AdaptiveSharedTargetGoal(mob, tier, 4, 12.0D));
|
||||
@@ -106,6 +108,7 @@ public final class AdaptiveGoalInjector {
|
||||
// otherwise it can never take over while the zombie is already fighting.
|
||||
mob.goalSelector.addGoal(1, new AdaptiveZombiePackTacticsGoal((Zombie) mob, tier));
|
||||
} else if (pathfinder != null && mob instanceof Creeper creeper && AMConfig.AI_CREEPER.get()) {
|
||||
creeper.maxSwell = creeperFuseTicks(tier.getAsInt());
|
||||
mob.goalSelector.addGoal(2, new AdaptiveCreeperPressureGoal(creeper, tier));
|
||||
mob.goalSelector.addGoal(4, new AdaptiveFlankGoal(pathfinder, tier, 2, 3.0D, 1.0D, false));
|
||||
} else if (pathfinder != null && mob instanceof Spider && AMConfig.AI_SPIDER.get()) {
|
||||
@@ -145,7 +148,7 @@ public final class AdaptiveGoalInjector {
|
||||
mob.goalSelector.addGoal(8, new AdaptiveBiomeTacticsGoal(pathfinder, tier));
|
||||
}
|
||||
if (pathfinder != null && AMConfig.ENABLE_ANTI_CHEESE_AI.get()) {
|
||||
mob.goalSelector.addGoal(mob instanceof Zombie ? 1 : 7, new AdaptiveAntiCheeseGoal(pathfinder, tier));
|
||||
mob.goalSelector.addGoal(0, new AdaptiveAntiCheeseGoal(pathfinder, tier));
|
||||
}
|
||||
if (pathfinder != null && isRangedMob(mob) && rangedEnabled(mob)) {
|
||||
mob.goalSelector.addGoal(3, new AdaptiveRangedAttackGoal(pathfinder, tier, rangedOptimalRange(mob), 1.0D));
|
||||
@@ -307,6 +310,10 @@ public final class AdaptiveGoalInjector {
|
||||
return 1.05D;
|
||||
}
|
||||
|
||||
private static int creeperFuseTicks(int tier) {
|
||||
return Math.max(18, 30 - Math.max(0, tier - 1) * 3);
|
||||
}
|
||||
|
||||
private static boolean conservativeMelee(Mob mob) {
|
||||
return mob instanceof PiglinBrute || mob instanceof Ravager || mob instanceof Warden || mob instanceof ElderGuardian;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ public final class AdaptiveMemory {
|
||||
if (!(mob.level() instanceof ServerLevel level)) {
|
||||
return;
|
||||
}
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(player) || !mob.canAttack(player)) {
|
||||
return;
|
||||
}
|
||||
THREATS.put(mob, new ThreatEntry(player.getUUID(), level.getGameTime() + ticks));
|
||||
}
|
||||
|
||||
@@ -41,7 +44,9 @@ public final class AdaptiveMemory {
|
||||
return Optional.empty();
|
||||
}
|
||||
ServerPlayer player = level.getServer().getPlayerList().getPlayer(entry.playerId);
|
||||
return player != null && player.isAlive() ? Optional.of(player) : Optional.empty();
|
||||
return AdaptiveAIGoalUtils.isValidAdaptiveTarget(player) && mob.canAttack(player)
|
||||
? Optional.of(player)
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
public static void rememberSound(ServerLevel level, BlockPos pos, SoundKind kind, int ticks) {
|
||||
|
||||
@@ -27,7 +27,7 @@ public final class AdaptiveMobHooks {
|
||||
return;
|
||||
}
|
||||
LivingEntity target = mob.getTarget();
|
||||
if (target == null || !target.isAlive()) {
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return;
|
||||
}
|
||||
int tier = AdaptiveDifficultyManager.getMobTier(level, mob.getType());
|
||||
|
||||
@@ -91,9 +91,11 @@ public final class AdaptiveSpecialAbilities {
|
||||
if (mob.getRandom().nextFloat() < poisonChance) {
|
||||
arrow.addEffect(new MobEffectInstance(MobEffects.POISON, 80 + mob.getRandom().nextInt(41), 0));
|
||||
}
|
||||
if (tier >= 5 && target != null && AdaptiveAIGoalUtils.isBlockingShield(target) && mob.getRandom().nextFloat() < 0.18F) {
|
||||
if (tier >= 5 && AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)
|
||||
&& AdaptiveAIGoalUtils.isBlockingShield(target) && mob.getRandom().nextFloat() < 0.18F) {
|
||||
arrow.addEffect(new MobEffectInstance(MobEffects.WEAKNESS, 60, 0));
|
||||
} else if (tier >= 5 && target != null && target.isSprinting() && mob.getRandom().nextFloat() < 0.18F) {
|
||||
} else if (tier >= 5 && AdaptiveAIGoalUtils.isValidAdaptiveTarget(target) && target.isSprinting()
|
||||
&& mob.getRandom().nextFloat() < 0.18F) {
|
||||
arrow.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 60, 0));
|
||||
}
|
||||
return;
|
||||
@@ -145,7 +147,8 @@ public final class AdaptiveSpecialAbilities {
|
||||
continue;
|
||||
}
|
||||
ServerLevel level = event.getServer().getLevel(pending.dimension);
|
||||
if (level != null && pending.mob.isAlive() && pending.target.isAlive()) {
|
||||
if (level != null && pending.mob.isAlive() && AdaptiveAIGoalUtils.isValidAdaptiveTarget(pending.target)
|
||||
&& pending.mob.canAttack(pending.target)) {
|
||||
Vec3 side = AdaptivePositioningUtils.sidePosition(pending.mob, pending.target, 1.6D,
|
||||
pending.mob.getRandom().nextBoolean() ? 1.0D : -1.0D, 0.0D);
|
||||
pending.mob.moveTo(side.x, side.y, side.z, pending.mob.getYRot(), pending.mob.getXRot());
|
||||
@@ -159,7 +162,7 @@ public final class AdaptiveSpecialAbilities {
|
||||
|
||||
private static void onMobDamaged(ServerLevel level, Mob mob, LivingEntity attacker, Entity directEntity) {
|
||||
int tier = tier(level, mob);
|
||||
if (tier <= 0) {
|
||||
if (tier <= 0 || !AdaptiveAIGoalUtils.isValidAdaptiveTarget(attacker) || !mob.canAttack(attacker)) {
|
||||
return;
|
||||
}
|
||||
if (tier >= 5 && mob instanceof ZombifiedPiglin && ready(mob, "blood_call", 160)) {
|
||||
@@ -170,7 +173,7 @@ public final class AdaptiveSpecialAbilities {
|
||||
}
|
||||
AdaptiveAIGoalUtils.debug(mob, () -> tier, "zombified piglin blood call");
|
||||
}
|
||||
if (tier >= 4 && mob instanceof Spider && ready(mob, "web_retreat", 150)) {
|
||||
if (tier >= 4 && mob instanceof Spider && ready(mob, "web_retreat", 600)) {
|
||||
placeTemporaryCobweb(level, attacker.blockPosition(), level.getGameTime() + 100 + mob.getRandom().nextInt(61));
|
||||
}
|
||||
if (tier >= 4 && mob instanceof EnderMan enderman && directEntity == attacker && ready(mob, "backstep_ambush", tier >= 5 ? 80 : 130)) {
|
||||
@@ -215,7 +218,7 @@ public final class AdaptiveSpecialAbilities {
|
||||
|
||||
private static void onPlayerHurt(ServerLevel level, ServerPlayer player, Mob mob, Entity directEntity, LivingHurtEvent event) {
|
||||
int tier = tier(level, mob);
|
||||
if (tier <= 0) {
|
||||
if (tier <= 0 || !AdaptiveAIGoalUtils.isValidAdaptiveTarget(player)) {
|
||||
return;
|
||||
}
|
||||
if (tier >= 4 && mob instanceof Drowned && mob.isInWater() && player.isInWater() && ready(mob, "dragging_grip", 160)) {
|
||||
@@ -227,7 +230,7 @@ public final class AdaptiveSpecialAbilities {
|
||||
player.addEffect(new MobEffectInstance(MobEffects.WITHER, tier >= 5 ? 140 : 100, 0));
|
||||
shove(player, mob.position(), tier >= 5 ? 0.55D : 0.35D, 0.08D);
|
||||
}
|
||||
if (mob instanceof Spider && tier >= 4 && ready(mob, "web_hit", mob instanceof CaveSpider ? 100 : 140)) {
|
||||
if (mob instanceof Spider && tier >= 4 && ready(mob, "web_hit", mob instanceof CaveSpider ? 400 : 600)) {
|
||||
placeTemporaryCobweb(level, player.blockPosition(), level.getGameTime() + 100 + mob.getRandom().nextInt(61));
|
||||
}
|
||||
if (mob instanceof CaveSpider && tier >= 3
|
||||
|
||||
@@ -34,14 +34,15 @@ public class AdaptiveAirbornePositioningGoal extends Goal {
|
||||
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
return tierSupplier.getAsInt() >= 2 && mob.getTarget() != null && mob.getTarget().isAlive() && --cooldown <= 0;
|
||||
return tierSupplier.getAsInt() >= 2 && AdaptiveAIGoalUtils.isValidAdaptiveTarget(mob.getTarget())
|
||||
&& --cooldown <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
cooldown = AdaptiveAIGoalUtils.nextCooldown(mob);
|
||||
LivingEntity target = mob.getTarget();
|
||||
if (target == null || !target.isAlive()) {
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return;
|
||||
}
|
||||
int tier = tierSupplier.getAsInt();
|
||||
|
||||
@@ -6,17 +6,24 @@ import net.fretux.adaptivemobs.config.AMConfig;
|
||||
import net.minecraft.tags.FluidTags;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.PathfinderMob;
|
||||
import net.minecraft.world.entity.ai.goal.Goal;
|
||||
import net.minecraft.world.entity.monster.AbstractSkeleton;
|
||||
import net.minecraft.world.entity.monster.Creeper;
|
||||
import net.minecraft.world.entity.monster.EnderMan;
|
||||
import net.minecraft.world.entity.monster.Enemy;
|
||||
import net.minecraft.world.entity.monster.Pillager;
|
||||
import net.minecraft.world.entity.monster.Spider;
|
||||
import net.minecraft.world.entity.monster.Zombie;
|
||||
import net.minecraft.world.entity.vehicle.AbstractMinecart;
|
||||
import net.minecraft.world.entity.vehicle.Boat;
|
||||
import net.minecraft.world.entity.vehicle.ChestBoat;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
@@ -26,9 +33,13 @@ import java.util.function.IntSupplier;
|
||||
|
||||
public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
|
||||
private static final double TRAP_VEHICLE_BREAK_DISTANCE_SQR = 9.0D;
|
||||
|
||||
private final PathfinderMob mob;
|
||||
private final IntSupplier tierSupplier;
|
||||
private int cooldown;
|
||||
private LivingEntity activePillarTarget;
|
||||
private int activePillarTicks;
|
||||
|
||||
public AdaptiveAntiCheeseGoal(PathfinderMob mob, IntSupplier tierSupplier) {
|
||||
this.mob = mob;
|
||||
@@ -38,28 +49,32 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
if (!AMConfig.ENABLE_ANTI_CHEESE_AI.get() || tierSupplier.getAsInt() < 3) {
|
||||
if (!AMConfig.ENABLE_ANTI_CHEESE_AI.get()) {
|
||||
return false;
|
||||
}
|
||||
LivingEntity target = mob.getTarget();
|
||||
if (target == null || !target.isAlive()) {
|
||||
return false;
|
||||
}
|
||||
if (isInTrapVehicle() || isNearTrapVehicle() || nearestTrapBlock() != null) {
|
||||
if (isInTrapVehicle() || isTooCloseToTrapVehicle() || nearestTrapBlock() != null) {
|
||||
return true;
|
||||
}
|
||||
if (--cooldown > 0) {
|
||||
if (trappedAllyVehicle() != null) {
|
||||
return true;
|
||||
}
|
||||
if (mob instanceof Zombie && mob.isPassenger() && shouldUnstack(target)) {
|
||||
return true;
|
||||
}
|
||||
if (tierSupplier.getAsInt() < 3) {
|
||||
return false;
|
||||
}
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return false;
|
||||
}
|
||||
if (mob instanceof Zombie && isVerticalObstacleCheese(target)) {
|
||||
return true;
|
||||
}
|
||||
if ((mob instanceof AbstractSkeleton || mob instanceof Pillager) && !mob.hasLineOfSight(target)) {
|
||||
return true;
|
||||
if (--cooldown > 0) {
|
||||
return false;
|
||||
}
|
||||
if (mob instanceof Creeper creeper && target.hasLineOfSight(creeper)
|
||||
&& AdaptivePositioningUtils.isOpenToSky(mob)
|
||||
&& Math.sqrt(mob.distanceToSqr(target)) > 4.0D) {
|
||||
if ((mob instanceof AbstractSkeleton || mob instanceof Pillager) && !mob.hasLineOfSight(target)) {
|
||||
return true;
|
||||
}
|
||||
if (mob instanceof EnderMan && (mob.level().getFluidState(mob.blockPosition()).is(FluidTags.WATER)
|
||||
@@ -75,40 +90,46 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
@Override
|
||||
public void start() {
|
||||
LivingEntity target = mob.getTarget();
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
mob.getLookControl().setLookAt(target, 30.0F, 30.0F);
|
||||
|
||||
if (avoidTrap()) {
|
||||
cooldown = 8 + mob.getRandom().nextInt(16);
|
||||
return;
|
||||
}
|
||||
cooldown = 55 + mob.getRandom().nextInt(80);
|
||||
if (mob instanceof Zombie && isVerticalObstacleCheese(target)) {
|
||||
if (tryCorpseLadderSurge(target)) {
|
||||
return;
|
||||
}
|
||||
if (tryZombieStack(target)) {
|
||||
return;
|
||||
}
|
||||
Vec3 dest = AdaptivePositioningUtils.darkerSidePosition(mob, target, 2.5D, 0.0D);
|
||||
moveIfReasonable(dest, 1.0D, "zombie pillar gather");
|
||||
Entity trappedAllyVehicle = trappedAllyVehicle();
|
||||
if (trappedAllyVehicle != null && tryDestroyTrapVehicle(trappedAllyVehicle)) {
|
||||
cooldown = 10 + mob.getRandom().nextInt(20);
|
||||
return;
|
||||
}
|
||||
if (mob instanceof Zombie && mob.isPassenger() && shouldUnstack(target)) {
|
||||
mob.stopRiding();
|
||||
AdaptiveAIGoalUtils.clearAdaptiveStackedZombie(mob);
|
||||
cooldown = 12 + mob.getRandom().nextInt(18);
|
||||
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "zombie unstacking");
|
||||
return;
|
||||
}
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return;
|
||||
}
|
||||
mob.getLookControl().setLookAt(target, 30.0F, 30.0F);
|
||||
|
||||
if (mob instanceof Zombie && isVerticalObstacleCheese(target)) {
|
||||
activePillarTarget = target;
|
||||
activePillarTicks = 24 + mob.getRandom().nextInt(16);
|
||||
cooldown = 6 + mob.getRandom().nextInt(8);
|
||||
if (tryCorpseLadderPressure(target)) {
|
||||
return;
|
||||
}
|
||||
if (isPillaring(target) && tryZombieStack(target)) {
|
||||
return;
|
||||
}
|
||||
pressurePillarBase(target);
|
||||
return;
|
||||
}
|
||||
cooldown = 55 + mob.getRandom().nextInt(80);
|
||||
if ((mob instanceof AbstractSkeleton || mob instanceof Pillager) && !mob.hasLineOfSight(target)) {
|
||||
Vec3 dest = AdaptivePositioningUtils.darkerSidePosition(mob, target, 7.0D, 0.0D);
|
||||
moveIfReasonable(dest, 1.0D, "ranged tiny-window reposition");
|
||||
return;
|
||||
}
|
||||
if (mob instanceof Creeper creeper && target.hasLineOfSight(creeper)
|
||||
&& AdaptivePositioningUtils.isOpenToSky(mob)
|
||||
&& Math.sqrt(mob.distanceToSqr(target)) > 4.0D) {
|
||||
creeper.setSwellDir(-1);
|
||||
Vec3 dest = AdaptivePositioningUtils.darkerSidePosition(mob, target, 5.0D, 0.0D);
|
||||
moveIfReasonable(dest, 1.0D, "creeper open-ground hesitation");
|
||||
return;
|
||||
}
|
||||
if (mob instanceof EnderMan && (mob.level().getFluidState(mob.blockPosition()).is(FluidTags.WATER)
|
||||
|| target.level().getFluidState(target.blockPosition()).is(FluidTags.WATER))) {
|
||||
Vec3 dest = AdaptivePositioningUtils.retreatPosition(mob, target, 7.0D);
|
||||
@@ -119,18 +140,20 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
private boolean avoidTrap() {
|
||||
Entity vehicle = mob.getVehicle();
|
||||
if (vehicle instanceof Boat || vehicle instanceof AbstractMinecart) {
|
||||
mob.stopRiding();
|
||||
mob.getNavigation().stop();
|
||||
tryDestroyTrapVehicle(vehicle);
|
||||
moveAwayFrom(vehicle.position(), 1.15D, "trap vehicle escape");
|
||||
return true;
|
||||
}
|
||||
Entity trapVehicle = nearestTrapVehicle();
|
||||
if (trapVehicle != null && mob.distanceToSqr(trapVehicle) < 2.9D) {
|
||||
moveAwayFrom(trapVehicle.position(), 1.05D, "trap vehicle avoidance");
|
||||
if (trapVehicle != null && mob.distanceToSqr(trapVehicle) < TRAP_VEHICLE_BREAK_DISTANCE_SQR) {
|
||||
tryDestroyTrapVehicle(trapVehicle);
|
||||
return true;
|
||||
}
|
||||
Vec3 trapBlock = nearestTrapBlock();
|
||||
BlockPos trapBlock = nearestTrapBlock();
|
||||
if (trapBlock != null) {
|
||||
moveAwayFrom(trapBlock, 1.05D, "trap block avoidance");
|
||||
tryDestroyTrapBlock(trapBlock);
|
||||
moveAwayFrom(Vec3.atCenterOf(trapBlock), 1.05D, "trap block avoidance");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -141,13 +164,13 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
return vehicle instanceof Boat || vehicle instanceof AbstractMinecart;
|
||||
}
|
||||
|
||||
private boolean isNearTrapVehicle() {
|
||||
private boolean isTooCloseToTrapVehicle() {
|
||||
Entity trapVehicle = nearestTrapVehicle();
|
||||
return trapVehicle != null && mob.distanceToSqr(trapVehicle) < 2.9D;
|
||||
return trapVehicle != null && mob.distanceToSqr(trapVehicle) < TRAP_VEHICLE_BREAK_DISTANCE_SQR;
|
||||
}
|
||||
|
||||
private Entity nearestTrapVehicle() {
|
||||
List<Entity> vehicles = mob.level().getEntitiesOfClass(Entity.class, mob.getBoundingBox().inflate(2.5D),
|
||||
List<Entity> vehicles = mob.level().getEntitiesOfClass(Entity.class, mob.getBoundingBox().inflate(3.5D),
|
||||
entity -> entity instanceof Boat || entity instanceof AbstractMinecart);
|
||||
Entity nearest = null;
|
||||
double best = Double.MAX_VALUE;
|
||||
@@ -161,8 +184,105 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
return nearest;
|
||||
}
|
||||
|
||||
private Vec3 nearestTrapBlock() {
|
||||
Vec3 nearest = null;
|
||||
private Entity trappedAllyVehicle() {
|
||||
List<Entity> vehicles = mob.level().getEntitiesOfClass(Entity.class, mob.getBoundingBox().inflate(8.0D),
|
||||
entity -> isTrapVehicle(entity) && hasTrappedHostilePassenger(entity));
|
||||
Entity nearest = null;
|
||||
double best = Double.MAX_VALUE;
|
||||
for (Entity vehicle : vehicles) {
|
||||
double dist = mob.distanceToSqr(vehicle);
|
||||
if (dist < best) {
|
||||
best = dist;
|
||||
nearest = vehicle;
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
private boolean hasTrappedHostilePassenger(Entity vehicle) {
|
||||
for (Entity passenger : vehicle.getPassengers()) {
|
||||
if (passenger != mob && passenger instanceof Mob trapped && trapped instanceof Enemy && trapped.isAlive()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canContinueToUse() {
|
||||
return activePillarTicks > 0
|
||||
&& mob instanceof Zombie
|
||||
&& AdaptiveAIGoalUtils.isValidAdaptiveTarget(activePillarTarget)
|
||||
&& isVerticalObstacleCheese(activePillarTarget);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
if (activePillarTicks-- <= 0 || !(mob instanceof Zombie)
|
||||
|| !AdaptiveAIGoalUtils.isValidAdaptiveTarget(activePillarTarget)) {
|
||||
return;
|
||||
}
|
||||
mob.getLookControl().setLookAt(activePillarTarget, 30.0F, 30.0F);
|
||||
if (mob.isPassenger()) {
|
||||
tryCorpseLadderPressure(activePillarTarget);
|
||||
return;
|
||||
}
|
||||
if (!tryZombieStack(activePillarTarget)) {
|
||||
pressurePillarBase(activePillarTarget);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTrapVehicle(Entity entity) {
|
||||
return entity instanceof Boat || entity instanceof AbstractMinecart;
|
||||
}
|
||||
|
||||
private boolean tryDestroyTrapVehicle(Entity vehicle) {
|
||||
if (mob.isPassenger() && mob.getVehicle() != vehicle) {
|
||||
return false;
|
||||
}
|
||||
if (mob.distanceToSqr(vehicle) > TRAP_VEHICLE_BREAK_DISTANCE_SQR) {
|
||||
mob.getNavigation().moveTo(vehicle.getX(), vehicle.getY(), vehicle.getZ(), 1.12D);
|
||||
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "moving to break ally trap vehicle");
|
||||
return true;
|
||||
}
|
||||
mob.swing(mob.getUsedItemHand());
|
||||
dropTrapVehicleItem(vehicle);
|
||||
vehicle.ejectPassengers();
|
||||
vehicle.discard();
|
||||
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "breaking ally trap vehicle");
|
||||
return true;
|
||||
}
|
||||
|
||||
private void dropTrapVehicleItem(Entity vehicle) {
|
||||
Item drop = null;
|
||||
if (vehicle instanceof Boat boat) {
|
||||
drop = boatDrop(boat);
|
||||
} else if (vehicle instanceof AbstractMinecart) {
|
||||
drop = Items.MINECART;
|
||||
}
|
||||
if (drop != null) {
|
||||
vehicle.spawnAtLocation(drop);
|
||||
}
|
||||
}
|
||||
|
||||
private Item boatDrop(Boat boat) {
|
||||
boolean chest = boat instanceof ChestBoat;
|
||||
return switch (boat.getVariant()) {
|
||||
case OAK -> chest ? Items.OAK_CHEST_BOAT : Items.OAK_BOAT;
|
||||
case SPRUCE -> chest ? Items.SPRUCE_CHEST_BOAT : Items.SPRUCE_BOAT;
|
||||
case BIRCH -> chest ? Items.BIRCH_CHEST_BOAT : Items.BIRCH_BOAT;
|
||||
case JUNGLE -> chest ? Items.JUNGLE_CHEST_BOAT : Items.JUNGLE_BOAT;
|
||||
case ACACIA -> chest ? Items.ACACIA_CHEST_BOAT : Items.ACACIA_BOAT;
|
||||
case DARK_OAK -> chest ? Items.DARK_OAK_CHEST_BOAT : Items.DARK_OAK_BOAT;
|
||||
case MANGROVE -> chest ? Items.MANGROVE_CHEST_BOAT : Items.MANGROVE_BOAT;
|
||||
case CHERRY -> chest ? Items.CHERRY_CHEST_BOAT : Items.CHERRY_BOAT;
|
||||
case BAMBOO -> chest ? Items.BAMBOO_CHEST_RAFT : Items.BAMBOO_RAFT;
|
||||
default -> chest ? Items.OAK_CHEST_BOAT : Items.OAK_BOAT;
|
||||
};
|
||||
}
|
||||
|
||||
private BlockPos nearestTrapBlock() {
|
||||
BlockPos nearest = null;
|
||||
double best = Double.MAX_VALUE;
|
||||
for (int x = -1; x <= 1; x++) {
|
||||
for (int y = -1; y <= 1; y++) {
|
||||
@@ -171,8 +291,8 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
if (!isTrapBlock(state)) {
|
||||
continue;
|
||||
}
|
||||
Vec3 pos = Vec3.atCenterOf(mob.blockPosition().offset(x, y, z));
|
||||
double dist = mob.position().distanceToSqr(pos);
|
||||
BlockPos pos = mob.blockPosition().offset(x, y, z);
|
||||
double dist = mob.position().distanceToSqr(Vec3.atCenterOf(pos));
|
||||
if (dist < best) {
|
||||
best = dist;
|
||||
nearest = pos;
|
||||
@@ -189,6 +309,24 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
|| state.is(Blocks.SWEET_BERRY_BUSH);
|
||||
}
|
||||
|
||||
private void tryDestroyTrapBlock(BlockPos pos) {
|
||||
BlockState state = mob.level().getBlockState(pos);
|
||||
if (!isTrapBlock(state)) {
|
||||
return;
|
||||
}
|
||||
if (mob instanceof Spider && state.is(Blocks.COBWEB)) {
|
||||
return;
|
||||
}
|
||||
if (state.is(Blocks.COBWEB)) {
|
||||
Block.popResource(mob.level(), pos, new ItemStack(Items.STRING));
|
||||
} else if (state.is(Blocks.SWEET_BERRY_BUSH)) {
|
||||
Block.popResource(mob.level(), pos, new ItemStack(Items.SWEET_BERRIES));
|
||||
}
|
||||
mob.level().destroyBlock(pos, false, mob);
|
||||
mob.getNavigation().stop();
|
||||
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "breaking trap block");
|
||||
}
|
||||
|
||||
private void moveAwayFrom(Vec3 threat, double speed, String debug) {
|
||||
Vec3 away = mob.position().subtract(threat);
|
||||
double len = Math.sqrt(away.x * away.x + away.z * away.z);
|
||||
@@ -208,15 +346,15 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
zombie -> zombie != mob
|
||||
&& zombie.isAlive()
|
||||
&& !zombie.isVehicle()
|
||||
&& !zombie.isPassenger()
|
||||
&& zombie.getTarget() == target
|
||||
&& Math.abs(zombie.getY() - mob.getY()) < 1.25D);
|
||||
&& !isInVehicleChain(zombie, mob)
|
||||
&& Math.abs(zombie.getY() - mob.getY()) < 8.0D);
|
||||
Zombie bestCarrier = null;
|
||||
double best = Double.MAX_VALUE;
|
||||
double best = -1.0D;
|
||||
for (Zombie carrier : carriers) {
|
||||
double dist = mob.distanceToSqr(carrier);
|
||||
if (dist < best) {
|
||||
best = dist;
|
||||
double score = stackHeight(carrier) * 10.0D - mob.distanceToSqr(carrier);
|
||||
if (score > best) {
|
||||
best = score;
|
||||
bestCarrier = carrier;
|
||||
}
|
||||
}
|
||||
@@ -225,32 +363,108 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
}
|
||||
boolean mounted = mob.startRiding(bestCarrier, true);
|
||||
if (mounted) {
|
||||
AdaptiveAIGoalUtils.markAdaptiveStackedZombie(mob);
|
||||
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "zombie stacking over obstacle");
|
||||
}
|
||||
return mounted;
|
||||
}
|
||||
|
||||
private boolean tryCorpseLadderSurge(LivingEntity target) {
|
||||
if (tierSupplier.getAsInt() < 5 || !mob.isPassenger()) {
|
||||
private boolean tryCorpseLadderPressure(LivingEntity target) {
|
||||
if (!(mob instanceof Zombie) || !mob.isPassenger()) {
|
||||
return false;
|
||||
}
|
||||
Entity base = stackBase(mob);
|
||||
Vec3 baseDest = new Vec3(target.getX(), base.getY(), target.getZ());
|
||||
if (base instanceof PathfinderMob baseMob) {
|
||||
baseMob.getNavigation().moveTo(baseDest.x, baseDest.y, baseDest.z, 1.05D);
|
||||
}
|
||||
|
||||
double vertical = target.getY() - mob.getY();
|
||||
double horizontal = Math.sqrt((target.getX() - mob.getX()) * (target.getX() - mob.getX())
|
||||
+ (target.getZ() - mob.getZ()) * (target.getZ() - mob.getZ()));
|
||||
if (vertical < 0.75D || vertical > 2.25D || horizontal > 2.0D) {
|
||||
return false;
|
||||
if (horizontal > 3.2D || vertical < 0.25D || vertical > 5.5D) {
|
||||
return true;
|
||||
}
|
||||
List<Zombie> nearby = mob.level().getEntitiesOfClass(Zombie.class, mob.getBoundingBox().inflate(2.5D),
|
||||
zombie -> zombie.isAlive() && zombie.getTarget() == target);
|
||||
if (nearby.size() < 3) {
|
||||
return false;
|
||||
}
|
||||
mob.setDeltaMovement(mob.getDeltaMovement().add(0.0D, 0.45D, 0.0D));
|
||||
|
||||
Vec3 toward = target.position().subtract(mob.position());
|
||||
double len = Math.max(0.001D, Math.sqrt(toward.x * toward.x + toward.z * toward.z));
|
||||
double lift = tierSupplier.getAsInt() >= 5 ? 0.42D : 0.32D;
|
||||
mob.setDeltaMovement(mob.getDeltaMovement().add(toward.x / len * 0.18D, lift, toward.z / len * 0.18D));
|
||||
mob.hurtMarked = true;
|
||||
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "zombie corpse ladder surge");
|
||||
|
||||
if (vertical <= 1.8D && horizontal <= 2.4D) {
|
||||
mob.stopRiding();
|
||||
AdaptiveAIGoalUtils.clearAdaptiveStackedZombie(mob);
|
||||
}
|
||||
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "zombie corpse ladder pressure");
|
||||
return true;
|
||||
}
|
||||
|
||||
private void pressurePillarBase(LivingEntity target) {
|
||||
Vec3 dest = AdaptivePositioningUtils.darkerSidePosition(mob, target, 1.4D, 0.0D);
|
||||
if (!moveIfReasonable(dest, 1.08D, "zombie pillar gather")) {
|
||||
mob.getNavigation().moveTo(target.getX(), mob.getY(), target.getZ(), 1.08D);
|
||||
}
|
||||
double horizontal = Math.sqrt((target.getX() - mob.getX()) * (target.getX() - mob.getX())
|
||||
+ (target.getZ() - mob.getZ()) * (target.getZ() - mob.getZ()));
|
||||
double vertical = target.getY() - mob.getY();
|
||||
if (horizontal < 2.0D && vertical > 1.8D && mob.onGround()) {
|
||||
Vec3 toward = target.position().subtract(mob.position());
|
||||
double len = Math.max(0.001D, Math.sqrt(toward.x * toward.x + toward.z * toward.z));
|
||||
mob.setDeltaMovement(mob.getDeltaMovement().add(toward.x / len * 0.10D, 0.26D, toward.z / len * 0.10D));
|
||||
mob.hurtMarked = true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldUnstack(LivingEntity target) {
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
AdaptiveAIGoalUtils.clearAdaptiveStackedZombie(mob);
|
||||
return true;
|
||||
}
|
||||
if (mob.distanceToSqr(target) > 8.0D * 8.0D) {
|
||||
AdaptiveAIGoalUtils.clearAdaptiveStackedZombie(mob);
|
||||
return true;
|
||||
}
|
||||
Entity base = stackBase(mob);
|
||||
double verticalFromBase = target.getY() - base.getY();
|
||||
double horizontalFromBase = Math.sqrt((target.getX() - base.getX()) * (target.getX() - base.getX())
|
||||
+ (target.getZ() - base.getZ()) * (target.getZ() - base.getZ()));
|
||||
if (verticalFromBase > 2.5D && horizontalFromBase < 5.5D) {
|
||||
return false;
|
||||
}
|
||||
AdaptiveAIGoalUtils.clearAdaptiveStackedZombie(mob);
|
||||
return !isVerticalObstacleCheese(target);
|
||||
}
|
||||
|
||||
private int stackHeight(Entity entity) {
|
||||
int height = 1;
|
||||
Entity cursor = entity;
|
||||
while (cursor.getVehicle() instanceof Zombie) {
|
||||
height++;
|
||||
cursor = cursor.getVehicle();
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
private boolean isInVehicleChain(Entity start, Entity candidate) {
|
||||
Entity cursor = start;
|
||||
while (cursor != null) {
|
||||
if (cursor == candidate) {
|
||||
return true;
|
||||
}
|
||||
cursor = cursor.getVehicle();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Entity stackBase(Entity entity) {
|
||||
Entity cursor = entity;
|
||||
while (cursor.getVehicle() instanceof Zombie) {
|
||||
cursor = cursor.getVehicle();
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
private boolean isVerticalObstacleCheese(LivingEntity target) {
|
||||
return isPillaring(target) || isSimpleWallBlocked(target);
|
||||
}
|
||||
@@ -285,10 +499,12 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
return !mob.level().getBlockState(pos).getCollisionShape(mob.level(), pos).isEmpty();
|
||||
}
|
||||
|
||||
private void moveIfReasonable(Vec3 dest, double speed, String debug) {
|
||||
private boolean moveIfReasonable(Vec3 dest, double speed, String debug) {
|
||||
if (AdaptivePositioningUtils.isReasonableGround(mob, dest)) {
|
||||
mob.getNavigation().moveTo(dest.x, dest.y, dest.z, speed);
|
||||
AdaptiveAIGoalUtils.debug(mob, tierSupplier, debug);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,28 @@ package net.fretux.adaptivemobs.ai.goals;
|
||||
|
||||
import net.fretux.adaptivemobs.ai.AdaptiveAIGoalUtils;
|
||||
import net.fretux.adaptivemobs.ai.AdaptivePositioningUtils;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ai.goal.Goal;
|
||||
import net.minecraft.world.entity.monster.Creeper;
|
||||
import net.minecraft.world.entity.vehicle.AbstractMinecart;
|
||||
import net.minecraft.world.entity.vehicle.Boat;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.pathfinder.Path;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
public class AdaptiveCreeperPressureGoal extends Goal {
|
||||
|
||||
private final Creeper creeper;
|
||||
private final IntSupplier tierSupplier;
|
||||
private int pathCheckCooldown;
|
||||
private boolean targetReachable;
|
||||
|
||||
public AdaptiveCreeperPressureGoal(Creeper creeper, IntSupplier tierSupplier) {
|
||||
this.creeper = creeper;
|
||||
@@ -24,13 +34,13 @@ public class AdaptiveCreeperPressureGoal extends Goal {
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
LivingEntity target = creeper.getTarget();
|
||||
return tierSupplier.getAsInt() >= 3 && target != null && target.isAlive();
|
||||
return tierSupplier.getAsInt() >= 3 && AdaptiveAIGoalUtils.isValidAdaptiveTarget(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
LivingEntity target = creeper.getTarget();
|
||||
if (target == null) {
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return;
|
||||
}
|
||||
int tier = tierSupplier.getAsInt();
|
||||
@@ -38,14 +48,26 @@ public class AdaptiveCreeperPressureGoal extends Goal {
|
||||
double stopSwell = tier >= 4 ? 7.0D : 6.0D;
|
||||
double dist = Math.sqrt(creeper.distanceToSqr(target));
|
||||
creeper.getLookControl().setLookAt(target, 30.0F, 30.0F);
|
||||
updateReachability(target);
|
||||
|
||||
if (avoidTrapOnApproach(target)) {
|
||||
creeper.setSwellDir(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!targetReachable) {
|
||||
keepDistance(target, dist);
|
||||
return;
|
||||
}
|
||||
|
||||
if (AdaptiveAIGoalUtils.isBlockingShield(target) && dist <= stopSwell) {
|
||||
creeper.setSwellDir(-1);
|
||||
Vec3 dest = AdaptivePositioningUtils.sidePosition(creeper, target, Math.max(2.8D, dist),
|
||||
creeper.getRandom().nextBoolean() ? 1.0D : -1.0D, tier >= 5 ? 0.25D : 0.0D);
|
||||
if (AdaptivePositioningUtils.isReasonableGround(creeper, dest)) {
|
||||
creeper.getNavigation().moveTo(dest.x, dest.y, dest.z, 1.05D);
|
||||
Vec3 dest = behindTarget(target);
|
||||
if (!AdaptivePositioningUtils.isReasonableGround(creeper, dest)) {
|
||||
dest = AdaptivePositioningUtils.sidePosition(creeper, target, Math.max(2.8D, dist),
|
||||
creeper.getRandom().nextBoolean() ? 1.0D : -1.0D, tier >= 5 ? 0.25D : 0.0D);
|
||||
}
|
||||
moveIfReasonable(dest, 1.12D, "creeper shield backstep");
|
||||
return;
|
||||
}
|
||||
if (dist <= startSwell || (tier >= 4 && target.getLastHurtMob() != null && dist <= stopSwell)) {
|
||||
@@ -53,6 +75,122 @@ public class AdaptiveCreeperPressureGoal extends Goal {
|
||||
creeper.getNavigation().stop();
|
||||
} else {
|
||||
creeper.setSwellDir(-1);
|
||||
creeper.getNavigation().moveTo(target, tier >= 5 ? 1.12D : 1.05D);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateReachability(LivingEntity target) {
|
||||
if (pathCheckCooldown-- > 0) {
|
||||
return;
|
||||
}
|
||||
pathCheckCooldown = 15 + creeper.getRandom().nextInt(10);
|
||||
Path path = creeper.getNavigation().createPath(target, 0);
|
||||
targetReachable = path != null && path.canReach();
|
||||
}
|
||||
|
||||
private void keepDistance(LivingEntity target, double dist) {
|
||||
creeper.setSwellDir(-1);
|
||||
double desired = 8.0D;
|
||||
if (dist < desired) {
|
||||
Vec3 dest = AdaptivePositioningUtils.retreatPosition(creeper, target, desired - dist + 3.0D);
|
||||
if (!moveIfReasonable(dest, 1.05D, "creeper unreachable spacing")) {
|
||||
Vec3 side = AdaptivePositioningUtils.sidePosition(creeper, target, desired, 1.0D, 0.0D);
|
||||
moveIfReasonable(side, 1.0D, "creeper unreachable side spacing");
|
||||
}
|
||||
} else {
|
||||
creeper.getNavigation().stop();
|
||||
}
|
||||
}
|
||||
|
||||
private Vec3 behindTarget(LivingEntity target) {
|
||||
Vec3 look = target.getLookAngle();
|
||||
double len = Math.sqrt(look.x * look.x + look.z * look.z);
|
||||
if (len < 1.0E-3D) {
|
||||
return target.position();
|
||||
}
|
||||
return target.position().subtract(look.x / len * 1.8D, 0.0D, look.z / len * 1.8D);
|
||||
}
|
||||
|
||||
private boolean avoidTrapOnApproach(LivingEntity target) {
|
||||
Entity trapVehicle = nearestTrapVehicle();
|
||||
if (trapVehicle != null && creeper.distanceToSqr(trapVehicle) < 9.0D) {
|
||||
return moveAwayFrom(trapVehicle.position(), 1.1D, "creeper trap vehicle avoidance");
|
||||
}
|
||||
Vec3 trapBlock = nearestTrapBlock();
|
||||
if (trapBlock != null && creeper.position().distanceToSqr(trapBlock) < 6.25D) {
|
||||
return moveAwayFrom(trapBlock, 1.1D, "creeper trap block avoidance");
|
||||
}
|
||||
Vec3 toTarget = target.position().subtract(creeper.position());
|
||||
double len = Math.sqrt(toTarget.x * toTarget.x + toTarget.z * toTarget.z);
|
||||
if (len < 1.0E-3D) {
|
||||
return false;
|
||||
}
|
||||
Vec3 ahead = creeper.position().add(toTarget.x / len * 2.0D, 0.0D, toTarget.z / len * 2.0D);
|
||||
BlockState aheadState = creeper.level().getBlockState(BlockPos.containing(ahead));
|
||||
return isTrapBlock(aheadState) && moveAwayFrom(ahead, 1.1D, "creeper approach trap avoidance");
|
||||
}
|
||||
|
||||
private Entity nearestTrapVehicle() {
|
||||
List<Entity> vehicles = creeper.level().getEntitiesOfClass(Entity.class, creeper.getBoundingBox().inflate(2.8D),
|
||||
entity -> entity instanceof Boat || entity instanceof AbstractMinecart);
|
||||
Entity nearest = null;
|
||||
double best = Double.MAX_VALUE;
|
||||
for (Entity entity : vehicles) {
|
||||
double dist = creeper.distanceToSqr(entity);
|
||||
if (dist < best) {
|
||||
best = dist;
|
||||
nearest = entity;
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
private Vec3 nearestTrapBlock() {
|
||||
Vec3 nearest = null;
|
||||
double best = Double.MAX_VALUE;
|
||||
BlockPos origin = creeper.blockPosition();
|
||||
for (int x = -1; x <= 1; x++) {
|
||||
for (int y = -1; y <= 1; y++) {
|
||||
for (int z = -1; z <= 1; z++) {
|
||||
BlockPos pos = origin.offset(x, y, z);
|
||||
if (!isTrapBlock(creeper.level().getBlockState(pos))) {
|
||||
continue;
|
||||
}
|
||||
Vec3 center = Vec3.atCenterOf(pos);
|
||||
double dist = creeper.position().distanceToSqr(center);
|
||||
if (dist < best) {
|
||||
best = dist;
|
||||
nearest = center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
private boolean isTrapBlock(BlockState state) {
|
||||
return state.is(Blocks.COBWEB)
|
||||
|| state.is(Blocks.POWDER_SNOW)
|
||||
|| state.is(Blocks.SWEET_BERRY_BUSH);
|
||||
}
|
||||
|
||||
private boolean moveAwayFrom(Vec3 threat, double speed, String debug) {
|
||||
Vec3 away = creeper.position().subtract(threat);
|
||||
double len = Math.sqrt(away.x * away.x + away.z * away.z);
|
||||
if (len < 1.0E-3D) {
|
||||
away = new Vec3(creeper.getRandom().nextBoolean() ? 1.0D : -1.0D, 0.0D, 0.0D);
|
||||
len = 1.0D;
|
||||
}
|
||||
Vec3 dest = creeper.position().add(away.x / len * 4.0D, 0.0D, away.z / len * 4.0D);
|
||||
return moveIfReasonable(dest, speed, debug);
|
||||
}
|
||||
|
||||
private boolean moveIfReasonable(Vec3 dest, double speed, String debug) {
|
||||
if (AdaptivePositioningUtils.isReasonableGround(creeper, dest)) {
|
||||
creeper.getNavigation().moveTo(dest.x, dest.y, dest.z, speed);
|
||||
AdaptiveAIGoalUtils.debug(creeper, tierSupplier, debug);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,16 @@ 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;
|
||||
@@ -17,9 +22,32 @@ 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;
|
||||
@@ -31,15 +59,14 @@ public class AdaptiveEndermanTacticsGoal extends Goal {
|
||||
public boolean canUse() {
|
||||
return AMConfig.ENABLE_ENDERMAN_AI.get()
|
||||
&& tierSupplier.getAsInt() >= 2
|
||||
&& enderman.getTarget() != null
|
||||
&& enderman.getTarget().isAlive()
|
||||
&& AdaptiveAIGoalUtils.isValidAdaptiveTarget(enderman.getTarget())
|
||||
&& --cooldown <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
LivingEntity target = enderman.getTarget();
|
||||
if (target == null) {
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return;
|
||||
}
|
||||
int tier = tierSupplier.getAsInt();
|
||||
@@ -51,10 +78,18 @@ public class AdaptiveEndermanTacticsGoal extends Goal {
|
||||
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 -> 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);
|
||||
@@ -71,4 +106,112 @@ public class AdaptiveEndermanTacticsGoal extends Goal {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public class AdaptiveEndermiteTacticsGoal extends Goal {
|
||||
public void start() {
|
||||
cooldown = 18 + mob.getRandom().nextInt(28);
|
||||
LivingEntity target = currentTarget();
|
||||
if (target == null) {
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return;
|
||||
}
|
||||
rememberedTarget = target;
|
||||
@@ -61,10 +61,12 @@ public class AdaptiveEndermiteTacticsGoal extends Goal {
|
||||
|
||||
private LivingEntity currentTarget() {
|
||||
LivingEntity target = mob.getTarget();
|
||||
if (target != null && target.isAlive()) {
|
||||
if (AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return target;
|
||||
}
|
||||
if (rememberedTarget != null && rememberedTarget.isAlive() && mob.distanceToSqr(rememberedTarget) < 16.0D * 16.0D) {
|
||||
if (AdaptiveAIGoalUtils.isValidAdaptiveTarget(rememberedTarget)
|
||||
&& mob.distanceToSqr(rememberedTarget) < 16.0D * 16.0D
|
||||
&& mob.canAttack(rememberedTarget)) {
|
||||
return rememberedTarget;
|
||||
}
|
||||
rememberedTarget = null;
|
||||
|
||||
@@ -32,8 +32,7 @@ public class AdaptiveGuardianTacticsGoal extends Goal {
|
||||
&& AMConfig.ENABLE_GUARDIAN_ADVANCED_AI.get()
|
||||
&& AMConfig.AI_GUARDIAN.get()
|
||||
&& tierSupplier.getAsInt() >= 2
|
||||
&& guardian.getTarget() != null
|
||||
&& guardian.getTarget().isAlive()
|
||||
&& AdaptiveAIGoalUtils.isValidAdaptiveTarget(guardian.getTarget())
|
||||
&& --cooldown <= 0;
|
||||
}
|
||||
|
||||
@@ -42,7 +41,7 @@ public class AdaptiveGuardianTacticsGoal extends Goal {
|
||||
boolean elder = guardian instanceof ElderGuardian;
|
||||
cooldown = elder ? 90 + guardian.getRandom().nextInt(80) : AdaptiveAIGoalUtils.nextCooldown(guardian);
|
||||
LivingEntity target = guardian.getTarget();
|
||||
if (target == null) {
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return;
|
||||
}
|
||||
int tier = tierSupplier.getAsInt();
|
||||
@@ -62,7 +61,7 @@ public class AdaptiveGuardianTacticsGoal extends Goal {
|
||||
private void retargetLowHealthGuardianTarget(LivingEntity current) {
|
||||
List<LivingEntity> candidates = guardian.level().getEntitiesOfClass(LivingEntity.class,
|
||||
guardian.getBoundingBox().inflate(12.0D),
|
||||
entity -> entity != guardian && entity.isAlive() && guardian.canAttack(entity)
|
||||
entity -> entity != guardian && AdaptiveAIGoalUtils.isValidAdaptiveTarget(entity) && guardian.canAttack(entity)
|
||||
&& guardian.hasLineOfSight(entity)
|
||||
&& AdaptiveAIGoalUtils.healthFraction(entity) < AdaptiveAIGoalUtils.healthFraction(current));
|
||||
if (!candidates.isEmpty()) {
|
||||
|
||||
@@ -40,7 +40,7 @@ public class AdaptiveRavagerTacticsGoal extends Goal {
|
||||
public void start() {
|
||||
cooldown = 55 + ravager.getRandom().nextInt(65);
|
||||
LivingEntity target = currentTarget();
|
||||
if (target == null) {
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return;
|
||||
}
|
||||
rememberedTarget = target;
|
||||
@@ -65,10 +65,12 @@ public class AdaptiveRavagerTacticsGoal extends Goal {
|
||||
|
||||
private LivingEntity currentTarget() {
|
||||
LivingEntity target = ravager.getTarget();
|
||||
if (target != null && target.isAlive()) {
|
||||
if (AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return target;
|
||||
}
|
||||
if (rememberedTarget != null && rememberedTarget.isAlive() && ravager.distanceToSqr(rememberedTarget) < 20.0D * 20.0D) {
|
||||
if (AdaptiveAIGoalUtils.isValidAdaptiveTarget(rememberedTarget)
|
||||
&& ravager.distanceToSqr(rememberedTarget) < 20.0D * 20.0D
|
||||
&& ravager.canAttack(rememberedTarget)) {
|
||||
return rememberedTarget;
|
||||
}
|
||||
rememberedTarget = null;
|
||||
|
||||
@@ -28,17 +28,19 @@ public class AdaptiveSharedTargetGoal extends Goal {
|
||||
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
return tierSupplier.getAsInt() >= minTier && mob.getTarget() != null && --cooldown <= 0;
|
||||
return tierSupplier.getAsInt() >= minTier && AdaptiveAIGoalUtils.isValidAdaptiveTarget(mob.getTarget())
|
||||
&& --cooldown <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
cooldown = AdaptiveAIGoalUtils.nextCooldown(mob);
|
||||
LivingEntity target = mob.getTarget();
|
||||
if (target == null || !target.isAlive()) {
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return;
|
||||
}
|
||||
List<Mob> allies = AdaptiveTargetingUtils.nearbyHostiles(mob, radius, other -> other.getTarget() == null);
|
||||
List<Mob> allies = AdaptiveTargetingUtils.nearbyHostiles(mob, radius,
|
||||
other -> other.getTarget() == null && other.canAttack(target));
|
||||
int shared = 0;
|
||||
for (Mob ally : allies) {
|
||||
if (shared++ >= 4) {
|
||||
|
||||
@@ -30,8 +30,7 @@ public class AdaptiveShulkerTacticsGoal extends Goal {
|
||||
&& AMConfig.ENABLE_SHULKER_ADVANCED_AI.get()
|
||||
&& AMConfig.AI_SHULKER.get()
|
||||
&& tierSupplier.getAsInt() >= 2
|
||||
&& shulker.getTarget() != null
|
||||
&& shulker.getTarget().isAlive()
|
||||
&& AdaptiveAIGoalUtils.isValidAdaptiveTarget(shulker.getTarget())
|
||||
&& --cooldown <= 0;
|
||||
}
|
||||
|
||||
@@ -42,7 +41,7 @@ public class AdaptiveShulkerTacticsGoal extends Goal {
|
||||
cooldown = inEnd && tier >= 5 ? 30 + shulker.getRandom().nextInt(35)
|
||||
: 50 + shulker.getRandom().nextInt(tier >= 5 ? 45 : 70);
|
||||
LivingEntity target = shulker.getTarget();
|
||||
if (target == null) {
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return;
|
||||
}
|
||||
if (!shulker.hasLineOfSight(target)) {
|
||||
@@ -58,7 +57,9 @@ public class AdaptiveShulkerTacticsGoal extends Goal {
|
||||
if (tier >= 4) {
|
||||
double radius = inEnd && tier >= 5 ? 18.0D : 12.0D;
|
||||
AdaptiveTargetingUtils.nearbyHostiles(shulker, radius,
|
||||
other -> other instanceof Shulker && (other.getTarget() == null || other.getTarget() == target))
|
||||
other -> other instanceof Shulker
|
||||
&& (other.getTarget() == null || other.getTarget() == target)
|
||||
&& other.canAttack(target))
|
||||
.forEach(other -> other.setTarget(target));
|
||||
AdaptiveAIGoalUtils.debug(shulker, tierSupplier, "shulker coordinated target");
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package net.fretux.adaptivemobs.ai.goals;
|
||||
|
||||
import net.fretux.adaptivemobs.ai.AdaptiveAIGoalUtils;
|
||||
import net.fretux.adaptivemobs.ai.AdaptivePositioningUtils;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.PathfinderMob;
|
||||
import net.minecraft.world.entity.ai.goal.Goal;
|
||||
@@ -16,6 +17,8 @@ public class AdaptiveSpiderTacticsGoal extends Goal {
|
||||
private final IntSupplier tierSupplier;
|
||||
private final boolean caveSpider;
|
||||
private int cooldown;
|
||||
private LivingEntity climbTarget;
|
||||
private int climbTicks;
|
||||
|
||||
public AdaptiveSpiderTacticsGoal(PathfinderMob mob, IntSupplier tierSupplier, boolean caveSpider) {
|
||||
this.mob = mob;
|
||||
@@ -28,18 +31,40 @@ public class AdaptiveSpiderTacticsGoal extends Goal {
|
||||
public boolean canUse() {
|
||||
int tier = tierSupplier.getAsInt();
|
||||
int min = caveSpider ? 2 : 3;
|
||||
return tier >= min && mob.getTarget() != null && mob.getTarget().isAlive() && --cooldown <= 0;
|
||||
return tier >= min && AdaptiveAIGoalUtils.isValidAdaptiveTarget(mob.getTarget()) && --cooldown <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canContinueToUse() {
|
||||
return climbTicks > 0 && AdaptiveAIGoalUtils.isValidAdaptiveTarget(climbTarget) && isPillaring(climbTarget);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
if (climbTarget == null || climbTicks-- <= 0) {
|
||||
return;
|
||||
}
|
||||
climbPillar(climbTarget);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
cooldown = caveSpider ? 30 + mob.getRandom().nextInt(40) : AdaptiveAIGoalUtils.nextCooldown(mob);
|
||||
LivingEntity target = mob.getTarget();
|
||||
if (target == null) {
|
||||
climbTarget = null;
|
||||
climbTicks = 0;
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return;
|
||||
}
|
||||
int tier = tierSupplier.getAsInt();
|
||||
double distance = Math.sqrt(mob.distanceToSqr(target));
|
||||
if (tier >= 3 && isPillaring(target)) {
|
||||
climbTarget = target;
|
||||
climbTicks = caveSpider ? 45 : 35;
|
||||
climbPillar(target);
|
||||
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "spider pillar climb");
|
||||
return;
|
||||
}
|
||||
if (tier >= 4) {
|
||||
Vec3 elevated = AdaptivePositioningUtils.elevatedPosition(mob, target, 3.0D);
|
||||
mob.getNavigation().moveTo(elevated.x, elevated.y, elevated.z, caveSpider ? 1.2D : 1.1D);
|
||||
@@ -54,4 +79,37 @@ public class AdaptiveSpiderTacticsGoal extends Goal {
|
||||
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "spider lunge");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPillaring(LivingEntity target) {
|
||||
double vertical = target.getY() - mob.getY();
|
||||
double horizontal = Math.sqrt((target.getX() - mob.getX()) * (target.getX() - mob.getX())
|
||||
+ (target.getZ() - mob.getZ()) * (target.getZ() - mob.getZ()));
|
||||
return vertical > 2.2D && vertical <= 10.0D && horizontal < 6.5D;
|
||||
}
|
||||
|
||||
private void climbPillar(LivingEntity target) {
|
||||
Vec3 side = pillarSidePosition(target);
|
||||
mob.getLookControl().setLookAt(target, 35.0F, 35.0F);
|
||||
mob.getNavigation().moveTo(side.x, mob.getY(), side.z, caveSpider ? 1.25D : 1.15D);
|
||||
|
||||
double horizontal = Math.sqrt((side.x - mob.getX()) * (side.x - mob.getX()) + (side.z - mob.getZ()) * (side.z - mob.getZ()));
|
||||
if (horizontal < 1.4D || mob.horizontalCollision) {
|
||||
Vec3 toward = new Vec3(target.getX() - mob.getX(), 0.0D, target.getZ() - mob.getZ());
|
||||
double len = Math.max(0.001D, Math.sqrt(toward.x * toward.x + toward.z * toward.z));
|
||||
mob.setDeltaMovement(mob.getDeltaMovement().add(toward.x / len * 0.08D, caveSpider ? 0.26D : 0.22D,
|
||||
toward.z / len * 0.08D));
|
||||
mob.hurtMarked = true;
|
||||
}
|
||||
}
|
||||
|
||||
private Vec3 pillarSidePosition(LivingEntity target) {
|
||||
BlockPos targetBase = BlockPos.containing(target.getX(), mob.getY(), target.getZ());
|
||||
Vec3 away = mob.position().subtract(Vec3.atCenterOf(targetBase));
|
||||
if (Math.abs(away.x) > Math.abs(away.z)) {
|
||||
targetBase = targetBase.offset(away.x >= 0.0D ? 1 : -1, 0, 0);
|
||||
} else {
|
||||
targetBase = targetBase.offset(0, 0, away.z >= 0.0D ? 1 : -1);
|
||||
}
|
||||
return Vec3.atCenterOf(targetBase);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ public class AdaptiveTargetPriorityGoal extends Goal {
|
||||
cooldown = 80 + mob.getRandom().nextInt(80);
|
||||
double radius = AdaptiveAIGoalUtils.followRange(mob, 16.0D);
|
||||
List<Player> players = mob.level().getEntitiesOfClass(Player.class, mob.getBoundingBox().inflate(radius),
|
||||
player -> player.isAlive() && !player.isCreative() && !player.isSpectator()
|
||||
&& mob.canAttack(player) && mob.hasLineOfSight(player));
|
||||
player -> AdaptiveAIGoalUtils.isValidAdaptiveTarget(player) && mob.canAttack(player)
|
||||
&& mob.hasLineOfSight(player));
|
||||
Player best = null;
|
||||
double bestScore = currentScore();
|
||||
for (Player player : players) {
|
||||
@@ -52,7 +52,7 @@ public class AdaptiveTargetPriorityGoal extends Goal {
|
||||
}
|
||||
|
||||
private double currentScore() {
|
||||
if (mob.getTarget() instanceof Player player) {
|
||||
if (mob.getTarget() instanceof Player player && AdaptiveAIGoalUtils.isValidAdaptiveTarget(player)) {
|
||||
return AdaptiveAIGoalUtils.playerPriorityScore(mob, player);
|
||||
}
|
||||
return 0.0D;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package net.fretux.adaptivemobs.ai.goals;
|
||||
|
||||
import net.fretux.adaptivemobs.ai.AdaptiveAIGoalUtils;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.ai.goal.Goal;
|
||||
import net.minecraft.world.entity.monster.Zombie;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class AdaptiveTargetSanitizerGoal extends Goal {
|
||||
|
||||
private final Mob mob;
|
||||
|
||||
public AdaptiveTargetSanitizerGoal(Mob mob) {
|
||||
this.mob = mob;
|
||||
setFlags(EnumSet.noneOf(Flag.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
LivingEntity target = mob.getTarget();
|
||||
return (target != null && !AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) || shouldReleaseZombieStack(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
LivingEntity target = mob.getTarget();
|
||||
if (target != null && !AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
mob.setTarget(null);
|
||||
}
|
||||
if (shouldReleaseZombieStack(target)) {
|
||||
mob.stopRiding();
|
||||
AdaptiveAIGoalUtils.clearAdaptiveStackedZombie(mob);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldReleaseZombieStack(LivingEntity target) {
|
||||
if (!(mob instanceof Zombie) || !(mob.getVehicle() instanceof Zombie)) {
|
||||
return false;
|
||||
}
|
||||
if (!AdaptiveAIGoalUtils.isAdaptiveStackedZombie(mob)) {
|
||||
return true;
|
||||
}
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return true;
|
||||
}
|
||||
if (mob.distanceToSqr(target) > 8.0D * 8.0D) {
|
||||
return true;
|
||||
}
|
||||
Entity base = stackBase(mob);
|
||||
double verticalFromBase = target.getY() - base.getY();
|
||||
double horizontalFromBase = Math.sqrt((target.getX() - base.getX()) * (target.getX() - base.getX())
|
||||
+ (target.getZ() - base.getZ()) * (target.getZ() - base.getZ()));
|
||||
return verticalFromBase <= 2.5D || horizontalFromBase >= 5.5D;
|
||||
}
|
||||
|
||||
private Entity stackBase(Entity entity) {
|
||||
Entity cursor = entity;
|
||||
while (cursor.getVehicle() instanceof Mob) {
|
||||
cursor = cursor.getVehicle();
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,9 @@ public class AdaptiveThreatMemoryGoal extends Goal {
|
||||
return;
|
||||
}
|
||||
Player player = remembered.get();
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(player) || !mob.canAttack(player)) {
|
||||
return;
|
||||
}
|
||||
if (mob.getTarget() == null || !mob.getTarget().isAlive()) {
|
||||
mob.setTarget(player);
|
||||
}
|
||||
|
||||
@@ -37,18 +37,21 @@ public class AdaptiveWardenTacticsGoal extends Goal {
|
||||
public void start() {
|
||||
cooldown = 80 + warden.getRandom().nextInt(80);
|
||||
LivingEntity target = warden.getTarget();
|
||||
if (target != null && target.isAlive()) {
|
||||
if (AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
rememberedTarget = target;
|
||||
warden.getLookControl().setLookAt(target, 25.0F, 25.0F);
|
||||
return;
|
||||
}
|
||||
if (rememberedTarget != null && rememberedTarget.isAlive() && warden.distanceToSqr(rememberedTarget) < 24.0D * 24.0D) {
|
||||
if (AdaptiveAIGoalUtils.isValidAdaptiveTarget(rememberedTarget)
|
||||
&& warden.distanceToSqr(rememberedTarget) < 24.0D * 24.0D
|
||||
&& warden.canAttack(rememberedTarget)) {
|
||||
warden.setAttackTarget(rememberedTarget);
|
||||
AdaptiveAIGoalUtils.debug(warden, tierSupplier, "warden conservative target persistence");
|
||||
}
|
||||
|
||||
LivingEntity hurtBy = warden.getLastHurtByMob();
|
||||
if (tierSupplier.getAsInt() >= 5 && hurtBy != null && hurtBy.isAlive() && AdaptiveAIGoalUtils.isUsingRangedWeapon(hurtBy)) {
|
||||
if (tierSupplier.getAsInt() >= 5 && AdaptiveAIGoalUtils.isValidAdaptiveTarget(hurtBy)
|
||||
&& warden.canAttack(hurtBy) && AdaptiveAIGoalUtils.isUsingRangedWeapon(hurtBy)) {
|
||||
repeatedRangedTicks = hurtBy == lastHurtBy ? repeatedRangedTicks + 1 : 1;
|
||||
lastHurtBy = hurtBy;
|
||||
if (repeatedRangedTicks >= 2) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.fretux.adaptivemobs.ai.goals;
|
||||
|
||||
import net.fretux.adaptivemobs.ai.AdaptiveAIGoalUtils;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.ai.goal.Goal;
|
||||
@@ -28,19 +29,20 @@ public class SharedTargetGoal extends Goal {
|
||||
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
return tierSupplier.getAsInt() >= minTier && mob.getTarget() != null && --cooldown <= 0;
|
||||
return tierSupplier.getAsInt() >= minTier && AdaptiveAIGoalUtils.isValidAdaptiveTarget(mob.getTarget())
|
||||
&& --cooldown <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
cooldown = 40 + mob.getRandom().nextInt(40);
|
||||
LivingEntity target = mob.getTarget();
|
||||
if (target == null || !target.isAlive()) {
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return;
|
||||
}
|
||||
AABB box = mob.getBoundingBox().inflate(radius);
|
||||
List<Mob> allies = mob.level().getEntitiesOfClass(Mob.class, box,
|
||||
other -> other != mob && other instanceof Enemy && other.getTarget() == null);
|
||||
other -> other != mob && other instanceof Enemy && other.getTarget() == null && other.canAttack(target));
|
||||
int shared = 0;
|
||||
for (Mob ally : allies) {
|
||||
if (shared++ >= 4) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.fretux.adaptivemobs.event;
|
||||
|
||||
import net.fretux.adaptivemobs.ai.AdaptiveGoalInjector;
|
||||
import net.fretux.adaptivemobs.ai.AdaptiveAIGoalUtils;
|
||||
import net.fretux.adaptivemobs.ai.AdaptiveMemory;
|
||||
import net.fretux.adaptivemobs.ai.AdaptiveMobHooks;
|
||||
import net.fretux.adaptivemobs.ai.AdaptiveSpecialAbilities;
|
||||
@@ -61,6 +62,10 @@ public class AdaptiveMobsEvents {
|
||||
public void onBlockBreak(BlockEvent.BreakEvent event) {
|
||||
if (AMConfig.enabled && AMConfig.ENABLE_SOUND_INVESTIGATION_AI.get()
|
||||
&& event.getLevel() instanceof ServerLevel level) {
|
||||
if (event.getPlayer() instanceof ServerPlayer player
|
||||
&& !AdaptiveAIGoalUtils.isValidAdaptiveTarget(player)) {
|
||||
return;
|
||||
}
|
||||
AdaptiveMemory.rememberSound(level, event.getPos(), AdaptiveMemory.SoundKind.BLOCK_BREAK, 20 * 8);
|
||||
}
|
||||
}
|
||||
@@ -68,7 +73,8 @@ public class AdaptiveMobsEvents {
|
||||
@SubscribeEvent
|
||||
public void onItemUseFinish(LivingEntityUseItemEvent.Finish event) {
|
||||
if (AMConfig.enabled && AMConfig.ENABLE_SOUND_INVESTIGATION_AI.get()
|
||||
&& event.getEntity() instanceof ServerPlayer player && event.getItem().isEdible()) {
|
||||
&& event.getEntity() instanceof ServerPlayer player && event.getItem().isEdible()
|
||||
&& AdaptiveAIGoalUtils.isValidAdaptiveTarget(player)) {
|
||||
AdaptiveMemory.rememberSound(player.serverLevel(), player.blockPosition(), AdaptiveMemory.SoundKind.EATING, 20 * 6);
|
||||
}
|
||||
}
|
||||
@@ -102,7 +108,8 @@ public class AdaptiveMobsEvents {
|
||||
AdaptiveGoalInjector.inject(mob, level, AdaptiveDifficultyManager.getMobTier(level, mob.getType()));
|
||||
}
|
||||
if (AMConfig.enabled && AMConfig.ENABLE_SOUND_INVESTIGATION_AI.get()
|
||||
&& entity instanceof Projectile projectile && projectile.getOwner() instanceof ServerPlayer player) {
|
||||
&& entity instanceof Projectile projectile && projectile.getOwner() instanceof ServerPlayer player
|
||||
&& AdaptiveAIGoalUtils.isValidAdaptiveTarget(player)) {
|
||||
AdaptiveMemory.rememberSound(level, player.blockPosition(), AdaptiveMemory.SoundKind.BOW_SHOT, 20 * 6);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,3 +3,6 @@
|
||||
# without resorting to mixins.
|
||||
public net.minecraft.world.entity.Mob f_21345_ # goalSelector
|
||||
public net.minecraft.world.entity.Mob f_21346_ # targetSelector
|
||||
|
||||
# Allow adaptive tiers to shorten the Creeper's vanilla 30-tick fuse.
|
||||
public net.minecraft.world.entity.monster.Creeper f_32271_ # maxSwell
|
||||
|
||||
Reference in New Issue
Block a user