mirror of
https://github.com/PaperMC/Folia.git
synced 2025-04-22 20:29:19 +08:00
Fix spectator camera implementation
There are several problems that were solved: 1. Spectating failed to follow players through cross-region/cross-world teleporting. 2. Inability to set camera to off-region entity 3. Various crashes involved with off-region camera entity 4. Spectator does not always follow entities through portals We fix #1 by sending ClientboundSetCameraPacket when the spectated entity becomes in tracked, as the client appears to be unable to correctly handle when the camera entity is removed, even temporarily. We fix #2 by allowing off-region entities that are not dead. However, this required the teleportation code in both setCamera and the ServerPlayer#tick method to be modified to correctly handle off-region camera targets. We fix #3 by correctly teleporting to off-region locations when needed. We fix #4 by following the Bukkit entity. This allows spectator mode to properly follow non-player entities through portals. Issues #1 (cross-world) and #4 are probably present in Paper/Vanilla, however solving #2 and #3 required a solution for these. Fixes https://github.com/PaperMC/Folia/issues/324
This commit is contained in:
parent
e7bb50eac6
commit
ec78da5ae0
@ -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<Entity> onComplete) {
|
||||
+ public static <T extends Entity> 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<Entity> onComplete) {
|
||||
+ teleport(from, useFromRootVehicle, to, yaw, pitch, teleportFlags, cause, onComplete, null);
|
||||
+ }
|
||||
+
|
||||
+ public static <T extends Entity> 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<Entity> onComplete,
|
||||
+ final java.util.function.Predicate<T> preTeleport) {
|
||||
+ // retrieve coordinates
|
||||
+ final CallbackCompletable<Location> 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
|
||||
|
@ -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 @@
|
||||
}
|
||||
|
||||
|
@ -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 @@
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user