From 5d52a060f0becb79fb8cce2c4d635201402abaaa Mon Sep 17 00:00:00 2001 From: LeonTG Date: Sat, 17 Oct 2026 00:35:55 +0200 Subject: [PATCH] Add way to get entity by uuid, like you can for entity by id --- .../util/SpigotConversionUtil.java | 15 +++ .../util/SpigotReflectionUtil.java | 101 ++++++++++++------ 2 files changed, 85 insertions(+), 31 deletions(-) diff --git a/spigot/src/main/java/io/github/retrooper/packetevents/util/SpigotConversionUtil.java b/spigot/src/main/java/io/github/retrooper/packetevents/util/SpigotConversionUtil.java index 60b4ea6280..acb5c0010c 100644 --- a/spigot/src/main/java/io/github/retrooper/packetevents/util/SpigotConversionUtil.java +++ b/spigot/src/main/java/io/github/retrooper/packetevents/util/SpigotConversionUtil.java @@ -49,6 +49,7 @@ import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.UUID; public final class SpigotConversionUtil { @@ -231,6 +232,20 @@ public static Enum toBukkitParticle(ParticleType particle) { return SpigotReflectionUtil.getEntityById(world, entityId); } + /** + * WARNING: This is not a safe operation, avoid usage + * of this method if possible! + *

+ * Access the Bukkit Entity associated to the Entity UUID. + * + * @param world The world they are in. This field is optional, but is recommended as it could boost performance. + * @param entityUUID The associated Entity UUID + * @return The Bukkit Entity + */ + public static org.bukkit.entity.@Nullable Entity getEntityByUUID(@Nullable World world, UUID entityUUID) { + return SpigotReflectionUtil.getEntityByUUID(world, entityUUID); + } + public static Pose toBukkitPose(EntityPose pose) { return Pose.values()[pose.ordinal()]; } diff --git a/spigot/src/main/java/io/github/retrooper/packetevents/util/SpigotReflectionUtil.java b/spigot/src/main/java/io/github/retrooper/packetevents/util/SpigotReflectionUtil.java index 18d1f1ecf5..1beb5bffc3 100644 --- a/spigot/src/main/java/io/github/retrooper/packetevents/util/SpigotReflectionUtil.java +++ b/spigot/src/main/java/io/github/retrooper/packetevents/util/SpigotReflectionUtil.java @@ -66,6 +66,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; @@ -123,7 +124,7 @@ public final class SpigotReflectionUtil { //Methods public static Method IS_DEBUGGING, GET_CRAFT_PLAYER_HANDLE_METHOD, GET_CRAFT_ENTITY_HANDLE_METHOD, GET_CRAFT_WORLD_HANDLE_METHOD, GET_MOB_EFFECT_LIST_ID_METHOD, GET_MOB_EFFECT_LIST_BY_ID_METHOD, GET_ITEM_ID_METHOD, GET_ITEM_BY_ID_METHOD, - GET_BUKKIT_ENTITY_METHOD, GET_LEVEL_ENTITY_GETTER_ITERABLE_METHOD, GET_ENTITY_BY_ID_LEVEL_ENTITY_GETTER_METHOD, GET_ENTITY_BY_ID_METHOD, + GET_BUKKIT_ENTITY_METHOD, GET_LEVEL_ENTITY_GETTER_ITERABLE_METHOD, GET_ENTITY_BY_ID_LEVEL_ENTITY_GETTER_METHOD, GET_ENTITY_BY_ID_METHOD, GET_ENTITY_BY_UUID_LEVEL_ENTITY_GETTER_METHOD, GET_ENTITY_BY_UUID_METHOD, CRAFT_ITEM_STACK_AS_BUKKIT_COPY, CRAFT_ITEM_STACK_AS_NMS_COPY, BUKKIT_PARTICLE_TO_NMS_ENUM_PARTICLE, NMS_ENUM_PARTICLE_TO_BUKKIT_PARTICLE, READ_ITEM_STACK_IN_PACKET_DATA_SERIALIZER_METHOD, WRITE_ITEM_STACK_IN_PACKET_DATA_SERIALIZER_METHOD, GET_COMBINED_ID, @@ -153,6 +154,7 @@ public final class SpigotReflectionUtil { //Cache entities right after we request/find them for faster search. public static Map ENTITY_ID_CACHE = new MapMaker().weakValues().makeMap(); + public static Map ENTITY_UUID_CACHE = new MapMaker().weakValues().makeMap(); private static void initConstructors() { Class itemClass = NMS_IMATERIAL_CLASS != null ? NMS_IMATERIAL_CLASS : NMS_ITEM_CLASS; @@ -189,6 +191,7 @@ private static void initMethods() { if (V_1_17_OR_HIGHER) { GET_LEVEL_ENTITY_GETTER_ITERABLE_METHOD = Reflection.getMethod(LEVEL_ENTITY_GETTER_CLASS, Iterable.class, 0); GET_ENTITY_BY_ID_LEVEL_ENTITY_GETTER_METHOD = Reflection.getMethod(LEVEL_ENTITY_GETTER_CLASS, ENTITY_ACCESS_CLASS, 0, int.class); + GET_ENTITY_BY_UUID_LEVEL_ENTITY_GETTER_METHOD = Reflection.getMethod(LEVEL_ENTITY_GETTER_CLASS, ENTITY_ACCESS_CLASS, 0, UUID.class); } if (DIMENSION_MANAGER_CLASS != null) { GET_DIMENSION_KEY = Reflection.getMethod(LEVEL_CLASS, "getTypeKey", 0); @@ -203,6 +206,7 @@ private static void initMethods() { if (GET_ENTITY_BY_ID_METHOD == null) { GET_ENTITY_BY_ID_METHOD = Reflection.getMethodExact(SERVER_LEVEL_CLASS, "getEntity", NMS_ENTITY_CLASS, int.class); } + GET_ENTITY_BY_UUID_METHOD = Reflection.getMethodExact(SERVER_LEVEL_CLASS, "getEntity", NMS_ENTITY_CLASS, UUID.class); if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThanOrEquals(ServerVersion.V_1_12_2)) { BUKKIT_PARTICLE_TO_NMS_ENUM_PARTICLE = Reflection.getMethod(CRAFT_PARTICLE_CLASS, "toNMS", NMS_ENUM_PARTICLE_CLASS); @@ -1113,45 +1117,57 @@ public static Object readNmsNbtFromStream(DataInputStream in) { } private static @Nullable Entity getEntityByIdWithWorldUnsafe(World world, int id) { + try { + return getEntityWithWorldUnsafe(world, id, ENTITY_ID_CACHE, GET_ENTITY_BY_ID_LEVEL_ENTITY_GETTER_METHOD, GET_ENTITY_BY_ID_METHOD); + } catch (IllegalAccessException | InvocationTargetException exception) { + throw new RuntimeException("Error while looking up entity by id " + id + " in " + world, exception); + } + } + + private static @Nullable Entity getEntityByUUIDWithWorldUnsafe(World world, UUID uuid) { + try { + return getEntityWithWorldUnsafe(world, uuid, ENTITY_UUID_CACHE, GET_ENTITY_BY_UUID_LEVEL_ENTITY_GETTER_METHOD, GET_ENTITY_BY_UUID_METHOD); + } catch (IllegalAccessException | InvocationTargetException exception) { + throw new RuntimeException("Error while looking up entity by uuid " + uuid + " in " + world, exception); + } + } + + private static @Nullable Entity getEntityWithWorldUnsafe(World world, T type, Map entityCache, Method getEntityLevelEntityGetterMethod, Method getEntityMethod) throws IllegalAccessException, InvocationTargetException { if (world == null) { return null; } - Entity cachedEntity = ENTITY_ID_CACHE.getOrDefault(id, null); + Entity cachedEntity = entityCache.getOrDefault(type, null); if (cachedEntity != null) { return cachedEntity; } - try { - Object serverLevel = GET_CRAFT_WORLD_HANDLE_METHOD.invoke(world); - Object nmsEntity; - //On 1.17 we need this to bypass - if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_17)) { - ReflectionObject reflectObj = PAPER_ENTITY_LOOKUP_LEGACY - ? new ReflectionObject(serverLevel, SERVER_LEVEL_CLASS) - : new ReflectionObject(serverLevel, LEVEL_CLASS); - Object levelEntityGetter; - if (PAPER_ENTITY_LOOKUP_EXISTS) { - levelEntityGetter = reflectObj.readObject(0, PAPER_ENTITY_LOOKUP_CLASS); - } else { - Object entitySectionManager = reflectObj.readObject(0, PERSISTENT_ENTITY_SECTION_MANAGER_CLASS); - ReflectionObject reflectEntitySectionManager = new ReflectionObject(entitySectionManager); - levelEntityGetter = reflectEntitySectionManager.readObject(0, LEVEL_ENTITY_GETTER_CLASS); - } - nmsEntity = GET_ENTITY_BY_ID_LEVEL_ENTITY_GETTER_METHOD.invoke(levelEntityGetter, id); + Object serverLevel = GET_CRAFT_WORLD_HANDLE_METHOD.invoke(world); + Object nmsEntity; + //On 1.17 we need this to bypass + if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_17)) { + ReflectionObject reflectObj = PAPER_ENTITY_LOOKUP_LEGACY + ? new ReflectionObject(serverLevel, SERVER_LEVEL_CLASS) + : new ReflectionObject(serverLevel, LEVEL_CLASS); + Object levelEntityGetter; + if (PAPER_ENTITY_LOOKUP_EXISTS) { + levelEntityGetter = reflectObj.readObject(0, PAPER_ENTITY_LOOKUP_CLASS); } else { - nmsEntity = GET_ENTITY_BY_ID_METHOD.invoke(serverLevel, id); - } - if (nmsEntity == null) { - return null; + Object entitySectionManager = reflectObj.readObject(0, PERSISTENT_ENTITY_SECTION_MANAGER_CLASS); + ReflectionObject reflectEntitySectionManager = new ReflectionObject(entitySectionManager); + levelEntityGetter = reflectEntitySectionManager.readObject(0, LEVEL_ENTITY_GETTER_CLASS); } - Entity entity = getBukkitEntity(nmsEntity); - if (entity == null) { - return null; - } - ENTITY_ID_CACHE.put(id, entity); - return entity; - } catch (IllegalAccessException | InvocationTargetException exception) { - throw new RuntimeException("Error while looking up entity by id " + id + " in " + world, exception); + nmsEntity = getEntityLevelEntityGetterMethod.invoke(levelEntityGetter, type); + } else { + nmsEntity = getEntityMethod.invoke(serverLevel, type); } + if (nmsEntity == null) { + return null; + } + Entity entity = getBukkitEntity(nmsEntity); + if (entity == null) { + return null; + } + entityCache.put(type, entity); + return entity; } /** @@ -1187,6 +1203,29 @@ public static Object readNmsNbtFromStream(DataInputStream in) { return getEntityById(null, entityID); } + /** + * Get the entity by the uuid. + * + * @deprecated Please resort to {@link SpigotConversionUtil#getEntityByUUID(World, int)} since the reflection util is not API. + */ + @Deprecated + public static @Nullable Entity getEntityByUUID(@Nullable World origin, UUID uuid) { + if (origin != null) { + Entity e = getEntityByUUIDWithWorldUnsafe(origin, uuid); + if (e != null) { + return e; + } + } + //They specified the wrong world + for (World world : Bukkit.getWorlds()) { + Entity entity = getEntityByUUIDWithWorldUnsafe(world, uuid); + if (entity != null) { + return entity; + } + } + return null; + } + public static List getEntityList(World world) { if (V_1_17_OR_HIGHER) { Object worldServer = convertBukkitWorldToWorldServer(world);