fix various issues
This commit is contained in:
@@ -297,7 +297,10 @@ public final class AdaptiveGoalInjector {
|
|||||||
|| mob instanceof Hoglin || mob instanceof Zoglin) {
|
|| mob instanceof Hoglin || mob instanceof Zoglin) {
|
||||||
return AMConfig.ENABLE_NETHER_MOB_AI.get() && AMConfig.AI_PIGLIN_FAMILY.get();
|
return AMConfig.ENABLE_NETHER_MOB_AI.get() && AMConfig.AI_PIGLIN_FAMILY.get();
|
||||||
}
|
}
|
||||||
if (mob instanceof Vindicator || mob instanceof Ravager) {
|
if (mob instanceof Vindicator) {
|
||||||
|
return AMConfig.ENABLE_RAID_MOB_AI.get() && AMConfig.AI_VINDICATOR.get();
|
||||||
|
}
|
||||||
|
if (mob instanceof Ravager) {
|
||||||
return AMConfig.ENABLE_RAID_MOB_AI.get() && AMConfig.AI_RAVAGER.get();
|
return AMConfig.ENABLE_RAID_MOB_AI.get() && AMConfig.AI_RAVAGER.get();
|
||||||
}
|
}
|
||||||
if (mob instanceof Silverfish) {
|
if (mob instanceof Silverfish) {
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import net.minecraft.world.entity.Mob;
|
|||||||
import net.minecraft.world.level.ClipContext;
|
import net.minecraft.world.level.ClipContext;
|
||||||
import net.minecraft.world.phys.HitResult;
|
import net.minecraft.world.phys.HitResult;
|
||||||
import net.minecraft.world.phys.Vec3;
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.world.phys.AABB;
|
||||||
|
|
||||||
public final class AdaptivePositioningUtils {
|
public final class AdaptivePositioningUtils {
|
||||||
|
|
||||||
@@ -64,6 +67,9 @@ public final class AdaptivePositioningUtils {
|
|||||||
if (target == null) {
|
if (target == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!chunksLoaded(mob, mob.getEyePosition(), target.getEyePosition())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
HitResult hit = mob.level().clip(new ClipContext(mob.getEyePosition(), target.getEyePosition(),
|
HitResult hit = mob.level().clip(new ClipContext(mob.getEyePosition(), target.getEyePosition(),
|
||||||
ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, mob));
|
ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, mob));
|
||||||
return hit.getType() == HitResult.Type.MISS;
|
return hit.getType() == HitResult.Type.MISS;
|
||||||
@@ -71,9 +77,25 @@ public final class AdaptivePositioningUtils {
|
|||||||
|
|
||||||
public static boolean isReasonableGround(Mob mob, Vec3 pos) {
|
public static boolean isReasonableGround(Mob mob, Vec3 pos) {
|
||||||
BlockPos block = BlockPos.containing(pos);
|
BlockPos block = BlockPos.containing(pos);
|
||||||
return mob.level().getWorldBorder().isWithinBounds(block)
|
BlockPos below = block.below();
|
||||||
&& !mob.level().getBlockState(block).liquid()
|
AABB destinationBox = mob.getBoundingBox().move(pos.subtract(mob.position()));
|
||||||
&& mob.level().getBlockState(block).getCollisionShape(mob.level(), block).isEmpty();
|
if (!mob.level().getWorldBorder().isWithinBounds(block)
|
||||||
|
|| !chunksLoaded(mob, new Vec3(destinationBox.minX, destinationBox.minY, destinationBox.minZ),
|
||||||
|
new Vec3(destinationBox.maxX, destinationBox.maxY, destinationBox.maxZ))
|
||||||
|
|| !mob.level().hasChunkAt(below)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var state = mob.level().getBlockState(block);
|
||||||
|
var support = mob.level().getBlockState(below);
|
||||||
|
if (state.liquid() || !state.getCollisionShape(mob.level(), block).isEmpty()
|
||||||
|
|| !support.isFaceSturdy(mob.level(), below, Direction.UP)
|
||||||
|
|| support.is(Blocks.CACTUS) || support.is(Blocks.MAGMA_BLOCK)
|
||||||
|
|| support.is(Blocks.CAMPFIRE) || support.is(Blocks.SOUL_CAMPFIRE)
|
||||||
|
|| support.is(Blocks.POWDER_SNOW) || support.is(Blocks.FIRE) || support.is(Blocks.SOUL_FIRE)
|
||||||
|
|| !mob.level().noCollision(mob, destinationBox)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return mob.getNavigation().createPath(block, 0) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isOpenToSky(Mob mob) {
|
public static boolean isOpenToSky(Mob mob) {
|
||||||
@@ -81,7 +103,8 @@ public final class AdaptivePositioningUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int localLight(Mob mob, Vec3 pos) {
|
public static int localLight(Mob mob, Vec3 pos) {
|
||||||
return mob.level().getMaxLocalRawBrightness(BlockPos.containing(pos));
|
BlockPos block = BlockPos.containing(pos);
|
||||||
|
return mob.level().hasChunkAt(block) ? mob.level().getMaxLocalRawBrightness(block) : Integer.MAX_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Vec3 elevatedPosition(Mob mob, LivingEntity target, double radius) {
|
public static Vec3 elevatedPosition(Mob mob, LivingEntity target, double radius) {
|
||||||
@@ -94,4 +117,19 @@ public final class AdaptivePositioningUtils {
|
|||||||
}
|
}
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean chunksLoaded(Mob mob, Vec3 from, Vec3 to) {
|
||||||
|
int minChunkX = ((int) Math.floor(Math.min(from.x, to.x))) >> 4;
|
||||||
|
int maxChunkX = ((int) Math.floor(Math.max(from.x, to.x))) >> 4;
|
||||||
|
int minChunkZ = ((int) Math.floor(Math.min(from.z, to.z))) >> 4;
|
||||||
|
int maxChunkZ = ((int) Math.floor(Math.max(from.z, to.z))) >> 4;
|
||||||
|
for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) {
|
||||||
|
for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) {
|
||||||
|
if (!mob.level().hasChunkAt(new BlockPos(chunkX << 4, 0, chunkZ << 4))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package net.fretux.adaptivemobs.ai;
|
|||||||
|
|
||||||
import net.fretux.adaptivemobs.config.AMConfig;
|
import net.fretux.adaptivemobs.config.AMConfig;
|
||||||
import net.fretux.adaptivemobs.difficulty.AdaptiveDifficultyManager;
|
import net.fretux.adaptivemobs.difficulty.AdaptiveDifficultyManager;
|
||||||
|
import net.fretux.adaptivemobs.data.TemporaryBlockSavedData;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.Direction;
|
import net.minecraft.core.Direction;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
@@ -66,7 +67,6 @@ 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 String SPLIT_FIREBALL_KEY = "am_split_fireball";
|
||||||
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<>();
|
||||||
|
|
||||||
private AdaptiveSpecialAbilities() {
|
private AdaptiveSpecialAbilities() {
|
||||||
@@ -136,17 +136,20 @@ public final class AdaptiveSpecialAbilities {
|
|||||||
if (event.phase != TickEvent.Phase.END || event.getServer() == null) {
|
if (event.phase != TickEvent.Phase.END || event.getServer() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Iterator<TemporaryBlock> blockIterator = TEMPORARY_BLOCKS.iterator();
|
TemporaryBlockSavedData temporaryBlocks = TemporaryBlockSavedData.get(event.getServer().overworld());
|
||||||
|
Iterator<TemporaryBlockSavedData.Entry> blockIterator = temporaryBlocks.entries();
|
||||||
while (blockIterator.hasNext()) {
|
while (blockIterator.hasNext()) {
|
||||||
TemporaryBlock block = blockIterator.next();
|
TemporaryBlockSavedData.Entry block = blockIterator.next();
|
||||||
ServerLevel level = block.server == event.getServer() ? event.getServer().getLevel(block.dimension) : null;
|
ServerLevel level = event.getServer().getLevel(net.minecraft.resources.ResourceKey.create(
|
||||||
if (level == null || level.getGameTime() < block.expireTick) {
|
net.minecraft.core.registries.Registries.DIMENSION, block.dimension()));
|
||||||
|
if (level == null || level.getGameTime() < block.expireTick() || !level.hasChunkAt(block.pos())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (level.getBlockState(block.pos).equals(block.placedState)) {
|
if (level.getBlockState(block.pos()).is(Blocks.COBWEB)) {
|
||||||
block.replaced.restore(true, false);
|
level.setBlock(block.pos(), Blocks.AIR.defaultBlockState(), 3);
|
||||||
}
|
}
|
||||||
blockIterator.remove();
|
blockIterator.remove();
|
||||||
|
temporaryBlocks.changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator<DelayedReappear> reappearIterator = DELAYED_REAPPEARS.iterator();
|
Iterator<DelayedReappear> reappearIterator = DELAYED_REAPPEARS.iterator();
|
||||||
@@ -348,8 +351,7 @@ public final class AdaptiveSpecialAbilities {
|
|||||||
if (!level.setBlock(pos, placedState, 3)) {
|
if (!level.setBlock(pos, placedState, 3)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TEMPORARY_BLOCKS.add(new TemporaryBlock(level.getServer(), level.dimension(), pos.immutable(),
|
TemporaryBlockSavedData.get(level).add(level, pos, expireTick);
|
||||||
expireTick, replaced, placedState));
|
|
||||||
level.playSound(null, pos, SoundEvents.SPIDER_AMBIENT, SoundSource.HOSTILE, 0.6F, 1.25F);
|
level.playSound(null, pos, SoundEvents.SPIDER_AMBIENT, SoundSource.HOSTILE, 0.6F, 1.25F);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,34 +391,14 @@ public final class AdaptiveSpecialAbilities {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void invalidateTemporaryBlock(ServerLevel level, BlockPos pos) {
|
public static void invalidateTemporaryBlock(ServerLevel level, BlockPos pos) {
|
||||||
TEMPORARY_BLOCKS.removeIf(block -> block.server == level.getServer()
|
TemporaryBlockSavedData.get(level).remove(level, pos);
|
||||||
&& block.dimension == level.dimension() && block.pos.equals(pos));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void clear(ServerLevel level) {
|
public static void clear(ServerLevel level) {
|
||||||
TEMPORARY_BLOCKS.removeIf(block -> {
|
|
||||||
if (block.server != level.getServer() || block.dimension != level.dimension()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (level.getBlockState(block.pos).equals(block.placedState)) {
|
|
||||||
block.replaced.restore(true, false);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
DELAYED_REAPPEARS.removeIf(pending -> pending.server == level.getServer() && pending.dimension == level.dimension());
|
DELAYED_REAPPEARS.removeIf(pending -> pending.server == level.getServer() && pending.dimension == level.dimension());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void clear(net.minecraft.server.MinecraftServer server) {
|
public static void clear(net.minecraft.server.MinecraftServer server) {
|
||||||
TEMPORARY_BLOCKS.removeIf(block -> {
|
|
||||||
if (block.server != server) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ServerLevel level = server.getLevel(block.dimension);
|
|
||||||
if (level != null && level.getBlockState(block.pos).equals(block.placedState)) {
|
|
||||||
block.replaced.restore(true, false);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
DELAYED_REAPPEARS.removeIf(pending -> pending.server == server);
|
DELAYED_REAPPEARS.removeIf(pending -> pending.server == server);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,11 +406,6 @@ public final class AdaptiveSpecialAbilities {
|
|||||||
return AMConfig.enabled && AMConfig.AI_ENABLED.get() && AMConfig.ENABLE_ADVANCED_AI.get();
|
return AMConfig.enabled && AMConfig.AI_ENABLED.get() && AMConfig.ENABLE_ADVANCED_AI.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private record TemporaryBlock(net.minecraft.server.MinecraftServer server,
|
|
||||||
net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> dimension,
|
|
||||||
BlockPos pos, long expireTick, BlockSnapshot replaced, BlockState placedState) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private record DelayedReappear(net.minecraft.server.MinecraftServer server,
|
private record DelayedReappear(net.minecraft.server.MinecraftServer server,
|
||||||
net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> dimension,
|
net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> dimension,
|
||||||
long tick, UUID mobId, UUID targetId, int tier) {
|
long tick, UUID mobId, UUID targetId, int tier) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.util.function.IntSupplier;
|
|||||||
|
|
||||||
/** Keeps the creeper fuse synchronized with the current config-gated adaptive tier. */
|
/** Keeps the creeper fuse synchronized with the current config-gated adaptive tier. */
|
||||||
public final class AdaptiveCreeperFuseGoal extends Goal {
|
public final class AdaptiveCreeperFuseGoal extends Goal {
|
||||||
|
private static final String ORIGINAL_FUSE_KEY = "am_original_fuse";
|
||||||
private final Creeper creeper;
|
private final Creeper creeper;
|
||||||
private final IntSupplier tierSupplier;
|
private final IntSupplier tierSupplier;
|
||||||
private int updateCooldown;
|
private int updateCooldown;
|
||||||
@@ -33,6 +34,17 @@ public final class AdaptiveCreeperFuseGoal extends Goal {
|
|||||||
}
|
}
|
||||||
updateCooldown = 20;
|
updateCooldown = 20;
|
||||||
int tier = tierSupplier.getAsInt();
|
int tier = tierSupplier.getAsInt();
|
||||||
creeper.maxSwell = tier <= 0 ? 30 : Math.max(18, 30 - Math.max(0, tier - 1) * 3);
|
if (tier <= 0) {
|
||||||
|
if (creeper.getPersistentData().contains(ORIGINAL_FUSE_KEY)) {
|
||||||
|
creeper.maxSwell = creeper.getPersistentData().getInt(ORIGINAL_FUSE_KEY);
|
||||||
|
creeper.getPersistentData().remove(ORIGINAL_FUSE_KEY);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!creeper.getPersistentData().contains(ORIGINAL_FUSE_KEY)) {
|
||||||
|
creeper.getPersistentData().putInt(ORIGINAL_FUSE_KEY, creeper.maxSwell);
|
||||||
|
}
|
||||||
|
int original = creeper.getPersistentData().getInt(ORIGINAL_FUSE_KEY);
|
||||||
|
creeper.maxSwell = Math.max(18, original - Math.max(0, tier - 1) * 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,15 +17,23 @@ 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 String RECOMBINE_READY_AT_KEY = "am_recombine_ready_at";
|
||||||
|
private static final String RECOMBINE_CLAIM_KEY = "am_recombine_claim";
|
||||||
|
private static final String RECOMBINE_CLAIM_EXPIRES_KEY = "am_recombine_claim_expires";
|
||||||
private static final int MAX_RECOMBINE_SIZE = 4;
|
private static final int MAX_RECOMBINE_SIZE = 4;
|
||||||
private static final double RECOMBINE_SEARCH_RADIUS = 12.0D;
|
private static final double RECOMBINE_SEARCH_RADIUS = 12.0D;
|
||||||
private static final double RECOMBINE_DISTANCE_SQR = 3.24D;
|
private static final double RECOMBINE_DISTANCE_SQR = 3.24D;
|
||||||
|
private static final double MAX_CHASE_DISTANCE_SQR = 256.0D;
|
||||||
|
private static final int MAX_CHASE_TICKS = 200;
|
||||||
|
private static final int MAX_STUCK_TICKS = 40;
|
||||||
|
|
||||||
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 int recombineCheckCooldown;
|
||||||
private Slime recombinePartner;
|
private Slime recombinePartner;
|
||||||
|
private int chaseTicks;
|
||||||
|
private int stuckTicks;
|
||||||
|
private Vec3 lastChasePosition;
|
||||||
|
|
||||||
public AdaptiveSlimeTacticsGoal(Slime slime, IntSupplier tierSupplier) {
|
public AdaptiveSlimeTacticsGoal(Slime slime, IntSupplier tierSupplier) {
|
||||||
this.slime = slime;
|
this.slime = slime;
|
||||||
@@ -48,9 +56,10 @@ public class AdaptiveSlimeTacticsGoal extends Goal {
|
|||||||
&& --recombineCheckCooldown <= 0) {
|
&& --recombineCheckCooldown <= 0) {
|
||||||
recombineCheckCooldown = 10;
|
recombineCheckCooldown = 10;
|
||||||
recombinePartner = findRecombinePartner();
|
recombinePartner = findRecombinePartner();
|
||||||
if (recombinePartner != null) {
|
if (recombinePartner != null && claim(recombinePartner)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
recombinePartner = null;
|
||||||
}
|
}
|
||||||
return AdaptiveAIGoalUtils.isValidAdaptiveTarget(slime.getTarget()) && --cooldown <= 0;
|
return AdaptiveAIGoalUtils.isValidAdaptiveTarget(slime.getTarget()) && --cooldown <= 0;
|
||||||
}
|
}
|
||||||
@@ -59,6 +68,9 @@ public class AdaptiveSlimeTacticsGoal extends Goal {
|
|||||||
public void start() {
|
public void start() {
|
||||||
int tier = tierSupplier.getAsInt();
|
int tier = tierSupplier.getAsInt();
|
||||||
if (recombinePartner != null && recombinePartner.isAlive()) {
|
if (recombinePartner != null && recombinePartner.isAlive()) {
|
||||||
|
chaseTicks = 0;
|
||||||
|
stuckTicks = 0;
|
||||||
|
lastChasePosition = slime.position();
|
||||||
if (tryRecombine(recombinePartner)) {
|
if (tryRecombine(recombinePartner)) {
|
||||||
recombinePartner = null;
|
recombinePartner = null;
|
||||||
}
|
}
|
||||||
@@ -87,13 +99,20 @@ public class AdaptiveSlimeTacticsGoal extends Goal {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canContinueToUse() {
|
public boolean canContinueToUse() {
|
||||||
return recombinePartner != null && recombinePartner.isAlive()
|
return validPartner(recombinePartner, true) && chaseTicks < MAX_CHASE_TICKS
|
||||||
&& slime.isAlive() && tierSupplier.getAsInt() >= 5
|
&& stuckTicks < MAX_STUCK_TICKS
|
||||||
&& slime.getSize() < MAX_RECOMBINE_SIZE;
|
&& slime.distanceToSqr(recombinePartner) <= MAX_CHASE_DISTANCE_SQR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
|
chaseTicks++;
|
||||||
|
if (lastChasePosition != null && slime.position().distanceToSqr(lastChasePosition) < 0.0025D) {
|
||||||
|
stuckTicks++;
|
||||||
|
} else {
|
||||||
|
stuckTicks = 0;
|
||||||
|
lastChasePosition = slime.position();
|
||||||
|
}
|
||||||
if (recombinePartner != null && tryRecombine(recombinePartner)) {
|
if (recombinePartner != null && tryRecombine(recombinePartner)) {
|
||||||
recombinePartner = null;
|
recombinePartner = null;
|
||||||
}
|
}
|
||||||
@@ -101,6 +120,7 @@ public class AdaptiveSlimeTacticsGoal extends Goal {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
|
releaseClaim(recombinePartner);
|
||||||
recombinePartner = null;
|
recombinePartner = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,13 +128,17 @@ public class AdaptiveSlimeTacticsGoal extends Goal {
|
|||||||
boolean magma = slime instanceof MagmaCube;
|
boolean magma = slime instanceof MagmaCube;
|
||||||
List<Slime> candidates = slime.level().getEntitiesOfClass(Slime.class,
|
List<Slime> candidates = slime.level().getEntitiesOfClass(Slime.class,
|
||||||
slime.getBoundingBox().inflate(RECOMBINE_SEARCH_RADIUS),
|
slime.getBoundingBox().inflate(RECOMBINE_SEARCH_RADIUS),
|
||||||
other -> other != slime && other.isAlive() && other.getSize() == slime.getSize()
|
other -> validPartner(other, false)
|
||||||
&& (other instanceof MagmaCube) == magma
|
&& (other instanceof MagmaCube) == magma
|
||||||
&& other.level().getGameTime() >= other.getPersistentData().getLong(RECOMBINE_READY_AT_KEY));
|
&& claimAvailable(other));
|
||||||
return candidates.stream().min(Comparator.comparingDouble(slime::distanceToSqr)).orElse(null);
|
return candidates.stream().min(Comparator.comparingDouble(slime::distanceToSqr)).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean tryRecombine(Slime partner) {
|
private boolean tryRecombine(Slime partner) {
|
||||||
|
if (!validPartner(partner, true) || slime.distanceToSqr(partner) > MAX_CHASE_DISTANCE_SQR) {
|
||||||
|
releaseClaim(partner);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (slime.distanceToSqr(partner) > RECOMBINE_DISTANCE_SQR) {
|
if (slime.distanceToSqr(partner) > RECOMBINE_DISTANCE_SQR) {
|
||||||
slime.getLookControl().setLookAt(partner, 30.0F, 30.0F);
|
slime.getLookControl().setLookAt(partner, 30.0F, 30.0F);
|
||||||
slime.getMoveControl().setWantedPosition(partner.getX(), partner.getY(), partner.getZ(), 1.2D);
|
slime.getMoveControl().setWantedPosition(partner.getX(), partner.getY(), partner.getZ(), 1.2D);
|
||||||
@@ -122,8 +146,10 @@ public class AdaptiveSlimeTacticsGoal extends Goal {
|
|||||||
}
|
}
|
||||||
LivingEntity inheritedTarget = AdaptiveAIGoalUtils.isValidAdaptiveTarget(slime.getTarget())
|
LivingEntity inheritedTarget = AdaptiveAIGoalUtils.isValidAdaptiveTarget(slime.getTarget())
|
||||||
? slime.getTarget() : partner.getTarget();
|
? slime.getTarget() : partner.getTarget();
|
||||||
|
float healthFraction = slime.getHealth() / slime.getMaxHealth();
|
||||||
partner.discard();
|
partner.discard();
|
||||||
slime.setSize(Math.min(MAX_RECOMBINE_SIZE, slime.getSize() * 2), true);
|
slime.setSize(Math.min(MAX_RECOMBINE_SIZE, slime.getSize() * 2), false);
|
||||||
|
slime.setHealth(Math.max(1.0F, Math.min(slime.getMaxHealth(), slime.getMaxHealth() * healthFraction)));
|
||||||
if (AdaptiveAIGoalUtils.isValidAdaptiveTarget(inheritedTarget)) {
|
if (AdaptiveAIGoalUtils.isValidAdaptiveTarget(inheritedTarget)) {
|
||||||
slime.setTarget(inheritedTarget);
|
slime.setTarget(inheritedTarget);
|
||||||
}
|
}
|
||||||
@@ -133,6 +159,47 @@ public class AdaptiveSlimeTacticsGoal extends Goal {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean validPartner(Slime partner, boolean requireClaim) {
|
||||||
|
if (partner == null || partner == slime || !partner.isAlive() || !slime.isAlive()
|
||||||
|
|| tierSupplier.getAsInt() < 5 || slime.getSize() >= MAX_RECOMBINE_SIZE
|
||||||
|
|| partner.getSize() != slime.getSize()
|
||||||
|
|| (partner instanceof MagmaCube) != (slime instanceof MagmaCube)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
long now = slime.level().getGameTime();
|
||||||
|
if (now < slime.getPersistentData().getLong(RECOMBINE_READY_AT_KEY)
|
||||||
|
|| now < partner.getPersistentData().getLong(RECOMBINE_READY_AT_KEY)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !requireClaim || (partner.getPersistentData().hasUUID(RECOMBINE_CLAIM_KEY)
|
||||||
|
&& partner.getPersistentData().getUUID(RECOMBINE_CLAIM_KEY).equals(slime.getUUID())
|
||||||
|
&& partner.getPersistentData().getLong(RECOMBINE_CLAIM_EXPIRES_KEY) >= now);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean claimAvailable(Slime partner) {
|
||||||
|
long now = slime.level().getGameTime();
|
||||||
|
return !partner.getPersistentData().hasUUID(RECOMBINE_CLAIM_KEY)
|
||||||
|
|| partner.getPersistentData().getLong(RECOMBINE_CLAIM_EXPIRES_KEY) < now;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean claim(Slime partner) {
|
||||||
|
if (!validPartner(partner, false) || !claimAvailable(partner)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
partner.getPersistentData().putUUID(RECOMBINE_CLAIM_KEY, slime.getUUID());
|
||||||
|
partner.getPersistentData().putLong(RECOMBINE_CLAIM_EXPIRES_KEY,
|
||||||
|
slime.level().getGameTime() + MAX_CHASE_TICKS + 1L);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseClaim(Slime partner) {
|
||||||
|
if (partner != null && partner.getPersistentData().hasUUID(RECOMBINE_CLAIM_KEY)
|
||||||
|
&& partner.getPersistentData().getUUID(RECOMBINE_CLAIM_KEY).equals(slime.getUUID())) {
|
||||||
|
partner.getPersistentData().remove(RECOMBINE_CLAIM_KEY);
|
||||||
|
partner.getPersistentData().remove(RECOMBINE_CLAIM_EXPIRES_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void scheduleRecombine(int delayTicks) {
|
private void scheduleRecombine(int delayTicks) {
|
||||||
if (slime.getPersistentData().getLong(RECOMBINE_READY_AT_KEY) == 0L) {
|
if (slime.getPersistentData().getLong(RECOMBINE_READY_AT_KEY) == 0L) {
|
||||||
slime.getPersistentData().putLong(RECOMBINE_READY_AT_KEY, slime.level().getGameTime() + delayTicks);
|
slime.getPersistentData().putLong(RECOMBINE_READY_AT_KEY, slime.level().getGameTime() + delayTicks);
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ public final class AMConfig {
|
|||||||
public static final ForgeConfigSpec.BooleanValue AI_GUARDIAN = BUILDER.define("ai.guardians", true);
|
public static final ForgeConfigSpec.BooleanValue AI_GUARDIAN = BUILDER.define("ai.guardians", true);
|
||||||
public static final ForgeConfigSpec.BooleanValue AI_PHANTOM = BUILDER.define("ai.phantoms", true);
|
public static final ForgeConfigSpec.BooleanValue AI_PHANTOM = BUILDER.define("ai.phantoms", true);
|
||||||
public static final ForgeConfigSpec.BooleanValue AI_RAVAGER = BUILDER.define("ai.ravagers", true);
|
public static final ForgeConfigSpec.BooleanValue AI_RAVAGER = BUILDER.define("ai.ravagers", true);
|
||||||
|
public static final ForgeConfigSpec.BooleanValue AI_VINDICATOR = BUILDER.define("ai.vindicators", true);
|
||||||
public static final ForgeConfigSpec.BooleanValue AI_EVOKER = BUILDER.define("ai.evokers", true);
|
public static final ForgeConfigSpec.BooleanValue AI_EVOKER = BUILDER.define("ai.evokers", true);
|
||||||
public static final ForgeConfigSpec.BooleanValue AI_VEX = BUILDER.define("ai.vexes", true);
|
public static final ForgeConfigSpec.BooleanValue AI_VEX = BUILDER.define("ai.vexes", true);
|
||||||
public static final ForgeConfigSpec.BooleanValue AI_PIGLIN_FAMILY = BUILDER.define("ai.piglinFamily", true);
|
public static final ForgeConfigSpec.BooleanValue AI_PIGLIN_FAMILY = BUILDER.define("ai.piglinFamily", true);
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package net.fretux.adaptivemobs.data;
|
||||||
|
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.ListTag;
|
||||||
|
import net.minecraft.nbt.Tag;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.saveddata.SavedData;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Crash-safe registry for temporary blocks. Cobwebs only replace air, so restoration is unambiguous. */
|
||||||
|
public final class TemporaryBlockSavedData extends SavedData {
|
||||||
|
private static final String DATA_NAME = "adaptivemobs_temporary_blocks";
|
||||||
|
private final List<Entry> entries = new ArrayList<>();
|
||||||
|
|
||||||
|
public static TemporaryBlockSavedData get(ServerLevel level) {
|
||||||
|
ServerLevel overworld = level.getServer().overworld();
|
||||||
|
return overworld.getDataStorage().computeIfAbsent(TemporaryBlockSavedData::load,
|
||||||
|
TemporaryBlockSavedData::new, DATA_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(ServerLevel level, BlockPos pos, long expireTick) {
|
||||||
|
entries.add(new Entry(level.dimension().location(), pos.immutable(), expireTick));
|
||||||
|
setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(ServerLevel level, BlockPos pos) {
|
||||||
|
if (entries.removeIf(entry -> entry.dimension.equals(level.dimension().location()) && entry.pos.equals(pos))) {
|
||||||
|
setDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterator<Entry> entries() {
|
||||||
|
return entries.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changed() {
|
||||||
|
setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TemporaryBlockSavedData load(CompoundTag tag) {
|
||||||
|
TemporaryBlockSavedData data = new TemporaryBlockSavedData();
|
||||||
|
ListTag list = tag.getList("Blocks", Tag.TAG_COMPOUND);
|
||||||
|
for (Tag raw : list) {
|
||||||
|
CompoundTag entry = (CompoundTag) raw;
|
||||||
|
ResourceLocation dimension = ResourceLocation.tryParse(entry.getString("Dimension"));
|
||||||
|
if (dimension != null) {
|
||||||
|
data.entries.add(new Entry(dimension, BlockPos.of(entry.getLong("Pos")), entry.getLong("Expires")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundTag save(CompoundTag tag) {
|
||||||
|
ListTag list = new ListTag();
|
||||||
|
for (Entry entry : entries) {
|
||||||
|
CompoundTag encoded = new CompoundTag();
|
||||||
|
encoded.putString("Dimension", entry.dimension.toString());
|
||||||
|
encoded.putLong("Pos", entry.pos.asLong());
|
||||||
|
encoded.putLong("Expires", entry.expireTick);
|
||||||
|
list.add(encoded);
|
||||||
|
}
|
||||||
|
tag.put("Blocks", list);
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Entry(ResourceLocation dimension, BlockPos pos, long expireTick) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,6 +65,9 @@ public class AdaptiveMobsEvents {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int tier = AdaptiveDifficultyManager.getMobTier(level, victim.getType());
|
int tier = AdaptiveDifficultyManager.getMobTier(level, victim.getType());
|
||||||
|
if (!attacker.isAlliedTo(victim) && !victim.isAlliedTo(attacker)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (tier < 2 || !AMConfig.isMobEnabled(victim.getType())) {
|
if (tier < 2 || !AMConfig.isMobEnabled(victim.getType())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package net.fretux.adaptivemobs.gametest;
|
||||||
|
|
||||||
|
import net.fretux.adaptivemobs.ai.goals.AdaptiveSlimeTacticsGoal;
|
||||||
|
import net.fretux.adaptivemobs.data.TemporaryBlockSavedData;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.gametest.framework.GameTest;
|
||||||
|
import net.minecraft.gametest.framework.GameTestHelper;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.world.entity.EntityType;
|
||||||
|
import net.minecraft.world.entity.monster.Slime;
|
||||||
|
import net.minecraftforge.gametest.GameTestHolder;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
@GameTestHolder("adaptivemobs")
|
||||||
|
public final class AdaptiveServerBehaviorGameTests {
|
||||||
|
private AdaptiveServerBehaviorGameTests() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@GameTest(template = "empty")
|
||||||
|
public static void temporaryBlocksSurviveSavedDataReload(GameTestHelper helper) {
|
||||||
|
TemporaryBlockSavedData original = new TemporaryBlockSavedData();
|
||||||
|
BlockPos pos = helper.absolutePos(new BlockPos(1, 1, 1));
|
||||||
|
long expires = helper.getLevel().getGameTime() + 100L;
|
||||||
|
original.add(helper.getLevel(), pos, expires);
|
||||||
|
|
||||||
|
TemporaryBlockSavedData reloaded = TemporaryBlockSavedData.load(original.save(new CompoundTag()));
|
||||||
|
Iterator<TemporaryBlockSavedData.Entry> entries = reloaded.entries();
|
||||||
|
helper.assertTrue(entries.hasNext(), "temporary block record was lost during save/reload");
|
||||||
|
TemporaryBlockSavedData.Entry entry = entries.next();
|
||||||
|
helper.assertTrue(entry.dimension().equals(helper.getLevel().dimension().location()), "dimension was not preserved");
|
||||||
|
helper.assertTrue(entry.pos().equals(pos), "position was not preserved");
|
||||||
|
helper.assertTrue(entry.expireTick() == expires, "expiration was not preserved");
|
||||||
|
helper.assertTrue(!entries.hasNext(), "unexpected duplicate temporary block record");
|
||||||
|
helper.succeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GameTest(template = "empty")
|
||||||
|
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);
|
||||||
|
AdaptiveSlimeTacticsGoal goal = new AdaptiveSlimeTacticsGoal(slime, () -> 5);
|
||||||
|
|
||||||
|
helper.assertTrue(goal.canUse(), "slime did not select its eligible partner");
|
||||||
|
goal.start();
|
||||||
|
for (int i = 0; i <= 40; i++) {
|
||||||
|
goal.tick();
|
||||||
|
}
|
||||||
|
helper.assertTrue(!goal.canContinueToUse(), "stuck recombination chase did not time out");
|
||||||
|
goal.stop();
|
||||||
|
helper.succeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user