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 338aaac7..87f0139c 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java
@@ -36,6 +36,7 @@ import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import net.minecraft.util.com.mojang.authlib.GameProfile;
import net.minecraft.util.io.netty.buffer.ByteBuf;
import net.minecraft.util.io.netty.buffer.UnpooledByteBufAllocator;
import org.bukkit.World;
@@ -66,6 +67,7 @@ import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.ChunkPosition;
import com.comphenix.protocol.wrappers.WrappedAttribute;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
+import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.google.common.base.Function;
@@ -475,6 +477,19 @@ public class PacketContainer implements Serializable {
);
}
+ /**
+ * Retrieves a read/write structure for game profiles.
+ *
+ * This modifier will automatically marshall between WrappedGameProfile and the
+ * internal Minecraft GameProfile.
+ * @return A modifier for GameProfile fields.
+ */
+ public StructureModifier getGameProfiles() {
+ // Convert to and from the Bukkit wrapper
+ return structureModifier.withType(
+ GameProfile.class, BukkitConverters.getWrappedGameProfileConverter());
+ }
+
/**
* Retrieves the ID of this packet.
*
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java
index 381803a0..c552ae72 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java
@@ -35,6 +35,7 @@ import java.util.regex.Pattern;
import javax.annotation.Nonnull;
+import net.minecraft.util.com.mojang.authlib.GameProfile;
import net.minecraft.util.io.netty.buffer.ByteBuf;
import net.sf.cglib.asm.ClassReader;
import net.sf.cglib.asm.MethodVisitor;
@@ -517,6 +518,19 @@ public class MinecraftReflection {
}
}
+ /**
+ * Retrieve the GameProfile class in 1.7.2 and later.
+ * @return The game profile class.
+ * @throws IllegalStateException If we are running 1.6.4 or earlier.
+ */
+ public static Class> getGameProfileClass() {
+ if (!isUsingNetty())
+ throw new IllegalStateException("GameProfile does not exist in version 1.6.4 and earlier.");
+
+ // Yay, we can actually refer to it directly
+ return GameProfile.class;
+ }
+
/**
* Retrieve the entity (NMS) class.
* @return The entity class.
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java
index 2d2ea72f..301f9a0f 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java
@@ -234,6 +234,29 @@ public class BukkitConverters {
};
}
+ /**
+ * Retrieve a converter for wrapped attribute snapshots.
+ * @return Wrapped attribute snapshot converter.
+ */
+ public static EquivalentConverter getWrappedGameProfileConverter() {
+ return new IgnoreNullConverter() {
+ @Override
+ protected Object getGenericValue(Class> genericType, WrappedGameProfile specific) {
+ return specific.getHandle();
+ }
+
+ @Override
+ protected WrappedGameProfile getSpecificValue(Object generic) {
+ return WrappedGameProfile.fromHandle(generic);
+ }
+
+ @Override
+ public Class getSpecificType() {
+ return WrappedGameProfile.class;
+ }
+ };
+ }
+
/**
* Retrieve a converter for wrapped attribute snapshots.
* @return Wrapped attribute snapshot converter.
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedGameProfile.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedGameProfile.java
new file mode 100644
index 00000000..f390b208
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedGameProfile.java
@@ -0,0 +1,104 @@
+package com.comphenix.protocol.wrappers;
+
+import net.minecraft.util.com.mojang.authlib.GameProfile;
+
+/**
+ * Represents a wrapper for a game profile.
+ * @author Kristian
+ */
+public class WrappedGameProfile {
+ private GameProfile profile;
+
+ // Profile from a handle
+ private WrappedGameProfile(Object profile) {
+ if (profile == null)
+ throw new IllegalArgumentException("Profile cannot be NULL.");
+ if (!(profile instanceof GameProfile))
+ throw new IllegalArgumentException(profile + " is not a GameProfile");
+ this.profile = (GameProfile) profile;
+ }
+
+ /**
+ * Construct a new game profile with the given properties.
+ * @param id - the UUID of the player.
+ * @param name - the name of the player.
+ */
+ public WrappedGameProfile(String id, String name) {
+ this(new GameProfile(id, name));
+ }
+
+ /**
+ * Construct a wrapper around an existing game profile.
+ * @param profile - the underlying profile.
+ */
+ public static WrappedGameProfile fromHandle(Object handle) {
+ return new WrappedGameProfile(handle);
+ }
+
+ /**
+ * Retrieve the underlying game profile.
+ * @return The profile.
+ */
+ public Object getHandle() {
+ return profile;
+ }
+
+ /**
+ * Retrieve the UUID of the player.
+ * @return The UUID of the player, or NULL if not computed.
+ */
+ public String getId() {
+ return profile.getId();
+ }
+
+ /**
+ * Retrieve the name of the player.
+ * @return The player name.
+ */
+ public String getName() {
+ return profile.getName();
+ }
+
+ /**
+ * Construct a new game profile with the same ID, but different id.
+ * @param name - the new name of the profile to create.
+ * @return The new game profile.
+ */
+ public WrappedGameProfile withName(String name) {
+ return new WrappedGameProfile(getId(), name);
+ }
+
+ /**
+ * Construct a new game profile with the same name, but different id.
+ * @param id - the new id of the profile to create.
+ * @return The new game profile.
+ */
+ public WrappedGameProfile withId(String id) {
+ return new WrappedGameProfile(id, getName());
+ }
+
+ /**
+ * Determine if the game profile contains both an UUID and a name.
+ * @return TRUE if it does, FALSE otherwise.
+ */
+ public boolean isComplete() {
+ return profile.isComplete();
+ }
+
+ @Override
+ public int hashCode() {
+ return profile.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+
+ if (obj instanceof WrappedGameProfile) {
+ WrappedGameProfile other = (WrappedGameProfile) obj;
+ return profile.equals(other.profile);
+ }
+ return false;
+ }
+}
diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java
index 405d2d54..1738f471 100644
--- a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java
+++ b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java
@@ -52,6 +52,7 @@ import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.ChunkPosition;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
+import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
@@ -320,6 +321,15 @@ public class PacketContainerTest {
assertEquals(list, watchableAccessor.read(0));
}
+ @Test
+ public void testGameProfiles() {
+ PacketContainer spawnEntity = new PacketContainer(PacketType.Play.Server.NAMED_ENTITY_SPAWN);
+ WrappedGameProfile profile = new WrappedGameProfile("id", "name");
+ spawnEntity.getGameProfiles().write(0, profile);
+
+ assertEquals(profile, spawnEntity.getGameProfiles().read(0));
+ }
+
@Test
public void testSerialization() {
PacketContainer chat = new PacketContainer(PacketType.Play.Client.CHAT);
diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java
index ab891d21..47317ed6 100644
--- a/ProtocolLib/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java
+++ b/ProtocolLib/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java
@@ -4,6 +4,7 @@ import static org.junit.Assert.*;
import net.minecraft.server.v1_7_R1.NBTCompressedStreamTools;
import net.minecraft.util.com.google.common.collect.Maps;
+import net.minecraft.util.com.mojang.authlib.GameProfile;
import org.junit.AfterClass;
import org.junit.BeforeClass;
@@ -27,7 +28,7 @@ public class MinecraftReflectionTest {
public static void undoMocking() {
MinecraftReflection.minecraftPackage = null;
}
-
+
@Test
public void testNbtStreamTools() {
assertEquals(NBTCompressedStreamTools.class, MinecraftReflection.getNbtCompressedStreamToolsClass());