From 9d8cea70dcc2dd1a63ecb9e8570fb33ed1adcdf5 Mon Sep 17 00:00:00 2001 From: fretuks Date: Mon, 29 Jun 2026 18:59:23 +0200 Subject: [PATCH] update mob behaviour for zombies and spooders --- .../adaptivemobs/ai/AdaptiveGoalInjector.java | 8 ++- .../ai/goals/AdaptiveAntiCheeseGoal.java | 33 +++++++++-- .../ai/goals/AdaptiveSpiderDisengageGoal.java | 55 ++++++++++++++++--- 3 files changed, 80 insertions(+), 16 deletions(-) diff --git a/src/main/java/net/fretux/adaptivemobs/ai/AdaptiveGoalInjector.java b/src/main/java/net/fretux/adaptivemobs/ai/AdaptiveGoalInjector.java index fb26500..f971f5c 100644 --- a/src/main/java/net/fretux/adaptivemobs/ai/AdaptiveGoalInjector.java +++ b/src/main/java/net/fretux/adaptivemobs/ai/AdaptiveGoalInjector.java @@ -30,6 +30,7 @@ import net.fretux.adaptivemobs.ai.goals.AdaptiveTargetPriorityGoal; import net.fretux.adaptivemobs.ai.goals.AdaptiveThreatMemoryGoal; import net.fretux.adaptivemobs.ai.goals.AdaptiveWardenTacticsGoal; import net.fretux.adaptivemobs.ai.goals.AdaptiveWitchTacticsGoal; +import net.fretux.adaptivemobs.ai.goals.AdaptiveZombiePackTacticsGoal; import net.fretux.adaptivemobs.config.AMConfig; import net.fretux.adaptivemobs.difficulty.AdaptiveDifficultyManager; import net.minecraft.server.level.ServerLevel; @@ -101,6 +102,7 @@ public final class AdaptiveGoalInjector { mob.goalSelector.addGoal(4, new AdaptiveFlankGoal(pathfinder, tier, 2, 8.0D, 1.0D, true)); } else if (pathfinder != null && mob instanceof Zombie && AMConfig.AI_ZOMBIE.get()) { mob.goalSelector.addGoal(3, new AdaptiveFlankGoal(pathfinder, tier, 2, 2.5D, 1.05D, true)); + mob.goalSelector.addGoal(2, new AdaptiveZombiePackTacticsGoal((Zombie) mob, tier)); } else if (pathfinder != null && mob instanceof Creeper creeper && AMConfig.AI_CREEPER.get()) { mob.goalSelector.addGoal(2, new AdaptiveCreeperPressureGoal(creeper, tier)); mob.goalSelector.addGoal(4, new AdaptiveFlankGoal(pathfinder, tier, 2, 3.0D, 1.0D, false)); @@ -141,7 +143,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(7, new AdaptiveAntiCheeseGoal(pathfinder, tier)); + mob.goalSelector.addGoal(mob instanceof Zombie ? 1 : 7, new AdaptiveAntiCheeseGoal(pathfinder, tier)); } if (pathfinder != null && isRangedMob(mob) && rangedEnabled(mob)) { mob.goalSelector.addGoal(3, new AdaptiveRangedAttackGoal(pathfinder, tier, rangedOptimalRange(mob), 1.0D)); @@ -158,8 +160,8 @@ public final class AdaptiveGoalInjector { } if (pathfinder != null && mob instanceof Spider && AMConfig.ENABLE_SPIDER_AI.get() && AMConfig.AI_SPIDER.get()) { mob.goalSelector.addGoal(3, new AdaptiveSpiderTacticsGoal(pathfinder, tier, mob instanceof CaveSpider)); - mob.goalSelector.addGoal(6, new AdaptiveSpiderDisengageGoal(pathfinder, tier, mob instanceof CaveSpider ? 4 : 5, 0.25D, 1.2D)); - mob.goalSelector.addGoal(7, new AdaptiveLowHealthRetreatGoal(pathfinder, tier, 4, 0.28D, 1.15D)); + mob.goalSelector.addGoal(2, new AdaptiveSpiderDisengageGoal(pathfinder, tier, mob instanceof CaveSpider ? 3 : 4, 0.14D, 1.3D)); + mob.goalSelector.addGoal(5, new AdaptiveLowHealthRetreatGoal(pathfinder, tier, 4, 0.38D, 1.15D)); } if (mob instanceof Witch witch && AMConfig.ENABLE_WITCH_AI.get() && AMConfig.AI_WITCH.get()) { mob.goalSelector.addGoal(3, new AdaptiveWitchTacticsGoal(witch, tier)); diff --git a/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveAntiCheeseGoal.java b/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveAntiCheeseGoal.java index f8a8790..4d09d9b 100644 --- a/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveAntiCheeseGoal.java +++ b/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveAntiCheeseGoal.java @@ -38,16 +38,24 @@ public class AdaptiveAntiCheeseGoal extends Goal { @Override public boolean canUse() { - return AMConfig.ENABLE_ANTI_CHEESE_AI.get() - && tierSupplier.getAsInt() >= 3 - && mob.getTarget() != null - && mob.getTarget().isAlive() - && --cooldown <= 0; + if (!AMConfig.ENABLE_ANTI_CHEESE_AI.get() || tierSupplier.getAsInt() < 3) { + return false; + } + LivingEntity target = mob.getTarget(); + if (target == null || !target.isAlive()) { + return false; + } + if (isInTrapVehicle() || isNearTrapVehicle()) { + return true; + } + if (tierSupplier.getAsInt() >= 5 && mob instanceof Zombie && isVerticalObstacleCheese(target)) { + return true; + } + return --cooldown <= 0; } @Override public void start() { - cooldown = 55 + mob.getRandom().nextInt(80); LivingEntity target = mob.getTarget(); if (target == null) { return; @@ -55,8 +63,10 @@ public class AdaptiveAntiCheeseGoal extends Goal { 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; @@ -108,6 +118,16 @@ public class AdaptiveAntiCheeseGoal extends Goal { return false; } + private boolean isInTrapVehicle() { + Entity vehicle = mob.getVehicle(); + return vehicle instanceof Boat || vehicle instanceof AbstractMinecart; + } + + private boolean isNearTrapVehicle() { + Entity trapVehicle = nearestTrapVehicle(); + return trapVehicle != null && mob.distanceToSqr(trapVehicle) < 2.9D; + } + private Entity nearestTrapVehicle() { List vehicles = mob.level().getEntitiesOfClass(Entity.class, mob.getBoundingBox().inflate(2.5D), entity -> entity instanceof Boat || entity instanceof AbstractMinecart); @@ -170,6 +190,7 @@ 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); Zombie bestCarrier = null; diff --git a/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveSpiderDisengageGoal.java b/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveSpiderDisengageGoal.java index 46ccc00..f684c9a 100644 --- a/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveSpiderDisengageGoal.java +++ b/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveSpiderDisengageGoal.java @@ -16,6 +16,7 @@ import java.util.function.IntSupplier; public class AdaptiveSpiderDisengageGoal extends Goal { private static final int DISENGAGE_TICKS = 100; + private static final double LOW_HEALTH_TRIGGER = 0.62D; private final PathfinderMob mob; private final IntSupplier tierSupplier; @@ -23,6 +24,8 @@ public class AdaptiveSpiderDisengageGoal extends Goal { private final double triggerHealthLoss; private final double speed; private int cooldown; + private int retreatTicks; + private int pathRefreshTicks; private float lastHealth; public AdaptiveSpiderDisengageGoal(PathfinderMob mob, IntSupplier tierSupplier, @@ -41,17 +44,31 @@ public class AdaptiveSpiderDisengageGoal extends Goal { float current = mob.getHealth(); float lost = lastHealth - current; lastHealth = current; + LivingEntity target = mob.getTarget(); return AMConfig.AI_RETREAT_ENABLED.get() && tierSupplier.getAsInt() >= minTier - && lost >= mob.getMaxHealth() * triggerHealthLoss - && mob.getTarget() != null - && mob.getTarget().isAlive() - && cooldown-- <= 0; + && target != null + && target.isAlive() + && !mob.hasEffect(MobEffects.REGENERATION) + && cooldown-- <= 0 + && (lost >= mob.getMaxHealth() * triggerHealthLoss + || AdaptiveAIGoalUtils.healthFraction(mob) <= LOW_HEALTH_TRIGGER); + } + + @Override + public boolean canContinueToUse() { + LivingEntity target = mob.getTarget(); + return retreatTicks > 0 + && target != null + && target.isAlive() + && AdaptiveAIGoalUtils.healthFraction(mob) < 0.95D; } @Override public void start() { - cooldown = 110 + mob.getRandom().nextInt(90); + cooldown = 55 + mob.getRandom().nextInt(45); + retreatTicks = DISENGAGE_TICKS; + pathRefreshTicks = 0; LivingEntity target = mob.getTarget(); if (target == null) { return; @@ -59,8 +76,32 @@ public class AdaptiveSpiderDisengageGoal extends Goal { mob.addEffect(new MobEffectInstance(MobEffects.INVISIBILITY, DISENGAGE_TICKS, 0)); mob.addEffect(new MobEffectInstance(MobEffects.REGENERATION, DISENGAGE_TICKS, 4)); - Vec3 dest = AdaptivePositioningUtils.retreatPosition(mob, target, 5.0D); - mob.getNavigation().moveTo(dest.x, dest.y, dest.z, speed); + retreatFrom(target); AdaptiveAIGoalUtils.debug(mob, tierSupplier, "spider invisible regeneration disengage"); } + + @Override + public void tick() { + LivingEntity target = mob.getTarget(); + if (target == null) { + return; + } + retreatTicks--; + mob.getLookControl().setLookAt(target, 30.0F, 30.0F); + if (pathRefreshTicks-- <= 0) { + pathRefreshTicks = 12; + retreatFrom(target); + } + } + + @Override + public void stop() { + retreatTicks = 0; + pathRefreshTicks = 0; + } + + private void retreatFrom(LivingEntity target) { + Vec3 dest = AdaptivePositioningUtils.retreatPosition(mob, target, 5.0D); + mob.getNavigation().moveTo(dest.x, dest.y, dest.z, speed); + } }