update mob behaviour for zombies and spooders

This commit is contained in:
2026-06-29 18:59:23 +02:00
parent f8e4710c59
commit 9d8cea70dc
3 changed files with 80 additions and 16 deletions
@@ -30,6 +30,7 @@ import net.fretux.adaptivemobs.ai.goals.AdaptiveTargetPriorityGoal;
import net.fretux.adaptivemobs.ai.goals.AdaptiveThreatMemoryGoal; import net.fretux.adaptivemobs.ai.goals.AdaptiveThreatMemoryGoal;
import net.fretux.adaptivemobs.ai.goals.AdaptiveWardenTacticsGoal; import net.fretux.adaptivemobs.ai.goals.AdaptiveWardenTacticsGoal;
import net.fretux.adaptivemobs.ai.goals.AdaptiveWitchTacticsGoal; import net.fretux.adaptivemobs.ai.goals.AdaptiveWitchTacticsGoal;
import net.fretux.adaptivemobs.ai.goals.AdaptiveZombiePackTacticsGoal;
import net.fretux.adaptivemobs.config.AMConfig; import net.fretux.adaptivemobs.config.AMConfig;
import net.fretux.adaptivemobs.difficulty.AdaptiveDifficultyManager; import net.fretux.adaptivemobs.difficulty.AdaptiveDifficultyManager;
import net.minecraft.server.level.ServerLevel; 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)); 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()) { } 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(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()) { } else if (pathfinder != null && mob instanceof Creeper creeper && AMConfig.AI_CREEPER.get()) {
mob.goalSelector.addGoal(2, new AdaptiveCreeperPressureGoal(creeper, tier)); mob.goalSelector.addGoal(2, new AdaptiveCreeperPressureGoal(creeper, tier));
mob.goalSelector.addGoal(4, new AdaptiveFlankGoal(pathfinder, tier, 2, 3.0D, 1.0D, false)); 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)); mob.goalSelector.addGoal(8, new AdaptiveBiomeTacticsGoal(pathfinder, tier));
} }
if (pathfinder != null && AMConfig.ENABLE_ANTI_CHEESE_AI.get()) { 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)) { if (pathfinder != null && isRangedMob(mob) && rangedEnabled(mob)) {
mob.goalSelector.addGoal(3, new AdaptiveRangedAttackGoal(pathfinder, tier, rangedOptimalRange(mob), 1.0D)); 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()) { 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(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(2, new AdaptiveSpiderDisengageGoal(pathfinder, tier, mob instanceof CaveSpider ? 3 : 4, 0.14D, 1.3D));
mob.goalSelector.addGoal(7, new AdaptiveLowHealthRetreatGoal(pathfinder, tier, 4, 0.28D, 1.15D)); 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()) { if (mob instanceof Witch witch && AMConfig.ENABLE_WITCH_AI.get() && AMConfig.AI_WITCH.get()) {
mob.goalSelector.addGoal(3, new AdaptiveWitchTacticsGoal(witch, tier)); mob.goalSelector.addGoal(3, new AdaptiveWitchTacticsGoal(witch, tier));
@@ -38,16 +38,24 @@ public class AdaptiveAntiCheeseGoal extends Goal {
@Override @Override
public boolean canUse() { public boolean canUse() {
return AMConfig.ENABLE_ANTI_CHEESE_AI.get() if (!AMConfig.ENABLE_ANTI_CHEESE_AI.get() || tierSupplier.getAsInt() < 3) {
&& tierSupplier.getAsInt() >= 3 return false;
&& mob.getTarget() != null }
&& mob.getTarget().isAlive() LivingEntity target = mob.getTarget();
&& --cooldown <= 0; 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 @Override
public void start() { public void start() {
cooldown = 55 + mob.getRandom().nextInt(80);
LivingEntity target = mob.getTarget(); LivingEntity target = mob.getTarget();
if (target == null) { if (target == null) {
return; return;
@@ -55,8 +63,10 @@ public class AdaptiveAntiCheeseGoal extends Goal {
mob.getLookControl().setLookAt(target, 30.0F, 30.0F); mob.getLookControl().setLookAt(target, 30.0F, 30.0F);
if (avoidTrap()) { if (avoidTrap()) {
cooldown = 8 + mob.getRandom().nextInt(16);
return; return;
} }
cooldown = 55 + mob.getRandom().nextInt(80);
if (mob instanceof Zombie && isVerticalObstacleCheese(target)) { if (mob instanceof Zombie && isVerticalObstacleCheese(target)) {
if (tryCorpseLadderSurge(target)) { if (tryCorpseLadderSurge(target)) {
return; return;
@@ -108,6 +118,16 @@ public class AdaptiveAntiCheeseGoal extends Goal {
return false; 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() { 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(2.5D),
entity -> entity instanceof Boat || entity instanceof AbstractMinecart); entity -> entity instanceof Boat || entity instanceof AbstractMinecart);
@@ -170,6 +190,7 @@ public class AdaptiveAntiCheeseGoal extends Goal {
zombie -> zombie != mob zombie -> zombie != mob
&& zombie.isAlive() && zombie.isAlive()
&& !zombie.isVehicle() && !zombie.isVehicle()
&& !zombie.isPassenger()
&& zombie.getTarget() == target && zombie.getTarget() == target
&& Math.abs(zombie.getY() - mob.getY()) < 1.25D); && Math.abs(zombie.getY() - mob.getY()) < 1.25D);
Zombie bestCarrier = null; Zombie bestCarrier = null;
@@ -16,6 +16,7 @@ import java.util.function.IntSupplier;
public class AdaptiveSpiderDisengageGoal extends Goal { public class AdaptiveSpiderDisengageGoal extends Goal {
private static final int DISENGAGE_TICKS = 100; private static final int DISENGAGE_TICKS = 100;
private static final double LOW_HEALTH_TRIGGER = 0.62D;
private final PathfinderMob mob; private final PathfinderMob mob;
private final IntSupplier tierSupplier; private final IntSupplier tierSupplier;
@@ -23,6 +24,8 @@ public class AdaptiveSpiderDisengageGoal extends Goal {
private final double triggerHealthLoss; private final double triggerHealthLoss;
private final double speed; private final double speed;
private int cooldown; private int cooldown;
private int retreatTicks;
private int pathRefreshTicks;
private float lastHealth; private float lastHealth;
public AdaptiveSpiderDisengageGoal(PathfinderMob mob, IntSupplier tierSupplier, public AdaptiveSpiderDisengageGoal(PathfinderMob mob, IntSupplier tierSupplier,
@@ -41,17 +44,31 @@ public class AdaptiveSpiderDisengageGoal extends Goal {
float current = mob.getHealth(); float current = mob.getHealth();
float lost = lastHealth - current; float lost = lastHealth - current;
lastHealth = current; lastHealth = current;
LivingEntity target = mob.getTarget();
return AMConfig.AI_RETREAT_ENABLED.get() return AMConfig.AI_RETREAT_ENABLED.get()
&& tierSupplier.getAsInt() >= minTier && tierSupplier.getAsInt() >= minTier
&& lost >= mob.getMaxHealth() * triggerHealthLoss && target != null
&& mob.getTarget() != null && target.isAlive()
&& mob.getTarget().isAlive() && !mob.hasEffect(MobEffects.REGENERATION)
&& cooldown-- <= 0; && 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 @Override
public void start() { public void start() {
cooldown = 110 + mob.getRandom().nextInt(90); cooldown = 55 + mob.getRandom().nextInt(45);
retreatTicks = DISENGAGE_TICKS;
pathRefreshTicks = 0;
LivingEntity target = mob.getTarget(); LivingEntity target = mob.getTarget();
if (target == null) { if (target == null) {
return; return;
@@ -59,8 +76,32 @@ public class AdaptiveSpiderDisengageGoal extends Goal {
mob.addEffect(new MobEffectInstance(MobEffects.INVISIBILITY, DISENGAGE_TICKS, 0)); mob.addEffect(new MobEffectInstance(MobEffects.INVISIBILITY, DISENGAGE_TICKS, 0));
mob.addEffect(new MobEffectInstance(MobEffects.REGENERATION, DISENGAGE_TICKS, 4)); mob.addEffect(new MobEffectInstance(MobEffects.REGENERATION, DISENGAGE_TICKS, 4));
Vec3 dest = AdaptivePositioningUtils.retreatPosition(mob, target, 5.0D); retreatFrom(target);
mob.getNavigation().moveTo(dest.x, dest.y, dest.z, speed);
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "spider invisible regeneration disengage"); 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);
}
} }