Archiviert
13
0

Add backwards compatibility for versions 1.0 thru 1.7.10

Dieser Commit ist enthalten in:
Dan Mulloy 2015-03-29 22:56:11 -04:00
Ursprung 2c743e1c89
Commit addfacb19c
13 geänderte Dateien mit 475 neuen und 257 gelöschten Zeilen

Datei anzeigen

@ -163,3 +163,5 @@ pip-log.txt
# Mac crap # Mac crap
.DS_Store .DS_Store
/target
/target

Datei anzeigen

@ -12,7 +12,7 @@
<properties> <properties>
<project.build.sourceEncoding>cp1252</project.build.sourceEncoding> <project.build.sourceEncoding>cp1252</project.build.sourceEncoding>
<powermock.version>1.5</powermock.version> <jarName>ProtocolLib</jarName>
</properties> </properties>
<distributionManagement> <distributionManagement>
@ -75,6 +75,7 @@
<exclude>org.spigotmc:spigot</exclude> <exclude>org.spigotmc:spigot</exclude>
<exclude>org.spigotmc:spigot-api</exclude> <exclude>org.spigotmc:spigot-api</exclude>
<exclude>junit:junit</exclude> <exclude>junit:junit</exclude>
<exclude>com.google*</exclude>
</excludes> </excludes>
</artifactSet> </artifactSet>
</configuration> </configuration>
@ -99,7 +100,7 @@
<archive> <archive>
<addMavenDescriptor>false</addMavenDescriptor> <addMavenDescriptor>false</addMavenDescriptor>
</archive> </archive>
<finalName>${project.name}</finalName> <finalName>${jarName}</finalName>
</configuration> </configuration>
</plugin> </plugin>
@ -186,6 +187,62 @@
</plugins> </plugins>
</build> </build>
</profile> </profile>
<profile>
<id>backwards-compat</id>
<properties>
<jarName>ProtocolLib-Legacy</jarName>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>net.sf</pattern>
<shadedPattern>com.comphenix.net.sf</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.common</pattern>
<shadedPattern>com.comphenix.protocol.compat.com.google.common</shadedPattern>
</relocation>
<relocation>
<pattern>io.netty</pattern>
<shadedPattern>net.minecraft.util.io.netty</shadedPattern>
</relocation>
</relocations>
<artifactSet>
<excludes>
<exclude>org.spigotmc:spigot</exclude>
<exclude>org.spigotmc:spigot-api</exclude>
<exclude>junit:junit</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles> </profiles>
<scm> <scm>
@ -264,14 +321,24 @@
<dependency> <dependency>
<groupId>org.powermock</groupId> <groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId> <artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version> <version>1.5</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.powermock</groupId> <groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId> <artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version> <version>1.5</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>17.0</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.26.Final</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

Datei anzeigen

@ -83,7 +83,7 @@ public class ProtocolLibrary extends JavaPlugin {
/** /**
* The minimum version ProtocolLib has been tested with. * The minimum version ProtocolLib has been tested with.
*/ */
public static final String MINIMUM_MINECRAFT_VERSION = "1.8"; public static final String MINIMUM_MINECRAFT_VERSION = "1.0";
/** /**
* The maximum version ProtocolLib has been tested with, * The maximum version ProtocolLib has been tested with,

Datei anzeigen

@ -50,9 +50,9 @@ import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftMethods; import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftProtocolVersion; import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.MapMaker; import com.google.common.collect.MapMaker;
import com.mojang.authlib.GameProfile;
/** /**
* Represents a channel injector. * Represents a channel injector.
@ -563,13 +563,14 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
PACKET_LOGIN_CLIENT = loginClass; PACKET_LOGIN_CLIENT = loginClass;
} }
if (loginClient == null) { if (loginClient == null) {
loginClient = Accessors.getFieldAccessor(PACKET_LOGIN_CLIENT, GameProfile.class, true); loginClient = Accessors.getFieldAccessor(PACKET_LOGIN_CLIENT, MinecraftReflection.getGameProfileClass(), true);
LOGIN_GAME_PROFILE = loginClient; LOGIN_GAME_PROFILE = loginClient;
} }
// See if we are dealing with the login packet // See if we are dealing with the login packet
if (loginClass.equals(packetClass)) { if (loginClass.equals(packetClass)) {
GameProfile profile = (GameProfile) loginClient.get(packet); // GameProfile profile = (GameProfile) loginClient.get(packet);
WrappedGameProfile profile = WrappedGameProfile.fromHandle(loginClient.get(packet));
// Save the channel injector // Save the channel injector
factory.cacheInjector(profile.getName(), this); factory.cacheInjector(profile.getName(), this);

Datei anzeigen

@ -12,8 +12,10 @@ import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.injector.packet.MapContainer; import com.comphenix.protocol.injector.packet.MapContainer;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.BiMap; import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap; import com.google.common.collect.HashBiMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@ -109,6 +111,12 @@ public class NettyProtocolRegistry {
private synchronized void initialize() { private synchronized void initialize() {
Object[] protocols = enumProtocol.getEnumConstants(); Object[] protocols = enumProtocol.getEnumConstants();
// TODO: Fins a better less than 1.7 check
if (MinecraftVersion.getCurrentVersion().compareTo(MinecraftVersion.BOUNTIFUL_UPDATE) <= 0) {
initialize17();
return;
}
// ID to Packet class maps // ID to Packet class maps
Map<Object, Map<Integer, Class<?>>> serverMaps = Maps.newLinkedHashMap(); Map<Object, Map<Integer, Class<?>>> serverMaps = Maps.newLinkedHashMap();
Map<Object, Map<Integer, Class<?>>> clientMaps = Maps.newLinkedHashMap(); Map<Object, Map<Integer, Class<?>>> clientMaps = Maps.newLinkedHashMap();
@ -140,13 +148,13 @@ public class NettyProtocolRegistry {
result.containers.add(new MapContainer(map)); result.containers.add(new MapContainer(map));
} }
// // Heuristic - there are more server packets than client packets // Heuristic - there are more server packets than client packets
// if (sum(clientMaps) > sum(serverMaps)) { /* if (sum(clientMaps) > sum(serverMaps)) {
// // Swap if this is violated // Swap if this is violated
// List<Map<Integer, Class<?>>> temp = serverMaps; List<Map<Integer, Class<?>>> temp = serverMaps;
// serverMaps = clientMaps; serverMaps = clientMaps;
// clientMaps = temp; clientMaps = temp;
// } } */
for (int i = 0; i < protocols.length; i++) { for (int i = 0; i < protocols.length; i++) {
Object protocol = protocols[i]; Object protocol = protocols[i];
@ -159,6 +167,50 @@ public class NettyProtocolRegistry {
if (clientMaps.containsKey(protocol)) if (clientMaps.containsKey(protocol))
associatePackets(result, clientMaps.get(protocol), equivalent, Sender.CLIENT); associatePackets(result, clientMaps.get(protocol), equivalent, Sender.CLIENT);
} }
// Exchange (thread safe, as we have only one writer)
this.register = result;
}
private synchronized void initialize17() {
final Object[] protocols = enumProtocol.getEnumConstants();
List<Map<Integer, Class<?>>> serverMaps = Lists.newArrayList();
List<Map<Integer, Class<?>>> clientMaps = Lists.newArrayList();
StructureModifier<Object> modifier = null;
// Result
Register result = new Register();
for (Object protocol : protocols) {
if (modifier == null)
modifier = new StructureModifier<Object>(protocol.getClass().getSuperclass(), false);
StructureModifier<Map<Integer, Class<?>>> maps = modifier.withTarget(protocol).withType(Map.class);
serverMaps.add(maps.read(0));
clientMaps.add(maps.read(1));
}
// Maps we have to occationally check have changed
for (Map<Integer, Class<?>> map : Iterables.concat(serverMaps, clientMaps)) {
result.containers.add(new MapContainer(map));
}
// Heuristic - there are more server packets than client packets
if (sum(clientMaps) > sum(serverMaps)) {
// Swap if this is violated
List<Map<Integer, Class<?>>> temp = serverMaps;
serverMaps = clientMaps;
clientMaps = temp;
}
for (int i = 0; i < protocols.length; i++) {
Enum<?> enumProtocol = (Enum<?>) protocols[i];
Protocol equivalent = Protocol.fromVanilla(enumProtocol);
// Associate known types
associatePackets(result, serverMaps.get(i), equivalent, Sender.SERVER);
associatePackets(result, clientMaps.get(i), equivalent, Sender.CLIENT);
}
// Exchange (thread safe, as we have only one writer) // Exchange (thread safe, as we have only one writer)
this.register = result; this.register = result;
} }
@ -175,16 +227,16 @@ public class NettyProtocolRegistry {
} }
} }
// /** /**
// * Retrieve the number of mapping in all the maps. * Retrieve the number of mapping in all the maps.
// * @param maps - iterable of maps. * @param maps - iterable of maps.
// * @return The sum of all the entries. * @return The sum of all the entries.
// */ */
// private int sum(Iterable<? extends Map<Integer, Class<?>>> maps) { private int sum(Iterable<? extends Map<Integer, Class<?>>> maps) {
// int count = 0; int count = 0;
//
// for (Map<Integer, Class<?>> map : maps) for (Map<Integer, Class<?>> map : maps)
// count += map.size(); count += map.size();
// return count; return count;
// } }
} }

Datei anzeigen

@ -1,5 +1,18 @@
/** /**
* (c) 2015 dmulloy2 * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/ */
package com.comphenix.protocol.injector.netty; package com.comphenix.protocol.injector.netty;

Datei anzeigen

@ -52,7 +52,7 @@ public final class Accessors {
* @return The field accessor. * @return The field accessor.
* @throws IllegalArgumentException If the field cannot be found. * @throws IllegalArgumentException If the field cannot be found.
*/ */
public static FieldAccessor getFieldAccessor(Class<?> instanceClass, Class<?> fieldClass, boolean forceAccess) { public static FieldAccessor getFieldAccessor(Class<?> instanceClass, Class<?> fieldClass, boolean forceAccess) {
// Get a field accessor // Get a field accessor
Field field = FuzzyReflection.fromClass(instanceClass, forceAccess).getFieldByType(null, fieldClass); Field field = FuzzyReflection.fromClass(instanceClass, forceAccess).getFieldByType(null, fieldClass);
return Accessors.getFieldAccessor(field); return Accessors.getFieldAccessor(field);
@ -65,7 +65,7 @@ public final class Accessors {
* @param forceAccess - whether or not to look for private and protected fields. * @param forceAccess - whether or not to look for private and protected fields.
* @return The accessors. * @return The accessors.
*/ */
public static FieldAccessor[] getFieldAccessorArray(Class<?> instanceClass, Class<?> fieldClass, boolean forceAccess) { public static FieldAccessor[] getFieldAccessorArray(Class<?> instanceClass, Class<?> fieldClass, boolean forceAccess) {
List<Field> fields = FuzzyReflection.fromClass(instanceClass, forceAccess).getFieldListByType(fieldClass); List<Field> fields = FuzzyReflection.fromClass(instanceClass, forceAccess).getFieldListByType(fieldClass);
FieldAccessor[] accessors = new FieldAccessor[fields.size()]; FieldAccessor[] accessors = new FieldAccessor[fields.size()];
@ -83,7 +83,7 @@ public final class Accessors {
* @return The value of that field. * @return The value of that field.
* @throws IllegalArgumentException If the field cannot be found. * @throws IllegalArgumentException If the field cannot be found.
*/ */
public static FieldAccessor getFieldAccessor(Class<?> instanceClass, String fieldName, boolean forceAccess) { public static FieldAccessor getFieldAccessor(Class<?> instanceClass, String fieldName, boolean forceAccess) {
return Accessors.getFieldAccessor(ExactReflection.fromClass(instanceClass, true).getField(fieldName)); return Accessors.getFieldAccessor(ExactReflection.fromClass(instanceClass, true).getField(fieldName));
} }
@ -120,13 +120,27 @@ public final class Accessors {
// Verify the type // Verify the type
if (fieldType.isAssignableFrom(accessor.getField().getType())) { if (fieldType.isAssignableFrom(accessor.getField().getType())) {
return accessor; return accessor;
} }
return null; return null;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
return null; return null;
} }
} }
/**
* Retrieve a method accessor for a field with the given name and equivalent type, or NULL.
* @param clazz - the declaration class.
* @param methodName - the method name.
* @return The method accessor, or NULL if not found.
*/
public static MethodAccessor getMethodAcccessorOrNull(Class<?> clazz, String methodName) {
try {
return Accessors.getMethodAccessor(clazz, methodName);
} catch (IllegalArgumentException e) {
return null;
}
}
/** /**
* Find a specific constructor in a class. * Find a specific constructor in a class.
@ -145,7 +159,7 @@ public final class Accessors {
/** /**
* Retrieve a field accessor that will cache the content of the field. * Retrieve a field accessor that will cache the content of the field.
* <p> * <p>
* Note that we don't check if the underlying field has changed after the value has been cached, * Note that we don't check if the underlying field has changed after the value has been cached,
* so it's best to use this on final fields. * so it's best to use this on final fields.
* @param inner - the accessor. * @param inner - the accessor.
* @return A cached field accessor. * @return A cached field accessor.

Datei anzeigen

@ -40,12 +40,21 @@ public class BukkitCloner implements Cloner {
List<Class<?>> classes = Lists.newArrayList(); List<Class<?>> classes = Lists.newArrayList();
classes.add(MinecraftReflection.getItemStackClass()); classes.add(MinecraftReflection.getItemStackClass());
classes.add(MinecraftReflection.getBlockPositionClass());
classes.add(MinecraftReflection.getDataWatcherClass()); classes.add(MinecraftReflection.getDataWatcherClass());
// Try to add position classes
try {
classes.add(MinecraftReflection.getBlockPositionClass());
} catch (Throwable ex) { }
try {
classes.add(MinecraftReflection.getChunkPositionClass());
} catch (Throwable ex) { }
if (MinecraftReflection.isUsingNetty()) { if (MinecraftReflection.isUsingNetty()) {
classes.add(MinecraftReflection.getServerPingClass()); classes.add(MinecraftReflection.getServerPingClass());
} }
this.clonableClasses = classes.toArray(new Class<?>[0]); this.clonableClasses = classes.toArray(new Class<?>[0]);
} }

Datei anzeigen

@ -290,7 +290,6 @@ public class DefaultInstances implements InstanceProvider {
return createInstance(type, minimum, types, params); return createInstance(type, minimum, types, params);
} }
} catch (Exception e) { } catch (Exception e) {
// Nope, we couldn't create this type. Might for instance be NotConstructableException. // Nope, we couldn't create this type. Might for instance be NotConstructableException.
} }

Datei anzeigen

@ -596,8 +596,17 @@ public class MinecraftReflection {
if (!isUsingNetty()) if (!isUsingNetty())
throw new IllegalStateException("GameProfile does not exist in version 1.6.4 and earlier."); throw new IllegalStateException("GameProfile does not exist in version 1.6.4 and earlier.");
// Yay, we can actually refer to it directly try {
return GameProfile.class; return GameProfile.class;
} catch (Throwable ex) {
// As far as I can tell, the named entity spawn packet is the only packet that uses GameProfiles
FuzzyReflection reflection = FuzzyReflection.fromClass(PacketType.Play.Server.NAMED_ENTITY_SPAWN.getPacketClass(), true);
FuzzyFieldContract contract = FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.typeMatches(FuzzyMatchers.matchRegex("(.*)(GameProfile)", 1))
.build();
return reflection.getField(contract).getType();
}
} }
/** /**

Datei anzeigen

@ -1,5 +1,18 @@
/** /**
* (c) 2015 dmulloy2 * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/ */
package com.comphenix.protocol.wrappers; package com.comphenix.protocol.wrappers;

Datei anzeigen

@ -31,24 +31,29 @@ import java.util.Set;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.ReadOnlyFieldAccessor; import com.comphenix.protocol.reflect.accessors.ReadOnlyFieldAccessor;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.collection.ConvertedMap; import com.comphenix.protocol.wrappers.collection.ConvertedMap;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterators; import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
/** /**
* Wraps a DataWatcher that is used to transmit arbitrary key-value pairs with a given entity. * Wraps a DataWatcher that is used to transmit arbitrary key-value pairs with a given entity.
@ -56,127 +61,127 @@ import com.google.common.collect.Iterators;
* @author Kristian * @author Kristian
*/ */
public class WrappedDataWatcher extends AbstractWrapper implements Iterable<WrappedWatchableObject> { public class WrappedDataWatcher extends AbstractWrapper implements Iterable<WrappedWatchableObject> {
// /** /**
// * Every custom watchable type in Spigot #1628 and above. * Every custom watchable type in Spigot #1628 and above.
// * @author Kristian * @author Kristian
// */ */
// public enum CustomType { public enum CustomType {
// BYTE_SHORT("org.spigotmc.ProtocolData$ByteShort", 0, short.class), BYTE_SHORT("org.spigotmc.ProtocolData$ByteShort", 0, short.class),
// DUAL_BYTE("org.spigotmc.ProtocolData$DualByte", 0, byte.class, byte.class), DUAL_BYTE("org.spigotmc.ProtocolData$DualByte", 0, byte.class, byte.class),
// HIDDEN_BYTE("org.spigotmc.ProtocolData$HiddenByte", 0, byte.class), HIDDEN_BYTE("org.spigotmc.ProtocolData$HiddenByte", 0, byte.class),
// INT_BYTE("org.spigotmc.ProtocolData$IntByte", 2, int.class, byte.class), INT_BYTE("org.spigotmc.ProtocolData$IntByte", 2, int.class, byte.class),
// DUAL_INT("org.spigotmc.ProtocolData$DualInt", 2, int.class, int.class); DUAL_INT("org.spigotmc.ProtocolData$DualInt", 2, int.class, int.class);
//
// private Class<?> spigotClass; private Class<?> spigotClass;
// private ConstructorAccessor constructor; private ConstructorAccessor constructor;
// private FieldAccessor secondaryValue; private FieldAccessor secondaryValue;
// private int typeId; private int typeId;
//
// private CustomType(String className, int typeId, Class<?>... parameters) { private CustomType(String className, int typeId, Class<?>... parameters) {
// try { try {
// this.spigotClass = Class.forName(className); this.spigotClass = Class.forName(className);
// this.constructor = Accessors.getConstructorAccessor(spigotClass, parameters); this.constructor = Accessors.getConstructorAccessor(spigotClass, parameters);
// this.secondaryValue = parameters.length > 1 ? Accessors.getFieldAccessor(spigotClass, "value2", true) : null; this.secondaryValue = parameters.length > 1 ? Accessors.getFieldAccessor(spigotClass, "value2", true) : null;
//
// } catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
// ProtocolLibrary.log(Level.WARNING, "Unable to find " + className); ProtocolLibrary.log(Level.WARNING, "Unable to find " + className);
// this.spigotClass = null; this.spigotClass = null;
// } }
// this.typeId = typeId; this.typeId = typeId;
// } }
//
// /** /**
// * Construct a new instance of this Spigot type. * Construct a new instance of this Spigot type.
// * @param value - the value. Cannot be NULL. * @param value - the value. Cannot be NULL.
// * @return The instance to construct. * @return The instance to construct.
// */ */
// Object newInstance(Object value) { Object newInstance(Object value) {
// return newInstance(value, null); return newInstance(value, null);
// } }
//
// /** /**
// * Construct a new instance of this Spigot type. * Construct a new instance of this Spigot type.
// * <p> * <p>
// * The secondary value may be NULL if this custom type does not contain a secondary value. * The secondary value may be NULL if this custom type does not contain a secondary value.
// * @param value - the value. * @param value - the value.
// * @param secondary - optional secondary value. * @param secondary - optional secondary value.
// * @return * @return
// */ */
// Object newInstance(Object value, Object secondary) { Object newInstance(Object value, Object secondary) {
// Preconditions.checkNotNull(value, "value cannot be NULL."); Preconditions.checkNotNull(value, "value cannot be NULL.");
//
// if (hasSecondary()) { if (hasSecondary()) {
// return constructor.invoke(value, secondary); return constructor.invoke(value, secondary);
// } else { } else {
// if (secondary != null) { if (secondary != null) {
// throw new IllegalArgumentException("Cannot construct " + this + " with a secondary value"); throw new IllegalArgumentException("Cannot construct " + this + " with a secondary value");
// } }
// return constructor.invoke(value); return constructor.invoke(value);
// } }
// } }
//
// /** /**
// * Set the secondary value of a given type. * Set the secondary value of a given type.
// * @param instance - the instance. * @param instance - the instance.
// * @param secondary - the secondary value. * @param secondary - the secondary value.
// */ */
// void setSecondary(Object instance, Object secondary) { void setSecondary(Object instance, Object secondary) {
// if (!hasSecondary()) { if (!hasSecondary()) {
// throw new IllegalArgumentException(this + " does not have a secondary value."); throw new IllegalArgumentException(this + " does not have a secondary value.");
// } }
// secondaryValue.set(instance, secondary); secondaryValue.set(instance, secondary);
// } }
//
// /** /**
// * Get the secondary value of a type. * Get the secondary value of a type.
// * @param instance - the instance. * @param instance - the instance.
// * @return The secondary value. * @return The secondary value.
// */ */
// Object getSecondary(Object instance) { Object getSecondary(Object instance) {
// if (!hasSecondary()) { if (!hasSecondary()) {
// throw new IllegalArgumentException(this + " does not have a secondary value."); throw new IllegalArgumentException(this + " does not have a secondary value.");
// } }
// return secondaryValue.get(instance); return secondaryValue.get(instance);
// } }
//
// /** /**
// * Determine if this type has a secondary value. * Determine if this type has a secondary value.
// * @return TRUE if it does, FALSE otherwise. * @return TRUE if it does, FALSE otherwise.
// */ */
// public boolean hasSecondary() { public boolean hasSecondary() {
// return secondaryValue != null; return secondaryValue != null;
// } }
//
// /** /**
// * Underlying Spigot class. * Underlying Spigot class.
// * @return The class. * @return The class.
// */ */
// public Class<?> getSpigotClass() { public Class<?> getSpigotClass() {
// return spigotClass; return spigotClass;
// } }
//
// /** /**
// * The equivalent type ID. * The equivalent type ID.
// * @return The equivalent ID. * @return The equivalent ID.
// */ */
// public int getTypeId() { public int getTypeId() {
// return typeId; return typeId;
// } }
//
// /** /**
// * Retrieve the custom Spigot type of a value. * Retrieve the custom Spigot type of a value.
// * @param value - the value. * @param value - the value.
// * @return The Spigot type, or NULL if not found. * @return The Spigot type, or NULL if not found.
// */ */
// public static CustomType fromValue(Object value) { public static CustomType fromValue(Object value) {
// for (CustomType type : CustomType.values()) { for (CustomType type : CustomType.values()) {
// if (type.getSpigotClass().isInstance(value)) { if (type.getSpigotClass().isInstance(value)) {
// return type; return type;
// } }
// } }
// return null; return null;
// } }
// } }
/** /**
* Used to assign integer IDs to given types. * Used to assign integer IDs to given types.
@ -601,20 +606,20 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
} }
} }
// /** /**
// * Set a watched byte with an optional secondary value. * Set a watched byte with an optional secondary value.
// * @param index - index of the watched byte. * @param index - index of the watched byte.
// * @param newValue - the new watched value. * @param newValue - the new watched value.
// * @param secondary - optional secondary value. * @param secondary - optional secondary value.
// * @param update - whether or not to refresh every listening client. * @param update - whether or not to refresh every listening client.
// * @throws FieldAccessException Cannot read underlying field. * @throws FieldAccessException Cannot read underlying field.
// */ */
// public void setObject(int index, Object newValue, Object secondary, boolean update, CustomType type) throws FieldAccessException { public void setObject(int index, Object newValue, Object secondary, boolean update, CustomType type) throws FieldAccessException {
// Object created = type.newInstance(newValue, secondary); Object created = type.newInstance(newValue, secondary);
//
// // Now update the watcher // Now update the watcher
// setObject(index, created, update); setObject(index, created, update);
// } }
private Object getWatchedObject(int index) throws FieldAccessException { private Object getWatchedObject(int index) throws FieldAccessException {
// We use the get-method first and foremost // We use the get-method first and foremost
@ -718,7 +723,7 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
initializeSpigot(fuzzy); initializeSpigot(fuzzy);
// Any custom types // Any custom types
// CUSTOM_MAP = initializeCustom(); CUSTOM_MAP = initializeCustom();
// Initialize static type type // Initialize static type type
TYPE_MAP = (Map<Class<?>, Integer>) TYPE_MAP_ACCESSOR.get(null); TYPE_MAP = (Map<Class<?>, Integer>) TYPE_MAP_ACCESSOR.get(null);
@ -737,17 +742,17 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
initializeMethods(fuzzy); initializeMethods(fuzzy);
} }
// // For Spigot's bountiful update patch // For Spigot's bountiful update patch
// private static Map<Class<?>, Integer> initializeCustom() { private static Map<Class<?>, Integer> initializeCustom() {
// Map<Class<?>, Integer> map = Maps.newHashMap(); Map<Class<?>, Integer> map = Maps.newHashMap();
//
// for (CustomType type : CustomType.values()) { for (CustomType type : CustomType.values()) {
// if (type.getSpigotClass() != null) { if (type.getSpigotClass() != null) {
// map.put(type.getSpigotClass(), type.getTypeId()); map.put(type.getSpigotClass(), type.getTypeId());
// } }
// } }
// return map; return map;
// } }
// TODO: Remove, as this was fixed in build #1189 of Spigot // TODO: Remove, as this was fixed in build #1189 of Spigot
private static void initializeSpigot(FuzzyReflection fuzzy) { private static void initializeSpigot(FuzzyReflection fuzzy) {

Datei anzeigen

@ -14,13 +14,12 @@ import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.collection.ConvertedMultimap; import com.comphenix.protocol.wrappers.collection.ConvertedMultimap;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
/** /**
* Represents a wrapper for a game profile. * Represents a wrapper for a game profile.
@ -28,62 +27,79 @@ import com.mojang.authlib.properties.Property;
*/ */
public class WrappedGameProfile extends AbstractWrapper { public class WrappedGameProfile extends AbstractWrapper {
public static final ReportType REPORT_INVALID_UUID = new ReportType("Plugin %s created a profile with '%s' as an UUID."); public static final ReportType REPORT_INVALID_UUID = new ReportType("Plugin %s created a profile with '%s' as an UUID.");
// Version 1.7.2 and 1.7.8 respectively private static final Class<?> GAME_PROFILE = MinecraftReflection.getGameProfileClass();
private static final ConstructorAccessor CREATE_STRING_STRING = Accessors.getConstructorAccessorOrNull(GameProfile.class, String.class, String.class);
private static final FieldAccessor GET_UUID_STRING = Accessors.getFieldAcccessorOrNull(GameProfile.class, "id", String.class); private static final ConstructorAccessor CREATE_STRING_STRING = Accessors.getConstructorAccessorOrNull(
GAME_PROFILE, String.class, String.class);
private static final ConstructorAccessor CREATE_UUID_STRING = Accessors.getConstructorAccessorOrNull(
GAME_PROFILE, UUID.class, String.class);
private static final FieldAccessor GET_UUID_STRING = Accessors.getFieldAcccessorOrNull(
GAME_PROFILE, "id", String.class);
private static final MethodAccessor GET_ID = Accessors.getMethodAcccessorOrNull(
GAME_PROFILE, "getId");
private static final MethodAccessor GET_NAME = Accessors.getMethodAcccessorOrNull(
GAME_PROFILE, "getName");
private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAcccessorOrNull(
GAME_PROFILE, "getProperties");
private static final MethodAccessor IS_COMPLETE = Accessors.getMethodAcccessorOrNull(
GAME_PROFILE, "isComplete");
// Fetching game profile // Fetching game profile
private static FieldAccessor PLAYER_PROFILE; private static FieldAccessor PLAYER_PROFILE;
private static FieldAccessor OFFLINE_PROFILE; private static FieldAccessor OFFLINE_PROFILE;
// Property map // Property map
private Multimap<String, WrappedSignedProperty> propertyMap; private Multimap<String, WrappedSignedProperty> propertyMap;
// Parsed UUID // Parsed UUID
private volatile UUID parsedUUID; private volatile UUID parsedUUID;
// Profile from a handle // Profile from a handle
private WrappedGameProfile(Object profile) { private WrappedGameProfile(Object profile) {
super(GameProfile.class); super(GAME_PROFILE);
setHandle(profile); setHandle(profile);
} }
/** /**
* Retrieve the associated game profile of a player. * Retrieve the associated game profile of a player.
* <p> * <p>
* Note that this may not exist in the current Minecraft version. * Note that this may not exist in the current Minecraft version.
*
* @param player - the player. * @param player - the player.
* @return The game profile. * @return The game profile.
*/ */
public static WrappedGameProfile fromPlayer(Player player) { public static WrappedGameProfile fromPlayer(Player player) {
FieldAccessor accessor = PLAYER_PROFILE; FieldAccessor accessor = PLAYER_PROFILE;
Object nmsPlayer = BukkitUnwrapper.getInstance().unwrapItem(player);
if (accessor == null) { if (accessor == null) {
accessor = Accessors.getFieldAccessor(MinecraftReflection.getEntityHumanClass(), GameProfile.class, true); accessor = Accessors.getFieldAccessor(MinecraftReflection.getEntityHumanClass(), GAME_PROFILE, true);
PLAYER_PROFILE = accessor; PLAYER_PROFILE = accessor;
} }
Object nmsPlayer = BukkitUnwrapper.getInstance().unwrapItem(player);
return WrappedGameProfile.fromHandle(PLAYER_PROFILE.get(nmsPlayer)); return WrappedGameProfile.fromHandle(PLAYER_PROFILE.get(nmsPlayer));
} }
/** /**
* Retrieve the associated game profile of an offline player. * Retrieve the associated game profile of an offline player.
* <p> * <p>
* Note that this may not exist in the current Minecraft version. * Note that this may not exist in the current Minecraft version.
*
* @param player - the offline player. * @param player - the offline player.
* @return The game profile. * @return The game profile.
*/ */
public static WrappedGameProfile fromOfflinePlayer(OfflinePlayer player) { public static WrappedGameProfile fromOfflinePlayer(OfflinePlayer player) {
FieldAccessor accessor = OFFLINE_PROFILE; FieldAccessor accessor = OFFLINE_PROFILE;
if (accessor == null) { if (accessor == null) {
accessor = Accessors.getFieldAccessor(player.getClass(), GameProfile.class, true); accessor = Accessors.getFieldAccessor(player.getClass(), GAME_PROFILE, true);
OFFLINE_PROFILE = accessor; OFFLINE_PROFILE = accessor;
} }
return WrappedGameProfile.fromHandle(OFFLINE_PROFILE.get(player)); return WrappedGameProfile.fromHandle(OFFLINE_PROFILE.get(player));
} }
/** /**
* Construct a new game profile with the given properties. * Construct a new game profile with the given properties.
* <p> * <p>
@ -91,49 +107,58 @@ public class WrappedGameProfile extends AbstractWrapper {
* IDs that cannot be parsed as an UUID will be hashed and form a version 3 UUID instead. * IDs that cannot be parsed as an UUID will be hashed and form a version 3 UUID instead.
* <p> * <p>
* This method is deprecated for Minecraft 1.7.8 and above. * This method is deprecated for Minecraft 1.7.8 and above.
*
* @param id - the UUID of the player. * @param id - the UUID of the player.
* @param name - the name of the player. * @param name - the name of the player.
*/ */
@Deprecated @Deprecated
public WrappedGameProfile(String id, String name) { public WrappedGameProfile(String id, String name) {
super(GameProfile.class); super(GAME_PROFILE);
if (CREATE_STRING_STRING != null) { if (CREATE_STRING_STRING != null) {
setHandle(CREATE_STRING_STRING.invoke(id, name)); setHandle(CREATE_STRING_STRING.invoke(id, name));
} else if (CREATE_UUID_STRING != null) {
setHandle(CREATE_UUID_STRING.invoke(parseUUID(id), name));
} else { } else {
setHandle(new GameProfile(parseUUID(id), name)); throw new IllegalArgumentException("Unsupported GameProfile constructor.");
} }
} }
/** /**
* Construct a new game profile with the given properties. * Construct a new game profile with the given properties.
* <p> * <p>
* Note that at least one of the parameters must be non-null. * Note that at least one of the parameters must be non-null.
*
* @param uuid - the UUID of the player, or NULL. * @param uuid - the UUID of the player, or NULL.
* @param name - the name of the player, or NULL. * @param name - the name of the player, or NULL.
*/ */
public WrappedGameProfile(UUID uuid, String name) { public WrappedGameProfile(UUID uuid, String name) {
super(GameProfile.class); super(GAME_PROFILE);
if (CREATE_STRING_STRING != null) { if (CREATE_STRING_STRING != null) {
setHandle(CREATE_STRING_STRING.invoke(uuid != null ? uuid.toString() : null, name)); setHandle(CREATE_STRING_STRING.invoke(uuid != null ? uuid.toString() : null, name));
} else if (CREATE_UUID_STRING != null) {
setHandle(CREATE_UUID_STRING.invoke(uuid, name));
} else { } else {
setHandle(new GameProfile(uuid, name)); throw new IllegalArgumentException("Unsupported GameProfile constructor.");
} }
} }
/** /**
* Construct a wrapper around an existing game profile. * Construct a wrapper around an existing game profile.
*
* @param profile - the underlying profile, or NULL. * @param profile - the underlying profile, or NULL.
*/ */
public static WrappedGameProfile fromHandle(Object handle) { public static WrappedGameProfile fromHandle(Object handle) {
if (handle == null) if (handle == null)
return null; return null;
return new WrappedGameProfile(handle); return new WrappedGameProfile(handle);
} }
/** /**
* Parse an UUID using very lax rules, as specified in {@link #WrappedGameProfile(UUID, String)}. * Parse an UUID using very lax rules, as specified in {@link #WrappedGameProfile(UUID, String)}.
*
* @param id - text. * @param id - text.
* @return The corresponding UUID. * @return The corresponding UUID.
* @throws IllegalArgumentException If we cannot parse the text. * @throws IllegalArgumentException If we cannot parse the text.
@ -143,13 +168,10 @@ public class WrappedGameProfile extends AbstractWrapper {
return id != null ? UUID.fromString(id) : null; return id != null ? UUID.fromString(id) : null;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// Warn once every hour (per plugin) // Warn once every hour (per plugin)
ProtocolLibrary.getErrorReporter().reportWarning( ProtocolLibrary.getErrorReporter()
WrappedGameProfile.class, .reportWarning(WrappedGameProfile.class, Report.newBuilder(REPORT_INVALID_UUID)
Report.newBuilder(REPORT_INVALID_UUID). .rateLimit(1, TimeUnit.HOURS)
rateLimit(1, TimeUnit.HOURS). .messageParam(PluginContext.getPluginCaller(new Exception()), id));
messageParam(PluginContext.getPluginCaller(new Exception()), id)
);
return UUID.nameUUIDFromBytes(id.getBytes(Charsets.UTF_8)); return UUID.nameUUIDFromBytes(id.getBytes(Charsets.UTF_8));
} }
} }
@ -157,70 +179,86 @@ public class WrappedGameProfile extends AbstractWrapper {
/** /**
* Retrieve the UUID of the player. * Retrieve the UUID of the player.
* <p> * <p>
* Note that Minecraft 1.7.5 and earlier doesn't use UUIDs internally, and it may not be possible * Note that Minecraft 1.7.5 and earlier doesn't use UUIDs internally, and it may not be possible to convert the string to an UUID.
* to convert the string to an UUID.
* <p> * <p>
* We use the same lax conversion as in {@link #WrappedGameProfile(String, String)}. * We use the same lax conversion as in {@link #WrappedGameProfile(String, String)}.
*
* @return The UUID, or NULL if the UUID is NULL. * @return The UUID, or NULL if the UUID is NULL.
* @throws IllegalStateException If we cannot parse the internal ID as an UUID. * @throws IllegalStateException If we cannot parse the internal ID as an UUID.
*/ */
public UUID getUUID() { public UUID getUUID() {
UUID uuid = parsedUUID; UUID uuid = parsedUUID;
if (uuid == null) { if (uuid == null) {
try { try {
if (GET_UUID_STRING != null) { if (GET_UUID_STRING != null) {
uuid = parseUUID(getId()); uuid = parseUUID(getId());
} else if (GET_ID != null) {
uuid = (UUID) GET_ID.invoke(handle);
} else { } else {
uuid = getProfile().getId(); throw new IllegalStateException("Unsupported getId() method");
} }
// Cache for later // Cache for later
parsedUUID = uuid; parsedUUID = uuid;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new IllegalStateException("Cannot parse ID " + getId() + " as an UUID in player profile " + getName()); throw new IllegalStateException("Cannot parse ID " + getId() + " as an UUID in player profile " + getName(), e);
} }
} }
return uuid; return uuid;
} }
/** /**
* Retrieve the textual representation of the player's UUID. * Retrieve the textual representation of the player's UUID.
* <p> * <p>
* Note that there's nothing stopping plugins from creating non-standard UUIDs. * Note that there's nothing stopping plugins from creating non-standard UUIDs.
* <p> * <p>
* In Minecraft 1.7.8 and later, this simply returns the string form of {@link #getUUID()}. * In Minecraft 1.7.8 and later, this simply returns the string form of {@link #getUUID()}.
*
* @return The UUID of the player, or NULL if not computed. * @return The UUID of the player, or NULL if not computed.
*/ */
public String getId() { public String getId() {
if (GET_UUID_STRING != null) if (GET_UUID_STRING != null) {
return (String) GET_UUID_STRING.get(handle); return (String) GET_UUID_STRING.get(handle);
final GameProfile profile = getProfile(); } else if (GET_ID != null) {
return profile.getId() != null ? profile.getId().toString() : null; UUID uuid = (UUID) GET_ID.invoke(handle);
return uuid != null ? uuid.toString() : null;
} else {
throw new IllegalStateException("Unsupported getId() method");
}
} }
/** /**
* Retrieve the name of the player. * Retrieve the name of the player.
*
* @return The player name. * @return The player name.
*/ */
public String getName() { public String getName() {
return getProfile().getName(); if (GET_NAME != null) {
return (String) GET_NAME.invoke(handle);
} else {
throw new IllegalStateException("Unsupported getName() method");
}
} }
/** /**
* Retrieve the property map of signed values. * Retrieve the property map of signed values.
*
* @return Property map. * @return Property map.
*/ */
@SuppressWarnings({ "unchecked", "rawtypes" })
public Multimap<String, WrappedSignedProperty> getProperties() { public Multimap<String, WrappedSignedProperty> getProperties() {
Multimap<String, WrappedSignedProperty> result = propertyMap; Multimap<String, WrappedSignedProperty> result = propertyMap;
if (result == null) { if (result == null) {
result = new ConvertedMultimap<String, Property, WrappedSignedProperty>( Multimap properties = (Multimap) GET_PROPERTIES.invoke(handle);
GuavaWrappers.getBukkitMultimap(getProfile().getProperties())) { result = new ConvertedMultimap<String, Object, WrappedSignedProperty>(GuavaWrappers.getBukkitMultimap(properties)) {
@Override @Override
protected Property toInner(WrappedSignedProperty outer) { protected Object toInner(WrappedSignedProperty outer) {
return (Property) outer.handle; return outer.handle;
} }
@Override @Override
protected Object toInnerObject(Object outer) { protected Object toInnerObject(Object outer) {
if (outer instanceof WrappedSignedProperty) { if (outer instanceof WrappedSignedProperty) {
@ -228,9 +266,9 @@ public class WrappedGameProfile extends AbstractWrapper {
} }
return outer; return outer;
} }
@Override @Override
protected WrappedSignedProperty toOuter(Property inner) { protected WrappedSignedProperty toOuter(Object inner) {
return WrappedSignedProperty.fromHandle(inner); return WrappedSignedProperty.fromHandle(inner);
} }
}; };
@ -238,61 +276,57 @@ public class WrappedGameProfile extends AbstractWrapper {
} }
return result; return result;
} }
/**
* Retrieve the underlying GameProfile.
* @return The GameProfile.
*/
private GameProfile getProfile() {
return (GameProfile) handle;
}
/** /**
* Construct a new game profile with the same ID, but different name. * Construct a new game profile with the same ID, but different name.
*
* @param name - the new name of the profile to create. * @param name - the new name of the profile to create.
* @return The new game profile. * @return The new game profile.
*/ */
public WrappedGameProfile withName(String name) { public WrappedGameProfile withName(String name) {
return new WrappedGameProfile(getId(), name); return new WrappedGameProfile(getId(), name);
} }
/** /**
* Construct a new game profile with the same name, but different id. * Construct a new game profile with the same name, but different id.
*
* @param id - the new id of the profile to create. * @param id - the new id of the profile to create.
* @return The new game profile. * @return The new game profile.
*/ */
public WrappedGameProfile withId(String id) { public WrappedGameProfile withId(String id) {
return new WrappedGameProfile(id, getName()); return new WrappedGameProfile(id, getName());
} }
/** /**
* Determine if the game profile contains both an UUID and a name. * Determine if the game profile contains both an UUID and a name.
*
* @return TRUE if it does, FALSE otherwise. * @return TRUE if it does, FALSE otherwise.
*/ */
public boolean isComplete() { public boolean isComplete() {
return getProfile().isComplete(); return (Boolean) IS_COMPLETE.invoke(handle);
} }
@Override @Override
public String toString() { public String toString() {
return String.valueOf(getProfile()); return String.valueOf(getHandle());
} }
@Override @Override
public int hashCode() { public int hashCode() {
// Mojang's hashCode() is broken, it doesn't handle NULL id or name. So we implement our own // Mojang's hashCode() is broken, it doesn't handle NULL id or name. So we implement our own
return Objects.hashCode(getId(), getName()); return Objects.hashCode(getId(), getName());
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == this) if (obj == this)
return true; return true;
if (obj instanceof WrappedGameProfile) { if (obj instanceof WrappedGameProfile) {
WrappedGameProfile other = (WrappedGameProfile) obj; WrappedGameProfile other = (WrappedGameProfile) obj;
return Objects.equal(getProfile(), other.getProfile()); return Objects.equal(getHandle(), other.getHandle());
} }
return false; return false;
} }
} }