diff --git a/src/main/java/net/fretux/adaptivemobs/ai/AdaptiveGoalInjector.java b/src/main/java/net/fretux/adaptivemobs/ai/AdaptiveGoalInjector.java index 56b5870..cee806c 100644 --- a/src/main/java/net/fretux/adaptivemobs/ai/AdaptiveGoalInjector.java +++ b/src/main/java/net/fretux/adaptivemobs/ai/AdaptiveGoalInjector.java @@ -85,19 +85,19 @@ public final class AdaptiveGoalInjector { } public static void inject(Mob mob, ServerLevel level, int initialTier) { - if (!AMConfig.AI_ENABLED.get() || initialTier <= 0 || isBoss(mob)) { + if (isBoss(mob)) { return; } if (!INJECTED.add(mob)) { return; } 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)); - 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()) { 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)); } - if (AMConfig.ENABLE_ADVANCED_AI.get()) { - injectAdvanced(mob, pathfinder, tier); - AdaptiveAIGoalUtils.debug(mob, tier, "injected adaptive goals"); - } + // Install advanced goals even while disabled. Their tier supplier returns zero until the + // switches are enabled, allowing config reloads to affect already-loaded mobs both ways. + injectAdvanced(mob, pathfinder, advancedTier); + AdaptiveAIGoalUtils.debug(mob, tier, "injected adaptive goals"); } private static void injectAdvanced(Mob mob, PathfinderMob pathfinder, IntSupplier tier) { @@ -310,6 +310,32 @@ public final class AdaptiveGoalInjector { 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) { return Math.max(18, 30 - Math.max(0, tier - 1) * 3); } diff --git a/src/main/java/net/fretux/adaptivemobs/ai/AdaptiveSpecialAbilities.java b/src/main/java/net/fretux/adaptivemobs/ai/AdaptiveSpecialAbilities.java index c816038..2f5df6a 100644 --- a/src/main/java/net/fretux/adaptivemobs/ai/AdaptiveSpecialAbilities.java +++ b/src/main/java/net/fretux/adaptivemobs/ai/AdaptiveSpecialAbilities.java @@ -56,6 +56,7 @@ import net.minecraftforge.event.entity.living.LivingHurtEvent; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.UUID; public final class AdaptiveSpecialAbilities { @@ -68,7 +69,7 @@ public final class AdaptiveSpecialAbilities { } public static void onProjectileJoin(EntityJoinLevelEvent event) { - if (!AMConfig.enabled || !(event.getLevel() instanceof ServerLevel level)) { + if (!adaptiveAbilitiesEnabled() || !(event.getLevel() instanceof ServerLevel level)) { return; } Entity entity = event.getEntity(); @@ -112,7 +113,7 @@ public final class AdaptiveSpecialAbilities { } public static void onLivingHurt(LivingHurtEvent event) { - if (!AMConfig.enabled || !(event.getEntity().level() instanceof ServerLevel level)) { + if (!adaptiveAbilitiesEnabled() || !(event.getEntity().level() instanceof ServerLevel level)) { return; } LivingEntity victim = event.getEntity(); @@ -138,7 +139,7 @@ public final class AdaptiveSpecialAbilities { if (now < block.expireTick) { 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)) { level.removeBlock(block.pos, false); } @@ -151,15 +152,22 @@ public final class AdaptiveSpecialAbilities { if (now < pending.tick) { continue; } - ServerLevel level = event.getServer().getLevel(pending.dimension); - 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()); - pending.mob.removeEffect(MobEffects.INVISIBILITY); - pending.mob.setTarget(pending.target); - AdaptiveAIGoalUtils.debug(pending.mob, () -> pending.tier, "silverfish burrow burst"); + ServerLevel level = pending.server == event.getServer() ? event.getServer().getLevel(pending.dimension) : null; + Entity mobEntity = level == null ? null : level.getEntity(pending.mobId); + Entity targetEntity = level == null ? null : level.getEntity(pending.targetId); + if (mobEntity instanceof Silverfish mob && targetEntity instanceof LivingEntity target + && mob.isAlive() && AdaptiveAIGoalUtils.isValidAdaptiveTarget(target) && mob.canAttack(target)) { + Vec3 side = AdaptivePositioningUtils.sidePosition(mob, target, 1.6D, + mob.getRandom().nextBoolean() ? 1.0D : -1.0D, 0.0D); + BlockPos destination = BlockPos.containing(side); + 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(); } @@ -193,7 +201,8 @@ public final class AdaptiveSpecialAbilities { && ready(mob, "burrow_burst", 220)) { silverfish.addEffect(new MobEffectInstance(MobEffects.INVISIBILITY, 35, 0)); 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"); } 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) { - if (!AMConfig.isMobEnabled(mob.getType())) { + if (!adaptiveAbilitiesEnabled() || !AdaptiveGoalInjector.isAIEnabledFor(mob)) { return 0; } return AdaptiveDifficultyManager.getMobTier(level, mob.getType()); @@ -328,7 +337,7 @@ public final class AdaptiveSpecialAbilities { return; } 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); } @@ -367,11 +376,32 @@ public final class AdaptiveSpecialAbilities { level.addFreshEntity(split); } - private record TemporaryBlock(net.minecraft.resources.ResourceKey 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 dimension, BlockPos pos, long expireTick) { } - private record DelayedReappear(net.minecraft.resources.ResourceKey dimension, - long tick, Silverfish mob, LivingEntity target, int tier) { + private record DelayedReappear(net.minecraft.server.MinecraftServer server, + net.minecraft.resources.ResourceKey dimension, + long tick, UUID mobId, UUID targetId, int 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 ad74c68..439c42e 100644 --- a/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveAntiCheeseGoal.java +++ b/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveAntiCheeseGoal.java @@ -49,25 +49,17 @@ public class AdaptiveAntiCheeseGoal extends Goal { @Override 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; } 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)) { return false; } + if (isInTrapVehicle() || nearestTrapBlock() != null || trappedAllyVehicle() != null) { + return true; + } if (mob instanceof Zombie && isVerticalObstacleCheese(target)) { return true; } @@ -141,15 +133,10 @@ public class AdaptiveAntiCheeseGoal extends Goal { Entity vehicle = mob.getVehicle(); if (vehicle instanceof Boat || vehicle instanceof AbstractMinecart) { mob.getNavigation().stop(); - tryDestroyTrapVehicle(vehicle); + mob.stopRiding(); moveAwayFrom(vehicle.position(), 1.15D, "trap vehicle escape"); return true; } - Entity trapVehicle = nearestTrapVehicle(); - if (trapVehicle != null && mob.distanceToSqr(trapVehicle) < TRAP_VEHICLE_BREAK_DISTANCE_SQR) { - tryDestroyTrapVehicle(trapVehicle); - return true; - } BlockPos trapBlock = nearestTrapBlock(); if (trapBlock != null) { tryDestroyTrapBlock(trapBlock); @@ -164,26 +151,6 @@ public class AdaptiveAntiCheeseGoal extends Goal { 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 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() { List vehicles = mob.level().getEntitiesOfClass(Entity.class, mob.getBoundingBox().inflate(8.0D), entity -> isTrapVehicle(entity) && hasTrappedHostilePassenger(entity)); @@ -245,42 +212,15 @@ public class AdaptiveAntiCheeseGoal extends Goal { 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"); + for (Entity passenger : List.copyOf(vehicle.getPassengers())) { + if (passenger instanceof Mob trapped && trapped instanceof Enemy) { + trapped.stopRiding(); + } + } + AdaptiveAIGoalUtils.debug(mob, tierSupplier, "freeing ally from 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; @@ -317,6 +257,9 @@ public class AdaptiveAntiCheeseGoal extends Goal { if (mob instanceof Spider && state.is(Blocks.COBWEB)) { return; } + if (mob instanceof Zombie && !AMConfig.ALLOW_ZOMBIE_BLOCK_BREAK.get()) { + return; + } if (state.is(Blocks.COBWEB)) { Block.popResource(mob.level(), pos, new ItemStack(Items.STRING)); } else if (state.is(Blocks.SWEET_BERRY_BUSH)) { diff --git a/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveEndermanTacticsGoal.java b/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveEndermanTacticsGoal.java index 8e677d0..6a32c4f 100644 --- a/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveEndermanTacticsGoal.java +++ b/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveEndermanTacticsGoal.java @@ -11,11 +11,11 @@ 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 net.minecraftforge.event.ForgeEventFactory; import java.util.EnumSet; 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_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; @@ -57,7 +40,8 @@ public class AdaptiveEndermanTacticsGoal extends Goal { @Override 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 && AdaptiveAIGoalUtils.isValidAdaptiveTarget(enderman.getTarget()) && --cooldown <= 0; @@ -118,8 +102,13 @@ public class AdaptiveEndermanTacticsGoal extends Goal { if (!(enderman.level() instanceof ServerLevel level)) { return false; } - BlockState scaffoldState = randomScaffoldState(inEnd); - prepareScaffoldBlock(level, scaffoldState); + if (!ForgeEventFactory.getMobGriefingEvent(level, enderman)) { + return false; + } + BlockState scaffoldState = enderman.getCarriedBlock(); + if (!isUsefulScaffoldBlock(scaffoldState)) { + return false; + } BlockPos targetFeet = BlockPos.containing(target.getX(), target.getY(), target.getZ()); if (!targetFeet.equals(rampTargetFeet)) { rampTargetFeet = targetFeet; @@ -131,8 +120,7 @@ public class AdaptiveEndermanTacticsGoal extends Goal { 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); + level.setBlock(pos, 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"); @@ -142,55 +130,11 @@ public class AdaptiveEndermanTacticsGoal extends Goal { 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); diff --git a/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveTargetSanitizerGoal.java b/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveTargetSanitizerGoal.java index 74132fa..b6b82e8 100644 --- a/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveTargetSanitizerGoal.java +++ b/src/main/java/net/fretux/adaptivemobs/ai/goals/AdaptiveTargetSanitizerGoal.java @@ -1,6 +1,7 @@ package net.fretux.adaptivemobs.ai.goals; import net.fretux.adaptivemobs.ai.AdaptiveAIGoalUtils; +import net.fretux.adaptivemobs.ai.AdaptiveGoalInjector; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; @@ -20,6 +21,9 @@ public class AdaptiveTargetSanitizerGoal extends Goal { @Override public boolean canUse() { + if (!AdaptiveGoalInjector.isAIEnabledFor(mob)) { + return false; + } LivingEntity target = mob.getTarget(); return (target != null && !AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) || shouldReleaseZombieStack(target); } diff --git a/src/main/java/net/fretux/adaptivemobs/event/AdaptiveMobsEvents.java b/src/main/java/net/fretux/adaptivemobs/event/AdaptiveMobsEvents.java index 2a9853e..8878153 100644 --- a/src/main/java/net/fretux/adaptivemobs/event/AdaptiveMobsEvents.java +++ b/src/main/java/net/fretux/adaptivemobs/event/AdaptiveMobsEvents.java @@ -28,7 +28,9 @@ import net.minecraftforge.event.entity.living.MobSpawnEvent; import net.minecraftforge.event.level.BlockEvent; import net.minecraftforge.event.level.ExplosionEvent; import net.minecraftforge.event.level.LevelEvent; +import net.minecraftforge.event.level.BlockEvent.EntityPlaceEvent; import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; public class AdaptiveMobsEvents { @@ -97,6 +99,9 @@ public class AdaptiveMobsEvents { @SubscribeEvent 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() && event.getLevel() instanceof ServerLevel level) { 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 public void onItemUseFinish(LivingEntityUseItemEvent.Finish event) { if (AMConfig.enabled && AMConfig.ENABLE_SOUND_INVESTIGATION_AI.get() @@ -155,6 +167,7 @@ public class AdaptiveMobsEvents { public void onLevelUnload(LevelEvent.Unload event) { if (event.getLevel() instanceof ServerLevel level) { AdaptiveMemory.clear(level); + AdaptiveSpecialAbilities.clear(level); } } @@ -171,6 +184,11 @@ public class AdaptiveMobsEvents { AdaptiveSpecialAbilities.onServerTick(event); } + @SubscribeEvent + public void onServerStopping(ServerStoppingEvent event) { + AdaptiveSpecialAbilities.clear(event.getServer()); + } + private static void clearFriendlyFireTolerance(Mob mob) { mob.getPersistentData().remove(FRIENDLY_FIRE_SOURCE_KEY); mob.getPersistentData().remove(FRIENDLY_FIRE_COUNT_KEY); diff --git a/src/main/java/net/fretux/adaptivemobs/gear/GearScaler.java b/src/main/java/net/fretux/adaptivemobs/gear/GearScaler.java index 7146927..04d3ec0 100644 --- a/src/main/java/net/fretux/adaptivemobs/gear/GearScaler.java +++ b/src/main/java/net/fretux/adaptivemobs/gear/GearScaler.java @@ -115,6 +115,7 @@ public final class GearScaler { if (tier >= 5 && random.nextDouble() < 0.22D) { bow.enchant(Enchantments.FLAMING_ARROWS, 1); } + skeleton.setDropChance(EquipmentSlot.MAINHAND, 0.0F); } 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) { crossbow.enchant(Enchantments.PIERCING, 2); } + pillager.setDropChance(EquipmentSlot.MAINHAND, 0.0F); } // --- Helpers ----------------------------------------------------------- diff --git a/src/main/java/net/fretux/adaptivemobs/spawn/SpawnPressureHelper.java b/src/main/java/net/fretux/adaptivemobs/spawn/SpawnPressureHelper.java index 94f789c..ce588d5 100644 --- a/src/main/java/net/fretux/adaptivemobs/spawn/SpawnPressureHelper.java +++ b/src/main/java/net/fretux/adaptivemobs/spawn/SpawnPressureHelper.java @@ -10,7 +10,6 @@ import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.MobSpawnType; import net.minecraft.world.entity.monster.Monster; -import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.phys.AABB; /** @@ -66,8 +65,8 @@ public final class SpawnPressureHelper { for (int attempt = 0; attempt < 6; attempt++) { double dx = (random.nextDouble() - 0.5) * radius; double dz = (random.nextDouble() - 0.5) * radius; - BlockPos spawnPos = surfaceSpawnPos(level, original.getX() + dx, original.getZ() + dz); - if (!level.getWorldBorder().isWithinBounds(spawnPos)) { + BlockPos spawnPos = nearbySpawnPos(original, original.getX() + dx, original.getZ() + dz, random); + if (!level.hasChunkAt(spawnPos) || !level.getWorldBorder().isWithinBounds(spawnPos)) { continue; } 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 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); } }