diff --git a/src/main/java/net/minestom/server/ServerFlag.java b/src/main/java/net/minestom/server/ServerFlag.java index d68660ebb..da00eeb0c 100644 --- a/src/main/java/net/minestom/server/ServerFlag.java +++ b/src/main/java/net/minestom/server/ServerFlag.java @@ -15,6 +15,7 @@ public final class ServerFlag { public static final int SERVER_TICKS_PER_SECOND = Integer.getInteger("minestom.tps", 20); public static final int CHUNK_VIEW_DISTANCE = Integer.getInteger("minestom.chunk-view-distance", 8); public static final int ENTITY_VIEW_DISTANCE = Integer.getInteger("minestom.entity-view-distance", 5); + public static final int ENTITY_SYNCHRONIZATION_TICKS = Integer.getInteger("minestom.entity-synchronization-ticks", 20); public static final int WORKER_COUNT = Integer.getInteger("minestom.workers", Runtime.getRuntime().availableProcessors()); public static final int MAX_PACKET_SIZE = Integer.getInteger("minestom.max-packet-size", 2_097_151); // 3 bytes var-int public static final int SOCKET_SEND_BUFFER_SIZE = Integer.getInteger("minestom.send-buffer-size", 262_143); diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 8504f739e..73cf3e8db 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -5,10 +5,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEvent.ShowEntity; import net.kyori.adventure.text.event.HoverEventSource; -import net.minestom.server.MinecraftServer; -import net.minestom.server.ServerProcess; -import net.minestom.server.Tickable; -import net.minestom.server.Viewable; +import net.minestom.server.*; import net.minestom.server.collision.*; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Pos; @@ -31,8 +28,6 @@ import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockFace; import net.minestom.server.instance.block.BlockHandler; import net.minestom.server.network.packet.server.CachedPacket; -import net.minestom.server.network.packet.server.LazyPacket; -import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.play.*; import net.minestom.server.permission.Permission; import net.minestom.server.permission.PermissionHandler; @@ -57,7 +52,6 @@ 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.time.Cooldown; import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.ApiStatus; @@ -169,10 +163,9 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev protected EntityType entityType; // UNSAFE to change, modify at your own risk - // Network synchronization, send the absolute position of the entity each X milliseconds - private static final Duration SYNCHRONIZATION_COOLDOWN = Duration.of(1, TimeUnit.MINUTE); - private Duration customSynchronizationCooldown; - private long lastAbsoluteSynchronizationTime; + // Network synchronization, send the absolute position of the entity every n ticks + private long synchronizationTicks = ServerFlag.ENTITY_SYNCHRONIZATION_TICKS; + private long nextSynchronizationTick = synchronizationTicks; protected Metadata metadata = new Metadata(this); protected EntityMeta entityMeta; @@ -582,7 +575,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev effectTick(time); } // Scheduled synchronization - if (!Cooldown.hasCooldown(time, lastAbsoluteSynchronizationTime, getSynchronizationCooldown())) { + if (ticks >= nextSynchronizationTick) { synchronizePosition(false); } } @@ -1371,6 +1364,11 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev this.position = position; this.previousPosition = previousPosition; if (!position.samePoint(previousPosition)) refreshCoordinate(position); + if (nextSynchronizationTick <= ticks + 1) { + // The entity will be synchronized at the end of its tick + // not returning here will duplicate position packets + return; + } // Update viewers final boolean viewChange = !position.sameView(lastSyncedPosition); final double distanceX = Math.abs(position.x() - lastSyncedPosition.x()); @@ -1381,7 +1379,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev final Chunk chunk = getChunk(); if (distanceX > 8 || distanceY > 8 || distanceZ > 8) { PacketUtils.prepareViewablePacket(chunk, new EntityTeleportPacket(getEntityId(), position, isOnGround()), this); - this.lastAbsoluteSynchronizationTime = System.currentTimeMillis(); + nextSynchronizationTick = synchronizationTicks + 1; } else if (positionChange && viewChange) { PacketUtils.prepareViewablePacket(chunk, EntityPositionAndRotationPacket.getPacket(getEntityId(), position, lastSyncedPosition, isOnGround()), this); @@ -1681,9 +1679,11 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev @ApiStatus.Internal protected void synchronizePosition(boolean includeSelf) { final Pos posCache = this.position; - final ServerPacket packet = new EntityTeleportPacket(getEntityId(), posCache, isOnGround()); - PacketUtils.prepareViewablePacket(currentChunk, packet, this); - this.lastAbsoluteSynchronizationTime = System.currentTimeMillis(); + PacketUtils.prepareViewablePacket(currentChunk, new EntityTeleportPacket(getEntityId(), posCache, isOnGround()), this); + if (posCache.yaw() != lastSyncedPosition.yaw()) { + PacketUtils.prepareViewablePacket(currentChunk, new EntityHeadLookPacket(getEntityId(), position.yaw()), this); + } + nextSynchronizationTick = ticks + synchronizationTicks; this.lastSyncedPosition = posCache; } @@ -1693,28 +1693,34 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev } /** - * Asks for a synchronization (position) to happen during next entity tick. + * Asks for a position synchronization to happen during next entity tick. */ - public void askSynchronization() { - this.lastAbsoluteSynchronizationTime = 0; + public void synchronizeNextTick() { + this.nextSynchronizationTick = 0; } /** - * Set custom cooldown for position synchronization. + * Returns the current synchronization interval. The default value is {@link ServerFlag#ENTITY_SYNCHRONIZATION_TICKS} + * but can be overridden per entity with {@link #setSynchronizationTicks(long)}. * - * @param cooldown custom cooldown for position synchronization. + * @return The current synchronization ticks */ - public void setCustomSynchronizationCooldown(@Nullable Duration cooldown) { - this.customSynchronizationCooldown = cooldown; + public long getSynchronizationTicks() { + return this.synchronizationTicks; + } + + /** + * Set the tick period until this entity's position is synchronized. + * + * @param ticks the new synchronization tick period + */ + public void setSynchronizationTicks(long ticks) { + this.synchronizationTicks = ticks; } @Override public @NotNull HoverEvent asHoverEvent(@NotNull UnaryOperator op) { - return HoverEvent.showEntity(ShowEntity.of(this.entityType, this.uuid)); - } - - private Duration getSynchronizationCooldown() { - return Objects.requireNonNullElse(this.customSynchronizationCooldown, SYNCHRONIZATION_COOLDOWN); + return HoverEvent.showEntity(ShowEntity.showEntity(this.entityType, this.uuid)); } @ApiStatus.Experimental