mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-06-28 15:54:42 +02:00
1111 lines
31 KiB
Java
1111 lines
31 KiB
Java
/**
|
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
|
* Copyright (C) 2018 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;
|
|
|
|
import java.lang.reflect.*;
|
|
import java.util.*;
|
|
|
|
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
|
import com.comphenix.protocol.reflect.StructureModifier;
|
|
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.MethodAccessor;
|
|
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
|
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
|
import com.comphenix.protocol.wrappers.collection.ConvertedMap;
|
|
import com.google.common.base.Optional;
|
|
import com.google.common.collect.ImmutableBiMap;
|
|
|
|
import org.apache.commons.lang.Validate;
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.entity.Entity;
|
|
import org.bukkit.inventory.ItemStack;
|
|
|
|
/**
|
|
* Represents a DataWatcher
|
|
* @author dmulloy2
|
|
*/
|
|
public class WrappedDataWatcher extends AbstractWrapper implements Iterable<WrappedWatchableObject>, ClonableWrapper {
|
|
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getDataWatcherClass();
|
|
|
|
private static MethodAccessor GETTER = null;
|
|
private static MethodAccessor SETTER = null;
|
|
private static MethodAccessor REGISTER = null;
|
|
|
|
private static FieldAccessor ENTITY_DATA_FIELD = null;
|
|
private static FieldAccessor ENTITY_FIELD = null;
|
|
private static FieldAccessor MAP_FIELD = null;
|
|
|
|
private static ConstructorAccessor constructor = null;
|
|
private static ConstructorAccessor eggConstructor = null;
|
|
|
|
private static Object fakeEntity = null;
|
|
|
|
// ---- Construction
|
|
|
|
/**
|
|
* Constructs a new DataWatcher wrapper around a NMS handle. The resulting
|
|
* DataWatcher will likely have existing values that can be removed with
|
|
* {@link #clear()}.
|
|
*
|
|
* @param handle DataWatcher handle
|
|
*/
|
|
public WrappedDataWatcher(Object handle) {
|
|
super(HANDLE_TYPE);
|
|
setHandle(handle);
|
|
}
|
|
|
|
/**
|
|
* Constructs a new DataWatcher using a fake egg entity. The
|
|
* resulting DataWatcher will not have any keys or values and new ones will
|
|
* have to be added using watcher objects.
|
|
*/
|
|
public WrappedDataWatcher() {
|
|
this(newHandle(fakeEntity()));
|
|
}
|
|
|
|
/**
|
|
* Constructs a new DataWatcher using a real entity. The resulting
|
|
* DataWatcher will not have any keys or values and new ones will have to
|
|
* be added using watcher objects.
|
|
*
|
|
* @param entity The entity
|
|
*/
|
|
public WrappedDataWatcher(Entity entity) {
|
|
this(newHandle(BukkitUnwrapper.getInstance().unwrapItem(entity)));
|
|
}
|
|
|
|
/**
|
|
* Constructs a new DataWatcher using a fake egg entity and a given
|
|
* list of watchable objects.
|
|
*
|
|
* @param objects The list of objects
|
|
*/
|
|
public WrappedDataWatcher(List<WrappedWatchableObject> objects) {
|
|
this();
|
|
|
|
if (MinecraftReflection.watcherObjectExists()) {
|
|
for (WrappedWatchableObject object : objects) {
|
|
setObject(object.getWatcherObject(), object);
|
|
}
|
|
} else {
|
|
for (WrappedWatchableObject object : objects) {
|
|
setObject(object.getIndex(), object);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static Object newHandle(Object entity) {
|
|
if (constructor == null) {
|
|
constructor = Accessors.getConstructorAccessor(HANDLE_TYPE, MinecraftReflection.getEntityClass());
|
|
}
|
|
|
|
return constructor.invoke(entity);
|
|
}
|
|
|
|
private static Object fakeEntity() {
|
|
if (fakeEntity != null) {
|
|
return fakeEntity;
|
|
}
|
|
|
|
// We can create a fake egg without it affecting anything
|
|
// Mojang added difficulty to lightning strikes, so this'll have to do
|
|
if (eggConstructor == null) {
|
|
eggConstructor = Accessors.getConstructorAccessor(
|
|
MinecraftReflection.getMinecraftClass("world.entity.projectile.EntityEgg", "world.entity.projectile.ThrownEgg", "EntityEgg"),
|
|
MinecraftReflection.getNmsWorldClass(), double.class, double.class, double.class
|
|
);
|
|
}
|
|
|
|
Object world = BukkitUnwrapper.getInstance().unwrapItem(Bukkit.getWorlds().get(0));
|
|
return fakeEntity = eggConstructor.invoke(world, 0, 0, 0);
|
|
}
|
|
|
|
// ---- Collection Methods
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private Map<Integer, Object> getMap() {
|
|
if (MAP_FIELD == null) {
|
|
try {
|
|
FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType, true);
|
|
MAP_FIELD = Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract
|
|
.newBuilder()
|
|
.banModifier(Modifier.STATIC)
|
|
.typeDerivedOf(Map.class)
|
|
.build()));
|
|
} catch (IllegalArgumentException ex) {
|
|
throw new FieldAccessException("Failed to find watchable object map");
|
|
}
|
|
}
|
|
|
|
return (Map<Integer, Object>) MAP_FIELD.get(handle);
|
|
}
|
|
|
|
/**
|
|
* Gets the contents of this DataWatcher as a map.
|
|
* @return The contents
|
|
*/
|
|
public Map<Integer, WrappedWatchableObject> asMap() {
|
|
return new ConvertedMap<Integer, Object, WrappedWatchableObject>(getMap()) {
|
|
@Override
|
|
protected WrappedWatchableObject toOuter(Object inner) {
|
|
return inner != null ? new WrappedWatchableObject(inner) : null;
|
|
}
|
|
|
|
@Override
|
|
protected Object toInner(WrappedWatchableObject outer) {
|
|
return outer != null ? outer.getHandle() : null;
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Gets a set containing the registered indexes.
|
|
* @return The set
|
|
*/
|
|
public Set<Integer> getIndexes() {
|
|
return getMap().keySet();
|
|
}
|
|
|
|
/**
|
|
* Gets a list of the contents of this DataWatcher.
|
|
* @return The contents
|
|
*/
|
|
public List<WrappedWatchableObject> getWatchableObjects() {
|
|
return new ArrayList<>(asMap().values());
|
|
}
|
|
|
|
@Override
|
|
public Iterator<WrappedWatchableObject> iterator() {
|
|
return getWatchableObjects().iterator();
|
|
}
|
|
|
|
/**
|
|
* Gets the size of this DataWatcher's contents.
|
|
* @return The size
|
|
*/
|
|
public int size() {
|
|
return getMap().size();
|
|
}
|
|
|
|
/**
|
|
* Gets the item at a given index.
|
|
*
|
|
* @param index Index to get
|
|
* @return The watchable object, or null if none exists
|
|
*/
|
|
public WrappedWatchableObject getWatchableObject(int index) {
|
|
Object handle = getMap().get(index);
|
|
if (handle != null) {
|
|
return new WrappedWatchableObject(handle);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @deprecated Renamed to {@link #remove(int)}
|
|
*/
|
|
@Deprecated
|
|
public WrappedWatchableObject removeObject(int index) {
|
|
return remove(index);
|
|
}
|
|
|
|
/**
|
|
* Removes the item at a given index.
|
|
*
|
|
* @param index Index to remove
|
|
* @return The previous value, or null if none existed
|
|
*/
|
|
public WrappedWatchableObject remove(int index) {
|
|
Object removed = getMap().remove(index);
|
|
return removed != null ? new WrappedWatchableObject(removed) : null;
|
|
}
|
|
|
|
/**
|
|
* Whether or not this DataWatcher has an object at a given index.
|
|
*
|
|
* @param index Index to check for
|
|
* @return True if it does, false if not
|
|
*/
|
|
public boolean hasIndex(int index) {
|
|
return getMap().containsKey(index);
|
|
}
|
|
|
|
/**
|
|
* Returns a set containing all the registered indexes
|
|
* @return The set
|
|
*/
|
|
public Set<Integer> indexSet() {
|
|
return getMap().keySet();
|
|
}
|
|
|
|
/**
|
|
* Clears the contents of this DataWatcher. The watcher will be empty after
|
|
* this operation is called.
|
|
*/
|
|
public void clear() {
|
|
getMap().clear();
|
|
}
|
|
|
|
// ---- Object Getters
|
|
|
|
/**
|
|
* Get a watched byte.
|
|
*
|
|
* @param index - index of the watched byte.
|
|
* @return The watched byte, or NULL if this value doesn't exist.
|
|
*/
|
|
public Byte getByte(int index) {
|
|
return (Byte) getObject(index);
|
|
}
|
|
|
|
/**
|
|
* Get a watched short.
|
|
*
|
|
* @param index - index of the watched short.
|
|
* @return The watched short, or NULL if this value doesn't exist.
|
|
*/
|
|
public Short getShort(int index) {
|
|
return (Short) getObject(index);
|
|
}
|
|
|
|
/**
|
|
* Get a watched integer.
|
|
*
|
|
* @param index - index of the watched integer.
|
|
* @return The watched integer, or NULL if this value doesn't exist.
|
|
*/
|
|
public Integer getInteger(int index) {
|
|
return (Integer) getObject(index);
|
|
}
|
|
|
|
/**
|
|
* Get a watched float.
|
|
*
|
|
* @param index - index of the watched float.
|
|
* @return The watched float, or NULL if this value doesn't exist.
|
|
*/
|
|
public Float getFloat(int index) {
|
|
return (Float) getObject(index);
|
|
}
|
|
|
|
/**
|
|
* Get a watched string.
|
|
*
|
|
* @param index - index of the watched string.
|
|
* @return The watched string, or NULL if this value doesn't exist.
|
|
*/
|
|
public String getString(int index) {
|
|
return (String) getObject(index);
|
|
}
|
|
|
|
/**
|
|
* Get a watched string.
|
|
*
|
|
* @param index - index of the watched string.
|
|
* @return The watched string, or NULL if this value doesn't exist.
|
|
*/
|
|
public ItemStack getItemStack(int index) {
|
|
return (ItemStack) getObject(index);
|
|
}
|
|
|
|
/**
|
|
* Retrieve a watchable object by index.
|
|
*
|
|
* @param index Index of the object to retrieve.
|
|
* @return The watched object or null if it doesn't exist.
|
|
*/
|
|
public Object getObject(int index) {
|
|
return getObject(WrappedDataWatcherObject.fromIndex(index));
|
|
}
|
|
|
|
/**
|
|
* Retrieve a watchable object by watcher object.
|
|
*
|
|
* @param object The watcher object
|
|
* @return The watched object or null if it doesn't exist.
|
|
*/
|
|
public Object getObject(WrappedDataWatcherObject object) {
|
|
Validate.notNull(object, "Watcher object cannot be null!");
|
|
|
|
if (GETTER == null) {
|
|
FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType, true);
|
|
|
|
if (MinecraftReflection.watcherObjectExists()) {
|
|
GETTER = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
|
|
.parameterExactType(object.getHandleType())
|
|
.returnTypeExact(Object.class)
|
|
.build(), "get"));
|
|
} else {
|
|
GETTER = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
|
|
.parameterExactType(int.class)
|
|
.returnTypeExact(MinecraftReflection.getDataWatcherItemClass())
|
|
.build()));
|
|
}
|
|
}
|
|
|
|
try {
|
|
Object value = GETTER.invoke(handle, object.getHandle());
|
|
return WrappedWatchableObject.getWrapped(value);
|
|
} catch (Exception ex) {
|
|
// Nothing exists at this index
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ---- Object Setters
|
|
|
|
/**
|
|
* Sets the DataWatcher Item at a given index to a new value. In 1.9 and up,
|
|
* you cannot register objects without a watcher object.
|
|
*
|
|
* @param index Index of the object to set
|
|
* @param value New value
|
|
* @param update Whether or not to inform the client
|
|
*
|
|
* @see WrappedDataWatcher#setObject(WrappedDataWatcherObject, Object, boolean)
|
|
* @throws IllegalArgumentException in 1.9 and up if there isn't already an
|
|
* object at this index
|
|
*/
|
|
public void setObject(int index, Object value, boolean update) {
|
|
if (MinecraftReflection.watcherObjectExists() && !hasIndex(index)) {
|
|
throw new IllegalArgumentException("You cannot register objects without a watcher object!");
|
|
}
|
|
|
|
setObject(WrappedDataWatcherObject.fromIndex(index), value, update);
|
|
}
|
|
|
|
/**
|
|
* Shortcut for {@link #setObject(int, Object, boolean)}
|
|
*/
|
|
public void setObject(int index, Object value) {
|
|
setObject(index, value, false);
|
|
}
|
|
|
|
/**
|
|
* Sets the DataWatcher Item at a given index to a new value.
|
|
*
|
|
* @param index Index of the object to set
|
|
* @param serializer Serializer from {@link Registry#get(Class)}
|
|
* @param value New value
|
|
* @param update Whether or not to inform the client
|
|
*
|
|
* @see WrappedDataWatcher#setObject(WrappedDataWatcherObject, Object)
|
|
*/
|
|
public void setObject(int index, Serializer serializer, Object value, boolean update) {
|
|
setObject(new WrappedDataWatcherObject(index, serializer), value, update);
|
|
}
|
|
|
|
/**
|
|
* Alias for {@link #setObject(int, Serializer, Object, boolean)}
|
|
*/
|
|
public void setObject(int index, Serializer serializer, Object value) {
|
|
setObject(new WrappedDataWatcherObject(index, serializer), value, false);
|
|
}
|
|
|
|
/**
|
|
* Sets the DataWatcher Item at a given index to a new value.
|
|
*
|
|
* @param index Index of the object to set
|
|
* @param value New value
|
|
* @param update Whether or not to inform the client
|
|
*
|
|
* @see WrappedDataWatcher#setObject(int, Object, boolean)
|
|
*/
|
|
public void setObject(int index, WrappedWatchableObject value, boolean update) {
|
|
setObject(index, value.getRawValue(), update);
|
|
}
|
|
|
|
/**
|
|
* Alias for {@link #setObject(int, WrappedWatchableObject, boolean)}
|
|
*/
|
|
public void setObject(int index, WrappedWatchableObject value) {
|
|
setObject(index, value.getRawValue(), false);
|
|
}
|
|
|
|
/**
|
|
* Sets the DataWatcher Item associated with a given watcher object to a new value.
|
|
*
|
|
* @param object Associated watcher object
|
|
* @param value Wrapped value
|
|
* @param update Whether or not to inform the client
|
|
*
|
|
* @see #setObject(WrappedDataWatcherObject, Object)
|
|
*/
|
|
public void setObject(WrappedDataWatcherObject object, WrappedWatchableObject value, boolean update) {
|
|
setObject(object, value.getRawValue(), update);
|
|
}
|
|
|
|
/**
|
|
* Shortcut for {@link #setObject(WrappedDataWatcherObject, WrappedWatchableObject, boolean)}
|
|
*/
|
|
public void setObject(WrappedDataWatcherObject object, WrappedWatchableObject value) {
|
|
setObject(object, value.getRawValue(), false);
|
|
}
|
|
|
|
/**
|
|
* Sets the DataWatcher Item associated with a given watcher object to a
|
|
* new value. If there is not already an object at this index, the
|
|
* specified watcher object must have a serializer.
|
|
*
|
|
* @param object Associated watcher object
|
|
* @param value New value
|
|
*
|
|
* @throws IllegalArgumentException If the watcher object is null or must
|
|
* have a serializer and does not have one.
|
|
*/
|
|
public void setObject(WrappedDataWatcherObject object, Object value, boolean update) {
|
|
Validate.notNull(object, "Watcher object cannot be null!");
|
|
|
|
if (SETTER == null && REGISTER == null) {
|
|
FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType, true);
|
|
FuzzyMethodContract contract = FuzzyMethodContract.newBuilder()
|
|
.banModifier(Modifier.STATIC)
|
|
.requireModifier(Modifier.PUBLIC)
|
|
.parameterExactArray(object.getHandleType(), Object.class)
|
|
.build();
|
|
List<Method> methods = fuzzy.getMethodList(contract);
|
|
for (Method method : methods) {
|
|
if (method.getName().equals("set") || method.getName().equals("watch") || method.getName().equals("b")) {
|
|
SETTER = Accessors.getMethodAccessor(method);
|
|
} else {
|
|
REGISTER = Accessors.getMethodAccessor(method);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unwrap the object
|
|
value = WrappedWatchableObject.getUnwrapped(value);
|
|
|
|
if (hasIndex(object.getIndex())) {
|
|
SETTER.invoke(handle, object.getHandle(), value);
|
|
} else {
|
|
object.checkSerializer();
|
|
REGISTER.invoke(handle, object.getHandle(), value);
|
|
}
|
|
|
|
if (update) {
|
|
getWatchableObject(object.getIndex()).setDirtyState(update);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shortcut for {@link #setObject(WrappedDataWatcherObject, Object, boolean)}
|
|
*/
|
|
public void setObject(WrappedDataWatcherObject object, Object value) {
|
|
setObject(object, value, false);
|
|
}
|
|
|
|
// ---- Utility Methods
|
|
|
|
/**
|
|
* Clone the content of the current DataWatcher.
|
|
*
|
|
* @return A cloned data watcher.
|
|
*/
|
|
public WrappedDataWatcher deepClone() {
|
|
WrappedDataWatcher clone = new WrappedDataWatcher(getEntity());
|
|
|
|
if (MinecraftReflection.watcherObjectExists()) {
|
|
for (WrappedWatchableObject wrapper : this) {
|
|
clone.setObject(wrapper.getWatcherObject(), wrapper);
|
|
}
|
|
} else {
|
|
for (WrappedWatchableObject wrapper : this) {
|
|
clone.setObject(wrapper.getIndex(), wrapper);
|
|
}
|
|
}
|
|
|
|
return clone;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the data watcher associated with an entity.
|
|
*
|
|
* @param entity - the entity to read from.
|
|
* @return Associated data watcher.
|
|
*/
|
|
public static WrappedDataWatcher getEntityWatcher(Entity entity) {
|
|
if (ENTITY_DATA_FIELD == null) {
|
|
ENTITY_DATA_FIELD = Accessors.getFieldAccessor(MinecraftReflection.getEntityClass(), MinecraftReflection.getDataWatcherClass(), true);
|
|
}
|
|
|
|
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
|
|
Object handle = ENTITY_DATA_FIELD.get(unwrapper.unwrapItem(entity));
|
|
return handle != null ? new WrappedDataWatcher(handle) : null;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the entity associated with this data watcher.
|
|
* @return The entity, or NULL.
|
|
*/
|
|
public Entity getEntity() {
|
|
if (ENTITY_FIELD == null) {
|
|
ENTITY_FIELD = Accessors.getFieldAccessor(HANDLE_TYPE, MinecraftReflection.getEntityClass(), true);
|
|
}
|
|
|
|
Object entity = ENTITY_FIELD.get(handle);
|
|
if (entity == null) {
|
|
throw new NullPointerException(handle + "." + ENTITY_FIELD);
|
|
}
|
|
|
|
return (Entity) MinecraftReflection.getBukkitEntity(entity);
|
|
}
|
|
|
|
/**
|
|
* Set the entity associated with this data watcher.
|
|
* @param entity - the new entity.
|
|
*/
|
|
public void setEntity(Entity entity) {
|
|
if (ENTITY_FIELD == null) {
|
|
ENTITY_FIELD = Accessors.getFieldAccessor(HANDLE_TYPE, MinecraftReflection.getEntityClass(), true);
|
|
}
|
|
|
|
ENTITY_FIELD.set(handle, BukkitUnwrapper.getInstance().unwrapItem(entity));
|
|
}
|
|
|
|
private static final ImmutableBiMap<Class<?>, Integer> CLASS_TO_ID = new ImmutableBiMap.Builder<Class<?>, Integer>()
|
|
.put(Byte.class, 0)
|
|
.put(Short.class, 1)
|
|
.put(Integer.class, 2)
|
|
.put(Float.class, 3)
|
|
.put(String.class, 4)
|
|
.put(MinecraftReflection.getItemStackClass(), 5)
|
|
.put(MinecraftReflection.getBlockPositionClass(), 6)
|
|
.put(Vector3F.getMinecraftClass(), 7)
|
|
.build();
|
|
|
|
/**
|
|
* Retrieves the type ID associated with a given class. No longer supported
|
|
* in 1.9 and up due to the removal of type IDs.
|
|
*
|
|
* @param clazz Class to find ID for
|
|
* @return The ID, or null if not found
|
|
*/
|
|
public static Integer getTypeID(Class<?> clazz) {
|
|
return CLASS_TO_ID.get(clazz);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the class associated with a given type ID. No longer
|
|
* supported in 1.9 and up due to the removal of type IDs.
|
|
*
|
|
* @param typeID ID to find Class for
|
|
* @return The Class, or null if not found
|
|
*/
|
|
public static Class<?> getTypeClass(int typeID) {
|
|
return CLASS_TO_ID.inverse().get(typeID);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (obj == this) return true;
|
|
if (obj == null) return false;
|
|
|
|
if (obj instanceof WrappedDataWatcher) {
|
|
WrappedDataWatcher other = (WrappedDataWatcher) obj;
|
|
Iterator<WrappedWatchableObject> first = iterator(), second = other.iterator();
|
|
|
|
// Make sure they're the same size
|
|
if (size() != other.size())
|
|
return false;
|
|
|
|
for (; first.hasNext() && second.hasNext();) {
|
|
// See if the two elements are equal
|
|
if (!first.next().equals(second.next()))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return getWatchableObjects().hashCode();
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "WrappedDataWatcher[handle=" + handle + "]";
|
|
}
|
|
|
|
// ---- 1.9 classes
|
|
|
|
/**
|
|
* Represents a DataWatcherObject in 1.9. In order to register an object,
|
|
* the serializer must be specified.
|
|
*
|
|
* @author dmulloy2
|
|
*/
|
|
public static class WrappedDataWatcherObject {
|
|
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getDataWatcherObjectClass();
|
|
private static ConstructorAccessor constructor = null;
|
|
private static MethodAccessor getSerializer = null;
|
|
|
|
private StructureModifier<Object> modifier;
|
|
private Object handle;
|
|
|
|
protected WrappedDataWatcherObject() {
|
|
}
|
|
|
|
/**
|
|
* Creates a new watcher object from a NMS handle.
|
|
*
|
|
* @param handle The handle
|
|
*/
|
|
public WrappedDataWatcherObject(Object handle) {
|
|
this.handle = handle;
|
|
this.modifier = new StructureModifier<>(HANDLE_TYPE).withTarget(handle);
|
|
}
|
|
|
|
/**
|
|
* Creates a new watcher object from an index and serializer.
|
|
*
|
|
* @param index Index
|
|
* @param serializer Serializer, see {@link Registry}
|
|
*/
|
|
public WrappedDataWatcherObject(int index, Serializer serializer) {
|
|
this(newHandle(index, serializer));
|
|
}
|
|
|
|
static WrappedDataWatcherObject fromIndex(int index) {
|
|
if (MinecraftReflection.watcherObjectExists()) {
|
|
return new WrappedDataWatcherObject(newHandle(index));
|
|
} else {
|
|
return new DummyWatcherObject(index);
|
|
}
|
|
}
|
|
|
|
private static Object newHandle(int index) {
|
|
Validate.isTrue(index >= 0, "index cannot be negative!");
|
|
|
|
if (constructor == null) {
|
|
constructor = Accessors.getConstructorAccessor(HANDLE_TYPE.getConstructors()[0]);
|
|
}
|
|
|
|
return constructor.invoke(index, null);
|
|
}
|
|
|
|
private static Object newHandle(int index, Serializer serializer) {
|
|
Validate.isTrue(index >= 0, "index cannot be negative!");
|
|
Validate.notNull(serializer, "serializer cannot be null!");
|
|
|
|
if (constructor == null) {
|
|
constructor = Accessors.getConstructorAccessor(HANDLE_TYPE.getConstructors()[0]);
|
|
}
|
|
|
|
Object handle = serializer.getHandle();
|
|
return constructor.invoke(index, handle);
|
|
}
|
|
|
|
/**
|
|
* Gets this watcher object's index.
|
|
*
|
|
* @return The index
|
|
*/
|
|
public int getIndex() {
|
|
return (int) modifier.read(0);
|
|
}
|
|
|
|
/**
|
|
* Gets this watcher object's serializer. Will return null if the
|
|
* serializer was never specified.
|
|
*
|
|
* @return The serializer, or null
|
|
*/
|
|
public Serializer getSerializer() {
|
|
if (getSerializer == null) {
|
|
getSerializer = Accessors.getMethodAccessor(FuzzyReflection.fromClass(HANDLE_TYPE, true)
|
|
.getMethodByReturnTypeAndParameters("getSerializer", MinecraftReflection.getDataWatcherSerializerClass(), new Class[0]));
|
|
}
|
|
|
|
Object serializer = getSerializer.invoke(handle);
|
|
if (serializer != null) {
|
|
Serializer wrapper = Registry.fromHandle(serializer);
|
|
if (wrapper != null) {
|
|
return wrapper;
|
|
} else {
|
|
return new Serializer(null, serializer, false);
|
|
}
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public void checkSerializer() {
|
|
Validate.notNull(getSerializer(), "You must specify a serializer to register an object!");
|
|
}
|
|
|
|
public Object getHandle() {
|
|
return handle;
|
|
}
|
|
|
|
public Class<?> getHandleType() {
|
|
return HANDLE_TYPE;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "DataWatcherObject[index=" + getIndex() + ", serializer=" + getSerializer() + "]";
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (obj == this) return true;
|
|
|
|
if (obj instanceof WrappedDataWatcherObject) {
|
|
WrappedDataWatcherObject other = (WrappedDataWatcherObject) obj;
|
|
return handle.equals(other.handle);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return handle.hashCode();
|
|
}
|
|
}
|
|
|
|
private static class DummyWatcherObject extends WrappedDataWatcherObject {
|
|
private final int index;
|
|
|
|
public DummyWatcherObject(int index) {
|
|
this.index = index;
|
|
}
|
|
|
|
@Override
|
|
public int getIndex() {
|
|
return index;
|
|
}
|
|
|
|
@Override
|
|
public Serializer getSerializer() {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Object getHandle() {
|
|
return getIndex();
|
|
}
|
|
|
|
@Override
|
|
public Class<?> getHandleType() {
|
|
return int.class;
|
|
}
|
|
|
|
@Override
|
|
public void checkSerializer() {
|
|
// Do nothing
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (obj == this) return true;
|
|
if (obj == null) return false;
|
|
|
|
if (obj instanceof DummyWatcherObject) {
|
|
DummyWatcherObject that = (DummyWatcherObject) obj;
|
|
return this.index == that.index;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents a DataWatcherSerializer in 1.9. If a Serializer is optional,
|
|
* values must be wrapped in a {@link Optional}.
|
|
*
|
|
* @author dmulloy2
|
|
*/
|
|
public static class Serializer extends AbstractWrapper {
|
|
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getDataWatcherSerializerClass();
|
|
|
|
private final Class<?> type;
|
|
private final boolean optional;
|
|
|
|
/**
|
|
* Constructs a new Serializer
|
|
*
|
|
* @param type Type it serializes
|
|
* @param handle NMS handle
|
|
* @param optional Whether or not it's {@link Optional}
|
|
*/
|
|
public Serializer(Class<?> type, Object handle, boolean optional) {
|
|
super(HANDLE_TYPE);
|
|
this.type = type;
|
|
this.optional = optional;
|
|
|
|
setHandle(handle);
|
|
}
|
|
|
|
/**
|
|
* Gets the type this serializer serializes.
|
|
*
|
|
* @return The type
|
|
*/
|
|
public Class<?> getType() {
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* Whether or not this serializer is optional, that is whether or not
|
|
* the return type is wrapped in a {@link Optional}.
|
|
*
|
|
* @return True if it is, false if not
|
|
*/
|
|
public boolean isOptional() {
|
|
return optional;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "Serializer[type=" + type + ", handle=" + handle + ", optional=" + optional + "]";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents a DataWatcherRegistry containing the supported
|
|
* {@link Serializer}s in 1.9.
|
|
*
|
|
* <ul>
|
|
* <li>Byte</li>
|
|
* <li>Integer</li>
|
|
* <li>Float</li>
|
|
* <li>String</li>
|
|
* <li>IChatBaseComponent</li>
|
|
* <li>ItemStack</li>
|
|
* <li>Optional<IBlockData></li>
|
|
* <li>Boolean</li>
|
|
* <li>Vector3f</li>
|
|
* <li>BlockPosition</li>
|
|
* <li>Optional<BlockPosition></li>
|
|
* <li>EnumDirection</li>
|
|
* <li>Optional<UUID></li>
|
|
* <li>NBTTagCompound</li>
|
|
* </ul>
|
|
*
|
|
* @author dmulloy2
|
|
*/
|
|
public static class Registry {
|
|
private static boolean INITIALIZED = false;
|
|
private static List<Serializer> REGISTRY = new ArrayList<>();
|
|
|
|
/**
|
|
* Gets the first serializer associated with a given class.
|
|
*
|
|
* <p><b>Note</b>: If {@link Serializer#isOptional() the serializer is optional},
|
|
* values <i>must</i> be wrapped in an {@link Optional}.</p>
|
|
*
|
|
* <p>If there are multiple serializers for a given class (i.e. BlockPosition),
|
|
* you should use {@link #get(Class, boolean)} for more precision.</p>
|
|
*
|
|
* @param clazz Class to find serializer for
|
|
* @return The serializer, or null if none exists
|
|
*/
|
|
public static Serializer get(Class<?> clazz) {
|
|
Validate.notNull(clazz,"Class cannot be null!");
|
|
initialize();
|
|
|
|
for (Serializer serializer : REGISTRY) {
|
|
if (serializer.getType().equals(clazz)) {
|
|
return serializer;
|
|
}
|
|
}
|
|
|
|
throw new IllegalArgumentException("No serializer found for " + clazz);
|
|
}
|
|
|
|
/**
|
|
* Gets the first serializer associated with a given class and optional state.
|
|
*
|
|
* <p><b>Note</b>: If the serializer is optional, values <i>must<i> be
|
|
* wrapped in an {@link Optional}
|
|
*
|
|
* @param clazz Class to find serializer for
|
|
* @param optional Optional state
|
|
* @return The serializer, or null if none exists
|
|
*/
|
|
public static Serializer get(Class<?> clazz, boolean optional) {
|
|
Validate.notNull(clazz, "Class cannot be null!");
|
|
initialize();
|
|
|
|
Validate.notEmpty(REGISTRY, "Registry has no elements!");
|
|
|
|
for (Serializer serializer : REGISTRY) {
|
|
if (serializer.getType().equals(clazz)
|
|
&& serializer.isOptional() == optional) {
|
|
return serializer;
|
|
}
|
|
}
|
|
|
|
throw new IllegalArgumentException("No serializer found for " + (optional ? "Optional<" + clazz + ">" : clazz));
|
|
}
|
|
|
|
/**
|
|
* Gets the serializer associated with a given NMS handle.
|
|
* @param handle The handle
|
|
* @return The serializer, or null if none exists
|
|
*/
|
|
public static Serializer fromHandle(Object handle) {
|
|
Validate.notNull(handle, "handle cannot be null!");
|
|
initialize();
|
|
|
|
for (Serializer serializer : REGISTRY) {
|
|
if (serializer.getHandle().equals(handle)) {
|
|
return serializer;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static void initialize() {
|
|
if (!INITIALIZED) {
|
|
INITIALIZED = true;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
List<Field> candidates = FuzzyReflection.fromClass(MinecraftReflection.getDataWatcherRegistryClass(), true)
|
|
.getFieldListByType(MinecraftReflection.getDataWatcherSerializerClass());
|
|
for (Field candidate : candidates) {
|
|
Type generic = candidate.getGenericType();
|
|
if (generic instanceof ParameterizedType) {
|
|
ParameterizedType type = (ParameterizedType) generic;
|
|
Type[] args = type.getActualTypeArguments();
|
|
Type arg = args[0];
|
|
|
|
Class<?> innerClass;
|
|
boolean optional = false;
|
|
|
|
if (arg instanceof Class<?>) {
|
|
innerClass = (Class<?>) arg;
|
|
} else if (arg instanceof ParameterizedType) {
|
|
innerClass = (Class<?>) ((ParameterizedType) arg).getActualTypeArguments()[0];
|
|
optional = true;
|
|
} else {
|
|
throw new IllegalStateException("Failed to find inner class of field " + candidate);
|
|
}
|
|
|
|
Object serializer;
|
|
|
|
try {
|
|
serializer = candidate.get(null);
|
|
} catch (ReflectiveOperationException e) {
|
|
throw new IllegalStateException("Failed to read field " + candidate);
|
|
}
|
|
|
|
if (serializer == null) {
|
|
throw new RuntimeException("Failed to read serializer: " + candidate.getName());
|
|
}
|
|
|
|
REGISTRY.add(new Serializer(innerClass, serializer, optional));
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- Helper methods
|
|
|
|
/**
|
|
* Gets the serializer for IChatBaseComponents
|
|
* @return The serializer
|
|
*/
|
|
public static Serializer getChatComponentSerializer() {
|
|
return getChatComponentSerializer(false);
|
|
}
|
|
|
|
/**
|
|
* Gets the serializer for IChatBaseComponents
|
|
* @param optional If true, objects <b>must</b> be wrapped in an {@link Optional}
|
|
* @return The serializer
|
|
*/
|
|
public static Serializer getChatComponentSerializer(boolean optional) {
|
|
return get(MinecraftReflection.getIChatBaseComponentClass(), optional);
|
|
}
|
|
|
|
/**
|
|
* Gets the serializer for ItemStacks
|
|
* @param optional If true, objects <b>must</b> be wrapped in an {@link Optional}
|
|
* @return The serializer
|
|
*/
|
|
public static Serializer getItemStackSerializer(boolean optional) {
|
|
return get(MinecraftReflection.getItemStackClass(), optional);
|
|
}
|
|
|
|
/**
|
|
* Gets the serializer for BlockData
|
|
* @param optional If true, objects <b>must</b> be wrapped in an {@link Optional}
|
|
* @return The serializer
|
|
*/
|
|
public static Serializer getBlockDataSerializer(boolean optional) {
|
|
return get(MinecraftReflection.getIBlockDataClass(), optional);
|
|
}
|
|
|
|
/**
|
|
* Gets the serializer for Vector3Fs
|
|
* @return The serializer
|
|
*/
|
|
public static Serializer getVectorSerializer() {
|
|
return get(Vector3F.getMinecraftClass());
|
|
}
|
|
|
|
/**
|
|
* Gets the serializer for BlockPositions
|
|
* @param optional If true, objects <b>must</b> be wrapped in an {@link Optional}
|
|
* @return The serializer
|
|
*/
|
|
public static Serializer getBlockPositionSerializer(boolean optional) {
|
|
return get(MinecraftReflection.getBlockPositionClass(), optional);
|
|
}
|
|
|
|
/**
|
|
* Gets the serializer for Directions
|
|
* @return The serializer
|
|
*/
|
|
public static Serializer getDirectionSerializer() {
|
|
return get(EnumWrappers.getDirectionClass());
|
|
}
|
|
|
|
/**
|
|
* Gets the serializer for UUIDs
|
|
* @param optional If true, objects <b>must</b> be wrapped in an {@link Optional}
|
|
* @return The serializer
|
|
*/
|
|
public static Serializer getUUIDSerializer(boolean optional) {
|
|
return get(UUID.class, optional);
|
|
}
|
|
|
|
/**
|
|
* Gets the serializer for NBT Compound tags
|
|
* @return The serializer
|
|
*/
|
|
public static Serializer getNBTCompoundSerializer() {
|
|
return get(MinecraftReflection.getNBTCompoundClass(), false);
|
|
}
|
|
}
|
|
}
|