diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TeleportUtils.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TeleportUtils.java.patch index 5546e8681d..b5728deeba 100644 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TeleportUtils.java.patch +++ b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TeleportUtils.java.patch @@ -1,6 +1,6 @@ --- /dev/null +++ b/io/papermc/paper/threadedregions/TeleportUtils.java -@@ -1,0 +_,70 @@ +@@ -1,0 +_,82 @@ +package io.papermc.paper.threadedregions; + +import ca.spottedleaf.concurrentutil.completable.CallbackCompletable; @@ -13,8 +13,14 @@ + +public final class TeleportUtils { + -+ public static void teleport(final Entity from, final boolean useFromRootVehicle, final Entity to, final Float yaw, final Float pitch, -+ final long teleportFlags, final PlayerTeleportEvent.TeleportCause cause, final Consumer onComplete) { ++ public static void teleport(final T from, final boolean useFromRootVehicle, final Entity to, final Float yaw, final Float pitch, ++ final long teleportFlags, final PlayerTeleportEvent.TeleportCause cause, final Consumer onComplete) { ++ teleport(from, useFromRootVehicle, to, yaw, pitch, teleportFlags, cause, onComplete, null); ++ } ++ ++ public static void teleport(final T from, final boolean useFromRootVehicle, final Entity to, final Float yaw, final Float pitch, ++ final long teleportFlags, final PlayerTeleportEvent.TeleportCause cause, final Consumer onComplete, ++ final java.util.function.Predicate preTeleport) { + // retrieve coordinates + final CallbackCompletable positionCompletable = new CallbackCompletable<>(); + @@ -27,10 +33,16 @@ + return; + } + final boolean scheduled = from.getBukkitEntity().taskScheduler.schedule( -+ (final Entity realFrom) -> { ++ (final T realFrom) -> { + final Vec3 pos = new Vec3( + loc.getX(), loc.getY(), loc.getZ() + ); ++ if (preTeleport != null && !preTeleport.test(realFrom)) { ++ if (onComplete != null) { ++ onComplete.accept(null); ++ } ++ return; ++ } + (useFromRootVehicle ? realFrom.getRootVehicle() : realFrom).teleportAsync( + ((CraftWorld)loc.getWorld()).getHandle(), pos, null, null, null, + cause, teleportFlags, onComplete diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch index 51efc0e308..fafc5dd7b7 100644 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch +++ b/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch @@ -188,6 +188,31 @@ } else { LOGGER.warn("Failed to spawn player ender pearl in level ({}), skipping", optional.get()); } +@@ -817,12 +_,23 @@ + + Entity camera = this.getCamera(); + if (camera != this) { +- if (camera.isAlive()) { ++ if (camera.canBeSpectated()) { // Folia - region threading - replace removed check ++ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(camera) && !camera.isRemoved()) { // Folia - region threading + this.absMoveTo(camera.getX(), camera.getY(), camera.getZ(), camera.getYRot(), camera.getXRot()); + this.serverLevel().getChunkSource().move(this); + if (this.wantsToStopRiding()) { + this.setCamera(this); + } ++ } else { // Folia start - region threading ++ Entity realCamera = camera.getBukkitEntity().getHandleRaw(); ++ if (realCamera != camera) { ++ this.setCamera(this); ++ this.setCamera(realCamera); ++ } else { ++ this.teleportToCameraOffRegion(); ++ } ++ } ++ // Folia end - region threading + } else { + this.setCamera(this); + } @@ -1357,9 +_,332 @@ } } @@ -521,18 +546,66 @@ if (this.isSleeping()) return null; // CraftBukkit - SPIGOT-3154 if (this.isRemoved()) { return null; -@@ -2398,6 +_,11 @@ +@@ -2397,7 +_,30 @@ + return (Entity)(this.camera == null ? this : this.camera); } ++ // Folia start - region threading ++ private void teleportToCameraOffRegion() { ++ Entity cameraFinal = this.camera; ++ // use the task scheduler, as we don't know where the caller is invoking from ++ if (this != cameraFinal) { ++ this.getBukkitEntity().taskScheduler.schedule((final ServerPlayer newPlayer) -> { ++ io.papermc.paper.threadedregions.TeleportUtils.teleport( ++ newPlayer, false, cameraFinal, null, null, 0L, ++ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE, null, ++ (final ServerPlayer newerPlayer) -> { ++ return newerPlayer.camera == cameraFinal; ++ } ++ ); ++ }, null, 1L); ++ } // else: do not bother teleporting to self ++ } ++ // Folia end - region threading ++ public void setCamera(@Nullable Entity entityToSpectate) { + // Folia start - region threading -+ if (entityToSpectate != null && !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entityToSpectate)) { ++ if (entityToSpectate != null && (entityToSpectate != this && !entityToSpectate.canBeSpectated())) { + return; + } + // Folia end - region threading Entity camera = this.getCamera(); this.camera = (Entity)(entityToSpectate == null ? this : entityToSpectate); if (camera != this.camera) { +@@ -2416,16 +_,19 @@ + } + } + // Paper end - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity +- if (this.camera.level() instanceof ServerLevel serverLevel) { +- this.teleportTo(serverLevel, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), false, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit +- } +- +- if (entityToSpectate != null) { +- this.serverLevel().getChunkSource().move(this); +- } +- ++ // Folia - region threading - move down ++ ++ // Folia - region threading - not needed ++ ++ // Folia start - region threading - handle camera setting better ++ if (this.camera == this ++ || (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.camera) && this.camera.moonrise$getTrackedEntity() != null ++ && this.camera.moonrise$getTrackedEntity().seenBy.contains(this.connection))) { ++ // Folia end - region threading - handle camera setting better + this.connection.send(new ClientboundSetCameraPacket(this.camera)); +- this.connection.resetPosition(); ++ } // Folia - region threading - handle camera setting better ++ //this.connection.resetPosition(); // Folia - region threading - not needed ++ this.teleportToCameraOffRegion(); // Folia - region threading - moved down + } + } + @@ -2896,11 +_,11 @@ } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch index b4f4b03631..5cf56e2f66 100644 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch +++ b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch @@ -27,7 +27,7 @@ private EntityDimensions dimensions; private float eyeHeight; public boolean isInPowderSnow; -@@ -525,6 +_,19 @@ +@@ -525,6 +_,23 @@ } } // Paper end - optimise entity tracker @@ -43,6 +43,10 @@ + this.pistonDeltasGameTime += fromRedstoneTimeOffset; + } + } ++ ++ public boolean canBeSpectated() { ++ return !this.getBukkitEntity().taskScheduler.isRetired(); ++ } + // Folia end - region ticking public Entity(EntityType entityType, Level level) { @@ -992,6 +996,32 @@ protected void removeAfterChangingDimensions() { this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION, null); // CraftBukkit - add Bukkit remove cause if (this instanceof Leashable leashable && leashable.isLeashed()) { // Paper - only call if it is leashed +@@ -4246,6 +_,12 @@ + } + + public void startSeenByPlayer(ServerPlayer serverPlayer) { ++ // Folia start - region threading ++ if (serverPlayer.getCamera() == this) { ++ // set camera again ++ serverPlayer.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCameraPacket(this)); ++ } ++ // Folia end - region threading + } + + public void stopSeenByPlayer(ServerPlayer serverPlayer) { +@@ -4255,6 +_,12 @@ + new io.papermc.paper.event.player.PlayerUntrackEntityEvent(serverPlayer.getBukkitEntity(), this.getBukkitEntity()).callEvent(); + } + // Paper end - entity tracking events ++ // Folia start - region threading ++ if (serverPlayer.getCamera() == this) { ++ // unset camera, the player tick method should TP us close enough again to invoke startSeenByPlayer ++ serverPlayer.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCameraPacket(serverPlayer)); ++ } ++ // Folia end - region threading + } + + public float rotate(Rotation transformRotation) { @@ -4790,7 +_,8 @@ } } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch index c93d7ece46..408f1a25ee 100644 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch +++ b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch @@ -9,7 +9,7 @@ protected int autoSpinAttackTicks; protected float autoSpinAttackDmg; @Nullable -@@ -307,6 +_,21 @@ +@@ -307,6 +_,26 @@ return this.getYHeadRot(); } // CraftBukkit end @@ -23,6 +23,11 @@ + } + + @Override ++ public boolean canBeSpectated() { ++ return super.canBeSpectated() && this.getHealth() > 0.0F; ++ } ++ ++ @Override + protected void resetStoredPositions() { + super.resetStoredPositions(); + this.lastClimbablePos = Optional.empty();