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.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.world.damagesource.DamageTypes; import net.minecraft.tags.FluidTags; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.PathfinderMob; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.monster.AbstractSkeleton; import net.minecraft.world.entity.monster.Blaze; import net.minecraft.world.entity.monster.CaveSpider; import net.minecraft.world.entity.monster.Drowned; import net.minecraft.world.entity.monster.ElderGuardian; import net.minecraft.world.entity.monster.EnderMan; import net.minecraft.world.entity.monster.Endermite; import net.minecraft.world.entity.monster.Evoker; import net.minecraft.world.entity.monster.Guardian; import net.minecraft.world.entity.monster.MagmaCube; import net.minecraft.world.entity.monster.Phantom; import net.minecraft.world.entity.monster.Pillager; import net.minecraft.world.entity.monster.Ravager; import net.minecraft.world.entity.monster.Shulker; import net.minecraft.world.entity.monster.Silverfish; import net.minecraft.world.entity.monster.Slime; import net.minecraft.world.entity.monster.Spider; import net.minecraft.world.entity.monster.Stray; import net.minecraft.world.entity.monster.Vex; import net.minecraft.world.entity.monster.Vindicator; import net.minecraft.world.entity.monster.WitherSkeleton; import net.minecraft.world.entity.monster.Zoglin; import net.minecraft.world.entity.monster.ZombifiedPiglin; import net.minecraft.world.entity.monster.hoglin.Hoglin; import net.minecraft.world.entity.monster.piglin.PiglinBrute; import net.minecraft.world.entity.monster.warden.Warden; import net.minecraft.world.entity.projectile.AbstractArrow; import net.minecraft.world.entity.projectile.Arrow; 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.phys.Vec3; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.EntityJoinLevelEvent; import net.minecraftforge.event.entity.living.LivingHurtEvent; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public final class AdaptiveSpecialAbilities { private static final String COOLDOWN_PREFIX = "am_special_cd_"; private static final List TEMPORARY_BLOCKS = new ArrayList<>(); private static final List DELAYED_REAPPEARS = new ArrayList<>(); private AdaptiveSpecialAbilities() { } public static void onProjectileJoin(EntityJoinLevelEvent event) { if (!AMConfig.enabled || !(event.getLevel() instanceof ServerLevel level)) { return; } Entity entity = event.getEntity(); if (!(entity instanceof Projectile projectile) || !(projectile.getOwner() instanceof Mob mob)) { return; } int tier = tier(level, mob); if (tier < 4 || !(projectile instanceof Arrow arrow)) { return; } LivingEntity target = mob.getTarget(); if (mob instanceof Stray) { arrow.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, tier >= 5 ? 100 : 70, tier >= 5 ? 1 : 0)); if (tier >= 5 && mob.getRandom().nextFloat() < 0.22F) { arrow.addEffect(new MobEffectInstance(MobEffects.DIG_SLOWDOWN, 70, 0)); } return; } if (mob instanceof AbstractSkeleton) { float poisonChance = tier >= 5 ? 0.16F : 0.09F; if (mob.getRandom().nextFloat() < poisonChance) { arrow.addEffect(new MobEffectInstance(MobEffects.POISON, 80 + mob.getRandom().nextInt(41), 0)); } if (tier >= 5 && AdaptiveAIGoalUtils.isValidAdaptiveTarget(target) && AdaptiveAIGoalUtils.isBlockingShield(target) && mob.getRandom().nextFloat() < 0.18F) { arrow.addEffect(new MobEffectInstance(MobEffects.WEAKNESS, 60, 0)); } else if (tier >= 5 && AdaptiveAIGoalUtils.isValidAdaptiveTarget(target) && target.isSprinting() && mob.getRandom().nextFloat() < 0.18F) { arrow.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 60, 0)); } return; } if (mob instanceof Pillager && tier >= 5 && mob.getRandom().nextFloat() < 0.18F) { arrow.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 40, 1)); arrow.getPersistentData().putBoolean("am_pinning_bolt", true); } } public static void onLivingHurt(LivingHurtEvent event) { if (!AMConfig.enabled || !(event.getEntity().level() instanceof ServerLevel level)) { return; } LivingEntity victim = event.getEntity(); Entity sourceEntity = event.getSource().getEntity(); Entity directEntity = event.getSource().getDirectEntity(); if (victim instanceof Mob damagedMob && sourceEntity instanceof LivingEntity attacker) { onMobDamaged(level, damagedMob, attacker, directEntity); } if (victim instanceof ServerPlayer player && sourceEntity instanceof Mob attackerMob) { onPlayerHurt(level, player, attackerMob, directEntity, event); } } public static void onServerTick(TickEvent.ServerTickEvent event) { if (event.phase != TickEvent.Phase.END || event.getServer() == null) { return; } long now = event.getServer().overworld().getGameTime(); Iterator blockIterator = TEMPORARY_BLOCKS.iterator(); while (blockIterator.hasNext()) { TemporaryBlock block = blockIterator.next(); if (now < block.expireTick) { continue; } ServerLevel level = event.getServer().getLevel(block.dimension); if (level != null && level.getBlockState(block.pos).is(Blocks.COBWEB)) { level.removeBlock(block.pos, false); } blockIterator.remove(); } Iterator reappearIterator = DELAYED_REAPPEARS.iterator(); while (reappearIterator.hasNext()) { DelayedReappear pending = reappearIterator.next(); 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"); } reappearIterator.remove(); } } private static void onMobDamaged(ServerLevel level, Mob mob, LivingEntity attacker, Entity directEntity) { int tier = tier(level, mob); if (tier <= 0 || !AdaptiveAIGoalUtils.isValidAdaptiveTarget(attacker) || !mob.canAttack(attacker)) { return; } if (tier >= 5 && mob instanceof ZombifiedPiglin && ready(mob, "blood_call", 160)) { for (Mob ally : AdaptiveTargetingUtils.nearbyHostiles(mob, 12.0D, other -> other instanceof ZombifiedPiglin)) { ally.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SPEED, 100, 1)); ally.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, 100, 0)); ally.setTarget(attacker); } 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)); } 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)); if (enderman.randomTeleport(behind.x, behind.y, behind.z, true)) { enderman.setTarget(attacker); AdaptiveAIGoalUtils.debug(mob, () -> tier, "enderman backstep ambush"); } } if (tier >= 4 && mob instanceof Silverfish silverfish && AdaptiveAIGoalUtils.healthFraction(silverfish) < 0.45D && 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)); AdaptiveAIGoalUtils.debug(mob, () -> tier, "silverfish burrow vanish"); } if (tier >= 4 && mob instanceof Evoker evoker && ready(mob, "vex_relay", 120)) { for (Mob vex : AdaptiveTargetingUtils.nearbyHostiles(evoker, 14.0D, other -> other instanceof Vex)) { vex.setTarget(attacker); } Vec3 retreat = AdaptivePositioningUtils.retreatPosition(evoker, attacker, 5.0D); evoker.getNavigation().moveTo(retreat.x, retreat.y, retreat.z, 1.0D); AdaptiveAIGoalUtils.debug(mob, () -> tier, "evoker vex relay"); } if (tier >= 5 && mob instanceof Blaze && eventWasRanged(directEntity) && ready(mob, "overheat", 120)) { mob.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, 80, 0)); mob.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SPEED, 80, 0)); AdaptiveAIGoalUtils.debug(mob, () -> tier, "blaze overheat volley"); } if (tier >= 5 && mob instanceof Zoglin && AdaptiveAIGoalUtils.healthFraction(mob) < 0.50D && ready(mob, "rampage", 180)) { mob.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SPEED, 100, 1)); mob.addEffect(new MobEffectInstance(MobEffects.DAMAGE_RESISTANCE, 100, 0)); AdaptiveAIGoalUtils.debug(mob, () -> tier, "zoglin frenzied rampage"); } if (tier >= 5 && mob instanceof Warden && eventWasRanged(directEntity) && attacker instanceof ServerPlayer player) { AdaptiveMemory.rememberThreat(mob, player, 20 * 30); mob.getPersistentData().putLong(COOLDOWN_PREFIX + "echo_mark", level.getGameTime() + 20 * 30L); AdaptiveAIGoalUtils.debug(mob, () -> tier, "warden echo mark"); } } private static void onPlayerHurt(ServerLevel level, ServerPlayer player, Mob mob, Entity directEntity, LivingHurtEvent event) { int tier = tier(level, mob); if (tier <= 0 || !AdaptiveAIGoalUtils.isValidAdaptiveTarget(player)) { return; } if (tier >= 4 && mob instanceof Drowned && mob.isInWater() && player.isInWater() && ready(mob, "dragging_grip", 160)) { player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 60, 1)); player.setDeltaMovement(player.getDeltaMovement().add(0.0D, -0.22D, 0.0D)); AdaptiveAIGoalUtils.debug(mob, () -> tier, "drowned dragging grip"); } if (mob instanceof WitherSkeleton && tier >= 4 && AdaptiveAIGoalUtils.healthFraction(player) < 0.40D) { player.addEffect(new MobEffectInstance(MobEffects.WITHER, tier >= 5 ? 140 : 100, 0)); 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)); } if (mob instanceof CaveSpider && tier >= 3 && (AdaptiveAIGoalUtils.isUsingRangedWeapon(player) || player.isUsingItem())) { player.addEffect(new MobEffectInstance(MobEffects.POISON, 60, 0)); } if (mob instanceof Pillager && tier >= 5 && directEntity instanceof AbstractArrow arrow && arrow.getPersistentData().getBoolean("am_pinning_bolt")) { player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 30, 1)); disableShield(player); } if ((mob instanceof Vindicator || mob instanceof PiglinBrute) && tier >= 5) { event.setAmount(event.getAmount() + 2.0F); disableShield(player); } if ((mob instanceof Ravager || mob instanceof Hoglin) && tier >= 4 && ready(mob, "crushing_charge", 120)) { shove(player, mob.position(), mob instanceof Ravager ? 1.15D : 0.85D, 0.35D); if (player.horizontalCollision) { event.setAmount(event.getAmount() + 3.0F); } } if (mob instanceof Guardian && tier >= 4) { int focused = AdaptiveTargetingUtils.nearbyHostiles(mob, 12.0D, other -> other instanceof Guardian && other.getTarget() == player).size(); if (focused >= 2) { event.setAmount(event.getAmount() * (tier >= 5 ? 1.25F : 1.15F)); } if (mob instanceof ElderGuardian) { player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 50, 0)); player.setDeltaMovement(player.getDeltaMovement().add(0.0D, -0.12D, 0.0D)); } } if (mob instanceof Shulker && tier >= 4 && player.hasEffect(MobEffects.LEVITATION)) { player.addEffect(new MobEffectInstance(MobEffects.LEVITATION, 80, 0)); player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 50, 0)); } if ((mob instanceof Slime || mob instanceof MagmaCube) && tier >= 5 && mob.getBbWidth() <= 1.5F) { mob.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, 80, 0)); mob.addEffect(new MobEffectInstance(MobEffects.JUMP, 80, 1)); if (mob instanceof MagmaCube) { player.setSecondsOnFire(2); } } if (mob instanceof Phantom && tier >= 5) { player.addEffect(new MobEffectInstance(MobEffects.HUNGER, 100, 0)); } if (mob instanceof Vex && tier >= 5 && mob.noPhysics) { event.setAmount(event.getAmount() + 1.5F); player.addEffect(new MobEffectInstance(MobEffects.WEAKNESS, 50, 0)); } if (mob instanceof Endermite && tier >= 3) { for (Mob ally : AdaptiveTargetingUtils.nearbyHostiles(mob, 10.0D, other -> other instanceof Endermite || other instanceof EnderMan)) { ally.setTarget(player); } } if (mob.getPersistentData().getLong(COOLDOWN_PREFIX + "echo_mark") > level.getGameTime() && event.getSource().is(DamageTypes.SONIC_BOOM)) { player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 80, 1)); shove(player, mob.position(), 0.65D, 0.10D); } } private static int tier(ServerLevel level, Mob mob) { if (!AMConfig.isMobEnabled(mob.getType())) { return 0; } return AdaptiveDifficultyManager.getMobTier(level, mob.getType()); } private static boolean ready(Mob mob, String key, long cooldownTicks) { long now = mob.level().getGameTime(); String fullKey = COOLDOWN_PREFIX + key; if (mob.getPersistentData().getLong(fullKey) > now) { return false; } mob.getPersistentData().putLong(fullKey, now + cooldownTicks); return true; } private static void disableShield(ServerPlayer player) { if (AdaptiveAIGoalUtils.isBlockingShield(player)) { player.disableShield(true); } } private static void shove(LivingEntity target, Vec3 from, double horizontal, double vertical) { Vec3 away = target.position().subtract(from); double len = Math.max(0.001D, Math.sqrt(away.x * away.x + away.z * away.z)); target.setDeltaMovement(target.getDeltaMovement().add(away.x / len * horizontal, vertical, away.z / len * horizontal)); target.hurtMarked = true; } private static void placeTemporaryCobweb(ServerLevel level, BlockPos requestedPos, long expireTick) { BlockPos pos = level.getFluidState(requestedPos).is(FluidTags.WATER) ? requestedPos.above() : requestedPos; if (!level.getBlockState(pos).isAir() || !level.getWorldBorder().isWithinBounds(pos)) { return; } level.setBlock(pos, Blocks.COBWEB.defaultBlockState(), 3); TEMPORARY_BLOCKS.add(new TemporaryBlock(level.dimension(), pos.immutable(), expireTick)); level.playSound(null, pos, SoundEvents.SPIDER_AMBIENT, SoundSource.HOSTILE, 0.6F, 1.25F); } private static boolean eventWasRanged(Entity directEntity) { return directEntity instanceof Projectile; } private record TemporaryBlock(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) { } }