diff --git a/src/main/java/com/comphenix/protocol/PacketType.java b/src/main/java/com/comphenix/protocol/PacketType.java index e690dc971..d7769de76 100644 --- a/src/main/java/com/comphenix/protocol/PacketType.java +++ b/src/main/java/com/comphenix/protocol/PacketType.java @@ -14,18 +14,18 @@ import java.util.UUID; import java.util.function.BiConsumer; -import org.apache.commons.lang.WordUtils; -import org.bukkit.Bukkit; - import com.comphenix.protocol.PacketTypeLookup.ClassLookup; import com.comphenix.protocol.events.ConnectionSide; import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.scheduler.UniversalRunnable; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; + import com.google.common.base.Preconditions; import com.google.common.collect.ComparisonChain; import com.google.common.collect.Iterables; +import org.apache.commons.lang.WordUtils; +import org.bukkit.Bukkit; /** * Represents the type of a packet in a specific protocol. *

@@ -477,8 +477,8 @@ public static class Client extends PacketTypeEnum { public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x35, "SignUpdate", "UpdateSign", "CPacketUpdateSign"); public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x36, "Swing", "ArmAnimation", "CPacketAnimation"); public static final PacketType SPECTATE = new PacketType(PROTOCOL, SENDER, 0x37, "TeleportToEntity", "Spectate", "CPacketSpectate"); - public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x38, "UseItemOn", "UseItem", "CPacketPlayerTryUseItemOnBlock"); - public static final PacketType BLOCK_PLACE = new PacketType(PROTOCOL, SENDER, 0x39, "BlockPlace", "CPacketPlayerTryUseItem"); + public static final PacketType USE_ITEM_ON = new PacketType(PROTOCOL, SENDER, 0x38, "UseItemOn", "BlockPlace", "CPacketPlayerTryUseItemOnBlock"); + public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x39, "UseItem", "CPacketPlayerTryUseItem"); /** * @deprecated Removed in 1.17 @@ -498,6 +498,12 @@ public static class Client extends PacketTypeEnum { @Deprecated public static final PacketType CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 253, "ChatPreview"); + /** + * @deprecated Renamed to USE_ITEM_ON + */ + @Deprecated + public static final PacketType BLOCK_PLACE = USE_ITEM_ON.clone(); + private static final Client INSTANCE = new Client(); // Prevent accidental construction diff --git a/src/main/java/com/comphenix/protocol/events/AbstractStructure.java b/src/main/java/com/comphenix/protocol/events/AbstractStructure.java index ec8b9f2e5..b915ad38f 100644 --- a/src/main/java/com/comphenix/protocol/events/AbstractStructure.java +++ b/src/main/java/com/comphenix/protocol/events/AbstractStructure.java @@ -870,6 +870,55 @@ public StructureModifier getChatTypes() { EnumWrappers.getChatTypeConverter()); } + /** + * Retrieve a read/write structure for the DisplaySlot enum in 1.20.2. + * @return A modifier for DisplaySlot enum fields. + */ + public StructureModifier getDisplaySlots() { + return structureModifier.withType( + EnumWrappers.getDisplaySlotClass(), + EnumWrappers.getDisplaySlotConverter()); + } + + /** + * Retrieve a read/write structure for the RenderType enum. + * @return A modifier for RenderType enum fields. + */ + public StructureModifier getRenderTypes() { + return structureModifier.withType( + EnumWrappers.getRenderTypeClass(), + EnumWrappers.getRenderTypeConverter()); + } + + /** + * Retrieve a read/write structure for the ChatFormatting enum. + * @return A modifier for ChatFormatting enum fields. + */ + public StructureModifier getChatFormattings() { + return structureModifier.withType( + EnumWrappers.getChatFormattingClass(), + EnumWrappers.getChatFormattingConverter()); + } + + /** + * Retrieve a read/write structure for optional team parameters in 1.17+. + * @return A modifier for optional team parameters fields. + */ + public StructureModifier> getOptionalTeamParameters() { + return getOptionals(BukkitConverters.getWrappedTeamParametersConverter()); + } + + /** + * Retrieve a read/write structure for the NumberFormat class in 1.20.4+. + * @return A modifier for NumberFormat fields. + */ + public StructureModifier getNumberFormats() { + return structureModifier.withType( + MinecraftReflection.getNumberFormatClass().orElse(null), + BukkitConverters.getWrappedNumberFormatConverter()); + } + + /** * Retrieve a read/write structure for the MinecraftKey class. * @return A modifier for MinecraftKey fields. diff --git a/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 19ee953b6..ba50ca72f 100644 --- a/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; +import javax.annotation.Nullable; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.injector.StructureCache; @@ -58,10 +59,10 @@ import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.wrappers.Converters; import com.comphenix.protocol.wrappers.WrappedStreamCodec; + import com.google.common.collect.Sets; import io.netty.buffer.ByteBuf; import io.netty.util.ReferenceCountUtil; -import javax.annotation.Nullable; /** * Represents a Minecraft packet indirectly. @@ -80,7 +81,7 @@ public class PacketContainer extends AbstractStructure implements Serializable { // Used to clone packets private static final AggregateCloner DEEP_CLONER = AggregateCloner .newBuilder() - .instanceProvider(StructureCache::newPacket) + .instanceProvider(StructureCache::newInstance) .andThen(BukkitCloner.class) .andThen(ImmutableDetector.class) .andThen(JavaOptionalCloner.class) @@ -91,7 +92,7 @@ public class PacketContainer extends AbstractStructure implements Serializable { private static final AggregateCloner SHALLOW_CLONER = AggregateCloner .newBuilder() - .instanceProvider(StructureCache::newPacket) + .instanceProvider(StructureCache::newInstance) .andThen(param -> { if (param == null) throw new IllegalArgumentException("Cannot be NULL."); diff --git a/src/main/java/com/comphenix/protocol/events/SerializedOfflinePlayer.java b/src/main/java/com/comphenix/protocol/events/SerializedOfflinePlayer.java index 64a20cbb5..722ea5428 100644 --- a/src/main/java/com/comphenix/protocol/events/SerializedOfflinePlayer.java +++ b/src/main/java/com/comphenix/protocol/events/SerializedOfflinePlayer.java @@ -17,10 +17,25 @@ package com.comphenix.protocol.events; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.utility.ByteBuddyFactory; import com.comphenix.protocol.utility.Util; + import net.bytebuddy.description.ByteCodeElement; import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; @@ -35,27 +50,19 @@ import net.bytebuddy.implementation.bind.annotation.RuntimeType; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatchers; -import org.bukkit.*; +import org.bukkit.BanEntry; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.Statistic; +import org.bukkit.World; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.profile.PlayerProfile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.time.Duration; -import java.time.Instant; -import java.util.Date; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - /** * Represents a player object that can be serialized by Java. * diff --git a/src/main/java/com/comphenix/protocol/injector/StructureCache.java b/src/main/java/com/comphenix/protocol/injector/StructureCache.java index 6b50b465f..cb50e3c73 100644 --- a/src/main/java/com/comphenix/protocol/injector/StructureCache.java +++ b/src/main/java/com/comphenix/protocol/injector/StructureCache.java @@ -32,7 +32,7 @@ import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.reflect.instances.DefaultInstances; -import com.comphenix.protocol.reflect.instances.PacketCreator; +import com.comphenix.protocol.reflect.instances.InstanceCreator; import com.comphenix.protocol.utility.ByteBuddyFactory; import com.comphenix.protocol.utility.MinecraftMethods; import com.comphenix.protocol.utility.MinecraftReflection; @@ -57,7 +57,7 @@ public class StructureCache { // Structure modifiers - private static final Map, Supplier> PACKET_INSTANCE_CREATORS = new ConcurrentHashMap<>(); + private static final Map, Supplier> CACHED_INSTANCE_CREATORS = new ConcurrentHashMap<>(); private static final Map> STRUCTURE_MODIFIER_CACHE = new ConcurrentHashMap<>(); // packet data serializer which always returns an empty nbt tag compound @@ -67,77 +67,90 @@ public class StructureCache { private static Supplier TRICKED_DATA_SERIALIZER_BASE; private static Supplier TRICKED_DATA_SERIALIZER_JSON; + /** + * @deprecated Renamed to {@link #newInstance(Class)}. + */ + @Deprecated public static Object newPacket(Class packetClass) { - Supplier packetConstructor = PACKET_INSTANCE_CREATORS.computeIfAbsent(packetClass, packetClassKey -> { - PacketCreator creator = PacketCreator.forPacket(packetClassKey); + return newInstance(packetClass); + } + + public static Object newInstance(Class clazz) { + Supplier creator = CACHED_INSTANCE_CREATORS.computeIfAbsent(clazz, StructureCache::determineBestCreator); + return creator.get(); + } + + static Supplier determineBestCreator(Class clazz) { + try { + InstanceCreator creator = InstanceCreator.forClass(clazz); if (creator.get() != null) { return creator; } + } catch (Exception ignored) { + } - WrappedStreamCodec streamCodec = PacketRegistry.getStreamCodec(packetClassKey); + WrappedStreamCodec streamCodec = PacketRegistry.getStreamCodec(clazz); - // use the new stream codec for versions above 1.20.5 if possible - if (streamCodec != null && tryInitTrickDataSerializer()) { + // use the new stream codec for versions above 1.20.5 if possible + if (streamCodec != null && tryInitTrickDataSerializer()) { + try { + // first try with the base accessor + Object serializer = TRICKED_DATA_SERIALIZER_BASE.get(); + streamCodec.decode(serializer); // throwaway instance, for testing + + // method is working + return () -> streamCodec.decode(serializer); + } catch (Exception ignored) { try { - // first try with the base accessor - Object serializer = TRICKED_DATA_SERIALIZER_BASE.get(); + // try with the json accessor + Object serializer = TRICKED_DATA_SERIALIZER_JSON.get(); streamCodec.decode(serializer); // throwaway instance, for testing // method is working return () -> streamCodec.decode(serializer); - } catch (Exception ignored) { - try { - // try with the json accessor - Object serializer = TRICKED_DATA_SERIALIZER_JSON.get(); - streamCodec.decode(serializer); // throwaway instance, for testing - - // method is working - return () -> streamCodec.decode(serializer); - } catch (Exception ignored1) { - // shrug, fall back to default behaviour - } + } catch (Exception ignored1) { + // shrug, fall back to default behaviour } } + } - // prefer construction via PacketDataSerializer constructor on 1.17 and above - if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { - ConstructorAccessor serializerAccessor = Accessors.getConstructorAccessorOrNull( - packetClassKey, - MinecraftReflection.getPacketDataSerializerClass()); - if (serializerAccessor != null) { - // check if the method is possible - if (tryInitTrickDataSerializer()) { + // prefer construction via PacketDataSerializer constructor on 1.17 and above + if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { + ConstructorAccessor serializerAccessor = Accessors.getConstructorAccessorOrNull( + clazz, + MinecraftReflection.getPacketDataSerializerClass()); + if (serializerAccessor != null) { + // check if the method is possible + if (tryInitTrickDataSerializer()) { + try { + // first try with the base accessor + Object serializer = TRICKED_DATA_SERIALIZER_BASE.get(); + serializerAccessor.invoke(serializer); // throwaway instance, for testing + + // method is working + return () -> serializerAccessor.invoke(serializer); + } catch (Exception ignored) { try { - // first try with the base accessor - Object serializer = TRICKED_DATA_SERIALIZER_BASE.get(); + // try with the json accessor + Object serializer = TRICKED_DATA_SERIALIZER_JSON.get(); serializerAccessor.invoke(serializer); // throwaway instance, for testing // method is working return () -> serializerAccessor.invoke(serializer); - } catch (Exception ignored) { - try { - // try with the json accessor - Object serializer = TRICKED_DATA_SERIALIZER_JSON.get(); - serializerAccessor.invoke(serializer); // throwaway instance, for testing - - // method is working - return () -> serializerAccessor.invoke(serializer); - } catch (Exception ignored1) { - // shrug, fall back to default behaviour - } + } catch (Exception ignored1) { + // shrug, fall back to default behaviour } } } } + } - // try via DefaultInstances as fallback - return () -> { - Object packetInstance = DefaultInstances.DEFAULT.create(packetClassKey); - Objects.requireNonNull(packetInstance, "Unable to create packet instance for class " + packetClassKey + " - " + tryInitTrickDataSerializer() + " - " + streamCodec); - return packetInstance; - }; - }); - return packetConstructor.get(); + // try via DefaultInstances as fallback + return () -> { + Object packetInstance = DefaultInstances.DEFAULT.create(clazz); + Objects.requireNonNull(packetInstance, "Unable to create instance for class " + clazz + " - " + tryInitTrickDataSerializer() + " - " + streamCodec); + return packetInstance; + }; } /** diff --git a/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java b/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java index 24e9611cc..b84fd39a5 100644 --- a/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java +++ b/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java @@ -72,7 +72,7 @@ private static void fromManual(Supplier> getClass, Function MinecraftReflection.getMinecraftItemStack(MinecraftReflection.getBukkitItemStack(source).clone())); - fromWrapper(MinecraftReflection::getDataWatcherClass, WrappedDataWatcher::new); + fromManual(MinecraftReflection::getDataWatcherClass, source -> new WrappedDataWatcher(source).deepClone().getHandle()); fromConverter(MinecraftReflection::getBlockPositionClass, BlockPosition.getConverter()); fromWrapper(MinecraftReflection::getServerPingClass, WrappedServerPing::fromHandle); fromConverter(MinecraftReflection::getMinecraftKeyClass, MinecraftKey.getConverter()); diff --git a/src/main/java/com/comphenix/protocol/reflect/cloning/ImmutableDetector.java b/src/main/java/com/comphenix/protocol/reflect/cloning/ImmutableDetector.java index 6af2e5baa..eb7761d2a 100644 --- a/src/main/java/com/comphenix/protocol/reflect/cloning/ImmutableDetector.java +++ b/src/main/java/com/comphenix/protocol/reflect/cloning/ImmutableDetector.java @@ -62,12 +62,12 @@ public class ImmutableDetector implements Cloner { add(MinecraftReflection::getDataWatcherSerializerClass); add(MinecraftReflection::getBlockClass); add(MinecraftReflection::getItemClass); - add("sounds.SoundEffect", "sounds.SoundEvents", "SoundEffect"); + add(MinecraftReflection::getSoundEffectClass); if (MinecraftVersion.AQUATIC_UPDATE.atOrAbove()) { add(MinecraftReflection::getFluidTypeClass); add(MinecraftReflection::getParticleTypeClass); - add("core.particles.Particle","core.particles.ParticleType", "Particle"); + add(MinecraftReflection::getParticleClass); } if (MinecraftVersion.VILLAGE_UPDATE.atOrAbove()) { diff --git a/src/main/java/com/comphenix/protocol/reflect/instances/PacketCreator.java b/src/main/java/com/comphenix/protocol/reflect/instances/InstanceCreator.java similarity index 74% rename from src/main/java/com/comphenix/protocol/reflect/instances/PacketCreator.java rename to src/main/java/com/comphenix/protocol/reflect/instances/InstanceCreator.java index 3ed885619..c37a8a910 100644 --- a/src/main/java/com/comphenix/protocol/reflect/instances/PacketCreator.java +++ b/src/main/java/com/comphenix/protocol/reflect/instances/InstanceCreator.java @@ -8,30 +8,25 @@ import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; import com.comphenix.protocol.reflect.accessors.MethodAccessor; -import com.comphenix.protocol.utility.MinecraftReflection; -public final class PacketCreator implements Supplier { +public final class InstanceCreator implements Supplier { private ConstructorAccessor constructor = null; private MethodAccessor factoryMethod = null; - private Object[] params = null; + private Class[] paramTypes = null; private boolean failed = false; private final Class type; - private PacketCreator(Class type) { + private InstanceCreator(Class type) { this.type = type; } - public static PacketCreator forPacket(Class type) { + public static InstanceCreator forClass(Class type) { if (type == null) { throw new IllegalArgumentException("Type cannot be null."); } - if (!MinecraftReflection.getPacketClass().isAssignableFrom(type)) { - throw new IllegalArgumentException("Type must be a subclass of Packet."); - } - - return new PacketCreator(type); + return new InstanceCreator(type); } private Object createInstance(Class clazz) { @@ -42,8 +37,18 @@ private Object createInstance(Class clazz) { } } + private Object[] createParams(Class[] paramTypes) { + Object[] params = new Object[paramTypes.length]; + for (int i = 0; i < paramTypes.length; i++) { + params[i] = createInstance(paramTypes[i]); + } + return params; + } + @Override public Object get() { + Object[] params = paramTypes != null ? createParams(paramTypes) : null; + if (constructor != null) { return constructor.invoke(params); } @@ -65,16 +70,13 @@ public Object get() { continue; } - Object[] testParams = new Object[paramTypes.length]; - for (int i = 0; i < paramTypes.length; i++) { - testParams[i] = createInstance(paramTypes[i]); - } + Object[] testParams = createParams(paramTypes); try { result = testCtor.newInstance(testParams); minCount = paramTypes.length; this.constructor = Accessors.getConstructorAccessor(testCtor); - this.params = testParams; + this.paramTypes = paramTypes; } catch (Exception ignored) { } } @@ -100,16 +102,13 @@ public Object get() { continue; } - Object[] testParams = new Object[paramTypes.length]; - for (int i = 0; i < paramTypes.length; i++) { - testParams[i] = createInstance(paramTypes[i]); - } + Object[] testParams = createParams(paramTypes); try { result = testMethod.invoke(null, testParams); minCount = paramTypes.length; this.factoryMethod = Accessors.getMethodAccessor(testMethod); - this.params = testParams; + this.paramTypes = paramTypes; } catch (Exception ignored) { } } diff --git a/src/main/java/com/comphenix/protocol/utility/CachedPackage.java b/src/main/java/com/comphenix/protocol/utility/CachedPackage.java index 5c59bad58..dc4398ef1 100644 --- a/src/main/java/com/comphenix/protocol/utility/CachedPackage.java +++ b/src/main/java/com/comphenix/protocol/utility/CachedPackage.java @@ -64,11 +64,16 @@ public static String combine(String packageName, String className) { * @param clazz - type of class. */ public void setPackageClass(String className, Class clazz) { - if (clazz != null) { - this.cache.put(className, Optional.of(clazz)); - } else { - this.cache.remove(className); + Optional> previous = cache.get(className); + if (previous != null && previous.isPresent()) { + throw new IllegalStateException("Tried to redefine class " + className); } + + cache.put(className, Optional.ofNullable(clazz)); + } + + public void removePackageClass(String className) { + cache.remove(className); } private Optional> resolveClass(String className) { diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index 7bedc2a39..d0a3a22c4 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -32,12 +32,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLogger; import com.comphenix.protocol.injector.BukkitUnwrapper; @@ -54,6 +48,11 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; /** * Methods and constants specifically used in conjuction with reflecting Minecraft object. @@ -117,7 +116,7 @@ public final class MinecraftReflection { private static MethodAccessor asCraftMirror = null; private static MethodAccessor isEmpty = null; - private static boolean isMojangMapped = false; + private static Boolean isMojangMapped = null; private MinecraftReflection() { // No need to make this constructable. @@ -208,9 +207,6 @@ public static String getMinecraftPackage() { } } - // for now, we're going to say that it's Mojang mapped if the nms world was renamed to ServerLevel - isMojangMapped = getNmsWorldClass().getName().contains("ServerLevel"); - return MINECRAFT_FULL_PACKAGE; } catch (NoSuchMethodException exception) { throw new IllegalStateException("Cannot find getHandle() in CraftEntity", exception); @@ -518,7 +514,7 @@ public static Class getEntityPlayerClass() { .getMethodByName("getHandle"); // EntityPlayer is the return type - return setMinecraftClass("EntityPlayer", getHandle.getReturnType()); + return setMinecraftClass("server.level.EntityPlayer", getHandle.getReturnType()); } catch (IllegalArgumentException e1) { throw new RuntimeException("Could not find EntityPlayer class.", e1); } @@ -588,7 +584,7 @@ public static Class getNmsWorldClass() { try { return getMinecraftClass("world.level.World", "world.level.Level", "World"); } catch (RuntimeException e) { - return setMinecraftClass("World", getWorldServerClass().getSuperclass()); + return setMinecraftClass("world.level.World", getWorldServerClass().getSuperclass()); } } @@ -648,7 +644,7 @@ public static boolean isBundlePacket(Class packetClass) { } public static boolean isBundleDelimiter(Class packetClass) { - Class bundleDelimiterClass = getBundleDelimiterClass().orElse(null); + Class bundleDelimiterClass = getBundleDelimiterClass().orElse(null); return bundleDelimiterClass != null && (packetClass.equals(bundleDelimiterClass) || bundleDelimiterClass.isAssignableFrom(packetClass)); } @@ -679,6 +675,18 @@ public static Class getChatSerializerClass() { return getMinecraftClass("network.chat.IChatBaseComponent$ChatSerializer", "network.chat.Component$Serializer", "IChatBaseComponent$ChatSerializer"); } + /** + * Retrieve the component style serializer class. + * + * @return The serializer class. + */ + public static Class getStyleSerializerClass() { + return getMinecraftClass( + "network.chat.Style$Serializer", + "network.chat.ChatModifier$ChatModifierSerializer", + "ChatModifier$ChatModifierSerializer"); + } + /** * Retrieve the ServerPing class. * @@ -719,10 +727,10 @@ public static Class getMinecraftServerClass() { return getMinecraftClass("server.MinecraftServer", "MinecraftServer"); } catch (RuntimeException e) { // Reset cache and try again - setMinecraftClass("MinecraftServer", null); + resetCacheForNMSClass("server.MinecraftServer"); useFallbackServer(); - return getMinecraftClass("MinecraftServer"); + return getMinecraftClass("server.MinecraftServer"); } } @@ -754,10 +762,10 @@ public static Class getPlayerListClass() { return getMinecraftClass("server.players.PlayerList", "PlayerList"); } catch (RuntimeException e) { // Reset cache and try again - setMinecraftClass("PlayerList", null); + resetCacheForNMSClass("server.players.PlayerList"); useFallbackServer(); - return getMinecraftClass("PlayerList"); + return getMinecraftClass("server.players.PlayerList"); } } @@ -789,7 +797,7 @@ public static Class getItemStackClass() { return getMinecraftClass("world.item.ItemStack", "ItemStack"); } catch (RuntimeException e) { // Use the handle reference - return setMinecraftClass("ItemStack", FuzzyReflection.fromClass(getCraftItemStackClass(), true) + return setMinecraftClass("world.item.ItemStack", FuzzyReflection.fromClass(getCraftItemStackClass(), true) .getFieldByName("handle") .getType()); } @@ -1020,6 +1028,78 @@ public static Class getTileEntityClass() { return getMinecraftClass("world.level.block.entity.TileEntity", "world.level.block.entity.BlockEntity", "TileEntity"); } + /** + * Retrieve the NMS team parameters class. + * + * @return The team parameters class. + */ + public static Optional> getTeamParametersClass() { + Optional> clazz = getOptionalNMS( + "network.protocol.game.ClientboundSetPlayerTeamPacket$Parameters", + "network.protocol.game.PacketPlayOutScoreboardTeam$b" + ); + + if (!clazz.isPresent()) { + try { + Class clazz1 = PacketType.Play.Server.SCOREBOARD_TEAM.getPacketClass().getClasses()[0]; + setMinecraftClass("network.protocol.game.ClientboundSetPlayerTeamPacket$Parameters", clazz1); + return Optional.of(clazz1); + } catch (Exception ignored) { + } + } + + return clazz; + } + + /** + * Retrieve the NMS component style class. + * + * @return The component style class. + */ + public static Class getComponentStyleClass() { + return getMinecraftClass( + "network.chat.Style", + "network.chat.ChatModifier", + "ChatModifier" + ); + } + + /** + * Retrieve the NMS NumberFormat class. + * + * @return The NumberFormat class. + */ + public static Optional> getNumberFormatClass() { + return getOptionalNMS("network.chat.numbers.NumberFormat"); + } + + /** + * Retrieve the NMS BlankFormat class. + * + * @return The FixedFormat class. + */ + public static Optional> getBlankFormatClass() { + return getOptionalNMS("network.chat.numbers.BlankFormat"); + } + + /** + * Retrieve the NMS FixedFormat class. + * + * @return The FixedFormat class. + */ + public static Optional> getFixedFormatClass() { + return getOptionalNMS("network.chat.numbers.FixedFormat"); + } + + /** + * Retrieve the NMS StyledFormat class. + * + * @return The StyledFormat class. + */ + public static Optional> getStyledFormatClass() { + return getOptionalNMS("network.chat.numbers.StyledFormat"); + } + /** * Retrieve the Gson class used by Minecraft. * @@ -1105,19 +1185,21 @@ public static Class getCraftMessageClass() { public static Class getPlayerInfoDataClass() { try { return getMinecraftClass( - "network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", - "network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate", - "PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData"); - } catch (Exception ex) { - // todo: ClientboundPlayerInfoUpdatePacket$b, maybe get this via field type + "network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry", + "network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", + "network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate", + "PacketPlayOutPlayerInfo$PlayerInfoData", + "PlayerInfoData" + ); + } catch (Exception ignored) { return setMinecraftClass( - "network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", + "network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry", PacketType.Play.Server.PLAYER_INFO.getPacketClass().getClasses()[0]); } } - static Class getOrInferMinecraftClass(String className, Supplier> supplier) { - return getOptionalNMS(className).orElseGet(() -> { + static Class getOrInferMinecraftClass(String className, Supplier> supplier, String... aliases) { + return getOptionalNMS(className, aliases).orElseGet(() -> { Class clazz = supplier.get(); return setMinecraftClass(className, clazz); }); @@ -1432,16 +1514,22 @@ public static Optional> getOptionalNMS(String className, String... alia * Retrieves a nullable NMS (net.minecraft.server) class. We will attempt to * look up the class and its aliases, but will return null if none is found. * - * @deprecated - Use getOptionalNMS where possible * @param className NMS class name * @param aliases Potential aliases * @return The class, or null if not found */ - @Deprecated public static Class getNullableNMS(String className, String... aliases) { return getOptionalNMS(className, aliases).orElse(null); } + private static void resetCacheForNMSClass(String className) { + if (minecraftPackage == null) { + minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource()); + } + + minecraftPackage.removePackageClass(className); + } + /** * Set the class object for the specific Minecraft class. * @@ -1758,7 +1846,7 @@ public static Class getHolderClass() { } public static Class getCraftServer() { - return getCraftBukkitClass("CraftServer"); + return getCraftBukkitClass("CraftServer"); } public static Class getHolderLookupProviderClass() { @@ -1770,26 +1858,31 @@ public static Class getRegistryAccessClass() { } public static Class getProtocolInfoClass() { - return getMinecraftClass("network.ProtocolInfo"); + return getMinecraftClass("network.ProtocolInfo"); } public static Class getProtocolInfoUnboundClass() { - return getMinecraftClass("network.ProtocolInfo$a" /* Spigot Mappings */, "network.ProtocolInfo$Unbound" /* Mojang Mappings */); + return getMinecraftClass("network.ProtocolInfo$a" /* Spigot Mappings */, "network.ProtocolInfo$Unbound" /* Mojang Mappings */); } public static Class getPacketFlowClass() { - return getMinecraftClass("network.protocol.EnumProtocolDirection" /* Spigot Mappings */, "network.protocol.PacketFlow" /* Mojang Mappings */); + return getMinecraftClass("network.protocol.EnumProtocolDirection" /* Spigot Mappings */, "network.protocol.PacketFlow" /* Mojang Mappings */); } public static Class getStreamCodecClass() { - return getMinecraftClass("network.codec.StreamCodec"); + return getMinecraftClass("network.codec.StreamCodec"); } public static Optional> getRegistryFriendlyByteBufClass() { - return getOptionalNMS("network.RegistryFriendlyByteBuf"); + return getOptionalNMS("network.RegistryFriendlyByteBuf"); } public static boolean isMojangMapped() { + if (isMojangMapped == null) { + String nmsWorldName = getWorldServerClass().getName(); + isMojangMapped = nmsWorldName.contains("ServerLevel"); + } + return isMojangMapped; } } diff --git a/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java b/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java index 2556ba27a..1e030450f 100644 --- a/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java +++ b/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java @@ -1,5 +1,12 @@ package com.comphenix.protocol.utility; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.util.Base64; + import com.comphenix.protocol.injector.netty.NettyByteBufAdapter; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; @@ -8,14 +15,10 @@ import com.comphenix.protocol.wrappers.nbt.NbtCompound; import com.comphenix.protocol.wrappers.nbt.NbtFactory; import com.comphenix.protocol.wrappers.nbt.NbtType; + import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.util.ReferenceCountUtil; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.Base64; import org.bukkit.inventory.ItemStack; /** @@ -148,9 +151,14 @@ public NbtCompound deserializeCompound(DataInputStream input) { */ public void serializeString(DataOutputStream output, String text) { if (WRITE_STRING_METHOD == null) { - WRITE_STRING_METHOD = Accessors.getMethodAccessor(FuzzyReflection - .fromClass(MinecraftReflection.getPacketDataSerializerClass(), true) - .getMethodByParameters("writeString", String.class)); + FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getPacketDataSerializerClass(), false); + WRITE_STRING_METHOD = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder() + .parameterExactType(String.class) + .parameterCount(1) + .returnDerivedOf(ByteBuf.class) + .requireModifier(Modifier.PUBLIC) + .banModifier(Modifier.STATIC) + .build())); } ByteBuf buf = NettyByteBufAdapter.packetWriter(output); diff --git a/src/main/java/com/comphenix/protocol/wrappers/AdventureComponentConverter.java b/src/main/java/com/comphenix/protocol/wrappers/AdventureComponentConverter.java index 97be97270..2ebdc2180 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/AdventureComponentConverter.java +++ b/src/main/java/com/comphenix/protocol/wrappers/AdventureComponentConverter.java @@ -16,7 +16,10 @@ */ package com.comphenix.protocol.wrappers; +import com.comphenix.protocol.utility.MinecraftVersion; +import com.google.gson.JsonObject; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; /** @@ -25,7 +28,16 @@ * Note: The Adventure API Component is not included in CraftBukkit, Bukkit or Spigot and but is present in PaperMC. */ public class AdventureComponentConverter { - + private static final GsonComponentSerializer SERIALIZER; + + static { + if (MinecraftVersion.NETHER_UPDATE.atOrAbove()) { + SERIALIZER = GsonComponentSerializer.gson(); + } else { + SERIALIZER = GsonComponentSerializer.colorDownsamplingGson(); + } + } + private AdventureComponentConverter() { } @@ -35,7 +47,7 @@ private AdventureComponentConverter() { * @return Component */ public static Component fromWrapper(WrappedChatComponent wrapper) { - return GsonComponentSerializer.gson().deserialize(wrapper.getJson()); + return SERIALIZER.deserialize(wrapper.getJson()); } /** @@ -71,11 +83,29 @@ public static Object fromJsonAsObject(final String json) { * @return ProtocolLib wrapper */ public static WrappedChatComponent fromComponent(Component component) { - return WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(component)); + return WrappedChatComponent.fromJson(SERIALIZER.serialize(component)); + } + + /** + * Converts a {@link WrappedComponentStyle} into a {@link Style} + * @param wrapper ProtocolLib wrapper + * @return Style + */ + public static Style fromWrapper(WrappedComponentStyle wrapper) { + return SERIALIZER.serializer().fromJson(wrapper.getJson(), Style.class); + } + + /** + * Converts a {@link Style} into a ProtocolLib wrapper + * @param style Style + * @return ProtocolLib wrapper + */ + public static WrappedComponentStyle fromStyle(Style style) { + return WrappedComponentStyle.fromJson((JsonObject) SERIALIZER.serializer().toJsonTree(style)); } public static Class getComponentClass() { - return Component.class; + return Component.class; } public static Component clone(Object component) { diff --git a/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java b/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java index 7e714fcbe..c0dfb42dd 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java +++ b/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java @@ -16,14 +16,22 @@ */ package com.comphenix.protocol.wrappers; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.wrappers.Either.Left; -import com.comphenix.protocol.wrappers.Either.Right; -import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; import java.lang.ref.WeakReference; -import java.lang.reflect.*; -import java.util.*; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -32,6 +40,7 @@ import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolLogger; import com.comphenix.protocol.ProtocolManager; +import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.injector.PacketConstructor; import com.comphenix.protocol.injector.PacketConstructor.Unwrapper; @@ -47,8 +56,11 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; +import com.comphenix.protocol.wrappers.Either.Left; +import com.comphenix.protocol.wrappers.Either.Right; import com.comphenix.protocol.wrappers.EnumWrappers.Dimension; import com.comphenix.protocol.wrappers.EnumWrappers.FauxEnumConverter; +import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; import com.comphenix.protocol.wrappers.nbt.NbtBase; import com.comphenix.protocol.wrappers.nbt.NbtFactory; @@ -56,7 +68,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; - import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.Sound; @@ -640,6 +651,14 @@ public static EquivalentConverter getWrappedLig return ignoreNull(handle(WrappedLevelChunkData.LightData::getHandle, WrappedLevelChunkData.LightData::new, WrappedLevelChunkData.LightData.class)); } + public static EquivalentConverter getWrappedTeamParametersConverter() { + return ignoreNull(handle(WrappedTeamParameters::getHandle, WrappedTeamParameters::new, WrappedTeamParameters.class)); + } + + public static EquivalentConverter getWrappedNumberFormatConverter() { + return ignoreNull(handle(WrappedNumberFormat::getHandle, WrappedNumberFormat::fromHandle, WrappedNumberFormat.class)); + } + public static EquivalentConverter getPacketContainerConverter() { return ignoreNull(handle(PacketContainer::getHandle, PacketContainer::fromPacket, PacketContainer.class)); } @@ -1108,29 +1127,30 @@ public Vector getSpecific(Object generic) { static MethodAccessor getSoundEffect = null; static FieldAccessor soundKey = null; - static MethodAccessor getSoundEffectBySound = null; - static MethodAccessor getSoundByEffect = null; + static MethodAccessor bukkitToMinecraft = null; + static MethodAccessor minecraftToBukkit = null; static Map soundIndex = null; public static EquivalentConverter getSoundConverter() { // Try to create sound converter for new versions greater 1.16.4 if (MinecraftVersion.NETHER_UPDATE_4.atOrAbove()) { - if (getSoundEffectBySound == null || getSoundByEffect == null) { + if (bukkitToMinecraft == null && minecraftToBukkit == null) { Class craftSound = MinecraftReflection.getCraftSoundClass(); - FuzzyReflection fuzzy = FuzzyReflection.fromClass(craftSound, true); + Class soundEvent = MinecraftReflection.getSoundEffectClass(); + FuzzyReflection fuzzy = FuzzyReflection.fromClass(craftSound, false); - getSoundEffectBySound = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters( - "getSoundEffect", - MinecraftReflection.getSoundEffectClass(), - Sound.class - )); + bukkitToMinecraft = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder() + .returnTypeExact(soundEvent) + .parameterExactArray(Sound.class) + .requireModifier(Modifier.STATIC) + .build())); - getSoundByEffect = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters( - "getBukkit", - Sound.class, - MinecraftReflection.getSoundEffectClass() - )); + minecraftToBukkit = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder() + .returnTypeExact(Sound.class) + .parameterExactArray(soundEvent) + .requireModifier(Modifier.STATIC) + .build())); } return ignoreNull(new EquivalentConverter() { @@ -1142,13 +1162,13 @@ public Class getSpecificType() { @Override public Object getGeneric(Sound specific) { - return getSoundEffectBySound.invoke(null, specific); + return bukkitToMinecraft.invoke(null, specific); } @Override public Sound getSpecific(Object generic) { try { - return (Sound) getSoundByEffect.invoke(null, generic); + return (Sound) minecraftToBukkit.invoke(null, generic); } catch (IllegalStateException ex) { if (ex.getCause() instanceof NullPointerException || ex.getCause() instanceof NoSuchElementException) { // "null" sounds cause NPEs inside getSoundByEffect diff --git a/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java b/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java index f9cab38f5..5d670108b 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java +++ b/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java @@ -1,5 +1,16 @@ package com.comphenix.protocol.wrappers; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType.Protocol; import com.comphenix.protocol.ProtocolLogger; @@ -11,20 +22,11 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; + import org.apache.commons.lang.Validate; +import org.bukkit.ChatColor; import org.bukkit.GameMode; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - /** * Represents a generic enum converter. * @author Kristian @@ -464,6 +466,163 @@ public static Dimension fromId(int id) { } } + /** + * @since 1.20.2 + */ + public enum DisplaySlot { + LIST, + SIDEBAR, + BELOW_NAME, + TEAM_BLACK, + TEAM_DARK_BLUE, + TEAM_DARK_GREEN, + TEAM_DARK_AQUA, + TEAM_DARK_RED, + TEAM_DARK_PURPLE, + TEAM_GOLD, + TEAM_GRAY, + TEAM_DARK_GRAY, + TEAM_BLUE, + TEAM_GREEN, + TEAM_AQUA, + TEAM_RED, + TEAM_LIGHT_PURPLE, + TEAM_YELLOW, + TEAM_WHITE; + } + + public enum RenderType { + INTEGER, + HEARTS + } + + public enum ChatFormatting { + BLACK, + DARK_BLUE, + DARK_GREEN, + DARK_AQUA, + DARK_RED, + DARK_PURPLE, + GOLD, + GRAY, + DARK_GRAY, + BLUE, + GREEN, + AQUA, + RED, + LIGHT_PURPLE, + YELLOW, + WHITE, + OBFUSCATED, + BOLD, + STRIKETHROUGH, + UNDERLINE, + ITALIC, + RESET; + + public ChatColor toBukkit() { + switch (this){ + case BLACK: + return ChatColor.BLACK; + case DARK_BLUE: + return ChatColor.DARK_BLUE; + case DARK_GREEN: + return ChatColor.DARK_GREEN; + case DARK_AQUA: + return ChatColor.DARK_AQUA; + case DARK_RED: + return ChatColor.DARK_RED; + case DARK_PURPLE: + return ChatColor.DARK_PURPLE; + case GOLD: + return ChatColor.GOLD; + case GRAY: + return ChatColor.GRAY; + case DARK_GRAY: + return ChatColor.DARK_GRAY; + case BLUE: + return ChatColor.BLUE; + case GREEN: + return ChatColor.GREEN; + case AQUA: + return ChatColor.AQUA; + case RED: + return ChatColor.RED; + case LIGHT_PURPLE: + return ChatColor.LIGHT_PURPLE; + case YELLOW: + return ChatColor.YELLOW; + case WHITE: + return ChatColor.WHITE; + case OBFUSCATED: + return ChatColor.MAGIC; + case BOLD: + return ChatColor.BOLD; + case STRIKETHROUGH: + return ChatColor.STRIKETHROUGH; + case UNDERLINE: + return ChatColor.UNDERLINE; + case ITALIC: + return ChatColor.ITALIC; + case RESET: + return ChatColor.RESET; + default: + throw new IllegalStateException("Unimplemented Bukkit equivalent for " + name()); + } + } + + public static ChatFormatting fromBukkit(ChatColor color) { + switch (color){ + case BLACK: + return ChatFormatting.BLACK; + case DARK_BLUE: + return ChatFormatting.DARK_BLUE; + case DARK_GREEN: + return ChatFormatting.DARK_GREEN; + case DARK_AQUA: + return ChatFormatting.DARK_AQUA; + case DARK_RED: + return ChatFormatting.DARK_RED; + case DARK_PURPLE: + return ChatFormatting.DARK_PURPLE; + case GOLD: + return ChatFormatting.GOLD; + case GRAY: + return ChatFormatting.GRAY; + case DARK_GRAY: + return ChatFormatting.DARK_GRAY; + case BLUE: + return ChatFormatting.BLUE; + case GREEN: + return ChatFormatting.GREEN; + case AQUA: + return ChatFormatting.AQUA; + case RED: + return ChatFormatting.RED; + case LIGHT_PURPLE: + return ChatFormatting.LIGHT_PURPLE; + case YELLOW: + return ChatFormatting.YELLOW; + case WHITE: + return ChatFormatting.WHITE; + case MAGIC: + return ChatFormatting.OBFUSCATED; + case BOLD: + return ChatFormatting.BOLD; + case STRIKETHROUGH: + return ChatFormatting.STRIKETHROUGH; + case UNDERLINE: + return ChatFormatting.UNDERLINE; + case ITALIC: + return ChatFormatting.ITALIC; + case RESET: + return ChatFormatting.RESET; + default: + throw new IllegalStateException("Unknown ChatColor " + color); + } + } + } + private static Class PROTOCOL_CLASS = null; private static Class CLIENT_COMMAND_CLASS = null; private static Class CHAT_VISIBILITY_CLASS = null; @@ -485,6 +644,9 @@ public static Dimension fromId(int id) { private static Class DIRECTION_CLASS = null; private static Class CHAT_TYPE_CLASS = null; private static Class ENTITY_POSE_CLASS = null; + private static Class DISPLAY_SLOT_CLASS = null; + private static Class RENDER_TYPE_CLASS = null; + private static Class CHAT_FORMATTING_CLASS = null; private static boolean INITIALIZED = false; private static Map, EquivalentConverter> FROM_NATIVE = new HashMap<>(); @@ -574,6 +736,13 @@ private static void initialize() { CHAT_TYPE_CLASS = getEnum(PacketType.Play.Server.CHAT.getPacketClass(), 0); ENTITY_POSE_CLASS = MinecraftReflection.getNullableNMS("world.entity.EntityPose", "world.entity.Pose", "EntityPose"); + DISPLAY_SLOT_CLASS = MinecraftReflection.getNullableNMS("world.scores.DisplaySlot"); + + RENDER_TYPE_CLASS = MinecraftReflection.getNullableNMS( + "world.scores.criteria.ObjectiveCriteria$RenderType", + "world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay", + "IScoreboardCriteria$EnumScoreboardHealthDisplay"); + CHAT_FORMATTING_CLASS = MinecraftReflection.getNullableNMS("ChatFormatting", "EnumChatFormat"); associate(PROTOCOL_CLASS, Protocol.class, getProtocolConverter()); associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter()); @@ -595,6 +764,9 @@ private static void initialize() { associate(CHAT_TYPE_CLASS, ChatType.class, getChatTypeConverter()); associate(HAND_CLASS, Hand.class, getHandConverter()); associate(ENTITY_USE_ACTION_CLASS, EntityUseAction.class, getEntityUseActionConverter()); + associate(DISPLAY_SLOT_CLASS, DisplaySlot.class, getDisplaySlotConverter()); + associate(RENDER_TYPE_CLASS, RenderType.class, getRenderTypeConverter()); + associate(CHAT_FORMATTING_CLASS, ChatFormatting.class, getChatFormattingConverter()); if (ENTITY_POSE_CLASS != null) { associate(ENTITY_POSE_CLASS, EntityPose.class, getEntityPoseConverter()); @@ -746,6 +918,21 @@ public static Class getEntityPoseClass() { return ENTITY_POSE_CLASS; } + public static Class getDisplaySlotClass() { + initialize(); + return DISPLAY_SLOT_CLASS; + } + + public static Class getRenderTypeClass() { + initialize(); + return RENDER_TYPE_CLASS; + } + + public static Class getChatFormattingClass() { + initialize(); + return CHAT_FORMATTING_CLASS; + } + // Get the converters public static EquivalentConverter getProtocolConverter() { return new EnumConverter<>(getProtocolClass(), Protocol.class); @@ -826,7 +1013,19 @@ public static EquivalentConverter getDirectionConverter() { public static EquivalentConverter getChatTypeConverter() { return new EnumConverter<>(getChatTypeClass(), ChatType.class); } - + + public static EquivalentConverter getDisplaySlotConverter() { + return new EnumConverter<>(getDisplaySlotClass(), DisplaySlot.class); + } + + public static EquivalentConverter getRenderTypeConverter() { + return new EnumConverter<>(getRenderTypeClass(), RenderType.class); + } + + public static EquivalentConverter getChatFormattingConverter() { + return new EnumConverter<>(getChatFormattingClass(), ChatFormatting.class); + } + /** * @since 1.13+ * @return {@link EnumConverter} or null (if bellow 1.13 / nms EnumPose class cannot be found) diff --git a/src/main/java/com/comphenix/protocol/wrappers/IDataWatcher.java b/src/main/java/com/comphenix/protocol/wrappers/IDataWatcher.java new file mode 100644 index 000000000..3f93e3477 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/wrappers/IDataWatcher.java @@ -0,0 +1,46 @@ +package com.comphenix.protocol.wrappers; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject; + +import org.bukkit.entity.Entity; + +public interface IDataWatcher extends Iterable { + + Map asMap(); + + Set getIndexes(); + + List getWatchableObjects(); + + int size(); + + WrappedWatchableObject getWatchableObject(int index); + + WrappedWatchableObject remove(int index); + + boolean hasIndex(int index); + + void clear(); + + Object getObject(int index); + + Object getObject(WrappedDataWatcherObject object); + + void setObject(WrappedDataWatcherObject object, WrappedWatchableObject value, boolean update); + + void setObject(WrappedDataWatcherObject object, Object value, boolean update); + + IDataWatcher deepClone(); + + Object getHandle(); + + @Deprecated + Entity getEntity(); + + @Deprecated + void setEntity(Entity entity); +} diff --git a/src/main/java/com/comphenix/protocol/wrappers/InMemoryDataWatcher.java b/src/main/java/com/comphenix/protocol/wrappers/InMemoryDataWatcher.java new file mode 100644 index 000000000..4a8230179 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/wrappers/InMemoryDataWatcher.java @@ -0,0 +1,296 @@ +package com.comphenix.protocol.wrappers; + +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.comphenix.protocol.injector.BukkitUnwrapper; +import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.MinecraftVersion; +import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class InMemoryDataWatcher implements IDataWatcher { + private Object entityHandle; + private Map entries = new HashMap<>(); + + public InMemoryDataWatcher() { + + } + + public InMemoryDataWatcher(Object handle) { + this.entityHandle = entityFromWatcherHandle(handle); + this.populateFromHandle(handle); + } + + public InMemoryDataWatcher(Entity entity) { + this.entityHandle = BukkitUnwrapper.getInstance().unwrapItem(entity); + this.populateFromEntity(entity); + } + + public InMemoryDataWatcher(List objects) { + for (WrappedWatchableObject obj : objects) { + entries.put(obj.getIndex(), obj); + } + } + + private static FieldAccessor ENTITY_WATCHER_FIELD; + private static FieldAccessor WATCHER_ENTITY_FIELD; + private static FieldAccessor ARRAY_FIELD; + private static ConstructorAccessor CONSTRUCTOR; + + private static boolean ARRAY_BACKED = MinecraftVersion.v1_20_5.atOrAbove(); + + private static Class SYNCED_DATA_HOLDER_CLASS = ARRAY_BACKED + ? MinecraftReflection.getMinecraftClass("network.syncher.SyncedDataHolder") + : MinecraftReflection.getEntityClass(); + + public static WrappedDataWatcher getEntityWatcher(Entity entity) { + return new WrappedDataWatcher(entity); + } + + private static Object entityFromWatcherHandle(Object handle) { + if (WATCHER_ENTITY_FIELD == null) { + WATCHER_ENTITY_FIELD = Accessors.getFieldAccessor(MinecraftReflection.getDataWatcherClass(), SYNCED_DATA_HOLDER_CLASS, true); + } + + return WATCHER_ENTITY_FIELD.get(handle); + } + + public void populateFromEntity(Entity entity) { + populateFromEntity(BukkitUnwrapper.getInstance().unwrapItem(entity)); + } + + public void applyToEntity(Entity entity) { + applyToEntity(BukkitUnwrapper.getInstance().unwrapItem(entity)); + } + + private void applyToEntity(Object entityHandle) { + if (ENTITY_WATCHER_FIELD == null) { + ENTITY_WATCHER_FIELD = Accessors.getFieldAccessor(MinecraftReflection.getEntityClass(), MinecraftReflection.getDataWatcherClass(), true); + } + + Object handle = getHandle(entityHandle); + ENTITY_WATCHER_FIELD.set(entityHandle, handle); + } + + private void populateFromHandle(Object handle) { + if (ARRAY_FIELD == null) { + try { + FuzzyReflection fuzzy = FuzzyReflection.fromClass(handle.getClass(), true); + ARRAY_FIELD = Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract + .newBuilder() + .banModifier(Modifier.STATIC) + .typeDerivedOf(Object[].class) + .build())); + } catch (IllegalArgumentException ex) { + throw new FieldAccessException("Failed to find watchable object array", ex); + } + } + + Object[] backing = (Object[]) ARRAY_FIELD.get(handle); + for (Object itemHandle : backing) { + if (itemHandle == null) { + continue; + } + + WrappedWatchableObject object = new WrappedWatchableObject(itemHandle); + entries.put(object.getIndex(), object); + } + } + + private void populateFromEntity(Object entityHandle) { + if (ENTITY_WATCHER_FIELD == null) { + ENTITY_WATCHER_FIELD = Accessors.getFieldAccessor(MinecraftReflection.getEntityClass(), MinecraftReflection.getDataWatcherClass(), true); + } + + Object handle = ENTITY_WATCHER_FIELD.get(entityHandle); + populateFromHandle(handle); + } + + public Object getHandle() { + return getHandle(entityHandle); + } + + public Object getHandle(Object entityHandle) { + if (CONSTRUCTOR == null) { + CONSTRUCTOR = Accessors.getConstructorAccessor(MinecraftReflection.getDataWatcherClass(), + SYNCED_DATA_HOLDER_CLASS, MinecraftReflection.getArrayClass(MinecraftReflection.getDataWatcherItemClass())); + } + + if (CONSTRUCTOR == null) { + throw new IllegalStateException("Cannot find constructor for DataWatcher."); + } + + Object[] items = new Object[entries.size()]; + for (int i = 0; i < items.length; i++) { + items[i] = entries.get(i).getHandle(); + } + + return CONSTRUCTOR.invoke(null, entityHandle, items); + } + + /** + * @return + */ + @Override + public IDataWatcher deepClone() { + InMemoryDataWatcher clone = new InMemoryDataWatcher(); + clone.entries = new HashMap<>(this.entries.size()); + clone.entityHandle = this.entityHandle; + + for (WrappedWatchableObject object : this) { + clone.setObject(object.getWatcherObject(), object.getValue(), false); + } + + return clone; + } + + /** + * @return + */ + @Override + @Nullable + @Deprecated + public Entity getEntity() { + return entityHandle != null ? (Entity) MinecraftReflection.getBukkitEntity(entityHandle) : null; + } + + /** + * @param entity + */ + @Override + @Deprecated + public void setEntity(Entity entity) { + this.entityHandle = BukkitUnwrapper.getInstance().unwrapItem(entity); + } + + /** + * @return + */ + @Override + public Map asMap() { + return ImmutableMap.copyOf(entries); + } + + /** + * @return + */ + @Override + public Set getIndexes() { + return entries.keySet(); + } + + /** + * @return + */ + @Override + public List getWatchableObjects() { + return Lists.newArrayList(entries.values()); + } + + /** + * @return + */ + @Override + public int size() { + return entries.size(); + } + + /** + * @param index + * @return + */ + @Override + public WrappedWatchableObject getWatchableObject(int index) { + return entries.get(index); + } + + /** + * @param index + * @return + */ + @Override + public WrappedWatchableObject remove(int index) { + return entries.remove(index); + } + + /** + * @param index + * @return + */ + @Override + public boolean hasIndex(int index) { + return entries.containsKey(index); + } + + /** + * + */ + @Override + public void clear() { + entries.clear(); + } + + @Override + public Object getObject(int index) { + WrappedWatchableObject obj = getWatchableObject(index); + return obj != null ? obj.getValue() : null; + } + + /** + * @param object + * @return + */ + @Override + public Object getObject(WrappedDataWatcherObject object) { + return getObject(object.getIndex()); + } + + /** + * @param object + * @param value + * @param update + */ + @Override + public void setObject(WrappedDataWatcherObject object, WrappedWatchableObject value, boolean update) { + entries.put(object.getIndex(), value); + + if (update) { + value.setDirtyState(true); + } + } + + /** + * @param object + * @param value + * @param update + */ + @Override + public void setObject(WrappedDataWatcherObject object, Object value, boolean update) { + setObject(object, new WrappedWatchableObject(object, value), update); + } + + /** + * @return + */ + @NotNull + @Override + public Iterator iterator() { + return entries.values().iterator(); + } +} diff --git a/src/main/java/com/comphenix/protocol/wrappers/LegacyDataWatcher.java b/src/main/java/com/comphenix/protocol/wrappers/LegacyDataWatcher.java new file mode 100644 index 000000000..7e7baf808 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/wrappers/LegacyDataWatcher.java @@ -0,0 +1,469 @@ +/** + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2018 dmulloy2 + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ +package com.comphenix.protocol.wrappers; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.comphenix.protocol.injector.BukkitUnwrapper; +import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; +import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject; +import com.comphenix.protocol.wrappers.collection.ConvertedMap; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; + +/** + * Represents a DataWatcher + * @author dmulloy2 + */ +public class LegacyDataWatcher extends AbstractWrapper implements IDataWatcher { + private static final Class HANDLE_TYPE = MinecraftReflection.getDataWatcherClass(); + + private static MethodAccessor GETTER = null; + private static MethodAccessor SETTER = null; + private static MethodAccessor REGISTER = null; + + private static FieldAccessor ENTITY_DATA_FIELD = null; + private static FieldAccessor ENTITY_FIELD = null; + private static FieldAccessor MAP_FIELD = null; + + private static ConstructorAccessor constructor = null; + private static ConstructorAccessor eggConstructor = null; + + private static Object fakeEntity = null; + + // ---- Construction + + /** + * Constructs a new DataWatcher wrapper around a NMS handle. The resulting + * DataWatcher will likely have existing values that can be removed with + * {@link #clear()}. + * + * @param handle DataWatcher handle + */ + public LegacyDataWatcher(Object handle) { + super(HANDLE_TYPE); + setHandle(handle); + } + + /** + * Constructs a new DataWatcher using a fake egg entity. The + * resulting DataWatcher will not have any keys or values and new ones will + * have to be added using watcher objects. + */ + @Deprecated + public LegacyDataWatcher() { + this(new ArrayList<>()); + } + + /** + * Constructs a new DataWatcher using a real entity. The resulting + * DataWatcher will not have any keys or values and new ones will have to + * be added using watcher objects. + * + * @param entity The entity + */ + @Deprecated + public LegacyDataWatcher(Entity entity) { + this(getHandleFromEntity(entity)); + } + + /** + * Constructs a new DataWatcher using a fake egg entity and a given + * list of watchable objects. + * + * @param objects The list of objects + */ + @Deprecated + public LegacyDataWatcher(List objects) { + this(newHandle(fakeEntity(), objects)); + } + + private static Object newHandle(Object entity, List objects) { + if (constructor == null) { + constructor = Accessors.getConstructorAccessor(HANDLE_TYPE, MinecraftReflection.getEntityClass(), + MinecraftReflection.getArrayClass(MinecraftReflection.getDataWatcherItemClass())); + } + + Object[] genericItems = new Object[0];// (Object[]) ITEMS_CONVERTER.getGeneric(objects); + return constructor.invoke(entity, genericItems); + } + + private static Object fakeEntity() { + if (fakeEntity != null) { + return fakeEntity; + } + + // We can create a fake egg without it affecting anything + // Mojang added difficulty to lightning strikes, so this'll have to do + if (eggConstructor == null) { + eggConstructor = Accessors.getConstructorAccessor( + MinecraftReflection.getMinecraftClass("world.entity.projectile.EntityEgg", "world.entity.projectile.ThrownEgg", "EntityEgg"), + MinecraftReflection.getNmsWorldClass(), double.class, double.class, double.class + ); + } + + Object world = BukkitUnwrapper.getInstance().unwrapItem(Bukkit.getWorlds().get(0)); + return fakeEntity = eggConstructor.invoke(world, 0, 0, 0); + } + + // ---- Collection Methods + + @SuppressWarnings("unchecked") + @Deprecated + private Map getMap() { + if (MAP_FIELD == null) { + try { + FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType, true); + MAP_FIELD = Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract + .newBuilder() + .banModifier(Modifier.STATIC) + .typeDerivedOf(Map.class) + .build())); + } catch (IllegalArgumentException ex) { + throw new FieldAccessException("Failed to find watchable object map", ex); + } + } + + return (Map) MAP_FIELD.get(handle); + } + + /** + * Gets the contents of this DataWatcher as a map. + * @return The contents + */ + @Deprecated + public Map asMap() { + Map backingMap = getMap(); + + return new ConvertedMap(backingMap) { + @Override + protected WrappedWatchableObject toOuter(Object inner) { + return inner != null ? new WrappedWatchableObject(inner) : null; + } + + @Override + protected Object toInner(WrappedWatchableObject outer) { + return outer != null ? outer.getHandle() : null; + } + }; + } + + /** + * Gets a set containing the registered indexes. + * @return The set + */ + @Deprecated + public Set getIndexes() { + return getMap().keySet(); + } + + /** + * Gets a list of the contents of this DataWatcher. + * @return The contents + */ + @SuppressWarnings("unchecked") + public List getWatchableObjects() { + return new ArrayList<>(asMap().values()); + } + + @Override + public Iterator iterator() { + return getWatchableObjects().iterator(); + } + + /** + * Gets the size of this DataWatcher's contents. + * @return The size + */ + public int size() { + return getMap().size(); + } + + /** + * Gets the item at a given index. + * + * @param index Index to get + * @return The watchable object, or null if none exists + */ + public WrappedWatchableObject getWatchableObject(int index) { + Object handle = getMap().get(index); + + if (handle != null) { + return new WrappedWatchableObject(handle); + } else { + return null; + } + } + + /** + * @deprecated Renamed to {@link #remove(int)} + */ + @Deprecated + public WrappedWatchableObject removeObject(int index) { + return remove(index); + } + + /** + * Removes the item at a given index. + * + * @param index Index to remove + * @return The previous value, or null if none existed + */ + public WrappedWatchableObject remove(int index) { + Object removed = getMap().remove(index); + return removed != null ? new WrappedWatchableObject(removed) : null; + } + + /** + * Whether or not this DataWatcher has an object at a given index. + * + * @param index Index to check for + * @return True if it does, false if not + */ + public boolean hasIndex(int index) { + return getMap().containsKey(index); + } + + /** + * Returns a set containing all the registered indexes + * @return The set + */ + @Deprecated + public Set indexSet() { + return getIndexes(); + } + + /** + * Clears the contents of this DataWatcher. The watcher will be empty after + * this operation is called. + */ + @Deprecated + public void clear() { + getMap().clear(); + } + + /** + * Retrieve a watchable object by index. + * + * @param index Index of the object to retrieve. + * @return The watched object or null if it doesn't exist. + */ + public Object getObject(int index) { + return getObject(WrappedDataWatcherObject.fromIndex(index)); + } + + /** + * Retrieve a watchable object by watcher object. + * + * @param object The watcher object + * @return The watched object or null if it doesn't exist. + */ + public Object getObject(WrappedDataWatcherObject object) { + Validate.notNull(object, "Watcher object cannot be null!"); + + if (GETTER == null) { + FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType, true); + + if (MinecraftReflection.watcherObjectExists()) { + GETTER = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder() + .parameterExactType(object.getHandleType()) + .returnTypeExact(Object.class) + .build(), "get")); + } else { + GETTER = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder() + .parameterExactType(int.class) + .returnTypeExact(MinecraftReflection.getDataWatcherItemClass()) + .build())); + } + } + + try { + Object value = GETTER.invoke(handle, object.getHandle()); + return WrappedWatchableObject.getWrapped(value); + } catch (Exception ex) { + // Nothing exists at this index + return null; + } + } + + /** + * @param object + * @param value + * @param update + */ + @Override + public void setObject(WrappedDataWatcherObject object, WrappedWatchableObject value, boolean update) { + setObject(object, value.getRawValue(), update); + } + + /** + * Sets the DataWatcher Item associated with a given watcher object to a + * new value. If there is not already an object at this index, the + * specified watcher object must have a serializer. + * + * @param object Associated watcher object + * @param value New value + * + * @throws IllegalArgumentException If the watcher object is null or must + * have a serializer and does not have one. + */ + public void setObject(WrappedDataWatcherObject object, Object value, boolean update) { + Validate.notNull(object, "Watcher object cannot be null!"); + + if (SETTER == null && REGISTER == null) { + FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType, true); + FuzzyMethodContract contract = FuzzyMethodContract.newBuilder() + .banModifier(Modifier.STATIC) + .requireModifier(Modifier.PUBLIC) + .parameterExactArray(object.getHandleType(), Object.class) + .build(); + List methods = fuzzy.getMethodList(contract); + for (Method method : methods) { + if (method.getName().equals("set") || method.getName().equals("watch") || method.getName().equals("b")) { + SETTER = Accessors.getMethodAccessor(method); + } else { + REGISTER = Accessors.getMethodAccessor(method); + } + } + } + + // Unwrap the object + value = WrappedWatchableObject.getUnwrapped(value); + + if (hasIndex(object.getIndex())) { + SETTER.invoke(handle, object.getHandle(), value); + } else { + object.checkSerializer(); + REGISTER.invoke(handle, object.getHandle(), value); + } + + if (update) { + getWatchableObject(object.getIndex()).setDirtyState(update); + } + } + + // ---- Utility Methods + + /** + * Clone the content of the current DataWatcher. + * + * @return A cloned data watcher. + */ + public IDataWatcher deepClone() { + LegacyDataWatcher clone = new LegacyDataWatcher(getEntity()); + for (WrappedWatchableObject wrapper : this) { + clone.setObject(wrapper.getWatcherObject(), wrapper, false); + } + + return clone; + } + + private static Object getHandleFromEntity(Entity entity) { + if (ENTITY_DATA_FIELD == null) { + ENTITY_DATA_FIELD = Accessors.getFieldAccessor(MinecraftReflection.getEntityClass(), MinecraftReflection.getDataWatcherClass(), true); + } + + BukkitUnwrapper unwrapper = new BukkitUnwrapper(); + Object handle = ENTITY_DATA_FIELD.get(unwrapper.unwrapItem(entity)); + return handle; + } + + private Object getEntityHandle() { + if (ENTITY_FIELD == null) { + ENTITY_FIELD = Accessors.getFieldAccessor(HANDLE_TYPE, MinecraftReflection.getEntityClass(), true); + } + + Object entity = ENTITY_FIELD.get(handle); + if (entity == null) { + throw new NullPointerException(handle + "." + ENTITY_FIELD); + } + + return entity; + } + + /** + * Retrieve the entity associated with this data watcher. + * @return The entity, or NULL. + */ + public Entity getEntity() { + Object entity = getEntityHandle(); + return (Entity) MinecraftReflection.getBukkitEntity(entity); + } + + /** + * Set the entity associated with this data watcher. + * @param entity - the new entity. + */ + public void setEntity(Entity entity) { + if (ENTITY_FIELD == null) { + ENTITY_FIELD = Accessors.getFieldAccessor(HANDLE_TYPE, MinecraftReflection.getEntityClass(), true); + } + + ENTITY_FIELD.set(handle, BukkitUnwrapper.getInstance().unwrapItem(entity)); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null) return false; + + if (obj instanceof LegacyDataWatcher) { + LegacyDataWatcher other = (LegacyDataWatcher) obj; + Iterator first = iterator(), second = other.iterator(); + + // Make sure they're the same size + if (size() != other.size()) + return false; + + for (; first.hasNext() && second.hasNext();) { + // See if the two elements are equal + if (!first.next().equals(second.next())) + return false; + } + + return true; + } + + return false; + } + + @Override + public int hashCode() { + return getWatchableObjects().hashCode(); + } + + @Override + public String toString() { + return "LegacyDataWatcher[handle=" + handle + "]"; + } +} diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedAttribute.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedAttribute.java index 5667766a5..9ed33baf5 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedAttribute.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedAttribute.java @@ -26,7 +26,7 @@ public class WrappedAttribute extends AbstractWrapper { public static boolean KEY_WRAPPED = MinecraftVersion.NETHER_UPDATE.atOrAbove(); public static boolean IS_STATIC = MinecraftVersion.CAVES_CLIFFS_1.atOrAbove(); - public static boolean IS_IN_HOLDER = MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove(); + public static boolean IS_IN_HOLDER = MinecraftVersion.v1_20_5.atOrAbove(); // Shared structure modifier private static StructureModifier ATTRIBUTE_MODIFIER; diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedComponentStyle.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedComponentStyle.java new file mode 100644 index 000000000..f463a86fa --- /dev/null +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedComponentStyle.java @@ -0,0 +1,59 @@ +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.MinecraftVersion; +import com.comphenix.protocol.wrappers.codecs.WrappedCodec; +import com.comphenix.protocol.wrappers.codecs.WrappedDynamicOps; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +/** + * A wrapper around the component style NMS class. + * + * @author vytskalt + */ +public class WrappedComponentStyle extends AbstractWrapper { + private static final WrappedCodec CODEC; // 1.20.4+ + private static final Gson GSON; // Below 1.20.4 + + static { + if (MinecraftVersion.v1_20_4.atOrAbove()) { + FuzzyReflection fuzzySerializer = FuzzyReflection.fromClass(MinecraftReflection.getStyleSerializerClass(), true); + Object codec = Accessors.getFieldAccessor(fuzzySerializer.getFieldByType("CODEC", MinecraftReflection.getCodecClass())).get(null); + CODEC = WrappedCodec.fromHandle(codec); + GSON = null; + } else { + FuzzyReflection fuzzySerializer = FuzzyReflection.fromClass(MinecraftReflection.getChatSerializerClass(), true); + CODEC = null; + GSON = (Gson) Accessors.getFieldAccessor(fuzzySerializer.getFieldByType("gson", Gson.class)).get(null); + } + } + + public WrappedComponentStyle(Object handle) { + super(MinecraftReflection.getComponentStyleClass()); + setHandle(handle); + } + + public JsonElement getJson() { + if (CODEC != null) { + return (JsonElement) CODEC.encode(handle, WrappedDynamicOps.json(false)) + .getOrThrow(JsonParseException::new); + } else { + return GSON.toJsonTree(handle); + } + } + + public static WrappedComponentStyle fromJson(JsonElement json) { + Object handle; + if (CODEC != null) { + handle = CODEC.parse(json, WrappedDynamicOps.json(false)) + .getOrThrow(JsonParseException::new); + } else { + handle = GSON.fromJson(json, MinecraftReflection.getComponentStyleClass()); + } + return new WrappedComponentStyle(handle); + } +} diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedDataValue.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedDataValue.java index a4421be2a..5eba95e7e 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedDataValue.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedDataValue.java @@ -13,7 +13,12 @@ */ public class WrappedDataValue extends AbstractWrapper { - private static final Class HANDLE_TYPE = MinecraftReflection.getNullableNMS("network.syncher.DataWatcher$b", "network.syncher.SynchedEntityData$DataValue"); + // TODO: need a better solution + private static final Class HANDLE_TYPE = MinecraftReflection.getNullableNMS( + "network.syncher.SynchedEntityData$DataValue", + "network.syncher.DataWatcher$b", + "network.syncher.DataWatcher$c" + ); private static ConstructorAccessor constructor; diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java index 325aff256..ca7921f9f 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java @@ -16,61 +16,52 @@ */ package com.comphenix.protocol.wrappers; -import java.lang.reflect.*; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import com.comphenix.protocol.injector.BukkitUnwrapper; -import com.comphenix.protocol.reflect.EquivalentConverter; -import com.comphenix.protocol.reflect.FieldAccessException; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; -import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.reflect.accessors.MethodAccessor; -import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; -import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; -import com.comphenix.protocol.wrappers.collection.ConvertedMap; -import com.google.common.base.Optional; -import com.google.common.collect.ImmutableBiMap; +import com.comphenix.protocol.wrappers.EnumWrappers.Direction; +import com.comphenix.protocol.wrappers.EnumWrappers.EntityPose; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableMap; import org.apache.commons.lang.Validate; -import org.bukkit.Bukkit; import org.bukkit.entity.Entity; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; /** * Represents a DataWatcher * @author dmulloy2 */ -public class WrappedDataWatcher extends AbstractWrapper implements Iterable, ClonableWrapper { - private static final Class HANDLE_TYPE = MinecraftReflection.getDataWatcherClass(); - private static final boolean ARRAY_BACKED = MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove(); - - private static Class SYNCHED_DATA_HOLDER_CLASS = ARRAY_BACKED - ? MinecraftReflection.getMinecraftClass("network.syncher.SyncedDataHolder") - : MinecraftReflection.getEntityClass(); +public class WrappedDataWatcher implements IDataWatcher { + private static final boolean IN_MEMORY = MinecraftVersion.v1_20_4.atOrAbove(); - private static MethodAccessor GETTER = null; - private static MethodAccessor SETTER = null; - private static MethodAccessor REGISTER = null; - - private static FieldAccessor ENTITY_DATA_FIELD = null; - private static FieldAccessor ENTITY_FIELD = null; - private static FieldAccessor MAP_FIELD = null; - private static FieldAccessor ARRAY_FIELD = null; - - private static ConstructorAccessor constructor = null; - private static ConstructorAccessor eggConstructor = null; - - private static Object fakeEntity = null; + @NotNull + private final IDataWatcher impl; // ---- Construction + private WrappedDataWatcher(IDataWatcher impl) { + this.impl = impl; + } + /** * Constructs a new DataWatcher wrapper around a NMS handle. The resulting * DataWatcher will likely have existing values that can be removed with @@ -79,8 +70,9 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable()); + this.impl = IN_MEMORY + ? new InMemoryDataWatcher() + : new LegacyDataWatcher(); } /** @@ -100,9 +93,10 @@ public WrappedDataWatcher() { * * @param entity The entity */ - @Deprecated public WrappedDataWatcher(Entity entity) { - this(getHandleFromEntity(entity)); + this.impl = IN_MEMORY + ? new InMemoryDataWatcher(entity) + : new LegacyDataWatcher(entity); } /** @@ -111,138 +105,18 @@ public WrappedDataWatcher(Entity entity) { * * @param objects The list of objects */ - @Deprecated public WrappedDataWatcher(List objects) { - this(newHandle(fakeEntity(), objects)); - } - - private static final EquivalentConverter> ITEMS_CONVERTER = new EquivalentConverter>() { - @Override - public Object getGeneric(List specific) { - Object[] generic = (Object[]) Array.newInstance(MinecraftReflection.getDataWatcherItemClass(), specific.size()); - for (int i = 0; i < specific.size(); i++) { - generic[i] = specific.get(i).getHandle(); - } - return generic; - } - - @Override - public List getSpecific(Object generic) { - Object[] genericArray = (Object[]) generic; - List specific = new ArrayList<>(genericArray.length); - for (Object genericObj : genericArray) { - specific.add(new WrappedWatchableObject(genericObj)); - } - return specific; - } - - @Override - @SuppressWarnings("unchecked") - public Class> getSpecificType() { - Class dummy = List.class; - return (Class>) dummy; - } - }; - - public static WrappedDataWatcher create(Entity entity, List objects) { - return new WrappedDataWatcher(newHandle(entity, objects)); - } - - private static Object newHandle(Object entity, List objects) { - if (constructor == null) { - constructor = Accessors.getConstructorAccessor(HANDLE_TYPE, SYNCHED_DATA_HOLDER_CLASS, - MinecraftReflection.getArrayClass(MinecraftReflection.getDataWatcherItemClass())); - } - - Object[] genericItems = (Object[]) ITEMS_CONVERTER.getGeneric(objects); - return constructor.invoke(entity, genericItems); - } - - private static Object fakeEntity() { - if (fakeEntity != null) { - return fakeEntity; - } - - // We can create a fake egg without it affecting anything - // Mojang added difficulty to lightning strikes, so this'll have to do - if (eggConstructor == null) { - eggConstructor = Accessors.getConstructorAccessor( - MinecraftReflection.getMinecraftClass("world.entity.projectile.EntityEgg", "world.entity.projectile.ThrownEgg", "EntityEgg"), - MinecraftReflection.getNmsWorldClass(), double.class, double.class, double.class - ); - } - - Object world = BukkitUnwrapper.getInstance().unwrapItem(Bukkit.getWorlds().get(0)); - return fakeEntity = eggConstructor.invoke(world, 0, 0, 0); - } - - // ---- Collection Methods - - @SuppressWarnings("unchecked") - @Deprecated - private Map getMap() { - if (MAP_FIELD == null) { - try { - FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType, true); - MAP_FIELD = Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract - .newBuilder() - .banModifier(Modifier.STATIC) - .typeDerivedOf(Map.class) - .build())); - } catch (IllegalArgumentException ex) { - throw new FieldAccessException("Failed to find watchable object map", ex); - } - } - - return (Map) MAP_FIELD.get(handle); - } - - private Object[] getBackingArray() { - if (ARRAY_FIELD == null) { - try { - FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType, true); - ARRAY_FIELD = Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract - .newBuilder() - .banModifier(Modifier.STATIC) - .typeDerivedOf(Object[].class) - .build())); - } catch (IllegalArgumentException ex) { - throw new FieldAccessException("Failed to find watchable object array", ex); - } - } - - Object[] backing = (Object[]) ARRAY_FIELD.get(handle); - return backing; + this.impl = IN_MEMORY + ? new InMemoryDataWatcher(objects) + : new LegacyDataWatcher(objects); } /** * Gets the contents of this DataWatcher as a map. * @return The contents */ - @Deprecated public Map asMap() { - Map backingMap; - if (ARRAY_BACKED) { - Object[] backingArray = getBackingArray(); - backingMap = new HashMap<>(backingArray.length); - for (int i = 0; i < backingArray.length; i++) { - backingMap.put(i, backingArray[i]); - } - } else { - backingMap = getMap(); - } - - return new ConvertedMap(backingMap) { - @Override - protected WrappedWatchableObject toOuter(Object inner) { - return inner != null ? new WrappedWatchableObject(inner) : null; - } - - @Override - protected Object toInner(WrappedWatchableObject outer) { - return outer != null ? outer.getHandle() : null; - } - }; + return impl.asMap(); } /** @@ -251,11 +125,7 @@ protected Object toInner(WrappedWatchableObject outer) { */ @Deprecated public Set getIndexes() { - if (ARRAY_BACKED) { - return IntStream.range(0, size()).boxed().collect(Collectors.toSet()); - } - - return getMap().keySet(); + return impl.getIndexes(); } /** @@ -264,16 +134,12 @@ public Set getIndexes() { */ @SuppressWarnings("unchecked") public List getWatchableObjects() { - if (ARRAY_BACKED) { - return (List)ITEMS_CONVERTER.getSpecific(getBackingArray()); - } - - return new ArrayList<>(asMap().values()); + return impl.getWatchableObjects(); } @Override public Iterator iterator() { - return getWatchableObjects().iterator(); + return impl.iterator(); } /** @@ -281,11 +147,7 @@ public Iterator iterator() { * @return The size */ public int size() { - if (ARRAY_BACKED) { - return getBackingArray().length; - } - - return getMap().size(); + return impl.size(); } /** @@ -295,15 +157,7 @@ public int size() { * @return The watchable object, or null if none exists */ public WrappedWatchableObject getWatchableObject(int index) { - Object handle = ARRAY_BACKED - ? getBackingArray()[index] - : getMap().get(index); - - if (handle != null) { - return new WrappedWatchableObject(handle); - } else { - return null; - } + return impl.getWatchableObject(index); } /** @@ -321,8 +175,7 @@ public WrappedWatchableObject removeObject(int index) { * @return The previous value, or null if none existed */ public WrappedWatchableObject remove(int index) { - Object removed = getMap().remove(index); - return removed != null ? new WrappedWatchableObject(removed) : null; + return impl.remove(index); } /** @@ -332,11 +185,7 @@ public WrappedWatchableObject remove(int index) { * @return True if it does, false if not */ public boolean hasIndex(int index) { - if (ARRAY_BACKED) { - return index < size(); - } - - return getMap().containsKey(index); + return impl.hasIndex(index); } /** @@ -352,12 +201,11 @@ public Set indexSet() { * Clears the contents of this DataWatcher. The watcher will be empty after * this operation is called. */ - @Deprecated public void clear() { - getMap().clear(); + impl.clear(); } - // ---- Object Getters + // ---- 0: byte /** * Get a watched byte. @@ -369,6 +217,12 @@ public Byte getByte(int index) { return (Byte) getObject(index); } + public void setByte(int index, byte value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(Byte.class)), value, update); + } + + // ---- short (unused) + /** * Get a watched short. * @@ -379,6 +233,8 @@ public Short getShort(int index) { return (Short) getObject(index); } + // ---- 1: varint (integer) + /** * Get a watched integer. * @@ -389,6 +245,22 @@ public Integer getInteger(int index) { return (Integer) getObject(index); } + public void setInteger(int index, Integer value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(Integer.class)), value, update); + } + + // ---- 2: varlong (long) + + public Long getLong(int index) { + return (Long) getObject(index); + } + + public void setLong(int index, Long value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(Long.class)), value, update); + } + + // ---- 3: float + /** * Get a watched float. * @@ -399,6 +271,12 @@ public Float getFloat(int index) { return (Float) getObject(index); } + public void setFloat(int index, Float value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(Float.class)), value, update); + } + + // ---- 4: string + /** * Get a watched string. * @@ -409,6 +287,32 @@ public String getString(int index) { return (String) getObject(index); } + public void setString(int index, String value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(String.class)), value, update); + } + + // ---- 5: text component + + public WrappedChatComponent getChatComponent(int index) { + return (WrappedChatComponent) getObject(index); + } + + public void setChatComponent(int index, WrappedChatComponent value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getChatComponentSerializer()), value, update); + } + + // ---- 6: optional text component + + public Optional getOptionalChatComponent(int index) { + return (Optional) getObject(index); + } + + public void setOptionalChatComponent(int index, Optional value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getChatComponentSerializer(true)), value, update); + } + + // ---- 7: slot (item stack) + /** * Get a watched string. * @@ -419,6 +323,158 @@ public ItemStack getItemStack(int index) { return (ItemStack) getObject(index); } + public void setItemStack(int index, ItemStack itemStack, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getItemStackSerializer(false)), itemStack, update); + } + + // ---- 8: boolean + + public Boolean getBoolean(int index) { + return (Boolean) getObject(index); + } + + public void setBoolean(int index, Boolean value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(Boolean.class)), value, update); + } + + // ---- 9: rotations + // TODO + + // ---- 10: position + + public BlockPosition getPosition(int index) { + return (BlockPosition) getObject(index); + } + + public void setPosition(int index, BlockPosition position, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getBlockPositionSerializer(false)), position, update); + } + + // ---- 11: optional position + + public Optional getOptionalPosition(int index) { + return (Optional) getObject(index); + } + + public void setOptionalPosition(int index, java.util.Optional position, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getBlockPositionSerializer(true)), position, update); + } + + // ---- 12: direction + + public Direction getDirection(int index) { + return (Direction) getObject(index); + } + + public void setDirection(int index, Direction direction, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getDirectionSerializer()), direction, update); + } + + // ---- 13: optional uuid + + public Optional getOptionalUUID(int index) { + return (Optional) getObject(index); + } + + public void setOptionalUUID(int index, Optional uuid, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getUUIDSerializer(true)), uuid, update); + } + + // ---- 14: block state + + public WrappedBlockData getBlockState(int index) { + return (WrappedBlockData) getObject(index); + } + + public void setBlockState(int index, WrappedBlockData blockData, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getBlockDataSerializer(false)), blockData, update); + } + + // ---- 15: optional block state + + public Optional getOptionalBlockState(int index) { + return (Optional) getObject(index); + } + + public void setOptionalBlockState(int index, Optional value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getBlockDataSerializer(true)), value, update); + } + + // ---- 16: NBT + + public NbtCompound getNBTCompound(int index) { + return (NbtCompound) getObject(index); + } + + public void setNBTCompound(int index, NbtCompound nbt, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getNBTCompoundSerializer()), nbt, update); + } + + // ---- 17: particle + + public WrappedParticle getParticle(int index) { + return (WrappedParticle) getObject(index); + } + + public void setParticle(int index, WrappedParticle particle, boolean update) { + // TODO: is ParticleParam correct? + setObject(new WrappedDataWatcherObject(index, Registry.get(MinecraftReflection.getParticleParam(), false)), particle, update); + } + + // ---- 18: villager data + + public WrappedVillagerData getVillagerData(int index) { + return (WrappedVillagerData) getObject(index); + } + + public void setVillagerData(int index, WrappedVillagerData data, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(WrappedVillagerData.getNmsClass(), false)), data, update); + } + + // ---- 19: optional varint (int) + + public Optional getOptionalInteger(int index) { + return (Optional) getObject(index); + } + + public void setOptionalInteger(int index, Optional value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(Integer.class, true)), value, update); + } + + // ---- 20: pose + + public EntityPose getPose(int index) { + return (EntityPose) getObject(index); + } + + public void setPose(int index, EntityPose pose, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(EnumWrappers.getEntityPoseClass(), false)), pose, update); + } + + // TODO: 21-25, 27 + + // ---- 21: cat variant + + // ---- 22: frog variant + + // ---- 23: optional global position + + // ---- 24: painting variant + + // ---- 25: sniffer state + + // ---- 26: vector3 + + public Vector3F getVector3F(int index) { + return (Vector3F) getObject(index); + } + + public void setVector3F(int index, Vector3F vector, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(Vector3F.getMinecraftClass(), false)), vector, update); + } + + // ---- 27: quaternion + /** * Retrieve a watchable object by index. * @@ -436,31 +492,7 @@ public Object getObject(int index) { * @return The watched object or null if it doesn't exist. */ public Object getObject(WrappedDataWatcherObject object) { - Validate.notNull(object, "Watcher object cannot be null!"); - - if (GETTER == null) { - FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType, true); - - if (MinecraftReflection.watcherObjectExists()) { - GETTER = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder() - .parameterExactType(object.getHandleType()) - .returnTypeExact(Object.class) - .build(), "get")); - } else { - GETTER = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder() - .parameterExactType(int.class) - .returnTypeExact(MinecraftReflection.getDataWatcherItemClass()) - .build())); - } - } - - try { - Object value = GETTER.invoke(handle, object.getHandle()); - return WrappedWatchableObject.getWrapped(value); - } catch (Exception ex) { - // Nothing exists at this index - return null; - } + return impl.getObject(object); } // ---- Object Setters @@ -565,38 +597,7 @@ public void setObject(WrappedDataWatcherObject object, WrappedWatchableObject va * have a serializer and does not have one. */ public void setObject(WrappedDataWatcherObject object, Object value, boolean update) { - Validate.notNull(object, "Watcher object cannot be null!"); - - if (SETTER == null && REGISTER == null) { - FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType, true); - FuzzyMethodContract contract = FuzzyMethodContract.newBuilder() - .banModifier(Modifier.STATIC) - .requireModifier(Modifier.PUBLIC) - .parameterExactArray(object.getHandleType(), Object.class) - .build(); - List methods = fuzzy.getMethodList(contract); - for (Method method : methods) { - if (method.getName().equals("set") || method.getName().equals("watch") || method.getName().equals("b")) { - SETTER = Accessors.getMethodAccessor(method); - } else { - REGISTER = Accessors.getMethodAccessor(method); - } - } - } - - // Unwrap the object - value = WrappedWatchableObject.getUnwrapped(value); - - if (hasIndex(object.getIndex())) { - SETTER.invoke(handle, object.getHandle(), value); - } else { - object.checkSerializer(); - REGISTER.invoke(handle, object.getHandle(), value); - } - - if (update) { - getWatchableObject(object.getIndex()).setDirtyState(update); - } + impl.setObject(object, value, update); } /** @@ -614,23 +615,15 @@ public void setObject(WrappedDataWatcherObject object, Object value) { * @return A cloned data watcher. */ public WrappedDataWatcher deepClone() { - if (ARRAY_BACKED) { - return new WrappedDataWatcher(newHandle(getEntityHandle(), getWatchableObjects())); - } - - WrappedDataWatcher clone = new WrappedDataWatcher(getEntity()); - - if (MinecraftReflection.watcherObjectExists()) { - for (WrappedWatchableObject wrapper : this) { - clone.setObject(wrapper.getWatcherObject(), wrapper); - } - } else { - for (WrappedWatchableObject wrapper : this) { - clone.setObject(wrapper.getIndex(), wrapper); - } - } + return new WrappedDataWatcher(impl.deepClone()); + } - return clone; + /** + * @return + */ + @Override + public Object getHandle() { + return impl.getHandle(); } /** @@ -640,31 +633,7 @@ public WrappedDataWatcher deepClone() { * @return Associated data watcher. */ public static WrappedDataWatcher getEntityWatcher(Entity entity) { - Object handle = getHandleFromEntity(entity); - return handle != null ? new WrappedDataWatcher(handle) : null; - } - - private static Object getHandleFromEntity(Entity entity) { - if (ENTITY_DATA_FIELD == null) { - ENTITY_DATA_FIELD = Accessors.getFieldAccessor(MinecraftReflection.getEntityClass(), MinecraftReflection.getDataWatcherClass(), true); - } - - BukkitUnwrapper unwrapper = new BukkitUnwrapper(); - Object handle = ENTITY_DATA_FIELD.get(unwrapper.unwrapItem(entity)); - return handle; - } - - private Object getEntityHandle() { - if (ENTITY_FIELD == null) { - ENTITY_FIELD = Accessors.getFieldAccessor(HANDLE_TYPE, SYNCHED_DATA_HOLDER_CLASS, true); - } - - Object entity = ENTITY_FIELD.get(handle); - if (entity == null) { - throw new NullPointerException(handle + "." + ENTITY_FIELD); - } - - return entity; + return new WrappedDataWatcher(entity); } /** @@ -672,8 +641,7 @@ private Object getEntityHandle() { * @return The entity, or NULL. */ public Entity getEntity() { - Object entity = getEntityHandle(); - return (Entity) MinecraftReflection.getBukkitEntity(entity); + return impl.getEntity(); } /** @@ -681,11 +649,23 @@ public Entity getEntity() { * @param entity - the new entity. */ public void setEntity(Entity entity) { - if (ENTITY_FIELD == null) { - ENTITY_FIELD = Accessors.getFieldAccessor(HANDLE_TYPE, MinecraftReflection.getEntityClass(), true); - } + impl.setEntity(entity); + } - ENTITY_FIELD.set(handle, BukkitUnwrapper.getInstance().unwrapItem(entity)); + /** + * Exports the contents of this data watcher to a list of WrappedDataValues + * for use in the ENTITY_METADATA packet + * @return The data value collection + */ + public List toDataValueCollection() { + List objects = impl.getWatchableObjects(); + List values = new ArrayList<>(objects.size()); + for (WrappedWatchableObject object : objects) { + WrappedDataWatcherObject watcherObj = object.getWatcherObject(); + Object value = WrappedWatchableObject.getUnwrapped(object.getRawValue()); + values.add(new WrappedDataValue(watcherObj.getIndex(), watcherObj.getSerializer(), value)); + } + return values; } private static final ImmutableBiMap, Integer> CLASS_TO_ID = new ImmutableBiMap.Builder, Integer>() @@ -728,19 +708,7 @@ public boolean equals(Object obj) { if (obj instanceof WrappedDataWatcher) { WrappedDataWatcher other = (WrappedDataWatcher) obj; - Iterator first = iterator(), second = other.iterator(); - - // Make sure they're the same size - if (size() != other.size()) - return false; - - for (; first.hasNext() && second.hasNext();) { - // See if the two elements are equal - if (!first.next().equals(second.next())) - return false; - } - - return true; + return this.impl.equals(other.impl); } return false; @@ -748,12 +716,12 @@ public boolean equals(Object obj) { @Override public int hashCode() { - return getWatchableObjects().hashCode(); + return impl.hashCode(); } @Override public String toString() { - return "WrappedDataWatcher[handle=" + handle + "]"; + return impl.toString(); } // ---- 1.9 classes @@ -1017,7 +985,9 @@ public String toString() { */ public static class Registry { private static boolean INITIALIZED = false; - private static List REGISTRY = new ArrayList<>(); + + private static Map, Serializer> RAW_REGISTRY = null; + private static Map, Serializer> OPTIONAL_REGISTRY = null; /** * Gets the first serializer associated with a given class. @@ -1035,13 +1005,13 @@ public static Serializer get(Class clazz) { Validate.notNull(clazz,"Class cannot be null!"); initialize(); - for (Serializer serializer : REGISTRY) { - if (serializer.getType().equals(clazz)) { - return serializer; - } + Serializer serializer = RAW_REGISTRY.getOrDefault(clazz, + OPTIONAL_REGISTRY.getOrDefault(clazz, null)); + if (serializer == null) { + throw new IllegalArgumentException("No serializer found for " + clazz); } - throw new IllegalArgumentException("No serializer found for " + clazz); + return serializer; } /** @@ -1058,16 +1028,12 @@ public static Serializer get(Class clazz, boolean optional) { Validate.notNull(clazz, "Class cannot be null!"); initialize(); - Validate.notEmpty(REGISTRY, "Registry has no elements!"); - - for (Serializer serializer : REGISTRY) { - if (serializer.getType().equals(clazz) - && serializer.isOptional() == optional) { - return serializer; - } + Serializer serializer = optional ? OPTIONAL_REGISTRY.get(clazz) : RAW_REGISTRY.get(clazz); + if (serializer == null) { + throw new IllegalArgumentException("No serializer found for " + (optional ? "Optional<" + clazz + ">" : clazz)); } - throw new IllegalArgumentException("No serializer found for " + (optional ? "Optional<" + clazz + ">" : clazz)); + return serializer; } /** @@ -1079,7 +1045,13 @@ public static Serializer fromHandle(Object handle) { Validate.notNull(handle, "handle cannot be null!"); initialize(); - for (Serializer serializer : REGISTRY) { + for (Serializer serializer : RAW_REGISTRY.values()) { + if (serializer.getHandle().equals(handle)) { + return serializer; + } + } + + for (Serializer serializer : OPTIONAL_REGISTRY.values()) { if (serializer.getHandle().equals(handle)) { return serializer; } @@ -1095,6 +1067,9 @@ private static void initialize() { return; } + Map, Serializer> rawRegistry = new HashMap<>(); + Map, Serializer> optionalRegistry = new HashMap<>(); + List candidates = FuzzyReflection.fromClass(MinecraftReflection.getDataWatcherRegistryClass(), true) .getFieldListByType(MinecraftReflection.getDataWatcherSerializerClass()); for (Field candidate : candidates) { @@ -1128,9 +1103,16 @@ private static void initialize() { throw new RuntimeException("Failed to read serializer: " + candidate.getName()); } - REGISTRY.add(new Serializer(innerClass, serializer, optional)); + if (optional) { + optionalRegistry.put(innerClass, new Serializer(innerClass, serializer, true)); + } else { + rawRegistry.put(innerClass, new Serializer(innerClass, serializer, false)); + } } } + + RAW_REGISTRY = ImmutableMap.copyOf(rawRegistry); + OPTIONAL_REGISTRY = ImmutableMap.copyOf(optionalRegistry); } // ---- Helper methods diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedNumberFormat.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedNumberFormat.java new file mode 100644 index 000000000..3caa13df9 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedNumberFormat.java @@ -0,0 +1,126 @@ +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; +import com.comphenix.protocol.utility.MinecraftReflection; +import org.jetbrains.annotations.NotNull; + +/** + * A wrapper around the NumberFormat NMS classes. + * + * @author vytskalt + * @since 1.20.4 + */ +@SuppressWarnings("OptionalGetWithoutIsPresent") +public class WrappedNumberFormat extends AbstractWrapper { + private static final Object BLANK; + private static final ConstructorAccessor FIXED_CONSTRUCTOR, STYLED_CONSTRUCTOR; + + static { + if (!isSupported()) { + BLANK = null; + FIXED_CONSTRUCTOR = null; + STYLED_CONSTRUCTOR = null; + } else { + Class blankClass = MinecraftReflection.getBlankFormatClass().get(); + FuzzyReflection fuzzyBlank = FuzzyReflection.fromClass(blankClass, true); + BLANK = Accessors.getFieldAccessor(fuzzyBlank.getFieldByType("INSTANCE", blankClass)).get(null); + + FIXED_CONSTRUCTOR = Accessors.getConstructorAccessor( + MinecraftReflection.getFixedFormatClass().get(), + MinecraftReflection.getIChatBaseComponentClass() + ); + + STYLED_CONSTRUCTOR = Accessors.getConstructorAccessor( + MinecraftReflection.getStyledFormatClass().get(), + MinecraftReflection.getComponentStyleClass() + ); + } + } + + /** + * @return Whether the NumberFormat classes exist on the current server version + */ + public static boolean isSupported() { + return MinecraftReflection.getNumberFormatClass().isPresent(); + } + + public static WrappedNumberFormat fromHandle(Object handle) { + throwIfUnsupported(); + if (MinecraftReflection.getBlankFormatClass().get().isInstance(handle)) { + return new Blank(handle); + } else if (MinecraftReflection.getFixedFormatClass().get().isInstance(handle)) { + return new Fixed(handle); + } else if (MinecraftReflection.getStyledFormatClass().get().isInstance(handle)) { + return new Styled(handle); + } else { + throw new IllegalArgumentException("handle is not a NumberFormat instance, but " + handle.getClass()); + } + } + + public static Blank blank() { + throwIfUnsupported(); + return new Blank(WrappedNumberFormat.BLANK); + } + + public static Fixed fixed(@NotNull WrappedChatComponent content) { + throwIfUnsupported(); + Object handle = FIXED_CONSTRUCTOR.invoke(content.getHandle()); + return new Fixed(handle); + } + + public static Styled styled(@NotNull WrappedComponentStyle style) { + throwIfUnsupported(); + Object handle = STYLED_CONSTRUCTOR.invoke(style.getHandle()); + return new Styled(handle); + } + + private static void throwIfUnsupported() { + if (!isSupported()) { + throw new IllegalStateException("NumberFormat classes don't exist on this server version"); + } + } + + private WrappedNumberFormat(Class handleType) { + super(handleType); + } + + public static class Blank extends WrappedNumberFormat { + private Blank(Object handle) { + super(MinecraftReflection.getBlankFormatClass().get()); + setHandle(handle); + } + } + + public static class Fixed extends WrappedNumberFormat { + private final StructureModifier modifier; + + private Fixed(Object handle) { + super(MinecraftReflection.getFixedFormatClass().get()); + setHandle(handle); + this.modifier = new StructureModifier<>(handle.getClass()).withTarget(handle); + } + + public WrappedChatComponent getContent() { + Object handle = modifier.withType(MinecraftReflection.getIChatBaseComponentClass()).read(0); + return WrappedChatComponent.fromHandle(handle); + } + } + + public static class Styled extends WrappedNumberFormat { + private final StructureModifier modifier; + + private Styled(Object handle) { + super(MinecraftReflection.getStyledFormatClass().get()); + setHandle(handle); + this.modifier = new StructureModifier<>(handle.getClass()).withTarget(handle); + } + + public WrappedComponentStyle getStyle() { + Object handle = modifier.withType(MinecraftReflection.getComponentStyleClass()).read(0); + return new WrappedComponentStyle(handle); + } + } +} diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedParticle.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedParticle.java index 3f327683f..eeef01b40 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedParticle.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedParticle.java @@ -9,6 +9,8 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; + +import com.google.common.base.Preconditions; import org.bukkit.Color; import org.bukkit.Particle; import org.bukkit.block.data.BlockData; @@ -30,22 +32,26 @@ private static void ensureMethods() { return; } + Class particleType = MinecraftReflection.isMojangMapped() + ? MinecraftReflection.getParticleTypeClass() + : MinecraftReflection.getParticleClass(); + + Preconditions.checkNotNull(particleType, "Cannot find ParticleType class (MojMap: " + MinecraftReflection.isMojangMapped() + ")"); + FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getCraftBukkitClass("CraftParticle")); if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) { FuzzyMethodContract contract = FuzzyMethodContract .newBuilder() .requireModifier(Modifier.STATIC) .returnTypeExact(Particle.class) - .parameterExactArray(MinecraftReflection.isMojangMapped() - ? MinecraftReflection.getParticleTypeClass() - : MinecraftReflection.getParticleClass()) + .parameterExactArray(particleType) .build(); toBukkit = Accessors.getMethodAccessor(fuzzy.getMethod(contract)); FuzzyReflection particleParam = FuzzyReflection.fromClass(MinecraftReflection.getParticleParam(), false); contract = FuzzyMethodContract .newBuilder() - .returnTypeExact(MinecraftReflection.getParticleClass()) + .returnTypeExact(particleType) .parameterCount(0) .build(); getType = Accessors.getMethodAccessor(particleParam.getMethod(contract)); diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedTeamParameters.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedTeamParameters.java new file mode 100644 index 000000000..d0c59fb66 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedTeamParameters.java @@ -0,0 +1,170 @@ +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.injector.StructureCache; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.google.common.base.Preconditions; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A wrapper around the team parameters NMS class. + * + * @author vytskalt + * @since 1.17 + */ +public class WrappedTeamParameters extends AbstractWrapper { + public static Class getNmsClassOrThrow() { + return MinecraftReflection.getTeamParametersClass() + .orElseThrow(() -> new IllegalStateException("Team parameters class doesn't exist on this server version")); + } + + /** + * @return Whether the team parameters class exists on the current server version + */ + public static boolean isSupported() { + return MinecraftReflection.getTeamParametersClass().isPresent(); + } + + public static Builder newBuilder() { + return newBuilder(null); + } + + public static Builder newBuilder(@Nullable WrappedTeamParameters template) { + return new Builder(template); + } + + private final StructureModifier modifier; + + public WrappedTeamParameters(Object handle) { + super(getNmsClassOrThrow()); + setHandle(handle); + this.modifier = new StructureModifier<>(getNmsClassOrThrow()).withTarget(handle); + } + + @NotNull + public WrappedChatComponent getDisplayName() { + return readComponent(0); + } + + @NotNull + public WrappedChatComponent getPrefix() { + return readComponent(1); + } + + @NotNull + public WrappedChatComponent getSuffix() { + return readComponent(2); + } + + @NotNull + public String getNametagVisibility() { + return modifier.withType(String.class).read(0); + } + + @NotNull + public String getCollisionRule() { + return modifier.withType(String.class).read(1); + } + + @NotNull + public EnumWrappers.ChatFormatting getColor() { + Object handle = modifier.withType(EnumWrappers.getChatFormattingClass()).read(0); + return EnumWrappers.getChatFormattingConverter().getSpecific(handle); + } + + public int getOptions() { + return (int) modifier.withType(int.class).read(0); + } + + private WrappedChatComponent readComponent(int index) { + Object handle = modifier.withType(MinecraftReflection.getIChatBaseComponentClass()).read(index); + return WrappedChatComponent.fromHandle(handle); + } + + private void writeComponent(int index, WrappedChatComponent component) { + modifier.withType(MinecraftReflection.getIChatBaseComponentClass()).write(index, component.getHandle()); + } + + public static class Builder { + private WrappedChatComponent displayName, prefix, suffix; + private String nametagVisibility, collisionRule; + private EnumWrappers.ChatFormatting color; + private int options; + + private Builder(@Nullable WrappedTeamParameters template) { + if (template != null) { + this.displayName = template.getDisplayName(); + this.prefix = template.getDisplayName(); + this.suffix = template.getDisplayName(); + this.nametagVisibility = template.getNametagVisibility(); + this.collisionRule = template.getCollisionRule(); + this.color = template.getColor(); + this.options = template.getOptions(); + } + } + + public Builder displayName(@NotNull WrappedChatComponent displayName) { + Preconditions.checkNotNull(displayName); + this.displayName = displayName; + return this; + } + + public Builder prefix(@NotNull WrappedChatComponent prefix) { + Preconditions.checkNotNull(prefix); + this.prefix = prefix; + return this; + } + + public Builder suffix(@NotNull WrappedChatComponent suffix) { + Preconditions.checkNotNull(suffix); + this.suffix = suffix; + return this; + } + + public Builder nametagVisibility(@NotNull String nametagVisibility) { + Preconditions.checkNotNull(nametagVisibility); + this.nametagVisibility = nametagVisibility; + return this; + } + + public Builder collisionRule(@NotNull String collisionRule) { + Preconditions.checkNotNull(collisionRule); + this.collisionRule = collisionRule; + return this; + } + + public Builder color(@NotNull EnumWrappers.ChatFormatting color) { + Preconditions.checkNotNull(color); + this.color = color; + return this; + } + + public Builder options(int options) { + Preconditions.checkNotNull(collisionRule); + this.options = options; + return this; + } + + public WrappedTeamParameters build() { + Preconditions.checkNotNull(displayName, "Display name not set"); + Preconditions.checkNotNull(prefix, "Prefix not set"); + Preconditions.checkNotNull(suffix, "Suffix not set"); + Preconditions.checkNotNull(nametagVisibility, "Nametag visibility not set"); + Preconditions.checkNotNull(collisionRule, "Collision rule not set"); + Preconditions.checkNotNull(color, "Color not set"); + + Object handle = StructureCache.newInstance(getNmsClassOrThrow()); + + WrappedTeamParameters wrapped = new WrappedTeamParameters(handle); + wrapped.writeComponent(0, displayName); + wrapped.writeComponent(1, prefix); + wrapped.writeComponent(2, suffix); + wrapped.modifier.withType(String.class).write(0, nametagVisibility); + wrapped.modifier.withType(String.class).write(1, collisionRule); + wrapped.modifier.withType(EnumWrappers.getChatFormattingClass()).write(0, EnumWrappers.getChatFormattingConverter().getGeneric(color)); + wrapped.modifier.withType(int.class).write(0, options); + return wrapped; + } + } +} diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java index bc42bda3c..e77bfd141 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java @@ -14,6 +14,8 @@ */ package com.comphenix.protocol.wrappers; +import java.util.Optional; + import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; @@ -23,9 +25,8 @@ import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject; import com.comphenix.protocol.wrappers.nbt.NbtCompound; import com.comphenix.protocol.wrappers.nbt.NbtFactory; -import org.bukkit.inventory.ItemStack; -import java.util.Optional; +import org.bukkit.inventory.ItemStack; import static com.comphenix.protocol.utility.MinecraftReflection.is; @@ -34,7 +35,7 @@ * Use {@link WrappedDataValue} for 1.19.3 or later. * @author dmulloy2 */ -public class WrappedWatchableObject extends AbstractWrapper { +public final class WrappedWatchableObject extends AbstractWrapper { private static final Class HANDLE_TYPE = MinecraftReflection.getDataWatcherItemClass(); private static Integer VALUE_INDEX = null; @@ -80,11 +81,13 @@ private static Object newHandle(WrappedDataWatcherObject watcherObject, Object v constructor = Accessors.getConstructorAccessor(HANDLE_TYPE.getConstructors()[0]); } + Object nmsValue = getUnwrapped(value); + if (MinecraftReflection.watcherObjectExists()) { - return constructor.invoke(watcherObject.getHandle(), value); + return constructor.invoke(watcherObject.getHandle(), nmsValue); } else { // new WatchableObject(classId, index, value) - return constructor.invoke(WrappedDataWatcher.getTypeID(value.getClass()), watcherObject.getIndex(), value); + return constructor.invoke(WrappedDataWatcher.getTypeID(value.getClass()), watcherObject.getIndex(), nmsValue); } } diff --git a/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java b/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java index d6e10a504..26ff3cc49 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java +++ b/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java @@ -17,22 +17,6 @@ package com.comphenix.protocol.wrappers.nbt; -import com.comphenix.protocol.reflect.FieldAccessException; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.StructureModifier; -import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; -import com.comphenix.protocol.reflect.instances.DefaultInstances; -import com.comphenix.protocol.utility.MinecraftReflection; -import com.comphenix.protocol.utility.MinecraftVersion; -import com.comphenix.protocol.wrappers.BukkitConverters; -import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer; -import com.google.common.base.Preconditions; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.inventory.ItemStack; - -import javax.annotation.Nonnull; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileInputStream; @@ -47,6 +31,23 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; +import javax.annotation.Nonnull; + +import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; +import com.comphenix.protocol.reflect.instances.DefaultInstances; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.MinecraftVersion; +import com.comphenix.protocol.wrappers.BukkitConverters; +import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer; + +import com.google.common.base.Preconditions; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.inventory.ItemStack; /** * Factory methods for creating NBT elements, lists and compounds. diff --git a/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java b/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java index bec4371eb..7ec326152 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java +++ b/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java @@ -1,5 +1,9 @@ package com.comphenix.protocol.wrappers.nbt.io; +import java.io.DataInput; +import java.io.DataOutput; +import java.lang.reflect.Method; + import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; @@ -12,9 +16,6 @@ import com.comphenix.protocol.wrappers.nbt.NbtFactory; import com.comphenix.protocol.wrappers.nbt.NbtList; import com.comphenix.protocol.wrappers.nbt.NbtWrapper; -import java.io.DataInput; -import java.io.DataOutput; -import java.lang.reflect.Method; public class NbtBinarySerializer { @@ -153,15 +154,23 @@ private static class LoadMethodConfigPhaseUpdate implements CodecMethod { public LoadMethodConfigPhaseUpdate() { // there are now two methods with the same signature: readAnyTag/readUnnamedTag & writeAnyTag/writeUnnamedTag // we can only find the correct method here by using the method name... thanks Mojang + String readNbtMethodName = MinecraftReflection.isMojangMapped() + ? "readAnyTag" + : "b"; + + String writeNbtMethodName = MinecraftReflection.isMojangMapped() + ? "writeAnyTag" + : "a"; + Method readNbtMethod = getUtilityClass().getMethod(FuzzyMethodContract.newBuilder() - .nameExact("b") + .nameExact(readNbtMethodName) .returnTypeExact(MinecraftReflection.getNBTBaseClass()) .parameterExactArray(DataInput.class, this.readLimitClass) .build()); this.readNbt = Accessors.getMethodAccessor(readNbtMethod); Method writeNbtMethod = getUtilityClass().getMethod(FuzzyMethodContract.newBuilder() - .nameExact("a") + .nameExact(writeNbtMethodName) .parameterExactArray(MinecraftReflection.getNBTBaseClass(), DataOutput.class) .build()); this.writeNbt = Accessors.getMethodAccessor(writeNbtMethod); diff --git a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java index 9a64f286c..0b4ee3c74 100644 --- a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java +++ b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -334,7 +334,7 @@ public void testGetPositionCollectionModifier() { } @Test - @Disabled // TODO -- handle type is null + // @Disabled // TODO -- handle type is null public void testGetDataValueCollectionModifier() { PacketContainer entityMetadata = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); StructureModifier> watchableAccessor = entityMetadata.getDataValueCollectionModifier(); diff --git a/src/test/java/com/comphenix/protocol/wrappers/WrappedComponentStyleTest.java b/src/test/java/com/comphenix/protocol/wrappers/WrappedComponentStyleTest.java new file mode 100644 index 000000000..d0ff9d4b6 --- /dev/null +++ b/src/test/java/com/comphenix/protocol/wrappers/WrappedComponentStyleTest.java @@ -0,0 +1,40 @@ +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.BukkitInitialization; +import com.google.gson.JsonElement; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextDecoration; +import net.minecraft.EnumChatFormat; +import net.minecraft.network.chat.ChatModifier; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class WrappedComponentStyleTest { + + @BeforeAll + public static void initializeBukkit() { + BukkitInitialization.initializeAll(); + } + + @Test + public void testComponentStyle() { + ChatModifier style = ChatModifier.a.b(EnumChatFormat.m).a(true); + WrappedComponentStyle wrapped = new WrappedComponentStyle(style); + JsonElement json = wrapped.getJson(); + assertEquals("{\"color\":\"red\",\"bold\":true}", json.toString()); + assertEquals(style, WrappedComponentStyle.fromJson(json).getHandle()); + } + + @Test + public void testStyleAdventureConversion() { + Style adventureStyle = Style.style(NamedTextColor.GREEN, TextDecoration.BOLD) + .clickEvent(ClickEvent.changePage(10)); + + WrappedComponentStyle wrapped = AdventureComponentConverter.fromStyle(adventureStyle); + assertEquals(adventureStyle, AdventureComponentConverter.fromWrapper(wrapped)); + } +} diff --git a/src/test/java/com/comphenix/protocol/wrappers/WrappedDataWatcherTest.java b/src/test/java/com/comphenix/protocol/wrappers/WrappedDataWatcherTest.java index 4769d5ef3..91e6e5735 100644 --- a/src/test/java/com/comphenix/protocol/wrappers/WrappedDataWatcherTest.java +++ b/src/test/java/com/comphenix/protocol/wrappers/WrappedDataWatcherTest.java @@ -14,21 +14,26 @@ */ package com.comphenix.protocol.wrappers; +import java.util.List; +import java.util.Optional; + import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry; -import com.comphenix.protocol.wrappers.WrappedDataWatcher.Serializer; import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject; +import com.comphenix.protocol.wrappers.nbt.NbtBase; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; +import com.comphenix.protocol.wrappers.nbt.NbtFactory; import net.minecraft.world.entity.projectile.EntityEgg; +import org.bukkit.Material; +import org.bukkit.Particle; import org.bukkit.craftbukkit.v1_20_R4.entity.CraftEgg; -import org.bukkit.craftbukkit.v1_20_R4.entity.CraftEntity; import org.bukkit.entity.Entity; +import org.bukkit.inventory.ItemStack; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import java.util.UUID; - import static org.junit.jupiter.api.Assertions.*; /** @@ -46,8 +51,7 @@ public static void prepare() { } @Test - @Disabled // TODO -- SETTER is null - public void testBytes() { + public void testFromEntity() { WrappedDataWatcher watcher = WrappedDataWatcher.getEntityWatcher(mockEntity); WrappedWatchableObject watchable = watcher.getWatchableObject(0); @@ -57,51 +61,160 @@ public void testBytes() { assertEquals(object.getSerializer(), Registry.get(Byte.class)); // Make sure we can set existing objects - watcher.setObject(0, (byte) 21); - assertEquals(21, (byte) watcher.getByte(0)); + watcher.setByte(0, (byte) 21, false); + assertEquals((byte) 21, watcher.getByte(0)); + + assertTrue(watcher.hasIndex(1)); + assertFalse(watcher.hasIndex(9999)); + } + + @Test + public void testPrimitives() { + WrappedDataWatcher watcher = new WrappedDataWatcher(); + + watcher.setByte(0, (byte) 21, false); + assertEquals((byte) 21, watcher.getByte(0)); + + watcher.setInteger(1, 37, false); + assertEquals(37, watcher.getInteger(1)); + + watcher.setFloat(2, 42.1F, false); + assertEquals(42.1F, watcher.getFloat(2)); + + watcher.setLong(3, 69L, false); + assertEquals(69L, watcher.getLong(3)); + + watcher.setBoolean(4, true, false); + assertTrue(watcher.getBoolean(4)); + + assertTrue(watcher.hasIndex(4)); + assertFalse(watcher.hasIndex(5)); + assertEquals(5, watcher.size()); + + List dataValues = watcher.toDataValueCollection(); + assertEquals(5, dataValues.size()); + + assertEquals((byte) 21, dataValues.get(0).getValue()); + assertEquals((byte) 21, dataValues.get(0).getRawValue()); + + assertEquals(37, dataValues.get(1).getValue()); + assertEquals(37, dataValues.get(1).getRawValue()); + + assertEquals(42.1F, dataValues.get(2).getValue()); + assertEquals(42.1F, dataValues.get(2).getRawValue()); + + assertEquals(69L, dataValues.get(3).getValue()); + assertEquals(69L, dataValues.get(3).getRawValue()); + + assertEquals(true, dataValues.get(4).getValue()); + assertEquals(true, dataValues.get(4).getRawValue()); } @Test - @Disabled // TODO -- SETTER is null public void testStrings() { WrappedDataWatcher watcher = WrappedDataWatcher.getEntityWatcher(mockEntity); - // Make sure we can create watcher objects - Serializer serializer = Registry.get(String.class); - WrappedDataWatcherObject object = new WrappedDataWatcherObject(3, serializer); - watcher.setObject(object, "Test"); + watcher.setString(0, "Test", false); + + assertEquals("Test", watcher.getString(0)); - assertEquals(watcher.getString(3), "Test"); + WrappedDataValue wdv = watcher.toDataValueCollection().get(0); + assertEquals("Test", wdv.getValue()); + assertEquals("Test", wdv.getRawValue()); + + assertEquals(0, wdv.getIndex()); + assertNotNull(wdv.getSerializer()); + assertFalse(wdv.getSerializer().isOptional()); } @Test - @Disabled // TODO -- need a better example for floats - public void testFloats() { + public void testChatComponent() { WrappedDataWatcher wrapper = new WrappedDataWatcher(); + wrapper.setChatComponent(0, WrappedChatComponent.fromText("Test"), false); + wrapper.setOptionalChatComponent(1, Optional.of(WrappedChatComponent.fromText("Test")), false); + + WrappedChatComponent expected = WrappedChatComponent.fromText("Test"); + assertEquals(expected, wrapper.getChatComponent(0)); + assertTrue(wrapper.getOptionalChatComponent(1).isPresent()); + assertEquals(expected, wrapper.getOptionalChatComponent(1).get()); + + List wdvs = wrapper.toDataValueCollection(); + WrappedDataValue wdv1 = wdvs.get(0); + assertEquals(expected, wdv1.getValue()); - // Make sure we can add new entries - Serializer serializer = Registry.get(Float.class); - WrappedDataWatcherObject object = new WrappedDataWatcherObject(10, serializer); - wrapper.setObject(object, 21.0F); + WrappedDataValue wdv2 = wdvs.get(1); + assertEquals(expected, ((Optional) wdv2.getValue()).get()); - assertTrue(wrapper.hasIndex(10)); + assertEquals("literal{Test}", wdv1.getRawValue().toString()); } @Test - public void testSerializers() { - Serializer blockPos = Registry.get(net.minecraft.core.BlockPosition.class, false); - Serializer optionalBlockPos = Registry.get(net.minecraft.core.BlockPosition.class, true); - assertNotSame(blockPos, optionalBlockPos); + public void testItemStacks() { + WrappedDataWatcher wrapper = new WrappedDataWatcher(); + wrapper.setItemStack(0, new ItemStack(Material.ACACIA_FENCE), false); + assertEquals(Material.ACACIA_FENCE, wrapper.getItemStack(0).getType()); + + assertEquals(MinecraftReflection.getItemStackClass(), + wrapper.getWatchableObject(0).getRawValue().getClass()); + + WrappedDataValue wdv = wrapper.toDataValueCollection().get(0); + assertEquals(Material.ACACIA_FENCE, ((ItemStack) wdv.getValue()).getType()); - // assertNull(Registry.get(ItemStack.class, false)); // TODO - assertNotNull(Registry.get(UUID.class, true)); + Object raw = wdv.getRawValue(); + assertEquals(MinecraftReflection.getItemStackClass(), raw.getClass()); } @Test - public void testHasIndex() { - WrappedDataWatcher watcher = WrappedDataWatcher.getEntityWatcher(mockEntity); - assertTrue(watcher.hasIndex(1)); - assertFalse(watcher.hasIndex(9999)); + public void testMinecraftObjects() { + WrappedDataWatcher watcher = new WrappedDataWatcher(); + + watcher.setPosition(0, new BlockPosition(1, 2, 3), false); + assertEquals(new BlockPosition(1, 2, 3), watcher.getPosition(0)); + + watcher.setOptionalPosition(1, Optional.of(new BlockPosition(4, 5, 6)), false); + assertEquals(Optional.of(new BlockPosition(4, 5, 6)), watcher.getOptionalPosition(1)); + + watcher.setDirection(2, EnumWrappers.Direction.EAST, false); + assertEquals(EnumWrappers.Direction.EAST, watcher.getDirection(2)); + + watcher.setBlockState(3, WrappedBlockData.createData(Material.ACACIA_FENCE), false); + assertEquals(Material.ACACIA_FENCE, watcher.getBlockState(3).getType()); + + watcher.setOptionalBlockState(4, Optional.of(WrappedBlockData.createData(Material.ACACIA_FENCE)), false); + assertEquals(Optional.of(WrappedBlockData.createData(Material.ACACIA_FENCE)), watcher.getOptionalBlockState(4)); + + watcher.setParticle(5, WrappedParticle.create(Particle.CAMPFIRE_COSY_SMOKE, null), false); + assertEquals(Particle.CAMPFIRE_COSY_SMOKE, watcher.getParticle(5).getParticle()); + + watcher.setParticle(6, WrappedParticle.create(Particle.BLOCK, WrappedBlockData.createData(Material.AZALEA)), false); + assertEquals(Material.AZALEA, ((WrappedParticle) watcher.getParticle(6)).getData().getType()); + + assertEquals(7, watcher.size()); + + List wdvs = watcher.toDataValueCollection(); + assertEquals(7, wdvs.size()); + + assertEquals(new BlockPosition(1, 2, 3), wdvs.get(0).getValue()); + assertEquals(Optional.of(new BlockPosition(4, 5, 6)), wdvs.get(1).getValue()); + assertEquals(EnumWrappers.Direction.EAST, wdvs.get(2).getValue()); + assertEquals(Material.ACACIA_FENCE, ((WrappedBlockData) wdvs.get(3).getValue()).getType()); + assertEquals(Particle.CAMPFIRE_COSY_SMOKE, ((WrappedParticle) wdvs.get(5).getValue()).getParticle()); + } + + @Test + public void testNBT() { + WrappedDataWatcher watcher = new WrappedDataWatcher(); + + NbtBase nbt = NbtFactory.of("testTag", 17); + NbtCompound compound = NbtFactory.ofCompound("testCompound", List.of(nbt)); + watcher.setNBTCompound(0, compound, false); + + NbtCompound roundTrip = watcher.getNBTCompound(0); + assertEquals(17, roundTrip.getInteger("testTag")); + + List wdvs = watcher.toDataValueCollection(); + Object rawValue = wdvs.get(0).getRawValue(); + assertEquals(MinecraftReflection.getNBTCompoundClass(), rawValue.getClass()); } @Test @@ -110,6 +223,10 @@ public void testDeepClone() { WrappedDataWatcher cloned = watcher.deepClone(); assertEquals(watcher.size(), cloned.size()); - assertEquals(watcher.getObject(1), cloned.getObject(1)); + + int size = watcher.size(); + for (int i = 0; i < size; i++) { + assertEquals(watcher.getObject(i), cloned.getObject(i)); + } } } diff --git a/src/test/java/com/comphenix/protocol/wrappers/WrappedNumberFormatTest.java b/src/test/java/com/comphenix/protocol/wrappers/WrappedNumberFormatTest.java new file mode 100644 index 000000000..c8b417f8f --- /dev/null +++ b/src/test/java/com/comphenix/protocol/wrappers/WrappedNumberFormatTest.java @@ -0,0 +1,50 @@ +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.BukkitInitialization; +import net.minecraft.EnumChatFormat; +import net.minecraft.network.chat.ChatModifier; +import net.minecraft.network.chat.IChatBaseComponent; +import net.minecraft.network.chat.numbers.BlankFormat; +import net.minecraft.network.chat.numbers.FixedFormat; +import net.minecraft.network.chat.numbers.StyledFormat; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +public class WrappedNumberFormatTest { + + @BeforeAll + static void initializeBukkit() { + BukkitInitialization.initializeAll(); + } + + @Test + void testBlankFormat() { + assertInstanceOf(WrappedNumberFormat.Blank.class, WrappedNumberFormat.fromHandle(BlankFormat.a)); + assertEquals(BlankFormat.a, WrappedNumberFormat.blank().getHandle()); + } + + @Test + void testFixedFormat() { + IChatBaseComponent content = IChatBaseComponent.a("Fixed"); + WrappedNumberFormat wrappedHandle = WrappedNumberFormat.fromHandle(new FixedFormat(content)); + assertInstanceOf(WrappedNumberFormat.Fixed.class, wrappedHandle); + assertEquals(content, ((WrappedNumberFormat.Fixed) wrappedHandle).getContent().getHandle()); + + WrappedNumberFormat.Fixed wrapped = WrappedNumberFormat.fixed(WrappedChatComponent.fromHandle(content)); + assertEquals(content, wrapped.getContent().getHandle()); + } + + @Test + void testStyledFormat() { + ChatModifier style = ChatModifier.a.b(EnumChatFormat.g); + WrappedNumberFormat wrappedHandle = WrappedNumberFormat.fromHandle(new StyledFormat(style)); + assertInstanceOf(WrappedNumberFormat.Styled.class, wrappedHandle); + assertEquals(style, ((WrappedNumberFormat.Styled) wrappedHandle).getStyle().getHandle()); + + WrappedNumberFormat.Styled newWrapper = WrappedNumberFormat.styled(new WrappedComponentStyle(style)); + assertEquals(style, newWrapper.getStyle().getHandle()); + } +} diff --git a/src/test/java/com/comphenix/protocol/wrappers/WrappedTeamParametersTest.java b/src/test/java/com/comphenix/protocol/wrappers/WrappedTeamParametersTest.java new file mode 100644 index 000000000..c8f371fb6 --- /dev/null +++ b/src/test/java/com/comphenix/protocol/wrappers/WrappedTeamParametersTest.java @@ -0,0 +1,53 @@ +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.BukkitInitialization; +import net.minecraft.EnumChatFormat; +import net.minecraft.network.chat.IChatBaseComponent; +import net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class WrappedTeamParametersTest { + @BeforeAll + static void initializeBukkit() { + BukkitInitialization.initializeAll(); + } + + @Test + void testTeamParameters() { + IChatBaseComponent displayName = IChatBaseComponent.b("display name"); + IChatBaseComponent prefix = IChatBaseComponent.b("prefix"); + IChatBaseComponent suffix = IChatBaseComponent.b("suffix"); + String nametagVisibility = "always"; + String collisionRule = "never"; + + WrappedTeamParameters wrapped = WrappedTeamParameters.newBuilder() + .displayName(WrappedChatComponent.fromHandle(displayName)) + .prefix(WrappedChatComponent.fromHandle(prefix)) + .suffix(WrappedChatComponent.fromHandle(suffix)) + .nametagVisibility(nametagVisibility) + .collisionRule(collisionRule) + .color(EnumWrappers.ChatFormatting.RED) + .options(1) + .build(); + + assertEquals(displayName, wrapped.getDisplayName().getHandle()); + assertEquals(prefix, wrapped.getPrefix().getHandle()); + assertEquals(suffix, wrapped.getSuffix().getHandle()); + assertEquals(nametagVisibility, wrapped.getNametagVisibility()); + assertEquals(collisionRule, wrapped.getCollisionRule()); + assertEquals(EnumWrappers.ChatFormatting.RED, wrapped.getColor()); + assertEquals(1, wrapped.getOptions()); + + PacketPlayOutScoreboardTeam.b handle = (PacketPlayOutScoreboardTeam.b) wrapped.getHandle(); + assertEquals(handle.a(), displayName); + assertEquals(handle.f(), prefix); + assertEquals(handle.g(), suffix); + assertEquals(handle.d(), nametagVisibility); + assertEquals(handle.e(), collisionRule); + assertEquals(handle.c(), EnumChatFormat.m); + assertEquals(handle.b(), 1); + } +}