mirror of
https://github.com/Minestom/Minestom.git
synced 2025-02-11 01:41:47 +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.TaskSchedule;
|
||||
import net.minestom.server.utils.ArrayUtils;
|
||||
import net.minestom.server.utils.MathUtils;
|
||||
import net.minestom.server.utils.PacketViewableUtils;
|
||||
import net.minestom.server.utils.async.AsyncUtils;
|
||||
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,
|
||||
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();
|
||||
|
||||
// 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 Chunk currentChunk;
|
||||
protected Pos position;
|
||||
protected Pos position; // Should be updated by setPositionInternal only.
|
||||
protected Pos previousPosition;
|
||||
protected Pos lastSyncedPosition;
|
||||
protected boolean onGround;
|
||||
@ -202,6 +207,19 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
||||
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.
|
||||
*
|
||||
@ -325,7 +343,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
||||
|
||||
final Runnable endCallback = () -> {
|
||||
this.previousPosition = this.position;
|
||||
this.position = globalPosition;
|
||||
setPositionInternal(globalPosition);
|
||||
this.velocity = globalVelocity;
|
||||
refreshCoordinate(globalPosition);
|
||||
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) {
|
||||
final Pos currentPosition = this.position;
|
||||
if (currentPosition.sameView(yaw, pitch)) return;
|
||||
this.position = currentPosition.withView(yaw, pitch);
|
||||
setPositionInternal(currentPosition.withView(yaw, pitch));
|
||||
synchronizeView();
|
||||
}
|
||||
|
||||
@ -785,7 +803,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
||||
if (previousInstance != null) removeFromInstance(previousInstance);
|
||||
|
||||
this.isActive = true;
|
||||
this.position = spawnPosition;
|
||||
setPositionInternal(spawnPosition);
|
||||
this.previousPosition = spawnPosition;
|
||||
this.lastSyncedPosition = spawnPosition;
|
||||
this.previousPhysicsResult = null;
|
||||
@ -1239,7 +1257,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
||||
final var previousPosition = this.position;
|
||||
final Pos position = ignoreView ? previousPosition.withCoord(newPosition) : newPosition;
|
||||
if (position.equals(lastSyncedPosition)) return;
|
||||
this.position = position;
|
||||
setPositionInternal(position);
|
||||
this.previousPosition = previousPosition;
|
||||
if (!position.samePoint(previousPosition)) refreshCoordinate(position);
|
||||
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(),
|
||||
newPosition.y() + EntityUtils.getPassengerHeightOffset(this, passenger),
|
||||
newPosition.z());
|
||||
passenger.position = newPassengerPos;
|
||||
passenger.setPositionInternal(newPassengerPos);
|
||||
passenger.previousPosition = oldPassengerPos;
|
||||
passenger.refreshCoordinate(newPassengerPos);
|
||||
}
|
||||
@ -1477,7 +1495,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
||||
this.removed = true;
|
||||
if (!permanent) {
|
||||
// Reset some state to be ready for re-use
|
||||
this.position = Pos.ZERO;
|
||||
setPositionInternal(Pos.ZERO);
|
||||
this.previousPosition = Pos.ZERO;
|
||||
this.lastSyncedPosition = Pos.ZERO;
|
||||
}
|
||||
|
@ -115,6 +115,7 @@ public interface Palette {
|
||||
if (bitsPerEntry == 0) {
|
||||
// Single valued 0-0
|
||||
final int value = buffer.read(VAR_INT);
|
||||
buffer.read(VAR_INT); // Skip size
|
||||
return new PaletteSingle((byte) dimension, value);
|
||||
} else if (bitsPerEntry >= minIndirect && bitsPerEntry <= maxIndirect) {
|
||||
// Indirect palette
|
||||
|
@ -45,33 +45,33 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
||||
|
||||
private void writeInnerComponent(@NotNull NetworkBuffer buffer, @NotNull Component component) {
|
||||
buffer.write(BYTE, TAG_STRING); // Start first tag (always the type)
|
||||
writeUtf(buffer, "type");
|
||||
buffer.write(STRING_IO_UTF8, "type");
|
||||
switch (component) {
|
||||
case TextComponent text -> {
|
||||
writeUtf(buffer, "text");
|
||||
buffer.write(STRING_IO_UTF8, "text");
|
||||
|
||||
buffer.write(BYTE, TAG_STRING); // Start "text" tag
|
||||
writeUtf(buffer, "text");
|
||||
writeUtf(buffer, text.content());
|
||||
buffer.write(STRING_IO_UTF8, "text");
|
||||
buffer.write(STRING_IO_UTF8, text.content());
|
||||
}
|
||||
case TranslatableComponent translatable -> {
|
||||
writeUtf(buffer, "translatable");
|
||||
buffer.write(STRING_IO_UTF8, "translatable");
|
||||
|
||||
buffer.write(BYTE, TAG_STRING); // Start "translate" tag
|
||||
writeUtf(buffer, "translate");
|
||||
writeUtf(buffer, translatable.key());
|
||||
buffer.write(STRING_IO_UTF8, "translate");
|
||||
buffer.write(STRING_IO_UTF8, translatable.key());
|
||||
|
||||
final String fallback = translatable.fallback();
|
||||
if (fallback != null) {
|
||||
buffer.write(BYTE, TAG_STRING);
|
||||
writeUtf(buffer, "fallback");
|
||||
writeUtf(buffer, fallback);
|
||||
buffer.write(STRING_IO_UTF8, "fallback");
|
||||
buffer.write(STRING_IO_UTF8, fallback);
|
||||
}
|
||||
|
||||
final List<TranslationArgument> args = translatable.arguments();
|
||||
if (!args.isEmpty()) {
|
||||
buffer.write(BYTE, TAG_LIST);
|
||||
writeUtf(buffer, "with");
|
||||
buffer.write(STRING_IO_UTF8, "with");
|
||||
buffer.write(BYTE, TAG_COMPOUND); // List type
|
||||
buffer.write(INT, args.size());
|
||||
for (final TranslationArgument arg : args)
|
||||
@ -79,42 +79,42 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
||||
}
|
||||
}
|
||||
case ScoreComponent score -> {
|
||||
writeUtf(buffer, "score");
|
||||
buffer.write(STRING_IO_UTF8, "score");
|
||||
|
||||
buffer.write(BYTE, TAG_COMPOUND); // Start "score" tag
|
||||
writeUtf(buffer, "score");
|
||||
buffer.write(STRING_IO_UTF8, "score");
|
||||
{
|
||||
buffer.write(BYTE, TAG_STRING);
|
||||
writeUtf(buffer, "name");
|
||||
writeUtf(buffer, score.name());
|
||||
buffer.write(STRING_IO_UTF8, "name");
|
||||
buffer.write(STRING_IO_UTF8, score.name());
|
||||
|
||||
buffer.write(BYTE, TAG_STRING);
|
||||
writeUtf(buffer, "objective");
|
||||
writeUtf(buffer, score.objective());
|
||||
buffer.write(STRING_IO_UTF8, "objective");
|
||||
buffer.write(STRING_IO_UTF8, score.objective());
|
||||
}
|
||||
buffer.write(BYTE, TAG_END); // End "score" tag
|
||||
|
||||
}
|
||||
case SelectorComponent selector -> {
|
||||
writeUtf(buffer, "selector");
|
||||
buffer.write(STRING_IO_UTF8, "selector");
|
||||
|
||||
buffer.write(BYTE, TAG_STRING);
|
||||
writeUtf(buffer, "selector");
|
||||
writeUtf(buffer, selector.pattern());
|
||||
buffer.write(STRING_IO_UTF8, "selector");
|
||||
buffer.write(STRING_IO_UTF8, selector.pattern());
|
||||
|
||||
final Component separator = selector.separator();
|
||||
if (separator != null) {
|
||||
buffer.write(BYTE, TAG_COMPOUND);
|
||||
writeUtf(buffer, "separator");
|
||||
buffer.write(STRING_IO_UTF8, "separator");
|
||||
writeInnerComponent(buffer, separator);
|
||||
}
|
||||
}
|
||||
case KeybindComponent keybind -> {
|
||||
writeUtf(buffer, "keybind");
|
||||
buffer.write(STRING_IO_UTF8, "keybind");
|
||||
|
||||
buffer.write(BYTE, TAG_STRING);
|
||||
writeUtf(buffer, "keybind");
|
||||
writeUtf(buffer, keybind.keybind());
|
||||
buffer.write(STRING_IO_UTF8, "keybind");
|
||||
buffer.write(STRING_IO_UTF8, keybind.keybind());
|
||||
}
|
||||
case NBTComponent<?, ?> nbt -> {
|
||||
//todo
|
||||
@ -126,7 +126,7 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
||||
// Children
|
||||
if (!component.children().isEmpty()) {
|
||||
buffer.write(BYTE, TAG_LIST);
|
||||
writeUtf(buffer, "extra");
|
||||
buffer.write(STRING_IO_UTF8, "extra");
|
||||
buffer.write(BYTE, TAG_COMPOUND); // List type
|
||||
|
||||
buffer.write(INT, component.children().size());
|
||||
@ -144,59 +144,59 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
||||
final TextColor color = style.color();
|
||||
if (color != null) {
|
||||
buffer.write(BYTE, TAG_STRING);
|
||||
writeUtf(buffer, "color");
|
||||
buffer.write(STRING_IO_UTF8, "color");
|
||||
if (color instanceof NamedTextColor namedColor)
|
||||
writeUtf(buffer, namedColor.toString());
|
||||
else writeUtf(buffer, color.asHexString());
|
||||
buffer.write(STRING_IO_UTF8, namedColor.toString());
|
||||
else buffer.write(STRING_IO_UTF8, color.asHexString());
|
||||
}
|
||||
|
||||
final Key font = style.font();
|
||||
if (font != null) {
|
||||
buffer.write(BYTE, TAG_STRING);
|
||||
writeUtf(buffer, "font");
|
||||
writeUtf(buffer, font.asString());
|
||||
buffer.write(STRING_IO_UTF8, "font");
|
||||
buffer.write(STRING_IO_UTF8, font.asString());
|
||||
}
|
||||
|
||||
final TextDecoration.State bold = style.decoration(TextDecoration.BOLD);
|
||||
if (bold != TextDecoration.State.NOT_SET) {
|
||||
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);
|
||||
}
|
||||
|
||||
final TextDecoration.State italic = style.decoration(TextDecoration.ITALIC);
|
||||
if (italic != TextDecoration.State.NOT_SET) {
|
||||
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);
|
||||
}
|
||||
|
||||
final TextDecoration.State underlined = style.decoration(TextDecoration.UNDERLINED);
|
||||
if (underlined != TextDecoration.State.NOT_SET) {
|
||||
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);
|
||||
}
|
||||
|
||||
final TextDecoration.State strikethrough = style.decoration(TextDecoration.STRIKETHROUGH);
|
||||
if (strikethrough != TextDecoration.State.NOT_SET) {
|
||||
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);
|
||||
}
|
||||
|
||||
final TextDecoration.State obfuscated = style.decoration(TextDecoration.OBFUSCATED);
|
||||
if (obfuscated != TextDecoration.State.NOT_SET) {
|
||||
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);
|
||||
}
|
||||
|
||||
final String insertion = style.insertion();
|
||||
if (insertion != null) {
|
||||
buffer.write(BYTE, TAG_STRING);
|
||||
writeUtf(buffer, "insertion");
|
||||
writeUtf(buffer, insertion);
|
||||
buffer.write(STRING_IO_UTF8, "insertion");
|
||||
buffer.write(STRING_IO_UTF8, insertion);
|
||||
}
|
||||
|
||||
final ClickEvent clickEvent = style.clickEvent();
|
||||
@ -208,15 +208,15 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
||||
|
||||
private void writeClickEvent(@NotNull NetworkBuffer buffer, @NotNull ClickEvent clickEvent) {
|
||||
buffer.write(BYTE, TAG_COMPOUND);
|
||||
writeUtf(buffer, "clickEvent");
|
||||
buffer.write(STRING_IO_UTF8, "clickEvent");
|
||||
|
||||
buffer.write(BYTE, TAG_STRING);
|
||||
writeUtf(buffer, "action");
|
||||
writeUtf(buffer, clickEvent.action().name().toLowerCase(Locale.ROOT));
|
||||
buffer.write(STRING_IO_UTF8, "action");
|
||||
buffer.write(STRING_IO_UTF8, clickEvent.action().name().toLowerCase(Locale.ROOT));
|
||||
|
||||
buffer.write(BYTE, TAG_STRING);
|
||||
writeUtf(buffer, "value");
|
||||
writeUtf(buffer, clickEvent.value());
|
||||
buffer.write(STRING_IO_UTF8, "value");
|
||||
buffer.write(STRING_IO_UTF8, clickEvent.value());
|
||||
|
||||
buffer.write(BYTE, TAG_END);
|
||||
}
|
||||
@ -224,29 +224,29 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
||||
@SuppressWarnings("unchecked")
|
||||
private void writeHoverEvent(@NotNull NetworkBuffer buffer, @NotNull HoverEvent<?> hoverEvent) {
|
||||
buffer.write(BYTE, TAG_COMPOUND);
|
||||
writeUtf(buffer, "hoverEvent");
|
||||
buffer.write(STRING_IO_UTF8, "hoverEvent");
|
||||
|
||||
buffer.write(BYTE, TAG_STRING);
|
||||
writeUtf(buffer, "action");
|
||||
writeUtf(buffer, hoverEvent.action().toString().toLowerCase(Locale.ROOT));
|
||||
buffer.write(STRING_IO_UTF8, "action");
|
||||
buffer.write(STRING_IO_UTF8, hoverEvent.action().toString().toLowerCase(Locale.ROOT));
|
||||
|
||||
buffer.write(BYTE, TAG_COMPOUND); // Start contents tag
|
||||
writeUtf(buffer, "contents");
|
||||
buffer.write(STRING_IO_UTF8, "contents");
|
||||
if (hoverEvent.action() == HoverEvent.Action.SHOW_TEXT) {
|
||||
writeInnerComponent(buffer, (Component) hoverEvent.value());
|
||||
} else if (hoverEvent.action() == HoverEvent.Action.SHOW_ITEM) {
|
||||
var value = ((HoverEvent<HoverEvent.ShowItem>) hoverEvent).value();
|
||||
|
||||
buffer.write(BYTE, TAG_STRING);
|
||||
writeUtf(buffer, "id");
|
||||
writeUtf(buffer, value.item().asString());
|
||||
buffer.write(STRING_IO_UTF8, "id");
|
||||
buffer.write(STRING_IO_UTF8, value.item().asString());
|
||||
|
||||
buffer.write(BYTE, TAG_INT);
|
||||
writeUtf(buffer, "count");
|
||||
buffer.write(STRING_IO_UTF8, "count");
|
||||
buffer.write(INT, value.count());
|
||||
|
||||
buffer.write(BYTE, TAG_COMPOUND);
|
||||
writeUtf(buffer, "components");
|
||||
buffer.write(STRING_IO_UTF8, "components");
|
||||
//todo item components
|
||||
buffer.write(BYTE, TAG_END);
|
||||
|
||||
@ -257,17 +257,17 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
||||
final Component name = value.name();
|
||||
if (name != null) {
|
||||
buffer.write(BYTE, TAG_COMPOUND);
|
||||
writeUtf(buffer, "name");
|
||||
buffer.write(STRING_IO_UTF8, "name");
|
||||
writeInnerComponent(buffer, name);
|
||||
}
|
||||
|
||||
buffer.write(BYTE, TAG_STRING);
|
||||
writeUtf(buffer, "type");
|
||||
writeUtf(buffer, value.type().asString());
|
||||
buffer.write(STRING_IO_UTF8, "type");
|
||||
buffer.write(STRING_IO_UTF8, value.type().asString());
|
||||
|
||||
buffer.write(BYTE, TAG_STRING);
|
||||
writeUtf(buffer, "id");
|
||||
writeUtf(buffer, value.id().toString());
|
||||
buffer.write(STRING_IO_UTF8, "id");
|
||||
buffer.write(STRING_IO_UTF8, value.id().toString());
|
||||
|
||||
buffer.write(BYTE, TAG_END); // End contents tag
|
||||
} else {
|
||||
@ -276,53 +276,4 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
|
||||
|
||||
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<String> STRING = new NetworkBufferTypeImpl.StringType();
|
||||
Type<String> STRING_TERMINATED = new NetworkBufferTypeImpl.StringTerminatedType();
|
||||
Type<String> STRING_IO_UTF8 = new NetworkBufferTypeImpl.IOUTF8StringType();
|
||||
Type<BinaryTag> NBT = new NetworkBufferTypeImpl.NbtType();
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
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) {
|
||||
NetworkBuffer buffer = NetworkBufferImpl.dummy(registries);
|
||||
type.write(buffer, value);
|
||||
|
@ -202,6 +202,7 @@ public final class PacketWriting {
|
||||
if (written < minWrite) {
|
||||
// Try again with a bigger buffer
|
||||
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);
|
||||
} else {
|
||||
// At least one packet has been written
|
||||
|
@ -141,9 +141,9 @@ public abstract class PlayerConnection {
|
||||
*/
|
||||
public void disconnect() {
|
||||
this.online = false;
|
||||
MinecraftServer.getConnectionManager().removePlayer(this);
|
||||
final Player player = getPlayer();
|
||||
final Player player = MinecraftServer.getConnectionManager().getPlayer(this);
|
||||
if (player != null) {
|
||||
MinecraftServer.getConnectionManager().removePlayer(this);
|
||||
if (connectionState == ConnectionState.PLAY && !player.isRemoved())
|
||||
player.scheduleNextTick(Entity::remove);
|
||||
else {
|
||||
|
@ -8,6 +8,11 @@ import net.minestom.testing.Env;
|
||||
import net.minestom.testing.EnvTest;
|
||||
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;
|
||||
|
||||
@EnvTest
|
||||
@ -99,4 +104,21 @@ public class EntityTeleportIntegrationTest {
|
||||
player.teleport(new Pos(5, 10, 2, 5, 5), null, RelativeFlags.VIEW).join();
|
||||
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.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UTFDataFormatException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
@ -474,6 +476,62 @@ public class NetworkBufferTest {
|
||||
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) {
|
||||
var buffer = NetworkBuffer.resizableBuffer(MinecraftServer.process());
|
||||
action.write(buffer, type, value);
|
||||
|
Loading…
Reference in New Issue
Block a user