Merge pull request #1853 from KennyTV/master

Fix 1.12->1.13 show_item handling
This commit is contained in:
Myles 2020-06-28 11:52:59 +01:00 committed by GitHub
commit ed0db721dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 976 additions and 0 deletions

View File

@ -0,0 +1,75 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2020 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package us.myles.ViaVersion.api.minecraft.nbt;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
/**
* See https://github.com/KyoriPowered/adventure.
*/
public final class BinaryTagIO {
private BinaryTagIO() {
}
/**
* Reads a compound tag from a {@link String}.
*
* @param input the string
* @return the compound tag
* @throws IOException if an exception was encountered while reading a compound tag
*/
@NotNull
public static CompoundTag readString(final @NotNull String input) throws IOException {
try {
final CharBuffer buffer = new CharBuffer(input);
final TagStringReader parser = new TagStringReader(buffer);
final CompoundTag tag = parser.compound();
if (buffer.skipWhitespace().hasMore()) {
throw new IOException("Document had trailing content after first CompoundTag");
}
return tag;
} catch (final StringTagParseException ex) {
throw new IOException(ex);
}
}
/**
* Writes a compound tag to a {@link String}.
*
* @param tag the compound tag
* @return the string
* @throws IOException if an exception was encountered while writing the compound tag
*/
@NotNull
public static String writeString(final @NotNull CompoundTag tag) throws IOException {
final StringBuilder sb = new StringBuilder();
try (final TagStringWriter emit = new TagStringWriter(sb)) {
emit.writeTag(tag);
}
return sb.toString();
}
}

View File

@ -0,0 +1,120 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2020 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package us.myles.ViaVersion.api.minecraft.nbt;
/* package */ final class CharBuffer {
private final CharSequence sequence;
private int index;
CharBuffer(final CharSequence sequence) {
this.sequence = sequence;
}
/**
* Get the character at the current position
*
* @return The current character
*/
public char peek() {
return this.sequence.charAt(this.index);
}
public char peek(final int offset) {
return this.sequence.charAt(this.index + offset);
}
/**
* Get the current character and advance
*
* @return current character
*/
public char take() {
return this.sequence.charAt(this.index++);
}
public boolean advance() {
this.index++;
return this.hasMore();
}
public boolean hasMore() {
return this.index < this.sequence.length();
}
/**
* Search for the provided token, and advance the reader index past the {@code until} character.
*
* @param until Case-insensitive token
* @return the string starting at the current position (inclusive) and going until the location of {@code until}, exclusive
*/
public CharSequence takeUntil(char until) throws StringTagParseException {
until = Character.toLowerCase(until);
int endIdx = -1;
for (int idx = this.index; idx < this.sequence.length(); ++idx) {
if (this.sequence.charAt(idx) == Tokens.ESCAPE_MARKER) {
idx++;
} else if (Character.toLowerCase(this.sequence.charAt(idx)) == until) {
endIdx = idx;
break;
}
}
if (endIdx == -1) {
throw this.makeError("No occurrence of " + until + " was found");
}
final CharSequence result = this.sequence.subSequence(this.index, endIdx);
this.index = endIdx + 1;
return result;
}
/**
* Assert that the next non-whitespace character is the provided parameter.
*
* <p>If the assertion is successful, the token will be consumed.</p>
*
* @param expectedChar expected character
* @return this
* @throws StringTagParseException if EOF or non-matching value is found
*/
public CharBuffer expect(final char expectedChar) throws StringTagParseException {
this.skipWhitespace();
if (!this.hasMore()) {
throw this.makeError("Expected character '" + expectedChar + "' but got EOF");
}
if (this.peek() != expectedChar) {
throw this.makeError("Expected character '" + expectedChar + "' but got '" + this.peek() + "'");
}
this.take();
return this;
}
public CharBuffer skipWhitespace() {
while (this.hasMore() && Character.isWhitespace(this.peek())) this.advance();
return this;
}
public StringTagParseException makeError(final String message) {
return new StringTagParseException(message, this.sequence, this.index);
}
}

View File

@ -0,0 +1,48 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2020 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package us.myles.ViaVersion.api.minecraft.nbt;
import java.io.IOException;
/**
* An exception thrown when parsing a string tag
*/
/* package */ class StringTagParseException extends IOException {
private static final long serialVersionUID = -3001637554903912905L;
private final CharSequence buffer;
private final int position;
public StringTagParseException(final String message, final CharSequence buffer, final int position) {
super(message);
this.buffer = buffer;
this.position = position;
}
// TODO: Provide more specific position information
@Override
public String getMessage() {
return super.getMessage() + "(at position " + this.position + ")";
}
}

View File

@ -0,0 +1,315 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2020 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package us.myles.ViaVersion.api.minecraft.nbt;
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 java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
/**
* See https://github.com/KyoriPowered/adventure.
*/
/* package */ final class TagStringReader {
private static final Field NAME_FIELD = getNameField();
private final CharBuffer buffer;
private static Field getNameField() {
try {
return Tag.class.getDeclaredField("name");
} catch (NoSuchFieldException e) {
e.printStackTrace();
throw new IllegalArgumentException(e);
}
}
public TagStringReader(final CharBuffer buffer) {
this.buffer = buffer;
}
public CompoundTag compound() throws StringTagParseException {
this.buffer.expect(Tokens.COMPOUND_BEGIN);
final CompoundTag compoundTag = new CompoundTag("");
while (this.buffer.hasMore()) {
final String key = this.key();
final Tag tag = this.tag();
// Doesn't get around this with the steveice lib :/
try {
if (!NAME_FIELD.isAccessible()) {
NAME_FIELD.setAccessible(true);
}
NAME_FIELD.set(tag, key);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(e);
}
compoundTag.put(tag);
if (this.separatorOrCompleteWith(Tokens.COMPOUND_END)) {
return compoundTag;
}
}
throw this.buffer.makeError("Unterminated compound tag!");
}
public ListTag list() throws StringTagParseException {
final ListTag listTag = new ListTag("");
this.buffer.expect(Tokens.ARRAY_BEGIN);
while (this.buffer.hasMore()) {
final Tag next = this.tag();
// TODO: validate type
listTag.add(next);
if (this.separatorOrCompleteWith(Tokens.ARRAY_END)) {
return listTag;
}
}
throw this.buffer.makeError("Reached end of file without end of list tag!");
}
/**
* Similar to a list tag in syntax, but returning a single array tag rather than a list of tags.
*
* @return array-typed tag
*/
public Tag array(final char elementType) throws StringTagParseException {
this.buffer.expect(Tokens.ARRAY_BEGIN)
.expect(elementType)
.expect(Tokens.ARRAY_SIGNATURE_SEPARATOR);
if (elementType == Tokens.TYPE_BYTE) {
return new ByteArrayTag("", this.byteArray());
} else if (elementType == Tokens.TYPE_INT) {
return new IntArrayTag("", this.intArray());
} else if (elementType == Tokens.TYPE_LONG) {
return new LongArrayTag("", this.longArray());
} else {
throw this.buffer.makeError("Type " + elementType + " is not a valid element type in an array!");
}
}
private byte[] byteArray() throws StringTagParseException {
final List<Byte> bytes = new ArrayList<>();
while (this.buffer.hasMore()) {
final CharSequence value = this.buffer.skipWhitespace().takeUntil(Tokens.TYPE_BYTE);
try {
bytes.add(Byte.valueOf(value.toString()));
} catch (final NumberFormatException ex) {
throw this.buffer.makeError("All elements of a byte array must be bytes!");
}
if (this.separatorOrCompleteWith(Tokens.ARRAY_END)) {
final byte[] result = new byte[bytes.size()];
for (int i = 0; i < bytes.size(); ++i) { // todo yikes, let's do less boxing
result[i] = bytes.get(i);
}
return result;
}
}
throw this.buffer.makeError("Reached end of document without array close");
}
private int[] intArray() throws StringTagParseException {
final IntStream.Builder builder = IntStream.builder();
while (this.buffer.hasMore()) {
final Tag value = this.tag();
if (!(value instanceof IntTag)) {
throw this.buffer.makeError("All elements of an int array must be ints!");
}
builder.add(((IntTag) value).getValue());
if (this.separatorOrCompleteWith(Tokens.ARRAY_END)) {
return builder.build().toArray();
}
}
throw this.buffer.makeError("Reached end of document without array close");
}
private long[] longArray() throws StringTagParseException {
final List<Long> longs = new ArrayList<>();
while (this.buffer.hasMore()) {
final CharSequence value = this.buffer.skipWhitespace().takeUntil(Tokens.TYPE_LONG);
try {
longs.add(Long.valueOf(value.toString()));
} catch (final NumberFormatException ex) {
throw this.buffer.makeError("All elements of a long array must be longs!");
}
if (this.separatorOrCompleteWith(Tokens.ARRAY_END)) {
final long[] result = new long[longs.size()];
for (int i = 0; i < longs.size(); ++i) { // todo yikes
result[i] = longs.get(i);
}
return result;
}
}
throw this.buffer.makeError("Reached end of document without array close");
}
public String key() throws StringTagParseException {
this.buffer.skipWhitespace();
final char starChar = this.buffer.peek();
try {
if (starChar == Tokens.SINGLE_QUOTE || starChar == Tokens.DOUBLE_QUOTE) {
return unescape(this.buffer.takeUntil(this.buffer.take()).toString());
}
final StringBuilder builder = new StringBuilder();
while (Tokens.id(this.buffer.peek())) {
builder.append(this.buffer.take());
}
return builder.toString();
} finally {
this.buffer.expect(Tokens.COMPOUND_KEY_TERMINATOR);
}
}
public Tag tag() throws StringTagParseException {
final char startToken = this.buffer.skipWhitespace().peek();
switch (startToken) {
case Tokens.COMPOUND_BEGIN:
return this.compound();
case Tokens.ARRAY_BEGIN:
if (this.buffer.peek(2) == ';') { // we know we're an array tag
return this.array(this.buffer.peek(1));
} else {
return this.list();
}
case Tokens.SINGLE_QUOTE:
case Tokens.DOUBLE_QUOTE:
// definitely a string tag
this.buffer.advance();
return new StringTag("", unescape(this.buffer.takeUntil(startToken).toString()));
default: // scalar
return this.scalar();
}
}
/**
* A tag that is definitely some sort of scalar
*
* <p>Does not detect quoted strings, so </p>
*
* @return a parsed tag
*/
private Tag scalar() {
final StringBuilder builder = new StringBuilder();
boolean possiblyNumeric = true;
while (this.buffer.hasMore()) {
final char current = this.buffer.peek();
if (possiblyNumeric && !Tokens.numeric(current)) {
if (builder.length() != 0) {
Tag result = null;
try {
switch (Character.toUpperCase(current)) { // try to read and return as a number
// case Tokens.TYPE_INTEGER: // handled below, ints are ~special~
case Tokens.TYPE_BYTE:
result = new ByteTag("", Byte.parseByte(builder.toString()));
break;
case Tokens.TYPE_SHORT:
result = new ShortTag("", (Short.parseShort(builder.toString())));
break;
case Tokens.TYPE_LONG:
result = new LongTag("", (Long.parseLong(builder.toString())));
break;
case Tokens.TYPE_FLOAT:
result = new FloatTag("", (Float.parseFloat(builder.toString())));
break;
case Tokens.TYPE_DOUBLE:
result = new DoubleTag("", (Double.parseDouble(builder.toString())));
break;
}
} catch (final NumberFormatException ex) {
possiblyNumeric = false; // fallback to treating as a String
}
if (result != null) {
this.buffer.take();
return result;
}
}
}
if (current == '\\') { // escape -- we are significantly more lenient than original format at the moment
this.buffer.advance();
builder.append(this.buffer.take());
} else if (Tokens.id(current)) {
builder.append(this.buffer.take());
} else { // end of value
break;
}
}
// if we run out of content without an explicit value separator, then we're either an integer or string tag -- all others have a character at the end
final String built = builder.toString();
if (possiblyNumeric) {
try {
return new IntTag("", Integer.parseInt(built));
} catch (final NumberFormatException ex) {
// ignore
}
}
return new StringTag("", built);
}
private boolean separatorOrCompleteWith(final char endCharacter) throws StringTagParseException {
if (this.buffer.skipWhitespace().peek() == endCharacter) {
this.buffer.take();
return true;
}
this.buffer.expect(Tokens.VALUE_SEPARATOR);
return false;
}
/**
* Remove simple escape sequences from a string
*
* @param withEscapes input string with escapes
* @return string with escapes processed
*/
private static String unescape(final String withEscapes) {
int escapeIdx = withEscapes.indexOf(Tokens.ESCAPE_MARKER);
if (escapeIdx == -1) { // nothing to unescape
return withEscapes;
}
int lastEscape = 0;
final StringBuilder output = new StringBuilder(withEscapes.length());
do {
output.append(withEscapes, lastEscape, escapeIdx);
lastEscape = escapeIdx + 1;
} while ((escapeIdx = withEscapes.indexOf(Tokens.ESCAPE_MARKER, lastEscape + 1)) != -1); // add one extra character to make sure we don't include escaped backslashes
output.append(withEscapes.substring(lastEscape));
return output.toString();
}
}

View File

@ -0,0 +1,256 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2020 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package us.myles.ViaVersion.api.minecraft.nbt;
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 java.io.IOException;
import java.io.Writer;
/**
* See https://github.com/KyoriPowered/adventure.
*/
/* package */ final class TagStringWriter implements AutoCloseable {
private final Appendable out;
private final String indent = " "; // TODO: pretty-printing
private int level;
/**
* Whether a {@link Tokens#VALUE_SEPARATOR} needs to be printed before the beginning of the next object.
*/
private boolean needsSeparator;
public TagStringWriter(final Appendable out) {
this.out = out;
}
// NBT-specific
public TagStringWriter writeTag(final Tag tag) throws IOException {
if (tag instanceof CompoundTag) {
return this.writeCompound((CompoundTag) tag);
} else if (tag instanceof ListTag) {
return this.writeList((ListTag) tag);
} else if (tag instanceof ByteArrayTag) {
return this.writeByteArray((ByteArrayTag) tag);
} else if (tag instanceof IntArrayTag) {
return this.writeIntArray((IntArrayTag) tag);
} else if (tag instanceof LongArrayTag) {
return this.writeLongArray((LongArrayTag) tag);
} else if (tag instanceof StringTag) {
return this.value(((StringTag) tag).getValue(), Tokens.EOF);
} else if (tag instanceof ByteTag) {
return this.value(Byte.toString(((ByteTag) tag).getValue()), Tokens.TYPE_BYTE);
} else if (tag instanceof ShortTag) {
return this.value(Short.toString(((ShortTag) tag).getValue()), Tokens.TYPE_SHORT);
} else if (tag instanceof IntTag) {
return this.value(Integer.toString(((IntTag) tag).getValue()), Tokens.TYPE_INT);
} else if (tag instanceof LongTag) {
return this.value(Long.toString(((LongTag) tag).getValue()), Tokens.TYPE_LONG);
} else if (tag instanceof FloatTag) {
return this.value(Float.toString(((FloatTag) tag).getValue()), Tokens.TYPE_FLOAT);
} else if (tag instanceof DoubleTag) {
return this.value(Double.toString(((DoubleTag) tag).getValue()), Tokens.TYPE_DOUBLE);
} else {
throw new IOException("Unknown tag type: " + tag.getClass().getSimpleName());
// unknown!
}
}
private TagStringWriter writeCompound(final CompoundTag tag) throws IOException {
this.beginCompound();
for (Tag t : tag) {
this.key(t.getName());
this.writeTag(t);
}
this.endCompound();
return this;
}
private TagStringWriter writeList(final ListTag tag) throws IOException {
this.beginList();
for (final Tag el : tag) {
this.printAndResetSeparator();
this.writeTag(el);
}
this.endList();
return this;
}
private TagStringWriter writeByteArray(final ByteArrayTag tag) throws IOException {
this.beginArray(Tokens.TYPE_BYTE);
final byte[] value = tag.getValue();
for (int i = 0, length = value.length; i < length; i++) {
this.printAndResetSeparator();
this.value(Byte.toString(value[i]), Tokens.TYPE_BYTE);
}
this.endArray();
return this;
}
private TagStringWriter writeIntArray(final IntArrayTag tag) throws IOException {
this.beginArray(Tokens.TYPE_INT);
final int[] value = tag.getValue();
for (int i = 0, length = value.length; i < length; i++) {
this.printAndResetSeparator();
this.value(Integer.toString(value[i]), Tokens.TYPE_INT);
}
this.endArray();
return this;
}
private TagStringWriter writeLongArray(final LongArrayTag tag) throws IOException {
this.beginArray(Tokens.TYPE_LONG);
final long[] value = tag.getValue();
for (int i = 0, length = value.length; i < length; i++) {
this.printAndResetSeparator();
this.value(Long.toString(value[i]), Tokens.TYPE_LONG);
}
this.endArray();
return this;
}
// Value types
public TagStringWriter beginCompound() throws IOException {
this.printAndResetSeparator();
this.level++;
this.out.append(Tokens.COMPOUND_BEGIN);
return this;
}
public TagStringWriter endCompound() throws IOException {
this.out.append(Tokens.COMPOUND_END);
this.level--;
this.needsSeparator = true;
return this;
}
public TagStringWriter key(final String key) throws IOException {
this.printAndResetSeparator();
this.writeMaybeQuoted(key, false);
this.out.append(Tokens.COMPOUND_KEY_TERMINATOR); // TODO: spacing/pretty-printing
return this;
}
public TagStringWriter value(final String value, final char valueType) throws IOException {
if (valueType == Tokens.EOF) { // string doesn't have its type
this.writeMaybeQuoted(value, true);
} else {
this.out.append(value);
if (valueType != Tokens.TYPE_INT) {
this.out.append(valueType);
}
}
this.needsSeparator = true;
return this;
}
public TagStringWriter beginList() throws IOException {
this.printAndResetSeparator();
this.level++;
this.out.append(Tokens.ARRAY_BEGIN);
return this;
}
public TagStringWriter endList() throws IOException {
this.out.append(Tokens.ARRAY_END);
this.level--;
this.needsSeparator = true;
return this;
}
private TagStringWriter beginArray(final char type) throws IOException {
this.beginList()
.out.append(type)
.append(Tokens.ARRAY_SIGNATURE_SEPARATOR);
return this;
}
private TagStringWriter endArray() throws IOException {
return this.endList();
}
private void writeMaybeQuoted(final String content, boolean requireQuotes) throws IOException {
if (!requireQuotes) {
for (int i = 0; i < content.length(); ++i) {
if (!Tokens.id(content.charAt(i))) {
requireQuotes = true;
break;
}
}
}
if (requireQuotes) { // TODO: single quotes
this.out.append(Tokens.DOUBLE_QUOTE);
this.out.append(escape(content, Tokens.DOUBLE_QUOTE));
this.out.append(Tokens.DOUBLE_QUOTE);
} else {
this.out.append(content);
}
}
private static String escape(final String content, final char quoteChar) {
final StringBuilder output = new StringBuilder(content.length());
for (int i = 0; i < content.length(); ++i) {
final char c = content.charAt(i);
if (c == quoteChar || c == '\\') {
output.append(Tokens.ESCAPE_MARKER);
}
output.append(c);
}
return output.toString();
}
private void printAndResetSeparator() throws IOException {
if (this.needsSeparator) {
this.out.append(Tokens.VALUE_SEPARATOR);
this.needsSeparator = false;
}
}
@Override
public void close() throws IOException {
if (this.level != 0) {
throw new IllegalStateException("Document finished with unbalanced start and end objects");
}
if (this.out instanceof Writer) {
((Writer) this.out).flush();
}
}
}

View File

@ -0,0 +1,88 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2020 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package us.myles.ViaVersion.api.minecraft.nbt;
/**
* See https://github.com/KyoriPowered/adventure.
*/
/* package */ final class Tokens {
// Compounds
static final char COMPOUND_BEGIN = '{';
static final char COMPOUND_END = '}';
static final char COMPOUND_KEY_TERMINATOR = ':';
// Arrays
static final char ARRAY_BEGIN = '[';
static final char ARRAY_END = ']';
static final char ARRAY_SIGNATURE_SEPARATOR = ';';
static final char VALUE_SEPARATOR = ',';
static final char SINGLE_QUOTE = '\'';
static final char DOUBLE_QUOTE = '"';
static final char ESCAPE_MARKER = '\\';
static final char TYPE_BYTE = 'B';
static final char TYPE_SHORT = 'S';
static final char TYPE_INT = 'I'; // array only
static final char TYPE_LONG = 'L';
static final char TYPE_FLOAT = 'F';
static final char TYPE_DOUBLE = 'D';
static final char EOF = '\0';
private Tokens() {
}
/**
* Return if a character is a valid component in an identifier
*
* <p>An identifier character must match the expression {@code [a-zA-Z0-9_+.-]}</p>
*
* @param c the character
* @return identifier
*/
static boolean id(final char c) {
return (c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9')
|| c == '-' || c == '_'
|| c == '.' || c == '+';
}
/**
* Return whether a character could be at some position in a number.
* <p>
* A string passing this check does not necessarily mean it is syntactically valid
*
* @param c character to check
* @return if possibly part of a number
*/
static boolean numeric(final char c) {
return (c >= '0' && c <= '9') // digit
|| c == '+' || c == '-' // positive or negative
|| c == 'e' || c == 'E' // exponent
|| c == '.'; // decimal
}
}

View File

@ -1,16 +1,25 @@
package us.myles.ViaVersion.protocols.protocol1_13to1_12_2;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ShortTag;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.chat.ComponentSerializer;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.api.minecraft.item.Item;
import us.myles.ViaVersion.api.minecraft.nbt.BinaryTagIO;
import us.myles.ViaVersion.api.rewriters.ComponentRewriter;
import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.data.MappingData;
import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.packets.InventoryPackets;
import us.myles.ViaVersion.util.GsonUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
@ -20,6 +29,71 @@ public class ChatRewriter {
private static final Pattern URL = Pattern.compile("^(?:(https?)://)?([-\\w_.]{2,}\\.[a-z]{2,4})(/\\S*)?$");
private static final BaseComponent[] EMPTY_COMPONENTS = new BaseComponent[0];
private static final ComponentRewriter COMPONENT_REWRITER = new ComponentRewriter() {
@Override
protected void handleHoverEvent(JsonObject hoverEvent) {
super.handleHoverEvent(hoverEvent);
String action = hoverEvent.getAsJsonPrimitive("action").getAsString();
if (!action.equals("show_item")) return;
JsonElement value = hoverEvent.getAsJsonObject("value");
if (value == null) return;
String text = findItemNBT(value);
System.out.println(text);
try {
CompoundTag tag = BinaryTagIO.readString(text);
System.out.println(tag);
CompoundTag itemTag = tag.get("tag");
ShortTag damageTag = tag.get("Damage");
// Call item converter
short damage = damageTag != null ? damageTag.getValue() : 0;
Item item = new Item();
item.setData(damage);
item.setTag(itemTag);
InventoryPackets.toClient(item);
// Serialize again
if (damage != item.getData()) {
tag.put(new ShortTag("Damage", item.getData()));
}
if (itemTag != null) {
tag.put(itemTag);
}
JsonArray array = new JsonArray();
JsonObject object = new JsonObject();
array.add(object);
String serializedNBT = BinaryTagIO.writeString(tag);
object.addProperty("text", serializedNBT);
hoverEvent.add("value", array);
System.out.println(serializedNBT);
} catch (IOException e) {
Via.getPlatform().getLogger().warning("Invalid NBT in show_item:");
e.printStackTrace();
}
}
private String findItemNBT(JsonElement element) {
if (element.isJsonArray()) {
for (JsonElement jsonElement : element.getAsJsonArray()) {
String value = findItemNBT(jsonElement);
if (value != null) {
return value;
}
}
} else if (element.isJsonObject()) {
JsonPrimitive text = element.getAsJsonObject().getAsJsonPrimitive("text");
if (text != null) {
return text.getAsString();
}
} else if (element.isJsonPrimitive()) {
return element.getAsJsonPrimitive().getAsString();
}
return null;
}
@Override
protected void handleTranslate(JsonObject object, String translate) {
super.handleTranslate(object, translate);