Initial commit
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
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<TemporaryBlock> TEMPORARY_BLOCKS = new ArrayList<>();
|
||||
private static final List<DelayedReappear> 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 && target != null && AdaptiveAIGoalUtils.isBlockingShield(target) && mob.getRandom().nextFloat() < 0.18F) {
|
||||
arrow.addEffect(new MobEffectInstance(MobEffects.WEAKNESS, 60, 0));
|
||||
} else if (tier >= 5 && target != null && 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<TemporaryBlock> 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<DelayedReappear> 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() && pending.target.isAlive()) {
|
||||
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) {
|
||||
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", 150)) {
|
||||
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) {
|
||||
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 ? 100 : 140)) {
|
||||
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<net.minecraft.world.level.Level> dimension,
|
||||
BlockPos pos, long expireTick) {
|
||||
}
|
||||
|
||||
private record DelayedReappear(net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> dimension,
|
||||
long tick, Silverfish mob, LivingEntity target, int tier) {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user