mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-11-27 21:26:17 +01:00
Added the ability to read data watchers and watchable object lists.
This commit is contained in:
parent
a567721114
commit
ad69b0caac
@ -19,6 +19,8 @@ import com.comphenix.protocol.reflect.compiler.StructureCompiler;
|
||||
import com.comphenix.protocol.reflect.instances.CollectionGenerator;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
|
||||
import com.comphenix.protocol.wrappers.ChunkPosition;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
|
||||
/**
|
||||
* Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
|
||||
@ -45,7 +47,8 @@ class CleanupStaticMembers {
|
||||
BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class,
|
||||
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
|
||||
BackgroundCompiler.class, StructureCompiler.class,
|
||||
ObjectCloner.class, Packets.Server.class, Packets.Client.class
|
||||
ObjectCloner.class, Packets.Server.class, Packets.Client.class,
|
||||
ChunkPosition.class, WrappedDataWatcher.class
|
||||
};
|
||||
|
||||
String[] internalClasses = {
|
||||
@ -62,7 +65,8 @@ class CleanupStaticMembers {
|
||||
"com.comphenix.protocol.injector.ReadPacketModifier",
|
||||
"com.comphenix.protocol.injector.StructureCache",
|
||||
"com.comphenix.protocol.reflect.compiler.BoxingHelper",
|
||||
"com.comphenix.protocol.reflect.compiler.MethodDescriptor"
|
||||
"com.comphenix.protocol.reflect.compiler.MethodDescriptor",
|
||||
"com.comphenix.protocol.wrappers.WrappedWatchableObject"
|
||||
};
|
||||
|
||||
resetClasses(publicClasses);
|
||||
|
@ -25,26 +25,22 @@ import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.comphenix.protocol.injector.StructureCache;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.comphenix.protocol.wrappers.ChunkPosition;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
@ -66,24 +62,10 @@ public class PacketContainer implements Serializable {
|
||||
// Current structure modifier
|
||||
protected transient StructureModifier<Object> structureModifier;
|
||||
|
||||
// Check whether or not certain classes exists
|
||||
private static boolean hasWorldType = false;
|
||||
|
||||
// The getEntity method
|
||||
private static Method getEntity;
|
||||
|
||||
// Support for serialization
|
||||
private static Method writeMethod;
|
||||
private static Method readMethod;
|
||||
|
||||
static {
|
||||
try {
|
||||
Class.forName("net.minecraft.server.WorldType");
|
||||
hasWorldType = true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a packet container for a new packet.
|
||||
* @param id - ID of the packet to create.
|
||||
@ -150,22 +132,8 @@ public class PacketContainer implements Serializable {
|
||||
*/
|
||||
public StructureModifier<ItemStack> getItemModifier() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<ItemStack>withType(net.minecraft.server.ItemStack.class,
|
||||
getIgnoreNull(new EquivalentConverter<ItemStack>() {
|
||||
public Object getGeneric(ItemStack specific) {
|
||||
return toStackNMS(specific);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getSpecific(Object generic) {
|
||||
return new CraftItemStack((net.minecraft.server.ItemStack) generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ItemStack> getSpecificType() {
|
||||
return ItemStack.class;
|
||||
}
|
||||
}));
|
||||
return structureModifier.<ItemStack>withType(
|
||||
net.minecraft.server.ItemStack.class, BukkitConverters.getItemStackConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -176,17 +144,21 @@ public class PacketContainer implements Serializable {
|
||||
* @return A modifier for ItemStack array fields.
|
||||
*/
|
||||
public StructureModifier<ItemStack[]> getItemArrayModifier() {
|
||||
|
||||
final EquivalentConverter<ItemStack> stackConverter = BukkitConverters.getItemStackConverter();
|
||||
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<ItemStack[]>withType(
|
||||
net.minecraft.server.ItemStack[].class,
|
||||
getIgnoreNull(new EquivalentConverter<ItemStack[]>() {
|
||||
BukkitConverters.getIgnoreNull(new EquivalentConverter<ItemStack[]>() {
|
||||
|
||||
public Object getGeneric(ItemStack[] specific) {
|
||||
public Object getGeneric(Class<?>genericType, ItemStack[] specific) {
|
||||
net.minecraft.server.ItemStack[] result = new net.minecraft.server.ItemStack[specific.length];
|
||||
|
||||
// Unwrap every item
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = toStackNMS(specific[i]);
|
||||
result[i] = (net.minecraft.server.ItemStack) stackConverter.getGeneric(
|
||||
net.minecraft.server.ItemStack.class, specific[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -198,7 +170,7 @@ public class PacketContainer implements Serializable {
|
||||
|
||||
// Add the wrapper
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = new CraftItemStack(input[i]);
|
||||
result[i] = stackConverter.getSpecific(input[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -210,20 +182,6 @@ public class PacketContainer implements Serializable {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an item stack to the NMS equivalent.
|
||||
* @param stack - Bukkit stack to convert.
|
||||
* @return A bukkit stack.
|
||||
*/
|
||||
private net.minecraft.server.ItemStack toStackNMS(ItemStack stack) {
|
||||
// We must be prepared for an object that simply implements ItemStcak
|
||||
if (stack instanceof CraftItemStack) {
|
||||
return ((CraftItemStack) stack).getHandle();
|
||||
} else {
|
||||
return (new CraftItemStack(stack)).getHandle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for the world type enum.
|
||||
* <p>
|
||||
@ -232,33 +190,21 @@ public class PacketContainer implements Serializable {
|
||||
* @return A modifier for world type fields.
|
||||
*/
|
||||
public StructureModifier<WorldType> getWorldTypeModifier() {
|
||||
|
||||
if (!hasWorldType) {
|
||||
// We couldn't find the Minecraft equivalent
|
||||
return structureModifier.withType(null);
|
||||
}
|
||||
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<WorldType>withType(
|
||||
net.minecraft.server.WorldType.class,
|
||||
getIgnoreNull(new EquivalentConverter<WorldType>() {
|
||||
|
||||
@Override
|
||||
public Object getGeneric(WorldType specific) {
|
||||
return net.minecraft.server.WorldType.getType(specific.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldType getSpecific(Object generic) {
|
||||
net.minecraft.server.WorldType type = (net.minecraft.server.WorldType) generic;
|
||||
return WorldType.getByName(type.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WorldType> getSpecificType() {
|
||||
return WorldType.class;
|
||||
}
|
||||
}));
|
||||
BukkitConverters.getWorldTypeConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for data watchers.
|
||||
* @return A modifier for data watchers.
|
||||
*/
|
||||
public StructureModifier<WrappedDataWatcher> getDataWatcherModifier() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<WrappedDataWatcher>withType(
|
||||
net.minecraft.server.DataWatcher.class,
|
||||
BukkitConverters.getDataWatcherConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -272,64 +218,9 @@ public class PacketContainer implements Serializable {
|
||||
* @return A modifier entity types.
|
||||
*/
|
||||
public StructureModifier<Entity> getEntityModifier(World world) {
|
||||
|
||||
final Object worldServer = ((CraftWorld) world).getHandle();
|
||||
final Class<?> nmsEntityClass = net.minecraft.server.Entity.class;
|
||||
|
||||
if (getEntity == null)
|
||||
getEntity = FuzzyReflection.fromObject(worldServer).getMethodByParameters(
|
||||
"getEntity", nmsEntityClass, new Class[] { int.class });
|
||||
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<Entity>withType(
|
||||
int.class,
|
||||
getIgnoreNull(new EquivalentConverter<Entity>() {
|
||||
|
||||
@Override
|
||||
public Object getGeneric(Entity specific) {
|
||||
// Simple enough
|
||||
return specific.getEntityId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity getSpecific(Object generic) {
|
||||
try {
|
||||
net.minecraft.server.Entity nmsEntity = (net.minecraft.server.Entity)
|
||||
getEntity.invoke(worldServer, generic);
|
||||
Integer id = (Integer) generic;
|
||||
|
||||
// Attempt to get the Bukkit entity
|
||||
if (nmsEntity != null) {
|
||||
return nmsEntity.getBukkitEntity();
|
||||
} else {
|
||||
Server server = Bukkit.getServer();
|
||||
|
||||
// Maybe it's a player that has just logged in? Try a search
|
||||
if (server != null) {
|
||||
for (Player player : server.getOnlinePlayers()) {
|
||||
if (player.getEntityId() == id) {
|
||||
return player;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException("Incorrect arguments detected.", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot read field due to a security limitation.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Error occured in Minecraft method.", e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Entity> getSpecificType() {
|
||||
return Entity.class;
|
||||
}
|
||||
}));
|
||||
int.class, BukkitConverters.getEntityConverter(world));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -348,100 +239,35 @@ public class PacketContainer implements Serializable {
|
||||
* <p>
|
||||
* This modifier will automatically marshall between the visible ProtocolLib ChunkPosition and the
|
||||
* internal Minecraft ChunkPosition.
|
||||
* @return A modifier for ChunkPosition array fields.
|
||||
* @return A modifier for ChunkPosition list fields.
|
||||
*/
|
||||
public StructureModifier<List<ChunkPosition>> getPositionCollectionModifier() {
|
||||
final EquivalentConverter<ChunkPosition> converter = ChunkPosition.getConverter();
|
||||
|
||||
// Convert to and from the ProtocolLib wrapper
|
||||
final StructureModifier<List<ChunkPosition>> modifier = structureModifier.withType(
|
||||
return structureModifier.withType(
|
||||
Collection.class,
|
||||
getIgnoreNull(new EquivalentConverter<List<ChunkPosition>>() {
|
||||
private Class<?> collectionType;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public List<ChunkPosition> getSpecific(Object generic) {
|
||||
if (generic instanceof Collection) {
|
||||
List<ChunkPosition> positions = new ArrayList<ChunkPosition>();
|
||||
|
||||
// Save the type
|
||||
collectionType = generic.getClass();
|
||||
|
||||
// Copy everything to a new list
|
||||
for (Object item : (Collection<Object>) generic) {
|
||||
ChunkPosition result = converter.getSpecific(item);
|
||||
|
||||
if (item != null)
|
||||
positions.add(result);
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
// Not valid
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object getGeneric(List<ChunkPosition> specific) {
|
||||
// Just go by the first field
|
||||
if (collectionType == null) {
|
||||
collectionType = structureModifier.withType(Collection.class).getFields().get(0).getType();
|
||||
}
|
||||
|
||||
Collection<Object> newContainer = (Collection<Object>) DefaultInstances.DEFAULT.getDefault(collectionType);
|
||||
|
||||
// Convert each object
|
||||
for (ChunkPosition position : specific) {
|
||||
Object converted = converter.getGeneric(position);
|
||||
|
||||
if (position == null)
|
||||
newContainer.add(null);
|
||||
else if (converted != null)
|
||||
newContainer.add(converted);
|
||||
}
|
||||
return newContainer;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Class<List<ChunkPosition>> getSpecificType() {
|
||||
// Damn you Java
|
||||
Class<?> dummy = List.class;
|
||||
return (Class<List<ChunkPosition>>) dummy;
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
return modifier;
|
||||
BukkitConverters.getListConverter(
|
||||
net.minecraft.server.ChunkPosition.class,
|
||||
ChunkPosition.getConverter())
|
||||
);
|
||||
}
|
||||
|
||||
private <TType> EquivalentConverter<TType> getIgnoreNull(final EquivalentConverter<TType> delegate) {
|
||||
// Automatically wrap all parameters to the delegate with a NULL check
|
||||
return new EquivalentConverter<TType>() {
|
||||
public Object getGeneric(TType specific) {
|
||||
if (specific != null)
|
||||
return delegate.getGeneric(specific);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TType getSpecific(Object generic) {
|
||||
if (generic != null)
|
||||
return delegate.getSpecific(generic);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<TType> getSpecificType() {
|
||||
return delegate.getSpecificType();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Retrieves a read/write structure for collections of watchable objects.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between the visible WrappedWatchableObject and the
|
||||
* internal Minecraft WatchableObject.
|
||||
* @return A modifier for watchable object list fields.
|
||||
*/
|
||||
public StructureModifier<List<WrappedWatchableObject>> getWatchableCollectionModifier() {
|
||||
// Convert to and from the ProtocolLib wrapper
|
||||
return structureModifier.withType(
|
||||
Collection.class,
|
||||
BukkitConverters.getListConverter(
|
||||
net.minecraft.server.WatchableObject.class,
|
||||
BukkitConverters.getWatchableObjectConverter())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the ID of this packet.
|
||||
* @return Packet ID.
|
||||
|
@ -24,8 +24,25 @@ package com.comphenix.protocol.reflect;
|
||||
* @param <TType> The specific type.
|
||||
*/
|
||||
public interface EquivalentConverter<TType> {
|
||||
/**
|
||||
* Retrieve a copy of the specific type using an instance of the generic type.
|
||||
* @param generic - the generic type.
|
||||
* @return The new specific type.
|
||||
*/
|
||||
public TType getSpecific(Object generic);
|
||||
public Object getGeneric(TType specific);
|
||||
|
||||
/**
|
||||
* Retrieve a copy of the generic type from a specific type.
|
||||
* @param genericType - class or super class of the generic type.
|
||||
* @param specific - the specific type we need to copy.
|
||||
* @return A copy of the specific type.
|
||||
*/
|
||||
public Object getGeneric(Class<?> genericType, TType specific);
|
||||
|
||||
/**
|
||||
* Due to type erasion, we need to explicitly keep a reference to the specific type.
|
||||
* @return The specific type.
|
||||
*/
|
||||
public Class<TType> getSpecificType();
|
||||
}
|
||||
|
||||
|
@ -256,7 +256,7 @@ public class StructureModifier<TField> {
|
||||
throw new IllegalStateException("Cannot write to a NULL target.");
|
||||
|
||||
// Use the converter, if it exists
|
||||
Object obj = needConversion() ? converter.getGeneric(value) : value;
|
||||
Object obj = needConversion() ? converter.getGeneric(getFieldType(fieldIndex), value) : value;
|
||||
|
||||
try {
|
||||
FieldUtils.writeField(data.get(fieldIndex), target, obj, true);
|
||||
@ -268,6 +268,15 @@ public class StructureModifier<TField> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the type of a specified field.
|
||||
* @param index - the index.
|
||||
* @return The type of the given field.
|
||||
*/
|
||||
protected Class<?> getFieldType(int index) {
|
||||
return data.get(index).getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not we should use the converter instance.
|
||||
* @return TRUE if we should, FALSE otherwise.
|
||||
|
@ -108,7 +108,7 @@ public abstract class CompiledStructureModifier<TField> extends StructureModifie
|
||||
@Override
|
||||
public StructureModifier<TField> write(int index, Object value) throws FieldAccessException {
|
||||
if (converter != null)
|
||||
value = converter.getGeneric((TField) value);
|
||||
value = converter.getGeneric(getFieldType(index), (TField) value);
|
||||
return writeGenerated(index, value);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,307 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.server.DataWatcher;
|
||||
import net.minecraft.server.WatchableObject;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
|
||||
/**
|
||||
* Contains several useful equivalent converters for normal Bukkit types.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BukkitConverters {
|
||||
// Check whether or not certain classes exists
|
||||
private static boolean hasWorldType = false;
|
||||
|
||||
// The getEntity method
|
||||
private static Method getEntity;
|
||||
|
||||
static {
|
||||
try {
|
||||
Class.forName("net.minecraft.server.WorldType");
|
||||
hasWorldType = true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> EquivalentConverter<List<T>> getListConverter(final Class<?> genericItemType, final EquivalentConverter<T> itemConverter) {
|
||||
// Convert to and from the wrapper
|
||||
return getIgnoreNull(new EquivalentConverter<List<T>>() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public List<T> getSpecific(Object generic) {
|
||||
if (generic instanceof Collection) {
|
||||
List<T> items = new ArrayList<T>();
|
||||
|
||||
// Copy everything to a new list
|
||||
for (Object item : (Collection<Object>) generic) {
|
||||
T result = itemConverter.getSpecific(item);
|
||||
|
||||
if (item != null)
|
||||
items.add(result);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
// Not valid
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object getGeneric(Class<?> genericType, List<T> specific) {
|
||||
Collection<Object> newContainer = (Collection<Object>) DefaultInstances.DEFAULT.getDefault(genericType);
|
||||
|
||||
// Convert each object
|
||||
for (T position : specific) {
|
||||
Object converted = itemConverter.getGeneric(genericItemType, position);
|
||||
|
||||
if (position == null)
|
||||
newContainer.add(null);
|
||||
else if (converted != null)
|
||||
newContainer.add(converted);
|
||||
}
|
||||
return newContainer;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Class<List<T>> getSpecificType() {
|
||||
// Damn you Java
|
||||
Class<?> dummy = List.class;
|
||||
return (Class<List<T>>) dummy;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for watchable objects and the respective wrapper.
|
||||
* @return A watchable object converter.
|
||||
*/
|
||||
public static EquivalentConverter<WrappedWatchableObject> getWatchableObjectConverter() {
|
||||
return getIgnoreNull(new EquivalentConverter<WrappedWatchableObject>() {
|
||||
@Override
|
||||
public Object getGeneric(Class<?> genericType, WrappedWatchableObject specific) {
|
||||
return specific.getHandle();
|
||||
}
|
||||
|
||||
public WrappedWatchableObject getSpecific(Object generic) {
|
||||
if (generic instanceof WatchableObject)
|
||||
return new WrappedWatchableObject((WatchableObject) generic);
|
||||
else if (generic instanceof WrappedWatchableObject)
|
||||
return (WrappedWatchableObject) generic;
|
||||
else
|
||||
throw new IllegalArgumentException("Unrecognized type " + generic.getClass());
|
||||
};
|
||||
|
||||
@Override
|
||||
public Class<WrappedWatchableObject> getSpecificType() {
|
||||
return WrappedWatchableObject.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for the NMS DataWatcher class and our wrapper.
|
||||
* @return A DataWatcher converter.
|
||||
*/
|
||||
public static EquivalentConverter<WrappedDataWatcher> getDataWatcherConverter() {
|
||||
return getIgnoreNull(new EquivalentConverter<WrappedDataWatcher>() {
|
||||
@Override
|
||||
public Object getGeneric(Class<?> genericType, WrappedDataWatcher specific) {
|
||||
return specific.getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedDataWatcher getSpecific(Object generic) {
|
||||
if (generic instanceof DataWatcher)
|
||||
return new WrappedDataWatcher((DataWatcher) generic);
|
||||
else if (generic instanceof WrappedDataWatcher)
|
||||
return (WrappedDataWatcher) generic;
|
||||
else
|
||||
throw new IllegalArgumentException("Unrecognized type " + generic.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WrappedDataWatcher> getSpecificType() {
|
||||
return WrappedDataWatcher.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for Bukkit's world type enum and the NMS equivalent.
|
||||
* @return A world type enum converter.
|
||||
*/
|
||||
public static EquivalentConverter<WorldType> getWorldTypeConverter() {
|
||||
// Check that we can actually use this converter
|
||||
if (!hasWorldType)
|
||||
return null;
|
||||
|
||||
return getIgnoreNull(new EquivalentConverter<WorldType>() {
|
||||
@Override
|
||||
public Object getGeneric(Class<?> genericType, WorldType specific) {
|
||||
return net.minecraft.server.WorldType.getType(specific.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldType getSpecific(Object generic) {
|
||||
net.minecraft.server.WorldType type = (net.minecraft.server.WorldType) generic;
|
||||
return WorldType.getByName(type.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WorldType> getSpecificType() {
|
||||
return WorldType.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for NMS entities and Bukkit entities.
|
||||
* @param world - the current world.
|
||||
* @return A converter between the underlying NMS entity and Bukkit's wrapper.
|
||||
*/
|
||||
public static EquivalentConverter<Entity> getEntityConverter(World world) {
|
||||
final Object worldServer = ((CraftWorld) world).getHandle();
|
||||
final Class<?> nmsEntityClass = net.minecraft.server.Entity.class;
|
||||
|
||||
if (getEntity == null)
|
||||
getEntity = FuzzyReflection.fromObject(worldServer).getMethodByParameters(
|
||||
"getEntity", nmsEntityClass, new Class[] { int.class });
|
||||
|
||||
return getIgnoreNull(new EquivalentConverter<Entity>() {
|
||||
|
||||
@Override
|
||||
public Object getGeneric(Class<?> genericType, Entity specific) {
|
||||
// Simple enough
|
||||
return specific.getEntityId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity getSpecific(Object generic) {
|
||||
try {
|
||||
net.minecraft.server.Entity nmsEntity = (net.minecraft.server.Entity)
|
||||
getEntity.invoke(worldServer, generic);
|
||||
Integer id = (Integer) generic;
|
||||
|
||||
// Attempt to get the Bukkit entity
|
||||
if (nmsEntity != null) {
|
||||
return nmsEntity.getBukkitEntity();
|
||||
} else {
|
||||
Server server = Bukkit.getServer();
|
||||
|
||||
// Maybe it's a player that has just logged in? Try a search
|
||||
if (server != null) {
|
||||
for (Player player : server.getOnlinePlayers()) {
|
||||
if (player.getEntityId() == id) {
|
||||
return player;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException("Incorrect arguments detected.", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot read field due to a security limitation.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Error occured in Minecraft method.", e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Entity> getSpecificType() {
|
||||
return Entity.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the converter used to convert NMS ItemStacks to Bukkit's ItemStack.
|
||||
* @return Item stack converter.
|
||||
*/
|
||||
public static EquivalentConverter<ItemStack> getItemStackConverter() {
|
||||
return getIgnoreNull(new EquivalentConverter<ItemStack>() {
|
||||
public Object getGeneric(Class<?> genericType, ItemStack specific) {
|
||||
return toStackNMS(specific);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getSpecific(Object generic) {
|
||||
return new CraftItemStack((net.minecraft.server.ItemStack) generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ItemStack> getSpecificType() {
|
||||
return ItemStack.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an item stack to the NMS equivalent.
|
||||
* @param stack - Bukkit stack to convert.
|
||||
* @return A bukkit stack.
|
||||
*/
|
||||
private static net.minecraft.server.ItemStack toStackNMS(ItemStack stack) {
|
||||
// We must be prepared for an object that simply implements ItemStcak
|
||||
if (stack instanceof CraftItemStack) {
|
||||
return ((CraftItemStack) stack).getHandle();
|
||||
} else {
|
||||
return (new CraftItemStack(stack)).getHandle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a given equivalent converter in NULL checks, ensuring that such values are ignored.
|
||||
* @param delegate - the underlying equivalent converter.
|
||||
* @return A equivalent converter that ignores NULL values.
|
||||
*/
|
||||
public static <TType> EquivalentConverter<TType> getIgnoreNull(final EquivalentConverter<TType> delegate) {
|
||||
// Automatically wrap all parameters to the delegate with a NULL check
|
||||
return new EquivalentConverter<TType>() {
|
||||
public Object getGeneric(Class<?> genericType, TType specific) {
|
||||
if (specific != null)
|
||||
return delegate.getGeneric(genericType, specific);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TType getSpecific(Object generic) {
|
||||
if (generic != null)
|
||||
return delegate.getSpecific(generic);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<TType> getSpecificType() {
|
||||
return delegate.getSpecificType();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -8,11 +8,17 @@ import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
/**
|
||||
* Wraps a immutable net.minecraft.server.ChunkPosition, which represents a integer 3D vector.
|
||||
* Copies a immutable net.minecraft.server.ChunkPosition, which represents a integer 3D vector.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ChunkPosition {
|
||||
|
||||
/**
|
||||
* Represents the null (0, 0, 0) origin.
|
||||
*/
|
||||
public static ChunkPosition ORIGIN = new ChunkPosition(0, 0, 0);
|
||||
|
||||
// Use protected members, like Bukkit
|
||||
protected final int x;
|
||||
protected final int y;
|
||||
@ -74,14 +80,56 @@ public class ChunkPosition {
|
||||
return z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the current position and a given position together, producing a result position.
|
||||
* @param other - the other position.
|
||||
* @return The new result position.
|
||||
*/
|
||||
public ChunkPosition add(ChunkPosition other) {
|
||||
if (other == null)
|
||||
throw new IllegalArgumentException("other cannot be NULL");
|
||||
return new ChunkPosition(x + other.x, y + other.y, z + other.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the current position and a given position together, producing a result position.
|
||||
* @param other - the other position.
|
||||
* @return The new result position.
|
||||
*/
|
||||
public ChunkPosition subtract(ChunkPosition other) {
|
||||
if (other == null)
|
||||
throw new IllegalArgumentException("other cannot be NULL");
|
||||
return new ChunkPosition(x - other.x, y - other.y, z - other.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiply each dimension in the current position by the given factor.
|
||||
* @param factor - multiplier.
|
||||
* @return The new result.
|
||||
*/
|
||||
public ChunkPosition multiply(int factor) {
|
||||
return new ChunkPosition(x * factor, y * factor, z * factor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Divide each dimension in the current position by the given divisor.
|
||||
* @param divisor - the divisor.
|
||||
* @return The new result.
|
||||
*/
|
||||
public ChunkPosition divide(int divisor) {
|
||||
if (divisor == 0)
|
||||
throw new IllegalArgumentException("Cannot divide by null.");
|
||||
return new ChunkPosition(x / divisor, y / divisor, z / divisor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to convert between NMS ChunkPosition and the wrapper instance.
|
||||
* @return
|
||||
* @return A new converter.
|
||||
*/
|
||||
public static EquivalentConverter<ChunkPosition> getConverter() {
|
||||
return new EquivalentConverter<ChunkPosition>() {
|
||||
@Override
|
||||
public Object getGeneric(ChunkPosition specific) {
|
||||
public Object getGeneric(Class<?> genericType, ChunkPosition specific) {
|
||||
return new net.minecraft.server.ChunkPosition(specific.x, specific.z, specific.z);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,105 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import net.minecraft.server.ChunkCoordinates;
|
||||
|
||||
/**
|
||||
* Allows access to a chunk coordinate.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate> {
|
||||
|
||||
/**
|
||||
* If TRUE, NULLs should be put before non-null instances of this class.
|
||||
*/
|
||||
private static final boolean LARGER_THAN_NULL = true;
|
||||
|
||||
protected ChunkCoordinates handle;
|
||||
|
||||
public WrappedChunkCoordinate(ChunkCoordinates handle) {
|
||||
if (handle == null)
|
||||
throw new IllegalArgumentException("handle cannot be NULL");
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
public ChunkCoordinates getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the x coordinate of the underlying coordiate.
|
||||
* @return The x coordinate.
|
||||
*/
|
||||
public int getX() {
|
||||
return handle.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the x coordinate of the underlying coordiate.
|
||||
* @param newX - the new x coordinate.
|
||||
*/
|
||||
public void setX(int newX) {
|
||||
handle.x = newX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the y coordinate of the underlying coordiate.
|
||||
* @return The y coordinate.
|
||||
*/
|
||||
public int getY() {
|
||||
return handle.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the y coordinate of the underlying coordiate.
|
||||
* @param newY - the new y coordinate.
|
||||
*/
|
||||
public void setY(int newY) {
|
||||
handle.y = newY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the z coordinate of the underlying coordiate.
|
||||
* @return The z coordinate.
|
||||
*/
|
||||
public int getZ() {
|
||||
return handle.z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the z coordinate of the underlying coordiate.
|
||||
* @param newZ - the new z coordinate.
|
||||
*/
|
||||
public void setZ(int newZ) {
|
||||
handle.z = newZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(WrappedChunkCoordinate other) {
|
||||
// We'll handle NULL objects too, unlike ChunkCoordinates
|
||||
if (other.handle == null)
|
||||
return LARGER_THAN_NULL ? -1 : 1;
|
||||
else
|
||||
return handle.compareTo(other.handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof WrappedChunkCoordinate) {
|
||||
WrappedChunkCoordinate wrapper = (WrappedChunkCoordinate) other;
|
||||
return Objects.equal(handle, wrapper.handle);
|
||||
}
|
||||
|
||||
// It's tempting to handle the ChunkCoordinate case too, but then
|
||||
// the equals() method won't be commutative, causing a.equals(b) to
|
||||
// be different to b.equals(a).
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return handle.hashCode();
|
||||
}
|
||||
}
|
@ -0,0 +1,434 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import net.minecraft.server.ChunkCoordinates;
|
||||
import net.minecraft.server.DataWatcher;
|
||||
import net.minecraft.server.WatchableObject;
|
||||
|
||||
/**
|
||||
* Wraps a DataWatcher that is used to transmit arbitrary key-value pairs with a given entity.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedDataWatcher {
|
||||
|
||||
/**
|
||||
* Used to assign integer IDs to given types.
|
||||
*/
|
||||
private static Map<Class<?>, Integer> typeMap;
|
||||
|
||||
// Fields
|
||||
private static Field valueMapField;
|
||||
private static Field readWriteLockField;
|
||||
|
||||
// Methods
|
||||
private static Method createKeyValueMethod;
|
||||
private static Method updateKeyValueMethod;
|
||||
private static Method getKeyValueMethod;
|
||||
|
||||
/**
|
||||
* Whether or not this class has already been initialized.
|
||||
*/
|
||||
private static boolean hasInitialized;
|
||||
|
||||
// The underlying DataWatcher we're modifying
|
||||
protected DataWatcher handle;
|
||||
|
||||
// Lock
|
||||
private ReadWriteLock readWriteLock;
|
||||
|
||||
// Map of watchable objects
|
||||
private Map<Integer, Object> watchableObjects;
|
||||
|
||||
/**
|
||||
* Initialize a new data watcher.
|
||||
* @throws FieldAccessException If we're unable to wrap a DataWatcher.
|
||||
*/
|
||||
public WrappedDataWatcher() {
|
||||
// Just create a new watcher
|
||||
this(new DataWatcher());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapper for a given data watcher.
|
||||
* @param dataWatcher - the data watcher to wrap.
|
||||
* @throws FieldAccessException If we're unable to wrap a DataWatcher.
|
||||
*/
|
||||
public WrappedDataWatcher(DataWatcher handle) {
|
||||
this.handle = handle;
|
||||
|
||||
try {
|
||||
initialize();
|
||||
} catch (FieldAccessException e) {
|
||||
throw new RuntimeException("Cannot initialize wrapper.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the underlying data watcher.
|
||||
* @return The underlying data watcher.
|
||||
*/
|
||||
public DataWatcher getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the ID of a given type, if it's allowed to be watched.
|
||||
* @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();
|
||||
|
||||
return typeMap.get(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the type of a given ID, if it's allowed to be watched.
|
||||
* @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 : typeMap.entrySet()) {
|
||||
if (Objects.equal(entry.getValue(), id)) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown class type
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched byte.
|
||||
* @param index - index of the watched byte.
|
||||
* @return The watched byte, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public Byte getByte(int index) throws FieldAccessException {
|
||||
return (Byte) getObjectRaw(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched short.
|
||||
* @param index - index of the watched short.
|
||||
* @return The watched short, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public Short getShort(int index) throws FieldAccessException {
|
||||
return (Short) getObjectRaw(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched integer.
|
||||
* @param index - index of the watched integer.
|
||||
* @return The watched integer, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public Integer getInteger(int index) throws FieldAccessException {
|
||||
return (Integer) getObjectRaw(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched float.
|
||||
* @param index - index of the watched float.
|
||||
* @return The watched float, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public Float getFloat(int index) throws FieldAccessException {
|
||||
return (Float) getObjectRaw(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched string.
|
||||
* @param index - index of the watched string.
|
||||
* @return The watched string, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public String getString(int index) throws FieldAccessException {
|
||||
return (String) getObjectRaw(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched string.
|
||||
* @param index - index of the watched string.
|
||||
* @return The watched string, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public ItemStack getItemStack(int index) throws FieldAccessException {
|
||||
return (ItemStack) 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.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public WrappedChunkCoordinate getChunkCoordinate(int index) throws FieldAccessException {
|
||||
return (WrappedChunkCoordinate) getObject(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a watchable object by index.
|
||||
* @param index - index of the object to retrieve.
|
||||
* @return The watched object.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public Object getObject(int index) throws FieldAccessException {
|
||||
Object result = getObjectRaw(index);
|
||||
|
||||
// Handle the special cases too
|
||||
if (result instanceof net.minecraft.server.ItemStack) {
|
||||
return BukkitConverters.getItemStackConverter().getSpecific(result);
|
||||
} else if (result instanceof ChunkCoordinates) {
|
||||
return new WrappedChunkCoordinate((ChunkCoordinates) result);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a watchable object by index.
|
||||
* @param index - index of the object to retrieve.
|
||||
* @return The watched object.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
private Object getObjectRaw(int index) throws FieldAccessException {
|
||||
// The get method will take care of concurrency
|
||||
WatchableObject watchable = getWatchedObject(index);
|
||||
|
||||
if (watchable != null) {
|
||||
return new WrappedWatchableObject(watchable).getValue();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
try {
|
||||
getReadWriteLock().readLock().lock();
|
||||
return new HashSet<Integer>(getWatchableObjectMap().keySet());
|
||||
|
||||
} finally {
|
||||
getReadWriteLock().readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
try {
|
||||
getReadWriteLock().readLock().lock();
|
||||
return getWatchableObjectMap().size();
|
||||
|
||||
} finally {
|
||||
getReadWriteLock().readLock().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 clients.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public void setObject(int index, Object newValue, boolean update) throws FieldAccessException {
|
||||
// Convert special cases
|
||||
if (newValue instanceof WrappedChunkCoordinate)
|
||||
newValue = ((WrappedChunkCoordinate) newValue).getHandle();
|
||||
if (newValue instanceof ItemStack)
|
||||
newValue = BukkitConverters.getItemStackConverter().getGeneric(
|
||||
net.minecraft.server.ItemStack.class, (ItemStack) newValue);
|
||||
|
||||
// Next, set the object
|
||||
setObjectRaw(index, newValue, update);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a watchable object by index.
|
||||
* @param index - index of the object to retrieve.
|
||||
* @param newValue - the new watched value.
|
||||
* @param update - whether or not to refresh every listening clients.
|
||||
* @return The watched object.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
private void setObjectRaw(int index, Object newValue, boolean update) throws FieldAccessException {
|
||||
WatchableObject watchable;
|
||||
|
||||
try {
|
||||
// Aquire write lock
|
||||
getReadWriteLock().writeLock().lock();
|
||||
watchable = getWatchedObject(index);
|
||||
|
||||
if (watchable != null) {
|
||||
new WrappedWatchableObject(watchable).setValue(newValue, update);
|
||||
}
|
||||
} finally {
|
||||
getReadWriteLock().writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private WatchableObject getWatchedObject(int index) throws FieldAccessException {
|
||||
// We use the get-method first and foremost
|
||||
if (getKeyValueMethod != null) {
|
||||
try {
|
||||
return (WatchableObject) getKeyValueMethod.invoke(handle, index);
|
||||
} catch (Exception e) {
|
||||
throw new FieldAccessException("Cannot invoke get key method for index " + index, e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
getReadWriteLock().readLock().lock();
|
||||
return (WatchableObject) 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 (readWriteLockField != null)
|
||||
return readWriteLock = (ReadWriteLock) FieldUtils.readField(readWriteLockField, 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) {
|
||||
try {
|
||||
watchableObjects = (Map<Integer, Object>) FieldUtils.readField(valueMapField, handle, true);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot read watchable object field.", e);
|
||||
}
|
||||
}
|
||||
return watchableObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (!hasInitialized)
|
||||
hasInitialized = true;
|
||||
else
|
||||
return;
|
||||
|
||||
FuzzyReflection fuzzy = FuzzyReflection.fromClass(DataWatcher.class, true);
|
||||
|
||||
for (Field lookup : fuzzy.getFieldListByType(Map.class)) {
|
||||
if (Modifier.isStatic(lookup.getModifiers())) {
|
||||
// This must be the type map
|
||||
try {
|
||||
typeMap = (Map<Class<?>, Integer>) FieldUtils.readStaticField(lookup, true);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot access type map field.", e);
|
||||
}
|
||||
|
||||
} else {
|
||||
// If not, then we're probably dealing with the value map
|
||||
valueMapField = lookup;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
readWriteLockField = fuzzy.getFieldByType("readWriteLock", ReadWriteLock.class);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// It's not a big deal
|
||||
}
|
||||
|
||||
initializeMethods(fuzzy);
|
||||
}
|
||||
|
||||
private static void initializeMethods(FuzzyReflection fuzzy) {
|
||||
List<Method> candidates = fuzzy.getMethodListByParameters(Void.TYPE,
|
||||
new Class<?>[] { int.class, Object.class});
|
||||
|
||||
for (Method method : candidates) {
|
||||
|
||||
if (!method.getName().startsWith("watch")) {
|
||||
createKeyValueMethod = method;
|
||||
} else {
|
||||
updateKeyValueMethod = method;
|
||||
}
|
||||
}
|
||||
|
||||
// Did we succeed?
|
||||
if (updateKeyValueMethod == null || createKeyValueMethod == null) {
|
||||
// Go by index instead
|
||||
if (candidates.size() > 1) {
|
||||
createKeyValueMethod = candidates.get(0);
|
||||
updateKeyValueMethod = candidates.get(1);
|
||||
} else {
|
||||
throw new IllegalStateException("Unable to find create and update watchable object. Update ProtocolLib.");
|
||||
}
|
||||
}
|
||||
|
||||
// Load the get-method
|
||||
try {
|
||||
getKeyValueMethod = fuzzy.getMethodByParameters(
|
||||
"getWatchableObject", ".*WatchableObject", new String[] { int.class.getName() });
|
||||
getKeyValueMethod.setAccessible(true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Use fallback method
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
|
||||
import net.minecraft.server.WatchableObject;
|
||||
|
||||
/**
|
||||
* Represents a watchable object.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedWatchableObject {
|
||||
|
||||
// Whether or not the reflection machinery has been initialized
|
||||
private static boolean hasInitialized;
|
||||
|
||||
// The field containing the value itself
|
||||
private static StructureModifier<Object> baseModifier;
|
||||
|
||||
protected WatchableObject handle;
|
||||
protected StructureModifier<Object> modifier;
|
||||
|
||||
// Type of the stored value
|
||||
private Class<?> typeClass;
|
||||
|
||||
public WrappedWatchableObject(WatchableObject handle) {
|
||||
initialize();
|
||||
this.handle = handle;
|
||||
this.modifier = baseModifier.withTarget(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the underlying watchable object.
|
||||
* @return The underlying watchable object.
|
||||
*/
|
||||
public WatchableObject getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize reflection machinery.
|
||||
*/
|
||||
private static void initialize() {
|
||||
if (!hasInitialized) {
|
||||
hasInitialized = true;
|
||||
baseModifier = new StructureModifier<Object>(WatchableObject.class, null, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the correct super type of the current value.
|
||||
* @return Super type.
|
||||
* @throws FieldAccessException Unable to read values.
|
||||
*/
|
||||
public Class<?> getValueType() 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.
|
||||
* @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.
|
||||
*/
|
||||
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 (!getValueType().isAssignableFrom(newValue.getClass()))
|
||||
throw new IllegalArgumentException("Object " + newValue + " must be of type " + getValueType().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, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the value field.
|
||||
* @return The watched value.
|
||||
* @throws FieldAccessException Unable to use reflection.
|
||||
*/
|
||||
public Object getValue() throws FieldAccessException {
|
||||
return modifier.withType(Object.class).read(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user