diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 78a0604b..91bc1cb2 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -30,6 +30,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; @@ -76,6 +77,7 @@ import com.comphenix.protocol.wrappers.WrappedChatComponent; import com.comphenix.protocol.wrappers.WrappedDataWatcher; import com.comphenix.protocol.wrappers.WrappedGameProfile; import com.comphenix.protocol.wrappers.WrappedServerPing; +import com.comphenix.protocol.wrappers.WrappedStatistic; import com.comphenix.protocol.wrappers.WrappedWatchableObject; import com.comphenix.protocol.wrappers.EnumWrappers.ChatVisibility; import com.comphenix.protocol.wrappers.EnumWrappers.ClientCommand; @@ -357,6 +359,21 @@ public class PacketContainer implements Serializable { BukkitConverters.getIgnoreNull(new ItemStackArrayConverter())); } + /** + * Retrieve a read/write structure for maps of statistics. + *

+ * Note that you must write back the changed map to persist it. + * @return A modifier for maps of statistics. + */ + public StructureModifier> getStatisticMaps() { + return structureModifier.withType(Map.class, + BukkitConverters.getMapConverter( + MinecraftReflection.getStatisticClass(), + BukkitConverters.getWrappedStatisticConverter() + ) + ); + } + /** * Retrieves a read/write structure for the world type enum. *

diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index 629ca2b5..dfd0b7de 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -832,6 +832,24 @@ public class MinecraftReflection { } } + /** + * Retrieve the NMS statistics class. + * @return The statistics class. + */ + public static Class getStatisticClass() { + // TODO: Implement fallback + return getMinecraftClass("Statistic"); + } + + /** + * Retrieve the NMS statistic list class. + * @return The statistic list class. + */ + public static Class getStatisticListClass() { + // TODO: Implement fallback + return getMinecraftClass("StatisticList"); + } + /** * Fallback method that can determine the MinecraftServer and the ServerConfigurationManager. */ diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/AbstractWrapper.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/AbstractWrapper.java index 660a1b06..0da7d03b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/AbstractWrapper.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/AbstractWrapper.java @@ -28,7 +28,7 @@ public abstract class AbstractWrapper { if (handle == null) throw new IllegalArgumentException("handle cannot be NULL."); if (!handleType.isAssignableFrom(handle.getClass())) - throw new IllegalArgumentException("handle (" + handle + ") is not a " + handleType); + throw new IllegalArgumentException("handle (" + handle + ") is not a " + handleType + ", but " + handle.getClass()); this.handle = handle; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java index 61b03ae2..3ffdd03c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java @@ -57,6 +57,7 @@ import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; /** * Contains several useful equivalent converters for normal Bukkit types. @@ -191,6 +192,60 @@ public class BukkitConverters { } } + /** + * Retrieve an equivalent converter for a map of generic keys and primitive values. + * @param genericItemType - the generic item type. + * @param itemConverter - an equivalent converter for the generic type. + * @return An equivalent converter. + */ + public static EquivalentConverter> getMapConverter( + final Class genericKeyType, final EquivalentConverter keyConverter) { + // Convert to and from the wrapper + return new IgnoreNullConverter>() { + @SuppressWarnings("unchecked") + @Override + protected Map getSpecificValue(Object generic) { + if (generic instanceof Map) { + Map result = Maps.newHashMap(); + + // Copy everything to a new list + for (Entry entry : ((Map) generic).entrySet()) { + result.put( + keyConverter.getSpecific(entry.getKey()), + (U) entry.getValue() + ); + } + return result; + } + + // Not valid + return null; + } + + @SuppressWarnings("unchecked") + @Override + protected Object getGenericValue(Class genericType, Map specific) { + Map newContainer = (Map) DefaultInstances.DEFAULT.getDefault(genericType); + + // Convert each object + for (Entry entry : specific.entrySet()) { + newContainer.put( + keyConverter.getGeneric(genericKeyType, entry.getKey()), + entry.getValue() + ); + } + return newContainer; + } + + @SuppressWarnings("unchecked") + @Override + public Class> getSpecificType() { + Class dummy = Map.class; + return (Class>) dummy; + } + }; + } + /** * Retrieve an equivalent converter for a list of generic items. * @param genericItemType - the generic item type. @@ -594,6 +649,29 @@ public class BukkitConverters { }; } + /** + * Retrieve the converter for a statistic. + * @return Statistic converter. + */ + public static EquivalentConverter getWrappedStatisticConverter() { + return new IgnoreNullConverter() { + @Override + protected Object getGenericValue(Class genericType, WrappedStatistic specific) { + return specific.getHandle(); + } + + @Override + protected WrappedStatistic getSpecificValue(Object generic) { + return WrappedStatistic.fromHandle(generic); + } + + @Override + public Class getSpecificType() { + return WrappedStatistic.class; + } + }; + } + /** * Retrieve a converter for block instances. * @return A converter for block instances. @@ -756,6 +834,7 @@ public class BukkitConverters { builder.put(WrappedGameProfile.class, (EquivalentConverter) getWrappedGameProfileConverter()); builder.put(WrappedChatComponent.class, (EquivalentConverter) getWrappedChatComponentConverter()); builder.put(WrappedServerPing.class, (EquivalentConverter) getWrappedServerPingConverter()); + builder.put(WrappedStatistic.class, (EquivalentConverter) getWrappedStatisticConverter()); for (Entry, EquivalentConverter> entry : EnumWrappers.getFromWrapperMap().entrySet()) { builder.put((Class) entry.getKey(), (EquivalentConverter) entry.getValue()); @@ -798,6 +877,7 @@ public class BukkitConverters { builder.put(MinecraftReflection.getGameProfileClass(), (EquivalentConverter) getWrappedGameProfileConverter()); builder.put(MinecraftReflection.getIChatBaseComponentClass(), (EquivalentConverter) getWrappedChatComponentConverter()); builder.put(MinecraftReflection.getServerPingClass(), (EquivalentConverter) getWrappedServerPingConverter()); + builder.put(MinecraftReflection.getStatisticClass(), (EquivalentConverter) getWrappedStatisticConverter()); for (Entry, EquivalentConverter> entry : EnumWrappers.getFromNativeMap().entrySet()) { builder.put((Class) entry.getKey(), (EquivalentConverter) entry.getValue());