mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-11-24 11:36:51 +01:00
[Breaking] Update data watchers for 1.9
Basically I had to rewrite the classes since Mojang decided to muck the whole thing up. If there are any methods necessary, I'll try to readd them.
This commit is contained in:
parent
4987cd188a
commit
d359f7455f
@ -63,7 +63,6 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers;
|
|||||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||||
import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavaibleException;
|
import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavaibleException;
|
||||||
import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavaibleException.Reason;
|
import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavaibleException.Reason;
|
||||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
|
||||||
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
||||||
import com.comphenix.protocol.wrappers.nbt.NbtType;
|
import com.comphenix.protocol.wrappers.nbt.NbtType;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
@ -1247,11 +1246,8 @@ public class MinecraftReflection {
|
|||||||
* @return The ChunkPosition class.
|
* @return The ChunkPosition class.
|
||||||
*/
|
*/
|
||||||
public static Class<?> getChunkCoordinatesClass() {
|
public static Class<?> getChunkCoordinatesClass() {
|
||||||
try {
|
// TODO Figure out a fallback
|
||||||
return getMinecraftClass("ChunkCoordinates");
|
return getMinecraftClass("ChunkCoordinates");
|
||||||
} catch (RuntimeException e) {
|
|
||||||
return setMinecraftClass("ChunkCoordinates", WrappedDataWatcher.getTypeClass(6));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1288,6 +1284,10 @@ public class MinecraftReflection {
|
|||||||
* @return The WatchableObject class.
|
* @return The WatchableObject class.
|
||||||
*/
|
*/
|
||||||
public static Class<?> getWatchableObjectClass() {
|
public static Class<?> getWatchableObjectClass() {
|
||||||
|
if (dataWatcherItemExists()) {
|
||||||
|
return getDataWatcherItemClass();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return getMinecraftClass("WatchableObject", "DataWatcher$WatchableObject");
|
return getMinecraftClass("WatchableObject", "DataWatcher$WatchableObject");
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
@ -1303,6 +1303,30 @@ public class MinecraftReflection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Class<?> getDataWatcherItemClass() {
|
||||||
|
// TODO Implement a fallback
|
||||||
|
return getMinecraftClass("DataWatcher$Item");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Class<?> getDataWatcherObjectClass() {
|
||||||
|
// TODO Implement a fallback
|
||||||
|
return getMinecraftClass("DataWatcherObject");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Class<?> getDataWatcherSerializerClass() {
|
||||||
|
// TODO Implement a fallback
|
||||||
|
return getMinecraftClass("DataWatcherSerializer");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean dataWatcherItemExists() {
|
||||||
|
try {
|
||||||
|
getDataWatcherItemClass();
|
||||||
|
return true;
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the ServerConnection abstract class.
|
* Retrieve the ServerConnection abstract class.
|
||||||
* @return The ServerConnection class.
|
* @return The ServerConnection class.
|
||||||
|
@ -16,345 +16,92 @@
|
|||||||
*/
|
*/
|
||||||
package com.comphenix.protocol.wrappers;
|
package com.comphenix.protocol.wrappers;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Map.Entry;
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.bukkit.entity.Entity;
|
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
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.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.ConstructorAccessor;
|
||||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||||
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.google.common.base.Function;
|
|
||||||
import com.google.common.base.Objects;
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
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.
|
|
||||||
*
|
|
||||||
* @author Kristian
|
|
||||||
*/
|
|
||||||
public class WrappedDataWatcher extends AbstractWrapper implements Iterable<WrappedWatchableObject> {
|
public class WrappedDataWatcher extends AbstractWrapper implements Iterable<WrappedWatchableObject> {
|
||||||
/**
|
private static ConstructorAccessor dataWatcherObjectConstructor = null;
|
||||||
* Every custom watchable type in the Spigot protocol hack.
|
private static MethodAccessor getter = null;
|
||||||
* @author Kristian
|
private static MethodAccessor setter = null;
|
||||||
*/
|
|
||||||
public enum CustomType {
|
|
||||||
BYTE_SHORT("org.spigotmc.ProtocolData$ByteShort", 0, short.class),
|
|
||||||
DUAL_BYTE("org.spigotmc.ProtocolData$DualByte", 0, byte.class, byte.class),
|
|
||||||
HIDDEN_BYTE("org.spigotmc.ProtocolData$HiddenByte", 0, byte.class),
|
|
||||||
INT_BYTE("org.spigotmc.ProtocolData$IntByte", 2, int.class, byte.class),
|
|
||||||
DUAL_INT("org.spigotmc.ProtocolData$DualInt", 2, int.class, int.class);
|
|
||||||
|
|
||||||
private Class<?> spigotClass;
|
@Deprecated
|
||||||
private ConstructorAccessor constructor;
|
|
||||||
private FieldAccessor secondaryValue;
|
|
||||||
private int typeId;
|
|
||||||
|
|
||||||
private CustomType(String className, int typeId, Class<?>... parameters) {
|
|
||||||
try {
|
|
||||||
this.spigotClass = Class.forName(className);
|
|
||||||
this.constructor = Accessors.getConstructorAccessor(spigotClass, parameters);
|
|
||||||
this.secondaryValue = parameters.length > 1 ? Accessors.getFieldAccessor(spigotClass, "value2", true) : null;
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
// ProtocolLibrary.log(Level.WARNING, "Unable to find " + className);
|
|
||||||
this.spigotClass = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.typeId = typeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a new instance of this Spigot type.
|
|
||||||
* @param value - the value. Cannot be NULL.
|
|
||||||
* @return The instance to construct.
|
|
||||||
*/
|
|
||||||
Object newInstance(Object value) {
|
|
||||||
return newInstance(value, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a new instance of this Spigot type.
|
|
||||||
* <p>
|
|
||||||
* The secondary value may be NULL if this custom type does not contain a secondary value.
|
|
||||||
* @param value - the value.
|
|
||||||
* @param secondary - optional secondary value.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
Object newInstance(Object value, Object secondary) {
|
|
||||||
Preconditions.checkNotNull(value, "value cannot be NULL.");
|
|
||||||
|
|
||||||
if (hasSecondary()) {
|
|
||||||
return constructor.invoke(value, secondary);
|
|
||||||
} else {
|
|
||||||
if (secondary != null) {
|
|
||||||
throw new IllegalArgumentException("Cannot construct " + this + " with a secondary value");
|
|
||||||
}
|
|
||||||
return constructor.invoke(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the secondary value of a given type.
|
|
||||||
* @param instance - the instance.
|
|
||||||
* @param secondary - the secondary value.
|
|
||||||
*/
|
|
||||||
void setSecondary(Object instance, Object secondary) {
|
|
||||||
if (!hasSecondary()) {
|
|
||||||
throw new IllegalArgumentException(this + " does not have a secondary value.");
|
|
||||||
}
|
|
||||||
secondaryValue.set(instance, secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the secondary value of a type.
|
|
||||||
* @param instance - the instance.
|
|
||||||
* @return The secondary value.
|
|
||||||
*/
|
|
||||||
Object getSecondary(Object instance) {
|
|
||||||
if (!hasSecondary()) {
|
|
||||||
throw new IllegalArgumentException(this + " does not have a secondary value.");
|
|
||||||
}
|
|
||||||
return secondaryValue.get(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if this type has a secondary value.
|
|
||||||
* @return TRUE if it does, FALSE otherwise.
|
|
||||||
*/
|
|
||||||
public boolean hasSecondary() {
|
|
||||||
return secondaryValue != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Underlying Spigot class.
|
|
||||||
* @return The class.
|
|
||||||
*/
|
|
||||||
public Class<?> getSpigotClass() {
|
|
||||||
return spigotClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The equivalent type ID.
|
|
||||||
* @return The equivalent ID.
|
|
||||||
*/
|
|
||||||
public int getTypeId() {
|
|
||||||
return typeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the custom Spigot type of a value.
|
|
||||||
* @param value - the value.
|
|
||||||
* @return The Spigot type, or NULL if not found.
|
|
||||||
*/
|
|
||||||
public static CustomType fromValue(Object value) {
|
|
||||||
for (CustomType type : CustomType.values()) {
|
|
||||||
if (type.getSpigotClass().isInstance(value)) {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to assign integer IDs to given types.
|
|
||||||
*/
|
|
||||||
private static Map<Class<?>, Integer> TYPE_MAP;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom types in the bountiful update.
|
|
||||||
*/
|
|
||||||
private static Map<Class<?>, Integer> CUSTOM_MAP;
|
|
||||||
|
|
||||||
// Accessors
|
|
||||||
private static FieldAccessor TYPE_MAP_ACCESSOR;
|
|
||||||
private static FieldAccessor VALUE_MAP_ACCESSOR;
|
|
||||||
|
|
||||||
// Fields
|
|
||||||
private static Field READ_WRITE_LOCK_FIELD;
|
|
||||||
private static Field ENTITY_FIELD;
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
private static Method CREATE_KEY_VALUE_METHOD;
|
|
||||||
private static Method UPDATE_KEY_VALUE_METHOD;
|
|
||||||
private static Method GET_KEY_VALUE_METHOD;
|
|
||||||
|
|
||||||
// Constructors
|
|
||||||
private static Constructor<?> CREATE_DATA_WATCHER_CONSTRUCTOR;
|
|
||||||
|
|
||||||
// Entity methods
|
|
||||||
private volatile static Field ENTITY_DATA_FIELD;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not this class has already been initialized.
|
|
||||||
*/
|
|
||||||
private static boolean HAS_INITIALIZED;
|
|
||||||
|
|
||||||
// Lock
|
|
||||||
private ReadWriteLock readWriteLock;
|
|
||||||
|
|
||||||
// Map of watchable objects
|
|
||||||
private Map<Integer, Object> watchableObjects;
|
|
||||||
|
|
||||||
// A map view of all the watchable objects
|
|
||||||
private Map<Integer, WrappedWatchableObject> mapView;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a new data watcher.
|
|
||||||
* @throws FieldAccessException If we're unable to wrap a DataWatcher.
|
|
||||||
*/
|
|
||||||
public WrappedDataWatcher() {
|
public WrappedDataWatcher() {
|
||||||
super(MinecraftReflection.getDataWatcherClass());
|
super(MinecraftReflection.getDataWatcherClass());
|
||||||
|
|
||||||
// Just create a new watcher
|
|
||||||
try {
|
|
||||||
if (MinecraftReflection.isUsingNetty()) {
|
|
||||||
setHandle(newEntityHandle(null));
|
|
||||||
} else {
|
|
||||||
setHandle(getHandleType().newInstance());
|
|
||||||
}
|
|
||||||
initialize();
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Unable to construct DataWatcher.", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a wrapper for a given data watcher.
|
|
||||||
* @param handle - the data watcher to wrap.
|
|
||||||
* @throws FieldAccessException If we're unable to wrap a DataWatcher.
|
|
||||||
*/
|
|
||||||
public WrappedDataWatcher(Object handle) {
|
public WrappedDataWatcher(Object handle) {
|
||||||
super(MinecraftReflection.getDataWatcherClass());
|
|
||||||
setHandle(handle);
|
|
||||||
initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a new data watcher with the given entity.
|
|
||||||
* <p>
|
|
||||||
* In 1.6.4 and ealier, this will fall back to using {@link #WrappedDataWatcher()}.
|
|
||||||
* @param entity - the entity.
|
|
||||||
* @return The wrapped data watcher.
|
|
||||||
*/
|
|
||||||
public static WrappedDataWatcher newWithEntity(Entity entity) {
|
|
||||||
// Use the old constructor
|
|
||||||
if (!MinecraftReflection.isUsingNetty())
|
|
||||||
return new WrappedDataWatcher();
|
|
||||||
return new WrappedDataWatcher(newEntityHandle(entity));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a new native DataWatcher with the given entity.
|
|
||||||
* <p>
|
|
||||||
* Warning: This is only supported in 1.7.2 and above.
|
|
||||||
* @param entity - the entity, or NULL.
|
|
||||||
* @return The data watcher.
|
|
||||||
*/
|
|
||||||
private static Object newEntityHandle(Entity entity) {
|
|
||||||
Class<?> dataWatcher = MinecraftReflection.getDataWatcherClass();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (CREATE_DATA_WATCHER_CONSTRUCTOR == null)
|
|
||||||
CREATE_DATA_WATCHER_CONSTRUCTOR = dataWatcher.getConstructor(MinecraftReflection.getEntityClass());
|
|
||||||
|
|
||||||
return CREATE_DATA_WATCHER_CONSTRUCTOR.newInstance(
|
|
||||||
BukkitUnwrapper.getInstance().unwrapItem(entity)
|
|
||||||
);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Cannot construct data watcher.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new data watcher for a list of watchable objects.
|
|
||||||
* <p>
|
|
||||||
* Note that the watchable objects are not cloned, and will be modified in place. Use "deepClone" if
|
|
||||||
* that is not desirable.
|
|
||||||
* <p>
|
|
||||||
* The {@link #removeObject(int)} method will not modify the given list, however.
|
|
||||||
*
|
|
||||||
* @param watchableObjects - list of watchable objects that will be copied.
|
|
||||||
* @throws FieldAccessException Unable to read watchable objects.
|
|
||||||
*/
|
|
||||||
public WrappedDataWatcher(List<WrappedWatchableObject> watchableObjects) throws FieldAccessException {
|
|
||||||
this();
|
this();
|
||||||
|
setHandle(handle);
|
||||||
|
}
|
||||||
|
|
||||||
Lock writeLock = getReadWriteLock().writeLock();
|
@Override
|
||||||
Map<Integer, Object> map = getWatchableObjectMap();
|
public Iterator<WrappedWatchableObject> iterator() {
|
||||||
|
return getWatchableObjects().iterator();
|
||||||
|
}
|
||||||
|
|
||||||
writeLock.lock();
|
public List<WrappedWatchableObject> getWatchableObjects() {
|
||||||
|
return new ArrayList<>(asMap().values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<Integer, WrappedWatchableObject> asMap() {
|
||||||
|
FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType, true);
|
||||||
|
List<Field> candidates = fuzzy.getFieldListByType(Map.class);
|
||||||
|
|
||||||
|
Field match = null;
|
||||||
|
for (Field candidate : candidates) {
|
||||||
|
if (Modifier.isStatic(candidate.getModifiers())) {
|
||||||
|
// This is the entity class to current index map, which we really don't have a use for
|
||||||
|
} else {
|
||||||
|
// This is the map we're looking for
|
||||||
|
match = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match == null) {
|
||||||
|
throw new FieldAccessException("Could not find index -> object map.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Integer, ?> map = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Add the watchable objects by reference
|
match.setAccessible(true);
|
||||||
for (WrappedWatchableObject watched : watchableObjects) {
|
map = (Map<Integer, ?>) match.get(handle);
|
||||||
map.put(watched.getIndex(), watched.handle);
|
} catch (IllegalArgumentException e) {
|
||||||
}
|
throw new FieldAccessException(e);
|
||||||
} finally {
|
} catch (IllegalAccessException e) {
|
||||||
writeLock.unlock();
|
throw new FieldAccessException(e);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
Map<Integer, WrappedWatchableObject> ret = new HashMap<>();
|
||||||
* Retrieve the ID of a given type, if it's allowed to be watched.
|
for (Entry<Integer, ?> entry : map.entrySet()) {
|
||||||
* @param clazz - Class to check for.
|
ret.put(entry.getKey(), new WrappedWatchableObject(entry.getValue()));
|
||||||
* @return The ID, or NULL if it cannot be watched.
|
|
||||||
* @throws FieldAccessException If we cannot initialize the reflection machinery.
|
|
||||||
*/
|
|
||||||
public static Integer getTypeID(Class<?> clazz) throws FieldAccessException {
|
|
||||||
initialize();
|
|
||||||
Integer result = TYPE_MAP.get(WrappedWatchableObject.getUnwrappedType(clazz));
|
|
||||||
|
|
||||||
if (result == null) {
|
|
||||||
result = CUSTOM_MAP.get(clazz);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return ret;
|
||||||
* Retrieve the type of a given ID, if it's allowed to be watched.
|
|
||||||
* @param id - id to get type for
|
|
||||||
* @return The type using a given ID, or NULL if it cannot be watched.
|
|
||||||
* @throws FieldAccessException If we cannot initialize the reflection machinery.
|
|
||||||
*/
|
|
||||||
public static Class<?> getTypeClass(int id) throws FieldAccessException {
|
|
||||||
initialize();
|
|
||||||
|
|
||||||
for (Map.Entry<Class<?>, Integer> entry : TYPE_MAP.entrySet()) {
|
|
||||||
if (Objects.equal(entry.getValue(), id)) {
|
|
||||||
return entry.getKey();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unknown class type
|
public int size() {
|
||||||
return null;
|
return asMap().size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Object Getters
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a watched byte.
|
* Get a watched byte.
|
||||||
* @param index - index of the watched byte.
|
* @param index - index of the watched byte.
|
||||||
@ -432,49 +179,60 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
|
|||||||
* @throws FieldAccessException Cannot read underlying field.
|
* @throws FieldAccessException Cannot read underlying field.
|
||||||
*/
|
*/
|
||||||
public Object getObject(int index) throws FieldAccessException {
|
public Object getObject(int index) throws FieldAccessException {
|
||||||
Object watchable = getWatchedObject(index);
|
return getWatchedObject(index);
|
||||||
|
}
|
||||||
|
|
||||||
if (watchable != null) {
|
private Object getWatchedObject(int index) {
|
||||||
return new WrappedWatchableObject(watchable).getValue();
|
if (dataWatcherObjectConstructor == null) {
|
||||||
} else {
|
dataWatcherObjectConstructor = Accessors.getConstructorAccessor(MinecraftReflection.getDataWatcherObjectClass().getConstructors()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object object = dataWatcherObjectConstructor.invoke(index, null);
|
||||||
|
|
||||||
|
if (getter == null) {
|
||||||
|
getter = Accessors.getMethodAccessor(handleType, "get", object.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
return getter.invoke(handle, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Object Setters
|
||||||
|
|
||||||
|
public void setObject(int index, Object value) {
|
||||||
|
if (dataWatcherObjectConstructor == null) {
|
||||||
|
dataWatcherObjectConstructor = Accessors.getConstructorAccessor(MinecraftReflection.getDataWatcherObjectClass().getConstructors()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object object = dataWatcherObjectConstructor.invoke(index, null);
|
||||||
|
|
||||||
|
if (setter == null) {
|
||||||
|
setter = Accessors.getMethodAccessor(handleType, "set", object.getClass(), Object.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
setter.invoke(handle, object, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WrappedDataWatcher deepClone() {
|
||||||
|
// TODO This
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static Integer getTypeID(Class<?> clazz) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Deprecated
|
||||||
* Retrieve every watchable object in this watcher.
|
@SuppressWarnings("unused")
|
||||||
* @return Every watchable object.
|
public static Class<?> getTypeClass(int typeID) {
|
||||||
* @throws FieldAccessException If reflection failed.
|
return null;
|
||||||
*/
|
|
||||||
public List<WrappedWatchableObject> getWatchableObjects() throws FieldAccessException {
|
|
||||||
Lock readLock = getReadWriteLock().readLock();
|
|
||||||
readLock.lock();
|
|
||||||
|
|
||||||
try {
|
|
||||||
List<WrappedWatchableObject> result = new ArrayList<WrappedWatchableObject>();
|
|
||||||
|
|
||||||
// Add each watchable object to the list
|
|
||||||
for (Object watchable : getWatchableObjectMap().values()) {
|
|
||||||
if (watchable != null) {
|
|
||||||
result.add(new WrappedWatchableObject(watchable));
|
|
||||||
} else {
|
|
||||||
result.add(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
readLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
// Quick checks
|
if (obj == this) return true;
|
||||||
if (obj == this)
|
if (obj == null) return false;
|
||||||
return true;
|
|
||||||
if (obj == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (obj instanceof WrappedDataWatcher) {
|
if (obj instanceof WrappedDataWatcher) {
|
||||||
WrappedDataWatcher other = (WrappedDataWatcher) obj;
|
WrappedDataWatcher other = (WrappedDataWatcher) obj;
|
||||||
@ -489,8 +247,10 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
|
|||||||
if (!first.next().equals(second.next()))
|
if (!first.next().equals(second.next()))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -498,427 +258,4 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
|
|||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return getWatchableObjects().hashCode();
|
return getWatchableObjects().hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a copy of every index associated with a watched object.
|
|
||||||
* @return Every watched object index.
|
|
||||||
* @throws FieldAccessException If we're unable to read the underlying object.
|
|
||||||
*/
|
|
||||||
public Set<Integer> indexSet() throws FieldAccessException {
|
|
||||||
Lock readLock = getReadWriteLock().readLock();
|
|
||||||
readLock.lock();
|
|
||||||
|
|
||||||
try {
|
|
||||||
return new HashSet<Integer>(getWatchableObjectMap().keySet());
|
|
||||||
} finally {
|
|
||||||
readLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clone the content of the current DataWatcher.
|
|
||||||
* @return A cloned data watcher.
|
|
||||||
*/
|
|
||||||
public WrappedDataWatcher deepClone() {
|
|
||||||
WrappedDataWatcher clone = new WrappedDataWatcher();
|
|
||||||
|
|
||||||
// Make a new copy instead
|
|
||||||
for (WrappedWatchableObject watchable : this) {
|
|
||||||
clone.setObject(watchable.getIndex(), watchable.getClonedValue());
|
|
||||||
}
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the number of watched objects.
|
|
||||||
* @return Watched object count.
|
|
||||||
* @throws FieldAccessException If we're unable to read the underlying object.
|
|
||||||
*/
|
|
||||||
public int size() throws FieldAccessException {
|
|
||||||
Lock readLock = getReadWriteLock().readLock();
|
|
||||||
readLock.lock();
|
|
||||||
|
|
||||||
try {
|
|
||||||
return getWatchableObjectMap().size();
|
|
||||||
} finally {
|
|
||||||
readLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a given object from the underlying DataWatcher.
|
|
||||||
* @param index - index of the object to remove.
|
|
||||||
* @return The watchable object that was removed, or NULL If none could be found.
|
|
||||||
*/
|
|
||||||
public WrappedWatchableObject removeObject(int index) {
|
|
||||||
Lock writeLock = getReadWriteLock().writeLock();
|
|
||||||
writeLock.lock();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Object removed = getWatchableObjectMap().remove(index);
|
|
||||||
return removed != null ? new WrappedWatchableObject(removed) : null;
|
|
||||||
} finally {
|
|
||||||
writeLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a watched byte.
|
|
||||||
* @param index - index of the watched byte.
|
|
||||||
* @param newValue - the new watched value.
|
|
||||||
* @throws FieldAccessException Cannot read underlying field.
|
|
||||||
*/
|
|
||||||
public void setObject(int index, Object newValue) throws FieldAccessException {
|
|
||||||
setObject(index, newValue, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a watched byte.
|
|
||||||
* @param index - index of the watched byte.
|
|
||||||
* @param newValue - the new watched value.
|
|
||||||
* @param update - whether or not to refresh every listening client.
|
|
||||||
* @throws FieldAccessException Cannot read underlying field.
|
|
||||||
*/
|
|
||||||
public void setObject(int index, Object newValue, boolean update) throws FieldAccessException {
|
|
||||||
// Aquire write lock
|
|
||||||
Lock writeLock = getReadWriteLock().writeLock();
|
|
||||||
writeLock.lock();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Object watchable = getWatchedObject(index);
|
|
||||||
|
|
||||||
if (watchable != null) {
|
|
||||||
new WrappedWatchableObject(watchable).setValue(newValue, update);
|
|
||||||
} else {
|
|
||||||
CREATE_KEY_VALUE_METHOD.invoke(handle, index, WrappedWatchableObject.getUnwrapped(newValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle invoking the method
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new FieldAccessException("Cannot convert arguments.", e);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new FieldAccessException("Illegal access.", e);
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
throw new FieldAccessException("Checked exception in Minecraft.", e);
|
|
||||||
} finally {
|
|
||||||
writeLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a watched byte with an optional secondary value.
|
|
||||||
* @param index - index of the watched byte.
|
|
||||||
* @param newValue - the new watched value.
|
|
||||||
* @param secondary - optional secondary value.
|
|
||||||
* @param update - whether or not to refresh every listening client.
|
|
||||||
* @param type - custom type.
|
|
||||||
* @throws FieldAccessException If we cannot read the underlying field.
|
|
||||||
*/
|
|
||||||
public void setObject(int index, Object newValue, Object secondary, boolean update, CustomType type) throws FieldAccessException {
|
|
||||||
Object created = type.newInstance(newValue, secondary);
|
|
||||||
|
|
||||||
// Now update the watcher
|
|
||||||
setObject(index, created, update);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object getWatchedObject(int index) throws FieldAccessException {
|
|
||||||
// We use the get-method first and foremost
|
|
||||||
if (GET_KEY_VALUE_METHOD != null) {
|
|
||||||
try {
|
|
||||||
return GET_KEY_VALUE_METHOD.invoke(handle, index);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new FieldAccessException("Cannot invoke get key method for index " + index, e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
getReadWriteLock().readLock().lock();
|
|
||||||
return getWatchableObjectMap().get(index);
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
getReadWriteLock().readLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the current read write lock.
|
|
||||||
* @return Current read write lock.
|
|
||||||
* @throws FieldAccessException If we're unable to read the underlying field.
|
|
||||||
*/
|
|
||||||
protected ReadWriteLock getReadWriteLock() throws FieldAccessException {
|
|
||||||
try {
|
|
||||||
// Cache the read write lock
|
|
||||||
if (readWriteLock != null)
|
|
||||||
return readWriteLock;
|
|
||||||
else if (READ_WRITE_LOCK_FIELD != null)
|
|
||||||
return readWriteLock = (ReadWriteLock) FieldUtils.readField(READ_WRITE_LOCK_FIELD, handle, true);
|
|
||||||
else
|
|
||||||
return readWriteLock = new ReentrantReadWriteLock();
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new FieldAccessException("Unable to read lock field.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the underlying map of key values that stores watchable objects.
|
|
||||||
* @return A map of watchable objects.
|
|
||||||
* @throws FieldAccessException If we don't have permission to perform reflection.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected Map<Integer, Object> getWatchableObjectMap() throws FieldAccessException {
|
|
||||||
if (watchableObjects == null)
|
|
||||||
watchableObjects = (Map<Integer, Object>) VALUE_MAP_ACCESSOR.get(handle);
|
|
||||||
return watchableObjects;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the data watcher associated with an entity.
|
|
||||||
* @param entity - the entity to read from.
|
|
||||||
* @return Associated data watcher.
|
|
||||||
* @throws FieldAccessException Reflection failed.
|
|
||||||
*/
|
|
||||||
public static WrappedDataWatcher getEntityWatcher(Entity entity) throws FieldAccessException {
|
|
||||||
if (ENTITY_DATA_FIELD == null)
|
|
||||||
ENTITY_DATA_FIELD = FuzzyReflection.fromClass(MinecraftReflection.getEntityClass(), true).
|
|
||||||
getFieldByType("datawatcher", MinecraftReflection.getDataWatcherClass());
|
|
||||||
|
|
||||||
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Object nsmWatcher = FieldUtils.readField(ENTITY_DATA_FIELD, unwrapper.unwrapItem(entity), true);
|
|
||||||
|
|
||||||
if (nsmWatcher != null)
|
|
||||||
return new WrappedDataWatcher(nsmWatcher);
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new FieldAccessException("Cannot access DataWatcher field.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when a data watcher is first used.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static void initialize() throws FieldAccessException {
|
|
||||||
// This method should only be run once, even if an exception is thrown
|
|
||||||
if (!HAS_INITIALIZED)
|
|
||||||
HAS_INITIALIZED = true;
|
|
||||||
else
|
|
||||||
return;
|
|
||||||
|
|
||||||
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getDataWatcherClass(), true);
|
|
||||||
|
|
||||||
for (Field lookup : fuzzy.getFieldListByType(Map.class)) {
|
|
||||||
if (Modifier.isStatic(lookup.getModifiers())) {
|
|
||||||
// This must be the type map
|
|
||||||
TYPE_MAP_ACCESSOR = Accessors.getFieldAccessor(lookup, true);
|
|
||||||
} else {
|
|
||||||
// If not, then we're probably dealing with the value map
|
|
||||||
VALUE_MAP_ACCESSOR = Accessors.getFieldAccessor(lookup, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Spigot workaround (not necessary
|
|
||||||
initializeSpigot(fuzzy);
|
|
||||||
|
|
||||||
// Any custom types
|
|
||||||
CUSTOM_MAP = initializeCustom();
|
|
||||||
|
|
||||||
// Initialize static type type
|
|
||||||
TYPE_MAP = (Map<Class<?>, Integer>) TYPE_MAP_ACCESSOR.get(null);
|
|
||||||
|
|
||||||
try {
|
|
||||||
READ_WRITE_LOCK_FIELD = fuzzy.getFieldByType("readWriteLock", ReadWriteLock.class);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// It's not a big deal
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for the entity field as well
|
|
||||||
if (MinecraftReflection.isUsingNetty()) {
|
|
||||||
ENTITY_FIELD = fuzzy.getFieldByType("entity", MinecraftReflection.getEntityClass());
|
|
||||||
ENTITY_FIELD.setAccessible(true);
|
|
||||||
}
|
|
||||||
initializeMethods(fuzzy);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For Spigot's bountiful update patch
|
|
||||||
private static Map<Class<?>, Integer> initializeCustom() {
|
|
||||||
Map<Class<?>, Integer> map = Maps.newHashMap();
|
|
||||||
|
|
||||||
for (CustomType type : CustomType.values()) {
|
|
||||||
if (type.getSpigotClass() != null) {
|
|
||||||
map.put(type.getSpigotClass(), type.getTypeId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove, as this was fixed in build #1189 of Spigot
|
|
||||||
private static void initializeSpigot(FuzzyReflection fuzzy) {
|
|
||||||
// See if the workaround is needed
|
|
||||||
if (TYPE_MAP_ACCESSOR != null && VALUE_MAP_ACCESSOR != null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (Field lookup : fuzzy.getFields()) {
|
|
||||||
final Class<?> type = lookup.getType();
|
|
||||||
|
|
||||||
if (TroveWrapper.isTroveClass(type)) {
|
|
||||||
// Create a wrapper accessor
|
|
||||||
final ReadOnlyFieldAccessor accessor = TroveWrapper.wrapMapField(
|
|
||||||
Accessors.getFieldAccessor(lookup, true), new Function<Integer, Integer>() {
|
|
||||||
@Override
|
|
||||||
public Integer apply(@Nullable Integer value) {
|
|
||||||
// Do not use zero for no entry value
|
|
||||||
if (value == 0)
|
|
||||||
return -1;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Modifier.isStatic(lookup.getModifiers())) {
|
|
||||||
TYPE_MAP_ACCESSOR = accessor;
|
|
||||||
} else {
|
|
||||||
VALUE_MAP_ACCESSOR = accessor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TYPE_MAP_ACCESSOR == null)
|
|
||||||
throw new IllegalArgumentException("Unable to find static type map.");
|
|
||||||
if (VALUE_MAP_ACCESSOR == null)
|
|
||||||
throw new IllegalArgumentException("Unable to find static value map.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void initializeMethods(FuzzyReflection fuzzy) {
|
|
||||||
List<Method> candidates = fuzzy.getMethodListByParameters(Void.TYPE,
|
|
||||||
new Class<?>[] { int.class, Object.class});
|
|
||||||
|
|
||||||
// Load the get-method
|
|
||||||
try {
|
|
||||||
GET_KEY_VALUE_METHOD = fuzzy.getMethodByParameters(
|
|
||||||
"getWatchableObject", MinecraftReflection.getWatchableObjectClass(), new Class[] { int.class });
|
|
||||||
GET_KEY_VALUE_METHOD.setAccessible(true);
|
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// Use the fallback method
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Method method : candidates) {
|
|
||||||
if (!method.getName().startsWith("watch")) {
|
|
||||||
CREATE_KEY_VALUE_METHOD = method;
|
|
||||||
} else {
|
|
||||||
UPDATE_KEY_VALUE_METHOD = method;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Did we succeed?
|
|
||||||
if (UPDATE_KEY_VALUE_METHOD == null || CREATE_KEY_VALUE_METHOD == null) {
|
|
||||||
// Go by index instead
|
|
||||||
if (candidates.size() > 1) {
|
|
||||||
CREATE_KEY_VALUE_METHOD = candidates.get(0);
|
|
||||||
UPDATE_KEY_VALUE_METHOD = candidates.get(1);
|
|
||||||
} else {
|
|
||||||
//throw new IllegalStateException("Unable to find create and update watchable object. Update ProtocolLib.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Be a little scientist - see if this in fact IS the right way around
|
|
||||||
try {
|
|
||||||
WrappedDataWatcher watcher = new WrappedDataWatcher();
|
|
||||||
watcher.setObject(0, 0);
|
|
||||||
watcher.setObject(0, 1);
|
|
||||||
|
|
||||||
if (watcher.getInteger(0) != 1) {
|
|
||||||
throw new IllegalStateException("This cannot be!");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// Nope
|
|
||||||
UPDATE_KEY_VALUE_METHOD = candidates.get(0);
|
|
||||||
CREATE_KEY_VALUE_METHOD = candidates.get(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterator<WrappedWatchableObject> iterator() {
|
|
||||||
// We'll wrap the iterator instead of creating a new list every time
|
|
||||||
return Iterators.transform(getWatchableObjectMap().values().iterator(),
|
|
||||||
new Function<Object, WrappedWatchableObject>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WrappedWatchableObject apply(@Nullable Object item) {
|
|
||||||
if (item != null)
|
|
||||||
return new WrappedWatchableObject(item);
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a view of this DataWatcher as a map.
|
|
||||||
* <p>
|
|
||||||
* Any changes to the map will be reflected in this DataWatcher, and vice versa.
|
|
||||||
* @return A view of the data watcher as a map.
|
|
||||||
*/
|
|
||||||
public Map<Integer, WrappedWatchableObject> asMap() {
|
|
||||||
// Construct corresponding map
|
|
||||||
if (mapView == null) {
|
|
||||||
mapView = new ConvertedMap<Integer, Object, WrappedWatchableObject>(getWatchableObjectMap()) {
|
|
||||||
@Override
|
|
||||||
protected Object toInner(WrappedWatchableObject outer) {
|
|
||||||
if (outer == null)
|
|
||||||
return null;
|
|
||||||
return outer.getHandle();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected WrappedWatchableObject toOuter(Object inner) {
|
|
||||||
if (inner == null)
|
|
||||||
return null;
|
|
||||||
return new WrappedWatchableObject(inner);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return mapView;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return asMap().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the entity associated with this data watcher.
|
|
||||||
* <p>
|
|
||||||
* <b>Warning:</b> This is only supported on 1.7.2 and above.
|
|
||||||
* @return The entity, or NULL.
|
|
||||||
*/
|
|
||||||
public Entity getEntity() {
|
|
||||||
if (!MinecraftReflection.isUsingNetty())
|
|
||||||
throw new IllegalStateException("This method is only supported on 1.7.2 and above.");
|
|
||||||
|
|
||||||
try {
|
|
||||||
return (Entity) MinecraftReflection.getBukkitEntity(ENTITY_FIELD.get(handle));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Unable to retrieve entity.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the entity associated with this data watcher.
|
|
||||||
* <p>
|
|
||||||
* <b>Warning:</b> This is only supported on 1.7.2 and above.
|
|
||||||
* @param entity - the new entity.
|
|
||||||
*/
|
|
||||||
public void setEntity(Entity entity) {
|
|
||||||
if (!MinecraftReflection.isUsingNetty())
|
|
||||||
throw new IllegalStateException("This method is only supported on 1.7.2 and above.");
|
|
||||||
|
|
||||||
try {
|
|
||||||
ENTITY_FIELD.set(handle, BukkitUnwrapper.getInstance().unwrapItem(entity));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Unable to set entity.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,467 +1,42 @@
|
|||||||
/*
|
/**
|
||||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
* (c) 2016 dmulloy2
|
||||||
* Copyright (C) 2012 Kristian S. Stangeland
|
|
||||||
*
|
|
||||||
* 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;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
|
|
||||||
import org.bukkit.inventory.ItemStack;
|
|
||||||
|
|
||||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
|
||||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
|
||||||
import com.comphenix.protocol.reflect.StructureModifier;
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
|
||||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
import com.google.common.base.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a watchable object.
|
* @author dmulloy2
|
||||||
*
|
|
||||||
* @author Kristian
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class WrappedWatchableObject extends AbstractWrapper {
|
public class WrappedWatchableObject extends AbstractWrapper {
|
||||||
// Whether or not the reflection machinery has been initialized
|
|
||||||
private static boolean hasInitialized;
|
|
||||||
|
|
||||||
// The field containing the value itself
|
private WrappedWatchableObject() {
|
||||||
private static StructureModifier<Object> baseModifier;
|
super(MinecraftReflection.getDataWatcherItemClass());
|
||||||
|
}
|
||||||
|
|
||||||
// Used to create new watchable objects
|
|
||||||
private static Constructor<?> watchableConstructor;
|
|
||||||
|
|
||||||
// The watchable object class type
|
|
||||||
private static Class<?> watchableObjectClass;
|
|
||||||
|
|
||||||
protected StructureModifier<Object> modifier;
|
|
||||||
|
|
||||||
// Type of the stored value
|
|
||||||
private Class<?> typeClass;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrap a given raw Minecraft watchable object.
|
|
||||||
* @param handle - the raw watchable object to wrap.
|
|
||||||
*/
|
|
||||||
public WrappedWatchableObject(Object handle) {
|
public WrappedWatchableObject(Object handle) {
|
||||||
super(MinecraftReflection.getWatchableObjectClass());
|
this();
|
||||||
load(handle);
|
setHandle(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Object getValue() {
|
||||||
* Construct a watchable object from an index and a given value.
|
return new StructureModifier<Object>(handleType).withTarget(this).read(1);
|
||||||
* @param index - the index.
|
|
||||||
* @param value - non-null value of specific types.
|
|
||||||
*/
|
|
||||||
public WrappedWatchableObject(int index, Object value) {
|
|
||||||
super(MinecraftReflection.getWatchableObjectClass());
|
|
||||||
|
|
||||||
if (value == null)
|
|
||||||
throw new IllegalArgumentException("Value cannot be NULL.");
|
|
||||||
|
|
||||||
// Get the correct type ID
|
|
||||||
Integer typeID = WrappedDataWatcher.getTypeID(value.getClass());
|
|
||||||
|
|
||||||
if (typeID != null) {
|
|
||||||
if (watchableConstructor == null) {
|
|
||||||
try {
|
|
||||||
watchableConstructor = MinecraftReflection.getWatchableObjectClass().
|
|
||||||
getConstructor(int.class, int.class, Object.class);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Cannot get the WatchableObject(int, int, Object) constructor.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the object
|
|
||||||
try {
|
|
||||||
load(watchableConstructor.newInstance(typeID, index, getUnwrapped(value)));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Cannot construct underlying WatchableObject.", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Cannot watch the type " + value.getClass());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Construct a custom watchable object from an index, value and custom type.
|
|
||||||
// * @param index - the index.
|
|
||||||
// * @param primary - non-null value of specific types.
|
|
||||||
// * @param secondary - optional secondary value, if the type can store it.
|
|
||||||
// * @param type - the custom Spigot type.
|
|
||||||
// */
|
|
||||||
// public WrappedWatchableObject(int index, Object value, Object secondary, CustomType type) {
|
|
||||||
// this(index, type.newInstance(value, secondary));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Wrap a NMS object
|
|
||||||
private void load(Object handle) {
|
|
||||||
initialize();
|
|
||||||
this.handle = handle;
|
|
||||||
this.modifier = baseModifier.withTarget(handle);
|
|
||||||
|
|
||||||
// Make sure the type is correct
|
|
||||||
if (!watchableObjectClass.isAssignableFrom(handle.getClass())) {
|
|
||||||
throw new ClassCastException("Cannot cast the class " + handle.getClass().getName() +
|
|
||||||
" to " + watchableObjectClass.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize reflection machinery.
|
|
||||||
*/
|
|
||||||
private static void initialize() {
|
|
||||||
if (!hasInitialized) {
|
|
||||||
hasInitialized = true;
|
|
||||||
watchableObjectClass = MinecraftReflection.getWatchableObjectClass();
|
|
||||||
baseModifier = new StructureModifier<Object>(watchableObjectClass, null, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Retrieve the custom type of this object.
|
|
||||||
// * @return The custom type, or NULL if not applicable.
|
|
||||||
// */
|
|
||||||
// public CustomType getCustomType() {
|
|
||||||
// return CustomType.fromValue(getValue());
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the correct super type of the current value.
|
|
||||||
* @return Super type.
|
|
||||||
* @throws FieldAccessException Unable to read values.
|
|
||||||
*/
|
|
||||||
public Class<?> getType() throws FieldAccessException {
|
|
||||||
return getWrappedType(getTypeRaw());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the correct super type of the current value, given the raw NMS object.
|
|
||||||
* @return Super type.
|
|
||||||
* @throws FieldAccessException Unable to read values.
|
|
||||||
*/
|
|
||||||
private Class<?> getTypeRaw() throws FieldAccessException {
|
|
||||||
if (typeClass == null) {
|
|
||||||
typeClass = WrappedDataWatcher.getTypeClass(getTypeID());
|
|
||||||
|
|
||||||
if (typeClass == null) {
|
|
||||||
throw new IllegalStateException("Unrecognized data type: " + getTypeID());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the index of this watchable object. This is used to identify a value.
|
|
||||||
* @return Object index.
|
|
||||||
* @throws FieldAccessException Reflection failed.
|
|
||||||
*/
|
|
||||||
public int getIndex() throws FieldAccessException {
|
|
||||||
return modifier.<Integer>withType(int.class).read(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the the index of this watchable object.
|
|
||||||
* @param index - the new object index.
|
|
||||||
* @throws FieldAccessException Reflection failed.
|
|
||||||
*/
|
|
||||||
public void setIndex(int index) throws FieldAccessException {
|
|
||||||
modifier.<Integer>withType(int.class).write(1, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the type ID of a watchable object.
|
|
||||||
* <table border=1>
|
|
||||||
* <caption>Type to Value</caption>
|
|
||||||
* <tbody>
|
|
||||||
* <tr>
|
|
||||||
* <th>Type ID</th>
|
|
||||||
* <th>Data Type</th>
|
|
||||||
* </tr>
|
|
||||||
* <tr>
|
|
||||||
* <td>0</td>
|
|
||||||
* <td>Byte</td>
|
|
||||||
* </tr>
|
|
||||||
* <tr>
|
|
||||||
* <td>1</td>
|
|
||||||
* <td>Short</td>
|
|
||||||
* </tr>
|
|
||||||
* <tr>
|
|
||||||
* <td>2</td>
|
|
||||||
* <td>Int</td>
|
|
||||||
* </tr>
|
|
||||||
* <tr>
|
|
||||||
* <td>3</td>
|
|
||||||
* <td>Float</td>
|
|
||||||
* </tr>
|
|
||||||
* <tr>
|
|
||||||
* <td>4</td>
|
|
||||||
* <td>{@link String}</td>
|
|
||||||
* </tr>
|
|
||||||
* <tr>
|
|
||||||
* <td>5</td>
|
|
||||||
* <td>{@link org.bukkit.inventory.ItemStack ItemStack}</td>
|
|
||||||
* </tr>
|
|
||||||
* <tr>
|
|
||||||
* <td>6<sup>*</sup></td>
|
|
||||||
* <td>{@link com.comphenix.protocol.wrappers.ChunkPosition ChunkPosition}</td>
|
|
||||||
* </tr>
|
|
||||||
* </tbody>
|
|
||||||
* </table>
|
|
||||||
* @return Type ID that identifies the type of the value.
|
|
||||||
* @throws FieldAccessException Reflection failed.
|
|
||||||
*/
|
|
||||||
public int getTypeID() throws FieldAccessException {
|
|
||||||
return modifier.<Integer>withType(int.class).read(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the type ID of a watchable object.
|
|
||||||
* @param id - the new ID.
|
|
||||||
* @throws FieldAccessException Reflection failed.
|
|
||||||
* @see #getTypeID()
|
|
||||||
*/
|
|
||||||
public void setTypeID(int id) throws FieldAccessException {
|
|
||||||
modifier.<Integer>withType(int.class).write(0, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the value field.
|
|
||||||
* @param newValue - new value.
|
|
||||||
* @throws FieldAccessException Unable to use reflection.
|
|
||||||
*/
|
|
||||||
public void setValue(Object newValue) throws FieldAccessException {
|
|
||||||
setValue(newValue, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the value field.
|
|
||||||
* @param newValue - new value.
|
|
||||||
* @param updateClient - whether or not to update listening clients.
|
|
||||||
* @throws FieldAccessException Unable to use reflection.
|
|
||||||
*/
|
|
||||||
public void setValue(Object newValue, boolean updateClient) throws FieldAccessException {
|
|
||||||
// Verify a few quick things
|
|
||||||
if (newValue == null)
|
|
||||||
throw new IllegalArgumentException("Cannot watch a NULL value.");
|
|
||||||
if (!getType().isAssignableFrom(newValue.getClass()))
|
|
||||||
throw new IllegalArgumentException("Object " + newValue + " must be of type " + getType().getName());
|
|
||||||
|
|
||||||
// See if we should update the client to
|
|
||||||
if (updateClient)
|
|
||||||
setDirtyState(true);
|
|
||||||
|
|
||||||
// Use the modifier to set the value
|
|
||||||
modifier.withType(Object.class).write(0, getUnwrapped(newValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read the underlying value field.
|
|
||||||
* @return The underlying value.
|
|
||||||
*/
|
|
||||||
private Object getNMSValue() {
|
|
||||||
return modifier.withType(Object.class).read(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read the value field.
|
|
||||||
* @return The watched value.
|
|
||||||
* @throws FieldAccessException Unable to use reflection.
|
|
||||||
*/
|
|
||||||
public Object getValue() throws FieldAccessException {
|
|
||||||
return getWrapped(modifier.withType(Object.class).read(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Retrieve the secondary value associated with this watchable object.
|
|
||||||
// * <p>
|
|
||||||
// * This is only applicable for certain {@link CustomType}.
|
|
||||||
// * @return The secondary value, or NULL if not found.
|
|
||||||
// */
|
|
||||||
// public Object getSecondaryValue() {
|
|
||||||
// CustomType type = getCustomType();
|
|
||||||
// return type != null ? type.getSecondary(getValue()) : null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Set the secondary value.
|
|
||||||
// * @param secondary - the secondary value.
|
|
||||||
// * @throws IllegalStateException If this watchable object does not have a secondary value.
|
|
||||||
// */
|
|
||||||
// public void setSecondaryValue(Object secondary) {
|
|
||||||
// CustomType type = getCustomType();
|
|
||||||
//
|
|
||||||
// if (type == null) {
|
|
||||||
// throw new IllegalStateException(this + " does not have a custom type.");
|
|
||||||
// }
|
|
||||||
// type.setSecondary(getValue(), secondary);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set whether or not the value must be synchronized with the client.
|
|
||||||
* @param dirty - TRUE if the value should be synchronized, FALSE otherwise.
|
|
||||||
* @throws FieldAccessException Unable to use reflection.
|
|
||||||
*/
|
|
||||||
public void setDirtyState(boolean dirty) throws FieldAccessException {
|
|
||||||
modifier.<Boolean>withType(boolean.class).write(0, dirty);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve whether or not the value must be synchronized with the client.
|
|
||||||
* @return TRUE if the value should be synchronized, FALSE otherwise.
|
|
||||||
* @throws FieldAccessException Unable to use reflection.
|
|
||||||
*/
|
|
||||||
public boolean getDirtyState() throws FieldAccessException {
|
|
||||||
return modifier.<Boolean>withType(boolean.class).read(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the wrapped object value, if needed.
|
|
||||||
* @param value - the raw NMS object to wrap.
|
|
||||||
* @return The wrapped object.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
static Object getWrapped(Object value) {
|
|
||||||
// Handle the special cases
|
|
||||||
if (MinecraftReflection.isItemStack(value)) {
|
|
||||||
return BukkitConverters.getItemStackConverter().getSpecific(value);
|
|
||||||
} else if (MinecraftReflection.isChunkCoordinates(value)) {
|
|
||||||
return new WrappedChunkCoordinate((Comparable) value);
|
|
||||||
} else {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the wrapped type, if needed.
|
|
||||||
* @param wrapped - the wrapped class type.
|
|
||||||
* @return The wrapped class type.
|
|
||||||
*/
|
|
||||||
static Class<?> getWrappedType(Class<?> unwrapped) {
|
|
||||||
if (unwrapped.equals(MinecraftReflection.getChunkPositionClass()))
|
|
||||||
return ChunkPosition.class;
|
|
||||||
else if (unwrapped.equals(MinecraftReflection.getBlockPositionClass()))
|
|
||||||
return BlockPosition.class;
|
|
||||||
else if (unwrapped.equals(MinecraftReflection.getChunkCoordinatesClass()))
|
|
||||||
return WrappedChunkCoordinate.class;
|
|
||||||
else if (unwrapped.equals(MinecraftReflection.getItemStackClass()))
|
|
||||||
return ItemStack.class;
|
|
||||||
else
|
|
||||||
return unwrapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the raw NMS value.
|
|
||||||
* @param wrapped - the wrapped position.
|
|
||||||
* @return The raw NMS object.
|
|
||||||
*/
|
|
||||||
static Object getUnwrapped(Object wrapped) {
|
|
||||||
// Convert special cases
|
|
||||||
if (wrapped instanceof ChunkPosition)
|
|
||||||
return ChunkPosition.getConverter().getGeneric(MinecraftReflection.getChunkPositionClass(), (ChunkPosition) wrapped);
|
|
||||||
else if (wrapped instanceof BlockPosition)
|
|
||||||
return BlockPosition.getConverter().getGeneric(MinecraftReflection.getBlockPositionClass(), (BlockPosition) wrapped);
|
|
||||||
else if (wrapped instanceof WrappedChunkCoordinate)
|
|
||||||
return ((WrappedChunkCoordinate) wrapped).getHandle();
|
|
||||||
else if (wrapped instanceof ItemStack)
|
|
||||||
return BukkitConverters.getItemStackConverter().getGeneric(MinecraftReflection.getItemStackClass(), (ItemStack) wrapped);
|
|
||||||
else
|
|
||||||
return wrapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the unwrapped type, if needed.
|
|
||||||
* @param wrapped - the unwrapped class type.
|
|
||||||
* @return The unwrapped class type.
|
|
||||||
*/
|
|
||||||
static Class<?> getUnwrappedType(Class<?> wrapped) {
|
|
||||||
if (wrapped.equals(ChunkPosition.class))
|
|
||||||
return MinecraftReflection.getChunkPositionClass();
|
|
||||||
else if (wrapped.equals(BlockPosition.class))
|
|
||||||
return MinecraftReflection.getBlockPositionClass();
|
|
||||||
else if (wrapped.equals(WrappedChunkCoordinate.class))
|
|
||||||
return MinecraftReflection.getChunkCoordinatesClass();
|
|
||||||
else if (ItemStack.class.isAssignableFrom(wrapped))
|
|
||||||
return MinecraftReflection.getItemStackClass();
|
|
||||||
else
|
|
||||||
return wrapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clone the current wrapped watchable object, along with any contained objects.
|
|
||||||
* @return A deep clone of the current watchable object.
|
|
||||||
* @throws FieldAccessException If we're unable to use reflection.
|
|
||||||
*/
|
|
||||||
public WrappedWatchableObject deepClone() throws FieldAccessException {
|
|
||||||
WrappedWatchableObject clone = new WrappedWatchableObject(
|
|
||||||
DefaultInstances.DEFAULT.getDefault(MinecraftReflection.getWatchableObjectClass()));
|
|
||||||
|
|
||||||
clone.setDirtyState(getDirtyState());
|
|
||||||
clone.setIndex(getIndex());
|
|
||||||
clone.setTypeID(getTypeID());
|
|
||||||
clone.setValue(getClonedValue(), false);
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper
|
|
||||||
Object getClonedValue() throws FieldAccessException {
|
|
||||||
Object value = getNMSValue();
|
|
||||||
|
|
||||||
// Only a limited set of references types are supported
|
|
||||||
if (MinecraftReflection.isBlockPosition(value)) {
|
|
||||||
EquivalentConverter<BlockPosition> converter = BlockPosition.getConverter();
|
|
||||||
return converter.getGeneric(MinecraftReflection.getBlockPositionClass(), converter.getSpecific(value));
|
|
||||||
} else if (MinecraftReflection.isChunkPosition(value)) {
|
|
||||||
EquivalentConverter<ChunkPosition> converter = ChunkPosition.getConverter();
|
|
||||||
return converter.getGeneric(MinecraftReflection.getChunkPositionClass(), converter.getSpecific(value));
|
|
||||||
} else if (MinecraftReflection.isItemStack(value)) {
|
|
||||||
return MinecraftReflection.getMinecraftItemStack(MinecraftReflection.getBukkitItemStack(value).clone());
|
|
||||||
} else {
|
|
||||||
// A string or primitive wrapper, which are all immutable.
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
// Quick checks
|
if (obj == this) return true;
|
||||||
if (obj == this)
|
if (obj == null) return false;
|
||||||
return true;
|
|
||||||
if (obj == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (obj instanceof WrappedWatchableObject) {
|
if (obj instanceof WrappedWatchableObject) {
|
||||||
WrappedWatchableObject other = (WrappedWatchableObject) obj;
|
WrappedWatchableObject other = (WrappedWatchableObject) obj;
|
||||||
|
return other.handle.equals(handle);
|
||||||
return Objects.equal(getIndex(), other.getIndex()) &&
|
|
||||||
Objects.equal(getTypeID(), other.getTypeID()) &&
|
|
||||||
Objects.equal(getValue(), other.getValue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No, this is not equivalent
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// TODO Flesh out this class
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hashCode(getIndex(), getTypeID(), getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return String.format("[%s: %s (%s)]", getIndex(), getValue(), getType().getSimpleName());
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -26,6 +26,9 @@ import java.util.List;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import net.minecraft.server.v1_9_R1.AttributeModifier;
|
import net.minecraft.server.v1_9_R1.AttributeModifier;
|
||||||
|
import net.minecraft.server.v1_9_R1.DataWatcher;
|
||||||
|
import net.minecraft.server.v1_9_R1.Entity;
|
||||||
|
import net.minecraft.server.v1_9_R1.EntityLightning;
|
||||||
import net.minecraft.server.v1_9_R1.PacketPlayOutUpdateAttributes;
|
import net.minecraft.server.v1_9_R1.PacketPlayOutUpdateAttributes;
|
||||||
import net.minecraft.server.v1_9_R1.PacketPlayOutUpdateAttributes.AttributeSnapshot;
|
import net.minecraft.server.v1_9_R1.PacketPlayOutUpdateAttributes.AttributeSnapshot;
|
||||||
|
|
||||||
@ -50,7 +53,9 @@ import com.comphenix.protocol.utility.Util;
|
|||||||
import com.comphenix.protocol.wrappers.BlockPosition;
|
import com.comphenix.protocol.wrappers.BlockPosition;
|
||||||
import com.comphenix.protocol.wrappers.WrappedBlockData;
|
import com.comphenix.protocol.wrappers.WrappedBlockData;
|
||||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||||
|
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
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.NbtCompound;
|
||||||
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
@ -167,6 +172,7 @@ public class PacketContainerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO Find a packet with integer arrays
|
// TODO Find a packet with integer arrays
|
||||||
|
|
||||||
/*@Test
|
/*@Test
|
||||||
public void testGetIntegerArrays() {
|
public void testGetIntegerArrays() {
|
||||||
// Contains a byte array we will test
|
// Contains a byte array we will test
|
||||||
@ -278,21 +284,26 @@ public class PacketContainerTest {
|
|||||||
assertEquals(compound.getList("ages"), result.getList("ages"));
|
assertEquals(compound.getList("ages"), result.getList("ages"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*@Test
|
@Test
|
||||||
public void testGetDataWatcherModifier() {
|
public void testGetDataWatcherModifier() {
|
||||||
PacketContainer mobSpawnPacket = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY_LIVING);
|
PacketContainer mobSpawnPacket = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY_LIVING);
|
||||||
StructureModifier<WrappedDataWatcher> watcherAccessor = mobSpawnPacket.getDataWatcherModifier();
|
StructureModifier<WrappedDataWatcher> watcherAccessor = mobSpawnPacket.getDataWatcherModifier();
|
||||||
|
|
||||||
WrappedDataWatcher dataWatcher = new WrappedDataWatcher();
|
Entity entity = new EntityLightning(null, 0, 0, 0, true);
|
||||||
dataWatcher.setObject(1, 100);
|
DataWatcher watcher = entity.getDataWatcher();
|
||||||
dataWatcher.setObject(2, 125);
|
|
||||||
|
WrappedDataWatcher dataWatcher = new WrappedDataWatcher(watcher);
|
||||||
|
dataWatcher.setObject(1, (byte) 1);
|
||||||
|
dataWatcher.setObject(2, 301);
|
||||||
|
dataWatcher.setObject(3, true);
|
||||||
|
dataWatcher.setObject(4, "Lorem");
|
||||||
|
|
||||||
assertNull(watcherAccessor.read(0));
|
assertNull(watcherAccessor.read(0));
|
||||||
|
|
||||||
// Insert and read back
|
// Insert and read back
|
||||||
watcherAccessor.write(0, dataWatcher);
|
watcherAccessor.write(0, dataWatcher);
|
||||||
assertEquals(dataWatcher, watcherAccessor.read(0));
|
assertEquals(dataWatcher, watcherAccessor.read(0));
|
||||||
}*/
|
}
|
||||||
|
|
||||||
// Unfortunately, it might be too difficult to mock this one
|
// Unfortunately, it might be too difficult to mock this one
|
||||||
//
|
//
|
||||||
@ -322,7 +333,7 @@ public class PacketContainerTest {
|
|||||||
assertEquals(positions, cloned);
|
assertEquals(positions, cloned);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*@Test
|
@Test
|
||||||
public void testGetWatchableCollectionModifier() {
|
public void testGetWatchableCollectionModifier() {
|
||||||
PacketContainer entityMetadata = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
|
PacketContainer entityMetadata = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
|
||||||
StructureModifier<List<WrappedWatchableObject>> watchableAccessor =
|
StructureModifier<List<WrappedWatchableObject>> watchableAccessor =
|
||||||
@ -330,16 +341,16 @@ public class PacketContainerTest {
|
|||||||
|
|
||||||
assertNull(watchableAccessor.read(0));
|
assertNull(watchableAccessor.read(0));
|
||||||
|
|
||||||
WrappedDataWatcher watcher = new WrappedDataWatcher();
|
Entity entity = new EntityLightning(null, 0, 0, 0, true);
|
||||||
watcher.setObject(1, 10);
|
DataWatcher watcher = entity.getDataWatcher();
|
||||||
watcher.setObject(8, 10);
|
|
||||||
|
|
||||||
List<WrappedWatchableObject> list = watcher.getWatchableObjects();
|
WrappedDataWatcher wrapper = new WrappedDataWatcher(watcher);
|
||||||
|
List<WrappedWatchableObject> list = wrapper.getWatchableObjects();
|
||||||
|
|
||||||
// Insert and read back
|
// Insert and read back
|
||||||
watchableAccessor.write(0, list);
|
watchableAccessor.write(0, list);
|
||||||
assertEquals(list, watchableAccessor.read(0));
|
assertEquals(list, watchableAccessor.read(0));
|
||||||
}*/
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGameProfiles() {
|
public void testGameProfiles() {
|
||||||
@ -350,19 +361,18 @@ public class PacketContainerTest {
|
|||||||
assertEquals(profile, spawnEntity.getGameProfiles().read(0));
|
assertEquals(profile, spawnEntity.getGameProfiles().read(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*@Test
|
@Test
|
||||||
public void testChatComponents() {
|
public void testChatComponents() {
|
||||||
PacketContainer chatPacket = new PacketContainer(PacketType.Play.Server.CHAT);
|
PacketContainer chatPacket = new PacketContainer(PacketType.Play.Server.CHAT);
|
||||||
chatPacket.getChatComponents().write(0,
|
chatPacket.getChatComponents().write(0,
|
||||||
WrappedChatComponent.fromChatMessage("You shall not " + ChatColor.ITALIC + "pass!")[0]);
|
WrappedChatComponent.fromChatMessage("You shall not " + ChatColor.ITALIC + "pass!")[0]);
|
||||||
|
|
||||||
assertEquals("{\"extra\":[\"You shall not \",{\"italic\":true,\"text\":\"pass!\"}],\"text\":\"\"}",
|
assertEquals("{\"extra\":[{\"text\":\"You shall not \"},{\"italic\":true,\"text\":\"pass!\"}],\"text\":\"\"}",
|
||||||
chatPacket.getChatComponents().read(0).getJson());
|
chatPacket.getChatComponents().read(0).getJson());
|
||||||
}*/
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSerialization() {
|
public void testSerialization() {
|
||||||
try {
|
|
||||||
PacketContainer chat = new PacketContainer(PacketType.Play.Client.CHAT);
|
PacketContainer chat = new PacketContainer(PacketType.Play.Client.CHAT);
|
||||||
chat.getStrings().write(0, "Test");
|
chat.getStrings().write(0, "Test");
|
||||||
|
|
||||||
@ -370,12 +380,6 @@ public class PacketContainerTest {
|
|||||||
|
|
||||||
assertEquals(PacketType.Play.Client.CHAT, copy.getType());
|
assertEquals(PacketType.Play.Client.CHAT, copy.getType());
|
||||||
assertEquals("Test", copy.getStrings().read(0));
|
assertEquals("Test", copy.getStrings().read(0));
|
||||||
} catch (Throwable ex) {
|
|
||||||
// This occurs intermittently on Java 6, for the time being just log it and move on
|
|
||||||
// TODO: Possibly find a solution to this
|
|
||||||
System.err.println("Failed to serialize packet:");
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* (c) 2016 dmulloy2
|
||||||
|
*/
|
||||||
|
package com.comphenix.protocol.wrappers;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import net.minecraft.server.v1_9_R1.DataWatcher;
|
||||||
|
import net.minecraft.server.v1_9_R1.Entity;
|
||||||
|
import net.minecraft.server.v1_9_R1.EntityLightning;
|
||||||
|
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.BukkitInitialization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dmulloy2
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class WrappedDataWatcherTest {
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void prepare() {
|
||||||
|
BukkitInitialization.initializePackage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
Entity entity = new EntityLightning(null, 0, 0, 0, true);
|
||||||
|
DataWatcher handle = entity.getDataWatcher();
|
||||||
|
|
||||||
|
WrappedDataWatcher wrapper = new WrappedDataWatcher(handle);
|
||||||
|
|
||||||
|
wrapper.setObject(0, (byte) 1);
|
||||||
|
assertTrue(wrapper.getByte(0) == 1);
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,5 @@
|
|||||||
package com.comphenix.protocol.wrappers;
|
package com.comphenix.protocol.wrappers;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.inventory.ItemStack;
|
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||||
|
|
||||||
@ -19,11 +15,11 @@ public class WrappedWatchableObjectTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//@Test
|
//@Test
|
||||||
public void testItemStack() {
|
/* public void testItemStack() {
|
||||||
final ItemStack stack = new ItemStack(Material.GOLD_AXE);
|
final ItemStack stack = new ItemStack(Material.GOLD_AXE);
|
||||||
final WrappedWatchableObject test = new WrappedWatchableObject(0, stack);
|
final WrappedWatchableObject test = new WrappedWatchableObject(0, stack);
|
||||||
|
|
||||||
ItemStack value = (ItemStack) test.getValue();
|
ItemStack value = (ItemStack) test.getValue();
|
||||||
assertEquals(value.getType(), stack.getType());
|
assertEquals(value.getType(), stack.getType());
|
||||||
}
|
} */
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user