Restore compatibility with 1.8

1.8.8 is still one of the most popular server versions. As a result,
many servers were not receiving important fixes and new APIs.

I was able to accomplish this with minimal bloat, making it worth it.

Upon release, 4.1.0 will become the recommended version for 1.8 thru the
current Spigot build.
This commit is contained in:
Dan Mulloy 2016-07-12 13:37:55 -04:00
parent 6c982a83f0
commit 05ffeb8e7f
8 changed files with 372 additions and 92 deletions

View File

@ -31,7 +31,7 @@ import com.google.common.util.concurrent.Futures;
* Note that vanilla Minecraft reuses packet IDs per protocol (ping, game, login) and IDs are subject to change, so they are not reliable.
* @author Kristian
*/
public class PacketType implements Serializable, Comparable<PacketType> {
public class PacketType implements Serializable, Cloneable, Comparable<PacketType> {
// Increment whenever the type changes
private static final long serialVersionUID = 1L;
@ -182,29 +182,53 @@ public class PacketType implements Serializable, Comparable<PacketType> {
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x4B, 0x20, "UpdateAttributes");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x4C, 0x1D, "EntityEffect");
/**
* @deprecated Replaced by {@link WINDOW_DATA}
*/
@Deprecated
public static final PacketType CRAFT_PROGRESS_BAR = WINDOW_DATA;
// ---- Removed in 1.9
/**
* @deprecated Replaced by {@link REL_ENTITY_MOVE_LOOK}
* @deprecated Removed in 1.9
*/
@Deprecated
public static final PacketType ENTITY_MOVE_LOOK = REL_ENTITY_MOVE_LOOK;
public static final PacketType MAP_CHUNK_BULK = new PacketType(PROTOCOL, SENDER, -1, -1, "MapChunkBulk").deprecated();
/**
* @deprecated Replaced by {@link STATISTIC}
* @deprecated Removed in 1.9
*/
@Deprecated
public static final PacketType STATISTICS = STATISTIC;
public static final PacketType SET_COMPRESSION = new PacketType(PROTOCOL, SENDER, -1, -1, "SetCompression").deprecated();
/**
* @deprecated Removed in 1.9
*/
@Deprecated
public static final PacketType UPDATE_ENTITY_NBT = new PacketType(PROTOCOL, SENDER, -1, -1, "UpdateEntityNBT").deprecated();
// ----- Renamed packets
/**
* @deprecated Renamed to {@link WINDOW_DATA}
*/
@Deprecated
public static final PacketType CRAFT_PROGRESS_BAR = WINDOW_DATA.deprecated();
/**
* @deprecated Renamed to {@link REL_ENTITY_MOVE_LOOK}
*/
@Deprecated
public static final PacketType ENTITY_MOVE_LOOK = REL_ENTITY_MOVE_LOOK.deprecated();
/**
* @deprecated Renamed to {@link STATISTIC}
*/
@Deprecated
public static final PacketType STATISTICS = STATISTIC.deprecated();
// ----- Replaced in 1.9.4
/**
* @deprecated Replaced by {@link TILE_ENTITY_DATA}
*/
@Deprecated
public static final PacketType UPDATE_SIGN = TILE_ENTITY_DATA;
public static final PacketType UPDATE_SIGN = TILE_ENTITY_DATA.deprecated();
// The instance must
private final static Server INSTANCE = new Server();
@ -297,7 +321,7 @@ public class PacketType implements Serializable, Comparable<PacketType> {
* @deprecated Replaced by {@link SERVER_INFO}
*/
@Deprecated
public static final PacketType OUT_SERVER_INFO = SERVER_INFO;
public static final PacketType OUT_SERVER_INFO = SERVER_INFO.deprecated();
private final static Server INSTANCE = new Server();
@ -562,6 +586,7 @@ public class PacketType implements Serializable, Comparable<PacketType> {
private boolean forceAsync;
private boolean dynamic;
private boolean deprecated;
/**
* Retrieve the current packet/legacy lookup.
@ -1043,6 +1068,16 @@ public class PacketType implements Serializable, Comparable<PacketType> {
return forceAsync;
}
private PacketType deprecated() {
PacketType ret = clone();
ret.deprecated = true;
return ret;
}
public boolean isDeprecated() {
return deprecated;
}
@Override
public int hashCode() {
return Objects.hashCode(protocol, sender, currentId, legacyId);
@ -1078,8 +1113,17 @@ public class PacketType implements Serializable, Comparable<PacketType> {
Class<?> clazz = getPacketClass();
if (clazz == null)
return "UNREGISTERED[" + protocol + ", " + sender + ", " + currentId + ", legacy: " + legacyId + ", classNames: " + Arrays.toString(classNames) + "]";
return name() + "[" + protocol + ", " + sender + ", " + currentId + ", legacy: " + legacyId + ", classNames: " + Arrays.toString(classNames) + " (unregistered)]";
else
return clazz.getSimpleName() + "[" + currentId + ", legacy: " + legacyId + "]";
}
@Override
public PacketType clone() {
try {
return (PacketType) super.clone();
} catch (CloneNotSupportedException ex) {
throw new Error("This shouldn't happen", ex);
}
}
}

View File

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

View File

@ -18,6 +18,7 @@
package com.comphenix.protocol.injector.packet;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@ -28,6 +29,7 @@ import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.netty.NettyProtocolRegistry;
import com.comphenix.protocol.injector.netty.ProtocolRegistry;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.base.Function;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
@ -154,11 +156,10 @@ public class PacketRegistry {
*/
@Deprecated
public static Set<Integer> getServerPackets() throws FieldAccessException {
initialize();
if (LEGACY_SERVER_PACKETS == null) {
LEGACY_SERVER_PACKETS = toLegacy(NETTY.getServerPackets());
LEGACY_SERVER_PACKETS = toLegacy(getServerPacketTypes());
}
return LEGACY_SERVER_PACKETS;
}
@ -168,9 +169,18 @@ public class PacketRegistry {
*/
public static Set<PacketType> getServerPacketTypes() {
initialize();
NETTY.synchronize();
return NETTY.getServerPackets();
Set<PacketType> types = new HashSet<>();
// Filter out unsupported packets
for (PacketType type : NETTY.getServerPackets()) {
if (!type.isDeprecated()) {
types.add(type);
}
}
return types;
}
/**
@ -182,11 +192,10 @@ public class PacketRegistry {
*/
@Deprecated
public static Set<Integer> getClientPackets() throws FieldAccessException {
initialize();
if (LEGACY_CLIENT_PACKETS == null) {
LEGACY_CLIENT_PACKETS = toLegacy(NETTY.getClientPackets());
LEGACY_CLIENT_PACKETS = toLegacy(getClientPacketTypes());
}
return LEGACY_CLIENT_PACKETS;
}
@ -196,9 +205,18 @@ public class PacketRegistry {
*/
public static Set<PacketType> getClientPacketTypes() {
initialize();
NETTY.synchronize();
return NETTY.getClientPackets();
Set<PacketType> types = new HashSet<>();
// Filter out unsupported packets
for (PacketType type : NETTY.getClientPackets()) {
if (!type.isDeprecated()) {
types.add(type);
}
}
return types;
}
/**
@ -269,7 +287,24 @@ public class PacketRegistry {
*/
public static Class getPacketClassFromType(PacketType type, boolean forceVanilla) {
initialize();
return NETTY.getPacketTypeLookup().get(type);
// Try the lookup first
Class<?> clazz = NETTY.getPacketTypeLookup().get(type);
if (clazz != null) {
return clazz;
}
// Then try looking up the class names
for (String name : type.getClassNames()) {
try {
clazz = MinecraftReflection.getMinecraftClass(name);
break;
} catch (Exception ex) {
}
}
// TODO Cache the result?
return clazz;
}
/**

View File

@ -52,7 +52,7 @@ public class ObjectEnum<T> implements Iterable<T> {
@SuppressWarnings("unchecked")
protected void registerAll(Class<T> fieldType) {
try {
// Register every int field
// Register every non-deprecated field
for (Field entry : this.getClass().getFields()) {
if (Modifier.isStatic(entry.getModifiers()) && fieldType.isAssignableFrom(entry.getType())) {
T value = (T) entry.get(null);
@ -63,7 +63,6 @@ public class ObjectEnum<T> implements Iterable<T> {
registerMember(value, entry.getName());
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {

View File

@ -152,6 +152,7 @@ public class MinecraftReflection {
// Whether or not we are using netty
private static Boolean cachedNetty;
private static Boolean cachedWatcherObject;
private MinecraftReflection() {
// No need to make this constructable.
@ -1313,16 +1314,23 @@ public class MinecraftReflection {
}
public static Class<?> getDataWatcherObjectClass() {
// TODO Implement a fallback
return getMinecraftClass("DataWatcherObject");
try {
return getMinecraftClass("DataWatcherObject");
} catch (RuntimeException ex) {
return null;
}
}
public static boolean watcherObjectExists() {
try {
return getDataWatcherObjectClass() != null;
} catch (RuntimeException e) {
return false;
if (cachedWatcherObject == null) {
try {
return cachedWatcherObject = getDataWatcherObjectClass() != null;
} catch (Throwable ex) {
return cachedWatcherObject = false;
}
}
return cachedWatcherObject;
}
public static Class<?> getDataWatcherSerializerClass() {

View File

@ -17,6 +17,7 @@
package com.comphenix.protocol.wrappers;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
@ -44,9 +45,10 @@ 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;
/**
* Represents a DataWatcher in 1.9
* Represents a DataWatcher in 1.8 thru 1.10
*
* @author dmulloy2
*/
@ -54,8 +56,8 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getDataWatcherClass();
private static MethodAccessor GETTER = null;
private static MethodAccessor SETTER = null;
private static MethodAccessor REGISTER = null;
public static MethodAccessor SETTER = null;
public static MethodAccessor REGISTER = null;
private static FieldAccessor ENTITY_DATA_FIELD = null;
private static FieldAccessor ENTITY_FIELD = null;
@ -109,8 +111,14 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
public WrappedDataWatcher(List<WrappedWatchableObject> objects) {
this();
for (WrappedWatchableObject object : objects) {
setObject(object.getWatcherObject(), object);
if (MinecraftReflection.watcherObjectExists()) {
for (WrappedWatchableObject object : objects) {
setObject(object.getWatcherObject(), object);
}
} else {
for (WrappedWatchableObject object : objects) {
setObject(object.getIndex(), object);
}
}
}
@ -217,6 +225,14 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
}
}
/**
* @deprecated Renamed to {@link #remove(int)}
*/
@Deprecated
public WrappedWatchableObject removeObject(int index) {
return remove(index);
}
/**
* Removes the item at a given index.
*
@ -238,6 +254,14 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
return getObject(index) != null;
}
/**
* 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.
@ -325,7 +349,7 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
* @return The watched object or null if it doesn't exist.
*/
public Object getObject(int index) {
return getObject(new WrappedDataWatcherObject(index, null));
return getObject(WrappedDataWatcherObject.fromIndex(index));
}
/**
@ -338,11 +362,19 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
Validate.notNull(object, "Watcher object cannot be null!");
if (GETTER == null) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType);
GETTER = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
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 {
@ -361,12 +393,23 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
*
* @param index Index of the object to set
* @param value New value
* @param update Whether or not to inform the client
*
* @see {@link #setObject(WrappedDataWatcherObject, Object)}
* @see {@link #setObject(WrappedDataWatcherObject, Object, boolean)}
*/
public void setObject(int index, Object value, boolean update) {
if (MinecraftReflection.watcherObjectExists() && !hasIndex(index)) {
throw new IllegalArgumentException("You cannot register objects without the watcher object!");
}
setObject(WrappedDataWatcherObject.fromIndex(index), value, update);
}
/**
* Shortcut for {@link #setObject(int, Object, boolean)}
*/
public void setObject(int index, Object value) {
Validate.isTrue(hasIndex(index), "You cannot register objects without the watcher object!");
setObject(index, null, value);
setObject(index, value, false);
}
/**
@ -375,11 +418,19 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
* @param index Index of the object to set
* @param Serializer Serializer from {@link Serializer#get(Class)}
* @param value New value
* @param update Whether or not to inform the client
*
* @see {@link #setObject(WrappedDataWatcherObject, Object)}
*/
public void setObject(int index, Serializer serializer, Object value, boolean update) {
setObject(new WrappedDataWatcherObject(index, serializer), value, update);
}
/**
* Shortcut for {@link #setObject(int, Serializer, Object, boolean)}
*/
public void setObject(int index, Serializer serializer, Object value) {
setObject(new WrappedDataWatcherObject(index, serializer), value);
setObject(index, serializer, value, false);
}
/**
@ -387,11 +438,19 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
*
* @param object Associated watcher object
* @param value Wrapped value
* @param update Whether or not to inform the client
*
* @see {@link #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());
setObject(object, value, false);
}
/**
@ -405,27 +464,46 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
* @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) {
public void setObject(WrappedDataWatcherObject object, Object value, boolean update) {
Validate.notNull(object, "Watcher object cannot be null!");
if (SETTER == null || REGISTER == 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();
SETTER = Accessors.getMethodAccessor(fuzzy.getMethod(contract, "set"));
REGISTER = Accessors.getMethodAccessor(fuzzy.getMethod(contract, "register"));
List<Method> methods = fuzzy.getMethodList(contract);
for (Method method : methods) {
if (method.getName().equals("set") || method.getName().equals("watch")) {
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(), WrappedWatchableObject.getUnwrapped(value));
SETTER.invoke(handle, object.getHandle(), value);
} else {
Serializer serializer = object.getSerializer();
Validate.notNull(serializer, "You must specify a serializer to register an object!");
REGISTER.invoke(handle, object.getHandle(), WrappedWatchableObject.getUnwrapped(value));
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
@ -438,8 +516,14 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
public WrappedDataWatcher deepClone() {
WrappedDataWatcher clone = new WrappedDataWatcher(getEntity());
for (WrappedWatchableObject wrapper : this) {
clone.setObject(wrapper.getWatcherObject(), wrapper);
if (MinecraftReflection.watcherObjectExists()) {
for (WrappedWatchableObject wrapper : this) {
clone.setObject(wrapper.getWatcherObject(), wrapper);
}
} else {
for (WrappedWatchableObject wrapper : this) {
clone.setObject(wrapper.getIndex(), wrapper);
}
}
return clone;
@ -485,26 +569,37 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
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();
/**
* No longer supported in 1.9 due to the removal of a consistent type <-> ID map.
* 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
* @return Null
* @param clazz Class to find ID for
* @return The ID, or null if not found
*/
@Deprecated
public static Integer getTypeID(Class<?> clazz) {
return null;
return CLASS_TO_ID.get(clazz);
}
/**
* No longer supported in 1.9 due to the removal of a consistent type <-> ID map.
* 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
* @return Null
* @param typeID ID to find Class for
* @return The Class, or null if not found
*/
@Deprecated
public static Class<?> getTypeClass(int typeID) {
return null;
return CLASS_TO_ID.inverse().get(typeID);
}
@Override
@ -550,12 +645,16 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
*
* @author dmulloy2
*/
public static class WrappedDataWatcherObject extends AbstractWrapper {
public static class WrappedDataWatcherObject {
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getDataWatcherObjectClass();
private static ConstructorAccessor constructor = null;
private static MethodAccessor getSerializer = null;
private final StructureModifier<Object> modifier;
private StructureModifier<Object> modifier;
private Object handle;
protected WrappedDataWatcherObject() {
}
/**
* Creates a new watcher object from a NMS handle.
@ -563,9 +662,7 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
* @param handle The handle
*/
public WrappedDataWatcherObject(Object handle) {
super(HANDLE_TYPE);
setHandle(handle);
this.handle = handle;
this.modifier = new StructureModifier<Object>(HANDLE_TYPE).withTarget(handle);
}
@ -579,6 +676,14 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
this(newHandle(index, serializer));
}
static WrappedDataWatcherObject fromIndex(int index) {
if (MinecraftReflection.watcherObjectExists()) {
return new WrappedDataWatcherObject(index, null);
} else {
return new DummyWatcherObject(index);
}
}
private static Object newHandle(int index, Serializer serializer) {
if (constructor == null) {
constructor = Accessors.getConstructorAccessor(HANDLE_TYPE.getConstructors()[0]);
@ -622,6 +727,18 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
}
}
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() + "]";
@ -641,6 +758,52 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
}
}
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}.

View File

@ -30,18 +30,19 @@ import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObje
import com.google.common.base.Optional;
/**
* Represents a DataWatcher Item in 1.9.
* Represents a DataWatcher Item in 1.8 thru 1.10.
* @author dmulloy2
*/
public class WrappedWatchableObject extends AbstractWrapper {
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getDataWatcherItemClass();
private static Integer VALUE_INDEX = null;
private static ConstructorAccessor constructor;
private final StructureModifier<Object> modifier;
/**
* Constructs a wrapped watchable object around an existing NMS data watcher item.
* Constructs a DataWatcher Item wrapper from an existing NMS data watcher item.
* @param handle Data watcher item
*/
public WrappedWatchableObject(Object handle) {
@ -51,7 +52,18 @@ public class WrappedWatchableObject extends AbstractWrapper {
}
/**
* Constructs a wrapped watchable object with a given watcher object and initial value.
* Constructs a DataWatcher Item wrapper from a given index and initial value.
* <p>
* Not recommended in 1.9 and up.
* @param index Index of the Item
* @param value Initial value
*/
public WrappedWatchableObject(int index, Object value) {
this(newHandle(WrappedDataWatcherObject.fromIndex(index), value));
}
/**
* Constructs a DataWatcher Item wrapper from a given watcher object and initial value.
* @param watcherObject Watcher object
* @param value Initial value
*/
@ -64,7 +76,12 @@ public class WrappedWatchableObject extends AbstractWrapper {
constructor = Accessors.getConstructorAccessor(HANDLE_TYPE.getConstructors()[0]);
}
return constructor.invoke(watcherObject.getHandle(), value);
if (MinecraftReflection.watcherObjectExists()) {
return constructor.invoke(watcherObject.getHandle(), value);
} else {
// new WatchableObject(classId, index, value)
return constructor.invoke(WrappedDataWatcher.getTypeID(value.getClass()), watcherObject.getIndex(), value);
}
}
// ---- Getter methods
@ -82,7 +99,11 @@ public class WrappedWatchableObject extends AbstractWrapper {
* @return The index
*/
public int getIndex() {
return getWatcherObject().getIndex();
if (MinecraftReflection.watcherObjectExists()) {
return getWatcherObject().getIndex();
}
return modifier.<Integer>withType(int.class).read(1);
}
/**
@ -98,7 +119,11 @@ public class WrappedWatchableObject extends AbstractWrapper {
* @return Raw value
*/
public Object getRawValue() {
return modifier.readSafely(1);
if (VALUE_INDEX == null) {
VALUE_INDEX = MinecraftReflection.watcherObjectExists() ? 1 : 2;
}
return modifier.readSafely(VALUE_INDEX);
}
/**
@ -107,7 +132,11 @@ public class WrappedWatchableObject extends AbstractWrapper {
* @param updateClient Whether or not to update the client
*/
public void setValue(Object value, boolean updateClient) {
modifier.write(1, getUnwrapped(value));
if (VALUE_INDEX == null) {
VALUE_INDEX = MinecraftReflection.watcherObjectExists() ? 1 : 2;
}
modifier.write(VALUE_INDEX, getUnwrapped(value));
if (updateClient) {
setDirtyState(true);
@ -127,7 +156,7 @@ public class WrappedWatchableObject extends AbstractWrapper {
* @return True if it must, false if not
*/
public boolean getDirtyState() {
return (boolean) modifier.read(2);
return modifier.<Boolean>withType(boolean.class).read(0);
}
/**
@ -135,7 +164,7 @@ public class WrappedWatchableObject extends AbstractWrapper {
* @param dirty New state
*/
public void setDirtyState(boolean dirty) {
modifier.write(2, dirty);
modifier.<Boolean>withType(boolean.class).write(0, dirty);
}
@Override
@ -144,11 +173,10 @@ public class WrappedWatchableObject extends AbstractWrapper {
if (obj == null) return false;
if (obj instanceof WrappedWatchableObject) {
// watcher object, value, dirty state
WrappedWatchableObject other = (WrappedWatchableObject) obj;
return getWatcherObject().equals(other.getWatcherObject())
&& getRawValue().equals(other.getRawValue())
&& getDirtyState() == other.getDirtyState();
WrappedWatchableObject that = (WrappedWatchableObject) obj;
return this.getIndex() == that.getIndex() &&
this.getRawValue().equals(that.getRawValue()) &&
this.getDirtyState() == that.getDirtyState();
}
return false;
@ -156,7 +184,7 @@ public class WrappedWatchableObject extends AbstractWrapper {
@Override
public String toString() {
return "DataWatcherItem[object=" + getWatcherObject() + ", value=" + getValue() + ", dirty=" + getDirtyState() + "]";
return "DataWatcherItem[index=" + getIndex() + ", value=" + getValue() + ", dirty=" + getDirtyState() + "]";
}
// ---- Wrapping
@ -170,7 +198,12 @@ public class WrappedWatchableObject extends AbstractWrapper {
*/
@SuppressWarnings("rawtypes")
static Object getWrapped(Object value) {
// Deal with optionals first
// Handle watcher items first
if (is(MinecraftReflection.getDataWatcherItemClass(), value)) {
return getWrapped(new WrappedWatchableObject(value).getRawValue());
}
// Then deal with optionals
if (value instanceof Optional) {
Optional<?> optional = (Optional<?>) value;
if (optional.isPresent()) {

View File

@ -542,7 +542,7 @@ public class PacketContainerTest {
public void testDeepClone() {
// Try constructing all the packets
for (PacketType type : PacketType.values()) {
if (BLACKLISTED.contains(type)) {
if (BLACKLISTED.contains(type) || type.isDeprecated()) {
continue;
}
@ -588,9 +588,7 @@ public class PacketContainerTest {
}
} catch (IllegalArgumentException e) {
if (!registered) {
// Let the test pass
System.err.println("The packet ID " + type + " is not registered.");
assertEquals(e.getMessage(), "The packet ID " + type + " is not registered.");
e.printStackTrace();
} else {
// Something is very wrong
throw e;