mirror of
https://github.com/Minestom/Minestom.git
synced 2025-02-11 09:51:35 +01:00
Merge branch 'Minestom:master' into instance-based-threading
This commit is contained in:
commit
4f00d7a2cd
@ -46,6 +46,7 @@ import net.minestom.server.timer.Schedulable;
|
|||||||
import net.minestom.server.timer.Scheduler;
|
import net.minestom.server.timer.Scheduler;
|
||||||
import net.minestom.server.timer.TaskSchedule;
|
import net.minestom.server.timer.TaskSchedule;
|
||||||
import net.minestom.server.utils.ArrayUtils;
|
import net.minestom.server.utils.ArrayUtils;
|
||||||
|
import net.minestom.server.utils.MathUtils;
|
||||||
import net.minestom.server.utils.PacketViewableUtils;
|
import net.minestom.server.utils.PacketViewableUtils;
|
||||||
import net.minestom.server.utils.async.AsyncUtils;
|
import net.minestom.server.utils.async.AsyncUtils;
|
||||||
import net.minestom.server.utils.block.BlockIterator;
|
import net.minestom.server.utils.block.BlockIterator;
|
||||||
@ -79,6 +80,10 @@ import java.util.function.UnaryOperator;
|
|||||||
*/
|
*/
|
||||||
public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, EventHandler<EntityEvent>, Taggable,
|
public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, EventHandler<EntityEvent>, Taggable,
|
||||||
HoverEventSource<ShowEntity>, Sound.Emitter, Shape, AcquirableSource<Entity> {
|
HoverEventSource<ShowEntity>, Sound.Emitter, Shape, AcquirableSource<Entity> {
|
||||||
|
// This is somewhat arbitrary, but we don't want to hit the max int ever because it is very easy to
|
||||||
|
// overflow while working with a position at the max int (for example, looping over a bounding box)
|
||||||
|
private static final int MAX_COORDINATE = 2_000_000_000;
|
||||||
|
|
||||||
private static final AtomicInteger LAST_ENTITY_ID = new AtomicInteger();
|
private static final AtomicInteger LAST_ENTITY_ID = new AtomicInteger();
|
||||||
|
|
||||||
// Certain entities should only have their position packets sent during synchronization
|
// Certain entities should only have their position packets sent during synchronization
|
||||||
@ -96,7 +101,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
|||||||
|
|
||||||
protected Instance instance;
|
protected Instance instance;
|
||||||
protected Chunk currentChunk;
|
protected Chunk currentChunk;
|
||||||
protected Pos position;
|
protected Pos position; // Should be updated by setPositionInternal only.
|
||||||
protected Pos previousPosition;
|
protected Pos previousPosition;
|
||||||
protected Pos lastSyncedPosition;
|
protected Pos lastSyncedPosition;
|
||||||
protected boolean onGround;
|
protected boolean onGround;
|
||||||
@ -202,6 +207,19 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
|||||||
this(entityType, UUID.randomUUID());
|
this(entityType, UUID.randomUUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void setPositionInternal(@NotNull Pos newPosition) {
|
||||||
|
if (newPosition.x() >= MAX_COORDINATE || newPosition.x() <= -MAX_COORDINATE ||
|
||||||
|
newPosition.y() >= MAX_COORDINATE || newPosition.y() <= -MAX_COORDINATE ||
|
||||||
|
newPosition.z() >= MAX_COORDINATE || newPosition.z() <= -MAX_COORDINATE) {
|
||||||
|
newPosition = newPosition.withCoord(
|
||||||
|
MathUtils.clamp(newPosition.x(), -MAX_COORDINATE, MAX_COORDINATE),
|
||||||
|
MathUtils.clamp(newPosition.y(), -MAX_COORDINATE, MAX_COORDINATE),
|
||||||
|
MathUtils.clamp(newPosition.z(), -MAX_COORDINATE, MAX_COORDINATE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.position = newPosition;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedules a task to be run during the next entity tick.
|
* Schedules a task to be run during the next entity tick.
|
||||||
*
|
*
|
||||||
@ -325,7 +343,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
|||||||
|
|
||||||
final Runnable endCallback = () -> {
|
final Runnable endCallback = () -> {
|
||||||
this.previousPosition = this.position;
|
this.previousPosition = this.position;
|
||||||
this.position = globalPosition;
|
setPositionInternal(globalPosition);
|
||||||
this.velocity = globalVelocity;
|
this.velocity = globalVelocity;
|
||||||
refreshCoordinate(globalPosition);
|
refreshCoordinate(globalPosition);
|
||||||
if (this instanceof Player player) player.synchronizePositionAfterTeleport(position, velocity, flags, shouldConfirm);
|
if (this instanceof Player player) player.synchronizePositionAfterTeleport(position, velocity, flags, shouldConfirm);
|
||||||
@ -356,7 +374,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
|||||||
public void setView(float yaw, float pitch) {
|
public void setView(float yaw, float pitch) {
|
||||||
final Pos currentPosition = this.position;
|
final Pos currentPosition = this.position;
|
||||||
if (currentPosition.sameView(yaw, pitch)) return;
|
if (currentPosition.sameView(yaw, pitch)) return;
|
||||||
this.position = currentPosition.withView(yaw, pitch);
|
setPositionInternal(currentPosition.withView(yaw, pitch));
|
||||||
synchronizeView();
|
synchronizeView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -785,7 +803,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
|||||||
if (previousInstance != null) removeFromInstance(previousInstance);
|
if (previousInstance != null) removeFromInstance(previousInstance);
|
||||||
|
|
||||||
this.isActive = true;
|
this.isActive = true;
|
||||||
this.position = spawnPosition;
|
setPositionInternal(spawnPosition);
|
||||||
this.previousPosition = spawnPosition;
|
this.previousPosition = spawnPosition;
|
||||||
this.lastSyncedPosition = spawnPosition;
|
this.lastSyncedPosition = spawnPosition;
|
||||||
this.previousPhysicsResult = null;
|
this.previousPhysicsResult = null;
|
||||||
@ -1239,7 +1257,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
|||||||
final var previousPosition = this.position;
|
final var previousPosition = this.position;
|
||||||
final Pos position = ignoreView ? previousPosition.withCoord(newPosition) : newPosition;
|
final Pos position = ignoreView ? previousPosition.withCoord(newPosition) : newPosition;
|
||||||
if (position.equals(lastSyncedPosition)) return;
|
if (position.equals(lastSyncedPosition)) return;
|
||||||
this.position = position;
|
setPositionInternal(position);
|
||||||
this.previousPosition = previousPosition;
|
this.previousPosition = previousPosition;
|
||||||
if (!position.samePoint(previousPosition)) refreshCoordinate(position);
|
if (!position.samePoint(previousPosition)) refreshCoordinate(position);
|
||||||
if (nextSynchronizationTick <= ticks + 1 || !sendPackets) {
|
if (nextSynchronizationTick <= ticks + 1 || !sendPackets) {
|
||||||
@ -1300,7 +1318,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
|||||||
final Pos newPassengerPos = oldPassengerPos.withCoord(newPosition.x(),
|
final Pos newPassengerPos = oldPassengerPos.withCoord(newPosition.x(),
|
||||||
newPosition.y() + EntityUtils.getPassengerHeightOffset(this, passenger),
|
newPosition.y() + EntityUtils.getPassengerHeightOffset(this, passenger),
|
||||||
newPosition.z());
|
newPosition.z());
|
||||||
passenger.position = newPassengerPos;
|
passenger.setPositionInternal(newPassengerPos);
|
||||||
passenger.previousPosition = oldPassengerPos;
|
passenger.previousPosition = oldPassengerPos;
|
||||||
passenger.refreshCoordinate(newPassengerPos);
|
passenger.refreshCoordinate(newPassengerPos);
|
||||||
}
|
}
|
||||||
@ -1477,7 +1495,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
|||||||
this.removed = true;
|
this.removed = true;
|
||||||
if (!permanent) {
|
if (!permanent) {
|
||||||
// Reset some state to be ready for re-use
|
// Reset some state to be ready for re-use
|
||||||
this.position = Pos.ZERO;
|
setPositionInternal(Pos.ZERO);
|
||||||
this.previousPosition = Pos.ZERO;
|
this.previousPosition = Pos.ZERO;
|
||||||
this.lastSyncedPosition = Pos.ZERO;
|
this.lastSyncedPosition = Pos.ZERO;
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,7 @@ public interface Palette {
|
|||||||
if (bitsPerEntry == 0) {
|
if (bitsPerEntry == 0) {
|
||||||
// Single valued 0-0
|
// Single valued 0-0
|
||||||
final int value = buffer.read(VAR_INT);
|
final int value = buffer.read(VAR_INT);
|
||||||
|
buffer.read(VAR_INT); // Skip size
|
||||||
return new PaletteSingle((byte) dimension, value);
|
return new PaletteSingle((byte) dimension, value);
|
||||||
} else if (bitsPerEntry >= minIndirect && bitsPerEntry <= maxIndirect) {
|
} else if (bitsPerEntry >= minIndirect && bitsPerEntry <= maxIndirect) {
|
||||||
// Indirect palette
|
// Indirect palette
|
||||||
|
@ -45,33 +45,33 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
|||||||
|
|
||||||
private void writeInnerComponent(@NotNull NetworkBuffer buffer, @NotNull Component component) {
|
private void writeInnerComponent(@NotNull NetworkBuffer buffer, @NotNull Component component) {
|
||||||
buffer.write(BYTE, TAG_STRING); // Start first tag (always the type)
|
buffer.write(BYTE, TAG_STRING); // Start first tag (always the type)
|
||||||
writeUtf(buffer, "type");
|
buffer.write(STRING_IO_UTF8, "type");
|
||||||
switch (component) {
|
switch (component) {
|
||||||
case TextComponent text -> {
|
case TextComponent text -> {
|
||||||
writeUtf(buffer, "text");
|
buffer.write(STRING_IO_UTF8, "text");
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING); // Start "text" tag
|
buffer.write(BYTE, TAG_STRING); // Start "text" tag
|
||||||
writeUtf(buffer, "text");
|
buffer.write(STRING_IO_UTF8, "text");
|
||||||
writeUtf(buffer, text.content());
|
buffer.write(STRING_IO_UTF8, text.content());
|
||||||
}
|
}
|
||||||
case TranslatableComponent translatable -> {
|
case TranslatableComponent translatable -> {
|
||||||
writeUtf(buffer, "translatable");
|
buffer.write(STRING_IO_UTF8, "translatable");
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING); // Start "translate" tag
|
buffer.write(BYTE, TAG_STRING); // Start "translate" tag
|
||||||
writeUtf(buffer, "translate");
|
buffer.write(STRING_IO_UTF8, "translate");
|
||||||
writeUtf(buffer, translatable.key());
|
buffer.write(STRING_IO_UTF8, translatable.key());
|
||||||
|
|
||||||
final String fallback = translatable.fallback();
|
final String fallback = translatable.fallback();
|
||||||
if (fallback != null) {
|
if (fallback != null) {
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "fallback");
|
buffer.write(STRING_IO_UTF8, "fallback");
|
||||||
writeUtf(buffer, fallback);
|
buffer.write(STRING_IO_UTF8, fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<TranslationArgument> args = translatable.arguments();
|
final List<TranslationArgument> args = translatable.arguments();
|
||||||
if (!args.isEmpty()) {
|
if (!args.isEmpty()) {
|
||||||
buffer.write(BYTE, TAG_LIST);
|
buffer.write(BYTE, TAG_LIST);
|
||||||
writeUtf(buffer, "with");
|
buffer.write(STRING_IO_UTF8, "with");
|
||||||
buffer.write(BYTE, TAG_COMPOUND); // List type
|
buffer.write(BYTE, TAG_COMPOUND); // List type
|
||||||
buffer.write(INT, args.size());
|
buffer.write(INT, args.size());
|
||||||
for (final TranslationArgument arg : args)
|
for (final TranslationArgument arg : args)
|
||||||
@ -79,42 +79,42 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ScoreComponent score -> {
|
case ScoreComponent score -> {
|
||||||
writeUtf(buffer, "score");
|
buffer.write(STRING_IO_UTF8, "score");
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_COMPOUND); // Start "score" tag
|
buffer.write(BYTE, TAG_COMPOUND); // Start "score" tag
|
||||||
writeUtf(buffer, "score");
|
buffer.write(STRING_IO_UTF8, "score");
|
||||||
{
|
{
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "name");
|
buffer.write(STRING_IO_UTF8, "name");
|
||||||
writeUtf(buffer, score.name());
|
buffer.write(STRING_IO_UTF8, score.name());
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "objective");
|
buffer.write(STRING_IO_UTF8, "objective");
|
||||||
writeUtf(buffer, score.objective());
|
buffer.write(STRING_IO_UTF8, score.objective());
|
||||||
}
|
}
|
||||||
buffer.write(BYTE, TAG_END); // End "score" tag
|
buffer.write(BYTE, TAG_END); // End "score" tag
|
||||||
|
|
||||||
}
|
}
|
||||||
case SelectorComponent selector -> {
|
case SelectorComponent selector -> {
|
||||||
writeUtf(buffer, "selector");
|
buffer.write(STRING_IO_UTF8, "selector");
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "selector");
|
buffer.write(STRING_IO_UTF8, "selector");
|
||||||
writeUtf(buffer, selector.pattern());
|
buffer.write(STRING_IO_UTF8, selector.pattern());
|
||||||
|
|
||||||
final Component separator = selector.separator();
|
final Component separator = selector.separator();
|
||||||
if (separator != null) {
|
if (separator != null) {
|
||||||
buffer.write(BYTE, TAG_COMPOUND);
|
buffer.write(BYTE, TAG_COMPOUND);
|
||||||
writeUtf(buffer, "separator");
|
buffer.write(STRING_IO_UTF8, "separator");
|
||||||
writeInnerComponent(buffer, separator);
|
writeInnerComponent(buffer, separator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case KeybindComponent keybind -> {
|
case KeybindComponent keybind -> {
|
||||||
writeUtf(buffer, "keybind");
|
buffer.write(STRING_IO_UTF8, "keybind");
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "keybind");
|
buffer.write(STRING_IO_UTF8, "keybind");
|
||||||
writeUtf(buffer, keybind.keybind());
|
buffer.write(STRING_IO_UTF8, keybind.keybind());
|
||||||
}
|
}
|
||||||
case NBTComponent<?, ?> nbt -> {
|
case NBTComponent<?, ?> nbt -> {
|
||||||
//todo
|
//todo
|
||||||
@ -126,7 +126,7 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
|||||||
// Children
|
// Children
|
||||||
if (!component.children().isEmpty()) {
|
if (!component.children().isEmpty()) {
|
||||||
buffer.write(BYTE, TAG_LIST);
|
buffer.write(BYTE, TAG_LIST);
|
||||||
writeUtf(buffer, "extra");
|
buffer.write(STRING_IO_UTF8, "extra");
|
||||||
buffer.write(BYTE, TAG_COMPOUND); // List type
|
buffer.write(BYTE, TAG_COMPOUND); // List type
|
||||||
|
|
||||||
buffer.write(INT, component.children().size());
|
buffer.write(INT, component.children().size());
|
||||||
@ -144,59 +144,59 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
|||||||
final TextColor color = style.color();
|
final TextColor color = style.color();
|
||||||
if (color != null) {
|
if (color != null) {
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "color");
|
buffer.write(STRING_IO_UTF8, "color");
|
||||||
if (color instanceof NamedTextColor namedColor)
|
if (color instanceof NamedTextColor namedColor)
|
||||||
writeUtf(buffer, namedColor.toString());
|
buffer.write(STRING_IO_UTF8, namedColor.toString());
|
||||||
else writeUtf(buffer, color.asHexString());
|
else buffer.write(STRING_IO_UTF8, color.asHexString());
|
||||||
}
|
}
|
||||||
|
|
||||||
final Key font = style.font();
|
final Key font = style.font();
|
||||||
if (font != null) {
|
if (font != null) {
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "font");
|
buffer.write(STRING_IO_UTF8, "font");
|
||||||
writeUtf(buffer, font.asString());
|
buffer.write(STRING_IO_UTF8, font.asString());
|
||||||
}
|
}
|
||||||
|
|
||||||
final TextDecoration.State bold = style.decoration(TextDecoration.BOLD);
|
final TextDecoration.State bold = style.decoration(TextDecoration.BOLD);
|
||||||
if (bold != TextDecoration.State.NOT_SET) {
|
if (bold != TextDecoration.State.NOT_SET) {
|
||||||
buffer.write(BYTE, TAG_BYTE);
|
buffer.write(BYTE, TAG_BYTE);
|
||||||
writeUtf(buffer, "bold");
|
buffer.write(STRING_IO_UTF8, "bold");
|
||||||
buffer.write(BYTE, bold == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
buffer.write(BYTE, bold == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final TextDecoration.State italic = style.decoration(TextDecoration.ITALIC);
|
final TextDecoration.State italic = style.decoration(TextDecoration.ITALIC);
|
||||||
if (italic != TextDecoration.State.NOT_SET) {
|
if (italic != TextDecoration.State.NOT_SET) {
|
||||||
buffer.write(BYTE, TAG_BYTE);
|
buffer.write(BYTE, TAG_BYTE);
|
||||||
writeUtf(buffer, "italic");
|
buffer.write(STRING_IO_UTF8, "italic");
|
||||||
buffer.write(BYTE, italic == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
buffer.write(BYTE, italic == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final TextDecoration.State underlined = style.decoration(TextDecoration.UNDERLINED);
|
final TextDecoration.State underlined = style.decoration(TextDecoration.UNDERLINED);
|
||||||
if (underlined != TextDecoration.State.NOT_SET) {
|
if (underlined != TextDecoration.State.NOT_SET) {
|
||||||
buffer.write(BYTE, TAG_BYTE);
|
buffer.write(BYTE, TAG_BYTE);
|
||||||
writeUtf(buffer, "underlined");
|
buffer.write(STRING_IO_UTF8, "underlined");
|
||||||
buffer.write(BYTE, underlined == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
buffer.write(BYTE, underlined == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final TextDecoration.State strikethrough = style.decoration(TextDecoration.STRIKETHROUGH);
|
final TextDecoration.State strikethrough = style.decoration(TextDecoration.STRIKETHROUGH);
|
||||||
if (strikethrough != TextDecoration.State.NOT_SET) {
|
if (strikethrough != TextDecoration.State.NOT_SET) {
|
||||||
buffer.write(BYTE, TAG_BYTE);
|
buffer.write(BYTE, TAG_BYTE);
|
||||||
writeUtf(buffer, "strikethrough");
|
buffer.write(STRING_IO_UTF8, "strikethrough");
|
||||||
buffer.write(BYTE, strikethrough == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
buffer.write(BYTE, strikethrough == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final TextDecoration.State obfuscated = style.decoration(TextDecoration.OBFUSCATED);
|
final TextDecoration.State obfuscated = style.decoration(TextDecoration.OBFUSCATED);
|
||||||
if (obfuscated != TextDecoration.State.NOT_SET) {
|
if (obfuscated != TextDecoration.State.NOT_SET) {
|
||||||
buffer.write(BYTE, TAG_BYTE);
|
buffer.write(BYTE, TAG_BYTE);
|
||||||
writeUtf(buffer, "obfuscated");
|
buffer.write(STRING_IO_UTF8, "obfuscated");
|
||||||
buffer.write(BYTE, obfuscated == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
buffer.write(BYTE, obfuscated == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String insertion = style.insertion();
|
final String insertion = style.insertion();
|
||||||
if (insertion != null) {
|
if (insertion != null) {
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "insertion");
|
buffer.write(STRING_IO_UTF8, "insertion");
|
||||||
writeUtf(buffer, insertion);
|
buffer.write(STRING_IO_UTF8, insertion);
|
||||||
}
|
}
|
||||||
|
|
||||||
final ClickEvent clickEvent = style.clickEvent();
|
final ClickEvent clickEvent = style.clickEvent();
|
||||||
@ -208,15 +208,15 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
|||||||
|
|
||||||
private void writeClickEvent(@NotNull NetworkBuffer buffer, @NotNull ClickEvent clickEvent) {
|
private void writeClickEvent(@NotNull NetworkBuffer buffer, @NotNull ClickEvent clickEvent) {
|
||||||
buffer.write(BYTE, TAG_COMPOUND);
|
buffer.write(BYTE, TAG_COMPOUND);
|
||||||
writeUtf(buffer, "clickEvent");
|
buffer.write(STRING_IO_UTF8, "clickEvent");
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "action");
|
buffer.write(STRING_IO_UTF8, "action");
|
||||||
writeUtf(buffer, clickEvent.action().name().toLowerCase(Locale.ROOT));
|
buffer.write(STRING_IO_UTF8, clickEvent.action().name().toLowerCase(Locale.ROOT));
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "value");
|
buffer.write(STRING_IO_UTF8, "value");
|
||||||
writeUtf(buffer, clickEvent.value());
|
buffer.write(STRING_IO_UTF8, clickEvent.value());
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_END);
|
buffer.write(BYTE, TAG_END);
|
||||||
}
|
}
|
||||||
@ -224,29 +224,29 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void writeHoverEvent(@NotNull NetworkBuffer buffer, @NotNull HoverEvent<?> hoverEvent) {
|
private void writeHoverEvent(@NotNull NetworkBuffer buffer, @NotNull HoverEvent<?> hoverEvent) {
|
||||||
buffer.write(BYTE, TAG_COMPOUND);
|
buffer.write(BYTE, TAG_COMPOUND);
|
||||||
writeUtf(buffer, "hoverEvent");
|
buffer.write(STRING_IO_UTF8, "hoverEvent");
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "action");
|
buffer.write(STRING_IO_UTF8, "action");
|
||||||
writeUtf(buffer, hoverEvent.action().toString().toLowerCase(Locale.ROOT));
|
buffer.write(STRING_IO_UTF8, hoverEvent.action().toString().toLowerCase(Locale.ROOT));
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_COMPOUND); // Start contents tag
|
buffer.write(BYTE, TAG_COMPOUND); // Start contents tag
|
||||||
writeUtf(buffer, "contents");
|
buffer.write(STRING_IO_UTF8, "contents");
|
||||||
if (hoverEvent.action() == HoverEvent.Action.SHOW_TEXT) {
|
if (hoverEvent.action() == HoverEvent.Action.SHOW_TEXT) {
|
||||||
writeInnerComponent(buffer, (Component) hoverEvent.value());
|
writeInnerComponent(buffer, (Component) hoverEvent.value());
|
||||||
} else if (hoverEvent.action() == HoverEvent.Action.SHOW_ITEM) {
|
} else if (hoverEvent.action() == HoverEvent.Action.SHOW_ITEM) {
|
||||||
var value = ((HoverEvent<HoverEvent.ShowItem>) hoverEvent).value();
|
var value = ((HoverEvent<HoverEvent.ShowItem>) hoverEvent).value();
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "id");
|
buffer.write(STRING_IO_UTF8, "id");
|
||||||
writeUtf(buffer, value.item().asString());
|
buffer.write(STRING_IO_UTF8, value.item().asString());
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_INT);
|
buffer.write(BYTE, TAG_INT);
|
||||||
writeUtf(buffer, "count");
|
buffer.write(STRING_IO_UTF8, "count");
|
||||||
buffer.write(INT, value.count());
|
buffer.write(INT, value.count());
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_COMPOUND);
|
buffer.write(BYTE, TAG_COMPOUND);
|
||||||
writeUtf(buffer, "components");
|
buffer.write(STRING_IO_UTF8, "components");
|
||||||
//todo item components
|
//todo item components
|
||||||
buffer.write(BYTE, TAG_END);
|
buffer.write(BYTE, TAG_END);
|
||||||
|
|
||||||
@ -257,17 +257,17 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
|||||||
final Component name = value.name();
|
final Component name = value.name();
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
buffer.write(BYTE, TAG_COMPOUND);
|
buffer.write(BYTE, TAG_COMPOUND);
|
||||||
writeUtf(buffer, "name");
|
buffer.write(STRING_IO_UTF8, "name");
|
||||||
writeInnerComponent(buffer, name);
|
writeInnerComponent(buffer, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "type");
|
buffer.write(STRING_IO_UTF8, "type");
|
||||||
writeUtf(buffer, value.type().asString());
|
buffer.write(STRING_IO_UTF8, value.type().asString());
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_STRING);
|
buffer.write(BYTE, TAG_STRING);
|
||||||
writeUtf(buffer, "id");
|
buffer.write(STRING_IO_UTF8, "id");
|
||||||
writeUtf(buffer, value.id().toString());
|
buffer.write(STRING_IO_UTF8, value.id().toString());
|
||||||
|
|
||||||
buffer.write(BYTE, TAG_END); // End contents tag
|
buffer.write(BYTE, TAG_END); // End contents tag
|
||||||
} else {
|
} else {
|
||||||
@ -276,53 +276,4 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
|||||||
|
|
||||||
buffer.write(BYTE, TAG_END);
|
buffer.write(BYTE, TAG_END);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a very gross version of {@link java.io.DataOutputStream#writeUTF(String)}. We need the data in the java
|
|
||||||
* modified utf-8 format, and I couldnt find a method without creating a new buffer for it.
|
|
||||||
*
|
|
||||||
* @param buffer the buffer to write to
|
|
||||||
* @param str the string to write
|
|
||||||
*/
|
|
||||||
private static void writeUtf(@NotNull NetworkBuffer buffer, @NotNull String str) {
|
|
||||||
final int strlen = str.length();
|
|
||||||
int utflen = strlen; // optimized for ASCII
|
|
||||||
|
|
||||||
for (int i = 0; i < strlen; i++) {
|
|
||||||
int c = str.charAt(i);
|
|
||||||
if (c >= 0x80 || c == 0)
|
|
||||||
utflen += (c >= 0x800) ? 2 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (utflen > 65535 || /* overflow */ utflen < strlen)
|
|
||||||
throw new RuntimeException("UTF-8 string too long");
|
|
||||||
|
|
||||||
buffer.write(SHORT, (short) utflen);
|
|
||||||
buffer.ensureWritable(utflen);
|
|
||||||
var impl = (NetworkBufferImpl) buffer;
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < strlen; i++) { // optimized for initial run of ASCII
|
|
||||||
int c = str.charAt(i);
|
|
||||||
if (c >= 0x80 || c == 0) break;
|
|
||||||
impl._putByte(buffer.writeIndex(), (byte) c);
|
|
||||||
impl.advanceWrite(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (; i < strlen; i++) {
|
|
||||||
int c = str.charAt(i);
|
|
||||||
if (c < 0x80 && c != 0) {
|
|
||||||
impl._putByte(buffer.writeIndex(), (byte) c);
|
|
||||||
impl.advanceWrite(1);
|
|
||||||
} else if (c >= 0x800) {
|
|
||||||
impl._putByte(buffer.writeIndex(), (byte) (0xE0 | ((c >> 12) & 0x0F)));
|
|
||||||
impl._putByte(buffer.writeIndex() + 1, (byte) (0x80 | ((c >> 6) & 0x3F)));
|
|
||||||
impl._putByte(buffer.writeIndex() + 2, (byte) (0x80 | ((c >> 0) & 0x3F)));
|
|
||||||
impl.advanceWrite(3);
|
|
||||||
} else {
|
|
||||||
impl._putByte(buffer.writeIndex(), (byte) (0xC0 | ((c >> 6) & 0x1F)));
|
|
||||||
impl._putByte(buffer.writeIndex() + 1, (byte) (0x80 | ((c >> 0) & 0x3F)));
|
|
||||||
impl.advanceWrite(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ public sealed interface NetworkBuffer permits NetworkBufferImpl {
|
|||||||
Type<byte[]> RAW_BYTES = new NetworkBufferTypeImpl.RawBytesType(-1);
|
Type<byte[]> RAW_BYTES = new NetworkBufferTypeImpl.RawBytesType(-1);
|
||||||
Type<String> STRING = new NetworkBufferTypeImpl.StringType();
|
Type<String> STRING = new NetworkBufferTypeImpl.StringType();
|
||||||
Type<String> STRING_TERMINATED = new NetworkBufferTypeImpl.StringTerminatedType();
|
Type<String> STRING_TERMINATED = new NetworkBufferTypeImpl.StringTerminatedType();
|
||||||
|
Type<String> STRING_IO_UTF8 = new NetworkBufferTypeImpl.IOUTF8StringType();
|
||||||
Type<BinaryTag> NBT = new NetworkBufferTypeImpl.NbtType();
|
Type<BinaryTag> NBT = new NetworkBufferTypeImpl.NbtType();
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
Type<CompoundBinaryTag> NBT_COMPOUND = (Type) new NetworkBufferTypeImpl.NbtType();
|
Type<CompoundBinaryTag> NBT_COMPOUND = (Type) new NetworkBufferTypeImpl.NbtType();
|
||||||
|
@ -819,6 +819,123 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a very gross version of {@link java.io.DataOutputStream#writeUTF(String)} & ${@link DataInputStream#readUTF()}. We need the data in the java
|
||||||
|
* modified utf-8 format for Component, and I couldnt find a method without creating a new buffer for it.
|
||||||
|
*/
|
||||||
|
record IOUTF8StringType() implements NetworkBufferTypeImpl<String> {
|
||||||
|
@Override
|
||||||
|
public void write(@NotNull NetworkBuffer buffer, String value) {
|
||||||
|
final int strlen = value.length();
|
||||||
|
int utflen = strlen; // optimized for ASCII
|
||||||
|
|
||||||
|
for (int i = 0; i < strlen; i++) {
|
||||||
|
int c = value.charAt(i);
|
||||||
|
if (c >= 0x80 || c == 0)
|
||||||
|
utflen += (c >= 0x800) ? 2 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (utflen > 65535 || /* overflow */ utflen < strlen)
|
||||||
|
throw new RuntimeException("UTF-8 string too long");
|
||||||
|
|
||||||
|
buffer.write(SHORT, (short) utflen);
|
||||||
|
buffer.ensureWritable(utflen);
|
||||||
|
var impl = (NetworkBufferImpl) buffer;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < strlen; i++) { // optimized for initial run of ASCII
|
||||||
|
int c = value.charAt(i);
|
||||||
|
if (c >= 0x80 || c == 0) break;
|
||||||
|
impl._putByte(buffer.writeIndex(), (byte) c);
|
||||||
|
impl.advanceWrite(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; i < strlen; i++) {
|
||||||
|
int c = value.charAt(i);
|
||||||
|
if (c < 0x80 && c != 0) {
|
||||||
|
impl._putByte(buffer.writeIndex(), (byte) c);
|
||||||
|
impl.advanceWrite(1);
|
||||||
|
} else if (c >= 0x800) {
|
||||||
|
impl._putByte(buffer.writeIndex(), (byte) (0xE0 | ((c >> 12) & 0x0F)));
|
||||||
|
impl._putByte(buffer.writeIndex() + 1, (byte) (0x80 | ((c >> 6) & 0x3F)));
|
||||||
|
impl._putByte(buffer.writeIndex() + 2, (byte) (0x80 | ((c >> 0) & 0x3F)));
|
||||||
|
impl.advanceWrite(3);
|
||||||
|
} else {
|
||||||
|
impl._putByte(buffer.writeIndex(), (byte) (0xC0 | ((c >> 6) & 0x1F)));
|
||||||
|
impl._putByte(buffer.writeIndex() + 1, (byte) (0x80 | ((c >> 0) & 0x3F)));
|
||||||
|
impl.advanceWrite(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String read(@NotNull NetworkBuffer buffer) {
|
||||||
|
int utflen = buffer.read(UNSIGNED_SHORT);
|
||||||
|
if (buffer.readableBytes() < utflen) throw new IllegalArgumentException("Invalid String size.");
|
||||||
|
byte[] bytearr = buffer.read(RAW_BYTES);
|
||||||
|
final char[] chararr = new char[utflen];
|
||||||
|
|
||||||
|
int c, char2, char3;
|
||||||
|
int count = 0;
|
||||||
|
int chararr_count = 0;
|
||||||
|
|
||||||
|
while (count < utflen) {
|
||||||
|
c = (int) bytearr[count] & 0xff;
|
||||||
|
if (c > 127) break;
|
||||||
|
count++;
|
||||||
|
chararr[chararr_count++] = (char) c;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (count < utflen) {
|
||||||
|
c = (int) bytearr[count] & 0xff;
|
||||||
|
try { // Surround in try catch to throw a runtime exception instead of a checked one
|
||||||
|
switch (c >> 4) {
|
||||||
|
case 0, 1, 2, 3, 4, 5, 6, 7 -> {
|
||||||
|
/* 0xxxxxxx*/
|
||||||
|
count++;
|
||||||
|
chararr[chararr_count++] = (char) c;
|
||||||
|
}
|
||||||
|
case 12, 13 -> {
|
||||||
|
/* 110x xxxx 10xx xxxx*/
|
||||||
|
count += 2;
|
||||||
|
if (count > utflen)
|
||||||
|
throw new UTFDataFormatException(
|
||||||
|
"malformed input: partial character at end");
|
||||||
|
char2 = bytearr[count - 1];
|
||||||
|
if ((char2 & 0xC0) != 0x80)
|
||||||
|
throw new UTFDataFormatException(
|
||||||
|
"malformed input around byte " + count);
|
||||||
|
chararr[chararr_count++] = (char) (((c & 0x1F) << 6) |
|
||||||
|
(char2 & 0x3F));
|
||||||
|
}
|
||||||
|
case 14 -> {
|
||||||
|
/* 1110 xxxx 10xx xxxx 10xx xxxx */
|
||||||
|
count += 3;
|
||||||
|
if (count > utflen)
|
||||||
|
throw new UTFDataFormatException(
|
||||||
|
"malformed input: partial character at end");
|
||||||
|
char2 = bytearr[count - 2];
|
||||||
|
char3 = bytearr[count - 1];
|
||||||
|
if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
|
||||||
|
throw new UTFDataFormatException(
|
||||||
|
"malformed input around byte " + (count - 1));
|
||||||
|
chararr[chararr_count++] = (char) (((c & 0x0F) << 12) |
|
||||||
|
((char2 & 0x3F) << 6) |
|
||||||
|
((char3 & 0x3F) << 0));
|
||||||
|
}
|
||||||
|
default ->
|
||||||
|
/* 10xx xxxx, 1111 xxxx */
|
||||||
|
throw new UTFDataFormatException(
|
||||||
|
"malformed input around byte " + count);
|
||||||
|
}
|
||||||
|
} catch (UTFDataFormatException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The number of chars produced may be less than utflen
|
||||||
|
return new String(chararr, 0, chararr_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static <T> long sizeOf(Type<T> type, T value, Registries registries) {
|
static <T> long sizeOf(Type<T> type, T value, Registries registries) {
|
||||||
NetworkBuffer buffer = NetworkBufferImpl.dummy(registries);
|
NetworkBuffer buffer = NetworkBufferImpl.dummy(registries);
|
||||||
type.write(buffer, value);
|
type.write(buffer, value);
|
||||||
|
@ -202,6 +202,7 @@ public final class PacketWriting {
|
|||||||
if (written < minWrite) {
|
if (written < minWrite) {
|
||||||
// Try again with a bigger buffer
|
// Try again with a bigger buffer
|
||||||
final long newSize = Math.min(buffer.capacity() * 2, ServerFlag.MAX_PACKET_SIZE);
|
final long newSize = Math.min(buffer.capacity() * 2, ServerFlag.MAX_PACKET_SIZE);
|
||||||
|
if (newSize == buffer.capacity()) break; // We reached the maximum size
|
||||||
buffer.resize(newSize);
|
buffer.resize(newSize);
|
||||||
} else {
|
} else {
|
||||||
// At least one packet has been written
|
// At least one packet has been written
|
||||||
|
@ -141,9 +141,9 @@ public abstract class PlayerConnection {
|
|||||||
*/
|
*/
|
||||||
public void disconnect() {
|
public void disconnect() {
|
||||||
this.online = false;
|
this.online = false;
|
||||||
MinecraftServer.getConnectionManager().removePlayer(this);
|
final Player player = MinecraftServer.getConnectionManager().getPlayer(this);
|
||||||
final Player player = getPlayer();
|
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
|
MinecraftServer.getConnectionManager().removePlayer(this);
|
||||||
if (connectionState == ConnectionState.PLAY && !player.isRemoved())
|
if (connectionState == ConnectionState.PLAY && !player.isRemoved())
|
||||||
player.scheduleNextTick(Entity::remove);
|
player.scheduleNextTick(Entity::remove);
|
||||||
else {
|
else {
|
||||||
|
@ -8,6 +8,11 @@ import net.minestom.testing.Env;
|
|||||||
import net.minestom.testing.EnvTest;
|
import net.minestom.testing.EnvTest;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
@EnvTest
|
@EnvTest
|
||||||
@ -99,4 +104,21 @@ public class EntityTeleportIntegrationTest {
|
|||||||
player.teleport(new Pos(5, 10, 2, 5, 5), null, RelativeFlags.VIEW).join();
|
player.teleport(new Pos(5, 10, 2, 5, 5), null, RelativeFlags.VIEW).join();
|
||||||
assertEquals(player.getPosition(), new Pos(5, 10, 2, 95, 5));
|
assertEquals(player.getPosition(), new Pos(5, 10, 2, 95, 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void entityTeleportToInfinity(Env env) throws ExecutionException, InterruptedException, TimeoutException {
|
||||||
|
var instance = env.createFlatInstance();
|
||||||
|
var entity = new Entity(EntityTypes.ZOMBIE);
|
||||||
|
entity.setInstance(instance, new Pos(0, 42, 0)).join();
|
||||||
|
assertEquals(instance, entity.getInstance());
|
||||||
|
assertEquals(new Pos(0, 42, 0), entity.getPosition());
|
||||||
|
|
||||||
|
entity.teleport(new Pos(Double.POSITIVE_INFINITY, 42, 52)).join();
|
||||||
|
CompletableFuture.runAsync(() -> entity.tick(System.currentTimeMillis()))
|
||||||
|
.get(10, TimeUnit.SECONDS);
|
||||||
|
// This should not hang forever
|
||||||
|
|
||||||
|
// The position should have been capped at 2 billion.
|
||||||
|
assertEquals(new Pos(2_000_000_000, 42, 52), entity.getPosition());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,8 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.jetbrains.annotations.UnknownNullability;
|
import org.jetbrains.annotations.UnknownNullability;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UTFDataFormatException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@ -474,6 +476,62 @@ public class NetworkBufferTest {
|
|||||||
assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING)); // oom
|
assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING)); // oom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void oomStringUtf8Regression() {
|
||||||
|
var buffer = NetworkBuffer.resizableBuffer(100);
|
||||||
|
buffer.write(UNSIGNED_SHORT, 65535); // String length
|
||||||
|
buffer.write(RAW_BYTES, "Hello".getBytes(StandardCharsets.UTF_8)); // String data
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING)); // oom
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStringUtf8ModifiedWrite() throws IOException {
|
||||||
|
var stream = new java.io.ByteArrayOutputStream();
|
||||||
|
java.io.DataOutputStream out = new java.io.DataOutputStream(stream);
|
||||||
|
out.writeUTF("Hello");
|
||||||
|
|
||||||
|
assertBufferType(STRING_IO_UTF8, "Hello", stream.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStringUtf8ModifiedRead() throws IOException {
|
||||||
|
var stream = new java.io.ByteArrayOutputStream();
|
||||||
|
java.io.DataOutputStream out = new java.io.DataOutputStream(stream);
|
||||||
|
out.writeUTF("Hello");
|
||||||
|
var buffer = NetworkBuffer.wrap(stream.toByteArray(), 0, stream.size());
|
||||||
|
assertEquals("Hello", buffer.read(STRING_IO_UTF8));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void oomStringUtf8ModfiedRegression() throws IOException {
|
||||||
|
var buffer = NetworkBuffer.resizableBuffer(100);
|
||||||
|
buffer.write(UNSIGNED_SHORT, 65535); // String length
|
||||||
|
// Write the raw bytes that are invalid
|
||||||
|
buffer.write(RAW_BYTES, new byte[]{(byte) 0xC0, (byte) 0x80}); // Invalid UTF-8
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING_IO_UTF8)); // oom
|
||||||
|
buffer.clear();
|
||||||
|
|
||||||
|
var stream = new java.io.ByteArrayOutputStream();
|
||||||
|
java.io.DataOutputStream out = new java.io.DataOutputStream(stream);
|
||||||
|
out.writeUTF("Hello");
|
||||||
|
var byteArray = stream.toByteArray();
|
||||||
|
|
||||||
|
// Mess with the length to 0
|
||||||
|
byteArray[0] = (byte) 0x00;
|
||||||
|
byteArray[1] = (byte) 0x00;
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING_IO_UTF8)); // oom
|
||||||
|
|
||||||
|
buffer.clear();
|
||||||
|
buffer.write(UNSIGNED_SHORT, 5);
|
||||||
|
buffer.write(RAW_BYTES, new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}); // Invalid utf8
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING_IO_UTF8)); // oom
|
||||||
|
}
|
||||||
|
|
||||||
static <T> void assertBufferType(NetworkBuffer.@NotNull Type<T> type, @UnknownNullability T value, byte[] expected, @NotNull Action<T> action) {
|
static <T> void assertBufferType(NetworkBuffer.@NotNull Type<T> type, @UnknownNullability T value, byte[] expected, @NotNull Action<T> action) {
|
||||||
var buffer = NetworkBuffer.resizableBuffer(MinecraftServer.process());
|
var buffer = NetworkBuffer.resizableBuffer(MinecraftServer.process());
|
||||||
action.write(buffer, type, value);
|
action.write(buffer, type, value);
|
||||||
|
Loading…
Reference in New Issue
Block a user