Network improvements (#2324)

This commit is contained in:
TheMode 2024-08-28 20:50:21 +02:00 committed by Matt Worzala
parent 561470c82a
commit 3f079d252e
192 changed files with 4235 additions and 3909 deletions

View File

@ -1,6 +1,7 @@
package net.minestom.server.utils;
import net.minestom.server.utils.binary.BinaryBuffer;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.packet.PacketVanilla;
import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.L_Result;
@ -11,7 +12,7 @@ import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE;
@Outcome(id = "2", expect = ACCEPTABLE)
@State
public class ObjectPoolTest {
private final ObjectPool<BinaryBuffer> pool = ObjectPool.BUFFER_POOL;
private final ObjectPool<NetworkBuffer> pool = PacketVanilla.PACKET_POOL;
@Actor
public void actor1() {

View File

@ -22,6 +22,7 @@ import net.minestom.server.message.ChatType;
import net.minestom.server.monitoring.BenchmarkManager;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.packet.PacketParser;
import net.minestom.server.network.packet.client.ClientPacket;
import net.minestom.server.network.packet.server.common.PluginMessagePacket;
import net.minestom.server.network.packet.server.play.ServerDifficultyPacket;
import net.minestom.server.network.socket.Server;
@ -30,7 +31,7 @@ import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.scoreboard.TeamManager;
import net.minestom.server.thread.TickSchedulerThread;
import net.minestom.server.timer.SchedulerManager;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.PacketSendingUtils;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import net.minestom.server.utils.validate.Check;
import net.minestom.server.world.Difficulty;
@ -40,7 +41,6 @@ import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnknownNullability;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
@ -80,13 +80,8 @@ public final class MinecraftServer implements MinecraftConstants {
@ApiStatus.Internal
public static ServerProcess updateProcess() {
ServerProcess process;
try {
process = new ServerProcessImpl();
serverProcess = process;
} catch (IOException e) {
throw new RuntimeException(e);
}
ServerProcess process = new ServerProcessImpl();
serverProcess = process;
return process;
}
@ -108,7 +103,7 @@ public final class MinecraftServer implements MinecraftConstants {
*/
public static void setBrandName(@NotNull String brandName) {
MinecraftServer.brandName = brandName;
PacketUtils.broadcastPlayPacket(PluginMessagePacket.brandPacket(brandName));
PacketSendingUtils.broadcastPlayPacket(PluginMessagePacket.brandPacket(brandName));
}
/**
@ -128,7 +123,7 @@ public final class MinecraftServer implements MinecraftConstants {
*/
public static void setDifficulty(@NotNull Difficulty difficulty) {
MinecraftServer.difficulty = difficulty;
PacketUtils.broadcastPlayPacket(new ServerDifficultyPacket(difficulty, true));
PacketSendingUtils.broadcastPlayPacket(new ServerDifficultyPacket(difficulty, true));
}
public static @UnknownNullability ServerProcess process() {
@ -188,7 +183,7 @@ public final class MinecraftServer implements MinecraftConstants {
return serverProcess.bossBar();
}
public static @NotNull PacketParser.Client getPacketParser() {
public static @NotNull PacketParser<ClientPacket> getPacketParser() {
return serverProcess.packetParser();
}

View File

@ -18,13 +18,7 @@ public final class ServerFlag {
public static final int CHUNK_VIEW_DISTANCE = intProperty("minestom.chunk-view-distance", 8);
public static final int ENTITY_VIEW_DISTANCE = intProperty("minestom.entity-view-distance", 5);
public static final int ENTITY_SYNCHRONIZATION_TICKS = intProperty("minestom.entity-synchronization-ticks", 20);
public static final int WORKER_COUNT = intProperty("minestom.workers", Runtime.getRuntime().availableProcessors());
public static final int DISPATCHER_THREADS = intProperty("minestom.dispatcher-threads", 1);
public static final int MAX_PACKET_SIZE = intProperty("minestom.max-packet-size", 2_097_151); // 3 bytes var-int
public static final int SOCKET_SEND_BUFFER_SIZE = intProperty("minestom.send-buffer-size", 262_143);
public static final int SOCKET_RECEIVE_BUFFER_SIZE = intProperty("minestom.receive-buffer-size", 32_767);
public static final boolean SOCKET_NO_DELAY = booleanProperty("minestom.tcp-no-delay", true);
public static final int POOLED_BUFFER_SIZE = intProperty("minestom.pooled-buffer-size", 262_143);
public static final int SEND_LIGHT_AFTER_BLOCK_PLACEMENT_DELAY = intProperty("minestom.send-light-after-block-placement-delay", 100);
public static final long LOGIN_PLUGIN_MESSAGE_TIMEOUT = longProperty("minestom.login-plugin-message-timeout", 5_000);
@ -34,6 +28,15 @@ public final class ServerFlag {
public static final long KEEP_ALIVE_DELAY = longProperty("minestom.keep-alive-delay", 10_000);
public static final long KEEP_ALIVE_KICK = longProperty("minestom.keep-alive-kick", 15_000);
// Network buffers
public static final int MAX_PACKET_SIZE = intProperty("minestom.max-packet-size", 2_097_151); // 3 bytes var-int
public static final int MAX_PACKET_SIZE_PRE_AUTH = intProperty("minestom.max-packet-size-pre-auth", 8_192);
public static final int SOCKET_SEND_BUFFER_SIZE = intProperty("minestom.send-buffer-size", 262_143);
public static final int SOCKET_RECEIVE_BUFFER_SIZE = intProperty("minestom.receive-buffer-size", 32_767);
public static final boolean SOCKET_NO_DELAY = booleanProperty("minestom.tcp-no-delay", true);
public static final int SOCKET_TIMEOUT = intProperty("minestom.socket-timeout", 15_000);
public static final int POOLED_BUFFER_SIZE = intProperty("minestom.pooled-buffer-size", 16_383);
// Chunk update
public static final float MIN_CHUNKS_PER_TICK = floatProperty("minestom.chunk-queue.min-per-tick", 0.01f);
public static final float MAX_CHUNKS_PER_TICK = floatProperty("minestom.chunk-queue.max-per-tick", 64.0f);

View File

@ -14,6 +14,7 @@ import net.minestom.server.listener.manager.PacketListenerManager;
import net.minestom.server.monitoring.BenchmarkManager;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.packet.PacketParser;
import net.minestom.server.network.packet.client.ClientPacket;
import net.minestom.server.network.socket.Server;
import net.minestom.server.recipe.RecipeManager;
import net.minestom.server.registry.Registries;
@ -103,7 +104,7 @@ public interface ServerProcess extends Registries, Snapshotable {
* <p>
* Can be used if you want to convert a buffer to a client packet object.
*/
@NotNull PacketParser.Client packetParser();
@NotNull PacketParser<ClientPacket> packetParser();
/**
* Exposed socket server.

View File

@ -28,6 +28,8 @@ import net.minestom.server.monitoring.BenchmarkManager;
import net.minestom.server.monitoring.TickMonitor;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.packet.PacketParser;
import net.minestom.server.network.packet.PacketVanilla;
import net.minestom.server.network.packet.client.ClientPacket;
import net.minestom.server.network.socket.Server;
import net.minestom.server.recipe.RecipeManager;
import net.minestom.server.registry.DynamicRegistry;
@ -37,7 +39,7 @@ import net.minestom.server.thread.Acquirable;
import net.minestom.server.thread.ThreadDispatcher;
import net.minestom.server.thread.ThreadProvider;
import net.minestom.server.timer.SchedulerManager;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.PacketViewableUtils;
import net.minestom.server.utils.collection.MappedCollection;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import net.minestom.server.world.DimensionType;
@ -77,7 +79,7 @@ final class ServerProcessImpl implements ServerProcess {
private final ConnectionManager connection;
private final PacketListenerManager packetListener;
private final PacketParser.Client packetParser;
private final PacketParser<ClientPacket> packetParser;
private final InstanceManager instance;
private final BlockManager block;
private final CommandManager command;
@ -98,7 +100,7 @@ final class ServerProcessImpl implements ServerProcess {
private final AtomicBoolean started = new AtomicBoolean();
private final AtomicBoolean stopped = new AtomicBoolean();
public ServerProcessImpl() throws IOException {
public ServerProcessImpl() {
this.exception = new ExceptionManager();
// The order of initialization here is relevant, we must load the enchantment util registries before the vanilla data is loaded.
@ -122,7 +124,7 @@ final class ServerProcessImpl implements ServerProcess {
this.connection = new ConnectionManager();
this.packetListener = new PacketListenerManager();
this.packetParser = new PacketParser.Client();
this.packetParser = PacketVanilla.CLIENT_PACKET_PARSER;
this.instance = new InstanceManager(this);
this.block = new BlockManager();
this.command = new CommandManager();
@ -287,7 +289,7 @@ final class ServerProcessImpl implements ServerProcess {
}
@Override
public @NotNull PacketParser.Client packetParser() {
public @NotNull PacketParser<ClientPacket> packetParser() {
return packetParser;
}
@ -379,10 +381,7 @@ final class ServerProcessImpl implements ServerProcess {
scheduler().processTickEnd();
// Flush all waiting packets
PacketUtils.flush();
// Server connection tick
server().tick();
PacketViewableUtils.flush();
// Monitoring
{

View File

@ -5,7 +5,7 @@ import net.minestom.server.adventure.audience.PacketGroupingAudience;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.server.SendablePacket;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.PacketSendingUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
@ -60,7 +60,7 @@ public interface Viewable {
*/
default void sendPacketToViewers(@NotNull SendablePacket packet) {
if (packet instanceof ServerPacket serverPacket) {
PacketUtils.sendGroupedPacket(getViewers(), serverPacket);
PacketSendingUtils.sendGroupedPacket(getViewers(), serverPacket);
} else {
getViewers().forEach(player -> player.sendPacket(packet));
}

View File

@ -20,7 +20,7 @@ import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.play.ActionBarPacket;
import net.minestom.server.network.packet.server.play.ClearTitlesPacket;
import net.minestom.server.network.packet.server.play.PlayerListHeaderAndFooterPacket;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.PacketSendingUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
@ -54,7 +54,7 @@ public interface PacketGroupingAudience extends ForwardingAudience {
* @param packet the packet to broadcast
*/
default void sendGroupedPacket(@NotNull ServerPacket packet) {
PacketUtils.sendGroupedPacket(getPlayers(), packet);
PacketSendingUtils.sendGroupedPacket(getPlayers(), packet);
}
@Deprecated

View File

@ -3,7 +3,7 @@ package net.minestom.server.adventure.bossbar;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.text.Component;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.PacketSendingUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
@ -29,33 +29,32 @@ class BossBarListener implements BossBar.Listener {
@Override
public void bossBarNameChanged(@NotNull BossBar bar, @NotNull Component oldName, @NotNull Component newName) {
this.doIfRegistered(bar, holder -> PacketUtils.sendGroupedPacket(holder.players, holder.createTitleUpdate(newName)));
this.doIfRegistered(bar, holder -> PacketSendingUtils.sendGroupedPacket(holder.players, holder.createTitleUpdate(newName)));
}
@Override
public void bossBarProgressChanged(@NotNull BossBar bar, float oldProgress, float newProgress) {
this.doIfRegistered(bar, holder -> PacketUtils.sendGroupedPacket(holder.players, holder.createPercentUpdate(newProgress)));
this.doIfRegistered(bar, holder -> PacketSendingUtils.sendGroupedPacket(holder.players, holder.createPercentUpdate(newProgress)));
}
@Override
public void bossBarColorChanged(@NotNull BossBar bar, @NotNull BossBar.Color oldColor, @NotNull BossBar.Color newColor) {
this.doIfRegistered(bar, holder -> PacketUtils.sendGroupedPacket(holder.players, holder.createColorUpdate(newColor)));
this.doIfRegistered(bar, holder -> PacketSendingUtils.sendGroupedPacket(holder.players, holder.createColorUpdate(newColor)));
}
@Override
public void bossBarOverlayChanged(@NotNull BossBar bar, BossBar.@NotNull Overlay oldOverlay, BossBar.@NotNull Overlay newOverlay) {
this.doIfRegistered(bar, holder -> PacketUtils.sendGroupedPacket(holder.players, holder.createOverlayUpdate(newOverlay)));
this.doIfRegistered(bar, holder -> PacketSendingUtils.sendGroupedPacket(holder.players, holder.createOverlayUpdate(newOverlay)));
}
@Override
public void bossBarFlagsChanged(@NotNull BossBar bar, @NotNull Set<BossBar.Flag> flagsAdded, @NotNull Set<BossBar.Flag> flagsRemoved) {
this.doIfRegistered(bar, holder -> PacketUtils.sendGroupedPacket(holder.players, holder.createFlagsUpdate()));
this.doIfRegistered(bar, holder -> PacketSendingUtils.sendGroupedPacket(holder.players, holder.createFlagsUpdate()));
}
private void doIfRegistered(@NotNull BossBar bar, @NotNull Consumer<BossBarHolder> consumer) {
BossBarHolder holder = this.manager.bars.get(bar);
if (holder != null) {
consumer.accept(holder);
}

View File

@ -4,7 +4,7 @@ import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.bossbar.BossBar;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.PacketSendingUtils;
import org.jetbrains.annotations.NotNull;
import java.util.*;
@ -75,7 +75,7 @@ public class BossBarManager {
BossBarHolder holder = this.getOrCreateHandler(bar);
Collection<Player> addedPlayers = players.stream().filter(holder::addViewer).toList();
if (!addedPlayers.isEmpty()) {
PacketUtils.sendGroupedPacket(addedPlayers, holder.createAddPacket());
PacketSendingUtils.sendGroupedPacket(addedPlayers, holder.createAddPacket());
}
}
@ -90,7 +90,7 @@ public class BossBarManager {
if (holder != null) {
Collection<Player> removedPlayers = players.stream().filter(holder::removeViewer).toList();
if (!removedPlayers.isEmpty()) {
PacketUtils.sendGroupedPacket(removedPlayers, holder.createRemovePacket());
PacketSendingUtils.sendGroupedPacket(removedPlayers, holder.createRemovePacket());
}
}
}
@ -103,7 +103,7 @@ public class BossBarManager {
public void destroyBossBar(@NotNull BossBar bossBar) {
BossBarHolder holder = this.bars.remove(bossBar);
if (holder != null) {
PacketUtils.sendGroupedPacket(holder.players, holder.createRemovePacket());
PacketSendingUtils.sendGroupedPacket(holder.players, holder.createRemovePacket());
for (Player player : holder.players) {
this.removePlayer(player, holder);
}

View File

@ -16,17 +16,7 @@ import java.util.Objects;
public final class AlphaColor extends Color {
private static final int BIT_MASK = 0xff;
public static final NetworkBuffer.Type<AlphaColor> NETWORK_TYPE = new NetworkBuffer.Type<AlphaColor>() {
@Override
public void write(@NotNull NetworkBuffer buffer, AlphaColor value) {
buffer.write(NetworkBuffer.INT, value.asARGB());
}
@Override
public AlphaColor read(@NotNull NetworkBuffer buffer) {
return new AlphaColor(buffer.read(NetworkBuffer.INT));
}
};
public static final NetworkBuffer.Type<AlphaColor> NETWORK_TYPE = NetworkBuffer.INT.transform(AlphaColor::new, AlphaColor::asARGB);
private final int alpha;
public AlphaColor(int alpha, int red, int green, int blue) {

View File

@ -17,17 +17,10 @@ import java.util.Objects;
public class Color implements RGBLike {
private static final int BIT_MASK = 0xff;
public static final NetworkBuffer.Type<RGBLike> NETWORK_TYPE = new NetworkBuffer.Type<RGBLike>() {
@Override
public void write(@NotNull NetworkBuffer buffer, RGBLike value) {
buffer.write(NetworkBuffer.INT, Color.fromRGBLike(value).asRGB());
}
@Override
public RGBLike read(@NotNull NetworkBuffer buffer) {
return new Color(buffer.read(NetworkBuffer.INT));
}
};
public static final NetworkBuffer.Type<RGBLike> NETWORK_TYPE = NetworkBuffer.INT.transform(
Color::new,
color -> Color.fromRGBLike(color).asRGB()
);
public static final BinaryTagSerializer<RGBLike> NBT_TYPE = BinaryTagSerializer.INT
.map(Color::new, color -> Color.fromRGBLike(color).asRGB());
private final int red;

View File

@ -37,9 +37,7 @@ public class ArgumentString extends Argument<String> {
@Override
public byte @Nullable [] nodeProperties() {
return NetworkBuffer.makeArray(buffer -> {
buffer.write(NetworkBuffer.VAR_INT, 1); // Quotable phrase
});
return NetworkBuffer.makeArray(NetworkBuffer.VAR_INT, 1); // Quotable phrase
}
/**

View File

@ -32,9 +32,7 @@ public class ArgumentStringArray extends Argument<String[]> {
@Override
public byte @Nullable [] nodeProperties() {
return NetworkBuffer.makeArray(buffer -> {
buffer.write(NetworkBuffer.VAR_INT, 2); // Greedy phrase
});
return NetworkBuffer.makeArray(NetworkBuffer.VAR_INT, 2); // Greedy phrase
}
@Override

View File

@ -75,9 +75,7 @@ public class ArgumentWord extends Argument<String> {
@Override
public byte @Nullable [] nodeProperties() {
return NetworkBuffer.makeArray(buffer -> {
buffer.write(NetworkBuffer.VAR_INT, 0); // Single word
});
return NetworkBuffer.makeArray(NetworkBuffer.VAR_INT, 0); // Single word
}
/**

View File

@ -39,6 +39,6 @@ public class ArgumentResource extends Argument<String> {
@Override
public byte @Nullable [] nodeProperties() {
return NetworkBuffer.makeArray(buffer -> buffer.write(NetworkBuffer.STRING, identifier));
return NetworkBuffer.makeArray(NetworkBuffer.STRING, identifier);
}
}

View File

@ -39,8 +39,6 @@ public class ArgumentResourceOrTag extends Argument<String> {
@Override
public byte @Nullable [] nodeProperties() {
return NetworkBuffer.makeArray(buffer ->
buffer.write(NetworkBuffer.STRING, this.identifier)
);
return NetworkBuffer.makeArray(NetworkBuffer.STRING, identifier);
}
}

View File

@ -70,7 +70,7 @@ public class ArgumentTime extends Argument<Duration> {
@Override
public byte @Nullable [] nodeProperties() {
return NetworkBuffer.makeArray(buffer -> buffer.write(NetworkBuffer.INT, min));
return NetworkBuffer.makeArray(NetworkBuffer.INT, min);
}
@Override

View File

@ -1,37 +1,30 @@
package net.minestom.server.crypto;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import static net.minestom.server.network.NetworkBuffer.STRING;
public record ArgumentSignatures(@NotNull List<@NotNull Entry> entries) implements NetworkBuffer.Writer {
public record ArgumentSignatures(@NotNull List<@NotNull Entry> entries) {
public static final int MAX_ENTRIES = 8;
public ArgumentSignatures {
entries = List.copyOf(entries);
}
public ArgumentSignatures(@NotNull NetworkBuffer reader) {
this(reader.readCollection(Entry::new, MAX_ENTRIES));
}
public static final NetworkBuffer.Type<ArgumentSignatures> SERIALIZER = NetworkBufferTemplate.template(
Entry.SERIALIZER.list(MAX_ENTRIES), ArgumentSignatures::entries,
ArgumentSignatures::new
);
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.writeCollection(entries);
}
public record Entry(@NotNull String name, @NotNull MessageSignature signature) implements NetworkBuffer.Writer {
public Entry(@NotNull NetworkBuffer reader) {
this(reader.read(STRING), new MessageSignature(reader));
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(STRING, name);
writer.write(signature);
}
public record Entry(@NotNull String name, @NotNull MessageSignature signature) {
public static final NetworkBuffer.Type<Entry> SERIALIZER = NetworkBufferTemplate.template(
STRING, Entry::name,
MessageSignature.SERIALIZER, Entry::signature,
Entry::new
);
}
}

View File

@ -1,18 +1,17 @@
package net.minestom.server.crypto;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public record ChatSession(@NotNull UUID sessionId, @NotNull PlayerPublicKey publicKey) implements NetworkBuffer.Writer {
public ChatSession(@NotNull NetworkBuffer reader) {
this(reader.read(NetworkBuffer.UUID), new PlayerPublicKey(reader));
}
import static net.minestom.server.network.NetworkBuffer.UUID;
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(NetworkBuffer.UUID, sessionId);
writer.write(publicKey);
}
public record ChatSession(@NotNull UUID sessionId, @NotNull PlayerPublicKey publicKey) {
public static final NetworkBuffer.Type<ChatSession> SERIALIZER = NetworkBufferTemplate.template(
UUID, ChatSession::sessionId,
PlayerPublicKey.SERIALIZER, ChatSession::publicKey,
ChatSession::new
);
}

View File

@ -5,20 +5,25 @@ import org.jetbrains.annotations.NotNull;
import java.util.BitSet;
import static net.minestom.server.network.NetworkBuffer.LONG_ARRAY;
import static net.minestom.server.network.NetworkBuffer.BITSET;
public record FilterMask(@NotNull Type type, @NotNull BitSet mask) implements NetworkBuffer.Writer {
public FilterMask(@NotNull NetworkBuffer reader) {
this(reader.readEnum(Type.class), BitSet.valueOf(reader.read(LONG_ARRAY)));
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.writeEnum(Type.class, type);
if (type == Type.PARTIALLY_FILTERED) {
writer.write(LONG_ARRAY, mask.toLongArray());
public record FilterMask(@NotNull Type type, @NotNull BitSet mask) {
public static final NetworkBuffer.Type<FilterMask> SERIALIZER = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, FilterMask value) {
buffer.write(NetworkBuffer.Enum(Type.class), value.type);
if (value.type == Type.PARTIALLY_FILTERED) {
buffer.write(BITSET, value.mask);
}
}
}
@Override
public FilterMask read(@NotNull NetworkBuffer buffer) {
Type type = buffer.read(NetworkBuffer.Enum(Type.class));
BitSet mask = type == Type.PARTIALLY_FILTERED ? buffer.read(BITSET) : new BitSet();
return new FilterMask(type, mask);
}
};
public enum Type {
PASS_THROUGH,

View File

@ -1,50 +1,41 @@
package net.minestom.server.crypto;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import org.jetbrains.annotations.NotNull;
import java.util.BitSet;
import java.util.List;
import static net.minestom.server.network.NetworkBuffer.FixedBitSet;
import static net.minestom.server.network.NetworkBuffer.VAR_INT;
public record LastSeenMessages(@NotNull List<@NotNull MessageSignature> entries) implements NetworkBuffer.Writer {
public record LastSeenMessages(@NotNull List<@NotNull MessageSignature> entries) {
public static final int MAX_ENTRIES = 20;
public LastSeenMessages {
entries = List.copyOf(entries);
}
public LastSeenMessages(@NotNull NetworkBuffer reader) {
this(reader.readCollection(MessageSignature::new, MAX_ENTRIES));
}
public static final NetworkBuffer.Type<LastSeenMessages> SERIALIZER = NetworkBufferTemplate.template(
MessageSignature.SERIALIZER.list(MAX_ENTRIES), LastSeenMessages::entries,
LastSeenMessages::new
);
@Override
public void write(@NotNull NetworkBuffer writer) {
}
public record Packed(@NotNull List<MessageSignature.@NotNull Packed> entries) implements NetworkBuffer.Writer {
public record Packed(@NotNull List<MessageSignature.@NotNull Packed> entries) {
public static final Packed EMPTY = new Packed(List.of());
public Packed(@NotNull NetworkBuffer reader) {
this(reader.readCollection(MessageSignature.Packed::new, MAX_ENTRIES));
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.writeCollection(entries);
}
public static final NetworkBuffer.Type<Packed> SERIALIZER = NetworkBufferTemplate.template(
MessageSignature.Packed.SERIALIZER.list(MAX_ENTRIES), Packed::entries,
Packed::new
);
}
public record Update(int offset, @NotNull BitSet acknowledged) implements NetworkBuffer.Writer {
public Update(@NotNull NetworkBuffer reader) {
this(reader.read(VAR_INT), reader.readFixedBitSet(20));
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(VAR_INT, offset);
writer.writeFixedBitSet(acknowledged, 20);
}
public record Update(int offset, @NotNull BitSet acknowledged) {
public static final NetworkBuffer.Type<Update> SERIALIZER = NetworkBufferTemplate.template(
VAR_INT, Update::offset,
FixedBitSet(20), Update::acknowledged,
Update::new
);
}
}

View File

@ -1,14 +1,13 @@
package net.minestom.server.crypto;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnknownNullability;
import static net.minestom.server.network.NetworkBuffer.RAW_BYTES;
import static net.minestom.server.network.NetworkBuffer.VAR_INT;
public record MessageSignature(byte @NotNull [] signature) implements NetworkBuffer.Writer {
public record MessageSignature(byte @NotNull [] signature) {
static final int SIGNATURE_BYTE_LENGTH = 256;
public MessageSignature {
@ -17,33 +16,28 @@ public record MessageSignature(byte @NotNull [] signature) implements NetworkBuf
}
}
public MessageSignature(@NotNull NetworkBuffer reader) {
this(reader.readBytes(SIGNATURE_BYTE_LENGTH));
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(RAW_BYTES, signature);
}
public record Packed(int id, @UnknownNullability MessageSignature fullSignature) implements NetworkBuffer.Writer {
public Packed(@NotNull NetworkBuffer reader) {
this(read(reader));
}
public static final NetworkBuffer.Type<MessageSignature> SERIALIZER = NetworkBufferTemplate.template(
NetworkBuffer.RAW_BYTES, MessageSignature::signature,
MessageSignature::new
);
public record Packed(int id, @UnknownNullability MessageSignature fullSignature) {
private Packed(@NotNull Packed packed) {
this(packed.id, packed.fullSignature);
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(VAR_INT, id + 1);
if (id == 0) writer.write(fullSignature);
}
public static final NetworkBuffer.Type<Packed> SERIALIZER = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, Packed value) {
buffer.write(VAR_INT, value.id + 1);
if (value.id == 0) buffer.write(MessageSignature.SERIALIZER, value.fullSignature);
}
private static Packed read(NetworkBuffer reader) {
final int id = reader.read(VAR_INT) - 1;
return new Packed(id, id == -1 ? new MessageSignature(reader) : null);
}
@Override
public Packed read(@NotNull NetworkBuffer buffer) {
final int id = buffer.read(VAR_INT) - 1;
return new Packed(id, id == -1 ? buffer.read(MessageSignature.SERIALIZER) : null);
}
};
}
}

View File

@ -1,39 +1,21 @@
package net.minestom.server.crypto;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.crypto.KeyUtils;
import org.jetbrains.annotations.NotNull;
import net.minestom.server.network.NetworkBufferTemplate;
import java.security.PublicKey;
import java.time.Instant;
import java.util.Arrays;
import static net.minestom.server.network.NetworkBuffer.BYTE_ARRAY;
import static net.minestom.server.network.NetworkBuffer.LONG;
import static net.minestom.server.network.NetworkBuffer.*;
/**
* Player's public key used to sign chat messages
*/
public record PlayerPublicKey(Instant expiresAt, PublicKey publicKey,
byte[] signature) implements NetworkBuffer.Writer {
public PlayerPublicKey(@NotNull NetworkBuffer reader) {
this(Instant.ofEpochMilli(reader.read(LONG)),
KeyUtils.publicRSAKeyFrom(reader.read(BYTE_ARRAY)), reader.read(BYTE_ARRAY));
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(LONG, expiresAt().toEpochMilli());
writer.write(BYTE_ARRAY, publicKey.getEncoded());
writer.write(BYTE_ARRAY, signature());
}
@Override
public boolean equals(Object obj) {
if (obj instanceof PlayerPublicKey ppk) {
return expiresAt.equals(ppk.expiresAt) && publicKey.equals(ppk.publicKey) && Arrays.equals(signature, ppk.signature);
} else {
return false;
}
}
public record PlayerPublicKey(Instant expiresAt, PublicKey publicKey, byte[] signature) {
public static final NetworkBuffer.Type<PlayerPublicKey> SERIALIZER = NetworkBufferTemplate.template(
INSTANT_MS, PlayerPublicKey::expiresAt,
PUBLIC_KEY, PlayerPublicKey::publicKey,
BYTE_ARRAY, PlayerPublicKey::signature,
PlayerPublicKey::new
);
}

View File

@ -1,19 +1,12 @@
package net.minestom.server.crypto;
import net.minestom.server.network.NetworkBuffer;
import org.jetbrains.annotations.NotNull;
import net.minestom.server.network.NetworkBufferTemplate;
import static net.minestom.server.network.NetworkBuffer.BYTE_ARRAY;
import static net.minestom.server.network.NetworkBuffer.LONG;
public record SaltSignaturePair(long salt, byte[] signature) implements NetworkBuffer.Writer {
public SaltSignaturePair(@NotNull NetworkBuffer reader) {
this(reader.read(LONG), reader.read(BYTE_ARRAY));
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(LONG, salt);
writer.write(BYTE_ARRAY, signature);
}
public record SaltSignaturePair(long salt, byte[] signature) {
public static final NetworkBuffer.Type<SaltSignaturePair> SERIALIZER = NetworkBufferTemplate.template(
NetworkBuffer.LONG, SaltSignaturePair::salt,
NetworkBuffer.BYTE_ARRAY, SaltSignaturePair::signature,
SaltSignaturePair::new
);
}

View File

@ -1,6 +1,7 @@
package net.minestom.server.crypto;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import org.jetbrains.annotations.NotNull;
import java.time.Instant;
@ -8,24 +9,19 @@ import java.time.Instant;
public final class SignedMessageBody {
public record Packed(@NotNull String content, @NotNull Instant timeStamp, long salt,
LastSeenMessages.@NotNull Packed lastSeen) implements NetworkBuffer.Writer {
LastSeenMessages.@NotNull Packed lastSeen) {
public Packed {
if (content.length() > MessageSignature.SIGNATURE_BYTE_LENGTH) {
throw new IllegalArgumentException("Message content too long");
}
}
public Packed(@NotNull NetworkBuffer reader) {
this(reader.read(NetworkBuffer.STRING), Instant.ofEpochMilli(reader.read(NetworkBuffer.LONG)),
reader.read(NetworkBuffer.LONG), new LastSeenMessages.Packed(reader));
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(NetworkBuffer.STRING, content);
writer.write(NetworkBuffer.LONG, timeStamp.toEpochMilli());
writer.write(NetworkBuffer.LONG, salt);
writer.write(lastSeen);
}
public static final NetworkBuffer.Type<Packed> SERIALIZER = NetworkBufferTemplate.template(
NetworkBuffer.STRING, Packed::content,
NetworkBuffer.INSTANT_MS, Packed::timeStamp,
NetworkBuffer.LONG, Packed::salt,
LastSeenMessages.Packed.SERIALIZER, Packed::lastSeen,
Packed::new
);
}
}

View File

@ -1,20 +1,16 @@
package net.minestom.server.crypto;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
public record SignedMessageHeader(@Nullable MessageSignature previousSignature,
@NotNull UUID sender) implements NetworkBuffer.Writer {
public SignedMessageHeader(@NotNull NetworkBuffer reader) {
this(reader.readOptional(MessageSignature::new), reader.read(NetworkBuffer.UUID));
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.writeOptional(previousSignature);
writer.write(NetworkBuffer.UUID, sender);
}
public record SignedMessageHeader(@Nullable MessageSignature previousSignature, @NotNull UUID sender) {
public static final NetworkBuffer.Type<SignedMessageHeader> SERIALIZER = NetworkBufferTemplate.template(
MessageSignature.SERIALIZER.optional(), SignedMessageHeader::previousSignature,
NetworkBuffer.UUID, SignedMessageHeader::sender,
SignedMessageHeader::new
);
}

View File

@ -47,7 +47,7 @@ import net.minestom.server.timer.Schedulable;
import net.minestom.server.timer.Scheduler;
import net.minestom.server.timer.TaskSchedule;
import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.PacketViewableUtils;
import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.block.BlockIterator;
import net.minestom.server.utils.chunk.ChunkCache;
@ -1244,22 +1244,21 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
final Chunk chunk = getChunk();
assert chunk != null;
if (distanceX > 8 || distanceY > 8 || distanceZ > 8) {
PacketUtils.prepareViewablePacket(chunk, new EntityTeleportPacket(getEntityId(), position, isOnGround()), this);
PacketViewableUtils.prepareViewablePacket(chunk, new EntityTeleportPacket(getEntityId(), position, isOnGround()), this);
nextSynchronizationTick = synchronizationTicks + 1;
} else if (positionChange && viewChange) {
// PacketUtils.prepareViewablePacket(chunk, new EntityVelocityPacket(getEntityId(), new Vec(distanceX, distanceY, distanceZ).div(20 * 8000)));
PacketUtils.prepareViewablePacket(chunk, EntityPositionAndRotationPacket.getPacket(getEntityId(), position,
PacketViewableUtils.prepareViewablePacket(chunk, EntityPositionAndRotationPacket.getPacket(getEntityId(), position,
lastSyncedPosition, isOnGround()), this);
// Fix head rotation
PacketUtils.prepareViewablePacket(chunk, new EntityHeadLookPacket(getEntityId(), position.yaw()), this);
PacketViewableUtils.prepareViewablePacket(chunk, new EntityHeadLookPacket(getEntityId(), position.yaw()), this);
} else if (positionChange) {
// This is a confusing fix for a confusing issue. If rotation is only sent when the entity actually changes, then spawning an entity
// on the ground causes the entity not to update its rotation correctly. It works fine if the entity is spawned in the air. Very weird.
PacketUtils.prepareViewablePacket(chunk, EntityPositionAndRotationPacket.getPacket(getEntityId(), position,
PacketViewableUtils.prepareViewablePacket(chunk, EntityPositionAndRotationPacket.getPacket(getEntityId(), position,
lastSyncedPosition, onGround), this);
} else if (viewChange) {
PacketUtils.prepareViewablePacket(chunk, new EntityHeadLookPacket(getEntityId(), position.yaw()), this);
PacketUtils.prepareViewablePacket(chunk, EntityPositionAndRotationPacket.getPacket(getEntityId(), position,
PacketViewableUtils.prepareViewablePacket(chunk, new EntityHeadLookPacket(getEntityId(), position.yaw()), this);
PacketViewableUtils.prepareViewablePacket(chunk, EntityPositionAndRotationPacket.getPacket(getEntityId(), position,
lastSyncedPosition, isOnGround()), this);
}
this.lastSyncedPosition = position;
@ -1535,9 +1534,9 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
@ApiStatus.Internal
protected void synchronizePosition() {
final Pos posCache = this.position;
PacketUtils.prepareViewablePacket(currentChunk, new EntityTeleportPacket(getEntityId(), posCache, isOnGround()), this);
PacketViewableUtils.prepareViewablePacket(currentChunk, new EntityTeleportPacket(getEntityId(), posCache, isOnGround()), this);
if (posCache.yaw() != lastSyncedPosition.yaw()) {
PacketUtils.prepareViewablePacket(currentChunk, new EntityHeadLookPacket(getEntityId(), position.yaw()), this);
PacketViewableUtils.prepareViewablePacket(currentChunk, new EntityHeadLookPacket(getEntityId(), position.yaw()), this);
}
nextSynchronizationTick = ticks + synchronizationTicks;
this.lastSyncedPosition = posCache;

View File

@ -32,15 +32,18 @@ public enum GameMode {
return canTakeDamage;
}
public static final NetworkBuffer.Type<GameMode> NETWORK_TYPE = new NetworkBuffer.Type<>() {
public static final NetworkBuffer.Type<GameMode> NETWORK_TYPE = BYTE.transform(GameMode::fromId, gameMode -> gameMode.id);
public static final NetworkBuffer.Type<GameMode> OPT_NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, GameMode value) {
buffer.write(BYTE, value.id());
buffer.write(BYTE, value != null ? value.id() : -1);
}
@Override
public GameMode read(@NotNull NetworkBuffer buffer) {
return GameMode.fromId(buffer.read(BYTE));
final byte id = buffer.read(BYTE);
return id != -1 ? GameMode.fromId(id) : null;
}
};

View File

@ -16,7 +16,6 @@ import net.minestom.server.particle.Particle;
import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.utils.Direction;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
@ -213,15 +212,13 @@ public final class Metadata {
return (byte) NEXT_ID.getAndIncrement();
}
public sealed interface Entry<T> extends NetworkBuffer.Writer
permits MetadataImpl.EntryImpl {
public sealed interface Entry<T> permits MetadataImpl.EntryImpl {
@SuppressWarnings({"unchecked", "rawtypes"})
NetworkBuffer.Type<Entry<?>> SERIALIZER = (NetworkBuffer.Type) MetadataImpl.EntryImpl.SERIALIZER;
int type();
@UnknownNullability T value();
@ApiStatus.Internal
static @NotNull Entry<?> read(int type, @NotNull NetworkBuffer reader) {
return MetadataImpl.EntryImpl.read(type, reader);
}
@UnknownNullability
T value();
}
}

View File

@ -61,22 +61,23 @@ final class MetadataImpl {
EMPTY_VALUES.trim();
}
@SuppressWarnings({"rawtypes", "unchecked"})
record EntryImpl<T>(int type, @UnknownNullability T value,
@NotNull NetworkBuffer.Type<T> serializer) implements Metadata.Entry<T> {
static Entry<?> read(int type, @NotNull NetworkBuffer reader) {
final EntryImpl<?> value = (EntryImpl<?>) EMPTY_VALUES.get(type);
if (value == null) throw new UnsupportedOperationException("Unknown value type: " + type);
return value.withValue(reader);
}
static final NetworkBuffer.Type<EntryImpl<?>> SERIALIZER = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, EntryImpl value) {
buffer.write(VAR_INT, value.type);
buffer.write(value.serializer, value.value);
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(VAR_INT, type);
writer.write(serializer, value);
}
private EntryImpl<T> withValue(@NotNull NetworkBuffer reader) {
return new EntryImpl<>(type, reader.read(serializer), serializer);
}
@Override
public EntryImpl read(@NotNull NetworkBuffer buffer) {
final int type = buffer.read(VAR_INT);
final EntryImpl<?> value = (EntryImpl<?>) EMPTY_VALUES.get(type);
if (value == null) throw new UnsupportedOperationException("Unknown value type: " + type);
return new EntryImpl(type, value.serializer.read(buffer), value.serializer);
}
};
}
}

View File

@ -87,7 +87,7 @@ import net.minestom.server.statistic.PlayerStatistic;
import net.minestom.server.thread.Acquirable;
import net.minestom.server.timer.Scheduler;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.PacketSendingUtils;
import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.chunk.ChunkUpdateLimitChecker;
import net.minestom.server.utils.chunk.ChunkUtils;
@ -328,7 +328,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
EventDispatcher.call(skinInitEvent);
this.skin = skinInitEvent.getSkin();
// FIXME: when using Geyser, this line remove the skin of the client
PacketUtils.broadcastPlayPacket(getAddPlayerToList());
PacketSendingUtils.broadcastPlayPacket(getAddPlayerToList());
var connectionManager = MinecraftServer.getConnectionManager();
for (var player : connectionManager.getOnlinePlayers()) {
@ -604,7 +604,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
// Clear all viewable chunks
ChunkUtils.forChunksInRange(chunkX, chunkZ, settings.getEffectiveViewDistance(), chunkRemover);
// Remove from the tab-list
PacketUtils.broadcastPlayPacket(getRemovePlayerToList());
PacketSendingUtils.broadcastPlayPacket(getRemovePlayerToList());
// Prevent the player from being stuck in loading screen, or just unable to interact with the server
// This should be considered as a bug, since the player will ultimately time out anyway.
@ -1010,7 +1010,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
String title = PlainTextComponentSerializer.plainText().serialize(book.title());
String author = PlainTextComponentSerializer.plainText().serialize(book.author());
final ItemStack writtenBook = ItemStack.builder(Material.WRITTEN_BOOK)
.set(ItemComponent.WRITTEN_BOOK_CONTENT, new WrittenBookContent(book.pages(), title, author, 0, false))
.set(ItemComponent.WRITTEN_BOOK_CONTENT, new WrittenBookContent(title, author, 0, book.pages(), false))
.build();
// Set book in offhand
@ -1176,7 +1176,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
*/
public void setDisplayName(@Nullable Component displayName) {
this.displayName = displayName;
PacketUtils.broadcastPlayPacket(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, infoEntry()));
PacketSendingUtils.broadcastPlayPacket(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, infoEntry()));
}
/**
@ -1218,11 +1218,11 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
{
// Remove player
PacketUtils.broadcastPlayPacket(removePlayerPacket);
PacketSendingUtils.broadcastPlayPacket(removePlayerPacket);
sendPacketToViewers(destroyEntitiesPacket);
// Show player again
PacketUtils.broadcastPlayPacket(addPlayerPacket);
PacketSendingUtils.broadcastPlayPacket(addPlayerPacket);
getViewers().forEach(player -> showPlayer(player.getPlayerConnection()));
}
@ -1606,7 +1606,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
// Condition to prevent sending the packets before spawning the player
if (isActive()) {
sendPacket(new ChangeGameStatePacket(ChangeGameStatePacket.Reason.CHANGE_GAMEMODE, gameMode.id()));
PacketUtils.broadcastPlayPacket(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, infoEntry()));
PacketSendingUtils.broadcastPlayPacket(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, infoEntry()));
}
// The client updates their abilities based on the GameMode as follows
@ -2138,7 +2138,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
public void refreshLatency(int latency) {
this.latency = latency;
if (getPlayerConnection().getConnectionState() == ConnectionState.PLAY) {
PacketUtils.broadcastPlayPacket(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_LATENCY, infoEntry()));
PacketSendingUtils.broadcastPlayPacket(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_LATENCY, infoEntry()));
}
}
@ -2361,6 +2361,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
/**
* Send a {@link Notification} to the player.
*
* @param notification the {@link Notification} to send
*/
public void sendNotification(@NotNull Notification notification) {

View File

@ -12,7 +12,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public sealed interface Attribute extends StaticProtocolObject, Attributes permits AttributeImpl {
@NotNull NetworkBuffer.Type<Attribute> NETWORK_TYPE = NetworkBuffer.VAR_INT.map(AttributeImpl::getId, Attribute::id);
@NotNull NetworkBuffer.Type<Attribute> NETWORK_TYPE = NetworkBuffer.VAR_INT.transform(AttributeImpl::getId, Attribute::id);
@NotNull BinaryTagSerializer<Attribute> NBT_TYPE = BinaryTagSerializer.STRING.map(AttributeImpl::get, Attribute::name);
@Contract(pure = true)

View File

@ -22,13 +22,13 @@ public final class AttributeInstance {
public void write(@NotNull NetworkBuffer buffer, AttributeInstance value) {
buffer.write(Attribute.NETWORK_TYPE, value.attribute());
buffer.write(NetworkBuffer.DOUBLE, value.getBaseValue());
buffer.writeCollection(AttributeModifier.NETWORK_TYPE, value.modifiers());
buffer.write(AttributeModifier.NETWORK_TYPE.list(Short.MAX_VALUE), List.copyOf(value.modifiers()));
}
@Override
public AttributeInstance read(@NotNull NetworkBuffer buffer) {
return new AttributeInstance(buffer.read(Attribute.NETWORK_TYPE), buffer.read(NetworkBuffer.DOUBLE),
buffer.readCollection(AttributeModifier.NETWORK_TYPE, Short.MAX_VALUE), null);
buffer.read(AttributeModifier.NETWORK_TYPE.list(Short.MAX_VALUE)), null);
}
};

View File

@ -2,8 +2,8 @@ package net.minestom.server.event.player;
import net.minestom.server.entity.Player;
import net.minestom.server.event.trait.PlayerEvent;
import net.minestom.server.network.plugin.LoginPlugin;
import net.minestom.server.network.plugin.LoginPluginMessageProcessor;
import net.minestom.server.network.plugin.LoginPluginResponse;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
@ -64,7 +64,7 @@ public class AsyncPlayerPreLoginEvent implements PlayerEvent {
*
* @return a CompletableFuture for the response. The thread on which it completes is asynchronous.
*/
public @NotNull CompletableFuture<LoginPluginResponse> sendPluginRequest(String channel, byte[] requestPayload) {
public @NotNull CompletableFuture<LoginPlugin.Response> sendPluginRequest(String channel, byte[] requestPayload) {
return pluginMessageProcessor.request(channel, requestPayload);
}

View File

@ -7,6 +7,8 @@ import net.minestom.server.MinecraftServer;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.extras.query.event.BasicQueryEvent;
import net.minestom.server.extras.query.event.FullQueryEvent;
import net.minestom.server.extras.query.response.BasicQueryResponse;
import net.minestom.server.extras.query.response.FullQueryResponse;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.timer.Task;
import net.minestom.server.utils.time.TimeUnit;
@ -183,24 +185,24 @@ public class Query {
if (remaining == 0) { // basic
BasicQueryEvent event = new BasicQueryEvent(sender, sessionID);
EventDispatcher.callCancellable(event, () ->
sendResponse(event.getQueryResponse(), sessionID, sender));
sendResponse(BasicQueryResponse.SERIALIZER, event.getQueryResponse(), sessionID, sender));
} else if (remaining == 5) { // full
FullQueryEvent event = new FullQueryEvent(sender, sessionID);
EventDispatcher.callCancellable(event, () ->
sendResponse(event.getQueryResponse(), sessionID, sender));
sendResponse(FullQueryResponse.SERIALIZER, event.getQueryResponse(), sessionID, sender));
}
}
}
}
}
private static void sendResponse(@NotNull NetworkBuffer.Writer queryResponse, int sessionID, @NotNull SocketAddress sender) {
private static <T> void sendResponse(NetworkBuffer.Type<T> type, @NotNull T queryResponse, int sessionID, @NotNull SocketAddress sender) {
final byte[] responseData = NetworkBuffer.makeArray(buffer -> {
// header
buffer.write(NetworkBuffer.BYTE, (byte) 0);
buffer.write(NetworkBuffer.INT, sessionID);
// payload
buffer.write(queryResponse);
buffer.write(type, queryResponse);
});
try {
socket.send(new DatagramPacket(responseData, responseData.length, sender));

View File

@ -1,7 +1,6 @@
package net.minestom.server.extras.query.event;
import net.minestom.server.event.trait.CancellableEvent;
import net.minestom.server.network.NetworkBuffer;
import org.jetbrains.annotations.NotNull;
import java.net.SocketAddress;
@ -12,7 +11,7 @@ import java.util.Objects;
*
* @param <T> the type of the response
*/
public abstract class QueryEvent<T extends NetworkBuffer.Writer> implements CancellableEvent {
public abstract class QueryEvent<T> implements CancellableEvent {
private final SocketAddress sender;
private final int sessionID;

View File

@ -2,145 +2,43 @@ package net.minestom.server.extras.query.response;
import net.minestom.server.MinecraftServer;
import net.minestom.server.network.NetworkBuffer;
import org.jetbrains.annotations.NotNull;
import net.minestom.server.network.NetworkBufferTemplate;
import java.util.Objects;
import static net.minestom.server.network.NetworkBuffer.SHORT;
import static net.minestom.server.network.NetworkBuffer.STRING_TERMINATED;
/**
* A basic query response containing a fixed set of responses.
*/
public class BasicQueryResponse implements NetworkBuffer.Writer {
private String motd, gametype, map, numPlayers, maxPlayers;
public record BasicQueryResponse(String motd, String gameType,
String map,
String numPlayers, String maxPlayers,
short port, String address) {
/**
* Creates a new basic query response with pre-filled default values.
*/
public BasicQueryResponse() {
this.motd = "A Minestom Server";
this.gametype = "SMP";
this.map = "world";
this.numPlayers = String.valueOf(MinecraftServer.getConnectionManager().getOnlinePlayerCount());
this.maxPlayers = String.valueOf(Integer.parseInt(this.numPlayers) + 1);
this(
"A Minestom Server",
"SMP",
"world",
String.valueOf(MinecraftServer.getConnectionManager().getOnlinePlayerCount()),
"9999",
(short) MinecraftServer.getServer().getPort(),
Objects.requireNonNullElse(MinecraftServer.getServer().getAddress(), "")
);
}
/**
* Gets the MoTD.
*
* @return the motd
*/
public @NotNull String getMotd() {
return this.motd;
}
/**
* Sets the MoTD.
*
* @param motd the motd
*/
public void setMotd(@NotNull String motd) {
this.motd = Objects.requireNonNull(motd, "motd");
}
/**
* Gets the gametype.
*
* @return the gametype
*/
public @NotNull String getGametype() {
return this.gametype;
}
/**
* Sets the gametype.
*
* @param gametype the gametype
*/
public void setGametype(@NotNull String gametype) {
this.gametype = Objects.requireNonNull(gametype, "gametype");
}
/**
* Gets the map.
*
* @return the map
*/
public @NotNull String getMap() {
return this.map;
}
/**
* Sets the map.
*
* @param map the map
*/
public void setMap(@NotNull String map) {
this.map = Objects.requireNonNull(map, "map");
}
/**
* Gets the number of players.
*
* @return the number of players
*/
public @NotNull String getNumPlayers() {
return this.numPlayers;
}
/**
* Sets the number of players.
*
* @param numPlayers the number of players
*/
public void setNumPlayers(@NotNull String numPlayers) {
this.numPlayers = Objects.requireNonNull(numPlayers, "numPlayers");
}
/**
* Sets the number of players.
* This method is just an overload for {@link #setNumPlayers(String)}.
*
* @param numPlayers the number of players
*/
public void setNumPlayers(int numPlayers) {
this.setNumPlayers(String.valueOf(numPlayers));
}
/**
* Gets the max number of players.
*
* @return the max number of players
*/
public @NotNull String getMaxPlayers() {
return this.maxPlayers;
}
/**
* Sets the max number of players.
*
* @param maxPlayers the max number of players
*/
public void setMaxPlayers(@NotNull String maxPlayers) {
this.maxPlayers = Objects.requireNonNull(maxPlayers, "maxPlayers");
}
/**
* Sets the max number of players.
* This method is just an overload for {@link #setMaxPlayers(String)}
*
* @param maxPlayers the max number of players
*/
public void setMaxPlayers(int maxPlayers) {
this.setMaxPlayers(String.valueOf(maxPlayers));
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(NetworkBuffer.STRING_TERMINATED, this.motd);
writer.write(NetworkBuffer.STRING_TERMINATED, this.gametype);
writer.write(NetworkBuffer.STRING_TERMINATED, this.map);
writer.write(NetworkBuffer.STRING_TERMINATED, this.numPlayers);
writer.write(NetworkBuffer.STRING_TERMINATED, this.maxPlayers);
writer.write(NetworkBuffer.SHORT, (short) MinecraftServer.getServer().getPort()); // TODO little endian?
writer.write(NetworkBuffer.STRING_TERMINATED, Objects.requireNonNullElse(MinecraftServer.getServer().getAddress(), ""));
}
public static final NetworkBuffer.Type<BasicQueryResponse> SERIALIZER = NetworkBufferTemplate.template(
STRING_TERMINATED, BasicQueryResponse::motd,
STRING_TERMINATED, BasicQueryResponse::gameType,
STRING_TERMINATED, BasicQueryResponse::map,
STRING_TERMINATED, BasicQueryResponse::numPlayers,
STRING_TERMINATED, BasicQueryResponse::maxPlayers,
SHORT, BasicQueryResponse::port, // TODO little endian?
STRING_TERMINATED, BasicQueryResponse::address,
BasicQueryResponse::new
);
}

View File

@ -10,7 +10,7 @@ import java.util.*;
/**
* A full query response containing a dynamic set of responses.
*/
public class FullQueryResponse implements NetworkBuffer.Writer {
public class FullQueryResponse {
private static final PlainTextComponentSerializer PLAIN = PlainTextComponentSerializer.plainText();
private static final byte[] PADDING_10 = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
PADDING_11 = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
@ -123,20 +123,27 @@ public class FullQueryResponse implements NetworkBuffer.Writer {
return builder.toString();
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(NetworkBuffer.RAW_BYTES, PADDING_11);
// key-values
for (var entry : this.kv.entrySet()) {
writer.write(NetworkBuffer.STRING_TERMINATED, entry.getKey());
writer.write(NetworkBuffer.STRING_TERMINATED, entry.getValue());
public static final NetworkBuffer.Type<FullQueryResponse> SERIALIZER = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, FullQueryResponse value) {
buffer.write(NetworkBuffer.RAW_BYTES, PADDING_11);
// key-values
for (var entry : value.kv.entrySet()) {
buffer.write(NetworkBuffer.STRING_TERMINATED, entry.getKey());
buffer.write(NetworkBuffer.STRING_TERMINATED, entry.getValue());
}
buffer.write(NetworkBuffer.STRING_TERMINATED, "");
buffer.write(NetworkBuffer.RAW_BYTES, PADDING_10);
// players
for (String player : value.players) {
buffer.write(NetworkBuffer.STRING_TERMINATED, player);
}
buffer.write(NetworkBuffer.STRING_TERMINATED, "");
}
writer.write(NetworkBuffer.STRING_TERMINATED, "");
writer.write(NetworkBuffer.RAW_BYTES, PADDING_10);
// players
for (String player : this.players) {
writer.write(NetworkBuffer.STRING_TERMINATED, player);
@Override
public FullQueryResponse read(@NotNull NetworkBuffer buffer) {
throw new UnsupportedOperationException("FullQueryResponse is write-only");
}
writer.write(NetworkBuffer.STRING_TERMINATED, "");
}
};
}

View File

@ -55,7 +55,7 @@ public final class VelocityProxy {
for (int i = 0; i < signature.length; i++) {
signature[i] = buffer.read(BYTE);
}
final int index = buffer.readIndex();
final long index = buffer.readIndex();
final byte[] data = buffer.read(RAW_BYTES);
buffer.readIndex(index);
try {
@ -66,7 +66,7 @@ public final class VelocityProxy {
return false;
}
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
final int version = buffer.read(VAR_INT);
return version == SUPPORTED_FORWARDING_VERSION;

View File

@ -1,6 +1,6 @@
package net.minestom.server.gamedata.tags;
import net.minestom.server.registry.Registry;
import net.minestom.server.network.packet.server.common.TagsPacket;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.Nullable;
@ -18,7 +18,7 @@ public final class TagManager {
// Load required tags from files
for (var type : Tag.BasicType.values()) {
if (type.getResource() == null || type.getFunction() == null) continue;
final var json = Registry.load(type.getResource());
final var json = net.minestom.server.registry.Registry.load(type.getResource());
final var tagIdentifierMap = tagMap.computeIfAbsent(type, s -> new CopyOnWriteArrayList<>());
json.keySet().forEach(tagName -> {
final var tag = new Tag(NamespaceID.from(tagName), getValues(json, tagName));
@ -40,6 +40,22 @@ public final class TagManager {
return Collections.unmodifiableMap(tagMap);
}
public TagsPacket packet() {
List<TagsPacket.Registry> registries = new ArrayList<>();
for (Map.Entry<Tag.BasicType, List<Tag>> entry : tagMap.entrySet()) {
final Tag.BasicType type = entry.getKey();
final String registry = type.getIdentifier();
List<TagsPacket.Tag> tags = new ArrayList<>();
for (Tag tag : entry.getValue()) {
final String identifier = tag.getName().asString();
final int[] values = tag.getValues().stream().mapToInt(value -> type.getFunction().apply(value.asString())).toArray();
tags.add(new TagsPacket.Tag(identifier, values));
}
registries.add(new TagsPacket.Registry(registry, tags));
}
return new TagsPacket(registries);
}
private Set<NamespaceID> getValues(Map<String, Map<String, Object>> main, String value) {
Map<String, Object> tagObject = main.get(value);
final List<String> tagValues = (List<String>) tagObject.get("values");

View File

@ -12,6 +12,7 @@ import net.minestom.server.instance.block.BlockHandler;
import net.minestom.server.instance.heightmap.Heightmap;
import net.minestom.server.instance.heightmap.MotionBlockingHeightmap;
import net.minestom.server.instance.heightmap.WorldSurfaceHeightmap;
import net.minestom.server.instance.palette.Palette;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.packet.server.CachedPacket;
import net.minestom.server.network.packet.server.SendablePacket;
@ -35,6 +36,7 @@ import org.slf4j.LoggerFactory;
import java.util.*;
import static net.minestom.server.network.NetworkBuffer.SHORT;
import static net.minestom.server.utils.chunk.ChunkUtils.toSectionRelativeCoordinate;
/**
@ -260,7 +262,11 @@ public class DynamicChunk extends Chunk {
heightmapsNBT = getHeightmapNBT();
data = NetworkBuffer.makeArray(networkBuffer -> {
for (Section section : sections) networkBuffer.write(section);
for (Section section : sections) {
networkBuffer.write(SHORT, (short) section.blockPalette().count());
networkBuffer.write(Palette.BLOCK_SERIALIZER, section.blockPalette());
networkBuffer.write(Palette.BIOME_SERIALIZER, section.biomePalette());
}
});
}

View File

@ -3,7 +3,7 @@ package net.minestom.server.instance;
import net.minestom.server.coordinate.Point;
import net.minestom.server.instance.block.Block;
import net.minestom.server.network.packet.server.play.ExplosionPacket;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.PacketSendingUtils;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@ -73,7 +73,7 @@ public abstract class Explosion {
ExplosionPacket packet = new ExplosionPacket(centerX, centerY, centerZ, strength,
records, 0, 0, 0);
postExplosion(instance, blocks, packet);
PacketUtils.sendGroupedPacket(instance.getPlayers(), packet);
PacketSendingUtils.sendGroupedPacket(instance.getPlayers(), packet);
postSend(instance, blocks);
}

View File

@ -41,7 +41,7 @@ import net.minestom.server.timer.Schedulable;
import net.minestom.server.timer.Scheduler;
import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.PacketSendingUtils;
import net.minestom.server.utils.chunk.ChunkCache;
import net.minestom.server.utils.chunk.ChunkSupplier;
import net.minestom.server.utils.chunk.ChunkUtils;
@ -472,7 +472,7 @@ public abstract class Instance implements Block.Getter, Block.Setter,
*/
public void setTime(long time) {
this.time = time;
PacketUtils.sendGroupedPacket(getPlayers(), createTimePacket());
PacketSendingUtils.sendGroupedPacket(getPlayers(), createTimePacket());
}
/**
@ -774,7 +774,7 @@ public abstract class Instance implements Block.Getter, Block.Setter,
this.time += timeRate;
// time needs to be sent to players
if (timeSynchronizationTicks > 0 && this.worldAge % timeSynchronizationTicks == 0) {
PacketUtils.sendGroupedPacket(getPlayers(), createTimePacket());
PacketSendingUtils.sendGroupedPacket(getPlayers(), createTimePacket());
}
}

View File

@ -26,7 +26,7 @@ import net.minestom.server.network.packet.server.play.EffectPacket;
import net.minestom.server.network.packet.server.play.UnloadChunkPacket;
import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.PacketSendingUtils;
import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.block.BlockUtils;
import net.minestom.server.utils.chunk.ChunkCache;
@ -240,7 +240,7 @@ public class InstanceContainer extends Instance {
UNSAFE_setBlock(chunk, x, y, z, resultBlock, null,
new BlockHandler.PlayerDestroy(block, this, blockPosition, player), doBlockUpdates, 0);
// Send the block break effect packet
PacketUtils.sendGroupedPacket(chunk.getViewers(),
PacketSendingUtils.sendGroupedPacket(chunk.getViewers(),
new EffectPacket(2001 /*Block break + block break sound*/, blockPosition, block.stateId(), false),
// Prevent the block breaker to play the particles and sound two times
(viewer) -> !viewer.equals(player));

View File

@ -2,16 +2,14 @@ package net.minestom.server.instance;
import net.minestom.server.instance.light.Light;
import net.minestom.server.instance.palette.Palette;
import net.minestom.server.network.NetworkBuffer;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import static net.minestom.server.instance.light.LightCompute.CONTENT_FULLY_LIT;
import static net.minestom.server.instance.light.LightCompute.EMPTY_CONTENT;
import static net.minestom.server.network.NetworkBuffer.SHORT;
public final class Section implements NetworkBuffer.Writer {
public final class Section {
private final Palette blockPalette;
private final Palette biomePalette;
private final Light skyLight;
@ -56,13 +54,6 @@ public final class Section implements NetworkBuffer.Writer {
return new Section(this.blockPalette.clone(), this.biomePalette.clone(), skyLight, blockLight);
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(SHORT, (short) blockPalette.count());
writer.write(blockPalette);
writer.write(biomePalette);
}
public void setSkyLight(byte[] copyArray) {
if (copyArray == null || copyArray.length == 0) this.skyLight.set(EMPTY_CONTENT);
else if (Arrays.equals(copyArray, EMPTY_CONTENT)) this.skyLight.set(EMPTY_CONTENT);

View File

@ -9,8 +9,8 @@ import net.minestom.server.instance.Instance;
import net.minestom.server.instance.Section;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.BlockHandler;
import net.minestom.server.instance.palette.Palettes;
import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.async.AsyncUtils;
@ -196,7 +196,7 @@ public class AnvilLoader implements IChunkLoader {
int bitsPerEntry = packedIndices.length * 64 / biomeIndices.length;
if (bitsPerEntry > 3) bitsPerEntry = MathUtils.bitsToRepresent(convertedBiomePalette.length);
ArrayUtils.unpack(biomeIndices, packedIndices, bitsPerEntry);
Palettes.unpack(biomeIndices, packedIndices, bitsPerEntry);
section.biomePalette().setAll((x, y, z) -> {
final int index = x + z * 4 + y * 16;
@ -216,7 +216,7 @@ public class AnvilLoader implements IChunkLoader {
final long[] packedStates = blockStatesTag.getLongArray("data");
Check.stateCondition(packedStates.length == 0, "Missing packed states data");
int[] blockStateIndices = new int[Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SECTION_SIZE];
ArrayUtils.unpack(blockStateIndices, packedStates, packedStates.length * 64 / blockStateIndices.length);
Palettes.unpack(blockStateIndices, packedStates, packedStates.length * 64 / blockStateIndices.length);
for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) {
for (int z = 0; z < Chunk.CHUNK_SECTION_SIZE; z++) {
@ -465,7 +465,7 @@ public class AnvilLoader implements IChunkLoader {
if (blockPaletteEntries.size() > 1) {
// If there is only one entry we do not need to write the packed indices
var bitsPerEntry = (int) Math.max(4, Math.ceil(Math.log(blockPaletteEntries.size()) / Math.log(2)));
blockStates.putLongArray("data", ArrayUtils.pack(blockIndices, bitsPerEntry));
blockStates.putLongArray("data", Palettes.pack(blockIndices, bitsPerEntry));
}
sectionData.put("block_states", blockStates.build());
@ -474,7 +474,7 @@ public class AnvilLoader implements IChunkLoader {
if (biomePalette.size() > 1) {
// If there is only one entry we do not need to write the packed indices
var bitsPerEntry = (int) Math.max(1, Math.ceil(Math.log(biomePalette.size()) / Math.log(2)));
biomes.putLongArray("data", ArrayUtils.pack(biomeIndices, bitsPerEntry));
biomes.putLongArray("data", Palettes.pack(biomeIndices, bitsPerEntry));
}
sectionData.put("biomes", biomes.build());

View File

@ -27,7 +27,7 @@ import java.util.function.BiPredicate;
public sealed interface Block extends StaticProtocolObject, TagReadable, Blocks permits BlockImpl {
@NotNull
NetworkBuffer.Type<Block> NETWORK_TYPE = NetworkBuffer.VAR_INT.map(Block::fromStateId, Block::stateId);
NetworkBuffer.Type<Block> NETWORK_TYPE = NetworkBuffer.VAR_INT.transform(Block::fromStateId, Block::stateId);
/**
* Creates a new block with the the property {@code property} sets to {@code value}.

View File

@ -5,6 +5,7 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.BlockHandler;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.utils.block.BlockUtils;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
@ -14,6 +15,8 @@ import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import static net.minestom.server.network.NetworkBuffer.NBT_COMPOUND;
/**
* <p>A predicate to filter blocks based on their name, properties, and/or nbt.</p>
*
@ -25,8 +28,8 @@ import java.util.function.Predicate;
* is used for matching adventure mode blocks and must line up with client prediction.</p>
*
* @param blocks The block names/tags to match.
* @param state The block properties to match.
* @param nbt The block nbt to match.
* @param state The block properties to match.
* @param nbt The block nbt to match.
*/
public record BlockPredicate(
@Nullable BlockTypeFilter blocks,
@ -44,23 +47,13 @@ public record BlockPredicate(
*/
public static final BlockPredicate NONE = new BlockPredicate(null, new PropertiesPredicate(Map.of("no_such_property", new PropertiesPredicate.ValuePredicate.Exact("never"))), null);
public static final NetworkBuffer.Type<BlockPredicate> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, BlockPredicate value) {
buffer.writeOptional(BlockTypeFilter.NETWORK_TYPE, value.blocks);
buffer.writeOptional(PropertiesPredicate.NETWORK_TYPE, value.state);
buffer.writeOptional(NetworkBuffer.NBT, value.nbt);
}
public static final NetworkBuffer.Type<BlockPredicate> NETWORK_TYPE = NetworkBufferTemplate.template(
BlockTypeFilter.NETWORK_TYPE.optional(), BlockPredicate::blocks,
PropertiesPredicate.NETWORK_TYPE.optional(), BlockPredicate::state,
NBT_COMPOUND.optional(), BlockPredicate::nbt,
BlockPredicate::new
);
@Override
public BlockPredicate read(@NotNull NetworkBuffer buffer) {
return new BlockPredicate(
buffer.readOptional(BlockTypeFilter.NETWORK_TYPE),
buffer.readOptional(PropertiesPredicate.NETWORK_TYPE),
(CompoundBinaryTag) buffer.readOptional(NetworkBuffer.NBT)
);
}
};
public static final BinaryTagSerializer<BlockPredicate> NBT_TYPE = new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull BlockPredicate value) {
@ -116,5 +109,4 @@ public record BlockPredicate(
return false;
return nbt == null || Objects.equals(nbt, BlockUtils.extractClientNbt(block));
}
}

View File

@ -5,6 +5,7 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.StringBinaryTag;
import net.minestom.server.instance.block.Block;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -13,28 +14,14 @@ import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
import static net.minestom.server.network.NetworkBuffer.STRING;
public record PropertiesPredicate(@NotNull Map<String, ValuePredicate> properties) implements Predicate<Block> {
public static final NetworkBuffer.Type<PropertiesPredicate> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, PropertiesPredicate value) {
buffer.write(NetworkBuffer.VAR_INT, value.properties.size());
for (Map.Entry<String, ValuePredicate> entry : value.properties.entrySet()) {
buffer.write(NetworkBuffer.STRING, entry.getKey());
buffer.write(ValuePredicate.NETWORK_TYPE, entry.getValue());
}
}
@Override
public PropertiesPredicate read(@NotNull NetworkBuffer buffer) {
int size = buffer.read(NetworkBuffer.VAR_INT);
Map<String, ValuePredicate> properties = new HashMap<>(size);
for (int i = 0; i < size; i++) {
properties.put(buffer.read(NetworkBuffer.STRING), buffer.read(ValuePredicate.NETWORK_TYPE));
}
return new PropertiesPredicate(properties);
}
};
public static final NetworkBuffer.Type<PropertiesPredicate> NETWORK_TYPE = NetworkBufferTemplate.template(
NetworkBuffer.STRING.mapValue(ValuePredicate.NETWORK_TYPE), PropertiesPredicate::properties,
PropertiesPredicate::new
);
public static final BinaryTagSerializer<PropertiesPredicate> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> {
Map<String, ValuePredicate> properties = new HashMap<>();
@ -74,7 +61,7 @@ public record PropertiesPredicate(@NotNull Map<String, ValuePredicate> propertie
record Exact(@Nullable String value) implements ValuePredicate {
public static final NetworkBuffer.Type<Exact> NETWORK_TYPE = NetworkBuffer.STRING.map(Exact::new, Exact::value);
public static final NetworkBuffer.Type<Exact> NETWORK_TYPE = NetworkBuffer.STRING.transform(Exact::new, Exact::value);
public static final BinaryTagSerializer<Exact> NBT_TYPE = BinaryTagSerializer.STRING.map(Exact::new, Exact::value);
@Override
@ -94,19 +81,12 @@ public record PropertiesPredicate(@NotNull Map<String, ValuePredicate> propertie
* @param max The max value to match, exclusive
*/
record Range(@Nullable String min, @Nullable String max) implements ValuePredicate {
public static final NetworkBuffer.Type<Range> NETWORK_TYPE = NetworkBufferTemplate.template(
STRING.optional(), Range::min,
STRING.optional(), Range::max,
Range::new
);
public static final NetworkBuffer.Type<Range> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, Range value) {
buffer.writeOptional(NetworkBuffer.STRING, value.min);
buffer.writeOptional(NetworkBuffer.STRING, value.max);
}
@Override
public Range read(@NotNull NetworkBuffer buffer) {
return new Range(buffer.readOptional(NetworkBuffer.STRING), buffer.readOptional(NetworkBuffer.STRING));
}
};
public static final BinaryTagSerializer<Range> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> new Range(
tag.get("min") instanceof StringBinaryTag string ? string.value() : null,

View File

@ -2,7 +2,6 @@ package net.minestom.server.instance.palette;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.MathUtils;
import org.jetbrains.annotations.NotNull;
@ -20,7 +19,7 @@ final class AdaptivePalette implements Palette, Cloneable {
this.dimension = dimension;
this.maxBitsPerEntry = maxBitsPerEntry;
this.defaultBitsPerEntry = bitsPerEntry;
this.palette = new FilledPalette(dimension, 0);
this.palette = new PaletteSingle(dimension, 0);
}
@Override
@ -51,12 +50,12 @@ final class AdaptivePalette implements Palette, Cloneable {
@Override
public void fill(int value) {
this.palette = new FilledPalette(dimension, value);
this.palette = new PaletteSingle(dimension, value);
}
@Override
public void setAll(@NotNull EntrySupplier supplier) {
SpecializedPalette newPalette = new FlexiblePalette(this);
SpecializedPalette newPalette = new PaletteIndirect(this);
newPalette.setAll(supplier);
this.palette = newPalette;
}
@ -105,31 +104,24 @@ final class AdaptivePalette implements Palette, Cloneable {
}
}
@Override
public void write(@NotNull NetworkBuffer writer) {
final SpecializedPalette optimized = optimizedPalette();
this.palette = optimized;
optimized.write(writer);
}
SpecializedPalette optimizedPalette() {
var currentPalette = this.palette;
if (currentPalette instanceof FlexiblePalette flexiblePalette) {
final int count = flexiblePalette.count();
if (currentPalette instanceof PaletteIndirect paletteIndirect) {
final int count = paletteIndirect.count();
if (count == 0) {
return new FilledPalette(dimension, 0);
return new PaletteSingle(dimension, 0);
} else {
// Find all entries and compress the palette
IntSet entries = new IntOpenHashSet(flexiblePalette.paletteToValueList.size());
flexiblePalette.getAll((x, y, z, value) -> entries.add(value));
final int currentBitsPerEntry = flexiblePalette.bitsPerEntry();
IntSet entries = new IntOpenHashSet(paletteIndirect.paletteToValueList.size());
paletteIndirect.getAll((x, y, z, value) -> entries.add(value));
final int currentBitsPerEntry = paletteIndirect.bitsPerEntry();
final int bitsPerEntry;
if (entries.size() == 1) {
return new FilledPalette(dimension, entries.iterator().nextInt());
return new PaletteSingle(dimension, entries.iterator().nextInt());
} else if (currentBitsPerEntry > defaultBitsPerEntry &&
(bitsPerEntry = MathUtils.bitsToRepresent(entries.size() - 1)) < currentBitsPerEntry) {
flexiblePalette.resize((byte) bitsPerEntry);
return flexiblePalette;
paletteIndirect.resize((byte) bitsPerEntry);
return paletteIndirect;
}
}
}
@ -138,9 +130,9 @@ final class AdaptivePalette implements Palette, Cloneable {
Palette flexiblePalette() {
SpecializedPalette currentPalette = this.palette;
if (currentPalette instanceof FilledPalette filledPalette) {
currentPalette = new FlexiblePalette(this);
currentPalette.fill(filledPalette.value());
if (currentPalette instanceof PaletteSingle paletteSingle) {
currentPalette = new PaletteIndirect(this);
currentPalette.fill(paletteSingle.value());
this.palette = currentPalette;
}
return currentPalette;

View File

@ -5,12 +5,14 @@ import org.jetbrains.annotations.NotNull;
import java.util.function.IntUnaryOperator;
import static net.minestom.server.network.NetworkBuffer.*;
/**
* Represents a palette used to store blocks and biomes.
* <p>
* 0 is the default value.
*/
public interface Palette extends NetworkBuffer.Writer {
public interface Palette {
static Palette blocks() {
return newPalette(16, 8, 4);
}
@ -77,4 +79,58 @@ public interface Palette extends NetworkBuffer.Writer {
interface EntryFunction {
int apply(int x, int y, int z, int value);
}
NetworkBuffer.Type<Palette> BLOCK_SERIALIZER = serializer(16, 4, 8);
NetworkBuffer.Type<Palette> BIOME_SERIALIZER = serializer(4, 1, 3);
static NetworkBuffer.Type<Palette> serializer(int dimension, int minIndirect, int maxIndirect) {
return new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, Palette value) {
switch (value) {
case AdaptivePalette adaptive -> {
final SpecializedPalette optimized = adaptive.optimizedPalette();
adaptive.palette = optimized;
BLOCK_SERIALIZER.write(buffer, optimized);
}
case PaletteSingle single -> {
buffer.write(BYTE, (byte) 0);
buffer.write(VAR_INT, single.value());
buffer.write(VAR_INT, 0);
}
case PaletteIndirect indirect -> {
buffer.write(BYTE, (byte) value.bitsPerEntry());
if (indirect.bitsPerEntry() <= indirect.maxBitsPerEntry()) { // Palette index
buffer.write(VAR_INT.list(), indirect.paletteToValueList);
}
buffer.write(LONG_ARRAY, indirect.values);
}
default -> throw new UnsupportedOperationException("Unsupported palette type: " + value.getClass());
}
}
@Override
public Palette read(@NotNull NetworkBuffer buffer) {
final byte bitsPerEntry = buffer.read(BYTE);
if (bitsPerEntry == 0) {
// Single valued 0-0
final int value = buffer.read(VAR_INT);
return new PaletteSingle((byte) dimension, value);
} else if (bitsPerEntry >= minIndirect && bitsPerEntry <= maxIndirect) {
// Indirect palette
final int[] palette = buffer.read(VAR_INT_ARRAY);
final long[] data = buffer.read(LONG_ARRAY);
return new PaletteIndirect(dimension, maxIndirect, bitsPerEntry,
Palettes.count(bitsPerEntry, data),
palette, data);
} else {
// Direct palette
final long[] data = buffer.read(LONG_ARRAY);
return new PaletteIndirect(dimension, maxIndirect, bitsPerEntry,
Palettes.count(bitsPerEntry, data),
new int[0], data);
}
}
};
}
}

View File

@ -3,7 +3,6 @@ package net.minestom.server.instance.palette;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.minestom.server.MinecraftServer;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.MathUtils;
import org.jetbrains.annotations.NotNull;
@ -11,52 +10,64 @@ import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntUnaryOperator;
import static net.minestom.server.network.NetworkBuffer.*;
import static net.minestom.server.instance.palette.Palettes.arrayLength;
import static net.minestom.server.instance.palette.Palettes.read;
/**
* Palette able to take any value anywhere. May consume more memory than required.
*/
final class FlexiblePalette implements SpecializedPalette, Cloneable {
final class PaletteIndirect implements SpecializedPalette, Cloneable {
private static final ThreadLocal<int[]> WRITE_CACHE = ThreadLocal.withInitial(() -> new int[4096]);
// Specific to this palette type
private final AdaptivePalette adaptivePalette;
private final int dimension;
private final int maxBitsPerEntry;
private byte bitsPerEntry;
private int count;
private long[] values;
long[] values;
// palette index = value
IntArrayList paletteToValueList;
// value = palette index
private Int2IntOpenHashMap valueToPaletteMap;
FlexiblePalette(AdaptivePalette adaptivePalette, byte bitsPerEntry) {
this.adaptivePalette = adaptivePalette;
PaletteIndirect(int dimension, int maxBitsPerEntry, byte bitsPerEntry,
int count, int[] palette, long[] values) {
this.dimension = dimension;
this.maxBitsPerEntry = maxBitsPerEntry;
this.bitsPerEntry = bitsPerEntry;
this.paletteToValueList = new IntArrayList(1);
this.paletteToValueList.add(0);
this.valueToPaletteMap = new Int2IntOpenHashMap(1);
this.valueToPaletteMap.put(0, 0);
this.count = count;
this.values = values;
this.paletteToValueList = new IntArrayList(palette.length);
this.valueToPaletteMap = new Int2IntOpenHashMap(palette.length);
this.valueToPaletteMap.defaultReturnValue(-1);
final int valuesPerLong = 64 / bitsPerEntry;
this.values = new long[(maxSize() + valuesPerLong - 1) / valuesPerLong];
for (int i = 0; i < palette.length; i++) {
this.paletteToValueList.add(palette[i]);
this.valueToPaletteMap.put(palette[i], i);
}
this.values = new long[arrayLength(dimension(), bitsPerEntry)];
}
FlexiblePalette(AdaptivePalette adaptivePalette) {
this(adaptivePalette, adaptivePalette.defaultBitsPerEntry);
PaletteIndirect(int dimension, int maxBitsPerEntry, byte bitsPerEntry) {
this(dimension, maxBitsPerEntry, bitsPerEntry,
0,
new int[]{0},
new long[arrayLength(dimension, bitsPerEntry)]
);
}
PaletteIndirect(AdaptivePalette palette) {
this(palette.dimension, palette.maxBitsPerEntry, palette.defaultBitsPerEntry);
}
@Override
public int get(int x, int y, int z) {
final int bitsPerEntry = this.bitsPerEntry;
final int sectionIndex = getSectionIndex(dimension(), x, y, z);
final int valuesPerLong = 64 / bitsPerEntry;
final int index = sectionIndex / valuesPerLong;
final int bitIndex = (sectionIndex - index * valuesPerLong) * bitsPerEntry;
final int value = (int) (values[index] >> bitIndex) & ((1 << bitsPerEntry) - 1);
final int value = read(dimension(), bitsPerEntry, values, x, y, z);
// Change to palette value and return
return hasPalette() ? paletteToValueList.getInt(value) : value;
}
@ -74,20 +85,9 @@ final class FlexiblePalette implements SpecializedPalette, Cloneable {
@Override
public void set(int x, int y, int z, int value) {
value = getPaletteIndex(value);
final int bitsPerEntry = this.bitsPerEntry;
final long[] values = this.values;
// Change to palette value
final int valuesPerLong = 64 / bitsPerEntry;
final int sectionIndex = getSectionIndex(dimension(), x, y, z);
final int index = sectionIndex / valuesPerLong;
final int bitIndex = (sectionIndex - index * valuesPerLong) * bitsPerEntry;
final long block = values[index];
final long clear = (1L << bitsPerEntry) - 1L;
final long oldBlock = block >> bitIndex & clear;
values[index] = block & ~(clear << bitIndex) | ((long) value << bitIndex);
final int oldValue = Palettes.write(dimension(), bitsPerEntry, values, x, y, z, value);
// Check if block count needs to be updated
final boolean currentAir = oldBlock == 0;
final boolean currentAir = oldValue == 0;
if (currentAir != (value == 0)) this.count += currentAir ? 1 : -1;
}
@ -99,13 +99,7 @@ final class FlexiblePalette implements SpecializedPalette, Cloneable {
return;
}
value = getPaletteIndex(value);
final int bitsPerEntry = this.bitsPerEntry;
final int valuesPerLong = 64 / bitsPerEntry;
final long[] values = this.values;
long block = 0;
for (int i = 0; i < valuesPerLong; i++)
block |= (long) value << i * bitsPerEntry;
Arrays.fill(values, block);
Palettes.fill(bitsPerEntry, values, value);
this.count = maxSize();
}
@ -185,18 +179,18 @@ final class FlexiblePalette implements SpecializedPalette, Cloneable {
@Override
public int maxBitsPerEntry() {
return adaptivePalette.maxBitsPerEntry();
return maxBitsPerEntry;
}
@Override
public int dimension() {
return adaptivePalette.dimension();
return dimension;
}
@Override
public @NotNull SpecializedPalette clone() {
try {
FlexiblePalette palette = (FlexiblePalette) super.clone();
PaletteIndirect palette = (PaletteIndirect) super.clone();
palette.values = values != null ? values.clone() : null;
palette.paletteToValueList = paletteToValueList.clone();
palette.valueToPaletteMap = valueToPaletteMap.clone();
@ -208,15 +202,6 @@ final class FlexiblePalette implements SpecializedPalette, Cloneable {
}
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(BYTE, bitsPerEntry);
if (bitsPerEntry <= maxBitsPerEntry()) { // Palette index
writer.writeCollection(VAR_INT, paletteToValueList);
}
writer.write(LONG_ARRAY, values);
}
private void retrieveAll(@NotNull EntryConsumer consumer, boolean consumeEmpty) {
if (!consumeEmpty && count == 0) return;
final long[] values = this.values;
@ -268,7 +253,7 @@ final class FlexiblePalette implements SpecializedPalette, Cloneable {
void resize(byte newBitsPerEntry) {
newBitsPerEntry = newBitsPerEntry > maxBitsPerEntry() ? 15 : newBitsPerEntry;
FlexiblePalette palette = new FlexiblePalette(adaptivePalette, newBitsPerEntry);
PaletteIndirect palette = new PaletteIndirect(dimension, maxBitsPerEntry, newBitsPerEntry);
palette.paletteToValueList = paletteToValueList;
palette.valueToPaletteMap = valueToPaletteMap;
getAll(palette::set);
@ -297,14 +282,6 @@ final class FlexiblePalette implements SpecializedPalette, Cloneable {
return bitsPerEntry <= maxBitsPerEntry();
}
static int getSectionIndex(int dimension, int x, int y, int z) {
final int dimensionMask = dimension - 1;
final int dimensionBitCount = MathUtils.bitsToRepresent(dimensionMask);
return (y & dimensionMask) << (dimensionBitCount << 1) |
(z & dimensionMask) << dimensionBitCount |
(x & dimensionMask);
}
static int maxPaletteSize(int bitsPerEntry) {
return 1 << bitsPerEntry;
}

View File

@ -1,15 +1,11 @@
package net.minestom.server.instance.palette;
import net.minestom.server.network.NetworkBuffer;
import org.jetbrains.annotations.NotNull;
import static net.minestom.server.network.NetworkBuffer.BYTE;
import static net.minestom.server.network.NetworkBuffer.VAR_INT;
/**
* Palette containing a single value. Useful for both empty and full palettes.
*/
record FilledPalette(byte dim, int value) implements SpecializedPalette.Immutable {
record PaletteSingle(byte dim, int value) implements SpecializedPalette.Immutable {
@Override
public int get(int x, int y, int z) {
return value;
@ -44,11 +40,4 @@ record FilledPalette(byte dim, int value) implements SpecializedPalette.Immutabl
public @NotNull SpecializedPalette clone() {
return this;
}
@Override
public void write(@NotNull NetworkBuffer writer) {
writer.write(BYTE, (byte) 0);
writer.write(VAR_INT, value);
writer.write(VAR_INT, 0);
}
}

View File

@ -0,0 +1,98 @@
package net.minestom.server.instance.palette;
import net.minestom.server.utils.MathUtils;
import java.util.Arrays;
public final class Palettes {
private Palettes() {
}
public static long[] pack(int[] ints, int bitsPerEntry) {
final int intsPerLong = (int) Math.floor(64d / bitsPerEntry);
long[] longs = new long[(int) Math.ceil(ints.length / (double) intsPerLong)];
final long mask = (1L << bitsPerEntry) - 1L;
for (int i = 0; i < longs.length; i++) {
for (int intIndex = 0; intIndex < intsPerLong; intIndex++) {
final int bitIndex = intIndex * bitsPerEntry;
final int intActualIndex = intIndex + i * intsPerLong;
if (intActualIndex < ints.length) {
longs[i] |= (ints[intActualIndex] & mask) << bitIndex;
}
}
}
return longs;
}
public static void unpack(int[] out, long[] in, int bitsPerEntry) {
assert in.length != 0 : "unpack input array is zero";
final double intsPerLong = Math.floor(64d / bitsPerEntry);
final int intsPerLongCeil = (int) Math.ceil(intsPerLong);
long mask = (1L << bitsPerEntry) - 1L;
for (int i = 0; i < out.length; i++) {
final int longIndex = i / intsPerLongCeil;
final int subIndex = i % intsPerLongCeil;
out[i] = (int) ((in[longIndex] >>> (bitsPerEntry * subIndex)) & mask);
}
}
public static int arrayLength(int dimension, int bitsPerEntry) {
final int elementCount = dimension * dimension * dimension;
final int valuesPerLong = 64 / bitsPerEntry;
return (elementCount + valuesPerLong - 1) / valuesPerLong;
}
public static int read(int dimension, int bitsPerEntry, long[] values,
int x, int y, int z) {
final int sectionIndex = sectionIndex(dimension, x, y, z);
final int valuesPerLong = 64 / bitsPerEntry;
final int index = sectionIndex / valuesPerLong;
final int bitIndex = (sectionIndex - index * valuesPerLong) * bitsPerEntry;
return (int) (values[index] >> bitIndex) & ((1 << bitsPerEntry) - 1);
}
public static int write(int dimension, int bitsPerEntry, long[] values,
int x, int y, int z, int value) {
final int valuesPerLong = 64 / bitsPerEntry;
final int sectionIndex = sectionIndex(dimension, x, y, z);
final int index = sectionIndex / valuesPerLong;
final int bitIndex = (sectionIndex - index * valuesPerLong) * bitsPerEntry;
final long block = values[index];
final long clear = (1L << bitsPerEntry) - 1L;
final long oldBlock = block >> bitIndex & clear;
values[index] = block & ~(clear << bitIndex) | ((long) value << bitIndex);
return (int) oldBlock;
}
public static void fill(int bitsPerEntry, long[] values, int value) {
final int valuesPerLong = 64 / bitsPerEntry;
long block = 0;
for (int i = 0; i < valuesPerLong; i++) block |= (long) value << i * bitsPerEntry;
Arrays.fill(values, block);
}
public static int count(int bitsPerEntry, long[] values) {
final int valuesPerLong = 64 / bitsPerEntry;
int count = 0;
for (long block : values) {
for (int i = 0; i < valuesPerLong; i++) {
count += (block >>> i * bitsPerEntry) & ((1 << bitsPerEntry) - 1);
}
}
return count;
}
public static int sectionIndex(int dimension, int x, int y, int z) {
final int dimensionMask = dimension - 1;
final int dimensionBitCount = MathUtils.bitsToRepresent(dimensionMask);
return (y & dimensionMask) << (dimensionBitCount << 1) |
(z & dimensionMask) << dimensionBitCount |
(x & dimensionMask);
}
}

View File

@ -58,7 +58,7 @@ public sealed interface ItemStack extends TagReadable, DataComponent.Holder, Hov
return ItemStackImpl.create(material, amount, components);
}
};
@NotNull NetworkBuffer.Type<ItemStack> STRICT_NETWORK_TYPE = NETWORK_TYPE.map(itemStack -> {
@NotNull NetworkBuffer.Type<ItemStack> STRICT_NETWORK_TYPE = NETWORK_TYPE.transform(itemStack -> {
Check.argCondition(itemStack.amount() == 0 || itemStack.isAir(), "ItemStack cannot be empty");
return itemStack;
}, itemStack -> {

View File

@ -16,7 +16,7 @@ import java.util.Collection;
public sealed interface Material extends StaticProtocolObject, Materials permits MaterialImpl {
NetworkBuffer.Type<Material> NETWORK_TYPE = NetworkBuffer.VAR_INT.map(MaterialImpl::getId, Material::id);
NetworkBuffer.Type<Material> NETWORK_TYPE = NetworkBuffer.VAR_INT.transform(MaterialImpl::getId, Material::id);
BinaryTagSerializer<Material> NBT_TYPE = BinaryTagSerializer.STRING.map(MaterialImpl::getSafe, Material::name);
/**

View File

@ -21,12 +21,12 @@ public record FilteredText<T>(@NotNull T text, @Nullable T filtered) {
@Override
public void write(@NotNull NetworkBuffer buffer, FilteredText<T> value) {
buffer.write(inner, value.text);
buffer.writeOptional(inner, value.filtered);
buffer.write(inner.optional(), value.filtered);
}
@Override
public FilteredText<T> read(@NotNull NetworkBuffer buffer) {
return new FilteredText<>(buffer.read(inner), buffer.readOptional(inner));
return new FilteredText<>(buffer.read(inner), buffer.read(inner.optional()));
}
};
}

View File

@ -4,27 +4,20 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.item.armor.TrimMaterial;
import net.minestom.server.item.armor.TrimPattern;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
public record ArmorTrim(@NotNull DynamicRegistry.Key<TrimMaterial> material, @NotNull DynamicRegistry.Key<TrimPattern> pattern, boolean showInTooltip) {
public record ArmorTrim(@NotNull DynamicRegistry.Key<TrimMaterial> material,
@NotNull DynamicRegistry.Key<TrimPattern> pattern, boolean showInTooltip) {
public static final NetworkBuffer.Type<ArmorTrim> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, ArmorTrim value) {
buffer.write(TrimMaterial.NETWORK_TYPE, value.material);
buffer.write(TrimPattern.NETWORK_TYPE, value.pattern);
buffer.write(NetworkBuffer.BOOLEAN, value.showInTooltip);
}
@Override
public ArmorTrim read(@NotNull NetworkBuffer buffer) {
return new ArmorTrim(buffer.read(TrimMaterial.NETWORK_TYPE),
buffer.read(TrimPattern.NETWORK_TYPE),
buffer.read(NetworkBuffer.BOOLEAN));
}
};
public static final NetworkBuffer.Type<ArmorTrim> NETWORK_TYPE = NetworkBufferTemplate.template(
TrimMaterial.NETWORK_TYPE, ArmorTrim::material,
TrimPattern.NETWORK_TYPE, ArmorTrim::pattern,
NetworkBuffer.BOOLEAN, ArmorTrim::showInTooltip,
ArmorTrim::new
);
public static final BinaryTagSerializer<ArmorTrim> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> {
@ -43,5 +36,4 @@ public record ArmorTrim(@NotNull DynamicRegistry.Key<TrimMaterial> material, @No
public @NotNull ArmorTrim withTooltip(boolean showInTooltip) {
return new ArmorTrim(material, pattern, showInTooltip);
}
}

View File

@ -8,28 +8,23 @@ import net.minestom.server.entity.EquipmentSlotGroup;
import net.minestom.server.entity.attribute.Attribute;
import net.minestom.server.entity.attribute.AttributeModifier;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import static net.minestom.server.network.NetworkBuffer.BOOLEAN;
public record AttributeList(@NotNull List<Modifier> modifiers, boolean showInTooltip) {
public static final AttributeList EMPTY = new AttributeList(List.of(), true);
public static final NetworkBuffer.Type<AttributeList> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, AttributeList value) {
buffer.writeCollection(Modifier.NETWORK_TYPE, value.modifiers);
buffer.write(NetworkBuffer.BOOLEAN, value.showInTooltip);
}
@Override
public AttributeList read(@NotNull NetworkBuffer buffer) {
return new AttributeList(buffer.readCollection(Modifier.NETWORK_TYPE, Short.MAX_VALUE),
buffer.read(NetworkBuffer.BOOLEAN));
}
};
public static final NetworkBuffer.Type<AttributeList> NETWORK_TYPE = NetworkBufferTemplate.template(
Modifier.NETWORK_TYPE.list(Short.MAX_VALUE), AttributeList::modifiers,
BOOLEAN, AttributeList::showInTooltip,
AttributeList::new
);
public static final BinaryTagSerializer<AttributeList> NBT_TYPE = new BinaryTagSerializer<>() {
@Override
@ -57,22 +52,14 @@ public record AttributeList(@NotNull List<Modifier> modifiers, boolean showInToo
}
};
public record Modifier(@NotNull Attribute attribute, @NotNull AttributeModifier modifier, @NotNull EquipmentSlotGroup slot) {
public static final NetworkBuffer.Type<Modifier> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, Modifier value) {
buffer.write(Attribute.NETWORK_TYPE, value.attribute);
buffer.write(AttributeModifier.NETWORK_TYPE, value.modifier);
buffer.writeEnum(EquipmentSlotGroup.class, value.slot);
}
@Override
public Modifier read(@NotNull NetworkBuffer buffer) {
return new Modifier(buffer.read(Attribute.NETWORK_TYPE),
buffer.read(AttributeModifier.NETWORK_TYPE),
buffer.readEnum(EquipmentSlotGroup.class));
}
};
public record Modifier(@NotNull Attribute attribute, @NotNull AttributeModifier modifier,
@NotNull EquipmentSlotGroup slot) {
public static final NetworkBuffer.Type<Modifier> NETWORK_TYPE = NetworkBufferTemplate.template(
Attribute.NETWORK_TYPE, Modifier::attribute,
AttributeModifier.NETWORK_TYPE, Modifier::modifier,
NetworkBuffer.Enum(EquipmentSlotGroup.class), Modifier::slot,
Modifier::new
);
public static final BinaryTagSerializer<Modifier> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> new Modifier(
Attribute.NBT_TYPE.read(tag.get("type")),

View File

@ -5,6 +5,7 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.color.DyeColor;
import net.minestom.server.instance.block.banner.BannerPattern;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
@ -15,22 +16,15 @@ import java.util.List;
public record BannerPatterns(@NotNull List<Layer> layers) {
public static final int MAX_LAYERS = 1024;
public static final NetworkBuffer.Type<BannerPatterns> NETWORK_TYPE = Layer.NETWORK_TYPE.list(MAX_LAYERS).map(BannerPatterns::new, BannerPatterns::layers);
public static final NetworkBuffer.Type<BannerPatterns> NETWORK_TYPE = Layer.NETWORK_TYPE.list(MAX_LAYERS).transform(BannerPatterns::new, BannerPatterns::layers);
public static final BinaryTagSerializer<BannerPatterns> NBT_TYPE = Layer.NBT_TYPE.list().map(BannerPatterns::new, BannerPatterns::layers);
public record Layer(@NotNull DynamicRegistry.Key<BannerPattern> pattern, @NotNull DyeColor color) {
public static final NetworkBuffer.Type<Layer> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, Layer value) {
buffer.write(BannerPattern.NETWORK_TYPE, value.pattern);
buffer.write(DyeColor.NETWORK_TYPE, value.color);
}
@Override
public Layer read(@NotNull NetworkBuffer buffer) {
return new Layer(buffer.read(BannerPattern.NETWORK_TYPE), buffer.read(DyeColor.NETWORK_TYPE));
}
};
public static final NetworkBuffer.Type<Layer> NETWORK_TYPE = NetworkBufferTemplate.template(
BannerPattern.NETWORK_TYPE, Layer::pattern,
DyeColor.NETWORK_TYPE, Layer::color,
Layer::new
);
public static final BinaryTagSerializer<Layer> NBT_TYPE = new BinaryTagSerializer<Layer>() {
@Override
public @NotNull BinaryTag write(@NotNull Context context, @NotNull Layer value) {

View File

@ -2,26 +2,18 @@ package net.minestom.server.item.component;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
public record Bee(@NotNull CustomData entityData, int ticksInHive, int minTicksInHive) {
public static @NotNull NetworkBuffer.Type<Bee> NETWORK_TYPE = new NetworkBuffer.Type<Bee>() {
@Override
public void write(@NotNull NetworkBuffer buffer, Bee value) {
buffer.write(CustomData.NETWORK_TYPE, value.entityData);
buffer.write(NetworkBuffer.VAR_INT, value.ticksInHive);
buffer.write(NetworkBuffer.VAR_INT, value.minTicksInHive);
}
@Override
public Bee read(@NotNull NetworkBuffer buffer) {
return new Bee(buffer.read(CustomData.NETWORK_TYPE),
buffer.read(NetworkBuffer.VAR_INT),
buffer.read(NetworkBuffer.VAR_INT));
}
};
public static final NetworkBuffer.Type<Bee> NETWORK_TYPE = NetworkBufferTemplate.template(
CustomData.NETWORK_TYPE, Bee::entityData,
NetworkBuffer.VAR_INT, Bee::ticksInHive,
NetworkBuffer.VAR_INT, Bee::minTicksInHive,
Bee::new
);
public static @NotNull BinaryTagSerializer<Bee> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> new Bee(CustomData.NBT_TYPE.read(tag.getCompound("entity_data")),
tag.getInt("ticks_in_hive"),

View File

@ -6,24 +6,18 @@ import net.kyori.adventure.nbt.IntBinaryTag;
import net.kyori.adventure.util.RGBLike;
import net.minestom.server.color.Color;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
public record DyedItemColor(@NotNull RGBLike color, boolean showInTooltip) {
public static DyedItemColor LEATHER = new DyedItemColor(new Color(-6265536), true);
public static final NetworkBuffer.Type<DyedItemColor> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, DyedItemColor value) {
buffer.write(Color.NETWORK_TYPE, value.color);
buffer.write(NetworkBuffer.BOOLEAN, value.showInTooltip);
}
@Override
public DyedItemColor read(@NotNull NetworkBuffer buffer) {
return new DyedItemColor(buffer.read(Color.NETWORK_TYPE), buffer.read(NetworkBuffer.BOOLEAN));
}
};
public static final NetworkBuffer.Type<DyedItemColor> NETWORK_TYPE = NetworkBufferTemplate.template(
Color.NETWORK_TYPE, DyedItemColor::color,
NetworkBuffer.BOOLEAN, DyedItemColor::showInTooltip,
DyedItemColor::new
);
public static final BinaryTagSerializer<DyedItemColor> NBT_TYPE = new BinaryTagSerializer<>() {
@Override

View File

@ -4,9 +4,9 @@ import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.item.enchant.Enchantment;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
@ -18,30 +18,11 @@ public record EnchantmentList(@NotNull Map<DynamicRegistry.Key<Enchantment>, Int
boolean showInTooltip) {
public static final EnchantmentList EMPTY = new EnchantmentList(Map.of(), true);
public static NetworkBuffer.Type<EnchantmentList> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, @NotNull EnchantmentList value) {
buffer.write(NetworkBuffer.VAR_INT, value.enchantments.size());
for (Map.Entry<DynamicRegistry.Key<Enchantment>, Integer> entry : value.enchantments.entrySet()) {
buffer.write(Enchantment.NETWORK_TYPE, entry.getKey());
buffer.write(NetworkBuffer.VAR_INT, entry.getValue());
}
buffer.write(NetworkBuffer.BOOLEAN, value.showInTooltip);
}
@Override
public @NotNull EnchantmentList read(@NotNull NetworkBuffer buffer) {
int size = buffer.read(NetworkBuffer.VAR_INT);
Check.argCondition(size < 0 || size > Short.MAX_VALUE, "Invalid enchantment list size: {0}", size);
Map<DynamicRegistry.Key<Enchantment>, Integer> enchantments = new HashMap<>(size);
for (int i = 0; i < size; i++) {
DynamicRegistry.Key<Enchantment> enchantment = buffer.read(Enchantment.NETWORK_TYPE);
enchantments.put(enchantment, buffer.read(NetworkBuffer.VAR_INT));
}
boolean showInTooltip = buffer.read(NetworkBuffer.BOOLEAN);
return new EnchantmentList(enchantments, showInTooltip);
}
};
public static final NetworkBuffer.Type<EnchantmentList> NETWORK_TYPE = NetworkBufferTemplate.template(
Enchantment.NETWORK_TYPE.mapValue(NetworkBuffer.VAR_INT, Short.MAX_VALUE), EnchantmentList::enchantments,
NetworkBuffer.BOOLEAN, EnchantmentList::showInTooltip,
EnchantmentList::new
);
public static BinaryTagSerializer<EnchantmentList> NBT_TYPE = new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull Context context, @NotNull EnchantmentList value) {

View File

@ -4,6 +4,7 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.util.RGBLike;
import net.minestom.server.color.Color;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
@ -11,6 +12,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import static net.minestom.server.network.NetworkBuffer.BOOLEAN;
public record FireworkExplosion(
@NotNull Shape shape,
@NotNull List<RGBLike> colors,
@ -27,27 +30,14 @@ public record FireworkExplosion(
BURST
}
public static final NetworkBuffer.Type<FireworkExplosion> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, FireworkExplosion value) {
buffer.writeEnum(Shape.class, value.shape);
buffer.writeCollection(Color.NETWORK_TYPE, value.colors);
buffer.writeCollection(Color.NETWORK_TYPE, value.fadeColors);
buffer.write(NetworkBuffer.BOOLEAN, value.hasTrail);
buffer.write(NetworkBuffer.BOOLEAN, value.hasTwinkle);
}
@Override
public FireworkExplosion read(@NotNull NetworkBuffer buffer) {
return new FireworkExplosion(
buffer.readEnum(Shape.class),
buffer.readCollection(Color.NETWORK_TYPE, Short.MAX_VALUE),
buffer.readCollection(Color.NETWORK_TYPE, Short.MAX_VALUE),
buffer.read(NetworkBuffer.BOOLEAN),
buffer.read(NetworkBuffer.BOOLEAN)
);
}
};
public static final NetworkBuffer.Type<FireworkExplosion> NETWORK_TYPE = NetworkBufferTemplate.template(
NetworkBuffer.Enum(Shape.class), FireworkExplosion::shape,
Color.NETWORK_TYPE.list(Short.MAX_VALUE), FireworkExplosion::colors,
Color.NETWORK_TYPE.list(Short.MAX_VALUE), FireworkExplosion::fadeColors,
BOOLEAN, FireworkExplosion::hasTrail,
BOOLEAN, FireworkExplosion::hasTwinkle,
FireworkExplosion::new
);
public static final BinaryTagSerializer<FireworkExplosion> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> {

View File

@ -2,6 +2,7 @@ package net.minestom.server.item.component;
import net.kyori.adventure.nbt.*;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
@ -11,19 +12,11 @@ import java.util.List;
public record FireworkList(int flightDuration, @NotNull List<FireworkExplosion> explosions) {
public static final FireworkList EMPTY = new FireworkList(0, List.of());
public static final NetworkBuffer.Type<FireworkList> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, FireworkList value) {
buffer.write(NetworkBuffer.VAR_INT, value.flightDuration);
buffer.writeCollection(FireworkExplosion.NETWORK_TYPE, value.explosions);
}
@Override
public FireworkList read(@NotNull NetworkBuffer buffer) {
return new FireworkList(buffer.read(NetworkBuffer.VAR_INT),
buffer.readCollection(FireworkExplosion.NETWORK_TYPE, 256));
}
};
public static final NetworkBuffer.Type<FireworkList> NETWORK_TYPE = NetworkBufferTemplate.template(
NetworkBuffer.VAR_INT, FireworkList::flightDuration,
FireworkExplosion.NETWORK_TYPE.list(256), FireworkList::explosions,
FireworkList::new
);
public static final BinaryTagSerializer<FireworkList> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> {

View File

@ -6,43 +6,33 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.ServerFlag;
import net.minestom.server.item.ItemStack;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.potion.CustomPotionEffect;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import static net.minestom.server.network.NetworkBuffer.*;
public record Food(int nutrition, float saturationModifier, boolean canAlwaysEat, float eatSeconds,
@NotNull ItemStack usingConvertsTo, @NotNull List<EffectChance> effects) {
public static final float DEFAULT_EAT_SECONDS = 1.6f;
public static final NetworkBuffer.Type<Food> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, Food value) {
buffer.write(NetworkBuffer.VAR_INT, value.nutrition);
buffer.write(NetworkBuffer.FLOAT, value.saturationModifier);
buffer.write(NetworkBuffer.BOOLEAN, value.canAlwaysEat);
buffer.write(NetworkBuffer.FLOAT, value.eatSeconds);
buffer.write(ItemStack.NETWORK_TYPE, value.usingConvertsTo);
buffer.writeCollection(EffectChance.NETWORK_TYPE, value.effects);
}
public static final NetworkBuffer.Type<Food> NETWORK_TYPE = NetworkBufferTemplate.template(
VAR_INT, Food::nutrition,
FLOAT, Food::saturationModifier,
BOOLEAN, Food::canAlwaysEat,
FLOAT, Food::eatSeconds,
ItemStack.NETWORK_TYPE, Food::usingConvertsTo,
EffectChance.NETWORK_TYPE.list(Short.MAX_VALUE), Food::effects,
Food::new
);
@Override
public Food read(@NotNull NetworkBuffer buffer) {
return new Food(
buffer.read(NetworkBuffer.VAR_INT),
buffer.read(NetworkBuffer.FLOAT),
buffer.read(NetworkBuffer.BOOLEAN),
buffer.read(NetworkBuffer.FLOAT),
buffer.read(ItemStack.NETWORK_TYPE),
buffer.readCollection(EffectChance.NETWORK_TYPE, Short.MAX_VALUE)
);
}
};
public static final BinaryTagSerializer<Food> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> new Food(
tag.getInt("nutrition"),
tag.getFloat("saturation"),
tag.getFloat("saturation_modifier"),
tag.getBoolean("can_always_eat"),
tag.getFloat("eat_seconds", DEFAULT_EAT_SECONDS),
tag.get("using_converts_to") instanceof BinaryTag usingConvertsTo
@ -51,7 +41,7 @@ public record Food(int nutrition, float saturationModifier, boolean canAlwaysEat
value -> {
CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder()
.putInt("nutrition", value.nutrition)
.putFloat("saturation", value.saturationModifier)
.putFloat("saturation_odifier", value.saturationModifier)
.putBoolean("can_always_eat", value.canAlwaysEat)
.putFloat("eat_seconds", value.eatSeconds)
.put("effects", EffectChance.NBT_LIST_TYPE.write(value.effects));
@ -71,18 +61,12 @@ public record Food(int nutrition, float saturationModifier, boolean canAlwaysEat
}
public record EffectChance(@NotNull CustomPotionEffect effect, float probability) {
public static final NetworkBuffer.Type<EffectChance> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, EffectChance value) {
CustomPotionEffect.NETWORK_TYPE.write(buffer, value.effect);
buffer.write(NetworkBuffer.FLOAT, value.probability);
}
public static final NetworkBuffer.Type<EffectChance> NETWORK_TYPE = NetworkBufferTemplate.template(
CustomPotionEffect.NETWORK_TYPE, EffectChance::effect,
FLOAT, EffectChance::probability,
EffectChance::new
);
@Override
public EffectChance read(@NotNull NetworkBuffer buffer) {
return null;
}
};
public static final BinaryTagSerializer<EffectChance> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> new EffectChance(
CustomPotionEffect.NBT_TYPE.read(tag.getCompound("effect")),
@ -94,5 +78,4 @@ public record Food(int nutrition, float saturationModifier, boolean canAlwaysEat
);
public static final BinaryTagSerializer<List<EffectChance>> NBT_LIST_TYPE = NBT_TYPE.list();
}
}

View File

@ -5,6 +5,7 @@ import net.kyori.adventure.nbt.IntArrayBinaryTag;
import net.kyori.adventure.nbt.StringBinaryTag;
import net.minestom.server.entity.PlayerSkin;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -12,22 +13,19 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.UUID;
import static net.minestom.server.network.NetworkBuffer.STRING;
import static net.minestom.server.network.NetworkBuffer.UUID;
public record HeadProfile(@Nullable String name, @Nullable UUID uuid, @NotNull List<Property> properties) {
public static final HeadProfile EMPTY = new HeadProfile(null, null, List.of());
public static final NetworkBuffer.Type<HeadProfile> NETWORK_TYPE = new NetworkBuffer.Type<HeadProfile>() {
@Override
public void write(@NotNull NetworkBuffer buffer, HeadProfile value) {
buffer.writeOptional(NetworkBuffer.STRING, value.name);
buffer.writeOptional(NetworkBuffer.UUID, value.uuid);
buffer.writeCollection(Property.NETWORK_TYPE, value.properties);
}
public static final NetworkBuffer.Type<HeadProfile> NETWORK_TYPE = NetworkBufferTemplate.template(
STRING.optional(), HeadProfile::name,
UUID.optional(), HeadProfile::uuid,
Property.NETWORK_TYPE.list(Short.MAX_VALUE), HeadProfile::properties,
HeadProfile::new
);
@Override
public HeadProfile read(@NotNull NetworkBuffer buffer) {
return new HeadProfile(buffer.readOptional(NetworkBuffer.STRING), buffer.readOptional(NetworkBuffer.UUID), buffer.readCollection(Property.NETWORK_TYPE, Short.MAX_VALUE));
}
};
public static final BinaryTagSerializer<HeadProfile> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> new HeadProfile(
tag.get("name") instanceof StringBinaryTag string ? string.value() : null,
@ -38,7 +36,8 @@ public record HeadProfile(@Nullable String name, @Nullable UUID uuid, @NotNull L
CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder();
if (profile.name != null) builder.putString("name", profile.name);
if (profile.uuid != null) builder.put("uuid", BinaryTagSerializer.UUID.write(profile.uuid));
if (!profile.properties.isEmpty()) builder.put("properties", Property.NBT_LIST_TYPE.write(profile.properties));
if (!profile.properties.isEmpty())
builder.put("properties", Property.NBT_LIST_TYPE.write(profile.properties));
return builder.build();
}
);
@ -57,19 +56,13 @@ public record HeadProfile(@Nullable String name, @Nullable UUID uuid, @NotNull L
}
public record Property(@NotNull String name, @NotNull String value, @Nullable String signature) {
public static final NetworkBuffer.Type<Property> NETWORK_TYPE = new NetworkBuffer.Type<Property>() {
@Override
public void write(@NotNull NetworkBuffer buffer, Property value) {
buffer.write(NetworkBuffer.STRING, value.name);
buffer.write(NetworkBuffer.STRING, value.value);
buffer.writeOptional(NetworkBuffer.STRING, value.signature);
}
public static final NetworkBuffer.Type<Property> NETWORK_TYPE = NetworkBufferTemplate.template(
STRING, Property::name,
STRING, Property::value,
STRING.optional(), Property::signature,
Property::new
);
@Override
public Property read(@NotNull NetworkBuffer buffer) {
return new Property(buffer.read(NetworkBuffer.STRING), buffer.read(NetworkBuffer.STRING), buffer.readOptional(NetworkBuffer.STRING));
}
};
public static final BinaryTagSerializer<Property> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> new Property(tag.getString("name"), tag.getString("value"),
tag.get("signature") instanceof StringBinaryTag signature ? signature.value() : null),

View File

@ -5,6 +5,7 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.StringBinaryTag;
import net.minestom.server.instance.block.Block;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
@ -14,26 +15,10 @@ import java.util.Map;
public record ItemBlockState(@NotNull Map<String, String> properties) {
public static final ItemBlockState EMPTY = new ItemBlockState(Map.of());
public static final NetworkBuffer.Type<ItemBlockState> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, ItemBlockState value) {
buffer.write(NetworkBuffer.VAR_INT, value.properties.size());
for (Map.Entry<String, String> entry : value.properties.entrySet()) {
buffer.write(NetworkBuffer.STRING, entry.getKey());
buffer.write(NetworkBuffer.STRING, entry.getValue());
}
}
@Override
public ItemBlockState read(@NotNull NetworkBuffer buffer) {
int size = buffer.read(NetworkBuffer.VAR_INT);
Map<String, String> properties = new HashMap<>(size);
for (int i = 0; i < size; i++) {
properties.put(buffer.read(NetworkBuffer.STRING), buffer.read(NetworkBuffer.STRING));
}
return new ItemBlockState(properties);
}
};
public static final NetworkBuffer.Type<ItemBlockState> NETWORK_TYPE = NetworkBufferTemplate.template(
NetworkBuffer.STRING.mapValue(NetworkBuffer.STRING), ItemBlockState::properties,
ItemBlockState::new
);
public static final BinaryTagSerializer<ItemBlockState> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> {

View File

@ -3,6 +3,7 @@ package net.minestom.server.item.component;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.coordinate.Point;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.network.packet.server.play.data.WorldPos;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
@ -10,21 +11,11 @@ import org.jetbrains.annotations.Nullable;
public record LodestoneTracker(@Nullable WorldPos target, boolean tracked) {
public static final NetworkBuffer.Type<LodestoneTracker> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, @NotNull LodestoneTracker value) {
buffer.writeOptional(WorldPos.NETWORK_TYPE, value.target);
buffer.write(NetworkBuffer.BOOLEAN, value.tracked);
}
@Override
public @NotNull LodestoneTracker read(@NotNull NetworkBuffer buffer) {
return new LodestoneTracker(
buffer.readOptional(WorldPos.NETWORK_TYPE),
buffer.read(NetworkBuffer.BOOLEAN)
);
}
};
public static final NetworkBuffer.Type<LodestoneTracker> NETWORK_TYPE = NetworkBufferTemplate.template(
WorldPos.NETWORK_TYPE.optional(), LodestoneTracker::target,
NetworkBuffer.BOOLEAN, LodestoneTracker::tracked,
LodestoneTracker::new
);
public static final BinaryTagSerializer<LodestoneTracker> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> new LodestoneTracker(

View File

@ -1,22 +1,10 @@
package net.minestom.server.item.component;
import net.minestom.server.network.NetworkBuffer;
import org.jetbrains.annotations.NotNull;
public enum MapPostProcessing {
LOCK,
SCALE;
private static final MapPostProcessing[] VALUES = values();
public static final NetworkBuffer.Type<MapPostProcessing> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, MapPostProcessing value) {
buffer.write(NetworkBuffer.VAR_INT, value.ordinal());
}
@Override
public MapPostProcessing read(@NotNull NetworkBuffer buffer) {
return VALUES[buffer.read(NetworkBuffer.VAR_INT)];
}
};
public static final NetworkBuffer.Type<MapPostProcessing> NETWORK_TYPE = NetworkBuffer.Enum(MapPostProcessing.class);
}

View File

@ -16,7 +16,7 @@ public record PotDecorations(
public static final @NotNull Material DEFAULT_ITEM = Material.BRICK;
public static final PotDecorations EMPTY = new PotDecorations(DEFAULT_ITEM, DEFAULT_ITEM, DEFAULT_ITEM, DEFAULT_ITEM);
public static NetworkBuffer.Type<PotDecorations> NETWORK_TYPE = Material.NETWORK_TYPE.list(4).map(PotDecorations::new, PotDecorations::asList);
public static final NetworkBuffer.Type<PotDecorations> NETWORK_TYPE = Material.NETWORK_TYPE.list(4).transform(PotDecorations::new, PotDecorations::asList);
public static BinaryTagSerializer<PotDecorations> NBT_TYPE = Material.NBT_TYPE.list().map(PotDecorations::new, PotDecorations::asList);
public PotDecorations(@NotNull List<Material> list) {

View File

@ -25,18 +25,18 @@ public record PotionContents(
@Override
public void write(@NotNull NetworkBuffer buffer, PotionContents value) {
Integer typeId = value.potion == null ? null : value.potion.id();
buffer.writeOptional(NetworkBuffer.VAR_INT, typeId);
buffer.writeOptional(Color.NETWORK_TYPE, value.customColor);
buffer.writeCollection(CustomPotionEffect.NETWORK_TYPE, value.customEffects);
buffer.write(NetworkBuffer.VAR_INT.optional(), typeId);
buffer.write(Color.NETWORK_TYPE.optional(), value.customColor);
buffer.write(CustomPotionEffect.NETWORK_TYPE.list(), value.customEffects);
}
@Override
public PotionContents read(@NotNull NetworkBuffer buffer) {
Integer typeId = buffer.readOptional(NetworkBuffer.VAR_INT);
Integer typeId = buffer.read(NetworkBuffer.VAR_INT.optional());
return new PotionContents(
typeId == null ? null : PotionType.fromId(typeId),
buffer.readOptional(Color.NETWORK_TYPE),
buffer.readCollection(CustomPotionEffect.NETWORK_TYPE, Short.MAX_VALUE)
buffer.read(Color.NETWORK_TYPE.optional()),
buffer.read(CustomPotionEffect.NETWORK_TYPE.list(Short.MAX_VALUE))
);
}
};

View File

@ -2,6 +2,7 @@ package net.minestom.server.item.component;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.potion.PotionEffect;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
@ -13,7 +14,7 @@ public record SuspiciousStewEffects(@NotNull List<Effect> effects) {
public static final int DEFAULT_DURATION = 160;
public static final SuspiciousStewEffects EMPTY = new SuspiciousStewEffects(List.of());
public static final NetworkBuffer.Type<SuspiciousStewEffects> NETWORK_TYPE = Effect.NETWORK_TYPE.list(Short.MAX_VALUE).map(SuspiciousStewEffects::new, SuspiciousStewEffects::effects);
public static final NetworkBuffer.Type<SuspiciousStewEffects> NETWORK_TYPE = Effect.NETWORK_TYPE.list(Short.MAX_VALUE).transform(SuspiciousStewEffects::new, SuspiciousStewEffects::effects);
public static final BinaryTagSerializer<SuspiciousStewEffects> NBT_TYPE = Effect.NBT_TYPE.list().map(SuspiciousStewEffects::new, SuspiciousStewEffects::effects);
public SuspiciousStewEffects {
@ -32,18 +33,11 @@ public record SuspiciousStewEffects(@NotNull List<Effect> effects) {
public record Effect(@NotNull PotionEffect id, int durationTicks) {
public static final NetworkBuffer.Type<Effect> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, Effect value) {
buffer.write(PotionEffect.NETWORK_TYPE, value.id);
buffer.write(NetworkBuffer.VAR_INT, value.durationTicks);
}
@Override
public Effect read(@NotNull NetworkBuffer buffer) {
return new Effect(buffer.read(PotionEffect.NETWORK_TYPE), buffer.read(NetworkBuffer.VAR_INT));
}
};
public static final NetworkBuffer.Type<Effect> NETWORK_TYPE = NetworkBufferTemplate.template(
PotionEffect.NETWORK_TYPE, Effect::id,
NetworkBuffer.VAR_INT, Effect::durationTicks,
Effect::new
);
public static final BinaryTagSerializer<Effect> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> new Effect(PotionEffect.fromNamespaceId(tag.getString("id")),

View File

@ -6,6 +6,7 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.FloatBinaryTag;
import net.minestom.server.instance.block.predicate.BlockTypeFilter;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -62,23 +63,12 @@ public record Tool(@NotNull List<Rule> rules, float defaultMiningSpeed, int dama
public record Rule(@NotNull BlockTypeFilter blocks, @Nullable Float speed, @Nullable Boolean correctForDrops) {
public static final NetworkBuffer.Type<Rule> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, Rule value) {
buffer.write(BlockTypeFilter.NETWORK_TYPE, value.blocks());
buffer.writeOptional(NetworkBuffer.FLOAT, value.speed());
buffer.writeOptional(NetworkBuffer.BOOLEAN, value.correctForDrops());
}
@Override
public Rule read(@NotNull NetworkBuffer buffer) {
return new Rule(
buffer.read(BlockTypeFilter.NETWORK_TYPE),
buffer.readOptional(NetworkBuffer.FLOAT),
buffer.readOptional(NetworkBuffer.BOOLEAN)
);
}
};
public static final NetworkBuffer.Type<Rule> NETWORK_TYPE = NetworkBufferTemplate.template(
BlockTypeFilter.NETWORK_TYPE, Rule::blocks,
NetworkBuffer.FLOAT, Rule::speed,
NetworkBuffer.BOOLEAN.optional(), Rule::correctForDrops,
Rule::new
);
public static final BinaryTagSerializer<Rule> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> new Rule(
BlockTypeFilter.NBT_TYPE.read(Objects.requireNonNull(tag.get("blocks"))),

View File

@ -2,23 +2,16 @@ package net.minestom.server.item.component;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
public record Unbreakable(boolean showInTooltip) {
public static final Unbreakable DEFAULT = new Unbreakable();
public static final NetworkBuffer.Type<Unbreakable> NETWORK_TYPE = new NetworkBuffer.Type<Unbreakable>() {
@Override
public void write(@NotNull NetworkBuffer buffer, Unbreakable value) {
buffer.write(NetworkBuffer.BOOLEAN, value.showInTooltip());
}
@Override
public Unbreakable read(@NotNull NetworkBuffer buffer) {
return new Unbreakable(buffer.read(NetworkBuffer.BOOLEAN));
}
};
public static final NetworkBuffer.Type<Unbreakable> NETWORK_TYPE = NetworkBufferTemplate.template(
NetworkBuffer.BOOLEAN, Unbreakable::showInTooltip,
Unbreakable::new
);
public Unbreakable() {
this(true);
@ -28,5 +21,4 @@ public record Unbreakable(boolean showInTooltip) {
tag -> new Unbreakable(tag.getBoolean("showInTooltip", true)),
unbreakable -> CompoundBinaryTag.builder().putBoolean("showInTooltip", unbreakable.showInTooltip()).build()
);
}

View File

@ -5,6 +5,7 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.ListBinaryTag;
import net.minestom.server.item.book.FilteredText;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
@ -14,17 +15,10 @@ import java.util.List;
public record WritableBookContent(@NotNull List<FilteredText<String>> pages) {
public static final WritableBookContent EMPTY = new WritableBookContent(List.of());
public static final NetworkBuffer.Type<WritableBookContent> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, WritableBookContent value) {
buffer.writeCollection(FilteredText.STRING_NETWORK_TYPE, value.pages);
}
@Override
public WritableBookContent read(@NotNull NetworkBuffer buffer) {
return new WritableBookContent(buffer.readCollection(FilteredText.STRING_NETWORK_TYPE, 100));
}
};
public static final NetworkBuffer.Type<WritableBookContent> NETWORK_TYPE = NetworkBufferTemplate.template(
FilteredText.STRING_NETWORK_TYPE.list(100), WritableBookContent::pages,
WritableBookContent::new
);
public static final BinaryTagSerializer<WritableBookContent> NBT_TYPE = new BinaryTagSerializer<>() {
@Override

View File

@ -6,34 +6,26 @@ import net.kyori.adventure.nbt.ListBinaryTag;
import net.kyori.adventure.text.Component;
import net.minestom.server.item.book.FilteredText;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public record WrittenBookContent(@NotNull List<FilteredText<Component>> pages, @NotNull FilteredText<String> title, @NotNull String author, int generation, boolean resolved) {
public static final WrittenBookContent EMPTY = new WrittenBookContent(List.of(), new FilteredText<>("", null), "", 0, true);
import static net.minestom.server.network.NetworkBuffer.*;
public static final @NotNull NetworkBuffer.Type<WrittenBookContent> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, WrittenBookContent value) {
buffer.write(FilteredText.STRING_NETWORK_TYPE, value.title);
buffer.write(NetworkBuffer.STRING, value.author);
buffer.write(NetworkBuffer.VAR_INT, value.generation);
buffer.writeCollection(FilteredText.COMPONENT_NETWORK_TYPE, value.pages);
buffer.write(NetworkBuffer.BOOLEAN, value.resolved);
}
public record WrittenBookContent(@NotNull FilteredText<String> title, @NotNull String author, int generation,
@NotNull List<FilteredText<Component>> pages, boolean resolved) {
public static final WrittenBookContent EMPTY = new WrittenBookContent(new FilteredText<>("", null), "", 0, List.of(), true);
@Override
public WrittenBookContent read(@NotNull NetworkBuffer buffer) {
FilteredText<String> title = buffer.read(FilteredText.STRING_NETWORK_TYPE);
String author = buffer.read(NetworkBuffer.STRING);
int generation = buffer.read(NetworkBuffer.VAR_INT);
List<FilteredText<Component>> pages = buffer.readCollection(FilteredText.COMPONENT_NETWORK_TYPE, 100);
boolean resolved = buffer.read(NetworkBuffer.BOOLEAN);
return new WrittenBookContent(pages, title, author, generation, resolved);
}
};
public static final NetworkBuffer.Type<WrittenBookContent> NETWORK_TYPE = NetworkBufferTemplate.template(
FilteredText.STRING_NETWORK_TYPE, WrittenBookContent::title,
STRING, WrittenBookContent::author,
VAR_INT, WrittenBookContent::generation,
FilteredText.COMPONENT_NETWORK_TYPE.list(100), WrittenBookContent::pages,
BOOLEAN, WrittenBookContent::resolved,
WrittenBookContent::new
);
public static final @NotNull BinaryTagSerializer<WrittenBookContent> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
compound -> {
@ -45,7 +37,7 @@ public record WrittenBookContent(@NotNull List<FilteredText<Component>> pages, @
String author = compound.getString("author");
int generation = compound.getInt("generation");
boolean resolved = compound.getBoolean("resolved");
return new WrittenBookContent(pages, title, author, generation, resolved);
return new WrittenBookContent(title, author, generation, pages, resolved);
},
value -> {
ListBinaryTag.Builder<BinaryTag> pagesTag = ListBinaryTag.builder();
@ -66,11 +58,11 @@ public record WrittenBookContent(@NotNull List<FilteredText<Component>> pages, @
pages = List.copyOf(pages);
}
public WrittenBookContent(@NotNull List<Component> pages, @NotNull String title, @NotNull String author) {
this(pages, title, author, 0, true);
public WrittenBookContent(@NotNull String title, @NotNull String author, @NotNull List<Component> pages) {
this(title, author, 0, pages, true);
}
public WrittenBookContent(@NotNull List<Component> pages, @NotNull String title, @NotNull String author, int generation, boolean resolved) {
this(pages.stream().map(page -> new FilteredText<>(page, null)).toList(), new FilteredText<>(title, null), author, generation, resolved);
public WrittenBookContent(@NotNull String title, @NotNull String author, int generation, @NotNull List<Component> pages, boolean resolved) {
this(new FilteredText<>(title, null), author, generation, pages.stream().map(page -> new FilteredText<>(page, null)).toList(), resolved);
}
}

View File

@ -114,7 +114,7 @@ public final class PacketListenerManager {
// Listener can be null if none has been set before, call PacketConsumer anyway
if (packetListenerConsumer == null) {
LOGGER.warn("Packet " + clazz + " does not have any default listener! (The issue comes from Minestom)");
LOGGER.warn("Packet {}:{} does not have any default listener! (The issue likely comes from Minestom)", clazz, state);
return;
}

View File

@ -22,8 +22,8 @@ import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
import net.minestom.server.network.player.GameProfile;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.network.player.PlayerSocketConnection;
import net.minestom.server.network.plugin.LoginPlugin;
import net.minestom.server.network.plugin.LoginPluginMessageProcessor;
import net.minestom.server.network.plugin.LoginPluginResponse;
import net.minestom.server.utils.async.AsyncUtils;
import org.jetbrains.annotations.NotNull;
@ -33,7 +33,6 @@ import java.net.*;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
@ -56,7 +55,7 @@ public final class LoginListener {
socketConnection.UNSAFE_setLoginUsername(packet.username());
// Velocity support
if (VelocityProxy.isEnabled()) {
connection.loginPluginMessageProcessor().request(VelocityProxy.PLAYER_INFO_CHANNEL, null)
connection.loginPluginMessageProcessor().request(VelocityProxy.PLAYER_INFO_CHANNEL, new byte[0])
.thenAccept(response -> handleVelocityProxyResponse(socketConnection, response));
return;
}
@ -171,14 +170,13 @@ public final class LoginListener {
return MojangCrypt.decryptByteToSecretKey(MojangAuth.getKeyPair().getPrivate(), sharedSecret);
}
private static void handleVelocityProxyResponse(PlayerSocketConnection socketConnection, LoginPluginResponse response) {
byte[] data = response.getPayload();
private static void handleVelocityProxyResponse(PlayerSocketConnection socketConnection, LoginPlugin.Response response) {
final byte[] data = response.payload();
SocketAddress socketAddress = null;
GameProfile gameProfile = null;
boolean success = false;
if (data != null && data.length > 0) {
NetworkBuffer buffer = new NetworkBuffer(ByteBuffer.wrap(data));
NetworkBuffer buffer = NetworkBuffer.wrap(data, 0, data.length);
success = VelocityProxy.checkIntegrity(buffer);
if (success) {
// Get the real connection address
@ -191,7 +189,7 @@ public final class LoginListener {
}
final int port = ((java.net.InetSocketAddress) socketConnection.getRemoteAddress()).getPort();
socketAddress = new InetSocketAddress(address, port);
gameProfile = new GameProfile(buffer);
gameProfile = GameProfile.SERIALIZER.read(buffer);
}
}

View File

@ -4,7 +4,7 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.server.play.SystemChatPacket;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.PacketSendingUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -50,7 +50,7 @@ public final class Messenger {
*/
public static void sendMessage(@NotNull Collection<Player> players, @NotNull Component message,
@NotNull ChatPosition position, @Nullable UUID uuid) {
PacketUtils.sendGroupedPacket(players, new SystemChatPacket(message, false),
PacketSendingUtils.sendGroupedPacket(players, new SystemChatPacket(message, false),
player -> getChatMessageType(player).accepts(position));
}

View File

@ -48,7 +48,8 @@ public final class ConnectionManager {
private CachedPacket getDefaultTags() {
var defaultTags = this.defaultTags;
if (defaultTags == null) {
this.defaultTags = defaultTags = new CachedPacket(new TagsPacket(MinecraftServer.getTagManager().getTagMap()));
final TagsPacket packet = MinecraftServer.getTagManager().packet();
this.defaultTags = defaultTags = new CachedPacket(packet);
}
return defaultTags;
}

View File

@ -1,8 +1,28 @@
package net.minestom.server.network;
/**
* Represents the current connection state of a {@link net.minestom.server.network.player.PlayerConnection}.
* Represents the connection state of a client.
*/
public enum ConnectionState {
HANDSHAKE, STATUS, LOGIN, CONFIGURATION, PLAY
/**
* Default state before any packet is received.
*/
HANDSHAKE,
/**
* Client declares `Status` intent during handshake.
*/
STATUS,
/**
* Client declares `Login` intent during handshake.
*/
LOGIN,
/**
* Client acknowledged login and is now configuring the game.
* Can also go back to configuration from play.
*/
CONFIGURATION,
/**
* Client (re-)finished configuration.
*/
PLAY
}

View File

@ -1,8 +1,8 @@
package net.minestom.server.network;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.text.Component;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.EntityPose;
@ -11,349 +11,278 @@ import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.Direction;
import net.minestom.server.utils.Unit;
import net.minestom.server.utils.nbt.BinaryTagReader;
import net.minestom.server.utils.nbt.BinaryTagWriter;
import net.minestom.server.utils.validate.Check;
import net.minestom.server.utils.crypto.KeyUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import javax.crypto.Cipher;
import java.io.IOException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SocketChannel;
import java.security.PublicKey;
import java.time.Instant;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.zip.DataFormatException;
public final class NetworkBuffer {
public static final Type<Unit> UNIT = new NetworkBufferTypeImpl.UnitType();
public static final Type<Boolean> BOOLEAN = new NetworkBufferTypeImpl.BooleanType();
public static final Type<Byte> BYTE = new NetworkBufferTypeImpl.ByteType();
public static final Type<Short> SHORT = new NetworkBufferTypeImpl.ShortType();
public static final Type<Integer> UNSIGNED_SHORT = new NetworkBufferTypeImpl.UnsignedShortType();
public static final Type<Integer> INT = new NetworkBufferTypeImpl.IntType();
public static final Type<Long> LONG = new NetworkBufferTypeImpl.LongType();
public static final Type<Float> FLOAT = new NetworkBufferTypeImpl.FloatType();
public static final Type<Double> DOUBLE = new NetworkBufferTypeImpl.DoubleType();
public static final Type<Integer> VAR_INT = new NetworkBufferTypeImpl.VarIntType();
public static final Type<Long> VAR_LONG = new NetworkBufferTypeImpl.VarLongType();
public static final Type<byte[]> RAW_BYTES = new NetworkBufferTypeImpl.RawBytesType();
public static final Type<String> STRING = new NetworkBufferTypeImpl.StringType();
public static final Type<String> STRING_TERMINATED = new NetworkBufferTypeImpl.StringTerminatedType();
public static final Type<BinaryTag> NBT = new NetworkBufferTypeImpl.NbtType();
public static final Type<Point> BLOCK_POSITION = new NetworkBufferTypeImpl.BlockPositionType();
public static final Type<Component> COMPONENT = new ComponentNetworkBufferTypeImpl();
public static final Type<Component> JSON_COMPONENT = new NetworkBufferTypeImpl.JsonComponentType();
public static final Type<UUID> UUID = new NetworkBufferTypeImpl.UUIDType();
public static final Type<Pos> POS = new NetworkBufferTypeImpl.PosType();
public sealed interface NetworkBuffer permits NetworkBufferImpl {
Type<Unit> UNIT = new NetworkBufferTypeImpl.UnitType();
Type<Boolean> BOOLEAN = new NetworkBufferTypeImpl.BooleanType();
Type<Byte> BYTE = new NetworkBufferTypeImpl.ByteType();
Type<Short> SHORT = new NetworkBufferTypeImpl.ShortType();
Type<Integer> UNSIGNED_SHORT = new NetworkBufferTypeImpl.UnsignedShortType();
Type<Integer> INT = new NetworkBufferTypeImpl.IntType();
Type<Long> LONG = new NetworkBufferTypeImpl.LongType();
Type<Float> FLOAT = new NetworkBufferTypeImpl.FloatType();
Type<Double> DOUBLE = new NetworkBufferTypeImpl.DoubleType();
Type<Integer> VAR_INT = new NetworkBufferTypeImpl.VarIntType();
Type<Integer> VAR_INT_3 = new NetworkBufferTypeImpl.VarInt3Type();
Type<Long> VAR_LONG = new NetworkBufferTypeImpl.VarLongType();
Type<byte[]> RAW_BYTES = new NetworkBufferTypeImpl.RawBytesType(-1);
Type<String> STRING = new NetworkBufferTypeImpl.StringType();
Type<String> STRING_TERMINATED = new NetworkBufferTypeImpl.StringTerminatedType();
Type<BinaryTag> NBT = new NetworkBufferTypeImpl.NbtType();
@SuppressWarnings({"unchecked", "rawtypes"})
Type<CompoundBinaryTag> NBT_COMPOUND = (Type) new NetworkBufferTypeImpl.NbtType();
Type<Point> BLOCK_POSITION = new NetworkBufferTypeImpl.BlockPositionType();
Type<Component> COMPONENT = new NetworkBufferTypeImpl.ComponentType();
Type<Component> JSON_COMPONENT = new NetworkBufferTypeImpl.JsonComponentType();
Type<UUID> UUID = new NetworkBufferTypeImpl.UUIDType();
Type<Pos> POS = new NetworkBufferTypeImpl.PosType();
public static final Type<byte[]> BYTE_ARRAY = new NetworkBufferTypeImpl.ByteArrayType();
public static final Type<long[]> LONG_ARRAY = new NetworkBufferTypeImpl.LongArrayType();
public static final Type<int[]> VAR_INT_ARRAY = new NetworkBufferTypeImpl.VarIntArrayType();
public static final Type<long[]> VAR_LONG_ARRAY = new NetworkBufferTypeImpl.VarLongArrayType();
Type<byte[]> BYTE_ARRAY = new NetworkBufferTypeImpl.ByteArrayType();
Type<long[]> LONG_ARRAY = new NetworkBufferTypeImpl.LongArrayType();
Type<int[]> VAR_INT_ARRAY = new NetworkBufferTypeImpl.VarIntArrayType();
Type<long[]> VAR_LONG_ARRAY = new NetworkBufferTypeImpl.VarLongArrayType();
public static <T extends ProtocolObject> @NotNull Type<DynamicRegistry.Key<T>> RegistryKey(@NotNull Function<Registries, DynamicRegistry<T>> selector) {
Type<BitSet> BITSET = LONG_ARRAY.transform(BitSet::valueOf, BitSet::toLongArray);
Type<Instant> INSTANT_MS = LONG.transform(Instant::ofEpochMilli, Instant::toEpochMilli);
Type<PublicKey> PUBLIC_KEY = BYTE_ARRAY.transform(KeyUtils::publicRSAKeyFrom, PublicKey::getEncoded);
static <T extends ProtocolObject> @NotNull Type<DynamicRegistry.Key<T>> RegistryKey(@NotNull Function<Registries, DynamicRegistry<T>> selector) {
return new NetworkBufferTypeImpl.RegistryTypeType<>(selector);
}
// METADATA
public static final Type<int[]> VILLAGER_DATA = new NetworkBufferTypeImpl.VillagerDataType();
public static final Type<Point> VECTOR3 = new NetworkBufferTypeImpl.Vector3Type();
public static final Type<Point> VECTOR3D = new NetworkBufferTypeImpl.Vector3DType();
public static final Type<float[]> QUATERNION = new NetworkBufferTypeImpl.QuaternionType();
Type<int[]> VILLAGER_DATA = new NetworkBufferTypeImpl.VillagerDataType();
Type<Point> VECTOR3 = new NetworkBufferTypeImpl.Vector3Type();
Type<Point> VECTOR3D = new NetworkBufferTypeImpl.Vector3DType();
Type<Point> VECTOR3B = new NetworkBufferTypeImpl.Vector3BType();
Type<float[]> QUATERNION = new NetworkBufferTypeImpl.QuaternionType();
public static final Type<@Nullable Component> OPT_CHAT = Optional(COMPONENT);
public static final Type<@Nullable Point> OPT_BLOCK_POSITION = Optional(BLOCK_POSITION);
public static final Type<@Nullable UUID> OPT_UUID = Optional(UUID);
Type<@Nullable Component> OPT_CHAT = COMPONENT.optional();
Type<@Nullable Point> OPT_BLOCK_POSITION = BLOCK_POSITION.optional();
Type<@Nullable UUID> OPT_UUID = UUID.optional();
public static final Type<Direction> DIRECTION = new NetworkBufferTypeImpl.EnumType<>(Direction.class);
public static final Type<EntityPose> POSE = new NetworkBufferTypeImpl.EnumType<>(EntityPose.class);
Type<Direction> DIRECTION = Enum(Direction.class);
Type<EntityPose> POSE = Enum(EntityPose.class);
// Combinators
public static <T> @NotNull Type<@Nullable T> Optional(@NotNull Type<T> type) {
return new NetworkBufferTypeImpl.OptionalType<>(type);
static <E extends Enum<E>> @NotNull Type<E> Enum(@NotNull Class<E> enumClass) {
final E[] values = enumClass.getEnumConstants();
return VAR_INT.transform(integer -> values[integer], Enum::ordinal);
}
public static <E extends Enum<E>> @NotNull Type<E> Enum(@NotNull Class<E> enumClass) {
return new NetworkBufferTypeImpl.EnumType<>(enumClass);
static <E extends Enum<E>> @NotNull Type<EnumSet<E>> EnumSet(@NotNull Class<E> enumClass) {
return new NetworkBufferTypeImpl.EnumSetType<>(enumClass, enumClass.getEnumConstants());
}
public static <T> @NotNull Type<T> Lazy(@NotNull Supplier<NetworkBuffer.@NotNull Type<T>> supplier) {
static @NotNull Type<BitSet> FixedBitSet(int length) {
return new NetworkBufferTypeImpl.FixedBitSetType(length);
}
static @NotNull Type<byte[]> FixedRawBytes(int length) {
return new NetworkBufferTypeImpl.RawBytesType(length);
}
static <T> @NotNull Type<T> Lazy(@NotNull Supplier<@NotNull Type<T>> supplier) {
return new NetworkBufferTypeImpl.LazyType<>(supplier);
}
<T> void write(@NotNull Type<T> type, @UnknownNullability T value);
ByteBuffer nioBuffer;
final boolean resizable;
int writeIndex;
int readIndex;
<T> @UnknownNullability T read(@NotNull Type<T> type);
BinaryTagWriter nbtWriter;
BinaryTagReader nbtReader;
<T> void writeAt(long index, @NotNull Type<T> type, @UnknownNullability T value);
// In the future, this should be passed as a parameter.
final Registries registries = MinecraftServer.process();
<T> @UnknownNullability T readAt(long index, @NotNull Type<T> type);
public NetworkBuffer(@NotNull ByteBuffer buffer, boolean resizable) {
this.nioBuffer = buffer.order(ByteOrder.BIG_ENDIAN);
this.resizable = resizable;
void copyTo(long srcOffset, byte @NotNull [] dest, long destOffset, long length);
this.writeIndex = buffer.position();
this.readIndex = buffer.position();
byte @NotNull [] extractBytes(@NotNull Consumer<@NotNull NetworkBuffer> extractor);
@NotNull NetworkBuffer clear();
long writeIndex();
long readIndex();
@NotNull NetworkBuffer writeIndex(long writeIndex);
@NotNull NetworkBuffer readIndex(long readIndex);
@NotNull NetworkBuffer index(long readIndex, long writeIndex);
long advanceWrite(long length);
long advanceRead(long length);
long readableBytes();
long writableBytes();
long capacity();
void readOnly();
boolean isReadOnly();
void resize(long newSize);
void ensureWritable(long length);
void compact();
NetworkBuffer slice(long index, long length, long readIndex, long writeIndex);
NetworkBuffer copy(long index, long length, long readIndex, long writeIndex);
default NetworkBuffer copy(long index, long length) {
return copy(index, length, readIndex(), writeIndex());
}
public NetworkBuffer(@NotNull ByteBuffer buffer) {
this(buffer, true);
}
int readChannel(ReadableByteChannel channel) throws IOException;
public NetworkBuffer(int initialCapacity) {
this(ByteBuffer.allocateDirect(initialCapacity), true);
}
boolean writeChannel(SocketChannel channel) throws IOException;
public NetworkBuffer() {
this(1024);
}
void cipher(Cipher cipher, long start, long length);
public <T> void write(@NotNull Type<T> type, @NotNull T value) {
type.write(this, value);
}
long compress(long start, long length, NetworkBuffer output);
public <T> void write(@NotNull Writer writer) {
writer.write(this);
}
long decompress(long start, long length, NetworkBuffer output) throws DataFormatException;
public <T> @NotNull T read(@NotNull Type<T> type) {
return type.read(this);
}
@Nullable Registries registries();
public <T> void writeOptional(@NotNull Type<T> type, @Nullable T value) {
write(BOOLEAN, value != null);
if (value != null) write(type, value);
}
public void writeOptional(@Nullable Writer writer) {
write(BOOLEAN, writer != null);
if (writer != null) write(writer);
}
public <T> @Nullable T readOptional(@NotNull Type<T> type) {
return read(BOOLEAN) ? read(type) : null;
}
public <T> @Nullable T readOptional(@NotNull Function<@NotNull NetworkBuffer, @NotNull T> function) {
return read(BOOLEAN) ? function.apply(this) : null;
}
public <T> void writeCollection(@NotNull Type<T> type, @Nullable Collection<@NotNull T> values) {
if (values == null) {
write(BYTE, (byte) 0);
return;
}
write(VAR_INT, values.size());
for (T value : values) write(type, value);
}
@SafeVarargs
public final <T> void writeCollection(@NotNull Type<T> type, @NotNull T @Nullable ... values) {
writeCollection(type, values == null ? null : List.of(values));
}
public <T extends Writer> void writeCollection(@Nullable Collection<@NotNull T> values) {
if (values == null) {
write(BYTE, (byte) 0);
return;
}
write(VAR_INT, values.size());
for (T value : values) write(value);
}
public <T> void writeCollection(@Nullable Collection<@NotNull T> values,
@NotNull BiConsumer<@NotNull NetworkBuffer, @NotNull T> consumer) {
if (values == null) {
write(BYTE, (byte) 0);
return;
}
write(VAR_INT, values.size());
for (T value : values) consumer.accept(this, value);
}
public <T> @NotNull List<@NotNull T> readCollection(@NotNull Type<T> type, int maxSize) {
final int size = read(VAR_INT);
Check.argCondition(size > maxSize, "Collection size ({0}) is higher than the maximum allowed size ({1})", size, maxSize);
final List<T> values = new java.util.ArrayList<>(size);
for (int i = 0; i < size; i++) values.add(read(type));
return values;
}
public <T> @NotNull List<@NotNull T> readCollection(@NotNull Function<@NotNull NetworkBuffer, @NotNull T> function, int maxSize) {
final int size = read(VAR_INT);
Check.argCondition(size > maxSize, "Collection size ({0}) is higher than the maximum allowed size ({1})", size, maxSize);
final List<T> values = new java.util.ArrayList<>(size);
for (int i = 0; i < size; i++) values.add(function.apply(this));
return values;
}
public <K, V> @NotNull Map<K, V> writeMap(@NotNull NetworkBuffer.Type<K> keyType, @NotNull NetworkBuffer.Type<V> valueType, @NotNull Map<K, V> map) {
write(VAR_INT, map.size());
for (Map.Entry<K, V> entry : map.entrySet()) {
write(keyType, entry.getKey());
write(valueType, entry.getValue());
}
return map;
}
public <K, V> @NotNull Map<K, V> readMap(@NotNull NetworkBuffer.Type<K> keyType, @NotNull NetworkBuffer.Type<V> valueType, int maxSize) {
final int size = read(VAR_INT);
Check.argCondition(size > maxSize, "Map size ({0}) is higher than the maximum allowed size ({1})", size, maxSize);
final Map<K, V> map = new HashMap<>(size);
for (int i = 0; i < size; i++) {
map.put(read(keyType), read(valueType));
}
return map;
}
public <E extends Enum<?>> void writeEnum(@NotNull Class<E> enumClass, @NotNull E value) {
write(VAR_INT, value.ordinal());
}
public <E extends Enum<?>> @NotNull E readEnum(@NotNull Class<@NotNull E> enumClass) {
return enumClass.getEnumConstants()[read(VAR_INT)];
}
public <E extends Enum<E>> void writeEnumSet(EnumSet<E> enumSet, Class<E> enumType) {
final E[] values = enumType.getEnumConstants();
BitSet bitSet = new BitSet(values.length);
for (int i = 0; i < values.length; ++i) {
bitSet.set(i, enumSet.contains(values[i]));
}
writeFixedBitSet(bitSet, values.length);
}
public <E extends Enum<E>> @NotNull EnumSet<E> readEnumSet(Class<E> enumType) {
final E[] values = enumType.getEnumConstants();
BitSet bitSet = readFixedBitSet(values.length);
EnumSet<E> enumSet = EnumSet.noneOf(enumType);
for (int i = 0; i < values.length; ++i) {
if (bitSet.get(i)) {
enumSet.add(values[i]);
}
}
return enumSet;
}
public void writeFixedBitSet(BitSet set, int length) {
final int setLength = set.length();
if (setLength > length) {
throw new IllegalArgumentException("BitSet is larger than expected size (" + setLength + ">" + length + ")");
} else {
final byte[] array = set.toByteArray();
write(RAW_BYTES, array);
}
}
@NotNull
public BitSet readFixedBitSet(int length) {
final byte[] array = readBytes((length + 7) / 8);
return BitSet.valueOf(array);
}
public byte[] readBytes(int length) {
byte[] bytes = new byte[length];
nioBuffer.get(readIndex, bytes, 0, length);
readIndex += length;
return bytes;
}
public void copyTo(int srcOffset, byte @NotNull [] dest, int destOffset, int length) {
this.nioBuffer.get(srcOffset, dest, destOffset, length);
}
public byte @NotNull [] extractBytes(@NotNull Consumer<@NotNull NetworkBuffer> extractor) {
final int startingPosition = readIndex();
extractor.accept(this);
final int endingPosition = readIndex();
byte[] output = new byte[endingPosition - startingPosition];
copyTo(startingPosition, output, 0, output.length);
return output;
}
public void clear() {
this.writeIndex = 0;
this.readIndex = 0;
}
public int writeIndex() {
return writeIndex;
}
public int readIndex() {
return readIndex;
}
public void writeIndex(int writeIndex) {
this.writeIndex = writeIndex;
}
public void readIndex(int readIndex) {
this.readIndex = readIndex;
}
public int skipWrite(int length) {
final int oldWriteIndex = writeIndex;
writeIndex += length;
return oldWriteIndex;
}
public int readableBytes() {
return writeIndex - readIndex;
}
void ensureSize(int length) {
if (!resizable) return;
if (nioBuffer.capacity() < writeIndex + length) {
final int newCapacity = Math.max(nioBuffer.capacity() * 2, writeIndex + length);
ByteBuffer newBuffer = ByteBuffer.allocateDirect(newCapacity);
nioBuffer.position(0);
newBuffer.put(nioBuffer);
nioBuffer = newBuffer.clear();
}
}
public interface Type<T> {
interface Type<T> {
void write(@NotNull NetworkBuffer buffer, T value);
T read(@NotNull NetworkBuffer buffer);
default <S> @NotNull Type<S> map(@NotNull Function<T, S> to, @NotNull Function<S, T> from) {
return new NetworkBufferTypeImpl.MappedType<>(this, to, from);
default long sizeOf(@NotNull T value, @Nullable Registries registries) {
return NetworkBufferTypeImpl.sizeOf(this, value, registries);
}
default long sizeOf(@NotNull T value) {
return sizeOf(value, null);
}
default <S> @NotNull Type<S> transform(@NotNull Function<T, S> to, @NotNull Function<S, T> from) {
return new NetworkBufferTypeImpl.TransformType<>(this, to, from);
}
default <V> @NotNull Type<Map<T, V>> mapValue(@NotNull Type<V> valueType, int maxSize) {
return new NetworkBufferTypeImpl.MapType<>(this, valueType, maxSize);
}
default <V> @NotNull Type<Map<T, V>> mapValue(@NotNull Type<V> valueType) {
return mapValue(valueType, Integer.MAX_VALUE);
}
default @NotNull Type<List<T>> list(int maxSize) {
return new NetworkBufferTypeImpl.ListType<>(this, maxSize);
}
default @NotNull Type<List<T>> list() {
return list(Integer.MAX_VALUE);
}
default @NotNull Type<T> optional() {
return new NetworkBufferTypeImpl.OptionalTypeImpl<>(this);
return new NetworkBufferTypeImpl.OptionalType<>(this);
}
}
@FunctionalInterface
public interface Writer {
void write(@NotNull NetworkBuffer writer);
static @NotNull Builder builder(long size) {
return new NetworkBufferImpl.Builder(size);
}
static @NotNull NetworkBuffer staticBuffer(long size, Registries registries) {
return builder(size).registry(registries).build();
}
static @NotNull NetworkBuffer staticBuffer(long size) {
return staticBuffer(size, null);
}
static @NotNull NetworkBuffer resizableBuffer(long initialSize, Registries registries) {
return builder(initialSize)
.autoResize(AutoResize.DOUBLE)
.registry(registries)
.build();
}
static @NotNull NetworkBuffer resizableBuffer(int initialSize) {
return resizableBuffer(initialSize, null);
}
static @NotNull NetworkBuffer resizableBuffer(Registries registries) {
return resizableBuffer(256, registries);
}
static @NotNull NetworkBuffer resizableBuffer() {
return resizableBuffer(null);
}
static @NotNull NetworkBuffer wrap(byte @NotNull [] bytes, int readIndex, int writeIndex, @Nullable Registries registries) {
return NetworkBufferImpl.wrap(bytes, readIndex, writeIndex, registries);
}
static @NotNull NetworkBuffer wrap(byte @NotNull [] bytes, int readIndex, int writeIndex) {
return wrap(bytes, readIndex, writeIndex, null);
}
sealed interface Builder permits NetworkBufferImpl.Builder {
@NotNull Builder autoResize(@Nullable AutoResize autoResize);
@NotNull Builder registry(@Nullable Registries registries);
@NotNull NetworkBuffer build();
}
@FunctionalInterface
public interface Reader<T> {
@NotNull T read(@NotNull NetworkBuffer reader);
interface AutoResize {
AutoResize DOUBLE = (capacity, targetSize) -> Math.max(capacity * 2, targetSize);
long resize(long capacity, long targetSize);
}
public static byte[] makeArray(@NotNull Consumer<@NotNull NetworkBuffer> writing) {
NetworkBuffer writer = new NetworkBuffer();
writing.accept(writer);
byte[] bytes = new byte[writer.writeIndex];
writer.copyTo(0, bytes, 0, bytes.length);
return bytes;
static byte[] makeArray(@NotNull Consumer<@NotNull NetworkBuffer> writing, @Nullable Registries registries) {
NetworkBuffer buffer = resizableBuffer(256, registries);
writing.accept(buffer);
return buffer.read(RAW_BYTES);
}
static byte[] makeArray(@NotNull Consumer<@NotNull NetworkBuffer> writing) {
return makeArray(writing, null);
}
static <T> byte[] makeArray(@NotNull Type<T> type, @NotNull T value, @Nullable Registries registries) {
return makeArray(buffer -> buffer.write(type, value), registries);
}
static <T> byte[] makeArray(@NotNull Type<T> type, @NotNull T value) {
return makeArray(type, value, null);
}
static void copy(NetworkBuffer srcBuffer, long srcOffset,
NetworkBuffer dstBuffer, long dstOffset, long length) {
NetworkBufferImpl.copy(srcBuffer, srcOffset, dstBuffer, dstOffset, length);
}
static boolean equals(NetworkBuffer buffer1, NetworkBuffer buffer2) {
return NetworkBufferImpl.equals(buffer1, buffer2);
}
}

View File

@ -0,0 +1,575 @@
package net.minestom.server.network;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.ObjectPool;
import net.minestom.server.utils.nbt.BinaryTagReader;
import net.minestom.server.utils.nbt.BinaryTagWriter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import javax.crypto.Cipher;
import javax.crypto.ShortBufferException;
import java.io.EOFException;
import java.io.IOException;
import java.lang.ref.Cleaner;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SocketChannel;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import static net.minestom.server.network.NetworkBufferUnsafe.*;
final class NetworkBufferImpl implements NetworkBuffer {
private static final Cleaner CLEANER = Cleaner.create();
private static final long DUMMY_ADDRESS = -1;
private final NetworkBufferImpl parent; // Used for slices so we can control GC over the parent buffer
private final BufferCleaner state;
// Address may be -1 if the buffer is a dummy buffer
// Dummy buffers are used for size calculations and do not have memory allocated
private long address, capacity;
private long readIndex, writeIndex;
boolean readOnly;
BinaryTagWriter nbtWriter;
BinaryTagReader nbtReader;
final @Nullable AutoResize autoResize;
final @Nullable Registries registries;
NetworkBufferImpl(NetworkBufferImpl parent,
long address, long capacity,
long readIndex, long writeIndex,
@Nullable AutoResize autoResize,
@Nullable Registries registries) {
this.parent = parent;
this.address = address;
this.capacity = capacity;
this.readIndex = readIndex;
this.writeIndex = writeIndex;
this.autoResize = autoResize;
this.registries = registries;
this.state = new BufferCleaner(new AtomicLong(address));
if (this.parent == null && address != DUMMY_ADDRESS) CLEANER.register(this, state);
}
private record BufferCleaner(AtomicLong address) implements Runnable {
@Override
public void run() {
UNSAFE.freeMemory(address.get());
}
}
@Override
public <T> void write(@NotNull Type<T> type, @UnknownNullability T value) {
assertReadOnly();
type.write(this, value);
}
@Override
public <T> @UnknownNullability T read(@NotNull Type<T> type) {
assertDummy();
return type.read(this);
}
@Override
public <T> void writeAt(long index, @NotNull Type<T> type, @UnknownNullability T value) {
assertReadOnly();
final long oldWriteIndex = writeIndex;
writeIndex = index;
try {
write(type, value);
} finally {
writeIndex = oldWriteIndex;
}
}
@Override
public <T> @UnknownNullability T readAt(long index, @NotNull Type<T> type) {
assertDummy();
final long oldReadIndex = readIndex;
readIndex = index;
try {
return read(type);
} finally {
readIndex = oldReadIndex;
}
}
@Override
public void copyTo(long srcOffset, byte @NotNull [] dest, long destOffset, long length) {
assertDummy();
assertOverflow(srcOffset + length);
assertOverflow(destOffset + length);
if (length == 0) return;
if (dest.length < destOffset + length) {
throw new IndexOutOfBoundsException("Destination array is too small: " + dest.length + " < " + (destOffset + length));
}
UNSAFE.copyMemory(null, address + srcOffset, dest, BYTE_ARRAY_OFFSET + destOffset, length);
}
public byte @NotNull [] extractBytes(@NotNull Consumer<@NotNull NetworkBuffer> extractor) {
assertDummy();
final long startingPosition = readIndex();
extractor.accept(this);
final long endingPosition = readIndex();
final long length = endingPosition - startingPosition;
assertOverflow(length);
byte[] output = new byte[(int) length];
copyTo(startingPosition, output, 0, output.length);
return output;
}
public @NotNull NetworkBuffer clear() {
return index(0, 0);
}
public long writeIndex() {
return writeIndex;
}
public long readIndex() {
return readIndex;
}
public @NotNull NetworkBuffer writeIndex(long writeIndex) {
this.writeIndex = writeIndex;
return this;
}
public @NotNull NetworkBuffer readIndex(long readIndex) {
this.readIndex = readIndex;
return this;
}
@Override
public @NotNull NetworkBuffer index(long readIndex, long writeIndex) {
this.readIndex = readIndex;
this.writeIndex = writeIndex;
return this;
}
public long advanceWrite(long length) {
final long oldWriteIndex = writeIndex;
writeIndex += length;
return oldWriteIndex;
}
@Override
public long advanceRead(long length) {
final long oldReadIndex = readIndex;
readIndex += length;
return oldReadIndex;
}
@Override
public long readableBytes() {
return writeIndex - readIndex;
}
@Override
public long writableBytes() {
return capacity() - writeIndex;
}
@Override
public long capacity() {
return capacity;
}
@Override
public void readOnly() {
this.readOnly = true;
}
@Override
public boolean isReadOnly() {
return readOnly;
}
@Override
public void resize(long newSize) {
assertDummy();
assertReadOnly();
if (newSize < capacity) throw new IllegalArgumentException("New size is smaller than the current size");
if (newSize == capacity) throw new IllegalArgumentException("New size is the same as the current size");
final long newAddress = UNSAFE.reallocateMemory(address, newSize);
this.address = newAddress;
this.capacity = newSize;
this.state.address.set(newAddress);
}
@Override
public void ensureWritable(long length) {
assertReadOnly();
if (writableBytes() >= length) return;
final long newCapacity = newCapacity(length, capacity());
resize(newCapacity);
}
private long newCapacity(long length, long capacity) {
final long targetSize = writeIndex + length;
final AutoResize strategy = this.autoResize;
if (strategy == null)
throw new IndexOutOfBoundsException("Buffer is full and cannot be resized: " + capacity + " -> " + targetSize);
final long newCapacity = strategy.resize(capacity, targetSize);
if (newCapacity == capacity)
throw new IndexOutOfBoundsException("Buffer is full has been resized to the same capacity: " + capacity + " -> " + targetSize);
return newCapacity;
}
@Override
public void compact() {
assertDummy();
assertReadOnly();
ByteBuffer nioBuffer = bufferSlice((int) readIndex, (int) readableBytes());
nioBuffer.compact();
writeIndex -= readIndex;
readIndex = 0;
}
@Override
public NetworkBuffer slice(long index, long length, long readIndex, long writeIndex) {
assertDummy();
Objects.checkFromIndexSize(index, length, capacity);
NetworkBufferImpl slice = new NetworkBufferImpl(this,
address + index, length,
readIndex, writeIndex,
autoResize, registries);
slice.readOnly = readOnly;
return slice;
}
@Override
public NetworkBuffer copy(long index, long length, long readIndex, long writeIndex) {
assertDummy();
Objects.checkFromIndexSize(index, length, capacity);
final long newAddress = UNSAFE.allocateMemory(length);
if (newAddress == 0) {
throw new OutOfMemoryError("Failed to allocate memory");
}
UNSAFE.copyMemory(address + index, newAddress, length);
return new NetworkBufferImpl(null,
newAddress, length,
readIndex, writeIndex,
autoResize, registries);
}
@Override
public int readChannel(ReadableByteChannel channel) throws IOException {
assertDummy();
assertReadOnly();
assertOverflow(writeIndex + writableBytes());
var buffer = bufferSlice((int) writeIndex, (int) writableBytes());
final int count = channel.read(buffer);
if (count == -1) throw new EOFException("Disconnected");
advanceWrite(count);
return count;
}
@Override
public boolean writeChannel(SocketChannel channel) throws IOException {
assertDummy();
final long readableBytes = readableBytes();
if (readableBytes == 0) return true; // Nothing to write
assertOverflow(readIndex + readableBytes);
var buffer = bufferSlice((int) readIndex, (int) readableBytes);
if (!buffer.hasRemaining())
return true; // Nothing to write
final int count = channel.write(buffer);
if (count == -1) throw new EOFException("Disconnected");
advanceRead(count);
return !buffer.hasRemaining();
}
@Override
public void cipher(Cipher cipher, long start, long length) {
assertDummy();
assertOverflow(start + length);
ByteBuffer input = bufferSlice((int) start, (int) length);
try {
cipher.update(input, input.duplicate());
} catch (ShortBufferException e) {
throw new RuntimeException(e);
}
}
private static final ObjectPool<Deflater> DEFLATER_POOL = ObjectPool.pool(Deflater::new);
private static final ObjectPool<Inflater> INFLATER_POOL = ObjectPool.pool(Inflater::new);
@Override
public long compress(long start, long length, NetworkBuffer output) {
assertDummy();
impl(output).assertReadOnly();
assertOverflow(start + length);
ByteBuffer input = bufferSlice((int) start, (int) length);
ByteBuffer outputBuffer = impl(output).bufferSlice((int) output.writeIndex(), (int) output.writableBytes());
Deflater deflater = DEFLATER_POOL.get();
try {
deflater.setInput(input);
deflater.finish();
final int bytes = deflater.deflate(outputBuffer);
deflater.reset();
output.advanceWrite(bytes);
return bytes;
} finally {
DEFLATER_POOL.add(deflater);
}
}
@Override
public long decompress(long start, long length, NetworkBuffer output) throws DataFormatException {
assertDummy();
impl(output).assertReadOnly();
assertOverflow(start + length);
ByteBuffer input = bufferSlice((int) start, (int) length);
ByteBuffer outputBuffer = impl(output).bufferSlice((int) output.writeIndex(), (int) output.writableBytes());
Inflater inflater = INFLATER_POOL.get();
try {
inflater.setInput(input);
final int bytes = inflater.inflate(outputBuffer);
inflater.reset();
output.advanceWrite(bytes);
return bytes;
} finally {
INFLATER_POOL.add(inflater);
}
}
@Override
public @Nullable Registries registries() {
return registries;
}
private ByteBuffer bufferSlice(int position, int length) {
ByteBuffer buffer = ByteBuffer.allocateDirect(0).order(ByteOrder.BIG_ENDIAN);
updateAddress(buffer, address);
updateCapacity(buffer, (int) capacity);
buffer.limit(position + length).position(position);
return buffer;
}
@Override
public String toString() {
return String.format("NetworkBuffer{r%d|w%d->%d, registries=%s, autoResize=%s, readOnly=%s}",
readIndex, writeIndex, capacity, registries != null, autoResize != null, readOnly);
}
private static final boolean ENDIAN_CONVERSION = ByteOrder.nativeOrder() != ByteOrder.BIG_ENDIAN;
private boolean isDummy() {
return address == DUMMY_ADDRESS;
}
// Internal writing methods
void _putBytes(long index, byte[] value) {
if (isDummy()) return;
assertReadOnly();
Objects.checkFromIndexSize(index, value.length, capacity);
UNSAFE.copyMemory(value, BYTE_ARRAY_OFFSET, null, address + index, value.length);
}
void _getBytes(long index, byte[] value) {
assertDummy();
Objects.checkFromIndexSize(index, value.length, capacity);
UNSAFE.copyMemory(null, address + index, value, BYTE_ARRAY_OFFSET, value.length);
}
void _putByte(long index, byte value) {
if (isDummy()) return;
assertReadOnly();
Objects.checkFromIndexSize(index, Byte.BYTES, capacity);
UNSAFE.putByte(address + index, value);
}
byte _getByte(long index) {
assertDummy();
Objects.checkFromIndexSize(index, Byte.BYTES, capacity);
return UNSAFE.getByte(address + index);
}
void _putShort(long index, short value) {
if (isDummy()) return;
assertReadOnly();
Objects.checkFromIndexSize(index, Short.BYTES, capacity);
if (ENDIAN_CONVERSION) value = Short.reverseBytes(value);
UNSAFE.putShort(address + index, value);
}
short _getShort(long index) {
assertDummy();
Objects.checkFromIndexSize(index, Short.BYTES, capacity);
final short value = UNSAFE.getShort(address + index);
return ENDIAN_CONVERSION ? Short.reverseBytes(value) : value;
}
void _putInt(long index, int value) {
if (isDummy()) return;
assertReadOnly();
Objects.checkFromIndexSize(index, Integer.BYTES, capacity);
if (ENDIAN_CONVERSION) value = Integer.reverseBytes(value);
UNSAFE.putInt(address + index, value);
}
int _getInt(long index) {
assertDummy();
Objects.checkFromIndexSize(index, Integer.BYTES, capacity);
final int value = UNSAFE.getInt(address + index);
return ENDIAN_CONVERSION ? Integer.reverseBytes(value) : value;
}
void _putLong(long index, long value) {
if (isDummy()) return;
assertReadOnly();
Objects.checkFromIndexSize(index, Long.BYTES, capacity);
if (ENDIAN_CONVERSION) value = Long.reverseBytes(value);
UNSAFE.putLong(address + index, value);
}
long _getLong(long index) {
assertDummy();
Objects.checkFromIndexSize(index, Long.BYTES, capacity);
final long value = UNSAFE.getLong(address + index);
return ENDIAN_CONVERSION ? Long.reverseBytes(value) : value;
}
void _putFloat(long index, float value) {
if (isDummy()) return;
assertReadOnly();
Objects.checkFromIndexSize(index, Float.BYTES, capacity);
int intValue = Float.floatToIntBits(value);
if (ENDIAN_CONVERSION) intValue = Integer.reverseBytes(intValue);
UNSAFE.putInt(address + index, intValue);
}
float _getFloat(long index) {
assertDummy();
Objects.checkFromIndexSize(index, Float.BYTES, capacity);
int intValue = UNSAFE.getInt(address + index);
if (ENDIAN_CONVERSION) intValue = Integer.reverseBytes(intValue);
return Float.intBitsToFloat(intValue);
}
void _putDouble(long index, double value) {
if (isDummy()) return;
assertReadOnly();
Objects.checkFromIndexSize(index, Double.BYTES, capacity);
long longValue = Double.doubleToLongBits(value);
if (ENDIAN_CONVERSION) longValue = Long.reverseBytes(longValue);
UNSAFE.putLong(address + index, longValue);
}
double _getDouble(long index) {
assertDummy();
Objects.checkFromIndexSize(index, Double.BYTES, capacity);
long longValue = UNSAFE.getLong(address + index);
if (ENDIAN_CONVERSION) longValue = Long.reverseBytes(longValue);
return Double.longBitsToDouble(longValue);
}
static NetworkBuffer wrap(byte @NotNull [] bytes, long readIndex, long writeIndex, @Nullable Registries registries) {
var buffer = new Builder(bytes.length).registry(registries).build();
buffer.writeAt(0, NetworkBuffer.RAW_BYTES, bytes);
buffer.index(readIndex, writeIndex);
return buffer;
}
static void copy(NetworkBuffer srcBuffer, long srcOffset,
NetworkBuffer dstBuffer, long dstOffset, long length) {
var src = impl(srcBuffer);
var dst = impl(dstBuffer);
dst.assertReadOnly();
Objects.checkFromIndexSize(srcOffset, length, src.capacity);
Objects.checkFromIndexSize(dstOffset, length, dst.capacity);
final long srcAddress = src.address + srcOffset;
final long dstAddress = dst.address + dstOffset;
UNSAFE.copyMemory(srcAddress, dstAddress, length);
}
public static boolean equals(NetworkBuffer buffer1, NetworkBuffer buffer2) {
var impl1 = impl(buffer1);
var impl2 = impl(buffer2);
final int capacity = (int) impl1.capacity;
if (capacity != impl2.capacity) return false;
final long address1 = impl1.address;
final long address2 = impl2.address;
for (long i = 0; i < capacity; i++) {
if (UNSAFE.getByte(address1 + i) != UNSAFE.getByte(address2 + i)) {
return false;
}
}
return true;
}
void assertReadOnly() {
if (readOnly) throw new UnsupportedOperationException("Buffer is read-only");
}
void assertDummy() {
if (isDummy()) throw new UnsupportedOperationException("Buffer is a dummy buffer");
}
static final class Builder implements NetworkBuffer.Builder {
private final long initialSize;
private AutoResize autoResize;
private Registries registries;
public Builder(long initialSize) {
this.initialSize = initialSize;
}
@Override
public NetworkBuffer.@NotNull Builder autoResize(@Nullable AutoResize autoResize) {
this.autoResize = autoResize;
return this;
}
@Override
public NetworkBuffer.@NotNull Builder registry(Registries registries) {
this.registries = registries;
return this;
}
@Override
public @NotNull NetworkBuffer build() {
final long address = UNSAFE.allocateMemory(initialSize);
return new NetworkBufferImpl(null,
address, initialSize,
0, 0,
autoResize, registries);
}
}
static NetworkBufferImpl dummy(Registries registries) {
// Dummy buffer with no memory allocated
// Useful for size calculations
return new NetworkBufferImpl(null,
DUMMY_ADDRESS, Long.MAX_VALUE,
0, 0,
null, registries);
}
static NetworkBufferImpl impl(NetworkBuffer buffer) {
return (NetworkBufferImpl) buffer;
}
private static void assertOverflow(long value) {
try {
Math.toIntExact(value); // Check if long is within the bounds of an int
} catch (ArithmeticException e) {
throw new RuntimeException("Method does not support long values: " + value);
}
}
}

View File

@ -58,6 +58,21 @@ public final class NetworkBufferTemplate {
R apply(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8, P9 p9, P10 p10);
}
@FunctionalInterface
public interface F11<P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, R> {
R apply(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8, P9 p9, P10 p10, P11 p11);
}
@FunctionalInterface
public interface F12<P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, R> {
R apply(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8, P9 p9, P10 p10, P11 p11, P12 p12);
}
@FunctionalInterface
public interface F19<P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, R> {
R apply(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8, P9 p9, P10 p10, P11 p11, P12 p12, P13 p13, P14 p14, P15 p15, P16 p16, P17 p17, P18 p18, P19 p19);
}
public static <R> Type<R> template(Supplier<R> supplier) {
return new NetworkBufferTypeImpl<>() {
@Override
@ -331,4 +346,135 @@ public final class NetworkBufferTemplate {
}
};
}
public static <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, R> Type<R> template(
Type<P1> p1, Function<R, P1> g1, Type<P2> p2, Function<R, P2> g2,
Type<P3> p3, Function<R, P3> g3, Type<P4> p4, Function<R, P4> g4,
Type<P5> p5, Function<R, P5> g5, Type<P6> p6, Function<R, P6> g6,
Type<P7> p7, Function<R, P7> g7, Type<P8> p8, Function<R, P8> g8,
Type<P9> p9, Function<R, P9> g9, Type<P10> p10, Function<R, P10> g10,
Type<P11> p11, Function<R, P11> g11, F11<P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, R> reader
) {
return new NetworkBufferTypeImpl<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, R value) {
p1.write(buffer, g1.apply(value));
p2.write(buffer, g2.apply(value));
p3.write(buffer, g3.apply(value));
p4.write(buffer, g4.apply(value));
p5.write(buffer, g5.apply(value));
p6.write(buffer, g6.apply(value));
p7.write(buffer, g7.apply(value));
p8.write(buffer, g8.apply(value));
p9.write(buffer, g9.apply(value));
p10.write(buffer, g10.apply(value));
p11.write(buffer, g11.apply(value));
}
@Override
public R read(@NotNull NetworkBuffer buffer) {
return reader.apply(
p1.read(buffer), p2.read(buffer),
p3.read(buffer), p4.read(buffer),
p5.read(buffer), p6.read(buffer),
p7.read(buffer), p8.read(buffer),
p9.read(buffer), p10.read(buffer),
p11.read(buffer)
);
}
};
}
public static <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, R> Type<R> template(
Type<P1> p1, Function<R, P1> g1, Type<P2> p2, Function<R, P2> g2,
Type<P3> p3, Function<R, P3> g3, Type<P4> p4, Function<R, P4> g4,
Type<P5> p5, Function<R, P5> g5, Type<P6> p6, Function<R, P6> g6,
Type<P7> p7, Function<R, P7> g7, Type<P8> p8, Function<R, P8> g8,
Type<P9> p9, Function<R, P9> g9, Type<P10> p10, Function<R, P10> g10,
Type<P11> p11, Function<R, P11> g11, Type<P12> p12, Function<R, P12> g12, F12<P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, R> reader
) {
return new NetworkBufferTypeImpl<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, R value) {
p1.write(buffer, g1.apply(value));
p2.write(buffer, g2.apply(value));
p3.write(buffer, g3.apply(value));
p4.write(buffer, g4.apply(value));
p5.write(buffer, g5.apply(value));
p6.write(buffer, g6.apply(value));
p7.write(buffer, g7.apply(value));
p8.write(buffer, g8.apply(value));
p9.write(buffer, g9.apply(value));
p10.write(buffer, g10.apply(value));
p11.write(buffer, g11.apply(value));
p12.write(buffer, g12.apply(value));
}
@Override
public R read(@NotNull NetworkBuffer buffer) {
return reader.apply(
p1.read(buffer), p2.read(buffer),
p3.read(buffer), p4.read(buffer),
p5.read(buffer), p6.read(buffer),
p7.read(buffer), p8.read(buffer),
p9.read(buffer), p10.read(buffer),
p11.read(buffer), p12.read(buffer)
);
}
};
}
public static <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, R> Type<R> template(
Type<P1> p1, Function<R, P1> g1, Type<P2> p2, Function<R, P2> g2,
Type<P3> p3, Function<R, P3> g3, Type<P4> p4, Function<R, P4> g4,
Type<P5> p5, Function<R, P5> g5, Type<P6> p6, Function<R, P6> g6,
Type<P7> p7, Function<R, P7> g7, Type<P8> p8, Function<R, P8> g8,
Type<P9> p9, Function<R, P9> g9, Type<P10> p10, Function<R, P10> g10,
Type<P11> p11, Function<R, P11> g11, Type<P12> p12, Function<R, P12> g12,
Type<P13> p13, Function<R, P13> g13, Type<P14> p14, Function<R, P14> g14,
Type<P15> p15, Function<R, P15> g15, Type<P16> p16, Function<R, P16> g16,
Type<P17> p17, Function<R, P17> g17, Type<P18> p18, Function<R, P18> g18,
Type<P19> p19, Function<R, P19> g19, F19<P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, R> reader
) {
return new NetworkBufferTypeImpl<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, R value) {
p1.write(buffer, g1.apply(value));
p2.write(buffer, g2.apply(value));
p3.write(buffer, g3.apply(value));
p4.write(buffer, g4.apply(value));
p5.write(buffer, g5.apply(value));
p6.write(buffer, g6.apply(value));
p7.write(buffer, g7.apply(value));
p8.write(buffer, g8.apply(value));
p9.write(buffer, g9.apply(value));
p10.write(buffer, g10.apply(value));
p11.write(buffer, g11.apply(value));
p12.write(buffer, g12.apply(value));
p13.write(buffer, g13.apply(value));
p14.write(buffer, g14.apply(value));
p15.write(buffer, g15.apply(value));
p16.write(buffer, g16.apply(value));
p17.write(buffer, g17.apply(value));
p18.write(buffer, g18.apply(value));
p19.write(buffer, g19.apply(value));
}
@Override
public R read(@NotNull NetworkBuffer buffer) {
return reader.apply(
p1.read(buffer), p2.read(buffer),
p3.read(buffer), p4.read(buffer),
p5.read(buffer), p6.read(buffer),
p7.read(buffer), p8.read(buffer),
p9.read(buffer), p10.read(buffer),
p11.read(buffer), p12.read(buffer),
p13.read(buffer), p14.read(buffer),
p15.read(buffer), p16.read(buffer),
p17.read(buffer), p18.read(buffer),
p19.read(buffer)
);
}
};
}
}

View File

@ -1,13 +1,13 @@
package net.minestom.server.network;
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.network.packet.server.play.data.WorldPos;
import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.Registries;
@ -20,12 +20,12 @@ import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.UUID;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import static net.minestom.server.network.NetworkBuffer.*;
import static net.minestom.server.network.NetworkBufferImpl.impl;
interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
int SEGMENT_BITS = 0x7F;
@ -45,15 +45,15 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
record BooleanType() implements NetworkBufferTypeImpl<Boolean> {
@Override
public void write(@NotNull NetworkBuffer buffer, Boolean value) {
buffer.ensureSize(1);
buffer.nioBuffer.put(buffer.writeIndex(), value ? (byte) 1 : (byte) 0);
buffer.writeIndex += 1;
buffer.ensureWritable(1);
impl(buffer)._putByte(buffer.writeIndex(), value ? (byte) 1 : (byte) 0);
buffer.advanceWrite(1);
}
@Override
public Boolean read(@NotNull NetworkBuffer buffer) {
final byte value = buffer.nioBuffer.get(buffer.readIndex());
buffer.readIndex += 1;
final byte value = impl(buffer)._getByte(buffer.readIndex());
buffer.advanceRead(1);
return value == 1;
}
}
@ -61,15 +61,15 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
record ByteType() implements NetworkBufferTypeImpl<Byte> {
@Override
public void write(@NotNull NetworkBuffer buffer, Byte value) {
buffer.ensureSize(1);
buffer.nioBuffer.put(buffer.writeIndex(), value);
buffer.writeIndex += 1;
buffer.ensureWritable(1);
impl(buffer)._putByte(buffer.writeIndex(), value);
buffer.advanceWrite(1);
}
@Override
public Byte read(@NotNull NetworkBuffer buffer) {
final byte value = buffer.nioBuffer.get(buffer.readIndex());
buffer.readIndex += 1;
final byte value = impl(buffer)._getByte(buffer.readIndex());
buffer.advanceRead(1);
return value;
}
}
@ -77,15 +77,15 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
record ShortType() implements NetworkBufferTypeImpl<Short> {
@Override
public void write(@NotNull NetworkBuffer buffer, Short value) {
buffer.ensureSize(2);
buffer.nioBuffer.putShort(buffer.writeIndex(), value);
buffer.writeIndex += 2;
buffer.ensureWritable(2);
impl(buffer)._putShort(buffer.writeIndex(), value);
buffer.advanceWrite(2);
}
@Override
public Short read(@NotNull NetworkBuffer buffer) {
final short value = buffer.nioBuffer.getShort(buffer.readIndex());
buffer.readIndex += 2;
final short value = impl(buffer)._getShort(buffer.readIndex());
buffer.advanceRead(2);
return value;
}
}
@ -93,15 +93,15 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
record UnsignedShortType() implements NetworkBufferTypeImpl<Integer> {
@Override
public void write(@NotNull NetworkBuffer buffer, Integer value) {
buffer.ensureSize(2);
buffer.nioBuffer.putShort(buffer.writeIndex(), (short) (value & 0xFFFF));
buffer.writeIndex += 2;
buffer.ensureWritable(2);
impl(buffer)._putShort(buffer.writeIndex(), (short) (value & 0xFFFF));
buffer.advanceWrite(2);
}
@Override
public Integer read(@NotNull NetworkBuffer buffer) {
final short value = buffer.nioBuffer.getShort(buffer.readIndex());
buffer.readIndex += 2;
final short value = impl(buffer)._getShort(buffer.readIndex());
buffer.advanceRead(2);
return value & 0xFFFF;
}
}
@ -109,15 +109,15 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
record IntType() implements NetworkBufferTypeImpl<Integer> {
@Override
public void write(@NotNull NetworkBuffer buffer, Integer value) {
buffer.ensureSize(4);
buffer.nioBuffer.putInt(buffer.writeIndex(), value);
buffer.writeIndex += 4;
buffer.ensureWritable(4);
impl(buffer)._putInt(buffer.writeIndex(), value);
buffer.advanceWrite(4);
}
@Override
public Integer read(@NotNull NetworkBuffer buffer) {
final int value = buffer.nioBuffer.getInt(buffer.readIndex());
buffer.readIndex += 4;
final int value = impl(buffer)._getInt(buffer.readIndex());
buffer.advanceRead(4);
return value;
}
}
@ -125,15 +125,15 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
record LongType() implements NetworkBufferTypeImpl<Long> {
@Override
public void write(@NotNull NetworkBuffer buffer, Long value) {
buffer.ensureSize(8);
buffer.nioBuffer.putLong(buffer.writeIndex(), value);
buffer.writeIndex += 8;
buffer.ensureWritable(8);
impl(buffer)._putLong(buffer.writeIndex(), value);
buffer.advanceWrite(8);
}
@Override
public Long read(@NotNull NetworkBuffer buffer) {
final long value = buffer.nioBuffer.getLong(buffer.readIndex());
buffer.readIndex += 8;
final long value = impl(buffer)._getLong(buffer.readIndex());
buffer.advanceRead(8);
return value;
}
}
@ -141,15 +141,15 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
record FloatType() implements NetworkBufferTypeImpl<Float> {
@Override
public void write(@NotNull NetworkBuffer buffer, Float value) {
buffer.ensureSize(4);
buffer.nioBuffer.putFloat(buffer.writeIndex(), value);
buffer.writeIndex += 4;
buffer.ensureWritable(4);
impl(buffer)._putFloat(buffer.writeIndex(), value);
buffer.advanceWrite(4);
}
@Override
public Float read(@NotNull NetworkBuffer buffer) {
final float value = buffer.nioBuffer.getFloat(buffer.readIndex());
buffer.readIndex += 4;
final float value = impl(buffer)._getFloat(buffer.readIndex());
buffer.advanceRead(4);
return value;
}
}
@ -157,15 +157,15 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
record DoubleType() implements NetworkBufferTypeImpl<Double> {
@Override
public void write(@NotNull NetworkBuffer buffer, Double value) {
buffer.ensureSize(8);
buffer.nioBuffer.putDouble(buffer.writeIndex(), value);
buffer.writeIndex += 8;
buffer.ensureWritable(8);
impl(buffer)._putDouble(buffer.writeIndex(), value);
buffer.advanceWrite(8);
}
@Override
public Double read(@NotNull NetworkBuffer buffer) {
final double value = buffer.nioBuffer.getDouble(buffer.readIndex());
buffer.readIndex += 8;
final double value = impl(buffer)._getDouble(buffer.readIndex());
buffer.advanceRead(8);
return value;
}
}
@ -173,67 +173,73 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
record VarIntType() implements NetworkBufferTypeImpl<Integer> {
@Override
public void write(@NotNull NetworkBuffer buffer, Integer boxed) {
final int value = boxed;
final int index = buffer.writeIndex();
if ((value & (0xFFFFFFFF << 7)) == 0) {
buffer.ensureSize(1);
buffer.nioBuffer.put(index, (byte) value);
buffer.writeIndex += 1;
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
buffer.ensureSize(2);
buffer.nioBuffer.putShort(index, (short) ((value & 0x7F | 0x80) << 8 | (value >>> 7)));
buffer.writeIndex += 2;
} else if ((value & (0xFFFFFFFF << 21)) == 0) {
buffer.ensureSize(3);
var nio = buffer.nioBuffer;
nio.put(index, (byte) (value & 0x7F | 0x80));
nio.put(index + 1, (byte) ((value >>> 7) & 0x7F | 0x80));
nio.put(index + 2, (byte) (value >>> 14));
buffer.writeIndex += 3;
} else if ((value & (0xFFFFFFFF << 28)) == 0) {
buffer.ensureSize(4);
var nio = buffer.nioBuffer;
nio.putInt(index, (value & 0x7F | 0x80) << 24 | (((value >>> 7) & 0x7F | 0x80) << 16)
| ((value >>> 14) & 0x7F | 0x80) << 8 | (value >>> 21));
buffer.writeIndex += 4;
} else {
buffer.ensureSize(5);
var nio = buffer.nioBuffer;
nio.putInt(index, (value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16
| ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80));
nio.put(index + 4, (byte) (value >>> 28));
buffer.writeIndex += 5;
buffer.ensureWritable(5);
long index = buffer.writeIndex();
int value = boxed;
var nio = impl(buffer);
while (true) {
if ((value & ~SEGMENT_BITS) == 0) {
nio._putByte(index++, (byte) value);
buffer.advanceWrite(index - buffer.writeIndex());
return;
}
nio._putByte(index++, (byte) ((byte) (value & SEGMENT_BITS) | CONTINUE_BIT));
// Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone
value >>>= 7;
}
}
@Override
public Integer read(@NotNull NetworkBuffer buffer) {
int index = buffer.readIndex();
long index = buffer.readIndex();
// https://github.com/jvm-profiling-tools/async-profiler/blob/a38a375dc62b31a8109f3af97366a307abb0fe6f/src/converter/one/jfr/JfrReader.java#L393
int result = 0;
for (int shift = 0; ; shift += 7) {
byte b = buffer.nioBuffer.get(index++);
byte b = impl(buffer)._getByte(index++);
result |= (b & 0x7f) << shift;
if (b >= 0) {
buffer.readIndex += index - buffer.readIndex();
buffer.advanceRead(index - buffer.readIndex());
return result;
}
}
}
}
record VarInt3Type() implements NetworkBufferTypeImpl<Integer> {
@Override
public void write(@NotNull NetworkBuffer buffer, Integer boxed) {
final int value = boxed;
// Value must be between 0 and 2^21
Check.argCondition(value < 0 || value >= (1 << 21), "VarInt3 out of bounds: {0}", value);
buffer.ensureWritable(3);
final long startIndex = buffer.writeIndex();
var impl = impl(buffer);
impl._putByte(startIndex, (byte) (value & 0x7F | 0x80));
impl._putByte(startIndex + 1, (byte) ((value >>> 7) & 0x7F | 0x80));
impl._putByte(startIndex + 2, (byte) (value >>> 14));
buffer.advanceWrite(3);
}
@Override
public Integer read(@NotNull NetworkBuffer buffer) {
// Ensure that the buffer can read other var-int sizes
// The optimization is mostly relevant for writing
return buffer.read(VAR_INT);
}
}
record VarLongType() implements NetworkBufferTypeImpl<Long> {
@Override
public void write(@NotNull NetworkBuffer buffer, Long value) {
buffer.ensureSize(10);
buffer.ensureWritable(10);
int size = 0;
while (true) {
if ((value & ~((long) SEGMENT_BITS)) == 0) {
buffer.nioBuffer.put(buffer.writeIndex() + size, (byte) value.intValue());
buffer.writeIndex += size + 1;
impl(buffer)._putByte(buffer.writeIndex() + size, (byte) value.intValue());
buffer.advanceWrite(size + 1);
return;
}
buffer.nioBuffer.put(buffer.writeIndex() + size, (byte) (value & SEGMENT_BITS | CONTINUE_BIT));
impl(buffer)._putByte(buffer.writeIndex() + size, (byte) (value & SEGMENT_BITS | CONTINUE_BIT));
size++;
// Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone
value >>>= 7;
@ -247,34 +253,44 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
int position = 0;
byte currentByte;
while (true) {
currentByte = buffer.nioBuffer.get(buffer.readIndex() + length);
currentByte = impl(buffer)._getByte(buffer.readIndex() + length);
length++;
value |= (long) (currentByte & SEGMENT_BITS) << position;
if ((currentByte & CONTINUE_BIT) == 0) break;
position += 7;
if (position >= 64) throw new RuntimeException("VarLong is too big");
}
buffer.readIndex += length;
buffer.advanceRead(length);
return value;
}
}
record RawBytesType() implements NetworkBufferTypeImpl<byte[]> {
record RawBytesType(int length) implements NetworkBufferTypeImpl<byte[]> {
@Override
public void write(@NotNull NetworkBuffer buffer, byte[] value) {
buffer.ensureSize(value.length);
buffer.nioBuffer.put(buffer.writeIndex(), value);
buffer.writeIndex += value.length;
if (length != -1 && value.length != length) {
throw new IllegalArgumentException("Invalid length: " + value.length + " != " + length);
}
final int length = value.length;
if (length == 0) return;
buffer.ensureWritable(length);
impl(buffer)._putBytes(buffer.writeIndex(), value);
buffer.advanceWrite(length);
}
@Override
public byte[] read(@NotNull NetworkBuffer buffer) {
final int limit = buffer.nioBuffer.limit();
final int length = limit - buffer.readIndex();
assert length >= 0 : "Invalid remaining: " + length;
final byte[] bytes = new byte[length];
buffer.nioBuffer.get(buffer.readIndex(), bytes);
buffer.readIndex += length;
long length = buffer.readableBytes();
if (this.length != -1) {
length = Math.min(length, this.length);
}
if (length == 0) return new byte[0];
assert length > 0 : "Invalid remaining: " + length;
final int arrayLength = Math.toIntExact(length);
final byte[] bytes = new byte[arrayLength];
impl(buffer)._getBytes(buffer.readIndex(), bytes);
buffer.advanceRead(arrayLength);
return bytes;
}
}
@ -283,18 +299,12 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
@Override
public void write(@NotNull NetworkBuffer buffer, String value) {
final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
buffer.write(VAR_INT, bytes.length);
buffer.write(RAW_BYTES, bytes);
buffer.write(BYTE_ARRAY, bytes);
}
@Override
public String read(@NotNull NetworkBuffer buffer) {
final int length = buffer.read(VAR_INT);
final int remaining = buffer.nioBuffer.limit() - buffer.readIndex();
Check.argCondition(length > remaining, "String is too long (length: {0}, readable: {1})", length, remaining);
byte[] bytes = new byte[length];
buffer.nioBuffer.get(buffer.readIndex(), bytes);
buffer.readIndex += length;
final byte[] bytes = buffer.read(BYTE_ARRAY);
return new String(bytes, StandardCharsets.UTF_8);
}
}
@ -323,7 +333,7 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
record NbtType() implements NetworkBufferTypeImpl<BinaryTag> {
@Override
public void write(@NotNull NetworkBuffer buffer, BinaryTag value) {
BinaryTagWriter nbtWriter = buffer.nbtWriter;
BinaryTagWriter nbtWriter = impl(buffer).nbtWriter;
if (nbtWriter == null) {
nbtWriter = new BinaryTagWriter(new DataOutputStream(new OutputStream() {
@Override
@ -331,7 +341,7 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
buffer.write(BYTE, (byte) b);
}
}));
buffer.nbtWriter = nbtWriter;
impl(buffer).nbtWriter = nbtWriter;
}
try {
nbtWriter.writeNameless(value);
@ -342,7 +352,7 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
@Override
public BinaryTag read(@NotNull NetworkBuffer buffer) {
BinaryTagReader nbtReader = buffer.nbtReader;
BinaryTagReader nbtReader = impl(buffer).nbtReader;
if (nbtReader == null) {
nbtReader = new BinaryTagReader(new DataInputStream(new InputStream() {
@Override
@ -352,10 +362,10 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
@Override
public int available() {
return buffer.readableBytes();
return (int) buffer.readableBytes();
}
}));
buffer.nbtReader = nbtReader;
impl(buffer).nbtReader = nbtReader;
}
try {
return nbtReader.readNameless();
@ -447,10 +457,10 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
@Override
public byte[] read(@NotNull NetworkBuffer buffer) {
final int length = buffer.read(VAR_INT);
final byte[] bytes = new byte[length];
buffer.nioBuffer.get(buffer.readIndex(), bytes);
buffer.readIndex += length;
return bytes;
if (length == 0) return new byte[0];
final long remaining = buffer.readableBytes();
Check.argCondition(length > remaining, "String is too long (length: {0}, readable: {1})", length, remaining);
return buffer.read(FixedRawBytes(length));
}
}
@ -534,18 +544,6 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
}
}
record DeathLocationType() implements NetworkBufferTypeImpl<WorldPos> {
@Override
public void write(@NotNull NetworkBuffer buffer, WorldPos value) {
buffer.writeOptional(value);
}
@Override
public WorldPos read(@NotNull NetworkBuffer buffer) {
return buffer.readOptional(WorldPos::new);
}
}
record Vector3Type() implements NetworkBufferTypeImpl<Point> {
@Override
public void write(@NotNull NetworkBuffer buffer, Point value) {
@ -580,6 +578,23 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
}
}
record Vector3BType() implements NetworkBufferTypeImpl<Point> {
@Override
public void write(@NotNull NetworkBuffer buffer, Point value) {
buffer.write(BYTE, (byte) value.x());
buffer.write(BYTE, (byte) value.y());
buffer.write(BYTE, (byte) value.z());
}
@Override
public Point read(@NotNull NetworkBuffer buffer) {
final byte x = buffer.read(BYTE);
final byte y = buffer.read(BYTE);
final byte z = buffer.read(BYTE);
return new Vec(x, y, z);
}
}
record QuaternionType() implements NetworkBufferTypeImpl<float[]> {
@Override
public void write(@NotNull NetworkBuffer buffer, float[] value) {
@ -601,27 +616,61 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
// Combinators
record EnumType<E extends Enum<?>>(@NotNull Class<E> enumClass) implements NetworkBufferTypeImpl<E> {
record EnumSetType<E extends Enum<E>>(@NotNull Class<E> enumType,
E[] values) implements NetworkBufferTypeImpl<EnumSet<E>> {
@Override
public void write(@NotNull NetworkBuffer buffer, E value) {
buffer.writeEnum(enumClass, value);
public void write(@NotNull NetworkBuffer buffer, EnumSet<E> value) {
BitSet bitSet = new BitSet(values.length);
for (int i = 0; i < values.length; ++i) {
bitSet.set(i, value.contains(values[i]));
}
final byte[] array = bitSet.toByteArray();
buffer.write(RAW_BYTES, array);
}
@Override
public E read(@NotNull NetworkBuffer buffer) {
return buffer.readEnum(enumClass);
public EnumSet<E> read(@NotNull NetworkBuffer buffer) {
final byte[] array = buffer.read(FixedRawBytes((values.length + 7) / 8));
BitSet bitSet = BitSet.valueOf(array);
EnumSet<E> enumSet = EnumSet.noneOf(enumType);
for (int i = 0; i < values.length; ++i) {
if (bitSet.get(i)) {
enumSet.add(values[i]);
}
}
return enumSet;
}
}
record FixedBitSetType(int length) implements NetworkBufferTypeImpl<BitSet> {
@Override
public void write(@NotNull NetworkBuffer buffer, BitSet value) {
final int setLength = value.length();
if (setLength > length) {
throw new IllegalArgumentException("BitSet is larger than expected size (" + setLength + ">" + length + ")");
} else {
final byte[] array = value.toByteArray();
buffer.write(RAW_BYTES, array);
}
}
@Override
public BitSet read(@NotNull NetworkBuffer buffer) {
final byte[] array = buffer.read(FixedRawBytes((length + 7) / 8));
return BitSet.valueOf(array);
}
}
record OptionalType<T>(@NotNull Type<T> parent) implements NetworkBufferTypeImpl<@Nullable T> {
@Override
public void write(@NotNull NetworkBuffer buffer, T value) {
buffer.writeOptional(parent, value);
buffer.write(BOOLEAN, value != null);
if (value != null) buffer.write(parent, value);
}
@Override
public T read(@NotNull NetworkBuffer buffer) {
return buffer.readOptional(parent);
return buffer.read(BOOLEAN) ? buffer.read(parent) : null;
}
}
@ -646,8 +695,8 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
}
}
record MappedType<T, S>(@NotNull Type<T> parent, @NotNull Function<T, S> to,
@NotNull Function<S, T> from) implements NetworkBufferTypeImpl<S> {
record TransformType<T, S>(@NotNull Type<T> parent, @NotNull Function<T, S> to,
@NotNull Function<S, T> from) implements NetworkBufferTypeImpl<S> {
@Override
public void write(@NotNull NetworkBuffer buffer, S value) {
parent.write(buffer, from.apply(value));
@ -659,27 +708,51 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
}
}
record ListType<T>(@NotNull Type<T> parent, int maxSize) implements NetworkBufferTypeImpl<List<T>> {
record MapType<K, V>(@NotNull Type<K> parent, @NotNull NetworkBuffer.Type<V> valueType,
int maxSize) implements NetworkBufferTypeImpl<Map<K, V>> {
@Override
public void write(@NotNull NetworkBuffer buffer, List<T> value) {
buffer.writeCollection(parent, value);
public void write(@NotNull NetworkBuffer buffer, Map<K, V> map) {
buffer.write(VAR_INT, map.size());
for (Map.Entry<K, V> entry : map.entrySet()) {
buffer.write(parent, entry.getKey());
buffer.write(valueType, entry.getValue());
}
}
@SuppressWarnings("unchecked")
@Override
public List<T> read(@NotNull NetworkBuffer buffer) {
return buffer.readCollection(parent, maxSize);
public Map<K, V> read(@NotNull NetworkBuffer buffer) {
final int size = buffer.read(VAR_INT);
Check.argCondition(size > maxSize, "Map size ({0}) is higher than the maximum allowed size ({1})", size, maxSize);
K[] keys = (K[]) new Object[size];
V[] values = (V[]) new Object[size];
for (int i = 0; i < size; i++) {
keys[i] = buffer.read(parent);
values[i] = buffer.read(valueType);
}
return Map.copyOf(new Object2ObjectArrayMap<>(keys, values, size));
}
}
record OptionalTypeImpl<T>(@NotNull Type<T> parent) implements NetworkBufferTypeImpl<T> {
record ListType<T>(@NotNull Type<T> parent, int maxSize) implements NetworkBufferTypeImpl<List<T>> {
@Override
public void write(@NotNull NetworkBuffer buffer, T value) {
buffer.writeOptional(parent, value);
public void write(@NotNull NetworkBuffer buffer, List<T> values) {
if (values == null) {
buffer.write(BYTE, (byte) 0);
return;
}
buffer.write(VAR_INT, values.size());
for (T value : values) buffer.write(parent, value);
}
@SuppressWarnings("unchecked")
@Override
public T read(@NotNull NetworkBuffer buffer) {
return buffer.readOptional(parent);
public List<T> read(@NotNull NetworkBuffer buffer) {
final int size = buffer.read(VAR_INT);
Check.argCondition(size > maxSize, "Collection size ({0}) is higher than the maximum allowed size ({1})", size, maxSize);
T[] values = (T[]) new Object[size];
for (int i = 0; i < size; i++) values[i] = buffer.read(parent);
return List.of(values);
}
}
@ -687,23 +760,31 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
@NotNull Function<Registries, DynamicRegistry<T>> selector) implements NetworkBufferTypeImpl<DynamicRegistry.Key<T>> {
@Override
public void write(@NotNull NetworkBuffer buffer, DynamicRegistry.Key<T> value) {
Check.stateCondition(buffer.registries == null, "Buffer does not have registries");
final DynamicRegistry<T> registry = selector.apply(buffer.registries);
final Registries registries = impl(buffer).registries;
Check.stateCondition(registries == null, "Buffer does not have registries");
final DynamicRegistry<T> registry = selector.apply(registries);
// Painting variants may be sent in their entirety rather than a registry reference so the ID is offset by 1 to indicate this.
// FIXME: Support sending the entire registry object instead of an ID reference.
final int id = registry.id().equals("minecraft:painting_variant")?registry.getId(value)+1:registry.getId(value);
final int id = registry.id().equals("minecraft:painting_variant") ? registry.getId(value) + 1 : registry.getId(value);
Check.argCondition(id == -1, "Key is not registered: {0} > {1}", registry, value);
buffer.write(VAR_INT, id);
}
@Override
public DynamicRegistry.Key<T> read(@NotNull NetworkBuffer buffer) {
Check.stateCondition(buffer.registries == null, "Buffer does not have registries");
DynamicRegistry<T> registry = selector.apply(buffer.registries);
final Registries registries = impl(buffer).registries;
Check.stateCondition(registries == null, "Buffer does not have registries");
DynamicRegistry<T> registry = selector.apply(registries);
final int id = buffer.read(VAR_INT);
final DynamicRegistry.Key<T> key = registry.getKey(id);
Check.argCondition(key == null, "No such ID in registry: {0} > {1}", registry, id);
return key;
}
}
static <T> long sizeOf(Type<T> type, T value, Registries registries) {
NetworkBuffer buffer = NetworkBufferImpl.dummy(registries);
type.write(buffer, value);
return buffer.writeIndex();
}
}

View File

@ -0,0 +1,48 @@
package net.minestom.server.network;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.nio.Buffer;
import java.nio.ByteBuffer;
final class NetworkBufferUnsafe {
static final Unsafe UNSAFE;
static final Field ADDRESS, CAPACITY;
static final long ADDRESS_OFFSET, CAPACITY_OFFSET;
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
UNSAFE = (Unsafe) theUnsafe.get(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
try {
ADDRESS = Buffer.class.getDeclaredField("address");
CAPACITY = Buffer.class.getDeclaredField("capacity");
// Use Unsafe to read value of the address field. This way it will not fail on JDK9+ which
// will forbid changing the access level via reflection.
ADDRESS_OFFSET = UNSAFE.objectFieldOffset(ADDRESS);
CAPACITY_OFFSET = UNSAFE.objectFieldOffset(CAPACITY);
} catch (NoSuchFieldException e) {
throw new AssertionError(e);
}
}
/**
* The offset, in bytes, between the base memory address of a byte array and its first element.
*/
static final long BYTE_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);
static void updateAddress(ByteBuffer buffer, long address) {
UNSAFE.putLong(buffer, ADDRESS_OFFSET, address);
}
static void updateCapacity(ByteBuffer buffer, int capacity) {
UNSAFE.putInt(buffer, CAPACITY_OFFSET, capacity);
}
}

View File

@ -14,15 +14,15 @@ import org.jetbrains.annotations.NotNull;
*/
public sealed interface PacketParser<T> {
@NotNull PacketRegistry<T> handshakeRegistry();
@NotNull PacketRegistry<T> handshake();
@NotNull PacketRegistry<T> statusRegistry();
@NotNull PacketRegistry<T> status();
@NotNull PacketRegistry<T> loginRegistry();
@NotNull PacketRegistry<T> login();
@NotNull PacketRegistry<T> configurationRegistry();
@NotNull PacketRegistry<T> configuration();
@NotNull PacketRegistry<T> playRegistry();
@NotNull PacketRegistry<T> play();
default @NotNull T parse(@NotNull ConnectionState connectionState,
int packetId, @NotNull NetworkBuffer buffer) {
@ -32,20 +32,20 @@ public sealed interface PacketParser<T> {
default @NotNull PacketRegistry<T> stateRegistry(@NotNull ConnectionState connectionState) {
return switch (connectionState) {
case HANDSHAKE -> handshakeRegistry();
case STATUS -> statusRegistry();
case LOGIN -> loginRegistry();
case CONFIGURATION -> configurationRegistry();
case PLAY -> playRegistry();
case HANDSHAKE -> handshake();
case STATUS -> status();
case LOGIN -> login();
case CONFIGURATION -> configuration();
case PLAY -> play();
};
}
record Client(
PacketRegistry<ClientPacket> handshakeRegistry,
PacketRegistry<ClientPacket> statusRegistry,
PacketRegistry<ClientPacket> loginRegistry,
PacketRegistry<ClientPacket> configurationRegistry,
PacketRegistry<ClientPacket> playRegistry
PacketRegistry<ClientPacket> handshake,
PacketRegistry<ClientPacket> status,
PacketRegistry<ClientPacket> login,
PacketRegistry<ClientPacket> configuration,
PacketRegistry<ClientPacket> play
) implements PacketParser<ClientPacket> {
public Client() {
this(
@ -59,11 +59,11 @@ public sealed interface PacketParser<T> {
}
record Server(
PacketRegistry<ServerPacket> handshakeRegistry,
PacketRegistry<ServerPacket> statusRegistry,
PacketRegistry<ServerPacket> loginRegistry,
PacketRegistry<ServerPacket> configurationRegistry,
PacketRegistry<ServerPacket> playRegistry
PacketRegistry<ServerPacket> handshake,
PacketRegistry<ServerPacket> status,
PacketRegistry<ServerPacket> login,
PacketRegistry<ServerPacket> configuration,
PacketRegistry<ServerPacket> play
) implements PacketParser<ServerPacket> {
public Server() {
this(

View File

@ -0,0 +1,224 @@
package net.minestom.server.network.packet;
import net.minestom.server.ServerFlag;
import net.minestom.server.network.ConnectionState;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.packet.client.ClientPacket;
import net.minestom.server.network.packet.server.ServerPacket;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.zip.DataFormatException;
import static net.minestom.server.network.NetworkBuffer.VAR_INT;
/**
* Tools to read packets from a {@link NetworkBuffer} for network processing.
* <p>
* Fairly internal and performance sensitive.
*/
@SuppressWarnings("ALL")
@ApiStatus.Internal
public final class PacketReading {
private final static Logger LOGGER = LoggerFactory.getLogger(PacketReading.class);
private static final int MAX_VAR_INT_SIZE = 5;
private static final Result.Empty EMPTY_CLIENT_PACKET = new Result.Empty<>();
public sealed interface Result<T> {
/**
* At least one packet was read.
* The buffer may still contain half-read packets and should therefore be compacted for next read.
*/
record Success<T>(List<T> packets, ConnectionState newState) implements Result<T> {
public Success {
if (packets.isEmpty()) {
throw new IllegalArgumentException("Empty packets");
}
}
public Success(T packet, ConnectionState newState) {
this(List.of(packet), newState);
}
}
/**
* Represents no packet to read. Can generally be ignored.
* <p>
* Happens when a packet length or payload couldn't be read, but the buffer has enough capacity.
*/
record Empty<T>() implements Result<T> {
}
/**
* Represents a failure to read a packet due to insufficient buffer capacity.
* <p>
* Buffer should be expanded to at least {@code requiredCapacity} bytes.
* <p>
* If the buffer does not allow to read the packet length, max var-int length is returned.
*/
record Failure<T>(long requiredCapacity) implements Result<T> {
}
}
public static Result<ClientPacket> readClients(
@NotNull NetworkBuffer buffer,
@NotNull ConnectionState state,
boolean compressed
) throws DataFormatException {
return readPackets(buffer, PacketVanilla.CLIENT_PACKET_PARSER, state, PacketVanilla::nextClientState, compressed);
}
public static Result<ServerPacket> readServers(
@NotNull NetworkBuffer buffer,
@NotNull ConnectionState state,
boolean compressed
) throws DataFormatException {
return readPackets(buffer, PacketVanilla.SERVER_PACKET_PARSER, state, PacketVanilla::nextServerState, compressed);
}
public static <T> Result<T> readPackets(
@NotNull NetworkBuffer buffer,
@NotNull PacketParser<T> parser,
@NotNull ConnectionState state,
@NotNull BiFunction<T, ConnectionState, ConnectionState> stateUpdater,
boolean compressed
) throws DataFormatException {
List<T> packets = new ArrayList<>();
readLoop:
while (buffer.readableBytes() > 0) {
final Result<T> result = readPacket(buffer, parser, state, stateUpdater, compressed);
if (buffer.readableBytes() == 0 && packets.isEmpty()) return result;
switch (result) {
case Result.Success<T> success -> {
assert success.packets().size() == 1;
packets.add(success.packets().getFirst());
state = success.newState();
}
case Result.Empty<T> ignored -> {
break readLoop;
}
case Result.Failure<T> failure -> {
return packets.isEmpty() ? failure : new Result.Success<>(packets, state);
}
}
}
return !packets.isEmpty() ? new Result.Success<>(packets, state) : EMPTY_CLIENT_PACKET;
}
public static Result<ClientPacket> readClient(
@NotNull NetworkBuffer buffer,
@NotNull ConnectionState state,
boolean compressed
) throws DataFormatException {
return readPacket(buffer, PacketVanilla.CLIENT_PACKET_PARSER, state, PacketVanilla::nextClientState, compressed);
}
public static Result<ServerPacket> readServer(
@NotNull NetworkBuffer buffer,
@NotNull ConnectionState state,
boolean compressed
) throws DataFormatException {
return readPacket(buffer, PacketVanilla.SERVER_PACKET_PARSER, state, PacketVanilla::nextServerState, compressed);
}
public static <T> Result<T> readPacket(
@NotNull NetworkBuffer buffer,
@NotNull PacketParser<T> parser,
@NotNull ConnectionState state,
@NotNull BiFunction<T, ConnectionState, ConnectionState> stateUpdater,
boolean compressed
) throws DataFormatException {
final long beginMark = buffer.readIndex();
// READ PACKET LENGTH
final int packetLength;
try {
packetLength = buffer.read(VAR_INT);
} catch (IndexOutOfBoundsException e) {
// Couldn't read a single var-int
return new Result.Failure<>(MAX_VAR_INT_SIZE);
}
final long readerStart = buffer.readIndex();
if (readerStart > buffer.writeIndex()) {
// Can't read the packet length, buffer has enough capacity
buffer.readIndex(beginMark);
return EMPTY_CLIENT_PACKET;
}
final int maxPacketSize = maxPacketSize(state);
if (packetLength > maxPacketSize) {
throw new DataFormatException("Packet too large: " + packetLength);
}
// READ PAYLOAD https://wiki.vg/Protocol#Packet_format
if (buffer.readableBytes() < packetLength) {
// Can't read the full packet
buffer.readIndex(beginMark);
final long packetLengthVarIntSize = readerStart - beginMark;
final long requiredCapacity = packetLengthVarIntSize + packetLength;
// Must return a failure if the buffer is too small
// Otherwise do nothing, and hope to read the packet remains next time
if (requiredCapacity > buffer.capacity()) return new Result.Failure<>(requiredCapacity);
else return EMPTY_CLIENT_PACKET;
}
NetworkBuffer content = buffer.slice(buffer.readIndex(), packetLength, 0, packetLength);
final PacketRegistry<T> registry = parser.stateRegistry(state);
final T packet = readFramedPacket(content, registry, compressed);
final ConnectionState nextState = stateUpdater.apply(packet, state);
buffer.readIndex(readerStart + packetLength);
return new Result.Success<>(packet, nextState);
}
private static <T> T readFramedPacket(NetworkBuffer buffer,
PacketRegistry<T> registry,
boolean compressed) throws DataFormatException {
if (!compressed) {
// No compression format
return readPayload(buffer, registry);
}
final int dataLength = buffer.read(VAR_INT);
if (dataLength == 0) {
// Uncompressed packet
return readPayload(buffer, registry);
}
// Decompress the packet into the pooled buffer
// and read the uncompressed packet from it
NetworkBuffer decompressed = PacketVanilla.PACKET_POOL.get();
try {
if (decompressed.capacity() < dataLength) decompressed.resize(dataLength);
buffer.decompress(buffer.readIndex(), buffer.readableBytes(), decompressed);
return readPayload(decompressed, registry);
} finally {
PacketVanilla.PACKET_POOL.add(decompressed);
}
}
private static <T> T readPayload(NetworkBuffer buffer, PacketRegistry<T> registry) {
final int packetId = buffer.read(VAR_INT);
final PacketRegistry.PacketInfo<T> packetInfo = registry.packetInfo(packetId);
final NetworkBuffer.Type<T> serializer = packetInfo.serializer();
try {
final T packet = serializer.read(buffer);
if (buffer.readableBytes() != 0) {
LOGGER.warn("WARNING: Packet ({}) 0x{} not fully read ({})",
packetInfo.packetClass().getSimpleName(), Integer.toHexString(packetId), buffer);
}
return packet;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static int maxPacketSize(ConnectionState state) {
return switch (state) {
case HANDSHAKE, LOGIN -> ServerFlag.MAX_PACKET_SIZE_PRE_AUTH;
default -> ServerFlag.MAX_PACKET_SIZE;
};
}
}

View File

@ -25,9 +25,15 @@ public interface PacketRegistry<T> {
@UnknownNullability
T create(int packetId, @NotNull NetworkBuffer reader);
PacketInfo<T> packetInfo(Class<? extends T> packetClass);
PacketInfo<T> packetInfo(@NotNull Class<?> packetClass);
record PacketInfo<T>(Class<? extends T> packetClass, int id, NetworkBuffer.Type<T> serializer) {
default PacketInfo<T> packetInfo(@NotNull T packet) {
return packetInfo(packet.getClass());
}
PacketInfo<T> packetInfo(int packetId);
record PacketInfo<T>(Class<T> packetClass, int id, NetworkBuffer.Type<T> serializer) {
}
sealed class Client extends PacketRegistryTemplate<ClientPacket> {
@ -155,7 +161,9 @@ public interface PacketRegistry<T> {
final class ServerHandshake extends Server {
public ServerHandshake() {
super();
super(
// Empty
);
}
}
@ -336,16 +344,15 @@ public interface PacketRegistry<T> {
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
sealed class PacketRegistryTemplate<T> implements PacketRegistry<T> {
private final Entry<? extends T>[] suppliers;
private final PacketInfo<? extends T>[] suppliers;
private final ClassValue<PacketInfo<T>> packetIds = new ClassValue<>() {
@Override
protected PacketInfo<T> computeValue(@NotNull Class<?> type) {
for (int i = 0; i < suppliers.length; i++) {
final Entry<? extends T> entry = suppliers[i];
if (entry != null && entry.type == type) {
//noinspection unchecked
return new PacketInfo<T>(entry.type, i, (NetworkBuffer.Type<T>) entry.reader);
for (PacketInfo<? extends T> info : suppliers) {
if (info != null && info.packetClass == type) {
return (PacketInfo<T>) info;
}
}
throw new IllegalStateException("Packet type " + type + " isn't registered!");
@ -354,26 +361,39 @@ public interface PacketRegistry<T> {
@SafeVarargs
PacketRegistryTemplate(Entry<? extends T>... suppliers) {
this.suppliers = suppliers;
PacketInfo<? extends T>[] packetInfos = new PacketInfo[suppliers.length];
for (int i = 0; i < suppliers.length; i++) {
final Entry<? extends T> entry = suppliers[i];
if (entry == null) continue;
packetInfos[i] = new PacketInfo(entry.type, i, entry.reader);
}
this.suppliers = packetInfos;
}
public @UnknownNullability T create(int packetId, @NotNull NetworkBuffer reader) {
final Entry<? extends T> entry = suppliers[packetId];
if (entry == null)
throw new IllegalStateException("Packet id 0x" + Integer.toHexString(packetId) + " isn't registered!");
final NetworkBuffer.Type<? extends T> supplier = entry.reader;
final PacketInfo<T> info = packetInfo(packetId);
final NetworkBuffer.Type<T> supplier = info.serializer;
final T packet = supplier.read(reader);
if (packet == null) {
throw new IllegalStateException("Packet " + entry.type + " failed to read!");
throw new IllegalStateException("Packet " + info.packetClass + " failed to read!");
}
return packet;
}
@Override
public PacketInfo<T> packetInfo(Class<? extends T> packetClass) {
public PacketInfo<T> packetInfo(@NotNull Class<?> packetClass) {
return packetIds.get(packetClass);
}
@Override
public PacketInfo<T> packetInfo(int packetId) {
final PacketInfo<T> info;
if (packetId < 0 || packetId >= suppliers.length || (info = (PacketInfo<T>) suppliers[packetId]) == null) {
throw new IllegalStateException("Packet id 0x" + Integer.toHexString(packetId) + " isn't registered!");
}
return info;
}
record Entry<T>(Class<T> type, NetworkBuffer.Type<T> reader) {
}

View File

@ -0,0 +1,58 @@
package net.minestom.server.network.packet;
import net.minestom.server.MinecraftServer;
import net.minestom.server.ServerFlag;
import net.minestom.server.network.ConnectionState;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.packet.client.ClientPacket;
import net.minestom.server.network.packet.client.configuration.ClientFinishConfigurationPacket;
import net.minestom.server.network.packet.client.handshake.ClientHandshakePacket;
import net.minestom.server.network.packet.client.login.ClientLoginAcknowledgedPacket;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.configuration.FinishConfigurationPacket;
import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
import net.minestom.server.network.packet.server.play.StartConfigurationPacket;
import net.minestom.server.utils.ObjectPool;
import org.jetbrains.annotations.ApiStatus;
/**
* Constants and utilities for vanilla packets.
*/
@ApiStatus.Internal
public final class PacketVanilla {
public static final PacketParser<ClientPacket> CLIENT_PACKET_PARSER = new PacketParser.Client();
public static final PacketParser<ServerPacket> SERVER_PACKET_PARSER = new PacketParser.Server();
/**
* Pool containing a buffer able to hold the largest packet.
* <p>
* Size starts with {@link ServerFlag#POOLED_BUFFER_SIZE} and doubles until {@link ServerFlag#MAX_PACKET_SIZE}.
*/
public static final ObjectPool<NetworkBuffer> PACKET_POOL = ObjectPool.pool(
() -> NetworkBuffer.staticBuffer(ServerFlag.POOLED_BUFFER_SIZE, MinecraftServer.process()),
NetworkBuffer::clear);
public static ConnectionState nextClientState(ClientPacket packet, ConnectionState currentState) {
return switch (packet) {
case ClientHandshakePacket handshakePacket -> switch (handshakePacket.intent()) {
case STATUS -> ConnectionState.STATUS;
case LOGIN, TRANSFER -> ConnectionState.LOGIN;
};
case ClientLoginAcknowledgedPacket ignored -> ConnectionState.CONFIGURATION;
case ClientFinishConfigurationPacket ignored -> ConnectionState.PLAY;
default -> currentState;
};
}
public static ConnectionState nextServerState(ServerPacket packet, ConnectionState currentState) {
// Client chooses between STATUS or LOGIN state directly after the first handshake packet
if (currentState == ConnectionState.HANDSHAKE)
throw new IllegalStateException("No server Handshake packet exists");
return switch (packet) {
case LoginSuccessPacket ignored -> ConnectionState.CONFIGURATION;
case StartConfigurationPacket ignored -> ConnectionState.CONFIGURATION;
case FinishConfigurationPacket ignored -> ConnectionState.PLAY;
default -> currentState;
};
}
}

View File

@ -0,0 +1,163 @@
package net.minestom.server.network.packet;
import net.minestom.server.ServerFlag;
import net.minestom.server.network.ConnectionState;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.packet.client.ClientPacket;
import net.minestom.server.network.packet.server.ServerPacket;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* Tools to write packets into a {@link NetworkBuffer} for network processing.
* <p>
* Fairly internal and performance sensitive.
*/
@ApiStatus.Internal
public final class PacketWriting {
public static void writeFramedPacket(@NotNull NetworkBuffer buffer,
@NotNull ConnectionState state,
@NotNull ClientPacket packet,
int compressionThreshold) throws IndexOutOfBoundsException {
writeFramedPacket(buffer, PacketVanilla.CLIENT_PACKET_PARSER, state, packet, compressionThreshold);
}
public static void writeFramedPacket(@NotNull NetworkBuffer buffer,
@NotNull ConnectionState state,
@NotNull ServerPacket packet,
int compressionThreshold) throws IndexOutOfBoundsException {
writeFramedPacket(buffer, PacketVanilla.SERVER_PACKET_PARSER, state, packet, compressionThreshold);
}
public static <T> void writeFramedPacket(@NotNull NetworkBuffer buffer,
@NotNull PacketParser<T> parser,
@NotNull ConnectionState state,
@NotNull T packet,
int compressionThreshold) throws IndexOutOfBoundsException {
final PacketRegistry<T> registry = parser.stateRegistry(state);
writeFramedPacket(buffer, registry, packet, compressionThreshold);
}
public static <T> void writeFramedPacket(@NotNull NetworkBuffer buffer,
@NotNull PacketRegistry<T> registry,
@NotNull T packet,
int compressionThreshold) throws IndexOutOfBoundsException {
final PacketRegistry.PacketInfo<T> packetInfo = registry.packetInfo(packet);
final int id = packetInfo.id();
final NetworkBuffer.Type<T> serializer = packetInfo.serializer();
writeFramedPacket(
buffer, serializer,
id, packet,
compressionThreshold
);
}
public static <T> void writeFramedPacket(@NotNull NetworkBuffer buffer,
@NotNull NetworkBuffer.Type<T> type,
int id, @NotNull T packet,
int compressionThreshold) throws IndexOutOfBoundsException {
if (compressionThreshold <= 0) writeUncompressedFormat(buffer, type, id, packet);
else writeCompressedFormat(buffer, type, id, packet, compressionThreshold);
}
private static <T> void writeUncompressedFormat(NetworkBuffer buffer,
NetworkBuffer.Type<T> type,
int id, T packet) throws IndexOutOfBoundsException {
// Uncompressed format https://wiki.vg/Protocol#Without_compression
final long lengthIndex = buffer.advanceWrite(3);
buffer.write(NetworkBuffer.VAR_INT, id);
buffer.write(type, packet);
final long finalSize = buffer.writeIndex() - (lengthIndex + 3);
buffer.writeAt(lengthIndex, NetworkBuffer.VAR_INT_3, (int) finalSize);
}
private static <T> void writeCompressedFormat(NetworkBuffer buffer,
NetworkBuffer.Type<T> type,
int id, T packet,
int compressionThreshold) throws IndexOutOfBoundsException {
// Compressed format https://wiki.vg/Protocol#With_compression
final long compressedIndex = buffer.advanceWrite(3);
final long uncompressedIndex = buffer.advanceWrite(3);
final long contentStart = buffer.writeIndex();
buffer.write(NetworkBuffer.VAR_INT, id);
buffer.write(type, packet);
final long packetSize = buffer.writeIndex() - contentStart;
final boolean compressed = packetSize >= compressionThreshold;
if (compressed) {
// Write the compressed content into the pooled buffer
// and compress it into the current buffer
NetworkBuffer input = PacketVanilla.PACKET_POOL.get();
try {
if (input.capacity() < packetSize) input.resize(packetSize);
NetworkBuffer.copy(buffer, contentStart, input, 0, packetSize);
buffer.writeIndex(contentStart);
input.compress(0, packetSize, buffer);
} finally {
PacketVanilla.PACKET_POOL.add(input);
}
}
// Packet header (Packet + Data Length)
buffer.writeAt(compressedIndex, NetworkBuffer.VAR_INT_3, (int) (buffer.writeIndex() - uncompressedIndex));
buffer.writeAt(uncompressedIndex, NetworkBuffer.VAR_INT_3, compressed ? (int) packetSize : 0);
}
public static NetworkBuffer allocateTrimmedPacket(@NotNull ConnectionState state,
@NotNull ClientPacket packet,
int compressionThreshold) {
return allocateTrimmedPacket(PacketVanilla.CLIENT_PACKET_PARSER, state, packet, compressionThreshold);
}
public static NetworkBuffer allocateTrimmedPacket(@NotNull ConnectionState state,
@NotNull ServerPacket packet,
int compressionThreshold) {
return allocateTrimmedPacket(PacketVanilla.SERVER_PACKET_PARSER, state, packet, compressionThreshold);
}
public static <T> NetworkBuffer allocateTrimmedPacket(
@NotNull PacketParser<T> parser,
@NotNull ConnectionState state,
@NotNull T packet,
int compressionThreshold) {
NetworkBuffer buffer = PacketVanilla.PACKET_POOL.get();
try {
return allocateTrimmedPacket(buffer, parser, state, packet, compressionThreshold);
} finally {
PacketVanilla.PACKET_POOL.add(buffer);
}
}
public static <T> NetworkBuffer allocateTrimmedPacket(
@NotNull NetworkBuffer tmpBuffer,
@NotNull PacketParser<T> parser,
@NotNull ConnectionState state,
@NotNull T packet,
int compressionThreshold) {
final PacketRegistry<T> registry = parser.stateRegistry(state);
return allocateTrimmedPacket(tmpBuffer, registry, packet, compressionThreshold);
}
public static <T> NetworkBuffer allocateTrimmedPacket(
@NotNull NetworkBuffer tmpBuffer,
@NotNull PacketRegistry<T> registry,
@NotNull T packet,
int compressionThreshold) {
final PacketRegistry.PacketInfo<T> packetInfo = registry.packetInfo(packet);
final int id = packetInfo.id();
final NetworkBuffer.Type<T> serializer = packetInfo.serializer();
try {
writeFramedPacket(tmpBuffer, serializer, id, packet, compressionThreshold);
return tmpBuffer.copy(0, tmpBuffer.writeIndex());
} catch (IndexOutOfBoundsException e) {
final long sizeOf = serializer.sizeOf(packet, tmpBuffer.registries());
if (sizeOf > ServerFlag.MAX_PACKET_SIZE) {
throw new IllegalStateException("Packet too large: " + sizeOf);
}
// Add 15 bytes to account for the 3 potential varints in the packet header
// Packet Length - Data Length - Packet ID
tmpBuffer.resize(sizeOf + 15);
tmpBuffer.writeIndex(0);
writeFramedPacket(tmpBuffer, serializer, id, packet, compressionThreshold);
return tmpBuffer.copy(0, tmpBuffer.writeIndex());
}
}
}

View File

@ -1,21 +1,9 @@
package net.minestom.server.network.packet.client;
import org.jetbrains.annotations.ApiStatus;
/**
* Represents a packet received from a client.
* <p>
* Packets are value-based, and should therefore not be reliant on identity.
*/
public interface ClientPacket {
/**
* Determines whether this packet should be processed immediately
* or wait until the next server tick.
*
* @return true if this packet should process immediately
*/
@ApiStatus.Internal
default boolean processImmediately() {
return false;
}
}

View File

@ -24,9 +24,4 @@ public record ClientCookieResponsePacket(
Check.argCondition(value != null && value.length > CookieStorePacket.MAX_VALUE_LENGTH,
"Value is too long: {0} > {1}", value != null ? value.length : 0, CookieStorePacket.MAX_VALUE_LENGTH);
}
@Override
public boolean processImmediately() {
return true;
}
}

View File

@ -9,9 +9,4 @@ import static net.minestom.server.network.NetworkBuffer.LONG;
public record ClientKeepAlivePacket(long id) implements ClientPacket {
public static final NetworkBuffer.Type<ClientKeepAlivePacket> SERIALIZER = NetworkBufferTemplate.template(
LONG, ClientKeepAlivePacket::id, ClientKeepAlivePacket::new);
@Override
public boolean processImmediately() {
return true;
}
}

View File

@ -9,9 +9,4 @@ import static net.minestom.server.network.NetworkBuffer.LONG;
public record ClientPingRequestPacket(long number) implements ClientPacket {
public static final NetworkBuffer.Type<ClientPingRequestPacket> SERIALIZER = NetworkBufferTemplate.template(
LONG, ClientPingRequestPacket::number, ClientPingRequestPacket::new);
@Override
public boolean processImmediately() {
return true;
}
}

View File

@ -5,6 +5,9 @@ import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.network.packet.client.ClientPacket;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Objects;
import static net.minestom.server.network.NetworkBuffer.RAW_BYTES;
import static net.minestom.server.network.NetworkBuffer.STRING;
@ -18,4 +21,17 @@ public record ClientPluginMessagePacket(@NotNull String channel, byte[] data) im
if (channel.length() > 256)
throw new IllegalArgumentException("Channel cannot be more than 256 characters long");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ClientPluginMessagePacket that = (ClientPluginMessagePacket) o;
return Objects.deepEquals(data, that.data) && Objects.equals(channel, that.channel);
}
@Override
public int hashCode() {
return Objects.hash(channel, Arrays.hashCode(data));
}
}

View File

@ -2,31 +2,27 @@ package net.minestom.server.network.packet.client.common;
import net.kyori.adventure.resource.ResourcePackStatus;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.network.packet.client.ClientPacket;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
import static net.minestom.server.network.NetworkBuffer.UUID;
import static net.minestom.server.network.NetworkBuffer.VAR_INT;
public record ClientResourcePackStatusPacket(
@NotNull UUID id,
@NotNull ResourcePackStatus status
) implements ClientPacket {
public static NetworkBuffer.Type<ClientResourcePackStatusPacket> SERIALIZER = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, ClientResourcePackStatusPacket value) {
buffer.write(NetworkBuffer.UUID, value.id);
buffer.write(NetworkBuffer.VAR_INT, statusId(value.status));
}
public static final NetworkBuffer.Type<ClientResourcePackStatusPacket> SERIALIZER = NetworkBufferTemplate.template(
UUID, ClientResourcePackStatusPacket::id,
VAR_INT.transform(ClientResourcePackStatusPacket::readStatus, ClientResourcePackStatusPacket::statusId), ClientResourcePackStatusPacket::status,
ClientResourcePackStatusPacket::new
);
@Override
public ClientResourcePackStatusPacket read(@NotNull NetworkBuffer buffer) {
return new ClientResourcePackStatusPacket(buffer.read(NetworkBuffer.UUID), readStatus(buffer));
}
};
private static @NotNull ResourcePackStatus readStatus(@NotNull NetworkBuffer reader) {
var ordinal = reader.read(NetworkBuffer.VAR_INT);
return switch (ordinal) {
private static @NotNull ResourcePackStatus readStatus(int id) {
return switch (id) {
case 0 -> ResourcePackStatus.SUCCESSFULLY_LOADED;
case 1 -> ResourcePackStatus.DECLINED;
case 2 -> ResourcePackStatus.FAILED_DOWNLOAD;
@ -35,7 +31,7 @@ public record ClientResourcePackStatusPacket(
case 5 -> ResourcePackStatus.INVALID_URL;
case 6 -> ResourcePackStatus.FAILED_RELOAD;
case 7 -> ResourcePackStatus.DISCARDED;
default -> throw new IllegalStateException("Unexpected resource pack status: " + ordinal);
default -> throw new IllegalStateException("Unexpected resource pack status: " + id);
};
}

View File

@ -2,6 +2,7 @@ package net.minestom.server.network.packet.client.handshake;
import net.minestom.server.extras.bungee.BungeeCordProxy;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.NetworkBufferTemplate;
import net.minestom.server.network.packet.client.ClientPacket;
import org.jetbrains.annotations.NotNull;
@ -11,40 +12,19 @@ public record ClientHandshakePacket(int protocolVersion, @NotNull String serverA
int serverPort, @NotNull Intent intent) implements ClientPacket {
public ClientHandshakePacket {
if (serverAddress.length() > getMaxHandshakeLength()) {
if (serverAddress.length() > maxHandshakeLength()) {
throw new IllegalArgumentException("Server address too long: " + serverAddress.length());
}
}
public static NetworkBuffer.Type<ClientHandshakePacket> SERIALIZER = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, ClientHandshakePacket value) {
buffer.write(VAR_INT, value.protocolVersion);
int maxLength = getMaxHandshakeLength();
if (value.serverAddress.length() > maxLength) {
throw new IllegalArgumentException("serverAddress is " + value.serverAddress.length() + " characters long, maximum allowed is " + maxLength);
}
buffer.write(STRING, value.serverAddress);
buffer.write(UNSIGNED_SHORT, value.serverPort);
// Not a writeEnum call because the indices are not 0-based
buffer.write(VAR_INT, value.intent.id());
}
public static final NetworkBuffer.Type<ClientHandshakePacket> SERIALIZER = NetworkBufferTemplate.template(
VAR_INT, ClientHandshakePacket::protocolVersion,
STRING, ClientHandshakePacket::serverAddress,
UNSIGNED_SHORT, ClientHandshakePacket::serverPort,
VAR_INT.transform(Intent::fromId, Intent::id), ClientHandshakePacket::intent,
ClientHandshakePacket::new);
@Override
public @NotNull ClientHandshakePacket read(@NotNull NetworkBuffer buffer) {
return new ClientHandshakePacket(buffer.read(VAR_INT), buffer.read(STRING),
buffer.read(UNSIGNED_SHORT),
// Not a readEnum call because the indices are not 0-based
Intent.fromId(buffer.read(VAR_INT)));
}
};
@Override
public boolean processImmediately() {
return true;
}
private static int getMaxHandshakeLength() {
private static int maxHandshakeLength() {
// BungeeGuard limits handshake length to 2500 characters, while vanilla limits it to 255
return BungeeCordProxy.isEnabled() ? (BungeeCordProxy.isBungeeGuardEnabled() ? 2500 : Short.MAX_VALUE) : 255;
}

View File

@ -12,9 +12,4 @@ public record ClientEncryptionResponsePacket(byte[] sharedSecret,
BYTE_ARRAY, ClientEncryptionResponsePacket::sharedSecret,
BYTE_ARRAY, ClientEncryptionResponsePacket::encryptedVerifyToken,
ClientEncryptionResponsePacket::new);
@Override
public boolean processImmediately() {
return true;
}
}

Some files were not shown because too many files have changed in this diff Show More