Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.20.3 #767

Merged
merged 18 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>com.github.steveice10</groupId>
<artifactId>mcprotocollib</artifactId>
<version>1.20.2-1</version>
<version>1.20.3-1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>MCProtocolLib</name>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.github.steveice10.mc.protocol;

@FunctionalInterface
public interface CheckedBiConsumer<T, U, E extends Throwable> {
void accept(T t, U u) throws E;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.github.steveice10.mc.protocol;

@FunctionalInterface
public interface CheckedFunction<T, R, E extends Throwable> {
R apply(T t) throws E;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
package com.github.steveice10.mc.protocol.codec;

import com.github.steveice10.opennbt.tag.builtin.ByteArrayTag;
import com.github.steveice10.opennbt.tag.builtin.ByteTag;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.DoubleTag;
import com.github.steveice10.opennbt.tag.builtin.FloatTag;
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.LongArrayTag;
import com.github.steveice10.opennbt.tag.builtin.LongTag;
import com.github.steveice10.opennbt.tag.builtin.ShortTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

/**
* Taken from https://github.com/ViaVersion/ViaVersion/blob/dev/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_20_3to1_20_2/Protocol1_20_3To1_20_2.java
*/
public class ComponentSerializer {

private static final Set<String> BOOLEAN_TYPES = new HashSet<>(Arrays.asList(
"interpret",
"bold",
"italic",
"underlined",
"strikethrough",
"obfuscated"
));

private ComponentSerializer() {

}

@Contract("null -> null")
public static JsonElement tagComponentToJson(@Nullable final Tag tag) {
return convertToJson(null, tag);
}


public static @Nullable Tag jsonComponentToTag(@Nullable final JsonElement component) {
return convertToTag("", component);
}

@Contract("_, null -> null")
private static Tag convertToTag(String name, final @Nullable JsonElement element) {
if (element == null || element.isJsonNull()) {
return null;
} else if (element.isJsonObject()) {
final CompoundTag tag = new CompoundTag(name);
for (final Map.Entry<String, JsonElement> entry : element.getAsJsonObject().entrySet()) {
// Not strictly needed, but might as well make it more compact
convertObjectEntry(entry.getKey(), entry.getValue(), tag);
}
return tag;
} else if (element.isJsonArray()) {
return convertJsonArray(name, element);
} else if (element.isJsonPrimitive()) {
final JsonPrimitive primitive = element.getAsJsonPrimitive();
if (primitive.isString()) {
return new StringTag(name, primitive.getAsString());
} else if (primitive.isBoolean()) {
return new ByteTag(name, (byte) (primitive.getAsBoolean() ? 1 : 0));
}

final Number number = primitive.getAsNumber();
if (number instanceof Integer) {
return new IntTag(name, number.intValue());
} else if (number instanceof Byte) {
return new ByteTag(name, number.byteValue());
} else if (number instanceof Short) {
return new ShortTag(name, number.shortValue());
} else if (number instanceof Long) {
return new LongTag(name, number.longValue());
} else if (number instanceof Double) {
return new DoubleTag(name, number.doubleValue());
} else if (number instanceof Float) {
return new FloatTag(name, number.floatValue());
}
return new StringTag(primitive.getAsString()); // ???
}
throw new IllegalArgumentException("Unhandled json type " + element.getClass().getSimpleName() + " with value " + element.getAsString());
}

private static ListTag convertJsonArray(String name, final JsonElement element) {
// TODO Number arrays?
final ListTag listTag = new ListTag(name);
boolean singleType = true;
for (final JsonElement entry : element.getAsJsonArray()) {
final Tag convertedEntryTag = convertToTag("", entry);
if (listTag.getElementType() != null && listTag.getElementType() != convertedEntryTag.getClass()) {
singleType = false;
break;
}

listTag.add(convertedEntryTag);
}

if (singleType) {
return listTag;
}

// Generally, vanilla-esque serializers should not produce this format, so it should be rare
// Lists are only used for lists of components ("extra" and "with")
final ListTag processedListTag = new ListTag(name);
for (final JsonElement entry : element.getAsJsonArray()) {
final Tag convertedTag = convertToTag("extra", entry);
if (convertedTag instanceof CompoundTag) {
processedListTag.add(listTag);
continue;
}

// Wrap all entries in compound tags as lists can only consist of one type of tag
final CompoundTag compoundTag = new CompoundTag("");
compoundTag.put(new StringTag("text"));
compoundTag.put(convertedTag);
}
return processedListTag;
}

private static void convertObjectEntry(final String key, final JsonElement element, final CompoundTag tag) {
if ((key.equals("contents")) && element.isJsonObject()) {
// Store show_entity id as int array instead of uuid string
final JsonObject hoverEvent = element.getAsJsonObject();
final JsonElement id = hoverEvent.get("id");
final UUID uuid;
if (id != null && id.isJsonPrimitive() && (uuid = parseUUID(id.getAsString())) != null) {
hoverEvent.remove("id");

final CompoundTag convertedTag = (CompoundTag) convertToTag(key, element);
convertedTag.put(new IntArrayTag("id", uuidToIntArray(uuid)));
tag.put(convertedTag);
return;
}
}

tag.put(convertToTag(key, element));
}

private static @Nullable UUID parseUUID(final String uuidString) {
try {
return UUID.fromString(uuidString);
} catch (final IllegalArgumentException e) {
return null;
}
}

@Contract("_, null -> null")
private static JsonElement convertToJson(final @Nullable String key, final @Nullable Tag tag) {
if (tag == null) {
return null;
} else if (tag instanceof CompoundTag) {
final JsonObject object = new JsonObject();
for (final Tag entry : (CompoundTag) tag) {
convertCompoundTagEntry(entry.getName(), entry, object);
}
return object;
} else if (tag instanceof ListTag) {
final ListTag list = (ListTag) tag;
final JsonArray array = new JsonArray();
for (final Tag listEntry : list) {
array.add(convertToJson(null, listEntry));
}
return array;
} else if (tag.getValue() instanceof Number) {
Number number = (Number) tag.getValue();
if (key != null && BOOLEAN_TYPES.contains(key)) {
// Booleans don't have a direct representation in nbt
return new JsonPrimitive(number.byteValue() != 0);
}
return new JsonPrimitive(number);
} else if (tag instanceof StringTag) {
return new JsonPrimitive(((StringTag) tag).getValue());
} else if (tag instanceof ByteArrayTag) {
final ByteArrayTag arrayTag = (ByteArrayTag) tag;
final JsonArray array = new JsonArray();
for (final byte num : arrayTag.getValue()) {
array.add(num);
}
return array;
} else if (tag instanceof IntArrayTag) {
final IntArrayTag arrayTag = (IntArrayTag) tag;
final JsonArray array = new JsonArray();
for (final int num : arrayTag.getValue()) {
array.add(num);
}
return array;
} else if (tag instanceof LongArrayTag) {
final LongArrayTag arrayTag = (LongArrayTag) tag;
final JsonArray array = new JsonArray();
for (final long num : arrayTag.getValue()) {
array.add(num);
}
return array;
}
throw new IllegalArgumentException("Unhandled tag type " + tag.getClass().getSimpleName());
}

private static void convertCompoundTagEntry(final String key, final Tag tag, final JsonObject object) {
if ((key.equals("contents")) && tag instanceof CompoundTag) {
// Back to a UUID string
final CompoundTag showEntity = (CompoundTag) tag;
final Tag idTag = showEntity.get("id");
if (idTag instanceof IntArrayTag) {
showEntity.remove("id");

final JsonObject convertedElement = (JsonObject) convertToJson(key, tag);
convertedElement.addProperty("id", uuidIntsToString(((IntArrayTag) idTag).getValue()));
object.add(key, convertedElement);
return;
}
}

// "":1 is a valid tag, but not a valid json component
object.add(key.isEmpty() ? "text" : key, convertToJson(key, tag));
}

private static String uuidIntsToString(final int[] parts) {
if (parts.length != 4) {
return new UUID(0, 0).toString();
}
return uuidFromIntArray(parts).toString();
}

private static UUID uuidFromIntArray(int[] ints) {
return new UUID((long) ints[0] << 32 | ((long) ints[1] & 0xFFFFFFFFL), (long) ints[2] << 32 | ((long) ints[3] & 0xFFFFFFFFL));
}

private static int[] uuidToIntArray(UUID uuid) {
return bitsToIntArray(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());
}

private static int[] bitsToIntArray(long long1, long long2) {
return new int[]{(int) (long1 >> 32), (int) long1, (int) (long2 >> 32), (int) long2};
}
}
Loading