mirror of
https://github.com/Minestom/Minestom.git
synced 2025-02-16 04:11:39 +01:00
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:
parent
621f38c6a3
commit
59ea880d26
@ -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()) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
13
src/main/java/net/minestom/server/entity/RelativeFlags.java
Normal file
13
src/main/java/net/minestom/server/entity/RelativeFlags.java
Normal 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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user