feat: added player#teleportWithFlags for relative teleportation (#2029)

* feat: added player#teleportWithFlags for relative teleportation

* requested changes and improvements

* add the new teleport method override player

* chore: cleanup

* chore: change relative flags from enums to constants

---------

Co-authored-by: DeidaraMC <DeidaraMC>
This commit is contained in:
DeidaraMC 2024-03-27 21:45:55 -04:00 committed by GitHub
parent 621f38c6a3
commit 59ea880d26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 79 additions and 30 deletions

View File

@ -52,8 +52,10 @@ import net.minestom.server.utils.chunk.ChunkCache;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.entity.EntityUtils;
import net.minestom.server.utils.player.PlayerUtils;
import net.minestom.server.utils.position.PositionUtils;
import net.minestom.server.utils.time.TimeUnit;
import net.minestom.server.utils.validate.Check;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -305,16 +307,20 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
* @param chunks the chunk indexes to load before teleporting the entity,
* indexes are from {@link ChunkUtils#getChunkIndex(int, int)},
* can be null or empty to only load the chunk at {@code position}
* @param flags flags used to teleport the entity relatively rather than absolutely
* use {@link RelativeFlags} to see available flags
* @throws IllegalStateException if you try to teleport an entity before settings its instance
*/
public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position, long @Nullable [] chunks) {
public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position, long @Nullable [] chunks,
@MagicConstant(flagsFromClass = RelativeFlags.class) int flags) {
Check.stateCondition(instance == null, "You need to use Entity#setInstance before teleporting an entity!");
final Pos globalPosition = PositionUtils.getPositionWithRelativeFlags(this.position, position, flags);
final Runnable endCallback = () -> {
this.previousPosition = this.position;
this.position = position;
refreshCoordinate(position);
synchronizePosition(true);
sendPacketToViewers(new EntityHeadLookPacket(getEntityId(), position.yaw()));
this.position = globalPosition;
refreshCoordinate(globalPosition);
if (this instanceof Player player) player.synchronizePositionAfterTeleport(position, flags);
else synchronizePosition();
};
if (chunks != null && chunks.length > 0) {
@ -322,9 +328,9 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
return ChunkUtils.optionalLoadAll(instance, chunks, null).thenRun(endCallback);
}
final Pos currentPosition = this.position;
if (!currentPosition.sameChunk(position)) {
if (!currentPosition.sameChunk(globalPosition)) {
// Ensure that the chunk is loaded
return instance.loadOptionalChunk(position).thenRun(endCallback);
return instance.loadOptionalChunk(globalPosition).thenRun(endCallback);
} else {
// Position is in the same chunk, keep it sync
endCallback.run();
@ -333,7 +339,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
}
public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position) {
return teleport(position, null);
return teleport(position, null, RelativeFlags.NONE);
}
/**
@ -572,7 +578,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
}
// Scheduled synchronization
if (ticks >= nextSynchronizationTick) {
synchronizePosition(false);
synchronizePosition();
}
}
@ -962,7 +968,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
sendPacketToViewersAndSelf(getPassengersPacket());
// Updates the position of the new passenger, and then teleports the passenger
updatePassengerPosition(position, entity);
entity.synchronizePosition(false);
entity.synchronizePosition();
}
/**
@ -977,7 +983,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
if (!passengers.remove(entity)) return;
entity.vehicle = null;
sendPacketToViewersAndSelf(getPassengersPacket());
entity.synchronizePosition(false);
entity.synchronizePosition();
}
/**
@ -1345,7 +1351,8 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
*
* @param newPosition the new position
*/
private void refreshCoordinate(Point newPosition) {
@ApiStatus.Internal
protected void refreshCoordinate(Point newPosition) {
// Passengers update
final Set<Entity> passengers = getPassengers();
if (!passengers.isEmpty()) {
@ -1579,15 +1586,10 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
/**
* Used to synchronize entity position with viewers by sending an
* {@link EntityTeleportPacket} to viewers, in case of a player this is
* overridden in order to send an additional {@link PlayerPositionAndLookPacket}
* to itself.
*
* @param includeSelf if {@code true} and this is a {@link Player} an additional {@link PlayerPositionAndLookPacket}
* will be sent to the player itself
* {@link EntityTeleportPacket} and {@link EntityHeadLookPacket} to viewers.
*/
@ApiStatus.Internal
protected void synchronizePosition(boolean includeSelf) {
protected void synchronizePosition() {
final Pos posCache = this.position;
PacketUtils.prepareViewablePacket(currentChunk, new EntityTeleportPacket(getEntityId(), posCache, isOnGround()), this);
if (posCache.yaw() != lastSyncedPosition.yaw()) {

View File

@ -736,7 +736,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
ChunkUtils.forChunksInRange(spawnPosition, MinecraftServer.getChunkViewDistance(), chunkAdder);
}
synchronizePosition(true); // So the player doesn't get stuck
synchronizePositionAfterTeleport(spawnPosition, 0); // So the player doesn't get stuck
if (dimensionChange) {
sendPacket(new SpawnPositionPacket(spawnPosition, 0));
@ -1822,15 +1822,17 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
}
/**
* @see Entity#synchronizePosition(boolean)
* Used to synchronize player position with viewers on spawn or after {@link Entity#teleport(Pos, long[], RelativeFlags...)}
* in cases where a {@link PlayerPositionAndLookPacket} is required
*
* @param position the position used by {@link PlayerPositionAndLookPacket}
* this may not be the same as the {@link Entity#position}
* @param relativeFlags byte flags used by {@link PlayerPositionAndLookPacket}
*/
@Override
@ApiStatus.Internal
protected void synchronizePosition(boolean includeSelf) {
if (includeSelf) {
sendPacket(new PlayerPositionAndLookPacket(position, (byte) 0x00, getNextTeleportId()));
}
super.synchronizePosition(includeSelf);
void synchronizePositionAfterTeleport(@NotNull Pos position, int relativeFlags) {
sendPacket(new PlayerPositionAndLookPacket(position, (byte) relativeFlags, getNextTeleportId()));
super.synchronizePosition();
}
/**
@ -2370,10 +2372,13 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
}
}
/**
* @see #teleport(Pos, long[], int)
*/
@Override
public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position, long @Nullable [] chunks) {
public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position, long @Nullable [] chunks, int flags) {
chunkUpdateLimitChecker.clearHistory();
return super.teleport(position, chunks);
return super.teleport(position, chunks, flags);
}
/**

View File

@ -0,0 +1,13 @@
package net.minestom.server.entity;
public class RelativeFlags {
public static final int NONE = 0x00;
public static final int X = 0x01;
public static final int Y = 0x02;
public static final int Z = 0x04;
public static final int YAW = 0x08;
public static final int PITCH = 0x10;
public static final int COORD = X | Y | Z;
public static final int VIEW = YAW | PITCH;
public static final int ALL = COORD | VIEW;
}

View File

@ -1,6 +1,8 @@
package net.minestom.server.utils.position;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.RelativeFlags;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@ -24,4 +26,13 @@ public final class PositionUtils {
final double radians = -Math.atan2(dy, Math.max(Math.abs(dx), Math.abs(dz)));
return (float) Math.toDegrees(radians);
}
public static @NotNull Pos getPositionWithRelativeFlags(@NotNull Pos start, @NotNull Pos modifier, @MagicConstant(flagsFromClass = RelativeFlags.class) int flags) {
double x = (flags & RelativeFlags.X) == 0 ? modifier.x() : start.x() + modifier.x();
double y = (flags & RelativeFlags.Y) == 0 ? modifier.y() : start.y() + modifier.y();
double z = (flags & RelativeFlags.Z) == 0 ? modifier.z() : start.z() + modifier.z();
float yaw = (flags & RelativeFlags.YAW) == 0 ? modifier.yaw() : start.yaw() + modifier.yaw();
float pitch = (flags & RelativeFlags.PITCH) == 0 ? modifier.pitch() : start.pitch() + modifier.pitch();
return new Pos(x, y, z, yaw, pitch);
}
}

View File

@ -53,7 +53,7 @@ public class EntityTeleportIntegrationTest {
var tracker = connection.trackIncoming(ServerPacket.class);
var viewerTracker = viewerConnection.trackIncoming(ServerPacket.class);
var teleportPosition = new Pos(1, 42, 1);
var teleportPosition = new Pos(1, 42, 1).withYaw(5);
player.teleport(teleportPosition).join();
assertEquals(teleportPosition, player.getPosition());
@ -88,4 +88,21 @@ public class EntityTeleportIntegrationTest {
player.teleport(teleportPosition).join();
assertEquals(teleportPosition, player.getPosition());
}
@Test
public void playerTeleportWithFlagsTest(Env env) {
var instance = env.createFlatInstance();
var connection = env.createConnection();
var player = connection.connect(instance, new Pos(0, 0, 0)).join();
player.teleport(new Pos(10, 10, 10, 90, 0)).join();
assertEquals(player.getPosition(), new Pos(10, 10, 10, 90, 0));
player.teleport(new Pos(0, 0, 0, 0, 0), null, RelativeFlags.ALL).join();
assertEquals(player.getPosition(), new Pos(10, 10, 10, 90, 0));
var tracker = connection.trackIncoming(PlayerPositionAndLookPacket.class);
player.teleport(new Pos(5, 10, 2, 5, 5), null, RelativeFlags.VIEW).join();
assertEquals(player.getPosition(), new Pos(5, 10, 2, 95, 5));
}
}

View File

@ -22,6 +22,7 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import static net.minestom.server.entity.Player.*;
import static org.junit.jupiter.api.Assertions.*;
@EnvTest