diff --git a/src/main/java/com/comphenix/protocol/events/AbstractStructure.java b/src/main/java/com/comphenix/protocol/events/AbstractStructure.java new file mode 100644 index 00000000..b5adeee5 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/events/AbstractStructure.java @@ -0,0 +1,1153 @@ +package com.comphenix.protocol.events; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.MinecraftVersion; +import com.comphenix.protocol.utility.StreamSerializer; +import com.comphenix.protocol.wrappers.*; +import com.comphenix.protocol.wrappers.nbt.NbtBase; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; +import com.comphenix.protocol.wrappers.nbt.NbtFactory; +import com.google.common.base.Preconditions; +import org.apache.commons.lang.Validate; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.WorldType; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import javax.annotation.Nonnull; +import java.lang.reflect.Array; +import java.util.*; + +public abstract class AbstractStructure { + protected transient Object handle; + protected transient StructureModifier structureModifier; + + protected AbstractStructure() {} + + protected AbstractStructure(Object handle, StructureModifier modifier) { + Validate.notNull(handle, "handle cannot be null"); + Validate.notNull(modifier, "modifier cannot be null"); + + this.handle = handle; + this.structureModifier = modifier; + } + + public Object getHandle() { + return handle; + } + + public StructureModifier getModifier() { + return structureModifier; + } + + /** + * Retrieves a read/write structure for every field with the given type. + * @param Type + * @param primitiveType - the type to find. + * @return A modifier for this specific type. + */ + public StructureModifier getSpecificModifier(Class primitiveType) { + return structureModifier.withType(primitiveType); + } + + /** + * Retrieves a read/write structure for every byte field. + * @return A modifier for every byte field. + */ + public StructureModifier getBytes() { + return structureModifier.withType(byte.class); + } + + /** + * Retrieves a read/write structure for every boolean field. + * @return A modifier for every boolean field. + */ + public StructureModifier getBooleans() { + return structureModifier.withType(boolean.class); + } + + /** + * Retrieves a read/write structure for every short field. + * @return A modifier for every short field. + */ + public StructureModifier getShorts() { + return structureModifier.withType(short.class); + } + + /** + * Retrieves a read/write structure for every integer field. + * @return A modifier for every integer field. + */ + public StructureModifier getIntegers() { + return structureModifier.withType(int.class); + } + /** + * Retrieves a read/write structure for every long field. + * @return A modifier for every long field. + */ + public StructureModifier getLongs() { + return structureModifier.withType(long.class); + } + + /** + * Retrieves a read/write structure for every float field. + * @return A modifier for every float field. + */ + public StructureModifier getFloat() { + return structureModifier.withType(float.class); + } + + /** + * Retrieves a read/write structure for every double field. + * @return A modifier for every double field. + */ + public StructureModifier getDoubles() { + return structureModifier.withType(double.class); + } + + /** + * Retrieves a read/write structure for every String field. + * @return A modifier for every String field. + */ + public StructureModifier getStrings() { + return structureModifier.withType(String.class); + } + + /** + * Retrieves a read/write structure for every UUID field. + * @return A modifier for every UUID field. + */ + public StructureModifier getUUIDs() { + return structureModifier.withType(UUID.class); + } + + /** + * Retrieves a read/write structure for every String array field. + * @return A modifier for every String array field. + */ + public StructureModifier getStringArrays() { + return structureModifier.withType(String[].class); + } + + /** + * Retrieves a read/write structure for every byte array field. + * @return A modifier for every byte array field. + */ + public StructureModifier getByteArrays() { + return structureModifier.withType(byte[].class); + } + + /** + * Retrieve a serializer for reading and writing ItemStacks stored in a byte array. + * @return A instance of the serializer. + */ + public StreamSerializer getByteArraySerializer() { + return new StreamSerializer(); + } + + /** + * Retrieves a read/write structure for every int array field. + * @return A modifier for every int array field. + */ + public StructureModifier getIntegerArrays() { + return structureModifier.withType(int[].class); + } + + /** + * Retrieves a read/write structure for every short array field. + * @return A modifier for every short array field. + */ + public StructureModifier getShortArrays() { + return structureModifier.withType(short[].class); + } + + /** + * Retrieves a read/write structure for ItemStack. + *

+ * This modifier will automatically marshal between the Bukkit ItemStack and the + * internal Minecraft ItemStack. + * @return A modifier for ItemStack fields. + */ + public StructureModifier getItemModifier() { + // Convert to and from the Bukkit wrapper + return structureModifier.withType( + MinecraftReflection.getItemStackClass(), + BukkitConverters.getItemStackConverter()); + } + + /** + * Retrieves a read/write structure for arrays of ItemStacks. + *

+ * This modifier will automatically marshal between the Bukkit ItemStack and the + * internal Minecraft ItemStack. + * @return A modifier for ItemStack array fields. + */ + public StructureModifier getItemArrayModifier() { + // Convert to and from the Bukkit wrapper + return structureModifier.withType( + MinecraftReflection.getItemStackArrayClass(), + Converters.ignoreNull(new ItemStackArrayConverter())); + } + + /** + * Retrieves a read/write structure for lists of ItemStacks. + *

+ * This modifier will automatically marshal between the Bukkit ItemStack and the + * internal Minecraft ItemStack. + * @return A modifier for ItemStack list fields. + */ + public StructureModifier> getItemListModifier() { + // Convert to and from the Bukkit wrapper + return structureModifier.withType( + List.class, + BukkitConverters.getListConverter(BukkitConverters.getItemStackConverter()) + ); + } + + /** + * 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 getMaps( + BukkitConverters.getWrappedStatisticConverter(), + Converters.passthrough(Integer.class)); + } + + /** + * Retrieves a read/write structure for the world type enum. + *

+ * This modifier will automatically marshal between the Bukkit world type and the + * internal Minecraft world type. + * @return A modifier for world type fields. + */ + public StructureModifier getWorldTypeModifier() { + // Convert to and from the Bukkit wrapper + return structureModifier.withType( + MinecraftReflection.getWorldTypeClass(), + BukkitConverters.getWorldTypeConverter()); + } + + /** + * Retrieves a read/write structure for data watchers. + * @return A modifier for data watchers. + */ + public StructureModifier getDataWatcherModifier() { + // Convert to and from the Bukkit wrapper + return structureModifier.withType( + MinecraftReflection.getDataWatcherClass(), + BukkitConverters.getDataWatcherConverter()); + } + + /** + * Retrieves a read/write structure for entity objects. + *

+ * Note that entities are transmitted by integer ID, and the type may not be enough + * to distinguish between entities and other values. Thus, this structure modifier + * MAY return null or invalid entities for certain fields. Using the correct index + * is essential. + * + * @param world - the world each entity is currently occupying. + * @return A modifier entity types. + */ + public StructureModifier getEntityModifier(@Nonnull World world) { + Preconditions.checkNotNull(world, "world cannot be NULL."); + // Convert to and from the Bukkit wrapper + return structureModifier.withType( + int.class, BukkitConverters.getEntityConverter(world)); + } + + /** + * Retrieves a read/write structure for entity objects. + *

+ * Note that entities are transmitted by integer ID, and the type may not be enough + * to distinguish between entities and other values. Thus, this structure modifier + * MAY return null or invalid entities for certain fields. Using the correct index + * is essential. + * + * @param event - the original packet event. + * @return A modifier entity types. + */ + public StructureModifier getEntityModifier(@Nonnull PacketEvent event) { + Preconditions.checkNotNull(event, "event cannot be NULL."); + return getEntityModifier(event.getPlayer().getWorld()); + } + + /** + * Retrieves a read/write structure for entity types + * @return A modifier for an EntityType. + */ + public StructureModifier getEntityTypeModifier() { + return structureModifier.withType( + MinecraftReflection.getEntityTypes(), + BukkitConverters.getEntityTypeConverter()); + } + + /** + * Retrieves a read/write structure for chunk positions. + * @return A modifier for a ChunkPosition. + */ + public StructureModifier getPositionModifier() { + // Convert to and from the Bukkit wrapper + return structureModifier.withType( + MinecraftReflection.getChunkPositionClass(), + ChunkPosition.getConverter()); + } + + /** + * Retrieves a read/write structure for block positions. + * @return A modifier for a BlockPosition. + */ + public StructureModifier getBlockPositionModifier() { + // Convert to and from the Bukkit wrapper + return structureModifier.withType( + MinecraftReflection.getBlockPositionClass(), + BlockPosition.getConverter()); + } + + /** + * Retrieves a read/write structure for wrapped ChunkCoordIntPairs. + * @return A modifier for ChunkCoordIntPair. + */ + public StructureModifier getChunkCoordIntPairs() { + // Allow access to the NBT class in packet 130 + return structureModifier.withType( + MinecraftReflection.getChunkCoordIntPair(), + ChunkCoordIntPair.getConverter()); + } + + /** + * Retrieves a read/write structure for NBT classes. + * @return A modifier for NBT classes. + */ + public StructureModifier> getNbtModifier() { + // Allow access to the NBT class in packet 130 + return structureModifier.withType( + MinecraftReflection.getNBTBaseClass(), + BukkitConverters.getNbtConverter()); + } + + /** + * Retrieves a read/write structure for lists of NBT classes. + * @return A modifier for lists of NBT classes. + */ + public StructureModifier>> getListNbtModifier() { + // Convert to and from the ProtocolLib wrapper + return structureModifier.withType( + Collection.class, + BukkitConverters.getListConverter(BukkitConverters.getNbtConverter()) + ); + } + + /** + * Retrieves a read/write structure for Vectors. + * @return A modifier for Vectors. + */ + public StructureModifier getVectors() { + // Automatically marshal between Vec3d and the Bukkit wrapper + return structureModifier.withType( + MinecraftReflection.getVec3DClass(), + BukkitConverters.getVectorConverter()); + } + + /** + * Retrieves a read/write structure for collections of attribute snapshots. + *

+ * This modifier will automatically marshal between the visible ProtocolLib WrappedAttribute and the + * internal Minecraft AttributeSnapshot. + * @return A modifier for AttributeSnapshot collection fields. + */ + public StructureModifier> getAttributeCollectionModifier() { + // Convert to and from the ProtocolLib wrapper + return structureModifier.withType( + Collection.class, + BukkitConverters.getListConverter(BukkitConverters.getWrappedAttributeConverter()) + ); + } + + /** + * Retrieves a read/write structure for collections of chunk positions. + *

+ * This modifier will automatically marshal between the visible ProtocolLib ChunkPosition and the + * internal Minecraft ChunkPosition. + * + * @return A modifier for ChunkPosition list fields. + */ + public StructureModifier> getPositionCollectionModifier() { + // Convert to and from the ProtocolLib wrapper + return structureModifier.withType( + Collection.class, + BukkitConverters.getListConverter(ChunkPosition.getConverter())); + } + + /** + * Retrieves a read/write structure for collections of chunk positions. + *

+ * This modifier will automatically marshal between the visible ProtocolLib BlockPosition and the + * internal Minecraft BlockPosition. + * + * @return A modifier for ChunkPosition list fields. + */ + public StructureModifier> getBlockPositionCollectionModifier() { + // Convert to and from the ProtocolLib wrapper + return structureModifier.withType( + Collection.class, + BukkitConverters.getListConverter(BlockPosition.getConverter())); + } + + /** + * Retrieves a read/write structure for collections of watchable objects. + *

+ * This modifier will automatically marshal between the visible WrappedWatchableObject and the + * internal Minecraft WatchableObject. + * @return A modifier for watchable object list fields. + */ + public StructureModifier> getWatchableCollectionModifier() { + // Convert to and from the ProtocolLib wrapper + return structureModifier.withType( + Collection.class, + BukkitConverters.getListConverter(BukkitConverters.getWatchableObjectConverter())); + } + + /** + * Retrieves a read/write structure for block fields. + *

+ * This modifier will automatically marshal between Material and the + * internal Minecraft Block. + * @return A modifier for GameProfile fields. + */ + public StructureModifier getBlocks() { + // Convert to and from the Bukkit wrapper + return structureModifier.withType( + MinecraftReflection.getBlockClass(), BukkitConverters.getBlockConverter()); + } + + /** + * Retrieves a read/write structure for game profiles in Minecraft 1.7.2. + *

+ * This modifier will automatically marshal between WrappedGameProfile and the + * internal Minecraft GameProfile. + * @return A modifier for GameProfile fields. + */ + public StructureModifier getGameProfiles() { + // Convert to and from the Bukkit wrapper + return structureModifier.withType( + MinecraftReflection.getGameProfileClass(), BukkitConverters.getWrappedGameProfileConverter()); + } + + /** + * Retrieves a read/write structure for BlockData in Minecraft 1.8. + *

+ * This modifier will automatically marshal between WrappedBlockData and the + * internal Minecraft IBlockData. + * @return A modifier for BlockData fields. + */ + public StructureModifier getBlockData() { + // Convert to and from our wrapper + return structureModifier.withType( + MinecraftReflection.getIBlockDataClass(), + BukkitConverters.getWrappedBlockDataConverter() + ); + } + + /** + * Retrieves a read/write structure for IBlockData arrays in Minecraft 1.16.2+ + * @return A modifier for IBlockData array fields + */ + public StructureModifier getBlockDataArrays() { + // TODO we might want to make this a lazy converter and only convert indexes as needed + return structureModifier.withType( + MinecraftReflection.getArrayClass(MinecraftReflection.getIBlockDataClass()), + Converters.array(MinecraftReflection.getIBlockDataClass(), BukkitConverters.getWrappedBlockDataConverter()) + ); + } + + /** + * Retrieves a read/write structure for MultiBlockChangeInfo arrays in Minecraft 1.8. + *

+ * This modifier will automatically marshal between MultiBlockChangeInfo and the + * internal Minecraft MultiBlockChangeInfo. + * @return A modifier for BlockData fields. + */ + public StructureModifier getMultiBlockChangeInfoArrays() { + ChunkCoordIntPair chunk = getChunkCoordIntPairs().read(0); + + // Convert to and from our wrapper + return structureModifier.withType( + MinecraftReflection.getMultiBlockChangeInfoArrayClass(), + Converters.array(MinecraftReflection.getMultiBlockChangeInfoClass(), MultiBlockChangeInfo.getConverter(chunk)) + ); + } + + /** + * Retrieves a read/write structure for chat components in Minecraft 1.7.2. + *

+ * This modifier will automatically marshal between WrappedChatComponent and the + * internal Minecraft IChatBaseComponent. + * @return A modifier for ChatComponent fields. + */ + public StructureModifier getChatComponents() { + // Convert to and from the Bukkit wrapper + return structureModifier.withType( + MinecraftReflection.getIChatBaseComponentClass(), BukkitConverters.getWrappedChatComponentConverter()); + } + + /** + * Retrieves a read/write structure for arrays of chat components. + *

+ * This modifier will automatically marshal between WrappedChatComponent and the + * internal Minecraft IChatBaseComponent (1.9.2 and below) or the internal + * NBTCompound (1.9.4 and up). + *

+ * Note that in 1.9.4 and up this modifier only works properly with sign + * tile entities. + * @return A modifier for ChatComponent array fields. + */ + public StructureModifier getChatComponentArrays() { + // Convert to and from the Bukkit wrapper + return structureModifier.withType( + ComponentArrayConverter.getGenericType(), + Converters.ignoreNull(new ComponentArrayConverter())); + } + + /** + * Retrieve a read/write structure for the ServerPing fields in the following packet:
+ *

    + *
  • {@link PacketType.Status.Server#SERVER_INFO} + *
+ * @return A modifier for ServerPing fields. + */ + public StructureModifier getServerPings() { + // Convert to and from the wrapper + return structureModifier.withType( + MinecraftReflection.getServerPingClass(), + BukkitConverters.getWrappedServerPingConverter()); + } + + /** + * Retrieve a read/write structure for the PlayerInfoData list fields in the following packet:
+ *
    + *
  • {@link PacketType.Play.Server#PLAYER_INFO} + *
+ * @return A modifier for PlayerInfoData list fields. + */ + public StructureModifier> getPlayerInfoDataLists() { + // Convert to and from the ProtocolLib wrapper + return structureModifier.withType( + Collection.class, + BukkitConverters.getListConverter(PlayerInfoData.getConverter())); + } + + /** + * Retrieve a read/write structure for the Protocol enum in 1.7.2. + * @return A modifier for Protocol enum fields. + */ + public StructureModifier getProtocols() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getProtocolClass(), + EnumWrappers.getProtocolConverter()); + } + + /** + * Retrieve a read/write structure for the ClientCommand enum in 1.7.2. + * @return A modifier for ClientCommand enum fields. + */ + public StructureModifier getClientCommands() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getClientCommandClass(), + EnumWrappers.getClientCommandConverter()); + } + + /** + * Retrieve a read/write structure for the ChatVisibility enum in 1.7.2. + * @return A modifier for ChatVisibility enum fields. + */ + public StructureModifier getChatVisibilities() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getChatVisibilityClass(), + EnumWrappers.getChatVisibilityConverter()); + } + + /** + * Retrieve a read/write structure for the Difficulty enum in 1.7.2. + * @return A modifier for Difficulty enum fields. + */ + public StructureModifier getDifficulties() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getDifficultyClass(), + EnumWrappers.getDifficultyConverter()); + } + + /** + * Retrieve a read/write structure for the EntityUse enum in 1.7.2. + * @return A modifier for EntityUse enum fields. + */ + public StructureModifier getEntityUseActions() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getEntityUseActionClass(), + EnumWrappers.getEntityUseActionConverter()); + } + + /** + * Retrieves a read/write structure for the EntityUseAction class in the UseEntity packet sent by the client for + * 1.17 and above. + * @return A modifier for EntityUseAction class fields. + */ + public StructureModifier getEnumEntityUseActions() { + return structureModifier.withType( + MinecraftReflection.getEnumEntityUseActionClass(), + WrappedEnumEntityUseAction.CONVERTER); + } + + /** + * Retrieve a read/write structure for the NativeGameMode enum in 1.7.2. + * @return A modifier for NativeGameMode enum fields. + */ + public StructureModifier getGameModes() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getGameModeClass(), + EnumWrappers.getGameModeConverter()); + } + + /** + * Retrieve a read/write structure for the ResourcePackStatus enum in 1.8. + * @return A modifier for ResourcePackStatus enum fields. + */ + public StructureModifier getResourcePackStatus() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getResourcePackStatusClass(), + EnumWrappers.getResourcePackStatusConverter()); + } + + /** + * Retrieve a read/write structure for the PlayerInfo enum in 1.8. + * @return A modifier for PlayerInfoAction enum fields. + */ + public StructureModifier getPlayerInfoAction() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getPlayerInfoActionClass(), + EnumWrappers.getPlayerInfoActionConverter()); + } + + /** + * Retrieve a read/write structure for the TitleAction enum in 1.8. + * @return A modifier for TitleAction enum fields. + */ + public StructureModifier getTitleActions() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getTitleActionClass(), + EnumWrappers.getTitleActionConverter()); + } + + /** + * Retrieve a read/write structure for the WorldBorderAction enum in 1.8. + * @return A modifier for WorldBorderAction enum fields. + */ + public StructureModifier getWorldBorderActions() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getWorldBorderActionClass(), + EnumWrappers.getWorldBorderActionConverter()); + } + + /** + * Retrieve a read/write structure for the CombatEventType enum in 1.8. + * @return A modifier for CombatEventType enum fields. + */ + public StructureModifier getCombatEvents() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getCombatEventTypeClass(), + EnumWrappers.getCombatEventTypeConverter()); + } + + /** + * Retrieve a read/write structure for the PlayerDigType enum in 1.8. + * @return A modifier for PlayerDigType enum fields. + */ + public StructureModifier getPlayerDigTypes() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getPlayerDigTypeClass(), + EnumWrappers.getPlayerDiggingActionConverter()); + } + + /** + * Retrieve a read/write structure for the PlayerAction enum in 1.8. + * @return A modifier for PlayerAction enum fields. + */ + public StructureModifier getPlayerActions() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getPlayerActionClass(), + EnumWrappers.getEntityActionConverter()); + } + + /** + * Retrieve a read/write structure for the ScoreboardAction enum in 1.8. + * @return A modifier for ScoreboardAction enum fields. + */ + public StructureModifier getScoreboardActions() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getScoreboardActionClass(), + EnumWrappers.getUpdateScoreActionConverter()); + } + + /** + * Retrieve a read/write structure for the Particle enum in 1.8-1.12. + * NOTE: This will produce undesirable results in 1.13 + * @return A modifier for Particle enum fields. + */ + public StructureModifier getParticles() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getParticleClass(), + EnumWrappers.getParticleConverter()); + } + + /** + * Retrieve a read/write structure for ParticleParams in 1.13 + * @return A modifier for ParticleParam fields. + */ + public StructureModifier getNewParticles() { + return structureModifier.withType( + MinecraftReflection.getParticleParam(), + BukkitConverters.getParticleConverter() + ); + } + + /** + * Retrieve a read/write structure for the MobEffectList class in 1.9. + * @return A modifier for MobEffectList fields. + */ + public StructureModifier getEffectTypes() { + // Convert to and from Bukkit + return structureModifier.withType( + MinecraftReflection.getMobEffectListClass(), + BukkitConverters.getEffectTypeConverter()); + } + + /** + * Retrieve a read/write structure for the SoundCategory enum in 1.9. + * @return A modifier for SoundCategory enum fields. + */ + public StructureModifier getSoundCategories() { + // Convert to and from the enums + return structureModifier.withType( + EnumWrappers.getSoundCategoryClass(), + EnumWrappers.getSoundCategoryConverter()); + } + + /** + * Retrieve a read/write structure for the SoundEffect enum in 1.9. + * @return A modifier for SoundEffect enum fields. + */ + public StructureModifier getSoundEffects() { + // Convert to and from Bukkit + return structureModifier.withType( + MinecraftReflection.getSoundEffectClass(), + BukkitConverters.getSoundConverter()); + } + + /** + * Retrieve a read/write structure for the ItemSlot enum in 1.9. + * @return A modifier for ItemSlot enum fields. + */ + public StructureModifier getItemSlots() { + return structureModifier.withType( + EnumWrappers.getItemSlotClass(), + EnumWrappers.getItemSlotConverter()); + } + + /** + * Retrieve a read/write structure for the Hand enum in 1.9. + * @return A modifier for Hand enum fields. + */ + public StructureModifier getHands() { + return structureModifier.withType( + EnumWrappers.getHandClass(), + EnumWrappers.getHandConverter()); + } + + /** + * Retrieve a read/write structure for the Direction enum in 1.10. + * @return A modifier for Direction enum fields. + */ + public StructureModifier getDirections() { + return structureModifier.withType( + EnumWrappers.getDirectionClass(), + EnumWrappers.getDirectionConverter()); + } + + /** + * Retrieve a read/write structure for the ChatType enum in 1.12. + * @return A modifier for ChatType enum fields. + */ + public StructureModifier getChatTypes() { + return structureModifier.withType( + EnumWrappers.getChatTypeClass(), + EnumWrappers.getChatTypeConverter()); + } + + /** + * Retrieve a read/write structure for the MinecraftKey class. + * @return A modifier for MinecraftKey fields. + */ + public StructureModifier getMinecraftKeys() { + return structureModifier.withType( + MinecraftReflection.getMinecraftKeyClass(), + MinecraftKey.getConverter()); + } + + /** + * Retrieve a read/write structure for dimension IDs in 1.13.1+ + * @return A modifier for dimension IDs + */ + @Deprecated + public StructureModifier getDimensions() { + if (MinecraftVersion.NETHER_UPDATE.atOrAbove() && !MinecraftVersion.NETHER_UPDATE_2.atOrAbove()) { + return structureModifier.withParamType( + MinecraftReflection.getResourceKey(), + BukkitConverters.getDimensionIDConverter(), + MinecraftReflection.getDimensionManager() + ); + } else { + return structureModifier.withType( + MinecraftReflection.getDimensionManager(), + BukkitConverters.getDimensionIDConverter() + ); + } + } + + public StructureModifier getDimensionTypes() { + return structureModifier.withType( + MinecraftReflection.getDimensionManager(), + BukkitConverters.getDimensionConverter() + ); + } + + /** + * Retrieve a read/write structure for the MerchantRecipeList class. + * @return A modifier for MerchantRecipeList fields. + */ + public StructureModifier> getMerchantRecipeLists() { + return structureModifier.withType( + MinecraftReflection.getMerchantRecipeList(), + BukkitConverters.getMerchantRecipeListConverter() + ); + } + + /** + * Retrieve a read/write structure for ItemSlot/ItemStack pair lists in 1.16+ + * @return The Structure Modifier + */ + public StructureModifier>> getSlotStackPairLists() { + return getLists(BukkitConverters.getPairConverter( + EnumWrappers.getItemSlotConverter(), + BukkitConverters.getItemStackConverter() + )); + } + + /** + * Retrieve a read/write structure for MovingObjectPositionBlock in 1.16+ + * @return The Structure Modifier + */ + public StructureModifier getMovingBlockPositions() { + return structureModifier.withType( + MovingObjectPositionBlock.getNmsClass(), + MovingObjectPositionBlock.getConverter() + ); + } + + /** + * Retrieve a read/write structure for World ResourceKeys in 1.16+ + * @return The Structure Modifier + */ + public StructureModifier getWorldKeys() { + return structureModifier.withParamType( + MinecraftReflection.getResourceKey(), + BukkitConverters.getWorldKeyConverter(), + MinecraftReflection.getNmsWorldClass() + ); + } + + /** + * Retrieve a read/write structure for SectionPositions in 1.16.2+ + * @return The Structure Modifier + */ + public StructureModifier getSectionPositions() { + return structureModifier.withType( + MinecraftReflection.getSectionPosition(), + BukkitConverters.getSectionPositionConverter() + ); + } + + /** + * Retrieve a read/write structure for Game State IDs in 1.16+ + * @return The Structure Modifier + */ + public StructureModifier getGameStateIDs() { + return structureModifier.withType( + MinecraftReflection.getGameStateClass(), + BukkitConverters.getGameStateConverter() + ); + } + + /** + * Retrieve a read/write structure for the Map class. + * @param keyConverter Converter for map keys + * @param valConverter Converter for map values + * @param Key param + * @param Value param + * @return A modifier for Map fields. + * + * @see BukkitConverters + * @see EquivalentConverter + */ + public StructureModifier> getMaps(EquivalentConverter keyConverter, + EquivalentConverter valConverter) { + return structureModifier.withType( + Map.class, + BukkitConverters.getMapConverter(keyConverter, valConverter)); + } + + /** + * Retrieve a read/write structure for the Set class. + * @param converter Converter for elements + * @param Element param + * @return A modifier for Set fields + * + * @see BukkitConverters + * @see EquivalentConverter + */ + public StructureModifier> getSets(EquivalentConverter converter) { + return structureModifier.withType( + Set.class, + BukkitConverters.getSetConverter(converter)); + } + + /** + * Retrieve a read/write structure for the List class. + * @param converter Converter for elements + * @param Element param + * @return A modifier for List fields + */ + public StructureModifier> getLists(EquivalentConverter converter) { + return structureModifier.withType( + List.class, + BukkitConverters.getListConverter(converter)); + } + + /** + * Retrieve a read/write structure for an enum. This allows for the use of + * user-created enums that may not exist in ProtocolLib. The specific (user + * created) enum constants must match up perfectly with their generic (NMS) + * counterparts. + * + * @param enumClass The specific Enum class + * @param nmsClass The generic Enum class + * @return The modifier + */ + public > StructureModifier getEnumModifier(Class enumClass, Class nmsClass) { + return structureModifier.withType( + nmsClass, + new EnumWrappers.EnumConverter<>(nmsClass, enumClass)); + } + + /** + * Retrieve a read/write structure for an enum. This method is for convenience, + * see {@link #getEnumModifier(Class, Class)} for more information. + * + * @param enumClass The specific Enum class + * @param index Index of the generic Enum + * @return The modifier + * @see #getEnumModifier(Class, Class) + */ + public > StructureModifier getEnumModifier(Class enumClass, int index) { + return getEnumModifier( + enumClass, + structureModifier.getField(index).getType()); + } + + /** + * Represents an equivalent converter for ItemStack arrays. + * @author Kristian + */ + private static class ItemStackArrayConverter implements EquivalentConverter { + final EquivalentConverter stackConverter = BukkitConverters.getItemStackConverter(); + + @Override + public Object getGeneric(ItemStack[] specific) { + Class nmsStack = MinecraftReflection.getItemStackClass(); + Object[] result = (Object[]) Array.newInstance(nmsStack, specific.length); + + // Unwrap every item + for (int i = 0; i < result.length; i++) { + result[i] = stackConverter.getGeneric(specific[i]); + } + return result; + } + + @Override + public ItemStack[] getSpecific(Object generic) { + Object[] input = (Object[]) generic; + ItemStack[] result = new ItemStack[input.length]; + + // Add the wrapper + for (int i = 0; i < result.length; i++) { + result[i] = stackConverter.getSpecific(input[i]); + } + return result; + } + + @Override + public Class getSpecificType() { + return ItemStack[].class; + } + } + + /** + * Represents an equivalent converter for ChatComponent arrays. + * @author dmulloy2 + */ + private static class LegacyComponentConverter implements EquivalentConverter { + final EquivalentConverter componentConverter = BukkitConverters.getWrappedChatComponentConverter(); + + @Override + public Object getGeneric(WrappedChatComponent[] specific) { + Class nmsComponent = MinecraftReflection.getIChatBaseComponentClass(); + Object[] result = (Object[]) Array.newInstance(nmsComponent, specific.length); + + // Unwrap every item + for (int i = 0; i < result.length; i++) { + result[i] = componentConverter.getGeneric(specific[i]); + } + return result; + } + + @Override + public WrappedChatComponent[] getSpecific(Object generic) { + Object[] input = (Object[]) generic; + WrappedChatComponent[] result = new WrappedChatComponent[input.length]; + + // Add the wrapper + for (int i = 0; i < result.length; i++) { + result[i] = componentConverter.getSpecific(input[i]); + } + return result; + } + + @Override + public Class getSpecificType() { + return WrappedChatComponent[].class; + } + } + + /** + * Converts from NBT to WrappedChatComponent arrays + * @author dmulloy2 + */ + private static class NBTComponentConverter implements EquivalentConverter { + private EquivalentConverter> nbtConverter = BukkitConverters.getNbtConverter(); + private final int lines = 4; + + @Override + public WrappedChatComponent[] getSpecific(Object generic) { + NbtBase nbtBase = nbtConverter.getSpecific(generic); + NbtCompound compound = (NbtCompound) nbtBase; + + WrappedChatComponent[] components = new WrappedChatComponent[lines]; + for (int i = 0; i < lines; i++) { + if (compound.containsKey("Text" + (i + 1))) { + components[i] = WrappedChatComponent.fromJson(compound.getString("Text" + (i + 1))); + } else { + components[i] = WrappedChatComponent.fromText(""); + } + } + + return components; + } + + @Override + public Object getGeneric(WrappedChatComponent[] specific) { + NbtCompound compound = NbtFactory.ofCompound(""); + + for (int i = 0; i < lines; i++) { + WrappedChatComponent component; + if (i < specific.length && specific[i] != null) { + component = specific[i]; + } else { + component = WrappedChatComponent.fromText(""); + } + + compound.put("Text" + (i + 1), component.getJson()); + } + + return nbtConverter.getGeneric(compound); + } + + @Override + public Class getSpecificType() { + return WrappedChatComponent[].class; + } + } + + /** + * A delegated converter that supports NBT to Component Array and regular Component Array + * @author dmulloy2 + */ + private static class ComponentArrayConverter implements EquivalentConverter { + private static final EquivalentConverter DELEGATE; + static { + if (MinecraftReflection.signUpdateExists()) { + DELEGATE = new LegacyComponentConverter(); + } else { + DELEGATE = new NBTComponentConverter(); + } + } + + @Override + public WrappedChatComponent[] getSpecific(Object generic) { + return DELEGATE.getSpecific(generic); + } + + @Override + public Object getGeneric(WrappedChatComponent[] specific) { + return DELEGATE.getGeneric(specific); + } + + @Override + public Class getSpecificType() { + return DELEGATE.getSpecificType(); + } + + public static Class getGenericType() { + if (DELEGATE instanceof NBTComponentConverter) { + return MinecraftReflection.getNBTCompoundClass(); + } else { + return MinecraftReflection.getIChatBaseComponentArrayClass(); + } + } + } +} diff --git a/src/main/java/com/comphenix/protocol/events/InternalStructure.java b/src/main/java/com/comphenix/protocol/events/InternalStructure.java new file mode 100644 index 00000000..ba457a6d --- /dev/null +++ b/src/main/java/com/comphenix/protocol/events/InternalStructure.java @@ -0,0 +1,45 @@ +package com.comphenix.protocol.events; + +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.wrappers.Converters; + +import java.util.List; +import java.util.Optional; + +public class InternalStructure extends AbstractStructure { + + protected InternalStructure(Object handle, StructureModifier structureModifier) { + super(handle, structureModifier); + } + + protected static final EquivalentConverter CONVERTER = new EquivalentConverter() { + @Override + public Object getGeneric(InternalStructure specific) { + return specific.handle; + } + + @Override + public InternalStructure getSpecific(Object generic) { + return new InternalStructure(generic, new StructureModifier<>(generic.getClass()).withTarget(generic)); + } + + @Override + public Class getSpecificType() { + return InternalStructure.class; + } + }; + + public StructureModifier getStructures() { + return structureModifier.withType(Object.class, CONVERTER); + } + + public StructureModifier> getOptionalStructures() { + return structureModifier.withType(Optional.class, Converters.optional(CONVERTER)); + } + + @Override + public String toString() { + return "InternalStructure[handle=" + handle + " (" + handle.getClass().getSimpleName() + ")]"; + } +} diff --git a/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 654869e5..0f737914 100644 --- a/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -78,14 +78,10 @@ import org.bukkit.util.Vector; * @author Kristian */ @SuppressWarnings("unused") -public class PacketContainer implements Serializable { +public class PacketContainer extends AbstractStructure implements Serializable { private static final long serialVersionUID = 3; - - protected PacketType type; - protected transient Object handle; - // Current structure modifier - protected transient StructureModifier structureModifier; + private PacketType type; // Support for serialization private static ConcurrentMap, Method> writeMethods = Maps.newConcurrentMap(); @@ -149,16 +145,10 @@ public class PacketContainer implements Serializable { * @param structure - structure modifier. */ public PacketContainer(PacketType type, Object handle, StructureModifier structure) { - if (handle == null) - throw new IllegalArgumentException("handle cannot be null."); - if (type == null) - throw new IllegalArgumentException("type cannot be null."); + super(handle, structure); this.type = type; - this.handle = handle; - this.structureModifier = structure; - // TODO this is kinda hacky, come up with a better solution if (type == PacketType.Play.Server.CHAT) { getUUIDs().writeSafely(0, new UUID(0L, 0L)); } @@ -195,946 +185,13 @@ public class PacketContainer implements Serializable { public StructureModifier getModifier() { return structureModifier; } - - /** - * Retrieves a read/write structure for every field with the given type. - * @param Type - * @param primitiveType - the type to find. - * @return A modifier for this specific type. - */ - public StructureModifier getSpecificModifier(Class primitiveType) { - return structureModifier.withType(primitiveType); - } - - /** - * Retrieves a read/write structure for every byte field. - * @return A modifier for every byte field. - */ - public StructureModifier getBytes() { - return structureModifier.withType(byte.class); - } - - /** - * Retrieves a read/write structure for every boolean field. - * @return A modifier for every boolean field. - */ - public StructureModifier getBooleans() { - return structureModifier.withType(boolean.class); - } - - /** - * Retrieves a read/write structure for every short field. - * @return A modifier for every short field. - */ - public StructureModifier getShorts() { - return structureModifier.withType(short.class); - } - - /** - * Retrieves a read/write structure for every integer field. - * @return A modifier for every integer field. - */ - public StructureModifier getIntegers() { - return structureModifier.withType(int.class); - } - /** - * Retrieves a read/write structure for every long field. - * @return A modifier for every long field. - */ - public StructureModifier getLongs() { - return structureModifier.withType(long.class); - } - - /** - * Retrieves a read/write structure for every float field. - * @return A modifier for every float field. - */ - public StructureModifier getFloat() { - return structureModifier.withType(float.class); - } - - /** - * Retrieves a read/write structure for every double field. - * @return A modifier for every double field. - */ - public StructureModifier getDoubles() { - return structureModifier.withType(double.class); - } - - /** - * Retrieves a read/write structure for every String field. - * @return A modifier for every String field. - */ - public StructureModifier getStrings() { - return structureModifier.withType(String.class); + + public StructureModifier getStructures() { + return structureModifier.withType(Object.class, InternalStructure.CONVERTER); } - /** - * Retrieves a read/write structure for every UUID field. - * @return A modifier for every UUID field. - */ - public StructureModifier getUUIDs() { - return structureModifier.withType(UUID.class); - } - - /** - * Retrieves a read/write structure for every String array field. - * @return A modifier for every String array field. - */ - public StructureModifier getStringArrays() { - return structureModifier.withType(String[].class); - } - - /** - * Retrieves a read/write structure for every byte array field. - * @return A modifier for every byte array field. - */ - public StructureModifier getByteArrays() { - return structureModifier.withType(byte[].class); - } - - /** - * Retrieve a serializer for reading and writing ItemStacks stored in a byte array. - * @return A instance of the serializer. - */ - public StreamSerializer getByteArraySerializer() { - return new StreamSerializer(); - } - - /** - * Retrieves a read/write structure for every int array field. - * @return A modifier for every int array field. - */ - public StructureModifier getIntegerArrays() { - return structureModifier.withType(int[].class); - } - - /** - * Retrieves a read/write structure for every short array field. - * @return A modifier for every short array field. - */ - public StructureModifier getShortArrays() { - return structureModifier.withType(short[].class); - } - - /** - * Retrieves a read/write structure for ItemStack. - *

- * This modifier will automatically marshal between the Bukkit ItemStack and the - * internal Minecraft ItemStack. - * @return A modifier for ItemStack fields. - */ - public StructureModifier getItemModifier() { - // Convert to and from the Bukkit wrapper - return structureModifier.withType( - MinecraftReflection.getItemStackClass(), - BukkitConverters.getItemStackConverter()); - } - - /** - * Retrieves a read/write structure for arrays of ItemStacks. - *

- * This modifier will automatically marshal between the Bukkit ItemStack and the - * internal Minecraft ItemStack. - * @return A modifier for ItemStack array fields. - */ - public StructureModifier getItemArrayModifier() { - // Convert to and from the Bukkit wrapper - return structureModifier.withType( - MinecraftReflection.getItemStackArrayClass(), - Converters.ignoreNull(new ItemStackArrayConverter())); - } - - /** - * Retrieves a read/write structure for lists of ItemStacks. - *

- * This modifier will automatically marshal between the Bukkit ItemStack and the - * internal Minecraft ItemStack. - * @return A modifier for ItemStack list fields. - */ - public StructureModifier> getItemListModifier() { - // Convert to and from the Bukkit wrapper - return structureModifier.withType( - List.class, - BukkitConverters.getListConverter(BukkitConverters.getItemStackConverter()) - ); - } - - /** - * 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 getMaps( - BukkitConverters.getWrappedStatisticConverter(), - Converters.passthrough(Integer.class)); - } - - /** - * Retrieves a read/write structure for the world type enum. - *

- * This modifier will automatically marshal between the Bukkit world type and the - * internal Minecraft world type. - * @return A modifier for world type fields. - */ - public StructureModifier getWorldTypeModifier() { - // Convert to and from the Bukkit wrapper - return structureModifier.withType( - MinecraftReflection.getWorldTypeClass(), - BukkitConverters.getWorldTypeConverter()); - } - - /** - * Retrieves a read/write structure for data watchers. - * @return A modifier for data watchers. - */ - public StructureModifier getDataWatcherModifier() { - // Convert to and from the Bukkit wrapper - return structureModifier.withType( - MinecraftReflection.getDataWatcherClass(), - BukkitConverters.getDataWatcherConverter()); - } - - /** - * Retrieves a read/write structure for entity objects. - *

- * Note that entities are transmitted by integer ID, and the type may not be enough - * to distinguish between entities and other values. Thus, this structure modifier - * MAY return null or invalid entities for certain fields. Using the correct index - * is essential. - * - * @param world - the world each entity is currently occupying. - * @return A modifier entity types. - */ - public StructureModifier getEntityModifier(@Nonnull World world) { - Preconditions.checkNotNull(world, "world cannot be NULL."); - // Convert to and from the Bukkit wrapper - return structureModifier.withType( - int.class, BukkitConverters.getEntityConverter(world)); - } - - /** - * Retrieves a read/write structure for entity objects. - *

- * Note that entities are transmitted by integer ID, and the type may not be enough - * to distinguish between entities and other values. Thus, this structure modifier - * MAY return null or invalid entities for certain fields. Using the correct index - * is essential. - * - * @param event - the original packet event. - * @return A modifier entity types. - */ - public StructureModifier getEntityModifier(@Nonnull PacketEvent event) { - Preconditions.checkNotNull(event, "event cannot be NULL."); - return getEntityModifier(event.getPlayer().getWorld()); - } - - /** - * Retrieves a read/write structure for entity types - * @return A modifier for an EntityType. - */ - public StructureModifier getEntityTypeModifier() { - return structureModifier.withType( - MinecraftReflection.getEntityTypes(), - BukkitConverters.getEntityTypeConverter()); - } - - /** - * Retrieves a read/write structure for chunk positions. - * @return A modifier for a ChunkPosition. - */ - public StructureModifier getPositionModifier() { - // Convert to and from the Bukkit wrapper - return structureModifier.withType( - MinecraftReflection.getChunkPositionClass(), - ChunkPosition.getConverter()); - } - - /** - * Retrieves a read/write structure for block positions. - * @return A modifier for a BlockPosition. - */ - public StructureModifier getBlockPositionModifier() { - // Convert to and from the Bukkit wrapper - return structureModifier.withType( - MinecraftReflection.getBlockPositionClass(), - BlockPosition.getConverter()); - } - - /** - * Retrieves a read/write structure for wrapped ChunkCoordIntPairs. - * @return A modifier for ChunkCoordIntPair. - */ - public StructureModifier getChunkCoordIntPairs() { - // Allow access to the NBT class in packet 130 - return structureModifier.withType( - MinecraftReflection.getChunkCoordIntPair(), - ChunkCoordIntPair.getConverter()); - } - - /** - * Retrieves a read/write structure for NBT classes. - * @return A modifier for NBT classes. - */ - public StructureModifier> getNbtModifier() { - // Allow access to the NBT class in packet 130 - return structureModifier.withType( - MinecraftReflection.getNBTBaseClass(), - BukkitConverters.getNbtConverter()); - } - - /** - * Retrieves a read/write structure for lists of NBT classes. - * @return A modifier for lists of NBT classes. - */ - public StructureModifier>> getListNbtModifier() { - // Convert to and from the ProtocolLib wrapper - return structureModifier.withType( - Collection.class, - BukkitConverters.getListConverter(BukkitConverters.getNbtConverter()) - ); - } - - /** - * Retrieves a read/write structure for Vectors. - * @return A modifier for Vectors. - */ - public StructureModifier getVectors() { - // Automatically marshal between Vec3d and the Bukkit wrapper - return structureModifier.withType( - MinecraftReflection.getVec3DClass(), - BukkitConverters.getVectorConverter()); - } - - /** - * Retrieves a read/write structure for collections of attribute snapshots. - *

- * This modifier will automatically marshal between the visible ProtocolLib WrappedAttribute and the - * internal Minecraft AttributeSnapshot. - * @return A modifier for AttributeSnapshot collection fields. - */ - public StructureModifier> getAttributeCollectionModifier() { - // Convert to and from the ProtocolLib wrapper - return structureModifier.withType( - Collection.class, - BukkitConverters.getListConverter(BukkitConverters.getWrappedAttributeConverter()) - ); - } - - /** - * Retrieves a read/write structure for collections of chunk positions. - *

- * This modifier will automatically marshal between the visible ProtocolLib ChunkPosition and the - * internal Minecraft ChunkPosition. - * - * @return A modifier for ChunkPosition list fields. - */ - public StructureModifier> getPositionCollectionModifier() { - // Convert to and from the ProtocolLib wrapper - return structureModifier.withType( - Collection.class, - BukkitConverters.getListConverter(ChunkPosition.getConverter())); - } - - /** - * Retrieves a read/write structure for collections of chunk positions. - *

- * This modifier will automatically marshal between the visible ProtocolLib BlockPosition and the - * internal Minecraft BlockPosition. - * - * @return A modifier for ChunkPosition list fields. - */ - public StructureModifier> getBlockPositionCollectionModifier() { - // Convert to and from the ProtocolLib wrapper - return structureModifier.withType( - Collection.class, - BukkitConverters.getListConverter(BlockPosition.getConverter())); - } - - /** - * Retrieves a read/write structure for collections of watchable objects. - *

- * This modifier will automatically marshal between the visible WrappedWatchableObject and the - * internal Minecraft WatchableObject. - * @return A modifier for watchable object list fields. - */ - public StructureModifier> getWatchableCollectionModifier() { - // Convert to and from the ProtocolLib wrapper - return structureModifier.withType( - Collection.class, - BukkitConverters.getListConverter(BukkitConverters.getWatchableObjectConverter())); - } - - /** - * Retrieves a read/write structure for block fields. - *

- * This modifier will automatically marshal between Material and the - * internal Minecraft Block. - * @return A modifier for GameProfile fields. - */ - public StructureModifier getBlocks() { - // Convert to and from the Bukkit wrapper - return structureModifier.withType( - MinecraftReflection.getBlockClass(), BukkitConverters.getBlockConverter()); - } - - /** - * Retrieves a read/write structure for game profiles in Minecraft 1.7.2. - *

- * This modifier will automatically marshal between WrappedGameProfile and the - * internal Minecraft GameProfile. - * @return A modifier for GameProfile fields. - */ - public StructureModifier getGameProfiles() { - // Convert to and from the Bukkit wrapper - return structureModifier.withType( - MinecraftReflection.getGameProfileClass(), BukkitConverters.getWrappedGameProfileConverter()); - } - - /** - * Retrieves a read/write structure for BlockData in Minecraft 1.8. - *

- * This modifier will automatically marshal between WrappedBlockData and the - * internal Minecraft IBlockData. - * @return A modifier for BlockData fields. - */ - public StructureModifier getBlockData() { - // Convert to and from our wrapper - return structureModifier.withType( - MinecraftReflection.getIBlockDataClass(), - BukkitConverters.getWrappedBlockDataConverter() - ); - } - - /** - * Retrieves a read/write structure for IBlockData arrays in Minecraft 1.16.2+ - * @return A modifier for IBlockData array fields - */ - public StructureModifier getBlockDataArrays() { - // TODO we might want to make this a lazy converter and only convert indexes as needed - return structureModifier.withType( - MinecraftReflection.getArrayClass(MinecraftReflection.getIBlockDataClass()), - Converters.array(MinecraftReflection.getIBlockDataClass(), BukkitConverters.getWrappedBlockDataConverter()) - ); - } - - /** - * Retrieves a read/write structure for MultiBlockChangeInfo arrays in Minecraft 1.8. - *

- * This modifier will automatically marshal between MultiBlockChangeInfo and the - * internal Minecraft MultiBlockChangeInfo. - * @return A modifier for BlockData fields. - */ - public StructureModifier getMultiBlockChangeInfoArrays() { - ChunkCoordIntPair chunk = getChunkCoordIntPairs().read(0); - - // Convert to and from our wrapper - return structureModifier.withType( - MinecraftReflection.getMultiBlockChangeInfoArrayClass(), - Converters.array(MinecraftReflection.getMultiBlockChangeInfoClass(), MultiBlockChangeInfo.getConverter(chunk)) - ); - } - - /** - * Retrieves a read/write structure for chat components in Minecraft 1.7.2. - *

- * This modifier will automatically marshal between WrappedChatComponent and the - * internal Minecraft IChatBaseComponent. - * @return A modifier for ChatComponent fields. - */ - public StructureModifier getChatComponents() { - // Convert to and from the Bukkit wrapper - return structureModifier.withType( - MinecraftReflection.getIChatBaseComponentClass(), BukkitConverters.getWrappedChatComponentConverter()); - } - - /** - * Retrieves a read/write structure for arrays of chat components. - *

- * This modifier will automatically marshal between WrappedChatComponent and the - * internal Minecraft IChatBaseComponent (1.9.2 and below) or the internal - * NBTCompound (1.9.4 and up). - *

- * Note that in 1.9.4 and up this modifier only works properly with sign - * tile entities. - * @return A modifier for ChatComponent array fields. - */ - public StructureModifier getChatComponentArrays() { - // Convert to and from the Bukkit wrapper - return structureModifier.withType( - ComponentArrayConverter.getGenericType(), - Converters.ignoreNull(new ComponentArrayConverter())); - } - - /** - * Retrieve a read/write structure for the ServerPing fields in the following packet:
- *

    - *
  • {@link PacketType.Status.Server#SERVER_INFO} - *
- * @return A modifier for ServerPing fields. - */ - public StructureModifier getServerPings() { - // Convert to and from the wrapper - return structureModifier.withType( - MinecraftReflection.getServerPingClass(), - BukkitConverters.getWrappedServerPingConverter()); - } - - /** - * Retrieve a read/write structure for the PlayerInfoData list fields in the following packet:
- *
    - *
  • {@link PacketType.Play.Server#PLAYER_INFO} - *
- * @return A modifier for PlayerInfoData list fields. - */ - public StructureModifier> getPlayerInfoDataLists() { - // Convert to and from the ProtocolLib wrapper - return structureModifier.withType( - Collection.class, - BukkitConverters.getListConverter(PlayerInfoData.getConverter())); - } - - /** - * Retrieve a read/write structure for the Protocol enum in 1.7.2. - * @return A modifier for Protocol enum fields. - */ - public StructureModifier getProtocols() { - // Convert to and from the wrapper - return structureModifier.withType( - EnumWrappers.getProtocolClass(), - EnumWrappers.getProtocolConverter()); - } - - /** - * Retrieve a read/write structure for the ClientCommand enum in 1.7.2. - * @return A modifier for ClientCommand enum fields. - */ - public StructureModifier getClientCommands() { - // Convert to and from the wrapper - return structureModifier.withType( - EnumWrappers.getClientCommandClass(), - EnumWrappers.getClientCommandConverter()); - } - - /** - * Retrieve a read/write structure for the ChatVisibility enum in 1.7.2. - * @return A modifier for ChatVisibility enum fields. - */ - public StructureModifier getChatVisibilities() { - // Convert to and from the wrapper - return structureModifier.withType( - EnumWrappers.getChatVisibilityClass(), - EnumWrappers.getChatVisibilityConverter()); - } - - /** - * Retrieve a read/write structure for the Difficulty enum in 1.7.2. - * @return A modifier for Difficulty enum fields. - */ - public StructureModifier getDifficulties() { - // Convert to and from the wrapper - return structureModifier.withType( - EnumWrappers.getDifficultyClass(), - EnumWrappers.getDifficultyConverter()); - } - - /** - * Retrieve a read/write structure for the EntityUse enum in 1.7.2. - * @return A modifier for EntityUse enum fields. - */ - public StructureModifier getEntityUseActions() { - // Convert to and from the wrapper - return structureModifier.withType( - EnumWrappers.getEntityUseActionClass(), - EnumWrappers.getEntityUseActionConverter()); - } - - /** - * Retrieves a read/write structure for the EntityUseAction class in the UseEntity packet sent by the client for - * 1.17 and above. - * @return A modifier for EntityUseAction class fields. - */ - public StructureModifier getEnumEntityUseActions() { - return structureModifier.withType( - MinecraftReflection.getEnumEntityUseActionClass(), - WrappedEnumEntityUseAction.CONVERTER); - } - - /** - * Retrieve a read/write structure for the NativeGameMode enum in 1.7.2. - * @return A modifier for NativeGameMode enum fields. - */ - public StructureModifier getGameModes() { - // Convert to and from the wrapper - return structureModifier.withType( - EnumWrappers.getGameModeClass(), - EnumWrappers.getGameModeConverter()); - } - - /** - * Retrieve a read/write structure for the ResourcePackStatus enum in 1.8. - * @return A modifier for ResourcePackStatus enum fields. - */ - public StructureModifier getResourcePackStatus() { - // Convert to and from the wrapper - return structureModifier.withType( - EnumWrappers.getResourcePackStatusClass(), - EnumWrappers.getResourcePackStatusConverter()); - } - - /** - * Retrieve a read/write structure for the PlayerInfo enum in 1.8. - * @return A modifier for PlayerInfoAction enum fields. - */ - public StructureModifier getPlayerInfoAction() { - // Convert to and from the wrapper - return structureModifier.withType( - EnumWrappers.getPlayerInfoActionClass(), - EnumWrappers.getPlayerInfoActionConverter()); - } - - /** - * Retrieve a read/write structure for the TitleAction enum in 1.8. - * @return A modifier for TitleAction enum fields. - */ - public StructureModifier getTitleActions() { - // Convert to and from the wrapper - return structureModifier.withType( - EnumWrappers.getTitleActionClass(), - EnumWrappers.getTitleActionConverter()); - } - - /** - * Retrieve a read/write structure for the WorldBorderAction enum in 1.8. - * @return A modifier for WorldBorderAction enum fields. - */ - public StructureModifier getWorldBorderActions() { - // Convert to and from the wrapper - return structureModifier.withType( - EnumWrappers.getWorldBorderActionClass(), - EnumWrappers.getWorldBorderActionConverter()); - } - - /** - * Retrieve a read/write structure for the CombatEventType enum in 1.8. - * @return A modifier for CombatEventType enum fields. - */ - public StructureModifier getCombatEvents() { - // Convert to and from the wrapper - return structureModifier.withType( - EnumWrappers.getCombatEventTypeClass(), - EnumWrappers.getCombatEventTypeConverter()); - } - - /** - * Retrieve a read/write structure for the PlayerDigType enum in 1.8. - * @return A modifier for PlayerDigType enum fields. - */ - public StructureModifier getPlayerDigTypes() { - // Convert to and from the wrapper - return structureModifier.withType( - EnumWrappers.getPlayerDigTypeClass(), - EnumWrappers.getPlayerDiggingActionConverter()); - } - - /** - * Retrieve a read/write structure for the PlayerAction enum in 1.8. - * @return A modifier for PlayerAction enum fields. - */ - public StructureModifier getPlayerActions() { - // Convert to and from the wrapper - return structureModifier.withType( - EnumWrappers.getPlayerActionClass(), - EnumWrappers.getEntityActionConverter()); - } - - /** - * Retrieve a read/write structure for the ScoreboardAction enum in 1.8. - * @return A modifier for ScoreboardAction enum fields. - */ - public StructureModifier getScoreboardActions() { - // Convert to and from the wrapper - return structureModifier.withType( - EnumWrappers.getScoreboardActionClass(), - EnumWrappers.getUpdateScoreActionConverter()); - } - - /** - * Retrieve a read/write structure for the Particle enum in 1.8-1.12. - * NOTE: This will produce undesirable results in 1.13 - * @return A modifier for Particle enum fields. - */ - public StructureModifier getParticles() { - // Convert to and from the wrapper - return structureModifier.withType( - EnumWrappers.getParticleClass(), - EnumWrappers.getParticleConverter()); - } - - /** - * Retrieve a read/write structure for ParticleParams in 1.13 - * @return A modifier for ParticleParam fields. - */ - public StructureModifier getNewParticles() { - return structureModifier.withType( - MinecraftReflection.getParticleParam(), - BukkitConverters.getParticleConverter() - ); - } - - /** - * Retrieve a read/write structure for the MobEffectList class in 1.9. - * @return A modifier for MobEffectList fields. - */ - public StructureModifier getEffectTypes() { - // Convert to and from Bukkit - return structureModifier.withType( - MinecraftReflection.getMobEffectListClass(), - BukkitConverters.getEffectTypeConverter()); - } - - /** - * Retrieve a read/write structure for the SoundCategory enum in 1.9. - * @return A modifier for SoundCategory enum fields. - */ - public StructureModifier getSoundCategories() { - // Convert to and from the enums - return structureModifier.withType( - EnumWrappers.getSoundCategoryClass(), - EnumWrappers.getSoundCategoryConverter()); - } - - /** - * Retrieve a read/write structure for the SoundEffect enum in 1.9. - * @return A modifier for SoundEffect enum fields. - */ - public StructureModifier getSoundEffects() { - // Convert to and from Bukkit - return structureModifier.withType( - MinecraftReflection.getSoundEffectClass(), - BukkitConverters.getSoundConverter()); - } - - /** - * Retrieve a read/write structure for the ItemSlot enum in 1.9. - * @return A modifier for ItemSlot enum fields. - */ - public StructureModifier getItemSlots() { - return structureModifier.withType( - EnumWrappers.getItemSlotClass(), - EnumWrappers.getItemSlotConverter()); - } - - /** - * Retrieve a read/write structure for the Hand enum in 1.9. - * @return A modifier for Hand enum fields. - */ - public StructureModifier getHands() { - return structureModifier.withType( - EnumWrappers.getHandClass(), - EnumWrappers.getHandConverter()); - } - - /** - * Retrieve a read/write structure for the Direction enum in 1.10. - * @return A modifier for Direction enum fields. - */ - public StructureModifier getDirections() { - return structureModifier.withType( - EnumWrappers.getDirectionClass(), - EnumWrappers.getDirectionConverter()); - } - - /** - * Retrieve a read/write structure for the ChatType enum in 1.12. - * @return A modifier for ChatType enum fields. - */ - public StructureModifier getChatTypes() { - return structureModifier.withType( - EnumWrappers.getChatTypeClass(), - EnumWrappers.getChatTypeConverter()); - } - - /** - * Retrieve a read/write structure for the MinecraftKey class. - * @return A modifier for MinecraftKey fields. - */ - public StructureModifier getMinecraftKeys() { - return structureModifier.withType( - MinecraftReflection.getMinecraftKeyClass(), - MinecraftKey.getConverter()); - } - - /** - * Retrieve a read/write structure for dimension IDs in 1.13.1+ - * @return A modifier for dimension IDs - */ - @Deprecated - public StructureModifier getDimensions() { - if (MinecraftVersion.NETHER_UPDATE.atOrAbove() && !MinecraftVersion.NETHER_UPDATE_2.atOrAbove()) { - return structureModifier.withParamType( - MinecraftReflection.getResourceKey(), - BukkitConverters.getDimensionIDConverter(), - MinecraftReflection.getDimensionManager() - ); - } else { - return structureModifier.withType( - MinecraftReflection.getDimensionManager(), - BukkitConverters.getDimensionIDConverter() - ); - } - } - - public StructureModifier getDimensionTypes() { - return structureModifier.withType( - MinecraftReflection.getDimensionManager(), - BukkitConverters.getDimensionConverter() - ); - } - - /** - * Retrieve a read/write structure for the MerchantRecipeList class. - * @return A modifier for MerchantRecipeList fields. - */ - public StructureModifier> getMerchantRecipeLists() { - return structureModifier.withType( - MinecraftReflection.getMerchantRecipeList(), - BukkitConverters.getMerchantRecipeListConverter() - ); - } - - /** - * Retrieve a read/write structure for ItemSlot/ItemStack pair lists in 1.16+ - * @return The Structure Modifier - */ - public StructureModifier>> getSlotStackPairLists() { - return getLists(BukkitConverters.getPairConverter( - EnumWrappers.getItemSlotConverter(), - BukkitConverters.getItemStackConverter() - )); - } - - /** - * Retrieve a read/write structure for MovingObjectPositionBlock in 1.16+ - * @return The Structure Modifier - */ - public StructureModifier getMovingBlockPositions() { - return structureModifier.withType( - MovingObjectPositionBlock.getNmsClass(), - MovingObjectPositionBlock.getConverter() - ); - } - - /** - * Retrieve a read/write structure for World ResourceKeys in 1.16+ - * @return The Structure Modifier - */ - public StructureModifier getWorldKeys() { - return structureModifier.withParamType( - MinecraftReflection.getResourceKey(), - BukkitConverters.getWorldKeyConverter(), - MinecraftReflection.getNmsWorldClass() - ); - } - - /** - * Retrieve a read/write structure for SectionPositions in 1.16.2+ - * @return The Structure Modifier - */ - public StructureModifier getSectionPositions() { - return structureModifier.withType( - MinecraftReflection.getSectionPosition(), - BukkitConverters.getSectionPositionConverter() - ); - } - - /** - * Retrieve a read/write structure for Game State IDs in 1.16+ - * @return The Structure Modifier - */ - public StructureModifier getGameStateIDs() { - return structureModifier.withType( - MinecraftReflection.getGameStateClass(), - BukkitConverters.getGameStateConverter() - ); - } - - /** - * Retrieve a read/write structure for the Map class. - * @param keyConverter Converter for map keys - * @param valConverter Converter for map values - * @param Key param - * @param Value param - * @return A modifier for Map fields. - * - * @see BukkitConverters - * @see EquivalentConverter - */ - public StructureModifier> getMaps(EquivalentConverter keyConverter, - EquivalentConverter valConverter) { - return structureModifier.withType( - Map.class, - BukkitConverters.getMapConverter(keyConverter, valConverter)); - } - - /** - * Retrieve a read/write structure for the Set class. - * @param converter Converter for elements - * @param Element param - * @return A modifier for Set fields - * - * @see BukkitConverters - * @see EquivalentConverter - */ - public StructureModifier> getSets(EquivalentConverter converter) { - return structureModifier.withType( - Set.class, - BukkitConverters.getSetConverter(converter)); - } - - /** - * Retrieve a read/write structure for the List class. - * @param converter Converter for elements - * @param Element param - * @return A modifier for List fields - */ - public StructureModifier> getLists(EquivalentConverter converter) { - return structureModifier.withType( - List.class, - BukkitConverters.getListConverter(converter)); - } - - /** - * Retrieve a read/write structure for an enum. This allows for the use of - * user-created enums that may not exist in ProtocolLib. The specific (user - * created) enum constants must match up perfectly with their generic (NMS) - * counterparts. - * - * @param enumClass The specific Enum class - * @param nmsClass The generic Enum class - * @return The modifier - */ - public > StructureModifier getEnumModifier(Class enumClass, Class nmsClass) { - return structureModifier.withType( - nmsClass, - new EnumConverter<>(nmsClass, enumClass)); - } - - /** - * Retrieve a read/write structure for an enum. This method is for convenience, - * see {@link #getEnumModifier(Class, Class)} for more information. - * - * @param enumClass The specific Enum class - * @param index Index of the generic Enum - * @return The modifier - * @see #getEnumModifier(Class, Class) - */ - public > StructureModifier getEnumModifier(Class enumClass, int index) { - return getEnumModifier( - enumClass, - structureModifier.getField(index).getType()); + public StructureModifier> getOptionalStructures() { + return structureModifier.withType(Optional.class, Converters.optional(InternalStructure.CONVERTER)); } /** @@ -1374,167 +431,6 @@ public class PacketContainer implements Serializable { return method; } - - /** - * Represents an equivalent converter for ItemStack arrays. - * @author Kristian - */ - private static class ItemStackArrayConverter implements EquivalentConverter { - final EquivalentConverter stackConverter = BukkitConverters.getItemStackConverter(); - - @Override - public Object getGeneric(ItemStack[] specific) { - Class nmsStack = MinecraftReflection.getItemStackClass(); - Object[] result = (Object[]) Array.newInstance(nmsStack, specific.length); - - // Unwrap every item - for (int i = 0; i < result.length; i++) { - result[i] = stackConverter.getGeneric(specific[i]); - } - return result; - } - - @Override - public ItemStack[] getSpecific(Object generic) { - Object[] input = (Object[]) generic; - ItemStack[] result = new ItemStack[input.length]; - - // Add the wrapper - for (int i = 0; i < result.length; i++) { - result[i] = stackConverter.getSpecific(input[i]); - } - return result; - } - - @Override - public Class getSpecificType() { - return ItemStack[].class; - } - } - - /** - * Represents an equivalent converter for ChatComponent arrays. - * @author dmulloy2 - */ - private static class LegacyComponentConverter implements EquivalentConverter { - final EquivalentConverter componentConverter = BukkitConverters.getWrappedChatComponentConverter(); - - @Override - public Object getGeneric(WrappedChatComponent[] specific) { - Class nmsComponent = MinecraftReflection.getIChatBaseComponentClass(); - Object[] result = (Object[]) Array.newInstance(nmsComponent, specific.length); - - // Unwrap every item - for (int i = 0; i < result.length; i++) { - result[i] = componentConverter.getGeneric(specific[i]); - } - return result; - } - - @Override - public WrappedChatComponent[] getSpecific(Object generic) { - Object[] input = (Object[]) generic; - WrappedChatComponent[] result = new WrappedChatComponent[input.length]; - - // Add the wrapper - for (int i = 0; i < result.length; i++) { - result[i] = componentConverter.getSpecific(input[i]); - } - return result; - } - - @Override - public Class getSpecificType() { - return WrappedChatComponent[].class; - } - } - - /** - * Converts from NBT to WrappedChatComponent arrays - * @author dmulloy2 - */ - private static class NBTComponentConverter implements EquivalentConverter { - private EquivalentConverter> nbtConverter = BukkitConverters.getNbtConverter(); - private final int lines = 4; - - @Override - public WrappedChatComponent[] getSpecific(Object generic) { - NbtBase nbtBase = nbtConverter.getSpecific(generic); - NbtCompound compound = (NbtCompound) nbtBase; - - WrappedChatComponent[] components = new WrappedChatComponent[lines]; - for (int i = 0; i < lines; i++) { - if (compound.containsKey("Text" + (i + 1))) { - components[i] = WrappedChatComponent.fromJson(compound.getString("Text" + (i + 1))); - } else { - components[i] = WrappedChatComponent.fromText(""); - } - } - - return components; - } - - @Override - public Object getGeneric(WrappedChatComponent[] specific) { - NbtCompound compound = NbtFactory.ofCompound(""); - - for (int i = 0; i < lines; i++) { - WrappedChatComponent component; - if (i < specific.length && specific[i] != null) { - component = specific[i]; - } else { - component = WrappedChatComponent.fromText(""); - } - - compound.put("Text" + (i + 1), component.getJson()); - } - - return nbtConverter.getGeneric(compound); - } - - @Override - public Class getSpecificType() { - return WrappedChatComponent[].class; - } - } - - /** - * A delegated converter that supports NBT to Component Array and regular Component Array - * @author dmulloy2 - */ - private static class ComponentArrayConverter implements EquivalentConverter { - private static final EquivalentConverter DELEGATE; - static { - if (MinecraftReflection.signUpdateExists()) { - DELEGATE = new LegacyComponentConverter(); - } else { - DELEGATE = new NBTComponentConverter(); - } - } - - @Override - public WrappedChatComponent[] getSpecific(Object generic) { - return DELEGATE.getSpecific(generic); - } - - @Override - public Object getGeneric(WrappedChatComponent[] specific) { - return DELEGATE.getGeneric(specific); - } - - @Override - public Class getSpecificType() { - return DELEGATE.getSpecificType(); - } - - public static Class getGenericType() { - if (DELEGATE instanceof NBTComponentConverter) { - return MinecraftReflection.getNBTCompoundClass(); - } else { - return MinecraftReflection.getIChatBaseComponentArrayClass(); - } - } - } @Override public String toString() { diff --git a/src/main/java/com/comphenix/protocol/wrappers/Converters.java b/src/main/java/com/comphenix/protocol/wrappers/Converters.java index e87712ab..870a2226 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/Converters.java +++ b/src/main/java/com/comphenix/protocol/wrappers/Converters.java @@ -17,6 +17,7 @@ package com.comphenix.protocol.wrappers; import java.lang.reflect.Array; +import java.util.Optional; import java.util.function.Function; import com.comphenix.protocol.reflect.EquivalentConverter; @@ -150,4 +151,24 @@ public class Converters { } }; } + + public static EquivalentConverter> optional(final EquivalentConverter converter) { + return new EquivalentConverter>() { + @Override + public Object getGeneric(Optional specific) { + return specific.map(converter::getGeneric); + } + + @Override + public Optional getSpecific(Object generic) { + Optional optional = (Optional) generic; + return optional.map(converter::getSpecific); + } + + @Override + public Class> getSpecificType() { + return (Class>) Optional.empty().getClass(); + } + }; + } } diff --git a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java index 167537a1..300af14e 100644 --- a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java +++ b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -515,6 +515,24 @@ public class PacketContainerTest { // assertEquals(container.getEnumModifier(Action.class, PacketPlayOutBoss.d.class).read(0), Action.UPDATE_PCT); } + @Test + public void testInternalStructures() { + PacketContainer container = new PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM); + Optional optStruct = container.getOptionalStructures().read(0); + assertTrue(optStruct.isPresent()); + InternalStructure struct = optStruct.get(); + struct.getChatComponents().write(0, WrappedChatComponent.fromText("hi there")); + container.getOptionalStructures().write(0, Optional.of(struct)); + + optStruct = container.getOptionalStructures().read(0); + assertTrue(optStruct.isPresent()); + struct = optStruct.get(); + testEquality( + struct.getChatComponents().read(0), + WrappedChatComponent.fromText("hi there") + ); + } + // @Test public void testDimensions() { // TODO this won't work in testing, but hopefully will in live