|
11 | 11 | import net.minecraft.server.level.ServerLevel;
|
12 | 12 | import net.minecraft.server.level.ServerPlayer;
|
13 | 13 | import net.minecraft.sounds.SoundSource;
|
| 14 | +import net.minecraft.util.Mth; |
14 | 15 | import net.minecraft.world.entity.Entity;
|
15 | 16 | import net.minecraft.world.entity.EntityType;
|
16 | 17 | import net.minecraft.world.entity.player.Player;
|
17 | 18 | import net.minecraft.world.level.ChunkPos;
|
18 | 19 | import net.minecraft.world.level.Level;
|
19 | 20 | import net.minecraft.world.phys.AABB;
|
| 21 | +import net.minecraft.world.phys.Vec2; |
20 | 22 | import net.minecraft.world.phys.Vec3;
|
| 23 | +import net.minecraft.world.phys.shapes.Shapes; |
21 | 24 | import net.neoforged.neoforge.common.Tags;
|
22 | 25 | import net.neoforged.neoforge.entity.PartEntity;
|
23 | 26 | import org.joml.Quaternionf;
|
@@ -354,18 +357,42 @@ public Vec3 getTeleportTo(Entity entity, PortalEntity matchingPortal) {
|
354 | 357 | } else {
|
355 | 358 | entityHeightFraction = (entityBB.minY - portalBB.minY) / portalBB.getYsize();
|
356 | 359 | }
|
| 360 | + |
| 361 | + // Clamp fraction to [0, 1] to avoid out-of-bounds positioning |
| 362 | + entityHeightFraction = Math.max(0.0, Math.min(1.0, entityHeightFraction)); |
| 363 | + |
357 | 364 | if (matchingPortal.getDirection().getAxis() == Direction.Axis.Y) {
|
358 | 365 | if (matchingPortal.getAlignment() == Direction.Axis.X) {
|
359 |
| - teleportTo = new Vec3(linkedPortal.getBoundingBox().minX + entityHeightFraction * linkedPortal.getBoundingBox().getXsize(), linkedPortal.getY(), linkedPortal.getZ()).relative(linkedPortal.getDirection(), 0.5f); |
| 366 | + double offset = entityHeightFraction * linkedPortal.getBoundingBox().getXsize(); |
| 367 | + double buffer = entityBB.getXsize() / 2 + Shapes.EPSILON; |
| 368 | + // Don't collide in wall next to top/bottom of floor/ceiling portal |
| 369 | + offset = Mth.clamp(offset, buffer, linkedPortal.getBoundingBox().getXsize() - buffer); |
| 370 | + teleportTo = new Vec3(linkedPortal.getBoundingBox().minX + offset, linkedPortal.getY(), linkedPortal.getZ()); |
360 | 371 | } else {
|
361 |
| - teleportTo = new Vec3(linkedPortal.getX(), linkedPortal.getY(), linkedPortal.getBoundingBox().minZ + entityHeightFraction * linkedPortal.getBoundingBox().getZsize()).relative(linkedPortal.getDirection(), 0.5f); |
| 372 | + double offset = entityHeightFraction * linkedPortal.getBoundingBox().getZsize(); |
| 373 | + double buffer = entityBB.getZsize() / 2 + Shapes.EPSILON; |
| 374 | + // Don't collide in wall next to top/bottom of floor/ceiling portal |
| 375 | + offset = Mth.clamp(offset, buffer, linkedPortal.getBoundingBox().getZsize() - buffer); |
| 376 | + teleportTo = new Vec3(linkedPortal.getX(), linkedPortal.getY(), linkedPortal.getBoundingBox().minZ + offset); |
362 | 377 | }
|
363 | 378 | } else {
|
364 |
| - teleportTo = new Vec3(linkedPortal.getX(), linkedPortal.getBoundingBox().minY + entityHeightFraction * linkedPortal.getBoundingBox().getYsize(), linkedPortal.getZ()).relative(linkedPortal.getDirection(), 0.5f); |
| 379 | + teleportTo = new Vec3(linkedPortal.getX(), linkedPortal.getBoundingBox().minY + entityHeightFraction * linkedPortal.getBoundingBox().getYsize(), linkedPortal.getZ()); |
| 380 | + } |
| 381 | + |
| 382 | + // Move to 'side' of portal (except for portals facing up, where it would give us extra velocity) |
| 383 | + if (linkedPortal.getDirection() == Direction.DOWN) { |
| 384 | + teleportTo = teleportTo.relative(Direction.DOWN, entityBB.getYsize()); |
| 385 | + } else if (linkedPortal.getDirection() != Direction.UP) { |
| 386 | + if (linkedPortal.getDirection().getAxis() == Direction.Axis.X) { |
| 387 | + teleportTo = teleportTo.relative(linkedPortal.getDirection(), linkedPortal.getBoundingBox().getXsize() / 2 + entityBB.getXsize() / 2); |
| 388 | + } else if (linkedPortal.getDirection().getAxis() == Direction.Axis.Z) { |
| 389 | + teleportTo = teleportTo.relative(linkedPortal.getDirection(), linkedPortal.getBoundingBox().getZsize() / 2 + entityBB.getZsize() / 2); |
| 390 | + } |
365 | 391 | }
|
366 | 392 |
|
367 |
| - if (linkedPortal.getDirection() == Direction.DOWN) |
368 |
| - teleportTo = teleportTo.relative(Direction.DOWN, 1f); |
| 393 | + // Don't immediately collide again |
| 394 | + teleportTo = teleportTo.relative(linkedPortal.getDirection(), Shapes.EPSILON); |
| 395 | + |
369 | 396 | return teleportTo;
|
370 | 397 | }
|
371 | 398 |
|
@@ -399,14 +426,13 @@ public void teleport(Entity entity) {
|
399 | 426 | if (getLinkedPortal() != null) {
|
400 | 427 | Vec3 teleportTo = getTeleportTo(entity, linkedPortal);
|
401 | 428 | // Adjust the entity's rotation to match the exit portal's direction
|
402 |
| - float newYaw = getYawFromDirection(linkedPortal.getDirection()); |
403 |
| - float newPitch = entity.getXRot(); // Maintain the same pitch |
| 429 | + Vec2 newLookAngle = transformLookAngle(entity, linkedPortal); |
404 | 430 | entity.resetFallDistance();
|
405 | 431 |
|
406 | 432 | Vec3 newMotion = calculateVelocity(entity);
|
407 | 433 |
|
408 | 434 | // Teleport the entity to the new location and set its rotation
|
409 |
| - boolean success = entity.teleportTo((ServerLevel) linkedPortal.level(), teleportTo.x(), teleportTo.y(), teleportTo.z(), new HashSet<>(), newYaw, newPitch); |
| 435 | + boolean success = entity.teleportTo((ServerLevel) linkedPortal.level(), teleportTo.x(), teleportTo.y(), teleportTo.z(), new HashSet<>(), newLookAngle.y, newLookAngle.x); |
410 | 436 |
|
411 | 437 | if (success) {
|
412 | 438 | entity.resetFallDistance();
|
@@ -462,14 +488,25 @@ public static Vec3 transformMotion(Vec3 motion, Direction from, Direction to) {
|
462 | 488 | return new Vec3(motionVec.x(), motionVec.y(), motionVec.z());
|
463 | 489 | }
|
464 | 490 |
|
465 |
| - // Helper method to get the yaw from a direction |
466 |
| - private float getYawFromDirection(Direction direction) { |
467 |
| - return switch (direction) { |
468 |
| - case NORTH -> 180.0F; |
469 |
| - case SOUTH -> 0.0F; |
470 |
| - case WEST -> 90.0F; |
471 |
| - case EAST -> -90.0F; |
472 |
| - default -> 0.0F; |
473 |
| - }; |
| 491 | + /** |
| 492 | + * Adjust the entity's look angle (pitch and yaw) for seamless transitions between portals. |
| 493 | + * |
| 494 | + * @param entity entity |
| 495 | + * @param destination destination portal |
| 496 | + * @return adjusted (xrot, yrot) |
| 497 | + */ |
| 498 | + private Vec2 transformLookAngle(Entity entity, PortalEntity destination) { |
| 499 | + final Vec3 newLook = transformMotion(entity.getLookAngle(), getDirection(), destination.getDirection().getOpposite()); |
| 500 | + return toDegrees(newLook); |
| 501 | + } |
| 502 | + |
| 503 | + private static Vec2 toDegrees(Vec3 vector) { |
| 504 | + double x = vector.x; |
| 505 | + double y = vector.y; |
| 506 | + double z = vector.z; |
| 507 | + double hyp = Math.sqrt(x * x + z * z); |
| 508 | + float xrot = Mth.wrapDegrees((float)(-(Mth.atan2(y, hyp) * 180.0F / (float)Math.PI))); |
| 509 | + float yrot = Mth.wrapDegrees((float)(Mth.atan2(z, x) * 180.0F / (float)Math.PI) - 90.0F); |
| 510 | + return new Vec2(xrot, yrot); |
474 | 511 | }
|
475 | 512 | }
|
0 commit comments