mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-27 19:47:44 +01:00
chore: support relative velocity in entity teleportation
This commit is contained in:
parent
c44fe9db46
commit
8dfe4a20c6
@ -274,6 +274,31 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
||||
}
|
||||
}
|
||||
|
||||
public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position) {
|
||||
return teleport(position, null, RelativeFlags.NONE);
|
||||
}
|
||||
|
||||
public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position, @NotNull Vec velocity) {
|
||||
return teleport(position, velocity, null, RelativeFlags.NONE);
|
||||
}
|
||||
|
||||
public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position, long @Nullable [] chunks,
|
||||
@MagicConstant(flagsFromClass = RelativeFlags.class) int flags) {
|
||||
return teleport(position, chunks, flags, true);
|
||||
}
|
||||
|
||||
public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position, @NotNull Vec velocity, long @Nullable [] chunks,
|
||||
@MagicConstant(flagsFromClass = RelativeFlags.class) int flags) {
|
||||
return teleport(position, velocity, chunks, flags, true);
|
||||
}
|
||||
|
||||
public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position, long @Nullable [] chunks,
|
||||
@MagicConstant(flagsFromClass = RelativeFlags.class) int flags,
|
||||
boolean shouldConfirm) {
|
||||
// Use delta coord if not providing a delta velocity (to avoid resetting velocity)
|
||||
return teleport(position, Vec.ZERO, chunks, flags | RelativeFlags.DELTA_COORD, shouldConfirm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleports the entity only if the chunk at {@code position} is loaded or if
|
||||
* {@link Instance#hasEnabledAutoChunkLoad()} returns true.
|
||||
@ -287,7 +312,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
||||
* @param shouldConfirm if false, the teleportation will be done without confirmation
|
||||
* @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, @NotNull Vec velocity, long @Nullable [] chunks,
|
||||
@MagicConstant(flagsFromClass = RelativeFlags.class) int flags,
|
||||
boolean shouldConfirm) {
|
||||
Check.stateCondition(instance == null, "You need to use Entity#setInstance before teleporting an entity!");
|
||||
@ -296,12 +321,14 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
||||
EventDispatcher.call(event);
|
||||
|
||||
final Pos globalPosition = PositionUtils.getPositionWithRelativeFlags(this.position, position, flags);
|
||||
final Vec globalVelocity = PositionUtils.getVelocityWithRelativeFlags(this.velocity, velocity, flags);
|
||||
|
||||
final Runnable endCallback = () -> {
|
||||
this.previousPosition = this.position;
|
||||
this.position = globalPosition;
|
||||
this.velocity = globalVelocity;
|
||||
refreshCoordinate(globalPosition);
|
||||
if (this instanceof Player player) player.synchronizePositionAfterTeleport(position, flags, shouldConfirm);
|
||||
if (this instanceof Player player) player.synchronizePositionAfterTeleport(position, velocity, flags, shouldConfirm);
|
||||
else synchronizePosition();
|
||||
};
|
||||
|
||||
@ -320,15 +347,6 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
||||
}
|
||||
}
|
||||
|
||||
public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position, long @Nullable [] chunks,
|
||||
@MagicConstant(flagsFromClass = RelativeFlags.class) int flags) {
|
||||
return teleport(position, chunks, flags, true);
|
||||
}
|
||||
|
||||
public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position) {
|
||||
return teleport(position, null, RelativeFlags.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the view of the entity.
|
||||
*
|
||||
@ -1236,8 +1254,9 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
||||
final Chunk chunk = getChunk();
|
||||
assert chunk != null;
|
||||
if (distanceX > 8 || distanceY > 8 || distanceZ > 8) {
|
||||
// TODO(1.21.2) should we be setting delta to zero?
|
||||
PacketViewableUtils.prepareViewablePacket(chunk, new EntityTeleportPacket(getEntityId(), position, Vec.ZERO, 0, isOnGround()), this);
|
||||
// Send relative 0 velocity to avoid affecting it in this case
|
||||
PacketViewableUtils.prepareViewablePacket(chunk, new EntityTeleportPacket(getEntityId(), position,
|
||||
Vec.ZERO, RelativeFlags.DELTA_COORD, isOnGround()), this);
|
||||
nextSynchronizationTick = synchronizationTicks + 1;
|
||||
} else if (positionChange && viewChange) {
|
||||
PacketViewableUtils.prepareViewablePacket(chunk, EntityPositionAndRotationPacket.getPacket(getEntityId(), position,
|
||||
@ -1521,8 +1540,8 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to synchronize entity position with viewers by sending an
|
||||
* {@link EntityTeleportPacket} and {@link EntityHeadLookPacket} to viewers.
|
||||
* Used to synchronize entity position with viewers by sending a full
|
||||
* {@link EntityPositionSyncPacket} to viewers.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
protected void synchronizePosition() {
|
||||
|
@ -94,6 +94,7 @@ import net.minestom.server.utils.time.Cooldown;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import net.minestom.server.world.DimensionType;
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
import org.jctools.queues.MpscArrayQueue;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -733,7 +734,7 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou
|
||||
sendPendingChunks(); // Send available first chunk immediately to prevent falling through the floor
|
||||
}
|
||||
|
||||
synchronizePositionAfterTeleport(spawnPosition, 0, true); // So the player doesn't get stuck
|
||||
synchronizePositionAfterTeleport(spawnPosition, Vec.ZERO, RelativeFlags.NONE, true); // So the player doesn't get stuck
|
||||
|
||||
if (dimensionChange) {
|
||||
sendPacket(new SpawnPositionPacket(spawnPosition, 0));
|
||||
@ -811,7 +812,7 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou
|
||||
// In the vanilla server they have an anticheat which teleports the client back if they enter the floor,
|
||||
// but since Minestom does not have an anticheat this provides a similar effect.
|
||||
if (needsChunkPositionSync) {
|
||||
synchronizePositionAfterTeleport(getPosition(), RelativeFlags.NONE, true);
|
||||
synchronizePositionAfterTeleport(getPosition(), Vec.ZERO, RelativeFlags.NONE, true);
|
||||
needsChunkPositionSync = false;
|
||||
}
|
||||
} finally {
|
||||
@ -1818,10 +1819,11 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou
|
||||
* @param shouldConfirm if false, the teleportation will be done without confirmation
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
void synchronizePositionAfterTeleport(@NotNull Pos position, int relativeFlags, boolean shouldConfirm) {
|
||||
void synchronizePositionAfterTeleport(@NotNull Pos position, @NotNull Point velocity,
|
||||
@MagicConstant(flagsFromClass = RelativeFlags.class) int relativeFlags,
|
||||
boolean shouldConfirm) {
|
||||
int teleportId = shouldConfirm ? getNextTeleportId() : -1;
|
||||
// TODO(1.21.2): should delta be zero?
|
||||
sendPacket(new PlayerPositionAndLookPacket(teleportId, position, Vec.ZERO, position.yaw(), position.pitch(), (byte) relativeFlags));
|
||||
sendPacket(new PlayerPositionAndLookPacket(teleportId, position, velocity, position.yaw(), position.pitch(), relativeFlags));
|
||||
super.synchronizePosition();
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ public class RelativeFlags {
|
||||
|
||||
public static final int COORD = X | Y | Z;
|
||||
public static final int VIEW = YAW | PITCH;
|
||||
public static final int DELTA = DELTA_X | DELTA_Y | DELTA_Z | ROTATE_DELTA;
|
||||
public static final int DELTA_COORD = DELTA_X | DELTA_Y | DELTA_Z;
|
||||
public static final int DELTA = DELTA_COORD | ROTATE_DELTA;
|
||||
public static final int ALL = COORD | VIEW | DELTA;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package net.minestom.server.item;
|
||||
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.util.RGBLike;
|
||||
import net.minestom.server.color.Color;
|
||||
@ -95,8 +96,8 @@ public final class ItemComponent {
|
||||
public static final DataComponent<List<ItemStack>> CONTAINER = register("container", ItemStack.NETWORK_TYPE.list(256), BinaryTagSerializer.ITEM.list());
|
||||
public static final DataComponent<ItemBlockState> BLOCK_STATE = register("block_state", ItemBlockState.NETWORK_TYPE, ItemBlockState.NBT_TYPE);
|
||||
public static final DataComponent<List<Bee>> BEES = register("bees", Bee.NETWORK_TYPE.list(Short.MAX_VALUE), Bee.NBT_TYPE.list());
|
||||
// TODO(1.21.2) Updated NBT format > https://minecraft.wiki/w/Java_Edition_1.21.2#Data_components_3
|
||||
public static final DataComponent<String> LOCK = register("lock", null, BinaryTagSerializer.STRING);
|
||||
// Lock is an item predicate which we do not support, but can be user-represented as a compound tag (an empty tag would match everything).
|
||||
public static final DataComponent<CompoundBinaryTag> LOCK = register("lock", null, BinaryTagSerializer.COMPOUND);
|
||||
public static final DataComponent<SeededContainerLoot> CONTAINER_LOOT = register("container_loot", null, SeededContainerLoot.NBT_TYPE);
|
||||
|
||||
public static final NetworkBuffer.Type<DataComponentMap> PATCH_NETWORK_TYPE = DataComponentMap.patchNetworkType(ItemComponent::fromId);
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.minestom.server.utils.position;
|
||||
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.entity.RelativeFlags;
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@ -35,4 +36,11 @@ public final class PositionUtils {
|
||||
float pitch = (flags & RelativeFlags.PITCH) == 0 ? modifier.pitch() : start.pitch() + modifier.pitch();
|
||||
return new Pos(x, y, z, yaw, pitch);
|
||||
}
|
||||
|
||||
public static @NotNull Vec getVelocityWithRelativeFlags(@NotNull Vec start, @NotNull Vec modifier, @MagicConstant(flagsFromClass = RelativeFlags.class) int flags) {
|
||||
double x = (flags & RelativeFlags.DELTA_X) == 0 ? modifier.x() : start.x() + modifier.x();
|
||||
double y = (flags & RelativeFlags.DELTA_Y) == 0 ? modifier.y() : start.y() + modifier.y();
|
||||
double z = (flags & RelativeFlags.DELTA_Z) == 0 ? modifier.z() : start.z() + modifier.z();
|
||||
return new Vec(x, y, z);
|
||||
}
|
||||
}
|
||||
|
@ -248,7 +248,8 @@ public class PlayerIntegrationTest {
|
||||
|
||||
assertEquals(startingPlayerPos.withView(30, 20), player.getPosition());
|
||||
tracker.assertSingle(PlayerPositionAndLookPacket.class, packet -> {
|
||||
assertEquals(RelativeFlags.COORD, packet.flags());
|
||||
// Should be relative coord and velocity because we are only trying to change the view.
|
||||
assertEquals(RelativeFlags.COORD | RelativeFlags.DELTA_COORD, packet.flags());
|
||||
assertEquals(new Pos(0, 0, 0, 30, 20), packet.position());
|
||||
});
|
||||
}
|
||||
|
@ -14,8 +14,7 @@ public class StringTest extends AbstractItemComponentTest<String> {
|
||||
// as a reminder that tests should be added for that new component type.
|
||||
private static final List<DataComponent<String>> SHARED_COMPONENTS = List.of(
|
||||
ItemComponent.INSTRUMENT,
|
||||
ItemComponent.NOTE_BLOCK_SOUND,
|
||||
ItemComponent.LOCK
|
||||
ItemComponent.NOTE_BLOCK_SOUND
|
||||
);
|
||||
|
||||
@Override
|
||||
|
@ -24,7 +24,9 @@ import net.minestom.server.network.packet.server.play.*;
|
||||
import net.minestom.server.network.packet.server.status.ResponsePacket;
|
||||
import net.minestom.server.network.player.GameProfile;
|
||||
import net.minestom.server.recipe.Recipe;
|
||||
import net.minestom.server.recipe.RecipeBookCategory;
|
||||
import net.minestom.server.recipe.RecipeProperty;
|
||||
import net.minestom.server.recipe.display.RecipeDisplay;
|
||||
import net.minestom.server.recipe.display.SlotDisplay;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -80,7 +82,12 @@ public class PacketWriteReadTest {
|
||||
SERVER_PACKETS.add(new ClearTitlesPacket(false));
|
||||
SERVER_PACKETS.add(new CloseWindowPacket((byte) 2));
|
||||
SERVER_PACKETS.add(new CollectItemPacket(5, 5, 5));
|
||||
// SERVER_PACKETS.add(new PlaceGhostRecipePacket((byte) 2, "recipe")); // TODO(1.21.2)
|
||||
var recipeDisplay = new RecipeDisplay.CraftingShapeless(
|
||||
List.of(new SlotDisplay.Item(Material.STONE)),
|
||||
new SlotDisplay.Item(Material.STONE_BRICKS),
|
||||
new SlotDisplay.Item(Material.CRAFTING_TABLE)
|
||||
);
|
||||
SERVER_PACKETS.add(new PlaceGhostRecipePacket(0, recipeDisplay));
|
||||
SERVER_PACKETS.add(new DeathCombatEventPacket(5, COMPONENT));
|
||||
SERVER_PACKETS.add(new DeclareRecipesPacket(Map.of(
|
||||
RecipeProperty.SMITHING_BASE, List.of(Material.STONE),
|
||||
@ -93,7 +100,9 @@ public class PacketWriteReadTest {
|
||||
List.of(new DeclareRecipesPacket.StonecutterRecipe(new Recipe.Ingredient(Material.DIAMOND),
|
||||
new SlotDisplay.ItemStack(ItemStack.of(Material.GOLD_BLOCK))))
|
||||
));
|
||||
// TODO(1.21.2) recipe book add/remove
|
||||
SERVER_PACKETS.add(new RecipeBookAddPacket(List.of(new RecipeBookAddPacket.Entry(1, recipeDisplay, null,
|
||||
RecipeBookCategory.CRAFTING_MISC, List.of(new Recipe.Ingredient(Material.STONE)), true, true)), false));
|
||||
SERVER_PACKETS.add(new RecipeBookRemovePacket(List.of(1)));
|
||||
|
||||
SERVER_PACKETS.add(new DestroyEntitiesPacket(List.of(5, 5, 5)));
|
||||
SERVER_PACKETS.add(new DisconnectPacket(COMPONENT));
|
||||
|
Loading…
Reference in New Issue
Block a user