fix various issues
This commit is contained in:
+2
-1
@@ -95,7 +95,8 @@ minecraft {
|
||||
// By default, the server will crash when no gametests are provided.
|
||||
// The gametest system is also enabled by default for other run configs under the /test command.
|
||||
gameTestServer {
|
||||
property 'forge.enabledGameTestNamespaces', mod_id
|
||||
// The tests use a small vanilla structure template, so its namespace must also be enabled.
|
||||
property 'forge.enabledGameTestNamespaces', "${mod_id},minecraft"
|
||||
}
|
||||
|
||||
data {
|
||||
|
||||
@@ -27,6 +27,7 @@ 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.Ghast;
|
||||
import net.minecraft.world.entity.monster.Phantom;
|
||||
import net.minecraft.world.entity.monster.Pillager;
|
||||
@@ -55,6 +56,7 @@ 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.event.level.ExplosionEvent;
|
||||
import net.minecraftforge.common.util.BlockSnapshot;
|
||||
import net.minecraftforge.event.ForgeEventFactory;
|
||||
|
||||
@@ -67,6 +69,7 @@ public final class AdaptiveSpecialAbilities {
|
||||
|
||||
private static final String COOLDOWN_PREFIX = "am_special_cd_";
|
||||
private static final String SPLIT_FIREBALL_KEY = "am_split_fireball";
|
||||
private static final double SPLIT_FIREBALL_SPACING = 1.35D;
|
||||
private static final List<DelayedReappear> DELAYED_REAPPEARS = new ArrayList<>();
|
||||
|
||||
private AdaptiveSpecialAbilities() {
|
||||
@@ -241,6 +244,10 @@ public final class AdaptiveSpecialAbilities {
|
||||
if (tier <= 0 || !AdaptiveAIGoalUtils.isValidAdaptiveTarget(player)) {
|
||||
return;
|
||||
}
|
||||
if (mob instanceof MagmaCube && tier >= 3 && mob.getRandom().nextFloat() < 0.50F) {
|
||||
player.setSecondsOnFire(4);
|
||||
AdaptiveAIGoalUtils.debug(mob, () -> tier, "magma cube ignited target");
|
||||
}
|
||||
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));
|
||||
@@ -363,6 +370,7 @@ public final class AdaptiveSpecialAbilities {
|
||||
if (original.getPersistentData().getBoolean(SPLIT_FIREBALL_KEY)) {
|
||||
return;
|
||||
}
|
||||
original.getPersistentData().putBoolean(SPLIT_FIREBALL_KEY, true);
|
||||
Vec3 direction = original.getDeltaMovement();
|
||||
if (direction.lengthSqr() < 1.0E-4D && AdaptiveAIGoalUtils.isValidAdaptiveTarget(ghast.getTarget())) {
|
||||
direction = ghast.getTarget().getEyePosition().subtract(original.position());
|
||||
@@ -377,19 +385,28 @@ public final class AdaptiveSpecialAbilities {
|
||||
} 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());
|
||||
spawnSplitFireball(level, ghast, original, direction.add(side.scale(0.24D)).normalize(),
|
||||
side.scale(SPLIT_FIREBALL_SPACING));
|
||||
spawnSplitFireball(level, ghast, original, direction.subtract(side.scale(0.24D)).normalize(),
|
||||
side.scale(-SPLIT_FIREBALL_SPACING));
|
||||
AdaptiveAIGoalUtils.debug(ghast, () -> 5, "ghast splitting fireball");
|
||||
}
|
||||
|
||||
private static void spawnSplitFireball(ServerLevel level, Ghast ghast, LargeFireball original, Vec3 direction) {
|
||||
private static void spawnSplitFireball(ServerLevel level, Ghast ghast, LargeFireball original,
|
||||
Vec3 direction, Vec3 offset) {
|
||||
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());
|
||||
Vec3 position = original.position().add(offset).add(direction.scale(0.35D));
|
||||
split.setPos(position.x, position.y, position.z);
|
||||
level.addFreshEntity(split);
|
||||
}
|
||||
|
||||
public static void protectSplitFireballVolley(ExplosionEvent.Detonate event) {
|
||||
event.getAffectedEntities().removeIf(entity -> entity instanceof LargeFireball fireball
|
||||
&& fireball.getPersistentData().getBoolean(SPLIT_FIREBALL_KEY));
|
||||
}
|
||||
|
||||
public static void invalidateTemporaryBlock(ServerLevel level, BlockPos pos) {
|
||||
TemporaryBlockSavedData.get(level).remove(level, pos);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,14 @@ import java.util.function.IntSupplier;
|
||||
public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
|
||||
private static final double TRAP_VEHICLE_BREAK_DISTANCE_SQR = 9.0D;
|
||||
private static final double ZOMBIE_TOWER_COORDINATION_RADIUS = 12.0D;
|
||||
// A tight radius lets body collision create a permanent crowd around the selected tower.
|
||||
// Four blocks is still local, but allows the mount handoff before the crowd deadlocks.
|
||||
private static final double ZOMBIE_MOUNT_DISTANCE_SQR = 16.0D;
|
||||
private static final double ESTABLISHED_TOWER_MOUNT_DISTANCE_SQR =
|
||||
ZOMBIE_TOWER_COORDINATION_RADIUS * ZOMBIE_TOWER_COORDINATION_RADIUS;
|
||||
private static final String ZOMBIE_CLIMBER_UNTIL_KEY = "am_zombie_climber_until";
|
||||
private static final String LADDER_CLIMBER_UNTIL_KEY = "am_ladder_climber_until";
|
||||
|
||||
private final PathfinderMob mob;
|
||||
private final IntSupplier tierSupplier;
|
||||
@@ -58,6 +66,13 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return false;
|
||||
}
|
||||
if (mob instanceof Zombie && mob.isPassenger() && AdaptiveAIGoalUtils.isAdaptiveStackedZombie(mob)) {
|
||||
return true;
|
||||
}
|
||||
if (mob instanceof Zombie
|
||||
&& mob.getPersistentData().getLong(ZOMBIE_CLIMBER_UNTIL_KEY) > mob.level().getGameTime()) {
|
||||
return false;
|
||||
}
|
||||
if (isInTrapVehicle() || nearestTrapBlock() != null || trappedAllyVehicle() != null) {
|
||||
return true;
|
||||
}
|
||||
@@ -92,18 +107,24 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
cooldown = 10 + mob.getRandom().nextInt(20);
|
||||
return;
|
||||
}
|
||||
if (mob instanceof Zombie && mob.isPassenger() && shouldUnstack(target)) {
|
||||
mob.stopRiding();
|
||||
AdaptiveAIGoalUtils.clearAdaptiveStackedZombie(mob);
|
||||
cooldown = 12 + mob.getRandom().nextInt(18);
|
||||
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "zombie unstacking");
|
||||
return;
|
||||
}
|
||||
if (!AdaptiveAIGoalUtils.isValidAdaptiveTarget(target)) {
|
||||
return;
|
||||
}
|
||||
mob.getLookControl().setLookAt(target, 30.0F, 30.0F);
|
||||
|
||||
if (mob instanceof Zombie && mob.isPassenger() && AdaptiveAIGoalUtils.isAdaptiveStackedZombie(mob)) {
|
||||
activePillarTarget = target;
|
||||
activePillarTicks = 40;
|
||||
if (shouldUnstack(target)) {
|
||||
mob.stopRiding();
|
||||
AdaptiveAIGoalUtils.clearAdaptiveStackedZombie(mob);
|
||||
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "zombie ladder released after target fell");
|
||||
} else {
|
||||
tryCorpseLadderPressure(target);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mob instanceof Zombie && isVerticalObstacleCheese(target)) {
|
||||
activePillarTarget = target;
|
||||
activePillarTicks = 24 + mob.getRandom().nextInt(16);
|
||||
@@ -181,7 +202,8 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
return activePillarTicks > 0
|
||||
&& mob instanceof Zombie
|
||||
&& AdaptiveAIGoalUtils.isValidAdaptiveTarget(activePillarTarget)
|
||||
&& isVerticalObstacleCheese(activePillarTarget);
|
||||
&& ((mob.isPassenger() && AdaptiveAIGoalUtils.isAdaptiveStackedZombie(mob))
|
||||
|| isVerticalObstacleCheese(activePillarTarget));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -289,25 +311,42 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
if (mob.isPassenger() || mob.isVehicle()) {
|
||||
return false;
|
||||
}
|
||||
List<Zombie> carriers = mob.level().getEntitiesOfClass(Zombie.class, mob.getBoundingBox().inflate(2.0D),
|
||||
List<Zombie> carriers = mob.level().getEntitiesOfClass(Zombie.class,
|
||||
mob.getBoundingBox().inflate(ZOMBIE_TOWER_COORDINATION_RADIUS),
|
||||
zombie -> zombie != mob
|
||||
&& zombie.isAlive()
|
||||
&& !zombie.isVehicle()
|
||||
&& zombie.getTarget() == target
|
||||
&& zombie.getY() < target.getY()
|
||||
&& !isInVehicleChain(zombie, mob)
|
||||
&& Math.abs(zombie.getY() - mob.getY()) < 8.0D);
|
||||
Zombie bestCarrier = null;
|
||||
double best = -1.0D;
|
||||
int bestHeight = -1;
|
||||
int bestBaseId = Integer.MAX_VALUE;
|
||||
double bestDistance = Double.MAX_VALUE;
|
||||
for (Zombie carrier : carriers) {
|
||||
double score = stackHeight(carrier) * 10.0D - mob.distanceToSqr(carrier);
|
||||
if (score > best) {
|
||||
best = score;
|
||||
int height = stackHeight(carrier);
|
||||
int baseId = stackBase(carrier).getId();
|
||||
double distance = mob.distanceToSqr(carrier);
|
||||
if (height > bestHeight
|
||||
|| (height == bestHeight && baseId < bestBaseId)
|
||||
|| (height == bestHeight && baseId == bestBaseId && distance < bestDistance)) {
|
||||
bestHeight = height;
|
||||
bestBaseId = baseId;
|
||||
bestDistance = distance;
|
||||
bestCarrier = carrier;
|
||||
}
|
||||
}
|
||||
if (bestCarrier == null) {
|
||||
return false;
|
||||
}
|
||||
double mountDistanceSqr = bestHeight > 1
|
||||
? ESTABLISHED_TOWER_MOUNT_DISTANCE_SQR : ZOMBIE_MOUNT_DISTANCE_SQR;
|
||||
if (bestDistance > mountDistanceSqr) {
|
||||
mob.getNavigation().moveTo(bestCarrier, 1.1D);
|
||||
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "zombie converging on existing tower");
|
||||
return true;
|
||||
}
|
||||
boolean mounted = mob.startRiding(bestCarrier, true);
|
||||
if (mounted) {
|
||||
AdaptiveAIGoalUtils.markAdaptiveStackedZombie(mob);
|
||||
@@ -329,7 +368,28 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
double vertical = target.getY() - mob.getY();
|
||||
double horizontal = Math.sqrt((target.getX() - mob.getX()) * (target.getX() - mob.getX())
|
||||
+ (target.getZ() - mob.getZ()) * (target.getZ() - mob.getZ()));
|
||||
if (horizontal > 3.2D || vertical < 0.25D || vertical > 5.5D) {
|
||||
if (mob.isVehicle()) {
|
||||
return true;
|
||||
}
|
||||
if (horizontal <= 3.2D && vertical <= 1.0D) {
|
||||
long now = mob.level().getGameTime();
|
||||
if (base.getPersistentData().getLong(LADDER_CLIMBER_UNTIL_KEY) > now) {
|
||||
return true;
|
||||
}
|
||||
base.getPersistentData().putLong(LADDER_CLIMBER_UNTIL_KEY, now + 60L);
|
||||
mob.stopRiding();
|
||||
AdaptiveAIGoalUtils.clearAdaptiveStackedZombie(mob);
|
||||
mob.getPersistentData().putLong(ZOMBIE_CLIMBER_UNTIL_KEY, now + 80L);
|
||||
Vec3 toward = target.position().subtract(mob.position());
|
||||
double len = Math.max(0.001D, Math.sqrt(toward.x * toward.x + toward.z * toward.z));
|
||||
mob.setDeltaMovement(toward.x / len * 0.30D, 0.34D, toward.z / len * 0.30D);
|
||||
mob.hurtMarked = true;
|
||||
mob.getNavigation().moveTo(target, 1.2D);
|
||||
activePillarTicks = 0;
|
||||
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "top zombie launched from ladder");
|
||||
return true;
|
||||
}
|
||||
if (horizontal > 3.2D || vertical > 5.5D) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -339,10 +399,6 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
mob.setDeltaMovement(mob.getDeltaMovement().add(toward.x / len * 0.18D, lift, toward.z / len * 0.18D));
|
||||
mob.hurtMarked = true;
|
||||
|
||||
if (vertical <= 1.8D && horizontal <= 2.4D) {
|
||||
mob.stopRiding();
|
||||
AdaptiveAIGoalUtils.clearAdaptiveStackedZombie(mob);
|
||||
}
|
||||
AdaptiveAIGoalUtils.debug(mob, tierSupplier, "zombie corpse ladder pressure");
|
||||
return true;
|
||||
}
|
||||
@@ -368,19 +424,18 @@ public class AdaptiveAntiCheeseGoal extends Goal {
|
||||
AdaptiveAIGoalUtils.clearAdaptiveStackedZombie(mob);
|
||||
return true;
|
||||
}
|
||||
if (mob.distanceToSqr(target) > 8.0D * 8.0D) {
|
||||
AdaptiveAIGoalUtils.clearAdaptiveStackedZombie(mob);
|
||||
return true;
|
||||
}
|
||||
Entity base = stackBase(mob);
|
||||
double verticalFromBase = target.getY() - base.getY();
|
||||
double horizontalFromBase = Math.sqrt((target.getX() - base.getX()) * (target.getX() - base.getX())
|
||||
+ (target.getZ() - base.getZ()) * (target.getZ() - base.getZ()));
|
||||
if (verticalFromBase > 2.5D && horizontalFromBase < 5.5D) {
|
||||
// Preserve the ladder while its base approaches an elevated target. Rider-to-target
|
||||
// distance includes tower height and caused tall, otherwise valid ladders to oscillate.
|
||||
if (verticalFromBase > 2.5D
|
||||
&& horizontalFromBase <= ZOMBIE_TOWER_COORDINATION_RADIUS + 4.0D) {
|
||||
return false;
|
||||
}
|
||||
AdaptiveAIGoalUtils.clearAdaptiveStackedZombie(mob);
|
||||
return !isVerticalObstacleCheese(target);
|
||||
return true;
|
||||
}
|
||||
|
||||
private int stackHeight(Entity entity) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.monster.Enemy;
|
||||
import net.minecraft.world.entity.projectile.Projectile;
|
||||
import net.minecraft.tags.DamageTypeTags;
|
||||
import net.minecraftforge.event.RegisterCommandsEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingEntityUseItemEvent;
|
||||
import net.minecraftforge.event.entity.EntityJoinLevelEvent;
|
||||
@@ -57,6 +58,17 @@ public class AdaptiveMobsEvents {
|
||||
|
||||
@SubscribeEvent
|
||||
public void onLivingAttack(LivingAttackEvent event) {
|
||||
if (event.getEntity() instanceof ServerPlayer player
|
||||
&& event.getSource().getEntity() instanceof Mob explosiveMob
|
||||
&& event.getSource().is(DamageTypeTags.IS_EXPLOSION)
|
||||
&& player.level() instanceof ServerLevel level
|
||||
&& AMConfig.enabled && AMConfig.AI_ENABLED.get()
|
||||
&& AdaptiveGoalInjector.isAIEnabledFor(explosiveMob)
|
||||
&& AdaptiveDifficultyManager.getMobTier(level, explosiveMob.getType()) >= 5
|
||||
&& AdaptiveAIGoalUtils.isBlockingShield(player)) {
|
||||
player.disableShield(true);
|
||||
AdaptiveAIGoalUtils.debug(explosiveMob, () -> 5, "mob explosion disabled shield");
|
||||
}
|
||||
if (!AMConfig.enabled || !AMConfig.AI_ENABLED.get()
|
||||
|| !(event.getEntity() instanceof Mob victim) || !(victim instanceof Enemy)
|
||||
|| !(event.getSource().getDirectEntity() instanceof Projectile)
|
||||
@@ -182,6 +194,7 @@ public class AdaptiveMobsEvents {
|
||||
|
||||
@SubscribeEvent
|
||||
public void onExplosion(ExplosionEvent.Detonate event) {
|
||||
AdaptiveSpecialAbilities.protectSplitFireballVolley(event);
|
||||
if (AMConfig.enabled && AMConfig.ENABLE_SOUND_INVESTIGATION_AI.get()
|
||||
&& event.getLevel() instanceof ServerLevel level) {
|
||||
AdaptiveMemory.rememberSound(level, event.getExplosion().getPosition(), AdaptiveMemory.SoundKind.EXPLOSION, 20 * 10);
|
||||
|
||||
@@ -9,15 +9,17 @@ import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.monster.Slime;
|
||||
import net.minecraftforge.gametest.GameTestHolder;
|
||||
import net.minecraftforge.gametest.PrefixGameTestTemplate;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
@GameTestHolder("adaptivemobs")
|
||||
@PrefixGameTestTemplate(false)
|
||||
public final class AdaptiveServerBehaviorGameTests {
|
||||
private AdaptiveServerBehaviorGameTests() {
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
@GameTest(templateNamespace = "minecraft", template = "igloo/top")
|
||||
public static void temporaryBlocksSurviveSavedDataReload(GameTestHelper helper) {
|
||||
TemporaryBlockSavedData original = new TemporaryBlockSavedData();
|
||||
BlockPos pos = helper.absolutePos(new BlockPos(1, 1, 1));
|
||||
@@ -35,12 +37,15 @@ public final class AdaptiveServerBehaviorGameTests {
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
@GameTest(templateNamespace = "minecraft", template = "igloo/top")
|
||||
public static void slimeRecombinationCancelsWhenStuck(GameTestHelper helper) {
|
||||
Slime slime = helper.spawn(EntityType.SLIME, new BlockPos(1, 2, 1));
|
||||
Slime partner = helper.spawn(EntityType.SLIME, new BlockPos(8, 2, 1));
|
||||
slime.setSize(1, true);
|
||||
partner.setSize(1, true);
|
||||
long now = helper.getLevel().getGameTime();
|
||||
slime.getPersistentData().putLong("am_recombine_ready_at", now);
|
||||
partner.getPersistentData().putLong("am_recombine_ready_at", now);
|
||||
AdaptiveSlimeTacticsGoal goal = new AdaptiveSlimeTacticsGoal(slime, () -> 5);
|
||||
|
||||
helper.assertTrue(goal.canUse(), "slime did not select its eligible partner");
|
||||
|
||||
Reference in New Issue
Block a user