diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 5ab389c2..af181463 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -25,6 +25,9 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import org.bukkit.Bukkit; import org.bukkit.Server; @@ -40,6 +43,8 @@ import com.comphenix.protocol.injector.StructureCache; import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.instances.DefaultInstances; +import com.comphenix.protocol.wrappers.ChunkPosition; import net.minecraft.server.Packet; @@ -327,6 +332,91 @@ public class PacketContainer implements Serializable { })); } + /** + * Retrieves a read/write structure for chunk positions. + * @return A modifier for a ChunkPosition. + */ + public StructureModifier getPositionModifier() { + // Convert to and from the Bukkit wrapper + return structureModifier.withType( + net.minecraft.server.ChunkPosition.class, + ChunkPosition.getConverter()); + } + + /** + * Retrieves a read/write structure for collections of chunk positions. + *

+ * This modifier will automatically marshall between the visible ProtocolLib ChunkPosition and the + * internal Minecraft ChunkPosition. + * @return A modifier for ChunkPosition array fields. + */ + public StructureModifier> getPositionCollectionModifier() { + final EquivalentConverter converter = ChunkPosition.getConverter(); + + // Convert to and from the ProtocolLib wrapper + final StructureModifier> modifier = structureModifier.withType( + Collection.class, + getIgnoreNull(new EquivalentConverter>() { + private Class collectionType; + + @SuppressWarnings("unchecked") + @Override + public List getSpecific(Object generic) { + if (generic instanceof Collection) { + List positions = new ArrayList(); + + // Save the type + collectionType = generic.getClass(); + + // Copy everything to a new list + for (Object item : (Collection) generic) { + ChunkPosition result = converter.getSpecific(item); + + if (item != null) + positions.add(result); + } + return positions; + } + + // Not valid + return null; + } + + @SuppressWarnings("unchecked") + @Override + public Object getGeneric(List specific) { + // Just go by the first field + if (collectionType == null) { + collectionType = structureModifier.withType(Collection.class).getFields().get(0).getType(); + } + + Collection newContainer = (Collection) DefaultInstances.DEFAULT.getDefault(collectionType); + + // Convert each object + for (ChunkPosition position : specific) { + Object converted = converter.getGeneric(position); + + if (position == null) + newContainer.add(null); + else if (converted != null) + newContainer.add(converted); + } + return newContainer; + } + + @SuppressWarnings("unchecked") + @Override + public Class> getSpecificType() { + // Damn you Java + Class dummy = List.class; + return (Class>) dummy; + } + } + )); + + return modifier; + } + private EquivalentConverter getIgnoreNull(final EquivalentConverter delegate) { // Automatically wrap all parameters to the delegate with a NULL check return new EquivalentConverter() { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java new file mode 100644 index 00000000..7cbd5840 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java @@ -0,0 +1,146 @@ +package com.comphenix.protocol.wrappers; + +import org.bukkit.util.Vector; + +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.StructureModifier; +import com.google.common.base.Objects; + +/** + * Wraps a immutable net.minecraft.server.ChunkPosition, which represents a integer 3D vector. + * + * @author Kristian + */ +public class ChunkPosition { + // Use protected members, like Bukkit + protected final int x; + protected final int y; + protected final int z; + + // Used to access a ChunkPosition, in case it's names are changed + private static StructureModifier intModifier; + + /** + * Construct an immutable 3D vector. + */ + public ChunkPosition(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Construct an immutable integer 3D vector from a mutable Bukkit vector. + * @param vector - the mutable real Bukkit vector to copy. + */ + public ChunkPosition(Vector vector) { + if (vector == null) + throw new IllegalArgumentException("Vector cannot be NULL."); + this.x = vector.getBlockX(); + this.y = vector.getBlockY(); + this.z = vector.getBlockZ(); + } + + /** + * Convert this instance to an equivalent real 3D vector. + * @return Real 3D vector. + */ + public Vector toVector() { + return new Vector(x, y, z); + } + + /** + * Retrieve the x-coordinate. + * @return X coordinate. + */ + public int getX() { + return x; + } + + /** + * Retrieve the y-coordinate. + * @return Y coordinate. + */ + public int getY() { + return y; + } + + /** + * Retrieve the z-coordinate. + * @return Z coordinate. + */ + public int getZ() { + return z; + } + + /** + * Used to convert between NMS ChunkPosition and the wrapper instance. + * @return + */ + public static EquivalentConverter getConverter() { + return new EquivalentConverter() { + @Override + public Object getGeneric(ChunkPosition specific) { + return new net.minecraft.server.ChunkPosition(specific.x, specific.z, specific.z); + } + + @Override + public ChunkPosition getSpecific(Object generic) { + if (generic instanceof net.minecraft.server.ChunkPosition) { + net.minecraft.server.ChunkPosition other = (net.minecraft.server.ChunkPosition) generic; + + try { + if (intModifier == null) + return new ChunkPosition(other.x, other.y, other.z); + } catch (LinkageError e) { + // It could happen. If it does, use a structure modifier instead + intModifier = new StructureModifier(other.getClass(), null, false).withType(int.class); + + // Damn it all + if (intModifier.size() < 3) { + throw new IllegalStateException("Cannot read class " + other.getClass() + " for its integer fields."); + } + } + + if (intModifier.size() >= 3) { + try { + return new ChunkPosition(intModifier.read(0), intModifier.read(1), intModifier.read(2)); + } catch (FieldAccessException e) { + // This is an exeptional work-around, so we don't want to burden the caller with the messy details + throw new RuntimeException("Field access error.", e); + } + } + } + + // Otherwise, return NULL + return null; + } + + // Thanks Java Generics! + @Override + public Class getSpecificType() { + return ChunkPosition.class; + } + }; + } + + @Override + public boolean equals(Object obj) { + // Fast checks + if (this == obj) return true; + if (obj == null) return false; + + // Only compare objects of similar type + if (obj instanceof ChunkPosition) { + ChunkPosition other = (ChunkPosition) obj; + return x == other.x && y == other.y && z == other.z; + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(x, y, z); + } +}