package net.minestom.server.network; import net.kyori.adventure.text.Component; import net.minestom.server.coordinate.Point; import net.minestom.server.entity.Entity; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.server.play.data.DeathLocation; import net.minestom.server.utils.Direction; import net.minestom.server.utils.Either; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jglrxavpok.hephaistos.nbt.NBT; import org.jglrxavpok.hephaistos.nbt.NBTReader; import org.jglrxavpok.hephaistos.nbt.NBTWriter; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Collection; import java.util.List; import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; @ApiStatus.Experimental public final class NetworkBuffer { public static final Type BOOLEAN = NetworkBufferTypes.BOOLEAN; public static final Type BYTE = NetworkBufferTypes.BYTE; public static final Type SHORT = NetworkBufferTypes.SHORT; public static final Type UNSIGNED_SHORT = NetworkBufferTypes.UNSIGNED_SHORT; public static final Type INT = NetworkBufferTypes.INT; public static final Type LONG = NetworkBufferTypes.LONG; public static final Type FLOAT = NetworkBufferTypes.FLOAT; public static final Type DOUBLE = NetworkBufferTypes.DOUBLE; public static final Type VAR_INT = NetworkBufferTypes.VAR_INT; public static final Type VAR_LONG = NetworkBufferTypes.VAR_LONG; public static final Type RAW_BYTES = NetworkBufferTypes.RAW_BYTES; public static final Type STRING = NetworkBufferTypes.STRING; public static final Type NBT = NetworkBufferTypes.NBT; public static final Type BLOCK_POSITION = NetworkBufferTypes.BLOCK_POSITION; public static final Type COMPONENT = NetworkBufferTypes.COMPONENT; public static final Type UUID = NetworkBufferTypes.UUID; public static final Type ITEM = NetworkBufferTypes.ITEM; public static final Type BYTE_ARRAY = NetworkBufferTypes.BYTE_ARRAY; public static final Type LONG_ARRAY = NetworkBufferTypes.LONG_ARRAY; public static final Type VAR_INT_ARRAY = NetworkBufferTypes.VAR_INT_ARRAY; public static final Type VAR_LONG_ARRAY = NetworkBufferTypes.VAR_LONG_ARRAY; // METADATA public static final Type OPT_CHAT = NetworkBufferTypes.OPT_CHAT; public static final Type ROTATION = NetworkBufferTypes.ROTATION; public static final Type OPT_BLOCK_POSITION = NetworkBufferTypes.OPT_BLOCK_POSITION; public static final Type DIRECTION = NetworkBufferTypes.DIRECTION; public static final Type OPT_UUID = NetworkBufferTypes.OPT_UUID; public static final Type OPT_BLOCK_ID = NetworkBufferTypes.OPT_BLOCK_ID; public static final Type VILLAGER_DATA = NetworkBufferTypes.VILLAGER_DATA; public static final Type OPT_VAR_INT = NetworkBufferTypes.OPT_VAR_INT; public static final Type POSE = NetworkBufferTypes.POSE; public static final Type DEATH_LOCATION = NetworkBufferTypes.DEATH_LOCATION; ByteBuffer nioBuffer; final boolean resizable; int writeIndex; int readIndex; NBTWriter nbtWriter; NBTReader nbtReader; public NetworkBuffer(@NotNull ByteBuffer buffer, boolean resizable) { this.nioBuffer = buffer.order(ByteOrder.BIG_ENDIAN); this.resizable = resizable; this.writeIndex = buffer.position(); this.readIndex = buffer.position(); } public NetworkBuffer(@NotNull ByteBuffer buffer) { this(buffer, true); } public NetworkBuffer(int initialCapacity) { this(ByteBuffer.allocateDirect(initialCapacity), true); } public NetworkBuffer() { this(1024); } public void write(@NotNull Type type, @NotNull T value) { var impl = (NetworkBufferTypes.TypeImpl) type; final long length = impl.writer().write(this, value); if (length != -1) this.writeIndex += length; } public void write(@NotNull Writer writer) { writer.write(this); } public @NotNull T read(@NotNull Type type) { var impl = (NetworkBufferTypes.TypeImpl) type; return impl.reader().read(this); } public void writeOptional(@NotNull Type 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 @Nullable T readOptional(@NotNull Type type) { return read(BOOLEAN) ? read(type) : null; } public @Nullable T readOptional(@NotNull Function<@NotNull NetworkBuffer, @NotNull T> function) { return read(BOOLEAN) ? function.apply(this) : null; } public void writeCollection(@NotNull Type 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 void writeCollection(@NotNull Type type, @NotNull T @Nullable ... values) { writeCollection(type, values == null ? null : List.of(values)); } public 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 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 @NotNull List<@NotNull T> readCollection(@NotNull Type type) { final int size = read(VAR_INT); final List values = new java.util.ArrayList<>(size); for (int i = 0; i < size; i++) { values.add(read(type)); } return values; } public @NotNull List<@NotNull T> readCollection(@NotNull Function<@NotNull NetworkBuffer, @NotNull T> function) { final int size = read(VAR_INT); final List values = new java.util.ArrayList<>(size); for (int i = 0; i < size; i++) { values.add(function.apply(this)); } return values; } public void writeEither(Either either, BiConsumer leftWriter, BiConsumer rightWriter) { if (either.isLeft()) { write(BOOLEAN, true); leftWriter.accept(this, either.left()); } else { write(BOOLEAN, false); rightWriter.accept(this, either.right()); } } public @NotNull Either readEither(@NotNull Function leftReader, Function rightReader) { if (read(BOOLEAN)) { return Either.left(leftReader.apply(this)); } else { return Either.right(rightReader.apply(this)); } } public > void writeEnum(@NotNull Class enumClass, @NotNull E value) { write(VAR_INT, value.ordinal()); } public > @NotNull E readEnum(@NotNull Class<@NotNull E> enumClass) { return enumClass.getEnumConstants()[read(VAR_INT)]; } 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 sealed interface Type permits NetworkBufferTypes.TypeImpl { } @FunctionalInterface public interface Writer { void write(@NotNull NetworkBuffer writer); } 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; } }