fix various issues

This commit is contained in:
2026-07-20 01:12:26 +02:00
parent 62942bf017
commit 85a96a6678
8 changed files with 139 additions and 171 deletions
@@ -85,19 +85,19 @@ public final class AdaptiveGoalInjector {
} }
public static void inject(Mob mob, ServerLevel level, int initialTier) { public static void inject(Mob mob, ServerLevel level, int initialTier) {
if (!AMConfig.AI_ENABLED.get() || initialTier <= 0 || isBoss(mob)) { if (isBoss(mob)) {
return; return;
} }
if (!INJECTED.add(mob)) { if (!INJECTED.add(mob)) {
return; return;
} }
PathfinderMob pathfinder = mob instanceof PathfinderMob pf ? pf : null; PathfinderMob pathfinder = mob instanceof PathfinderMob pf ? pf : null;
IntSupplier tier = () -> AdaptiveDifficultyManager.getMobTier(level, mob.getType()); IntSupplier tier = () -> isAIEnabledFor(mob)
? AdaptiveDifficultyManager.getMobTier(level, mob.getType()) : 0;
IntSupplier advancedTier = () -> AMConfig.ENABLE_ADVANCED_AI.get() ? tier.getAsInt() : 0;
mob.targetSelector.addGoal(0, new AdaptiveTargetSanitizerGoal(mob)); mob.targetSelector.addGoal(0, new AdaptiveTargetSanitizerGoal(mob));
if (tier.getAsInt() >= 4) { mob.targetSelector.addGoal(2, new AdaptiveSharedTargetGoal(mob, tier, 4, 12.0D));
mob.targetSelector.addGoal(2, new AdaptiveSharedTargetGoal(mob, tier, 4, 12.0D));
}
if (pathfinder != null && mob instanceof AbstractSkeleton && AMConfig.AI_SKELETON.get()) { if (pathfinder != null && mob instanceof AbstractSkeleton && AMConfig.AI_SKELETON.get()) {
mob.goalSelector.addGoal(3, new AdaptiveSkeletonShieldGoal(pathfinder, tier)); mob.goalSelector.addGoal(3, new AdaptiveSkeletonShieldGoal(pathfinder, tier));
@@ -118,10 +118,10 @@ public final class AdaptiveGoalInjector {
mob.goalSelector.addGoal(4, new AdaptiveFlankGoal(pathfinder, tier, 2, 7.0D, 1.0D, false)); mob.goalSelector.addGoal(4, new AdaptiveFlankGoal(pathfinder, tier, 2, 7.0D, 1.0D, false));
} }
if (AMConfig.ENABLE_ADVANCED_AI.get()) { // Install advanced goals even while disabled. Their tier supplier returns zero until the
injectAdvanced(mob, pathfinder, tier); // switches are enabled, allowing config reloads to affect already-loaded mobs both ways.
AdaptiveAIGoalUtils.debug(mob, tier, "injected adaptive goals"); injectAdvanced(mob, pathfinder, advancedTier);
} AdaptiveAIGoalUtils.debug(mob, tier, "injected adaptive goals");
} }
private static void injectAdvanced(Mob mob, PathfinderMob pathfinder, IntSupplier tier) { private static void injectAdvanced(Mob mob, PathfinderMob pathfinder, IntSupplier tier) {
@@ -310,6 +310,32 @@ public final class AdaptiveGoalInjector {
return 1.05D; return 1.05D;
} }
public static boolean isAIEnabledFor(Mob mob) {
if (!AMConfig.enabled || !AMConfig.AI_ENABLED.get() || !AMConfig.isMobEnabled(mob.getType())) {
return false;
}
if (mob instanceof AbstractSkeleton) return AMConfig.AI_SKELETON.get();
if (mob instanceof Zombie) return AMConfig.AI_ZOMBIE.get();
if (mob instanceof Creeper) return AMConfig.AI_CREEPER.get();
if (mob instanceof Spider) return AMConfig.AI_SPIDER.get();
if (mob instanceof Witch) return AMConfig.AI_WITCH.get();
if (mob instanceof Pillager) return AMConfig.AI_PILLAGER.get();
if (mob instanceof EnderMan) return AMConfig.AI_ENDERMAN.get();
if (mob instanceof Ghast) return AMConfig.AI_GHAST.get();
if (mob instanceof Blaze) return AMConfig.AI_BLAZE.get();
if (mob instanceof Guardian) return AMConfig.AI_GUARDIAN.get();
if (mob instanceof Phantom) return AMConfig.AI_PHANTOM.get();
if (mob instanceof Ravager) return AMConfig.AI_RAVAGER.get();
if (mob instanceof Evoker) return AMConfig.AI_EVOKER.get();
if (mob instanceof Vex) return AMConfig.AI_VEX.get();
if (mob instanceof Piglin || mob instanceof Hoglin || mob instanceof Zoglin) return AMConfig.AI_PIGLIN_FAMILY.get();
if (mob instanceof Silverfish || mob instanceof Endermite) return AMConfig.AI_SMALL_SWARM.get();
if (mob instanceof Shulker) return AMConfig.AI_SHULKER.get();
if (mob instanceof Warden) return AMConfig.AI_WARDEN.get();
if (mob instanceof Slime) return AMConfig.AI_SLIME.get();
return true;
}
private static int creeperFuseTicks(int tier) { private static int creeperFuseTicks(int tier) {
return Math.max(18, 30 - Math.max(0, tier - 1) * 3); return Math.max(18, 30 - Math.max(0, tier - 1) * 3);
} }
@@ -56,6 +56,7 @@ import net.minecraftforge.event.entity.living.LivingHurtEvent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.UUID;
public final class AdaptiveSpecialAbilities { public final class AdaptiveSpecialAbilities {
@@ -68,7 +69,7 @@ public final class AdaptiveSpecialAbilities {
} }
public static void onProjectileJoin(EntityJoinLevelEvent event) { public static void onProjectileJoin(EntityJoinLevelEvent event) {
if (!AMConfig.enabled || !(event.getLevel() instanceof ServerLevel level)) { if (!adaptiveAbilitiesEnabled() || !(event.getLevel() instanceof ServerLevel level)) {
return; return;
} }
Entity entity = event.getEntity(); Entity entity = event.getEntity();
@@ -112,7 +113,7 @@ public final class AdaptiveSpecialAbilities {
} }
public static void onLivingHurt(LivingHurtEvent event) { public static void onLivingHurt(LivingHurtEvent event) {
if (!AMConfig.enabled || !(event.getEntity().level() instanceof ServerLevel level)) { if (!adaptiveAbilitiesEnabled() || !(event.getEntity().level() instanceof ServerLevel level)) {
return; return;
} }
LivingEntity victim = event.getEntity(); LivingEntity victim = event.getEntity();
@@ -138,7 +139,7 @@ public final class AdaptiveSpecialAbilities {
if (now < block.expireTick) { if (now < block.expireTick) {
continue; continue;
} }
ServerLevel level = event.getServer().getLevel(block.dimension); ServerLevel level = block.server == event.getServer() ? event.getServer().getLevel(block.dimension) : null;
if (level != null && level.getBlockState(block.pos).is(Blocks.COBWEB)) { if (level != null && level.getBlockState(block.pos).is(Blocks.COBWEB)) {
level.removeBlock(block.pos, false); level.removeBlock(block.pos, false);
} }
@@ -151,15 +152,22 @@ public final class AdaptiveSpecialAbilities {
if (now < pending.tick) { if (now < pending.tick) {
continue; continue;
} }
ServerLevel level = event.getServer().getLevel(pending.dimension); ServerLevel level = pending.server == event.getServer() ? event.getServer().getLevel(pending.dimension) : null;
if (level != null && pending.mob.isAlive() && AdaptiveAIGoalUtils.isValidAdaptiveTarget(pending.target) Entity mobEntity = level == null ? null : level.getEntity(pending.mobId);
&& pending.mob.canAttack(pending.target)) { Entity targetEntity = level == null ? null : level.getEntity(pending.targetId);
Vec3 side = AdaptivePositioningUtils.sidePosition(pending.mob, pending.target, 1.6D, if (mobEntity instanceof Silverfish mob && targetEntity instanceof LivingEntity target
pending.mob.getRandom().nextBoolean() ? 1.0D : -1.0D, 0.0D); && mob.isAlive() && AdaptiveAIGoalUtils.isValidAdaptiveTarget(target) && mob.canAttack(target)) {
pending.mob.moveTo(side.x, side.y, side.z, pending.mob.getYRot(), pending.mob.getXRot()); Vec3 side = AdaptivePositioningUtils.sidePosition(mob, target, 1.6D,
pending.mob.removeEffect(MobEffects.INVISIBILITY); mob.getRandom().nextBoolean() ? 1.0D : -1.0D, 0.0D);
pending.mob.setTarget(pending.target); BlockPos destination = BlockPos.containing(side);
AdaptiveAIGoalUtils.debug(pending.mob, () -> pending.tier, "silverfish burrow burst"); Vec3 delta = side.subtract(mob.position());
if (level.hasChunkAt(destination) && level.getWorldBorder().isWithinBounds(destination)
&& level.noCollision(mob, mob.getBoundingBox().move(delta))) {
mob.moveTo(side.x, side.y, side.z, mob.getYRot(), mob.getXRot());
mob.setTarget(target);
AdaptiveAIGoalUtils.debug(mob, () -> pending.tier, "silverfish burrow burst");
}
mob.removeEffect(MobEffects.INVISIBILITY);
} }
reappearIterator.remove(); reappearIterator.remove();
} }
@@ -193,7 +201,8 @@ public final class AdaptiveSpecialAbilities {
&& ready(mob, "burrow_burst", 220)) { && ready(mob, "burrow_burst", 220)) {
silverfish.addEffect(new MobEffectInstance(MobEffects.INVISIBILITY, 35, 0)); silverfish.addEffect(new MobEffectInstance(MobEffects.INVISIBILITY, 35, 0));
silverfish.getNavigation().stop(); silverfish.getNavigation().stop();
DELAYED_REAPPEARS.add(new DelayedReappear(level.dimension(), level.getGameTime() + 24, silverfish, attacker, tier)); DELAYED_REAPPEARS.add(new DelayedReappear(level.getServer(), level.dimension(), level.getGameTime() + 24,
silverfish.getUUID(), attacker.getUUID(), tier));
AdaptiveAIGoalUtils.debug(mob, () -> tier, "silverfish burrow vanish"); AdaptiveAIGoalUtils.debug(mob, () -> tier, "silverfish burrow vanish");
} }
if (tier >= 4 && mob instanceof Evoker evoker && ready(mob, "vex_relay", 120)) { if (tier >= 4 && mob instanceof Evoker evoker && ready(mob, "vex_relay", 120)) {
@@ -293,7 +302,7 @@ public final class AdaptiveSpecialAbilities {
} }
private static int tier(ServerLevel level, Mob mob) { private static int tier(ServerLevel level, Mob mob) {
if (!AMConfig.isMobEnabled(mob.getType())) { if (!adaptiveAbilitiesEnabled() || !AdaptiveGoalInjector.isAIEnabledFor(mob)) {
return 0; return 0;
} }
return AdaptiveDifficultyManager.getMobTier(level, mob.getType()); return AdaptiveDifficultyManager.getMobTier(level, mob.getType());
@@ -328,7 +337,7 @@ public final class AdaptiveSpecialAbilities {
return; return;
} }
level.setBlock(pos, Blocks.COBWEB.defaultBlockState(), 3); level.setBlock(pos, Blocks.COBWEB.defaultBlockState(), 3);
TEMPORARY_BLOCKS.add(new TemporaryBlock(level.dimension(), pos.immutable(), expireTick)); TEMPORARY_BLOCKS.add(new TemporaryBlock(level.getServer(), level.dimension(), pos.immutable(), expireTick));
level.playSound(null, pos, SoundEvents.SPIDER_AMBIENT, SoundSource.HOSTILE, 0.6F, 1.25F); level.playSound(null, pos, SoundEvents.SPIDER_AMBIENT, SoundSource.HOSTILE, 0.6F, 1.25F);
} }
@@ -367,11 +376,32 @@ public final class AdaptiveSpecialAbilities {
level.addFreshEntity(split); level.addFreshEntity(split);
} }
private record TemporaryBlock(net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> dimension, public static void invalidateTemporaryBlock(ServerLevel level, BlockPos pos) {
TEMPORARY_BLOCKS.removeIf(block -> block.server == level.getServer()
&& block.dimension == level.dimension() && block.pos.equals(pos));
}
public static void clear(ServerLevel level) {
TEMPORARY_BLOCKS.removeIf(block -> block.server == level.getServer() && block.dimension == level.dimension());
DELAYED_REAPPEARS.removeIf(pending -> pending.server == level.getServer() && pending.dimension == level.dimension());
}
public static void clear(net.minecraft.server.MinecraftServer server) {
TEMPORARY_BLOCKS.removeIf(block -> block.server == server);
DELAYED_REAPPEARS.removeIf(pending -> pending.server == server);
}
private static boolean adaptiveAbilitiesEnabled() {
return AMConfig.enabled && AMConfig.AI_ENABLED.get() && AMConfig.ENABLE_ADVANCED_AI.get();
}
private record TemporaryBlock(net.minecraft.server.MinecraftServer server,
net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> dimension,
BlockPos pos, long expireTick) { BlockPos pos, long expireTick) {
} }
private record DelayedReappear(net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> dimension, private record DelayedReappear(net.minecraft.server.MinecraftServer server,
long tick, Silverfish mob, LivingEntity target, int tier) { net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> dimension,
long tick, UUID mobId, UUID targetId, int tier) {
} }
} }
@@ -49,25 +49,17 @@ public class AdaptiveAntiCheeseGoal extends Goal {
@Override @Override
public boolean canUse() { public boolean canUse() {
if (!AMConfig.ENABLE_ANTI_CHEESE_AI.get()) { if (!AMConfig.AI_ENABLED.get() || !AMConfig.ENABLE_ADVANCED_AI.get()
|| !AMConfig.ENABLE_ANTI_CHEESE_AI.get() || tierSupplier.getAsInt() < 3) {
return false; return false;
} }
LivingEntity target = mob.getTarget(); LivingEntity target = mob.getTarget();
if (isInTrapVehicle() || isTooCloseToTrapVehicle() || nearestTrapBlock() != null) {
return true;
}
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)) { if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
return false; return false;
} }
if (isInTrapVehicle() || nearestTrapBlock() != null || trappedAllyVehicle() != null) {
return true;
}
if (mob instanceof Zombie && isVerticalObstacleCheese(target)) { if (mob instanceof Zombie && isVerticalObstacleCheese(target)) {
return true; return true;
} }
@@ -141,15 +133,10 @@ public class AdaptiveAntiCheeseGoal extends Goal {
Entity vehicle = mob.getVehicle(); Entity vehicle = mob.getVehicle();
if (vehicle instanceof Boat || vehicle instanceof AbstractMinecart) { if (vehicle instanceof Boat || vehicle instanceof AbstractMinecart) {
mob.getNavigation().stop(); mob.getNavigation().stop();
tryDestroyTrapVehicle(vehicle); mob.stopRiding();
moveAwayFrom(vehicle.position(), 1.15D, "trap vehicle escape"); moveAwayFrom(vehicle.position(), 1.15D, "trap vehicle escape");
return true; return true;
} }
Entity trapVehicle = nearestTrapVehicle();
if (trapVehicle != null && mob.distanceToSqr(trapVehicle) < TRAP_VEHICLE_BREAK_DISTANCE_SQR) {
tryDestroyTrapVehicle(trapVehicle);
return true;
}
BlockPos trapBlock = nearestTrapBlock(); BlockPos trapBlock = nearestTrapBlock();
if (trapBlock != null) { if (trapBlock != null) {
tryDestroyTrapBlock(trapBlock); tryDestroyTrapBlock(trapBlock);
@@ -164,26 +151,6 @@ public class AdaptiveAntiCheeseGoal extends Goal {
return vehicle instanceof Boat || vehicle instanceof AbstractMinecart; return vehicle instanceof Boat || vehicle instanceof AbstractMinecart;
} }
private boolean isTooCloseToTrapVehicle() {
Entity trapVehicle = nearestTrapVehicle();
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(3.5D),
entity -> entity instanceof Boat || entity instanceof AbstractMinecart);
Entity nearest = null;
double best = Double.MAX_VALUE;
for (Entity entity : vehicles) {
double dist = mob.distanceToSqr(entity);
if (dist < best) {
best = dist;
nearest = entity;
}
}
return nearest;
}
private Entity trappedAllyVehicle() { private Entity trappedAllyVehicle() {
List<Entity> vehicles = mob.level().getEntitiesOfClass(Entity.class, mob.getBoundingBox().inflate(8.0D), List<Entity> vehicles = mob.level().getEntitiesOfClass(Entity.class, mob.getBoundingBox().inflate(8.0D),
entity -> isTrapVehicle(entity) && hasTrappedHostilePassenger(entity)); entity -> isTrapVehicle(entity) && hasTrappedHostilePassenger(entity));
@@ -245,42 +212,15 @@ public class AdaptiveAntiCheeseGoal extends Goal {
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "moving to break ally trap vehicle"); AdaptiveAIGoalUtils.debug(mob, tierSupplier, "moving to break ally trap vehicle");
return true; return true;
} }
mob.swing(mob.getUsedItemHand()); for (Entity passenger : List.copyOf(vehicle.getPassengers())) {
dropTrapVehicleItem(vehicle); if (passenger instanceof Mob trapped && trapped instanceof Enemy) {
vehicle.ejectPassengers(); trapped.stopRiding();
vehicle.discard(); }
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "breaking ally trap vehicle"); }
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "freeing ally from trap vehicle");
return true; 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() { private BlockPos nearestTrapBlock() {
BlockPos nearest = null; BlockPos nearest = null;
double best = Double.MAX_VALUE; double best = Double.MAX_VALUE;
@@ -317,6 +257,9 @@ public class AdaptiveAntiCheeseGoal extends Goal {
if (mob instanceof Spider && state.is(Blocks.COBWEB)) { if (mob instanceof Spider && state.is(Blocks.COBWEB)) {
return; return;
} }
if (mob instanceof Zombie && !AMConfig.ALLOW_ZOMBIE_BLOCK_BREAK.get()) {
return;
}
if (state.is(Blocks.COBWEB)) { if (state.is(Blocks.COBWEB)) {
Block.popResource(mob.level(), pos, new ItemStack(Items.STRING)); Block.popResource(mob.level(), pos, new ItemStack(Items.STRING));
} else if (state.is(Blocks.SWEET_BERRY_BUSH)) { } else if (state.is(Blocks.SWEET_BERRY_BUSH)) {
@@ -11,11 +11,11 @@ import net.minecraft.tags.FluidTags;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.monster.EnderMan; import net.minecraft.world.entity.monster.EnderMan;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.minecraftforge.event.ForgeEventFactory;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.function.IntSupplier; import java.util.function.IntSupplier;
@@ -24,23 +24,6 @@ public class AdaptiveEndermanTacticsGoal extends Goal {
private static final int ANTI_PILLAR_BUILD_COOLDOWN_MIN = 4; private static final int ANTI_PILLAR_BUILD_COOLDOWN_MIN = 4;
private static final int ANTI_PILLAR_BUILD_COOLDOWN_RANDOM = 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 EnderMan enderman;
private final IntSupplier tierSupplier; private final IntSupplier tierSupplier;
private int cooldown; private int cooldown;
@@ -57,7 +40,8 @@ public class AdaptiveEndermanTacticsGoal extends Goal {
@Override @Override
public boolean canUse() { public boolean canUse() {
return AMConfig.ENABLE_ENDERMAN_AI.get() return AMConfig.AI_ENABLED.get() && AMConfig.ENABLE_ADVANCED_AI.get()
&& AMConfig.AI_ENDERMAN.get() && AMConfig.ENABLE_ENDERMAN_AI.get()
&& tierSupplier.getAsInt() >= 2 && tierSupplier.getAsInt() >= 2
&& AdaptiveAIGoalUtils.isValidAdaptiveTarget(enderman.getTarget()) && AdaptiveAIGoalUtils.isValidAdaptiveTarget(enderman.getTarget())
&& --cooldown <= 0; && --cooldown <= 0;
@@ -118,8 +102,13 @@ public class AdaptiveEndermanTacticsGoal extends Goal {
if (!(enderman.level() instanceof ServerLevel level)) { if (!(enderman.level() instanceof ServerLevel level)) {
return false; return false;
} }
BlockState scaffoldState = randomScaffoldState(inEnd); if (!ForgeEventFactory.getMobGriefingEvent(level, enderman)) {
prepareScaffoldBlock(level, scaffoldState); return false;
}
BlockState scaffoldState = enderman.getCarriedBlock();
if (!isUsefulScaffoldBlock(scaffoldState)) {
return false;
}
BlockPos targetFeet = BlockPos.containing(target.getX(), target.getY(), target.getZ()); BlockPos targetFeet = BlockPos.containing(target.getX(), target.getY(), target.getZ());
if (!targetFeet.equals(rampTargetFeet)) { if (!targetFeet.equals(rampTargetFeet)) {
rampTargetFeet = targetFeet; rampTargetFeet = targetFeet;
@@ -131,8 +120,7 @@ public class AdaptiveEndermanTacticsGoal extends Goal {
int distance = Math.max(1, rampRise - step); int distance = Math.max(1, rampRise - step);
BlockPos pos = new BlockPos(targetFeet.getX(), rampBaseY + step, targetFeet.getZ()).relative(rampApproach, distance); BlockPos pos = new BlockPos(targetFeet.getX(), rampBaseY + step, targetFeet.getZ()).relative(rampApproach, distance);
if (canPlaceRampStep(level, pos, rampApproach, step == 0)) { if (canPlaceRampStep(level, pos, rampApproach, step == 0)) {
BlockState carried = enderman.getCarriedBlock(); level.setBlock(pos, scaffoldState, 3);
level.setBlock(pos, isUsefulScaffoldBlock(carried) ? carried : scaffoldState, 3);
enderman.setCarriedBlock(null); enderman.setCarriedBlock(null);
enderman.randomTeleport(pos.getX() + 0.5D, pos.getY() + 1.0D, pos.getZ() + 0.5D, true); enderman.randomTeleport(pos.getX() + 0.5D, pos.getY() + 1.0D, pos.getZ() + 0.5D, true);
AdaptiveAIGoalUtils.debug(enderman, tierSupplier, "enderman placed anti-pillar scaffold"); AdaptiveAIGoalUtils.debug(enderman, tierSupplier, "enderman placed anti-pillar scaffold");
@@ -142,55 +130,11 @@ public class AdaptiveEndermanTacticsGoal extends Goal {
return false; 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) { private boolean isUsefulScaffoldBlock(BlockState state) {
return state != null && !state.liquid() return state != null && !state.liquid()
&& !state.getCollisionShape(enderman.level(), enderman.blockPosition()).isEmpty(); && !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) { private Direction sharedRampDirection(BlockPos targetFeet) {
Direction[] directions = {Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST}; Direction[] directions = {Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST};
int index = Math.floorMod(targetFeet.getX() * 31 + targetFeet.getZ() * 17, directions.length); int index = Math.floorMod(targetFeet.getX() * 31 + targetFeet.getZ() * 17, directions.length);
@@ -1,6 +1,7 @@
package net.fretux.adaptivemobs.ai.goals; package net.fretux.adaptivemobs.ai.goals;
import net.fretux.adaptivemobs.ai.AdaptiveAIGoalUtils; import net.fretux.adaptivemobs.ai.AdaptiveAIGoalUtils;
import net.fretux.adaptivemobs.ai.AdaptiveGoalInjector;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.Mob;
@@ -20,6 +21,9 @@ public class AdaptiveTargetSanitizerGoal extends Goal {
@Override @Override
public boolean canUse() { public boolean canUse() {
if (!AdaptiveGoalInjector.isAIEnabledFor(mob)) {
return false;
}
LivingEntity target = mob.getTarget(); LivingEntity target = mob.getTarget();
return (target != null && !AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) || shouldReleaseZombieStack(target); return (target != null && !AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) || shouldReleaseZombieStack(target);
} }
@@ -28,7 +28,9 @@ import net.minecraftforge.event.entity.living.MobSpawnEvent;
import net.minecraftforge.event.level.BlockEvent; import net.minecraftforge.event.level.BlockEvent;
import net.minecraftforge.event.level.ExplosionEvent; import net.minecraftforge.event.level.ExplosionEvent;
import net.minecraftforge.event.level.LevelEvent; import net.minecraftforge.event.level.LevelEvent;
import net.minecraftforge.event.level.BlockEvent.EntityPlaceEvent;
import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.server.ServerStoppingEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
public class AdaptiveMobsEvents { public class AdaptiveMobsEvents {
@@ -97,6 +99,9 @@ public class AdaptiveMobsEvents {
@SubscribeEvent @SubscribeEvent
public void onBlockBreak(BlockEvent.BreakEvent event) { public void onBlockBreak(BlockEvent.BreakEvent event) {
if (event.getLevel() instanceof ServerLevel level) {
AdaptiveSpecialAbilities.invalidateTemporaryBlock(level, event.getPos());
}
if (AMConfig.enabled && AMConfig.ENABLE_SOUND_INVESTIGATION_AI.get() if (AMConfig.enabled && AMConfig.ENABLE_SOUND_INVESTIGATION_AI.get()
&& event.getLevel() instanceof ServerLevel level) { && event.getLevel() instanceof ServerLevel level) {
if (event.getPlayer() instanceof ServerPlayer player if (event.getPlayer() instanceof ServerPlayer player
@@ -107,6 +112,13 @@ public class AdaptiveMobsEvents {
} }
} }
@SubscribeEvent
public void onBlockPlace(EntityPlaceEvent event) {
if (event.getLevel() instanceof ServerLevel level) {
AdaptiveSpecialAbilities.invalidateTemporaryBlock(level, event.getPos());
}
}
@SubscribeEvent @SubscribeEvent
public void onItemUseFinish(LivingEntityUseItemEvent.Finish event) { public void onItemUseFinish(LivingEntityUseItemEvent.Finish event) {
if (AMConfig.enabled && AMConfig.ENABLE_SOUND_INVESTIGATION_AI.get() if (AMConfig.enabled && AMConfig.ENABLE_SOUND_INVESTIGATION_AI.get()
@@ -155,6 +167,7 @@ public class AdaptiveMobsEvents {
public void onLevelUnload(LevelEvent.Unload event) { public void onLevelUnload(LevelEvent.Unload event) {
if (event.getLevel() instanceof ServerLevel level) { if (event.getLevel() instanceof ServerLevel level) {
AdaptiveMemory.clear(level); AdaptiveMemory.clear(level);
AdaptiveSpecialAbilities.clear(level);
} }
} }
@@ -171,6 +184,11 @@ public class AdaptiveMobsEvents {
AdaptiveSpecialAbilities.onServerTick(event); AdaptiveSpecialAbilities.onServerTick(event);
} }
@SubscribeEvent
public void onServerStopping(ServerStoppingEvent event) {
AdaptiveSpecialAbilities.clear(event.getServer());
}
private static void clearFriendlyFireTolerance(Mob mob) { private static void clearFriendlyFireTolerance(Mob mob) {
mob.getPersistentData().remove(FRIENDLY_FIRE_SOURCE_KEY); mob.getPersistentData().remove(FRIENDLY_FIRE_SOURCE_KEY);
mob.getPersistentData().remove(FRIENDLY_FIRE_COUNT_KEY); mob.getPersistentData().remove(FRIENDLY_FIRE_COUNT_KEY);
@@ -115,6 +115,7 @@ public final class GearScaler {
if (tier >= 5 && random.nextDouble() < 0.22D) { if (tier >= 5 && random.nextDouble() < 0.22D) {
bow.enchant(Enchantments.FLAMING_ARROWS, 1); bow.enchant(Enchantments.FLAMING_ARROWS, 1);
} }
skeleton.setDropChance(EquipmentSlot.MAINHAND, 0.0F);
} }
private static void upgradeCrossbow(Pillager pillager, int tier, RandomSource random) { private static void upgradeCrossbow(Pillager pillager, int tier, RandomSource random) {
@@ -130,6 +131,7 @@ public final class GearScaler {
} else if (tier >= 5 && random.nextDouble() < 0.35D) { } else if (tier >= 5 && random.nextDouble() < 0.35D) {
crossbow.enchant(Enchantments.PIERCING, 2); crossbow.enchant(Enchantments.PIERCING, 2);
} }
pillager.setDropChance(EquipmentSlot.MAINHAND, 0.0F);
} }
// --- Helpers ----------------------------------------------------------- // --- Helpers -----------------------------------------------------------
@@ -10,7 +10,6 @@ import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobSpawnType; import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.monster.Monster; import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
/** /**
@@ -66,8 +65,8 @@ public final class SpawnPressureHelper {
for (int attempt = 0; attempt < 6; attempt++) { for (int attempt = 0; attempt < 6; attempt++) {
double dx = (random.nextDouble() - 0.5) * radius; double dx = (random.nextDouble() - 0.5) * radius;
double dz = (random.nextDouble() - 0.5) * radius; double dz = (random.nextDouble() - 0.5) * radius;
BlockPos spawnPos = surfaceSpawnPos(level, original.getX() + dx, original.getZ() + dz); BlockPos spawnPos = nearbySpawnPos(original, original.getX() + dx, original.getZ() + dz, random);
if (!level.getWorldBorder().isWithinBounds(spawnPos)) { if (!level.hasChunkAt(spawnPos) || !level.getWorldBorder().isWithinBounds(spawnPos)) {
continue; continue;
} }
extra.moveTo(spawnPos.getX() + 0.5D, spawnPos.getY(), spawnPos.getZ() + 0.5D, extra.moveTo(spawnPos.getX() + 0.5D, spawnPos.getY(), spawnPos.getZ() + 0.5D,
@@ -93,10 +92,12 @@ public final class SpawnPressureHelper {
} }
} }
private static BlockPos surfaceSpawnPos(ServerLevel level, double x, double z) { private static BlockPos nearbySpawnPos(Mob original, double x, double z, RandomSource random) {
int blockX = BlockPos.containing(x, 0.0D, z).getX(); int blockX = BlockPos.containing(x, 0.0D, z).getX();
int blockZ = BlockPos.containing(x, 0.0D, z).getZ(); int blockZ = BlockPos.containing(x, 0.0D, z).getZ();
int y = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, blockX, blockZ); // Stay in the original mob's vertical habitat. Spawn rules and collision checks below
// decide whether a candidate suits ground, cave, aquatic, or airborne mobs.
int y = original.blockPosition().getY() + random.nextInt(9) - 4;
return new BlockPos(blockX, y, blockZ); return new BlockPos(blockX, y, blockZ);
} }
} }