fix various issues and add adaptive creeper fuse

This commit is contained in:
2026-07-20 20:30:27 +02:00
parent a0ca350526
commit 34260431f2
9 changed files with 110 additions and 31 deletions
@@ -1,6 +1,7 @@
package net.fretux.adaptivemobs.ai;
import net.fretux.adaptivemobs.ai.goals.AdaptiveCreeperPressureGoal;
import net.fretux.adaptivemobs.ai.goals.AdaptiveCreeperFuseGoal;
import net.fretux.adaptivemobs.ai.goals.AdaptiveAirbornePositioningGoal;
import net.fretux.adaptivemobs.ai.goals.AdaptiveAntiCheeseGoal;
import net.fretux.adaptivemobs.ai.goals.AdaptiveBiomeTacticsGoal;
@@ -109,7 +110,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) {
creeper.maxSwell = creeperFuseTicks(tier.getAsInt());
mob.goalSelector.addGoal(0, new AdaptiveCreeperFuseGoal(creeper, tier));
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) {
@@ -351,10 +352,6 @@ public final class AdaptiveGoalInjector {
return true;
}
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;
}
@@ -3,6 +3,7 @@ package net.fretux.adaptivemobs.ai;
import net.fretux.adaptivemobs.config.AMConfig;
import net.fretux.adaptivemobs.difficulty.AdaptiveDifficultyManager;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
@@ -48,10 +49,13 @@ import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.entity.projectile.ShulkerBullet;
import net.minecraft.world.entity.player.Player;
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.TickEvent;
import net.minecraftforge.event.entity.EntityJoinLevelEvent;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.common.util.BlockSnapshot;
import net.minecraftforge.event.ForgeEventFactory;
import java.util.ArrayList;
import java.util.Iterator;
@@ -139,8 +143,8 @@ public final class AdaptiveSpecialAbilities {
if (level == null || level.getGameTime() < block.expireTick) {
continue;
}
if (level.getBlockState(block.pos).is(Blocks.COBWEB)) {
level.removeBlock(block.pos, false);
if (level.getBlockState(block.pos).equals(block.placedState)) {
block.replaced.restore(true, false);
}
blockIterator.remove();
}
@@ -186,7 +190,7 @@ public final class AdaptiveSpecialAbilities {
AdaptiveAIGoalUtils.debug(mob, () -> tier, "zombified piglin blood call");
}
if (tier >= 4 && mob instanceof Spider && ready(mob, "web_retreat", 600)) {
placeTemporaryCobweb(level, attacker.blockPosition(), level.getGameTime() + 100 + mob.getRandom().nextInt(61));
placeTemporaryCobweb(level, mob, 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)) {
Vec3 behind = attacker.position().subtract(attacker.getLookAngle().normalize().scale(2.2D));
@@ -244,7 +248,7 @@ public final class AdaptiveSpecialAbilities {
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 ? 400 : 600)) {
placeTemporaryCobweb(level, player.blockPosition(), level.getGameTime() + 100 + mob.getRandom().nextInt(61));
placeTemporaryCobweb(level, mob, player.blockPosition(), level.getGameTime() + 100 + mob.getRandom().nextInt(61));
}
if (mob instanceof CaveSpider && tier >= 3
&& (AdaptiveAIGoalUtils.isUsingRangedWeapon(player) || player.isUsingItem())) {
@@ -330,13 +334,22 @@ public final class AdaptiveSpecialAbilities {
target.hurtMarked = true;
}
private static void placeTemporaryCobweb(ServerLevel level, BlockPos requestedPos, long expireTick) {
private static void placeTemporaryCobweb(ServerLevel level, Mob spider, BlockPos requestedPos, long expireTick) {
BlockPos pos = level.getFluidState(requestedPos).is(FluidTags.WATER) ? requestedPos.above() : requestedPos;
if (!level.getBlockState(pos).isAir() || !level.getWorldBorder().isWithinBounds(pos)) {
if (!ForgeEventFactory.getMobGriefingEvent(level, spider)
|| !level.getBlockState(pos).isAir() || !level.getWorldBorder().isWithinBounds(pos)) {
return;
}
level.setBlock(pos, Blocks.COBWEB.defaultBlockState(), 3);
TEMPORARY_BLOCKS.add(new TemporaryBlock(level.getServer(), level.dimension(), pos.immutable(), expireTick));
BlockSnapshot replaced = BlockSnapshot.create(level.dimension(), level, pos);
if (ForgeEventFactory.onBlockPlace(spider, replaced, Direction.UP)) {
return;
}
BlockState placedState = Blocks.COBWEB.defaultBlockState();
if (!level.setBlock(pos, placedState, 3)) {
return;
}
TEMPORARY_BLOCKS.add(new TemporaryBlock(level.getServer(), level.dimension(), pos.immutable(),
expireTick, replaced, placedState));
level.playSound(null, pos, SoundEvents.SPIDER_AMBIENT, SoundSource.HOSTILE, 0.6F, 1.25F);
}
@@ -385,8 +398,8 @@ public final class AdaptiveSpecialAbilities {
if (block.server != level.getServer() || block.dimension != level.dimension()) {
return false;
}
if (level.getBlockState(block.pos).is(Blocks.COBWEB)) {
level.removeBlock(block.pos, false);
if (level.getBlockState(block.pos).equals(block.placedState)) {
block.replaced.restore(true, false);
}
return true;
});
@@ -399,8 +412,8 @@ public final class AdaptiveSpecialAbilities {
return false;
}
ServerLevel level = server.getLevel(block.dimension);
if (level != null && level.getBlockState(block.pos).is(Blocks.COBWEB)) {
level.removeBlock(block.pos, false);
if (level != null && level.getBlockState(block.pos).equals(block.placedState)) {
block.replaced.restore(true, false);
}
return true;
});
@@ -413,7 +426,7 @@ public final class AdaptiveSpecialAbilities {
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, BlockSnapshot replaced, BlockState placedState) {
}
private record DelayedReappear(net.minecraft.server.MinecraftServer server,
@@ -25,6 +25,7 @@ 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.minecraftforge.common.ForgeHooks;
import net.minecraft.world.phys.Vec3;
import java.util.EnumSet;
@@ -260,6 +261,9 @@ public class AdaptiveAntiCheeseGoal extends Goal {
if (mob instanceof Zombie && !AMConfig.ALLOW_ZOMBIE_BLOCK_BREAK.get()) {
return;
}
if (!ForgeHooks.canEntityDestroy(mob.level(), pos, mob)) {
return;
}
if (state.is(Blocks.COBWEB)) {
Block.popResource(mob.level(), pos, new ItemStack(Items.STRING));
} else if (state.is(Blocks.SWEET_BERRY_BUSH)) {
@@ -0,0 +1,38 @@
package net.fretux.adaptivemobs.ai.goals;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.monster.Creeper;
import java.util.function.IntSupplier;
/** Keeps the creeper fuse synchronized with the current config-gated adaptive tier. */
public final class AdaptiveCreeperFuseGoal extends Goal {
private final Creeper creeper;
private final IntSupplier tierSupplier;
private int updateCooldown;
public AdaptiveCreeperFuseGoal(Creeper creeper, IntSupplier tierSupplier) {
this.creeper = creeper;
this.tierSupplier = tierSupplier;
}
@Override
public boolean canUse() {
return true;
}
@Override
public boolean canContinueToUse() {
return true;
}
@Override
public void tick() {
if (--updateCooldown > 0) {
return;
}
updateCooldown = 20;
int tier = tierSupplier.getAsInt();
creeper.maxSwell = tier <= 0 ? 30 : Math.max(18, 30 - Math.max(0, tier - 1) * 3);
}
}
@@ -18,6 +18,7 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.util.BlockSnapshot;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.event.ForgeEventFactory;
import java.util.EnumSet;
@@ -341,7 +342,8 @@ public class AdaptiveEndermanTacticsGoal extends Goal {
}
private boolean pickUpBlock(ServerLevel level, BlockPos pos, String debugMessage) {
if (enderman.getCarriedBlock() != null || !isMovableBlock(level, pos)) {
if (enderman.getCarriedBlock() != null || !isMovableBlock(level, pos)
|| !ForgeHooks.canEntityDestroy(level, pos, enderman)) {
return false;
}
BlockState state = level.getBlockState(pos);
@@ -59,8 +59,9 @@ public class AdaptiveSlimeTacticsGoal extends Goal {
public void start() {
int tier = tierSupplier.getAsInt();
if (recombinePartner != null && recombinePartner.isAlive()) {
tryRecombine(recombinePartner);
recombinePartner = null;
if (tryRecombine(recombinePartner)) {
recombinePartner = null;
}
return;
}
cooldown = Math.max(16, 52 - tier * 5) + slime.getRandom().nextInt(25);
@@ -84,6 +85,25 @@ public class AdaptiveSlimeTacticsGoal extends Goal {
AdaptiveAIGoalUtils.debug(slime, tierSupplier, "slime angled jump pressure");
}
@Override
public boolean canContinueToUse() {
return recombinePartner != null && recombinePartner.isAlive()
&& slime.isAlive() && tierSupplier.getAsInt() >= 5
&& slime.getSize() < MAX_RECOMBINE_SIZE;
}
@Override
public void tick() {
if (recombinePartner != null && tryRecombine(recombinePartner)) {
recombinePartner = null;
}
}
@Override
public void stop() {
recombinePartner = null;
}
private Slime findRecombinePartner() {
boolean magma = slime instanceof MagmaCube;
List<Slime> candidates = slime.level().getEntitiesOfClass(Slime.class,
@@ -94,11 +114,11 @@ public class AdaptiveSlimeTacticsGoal extends Goal {
return candidates.stream().min(Comparator.comparingDouble(slime::distanceToSqr)).orElse(null);
}
private void tryRecombine(Slime partner) {
private boolean tryRecombine(Slime partner) {
if (slime.distanceToSqr(partner) > RECOMBINE_DISTANCE_SQR) {
slime.getLookControl().setLookAt(partner, 30.0F, 30.0F);
slime.getMoveControl().setWantedPosition(partner.getX(), partner.getY(), partner.getZ(), 1.2D);
return;
return false;
}
LivingEntity inheritedTarget = AdaptiveAIGoalUtils.isValidAdaptiveTarget(slime.getTarget())
? slime.getTarget() : partner.getTarget();
@@ -110,6 +130,7 @@ public class AdaptiveSlimeTacticsGoal extends Goal {
scheduleRecombine(200 + slime.getRandom().nextInt(101));
AdaptiveAIGoalUtils.debug(slime, tierSupplier,
slime instanceof MagmaCube ? "magma cube recombined" : "slime recombined");
return true;
}
private void scheduleRecombine(int delayTicks) {
@@ -78,8 +78,11 @@ public class AdaptiveSavedData extends SavedData {
// --- Mutators (mark dirty) --------------------------------------------
public void addKill(String typeKey) {
totalHostileKills++;
killsByType.merge(typeKey, 1, Integer::sum);
if (totalHostileKills < Long.MAX_VALUE) {
totalHostileKills++;
}
killsByType.compute(typeKey, (key, current) ->
current == null ? 1 : current < Integer.MAX_VALUE ? current + 1 : Integer.MAX_VALUE);
setDirty();
}