From 85c2a4d86e5b7acb668fb8573a1b829621043c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossi=20Erkkil=C3=A4?= Date: Thu, 3 Sep 2026 19:26:45 +0300 Subject: [PATCH] Fix multiple issues related to rounding / inBounds checks Cast to int rounds towards zero instead of negative infinity resulting in inconsistent behaviour. The inBounds check failed when, for example, spawning an entity near the border of a chunk. --- .../common/event/tracking/phase/packet/PacketPhaseUtil.java | 4 ++-- src/main/java/org/spongepowered/common/util/VecHelper.java | 5 +++++ .../common/world/level/chunk/storage/SpongeEntityChunk.java | 4 ++-- .../api/minecraft/world/level/chunk/LevelChunkMixin_API.java | 5 ++--- .../common/mixin/core/item/EmptyMapItemMixin.java | 2 +- .../mixin/core/server/commands/TeleportCommandMixin.java | 2 +- .../common/mixin/core/world/level/LevelMixin.java | 2 +- .../world/entity/item/FallingBlockEntityMixin_Tracker.java | 2 +- 8 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/spongepowered/common/event/tracking/phase/packet/PacketPhaseUtil.java b/src/main/java/org/spongepowered/common/event/tracking/phase/packet/PacketPhaseUtil.java index b9c8d57cdfa..1c2ab6ee994 100644 --- a/src/main/java/org/spongepowered/common/event/tracking/phase/packet/PacketPhaseUtil.java +++ b/src/main/java/org/spongepowered/common/event/tracking/phase/packet/PacketPhaseUtil.java @@ -227,8 +227,8 @@ public static void onProcessPacket(final Packet packetIn, final PacketListener n // Basically, we need to sanity check the nearby blocks because if they have // any positional logic, we need to run captures final AABB boundingBox = packetPlayer.getBoundingBox(); - final BlockPos min = new BlockPos((int) (boundingBox.minX + 0.001D), (int) (boundingBox.minY + 0.001D), (int) (boundingBox.minZ + 0.001D)); - final BlockPos max = new BlockPos((int) (boundingBox.maxX - 0.001D), (int) (boundingBox.maxY - 0.001D), (int) (boundingBox.maxZ - 0.001D)); + final BlockPos min = BlockPos.containing(boundingBox.minX + 0.001D, boundingBox.minY + 0.001D, boundingBox.minZ + 0.001D); + final BlockPos max = BlockPos.containing(boundingBox.maxX - 0.001D, boundingBox.maxY - 0.001D, boundingBox.maxZ - 0.001D); final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); if (packetPlayer.level().hasChunksAt(min, max)) { for(int x = min.getX(); x <= max.getX(); ++x) { diff --git a/src/main/java/org/spongepowered/common/util/VecHelper.java b/src/main/java/org/spongepowered/common/util/VecHelper.java index 5d3c5b9d34a..7772d45a2e2 100644 --- a/src/main/java/org/spongepowered/common/util/VecHelper.java +++ b/src/main/java/org/spongepowered/common/util/VecHelper.java @@ -182,6 +182,11 @@ public static boolean inBounds(final BlockPos pos, final org.spongepowered.math. return VecHelper.inBounds(pos.getX(), pos.getY(), pos.getZ(), min, max); } + public static boolean inBounds(final org.spongepowered.math.vector.Vector3i pos, final org.spongepowered.math.vector.Vector3i min, + final org.spongepowered.math.vector.Vector3i max) { + return VecHelper.inBounds(pos.x(), pos.y(), pos.z(), min, max); + } + public static boolean inBounds(final org.spongepowered.math.vector.Vector3d pos, final org.spongepowered.math.vector.Vector3i min, final org.spongepowered.math.vector.Vector3i max) { return VecHelper.inBounds(pos.x(), pos.y(), pos.z(), min, max); diff --git a/src/main/java/org/spongepowered/common/world/level/chunk/storage/SpongeEntityChunk.java b/src/main/java/org/spongepowered/common/world/level/chunk/storage/SpongeEntityChunk.java index 3748cd81030..08186f8e900 100644 --- a/src/main/java/org/spongepowered/common/world/level/chunk/storage/SpongeEntityChunk.java +++ b/src/main/java/org/spongepowered/common/world/level/chunk/storage/SpongeEntityChunk.java @@ -187,7 +187,7 @@ public E createEntityNaturally(final EntityType type, fina @Override public Optional createEntity(final DataContainer container) { return Optional.ofNullable(((LevelBridge) this.level).bridge$createEntity(container, null, - position -> VecHelper.inBounds(position, this.min(), this.max()))); + position -> VecHelper.inBounds(position.toInt(), this.min(), this.max()))); } @Override @@ -231,7 +231,7 @@ public void filterEntities(final Class entityCla } private void checkPositionInChunk(final Vector3d position) { - if (!VecHelper.inBounds(position, this.min(), this.max())) { + if (!VecHelper.inBounds(position.toInt(), this.min(), this.max())) { throw new IllegalArgumentException("Supplied bounds are not within this chunk."); } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/world/level/chunk/LevelChunkMixin_API.java b/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/world/level/chunk/LevelChunkMixin_API.java index a44c8674d42..45c896ba6cf 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/world/level/chunk/LevelChunkMixin_API.java +++ b/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/world/level/chunk/LevelChunkMixin_API.java @@ -469,8 +469,7 @@ public E createEntityNaturally(final EntityType type, fina @Override public Optional createEntity(final DataContainer container) { - return Optional.ofNullable(((LevelBridge) this.level).bridge$createEntity(container, null, - position -> VecHelper.inBounds(position, this.min(), this.max()))); + return Optional.ofNullable(((LevelBridge) this.level).bridge$createEntity(container, null, this::api$isInBounds)); } @Override @@ -510,7 +509,7 @@ public boolean setBlock(final int x, final int y, final int z, final BlockState } private boolean api$isInBounds(final Vector3d position) { - return VecHelper.inBounds(position, this.min(), this.max()); + return VecHelper.inBounds(position.toInt(), this.min(), this.max()); } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/item/EmptyMapItemMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/item/EmptyMapItemMixin.java index c79a50a62fc..ad4d46a8af3 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/item/EmptyMapItemMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/item/EmptyMapItemMixin.java @@ -71,7 +71,7 @@ public abstract class EmptyMapItemMixin { frame.addContext(EventContextKeys.USED_ITEM, ItemStackUtil.snapshotOf(usedItem)); final Set> mapValues = Sets.newHashSet( - Value.immutableOf(Keys.MAP_LOCATION, Vector2i.from((int) player.getX(), (int) player.getZ())), + Value.immutableOf(Keys.MAP_LOCATION, Vector2i.from(player.getBlockX(), player.getBlockZ())), Value.immutableOf(Keys.MAP_WORLD, ((ServerWorld) level).key()) ); diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/server/commands/TeleportCommandMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/server/commands/TeleportCommandMixin.java index eb28867f3e4..0513e4dc3ee 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/server/commands/TeleportCommandMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/server/commands/TeleportCommandMixin.java @@ -100,7 +100,7 @@ public abstract class TeleportCommandMixin { if (entityIn instanceof ServerPlayer) { - ChunkPos chunkpos = new ChunkPos(new BlockPos((int) actualX, (int) actualY, (int) actualZ)); + ChunkPos chunkpos = new ChunkPos(BlockPos.containing(actualX, actualY, actualZ)); worldIn.getChunkSource().addRegionTicket(TicketType.POST_TELEPORT, chunkpos, 1, entityIn.getId()); entityIn.stopRiding(); diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/level/LevelMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/level/LevelMixin.java index fa45265c0f4..99c4529f9c4 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/level/LevelMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/level/LevelMixin.java @@ -228,7 +228,7 @@ public abstract class LevelMixin implements LevelBridge, LevelAccessor { if (naturally && entity instanceof Mob) { // Adding the default equipment - final DifficultyInstance difficulty = this.shadow$getCurrentDifficultyAt(new BlockPos((int) x, (int) y, (int) z)); + final DifficultyInstance difficulty = this.shadow$getCurrentDifficultyAt(BlockPos.containing(x, y, z)); ((MobAccessor) entity).invoker$populateDefaultEquipmentSlots(this.random, difficulty); } diff --git a/src/mixins/java/org/spongepowered/common/mixin/tracker/world/entity/item/FallingBlockEntityMixin_Tracker.java b/src/mixins/java/org/spongepowered/common/mixin/tracker/world/entity/item/FallingBlockEntityMixin_Tracker.java index 31aae90a6e1..e07aec2ea2e 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/tracker/world/entity/item/FallingBlockEntityMixin_Tracker.java +++ b/src/mixins/java/org/spongepowered/common/mixin/tracker/world/entity/item/FallingBlockEntityMixin_Tracker.java @@ -83,7 +83,7 @@ public abstract class FallingBlockEntityMixin_Tracker extends EntityMixin_Tracke // cancellable = true // ) private void tracker$handleBlockCapture(final CallbackInfo ci) { - final BlockPos pos = new BlockPos((int) this.shadow$getX(), (int) this.shadow$getY(), (int) this.shadow$getZ()); + final BlockPos pos = BlockPos.containing(this.shadow$getX(), this.shadow$getY(), this.shadow$getZ()); // So, there's two cases here: either the world is not cared for, or the // ChangeBlockEvent is not being listened to. If it's not being listened to, // we need to specifically just proceed as normal.