properly implement 2 mechanics
This commit is contained in:
+6
-3
@@ -103,13 +103,13 @@ Several hostile families also have identity-specific pressure tools. These use v
|
|||||||
- Guardians - Focused Beam: tier 4+ guardian beam damage increases slightly when multiple guardians are already focused on the same player.
|
- Guardians - Focused Beam: tier 4+ guardian beam damage increases slightly when multiple guardians are already focused on the same player.
|
||||||
- Elder guardians - Drowning Pressure: elder guardian beam hits add brief Slowness and a sinking pull.
|
- Elder guardians - Drowning Pressure: elder guardian beam hits add brief Slowness and a sinking pull.
|
||||||
- Shulkers - Gravity Chain: tier 4+ shulker hits against an already levitating player extend Levitation and apply brief Slowness.
|
- Shulkers - Gravity Chain: tier 4+ shulker hits against an already levitating player extend Levitation and apply brief Slowness.
|
||||||
- Slimes - Recombine: tier 5 small slimes gain short damage and jump pressure when they successfully hit, helping split slimes remain relevant.
|
- Slimes - Recombine: tier 5 equal-size Slimes that survive for 10-15 seconds seek one another within 12 blocks. On contact, one absorbs the other and doubles in size from 1 to 2 or from 2 to 4.
|
||||||
- Magma cubes - Molten Recombine: tier 5 small magma cubes use the same short damage and jump pressure as slimes, and briefly ignite the player on hit.
|
- Magma cubes - Molten Recombine: tier 5 Magma Cubes use the same delayed merge behavior, but only combine with equal-size Magma Cubes and never mix with ordinary Slimes.
|
||||||
- Piglins - Gold Frenzy: piglin-family Nether pressure is stronger at tier 5, while gold still remains valid counterplay rather than a hard immunity bypass.
|
- Piglins - Gold Frenzy: piglin-family Nether pressure is stronger at tier 5, while gold still remains valid counterplay rather than a hard immunity bypass.
|
||||||
- Piglin brutes - Guard Break: tier 5 brute hits add a small armor-piercing damage component and briefly disable shields.
|
- Piglin brutes - Guard Break: tier 5 brute hits add a small armor-piercing damage component and briefly disable shields.
|
||||||
- Hoglins - Gore Toss: tier 4+ hoglin hits can toss the player upward and backward on cooldown.
|
- Hoglins - Gore Toss: tier 4+ hoglin hits can toss the player upward and backward on cooldown.
|
||||||
- Zoglins - Frenzied Rampage: tier 5 low-health zoglins gain a short Speed and Resistance burst.
|
- Zoglins - Frenzied Rampage: tier 5 low-health zoglins gain a short Speed and Resistance burst.
|
||||||
- Ghasts - Splitting Fireball: treated as future work; current tier 5 ghasts already use stronger target prediction through airborne positioning and projectile aim adjustment.
|
- Ghasts - Splitting Fireball: every tier 5 Ghast fireball launches two additional fireballs at shallow side angles while preserving the original center shot. Split projectiles are marked so they cannot recursively split again.
|
||||||
- Blazes - Overheat Volley: tier 5 blazes that take ranged damage briefly gain Speed and Strength pressure to punish static bow farming.
|
- Blazes - Overheat Volley: tier 5 blazes that take ranged damage briefly gain Speed and Strength pressure to punish static bow farming.
|
||||||
- Phantoms - Bleeding Dive: tier 5 phantom hits apply brief Hunger as a vanilla bleeding-style damage-over-time pressure.
|
- Phantoms - Bleeding Dive: tier 5 phantom hits apply brief Hunger as a vanilla bleeding-style damage-over-time pressure.
|
||||||
- Vexes - Phase Cut: tier 5 vexes that hit while phasing gain bonus hit pressure and apply brief Weakness.
|
- Vexes - Phase Cut: tier 5 vexes that hit while phasing gain bonus hit pressure and apply brief Weakness.
|
||||||
@@ -363,6 +363,7 @@ AI changes:
|
|||||||
- Tier 2+ angled jump pressure instead of only direct movement.
|
- Tier 2+ angled jump pressure instead of only direct movement.
|
||||||
- Side positioning based on entity id so repeated slimes naturally spread.
|
- Side positioning based on entity id so repeated slimes naturally spread.
|
||||||
- Larger tier 5 slimes move and leap more aggressively.
|
- Larger tier 5 slimes move and leap more aggressively.
|
||||||
|
- Tier 5 equal-size Slimes become eligible to recombine after surviving for 10-15 seconds, seek a compatible partner within 12 blocks, and merge on contact. Two size-1 Slimes become size 2; two size-2 Slimes become size 4. Size-4 Slimes do not grow further.
|
||||||
- Adaptive melee positioning.
|
- Adaptive melee positioning.
|
||||||
- Tier 4+ shared targeting.
|
- Tier 4+ shared targeting.
|
||||||
|
|
||||||
@@ -376,6 +377,7 @@ Magma cubes receive shared scaling and spawn pressure through the generic hostil
|
|||||||
AI changes:
|
AI changes:
|
||||||
|
|
||||||
- The same angled jump pressure as slimes, controlled by magma-cube advanced AI.
|
- The same angled jump pressure as slimes, controlled by magma-cube advanced AI.
|
||||||
|
- Tier 5 Magma Cubes use the same 10-15 second recombination cycle and 1→2→4 size progression, but only with other Magma Cubes of exactly the same size.
|
||||||
- Adaptive melee positioning.
|
- Adaptive melee positioning.
|
||||||
- Nether biome tactics make them bolder in the Nether.
|
- Nether biome tactics make them bolder in the Nether.
|
||||||
- Tier 4+ shared targeting.
|
- Tier 4+ shared targeting.
|
||||||
@@ -407,6 +409,7 @@ AI changes:
|
|||||||
- Airborne repositioning around targets.
|
- Airborne repositioning around targets.
|
||||||
- Larger vertical offset than other airborne mobs.
|
- Larger vertical offset than other airborne mobs.
|
||||||
- Tier 5 target prediction for airborne positioning.
|
- Tier 5 target prediction for airborne positioning.
|
||||||
|
- Tier 5 Splitting Fireball adds two shallow-angle side projectiles to each normal center fireball. The side shots retain the Ghast as owner and use its normal explosion power.
|
||||||
- Nether biome tactics identify them as Nether mobs where applicable.
|
- Nether biome tactics identify them as Nether mobs where applicable.
|
||||||
- Tier 4+ shared targeting.
|
- Tier 4+ shared targeting.
|
||||||
|
|
||||||
|
|||||||
@@ -25,13 +25,12 @@ import net.minecraft.world.entity.monster.EnderMan;
|
|||||||
import net.minecraft.world.entity.monster.Endermite;
|
import net.minecraft.world.entity.monster.Endermite;
|
||||||
import net.minecraft.world.entity.monster.Evoker;
|
import net.minecraft.world.entity.monster.Evoker;
|
||||||
import net.minecraft.world.entity.monster.Guardian;
|
import net.minecraft.world.entity.monster.Guardian;
|
||||||
import net.minecraft.world.entity.monster.MagmaCube;
|
import net.minecraft.world.entity.monster.Ghast;
|
||||||
import net.minecraft.world.entity.monster.Phantom;
|
import net.minecraft.world.entity.monster.Phantom;
|
||||||
import net.minecraft.world.entity.monster.Pillager;
|
import net.minecraft.world.entity.monster.Pillager;
|
||||||
import net.minecraft.world.entity.monster.Ravager;
|
import net.minecraft.world.entity.monster.Ravager;
|
||||||
import net.minecraft.world.entity.monster.Shulker;
|
import net.minecraft.world.entity.monster.Shulker;
|
||||||
import net.minecraft.world.entity.monster.Silverfish;
|
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.Spider;
|
||||||
import net.minecraft.world.entity.monster.Stray;
|
import net.minecraft.world.entity.monster.Stray;
|
||||||
import net.minecraft.world.entity.monster.Vex;
|
import net.minecraft.world.entity.monster.Vex;
|
||||||
@@ -44,6 +43,7 @@ import net.minecraft.world.entity.monster.piglin.PiglinBrute;
|
|||||||
import net.minecraft.world.entity.monster.warden.Warden;
|
import net.minecraft.world.entity.monster.warden.Warden;
|
||||||
import net.minecraft.world.entity.projectile.AbstractArrow;
|
import net.minecraft.world.entity.projectile.AbstractArrow;
|
||||||
import net.minecraft.world.entity.projectile.Arrow;
|
import net.minecraft.world.entity.projectile.Arrow;
|
||||||
|
import net.minecraft.world.entity.projectile.LargeFireball;
|
||||||
import net.minecraft.world.entity.projectile.Projectile;
|
import net.minecraft.world.entity.projectile.Projectile;
|
||||||
import net.minecraft.world.entity.projectile.ShulkerBullet;
|
import net.minecraft.world.entity.projectile.ShulkerBullet;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.world.entity.player.Player;
|
||||||
@@ -60,6 +60,7 @@ import java.util.List;
|
|||||||
public final class AdaptiveSpecialAbilities {
|
public final class AdaptiveSpecialAbilities {
|
||||||
|
|
||||||
private static final String COOLDOWN_PREFIX = "am_special_cd_";
|
private static final String COOLDOWN_PREFIX = "am_special_cd_";
|
||||||
|
private static final String SPLIT_FIREBALL_KEY = "am_split_fireball";
|
||||||
private static final List<TemporaryBlock> TEMPORARY_BLOCKS = new ArrayList<>();
|
private static final List<TemporaryBlock> TEMPORARY_BLOCKS = new ArrayList<>();
|
||||||
private static final List<DelayedReappear> DELAYED_REAPPEARS = new ArrayList<>();
|
private static final List<DelayedReappear> DELAYED_REAPPEARS = new ArrayList<>();
|
||||||
|
|
||||||
@@ -75,6 +76,10 @@ public final class AdaptiveSpecialAbilities {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int tier = tier(level, mob);
|
int tier = tier(level, mob);
|
||||||
|
if (tier >= 5 && mob instanceof Ghast ghast && projectile instanceof LargeFireball fireball) {
|
||||||
|
splitGhastFireball(level, ghast, fireball);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (tier < 4 || !(projectile instanceof Arrow arrow)) {
|
if (tier < 4 || !(projectile instanceof Arrow arrow)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -267,13 +272,6 @@ public final class AdaptiveSpecialAbilities {
|
|||||||
player.addEffect(new MobEffectInstance(MobEffects.LEVITATION, 80, 0));
|
player.addEffect(new MobEffectInstance(MobEffects.LEVITATION, 80, 0));
|
||||||
player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 50, 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) {
|
if (mob instanceof Phantom && tier >= 5) {
|
||||||
player.addEffect(new MobEffectInstance(MobEffects.HUNGER, 100, 0));
|
player.addEffect(new MobEffectInstance(MobEffects.HUNGER, 100, 0));
|
||||||
}
|
}
|
||||||
@@ -338,6 +336,37 @@ public final class AdaptiveSpecialAbilities {
|
|||||||
return directEntity instanceof Projectile;
|
return directEntity instanceof Projectile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void splitGhastFireball(ServerLevel level, Ghast ghast, LargeFireball original) {
|
||||||
|
if (original.getPersistentData().getBoolean(SPLIT_FIREBALL_KEY)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Vec3 direction = original.getDeltaMovement();
|
||||||
|
if (direction.lengthSqr() < 1.0E-4D && AdaptiveAIGoalUtils.isValidAdaptiveTarget(ghast.getTarget())) {
|
||||||
|
direction = ghast.getTarget().getEyePosition().subtract(original.position());
|
||||||
|
}
|
||||||
|
if (direction.lengthSqr() < 1.0E-4D) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
direction = direction.normalize();
|
||||||
|
Vec3 side = new Vec3(-direction.z, 0.0D, direction.x);
|
||||||
|
if (side.lengthSqr() < 1.0E-4D) {
|
||||||
|
side = new Vec3(1.0D, 0.0D, 0.0D);
|
||||||
|
} else {
|
||||||
|
side = side.normalize();
|
||||||
|
}
|
||||||
|
spawnSplitFireball(level, ghast, original, direction.add(side.scale(0.18D)).normalize());
|
||||||
|
spawnSplitFireball(level, ghast, original, direction.subtract(side.scale(0.18D)).normalize());
|
||||||
|
AdaptiveAIGoalUtils.debug(ghast, () -> 5, "ghast splitting fireball");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void spawnSplitFireball(ServerLevel level, Ghast ghast, LargeFireball original, Vec3 direction) {
|
||||||
|
LargeFireball split = new LargeFireball(level, ghast, direction.x, direction.y, direction.z,
|
||||||
|
ghast.getExplosionPower());
|
||||||
|
split.getPersistentData().putBoolean(SPLIT_FIREBALL_KEY, true);
|
||||||
|
split.setPos(original.getX(), original.getY(), original.getZ());
|
||||||
|
level.addFreshEntity(split);
|
||||||
|
}
|
||||||
|
|
||||||
private record TemporaryBlock(net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> dimension,
|
private record TemporaryBlock(net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> dimension,
|
||||||
BlockPos pos, long expireTick) {
|
BlockPos pos, long expireTick) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,39 +9,63 @@ import net.minecraft.world.entity.monster.MagmaCube;
|
|||||||
import net.minecraft.world.entity.monster.Slime;
|
import net.minecraft.world.entity.monster.Slime;
|
||||||
import net.minecraft.world.phys.Vec3;
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.IntSupplier;
|
import java.util.function.IntSupplier;
|
||||||
|
|
||||||
public class AdaptiveSlimeTacticsGoal extends Goal {
|
public class AdaptiveSlimeTacticsGoal extends Goal {
|
||||||
|
|
||||||
|
private static final String RECOMBINE_READY_AT_KEY = "am_recombine_ready_at";
|
||||||
|
private static final int MAX_RECOMBINE_SIZE = 4;
|
||||||
|
private static final double RECOMBINE_SEARCH_RADIUS = 12.0D;
|
||||||
|
private static final double RECOMBINE_DISTANCE_SQR = 3.24D;
|
||||||
|
|
||||||
private final Slime slime;
|
private final Slime slime;
|
||||||
private final IntSupplier tierSupplier;
|
private final IntSupplier tierSupplier;
|
||||||
private int cooldown;
|
private int cooldown;
|
||||||
|
private int recombineCheckCooldown;
|
||||||
|
private Slime recombinePartner;
|
||||||
|
|
||||||
public AdaptiveSlimeTacticsGoal(Slime slime, IntSupplier tierSupplier) {
|
public AdaptiveSlimeTacticsGoal(Slime slime, IntSupplier tierSupplier) {
|
||||||
this.slime = slime;
|
this.slime = slime;
|
||||||
this.tierSupplier = tierSupplier;
|
this.tierSupplier = tierSupplier;
|
||||||
setFlags(EnumSet.of(Flag.MOVE, Flag.JUMP, Flag.LOOK));
|
setFlags(EnumSet.of(Flag.MOVE, Flag.JUMP, Flag.LOOK));
|
||||||
|
scheduleRecombine(200 + slime.getRandom().nextInt(101));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canUse() {
|
public boolean canUse() {
|
||||||
boolean magma = slime instanceof MagmaCube;
|
boolean magma = slime instanceof MagmaCube;
|
||||||
return AMConfig.ENABLE_MELEE_AI.get()
|
if (!AMConfig.ENABLE_MELEE_AI.get()
|
||||||
&& (magma ? AMConfig.ENABLE_MAGMA_CUBE_ADVANCED_AI.get() : AMConfig.ENABLE_SLIME_ADVANCED_AI.get())
|
|| !(magma ? AMConfig.ENABLE_MAGMA_CUBE_ADVANCED_AI.get() : AMConfig.ENABLE_SLIME_ADVANCED_AI.get())
|
||||||
&& AMConfig.AI_SLIME.get()
|
|| !AMConfig.AI_SLIME.get() || tierSupplier.getAsInt() < 2) {
|
||||||
&& tierSupplier.getAsInt() >= 2
|
return false;
|
||||||
&& slime.getTarget() != null
|
}
|
||||||
&& slime.getTarget().isAlive()
|
recombinePartner = null;
|
||||||
&& --cooldown <= 0;
|
if (tierSupplier.getAsInt() >= 5 && slime.getSize() < MAX_RECOMBINE_SIZE
|
||||||
|
&& slime.level().getGameTime() >= slime.getPersistentData().getLong(RECOMBINE_READY_AT_KEY)
|
||||||
|
&& --recombineCheckCooldown <= 0) {
|
||||||
|
recombineCheckCooldown = 10;
|
||||||
|
recombinePartner = findRecombinePartner();
|
||||||
|
if (recombinePartner != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AdaptiveAIGoalUtils.isValidAdaptiveTarget(slime.getTarget()) && --cooldown <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
int tier = tierSupplier.getAsInt();
|
int tier = tierSupplier.getAsInt();
|
||||||
|
if (recombinePartner != null && recombinePartner.isAlive()) {
|
||||||
|
tryRecombine(recombinePartner);
|
||||||
|
recombinePartner = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
cooldown = Math.max(16, 52 - tier * 5) + slime.getRandom().nextInt(25);
|
cooldown = Math.max(16, 52 - tier * 5) + slime.getRandom().nextInt(25);
|
||||||
LivingEntity target = slime.getTarget();
|
LivingEntity target = slime.getTarget();
|
||||||
if (target == null) {
|
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
double distance = Math.sqrt(slime.distanceToSqr(target));
|
double distance = Math.sqrt(slime.distanceToSqr(target));
|
||||||
@@ -59,4 +83,40 @@ public class AdaptiveSlimeTacticsGoal extends Goal {
|
|||||||
}
|
}
|
||||||
AdaptiveAIGoalUtils.debug(slime, tierSupplier, "slime angled jump pressure");
|
AdaptiveAIGoalUtils.debug(slime, tierSupplier, "slime angled jump pressure");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Slime findRecombinePartner() {
|
||||||
|
boolean magma = slime instanceof MagmaCube;
|
||||||
|
List<Slime> candidates = slime.level().getEntitiesOfClass(Slime.class,
|
||||||
|
slime.getBoundingBox().inflate(RECOMBINE_SEARCH_RADIUS),
|
||||||
|
other -> other != slime && other.isAlive() && other.getSize() == slime.getSize()
|
||||||
|
&& (other instanceof MagmaCube) == magma
|
||||||
|
&& other.level().getGameTime() >= other.getPersistentData().getLong(RECOMBINE_READY_AT_KEY));
|
||||||
|
return candidates.stream().min(Comparator.comparingDouble(slime::distanceToSqr)).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void 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;
|
||||||
|
}
|
||||||
|
LivingEntity inheritedTarget = AdaptiveAIGoalUtils.isValidAdaptiveTarget(slime.getTarget())
|
||||||
|
? slime.getTarget() : partner.getTarget();
|
||||||
|
partner.discard();
|
||||||
|
slime.setSize(Math.min(MAX_RECOMBINE_SIZE, slime.getSize() * 2), true);
|
||||||
|
if (AdaptiveAIGoalUtils.isValidAdaptiveTarget(inheritedTarget)) {
|
||||||
|
slime.setTarget(inheritedTarget);
|
||||||
|
}
|
||||||
|
scheduleRecombine(200 + slime.getRandom().nextInt(101));
|
||||||
|
AdaptiveAIGoalUtils.debug(slime, tierSupplier,
|
||||||
|
slime instanceof MagmaCube ? "magma cube recombined" : "slime recombined");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleRecombine(int delayTicks) {
|
||||||
|
if (slime.getPersistentData().getLong(RECOMBINE_READY_AT_KEY) == 0L) {
|
||||||
|
slime.getPersistentData().putLong(RECOMBINE_READY_AT_KEY, slime.level().getGameTime() + delayTicks);
|
||||||
|
} else if (slime.getSize() > 1) {
|
||||||
|
slime.getPersistentData().putLong(RECOMBINE_READY_AT_KEY, slime.level().getGameTime() + delayTicks);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user