From 4f248b1b5b89c1e727ac88065c4b6fb7efeeb443 Mon Sep 17 00:00:00 2001 From: Aikar Date: Tue, 2 Jan 2018 00:31:26 -0500 Subject: [PATCH] Fill Profile Property Events Allows plugins to populate profile properties from local sources to avoid calls out to Mojang API to fill in textures for example. If Mojang API does need to be hit, event fire so you can get the results. This is useful for implementing a ProfileCache for Player Skulls --- .../component/ResolvableProfile.java.patch | 11 ++++ .../block/entity/SkullBlockEntity.java.patch | 66 ++++++++++++++++++- .../profile/PaperMinecraftSessionService.java | 17 ++++- 3 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 paper-server/patches/sources/net/minecraft/world/item/component/ResolvableProfile.java.patch diff --git a/paper-server/patches/sources/net/minecraft/world/item/component/ResolvableProfile.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/ResolvableProfile.java.patch new file mode 100644 index 0000000000..c505c77d38 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/item/component/ResolvableProfile.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/item/component/ResolvableProfile.java ++++ b/net/minecraft/world/item/component/ResolvableProfile.java +@@ -49,7 +49,7 @@ + if (this.isResolved()) { + return CompletableFuture.completedFuture(this); + } else { +- return this.id.isPresent() ? SkullBlockEntity.fetchGameProfile(this.id.get()).thenApply(optional -> { ++ return this.id.isPresent() ? SkullBlockEntity.fetchGameProfile(this.id.get(), this.name.orElse(null)).thenApply(optional -> { // Paper - player profile events + GameProfile gameProfile = optional.orElseGet(() -> new GameProfile(this.id.get(), this.name.orElse(""))); + return new ResolvableProfile(gameProfile); + }) : SkullBlockEntity.fetchGameProfile(this.name.orElseThrow()).thenApply(profile -> { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch index 348568625e..a683a1cb1b 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch @@ -1,7 +1,54 @@ --- a/net/minecraft/world/level/block/entity/SkullBlockEntity.java +++ b/net/minecraft/world/level/block/entity/SkullBlockEntity.java -@@ -105,7 +105,7 @@ - ProfileResult profileResult = apiServices.sessionService().fetchProfile(uuid, true); +@@ -41,7 +41,7 @@ + @Nullable + private static LoadingCache>> profileCacheByName; + @Nullable +- private static LoadingCache>> profileCacheById; ++ private static LoadingCache, CompletableFuture>> profileCacheById; // Paper - player profile events + public static final Executor CHECKED_MAIN_THREAD_EXECUTOR = runnable -> { + Executor executor = mainThreadExecutor; + if (executor != null) { +@@ -76,9 +76,9 @@ + profileCacheById = CacheBuilder.newBuilder() + .expireAfterAccess(Duration.ofMinutes(10L)) + .maximumSize(256L) +- .build(new CacheLoader>>() { ++ .build(new CacheLoader<>() { // Paper - player profile events + @Override +- public CompletableFuture> load(UUID uUID) { ++ public CompletableFuture> load(com.mojang.datafixers.util.Pair uUID) { // Paper - player profile events + return SkullBlockEntity.fetchProfileById(uUID, apiServices, booleanSupplier); + } + }); +@@ -89,23 +89,29 @@ + .getAsync(name) + .thenCompose( + optional -> { +- LoadingCache>> loadingCache = profileCacheById; ++ LoadingCache, CompletableFuture>> loadingCache = profileCacheById; // Paper - player profile events + return loadingCache != null && !optional.isEmpty() +- ? loadingCache.getUnchecked(optional.get().getId()).thenApply(optional2 -> optional2.or(() -> optional)) ++ ? loadingCache.getUnchecked(new com.mojang.datafixers.util.Pair<>(optional.get().getId(), optional.get())).thenApply(optional2 -> optional2.or(() -> optional)) // Paper - player profile events + : CompletableFuture.completedFuture(Optional.empty()); + } + ); + } + +- static CompletableFuture> fetchProfileById(UUID uuid, Services apiServices, BooleanSupplier booleanSupplier) { ++ static CompletableFuture> fetchProfileById(com.mojang.datafixers.util.Pair pair, Services apiServices, BooleanSupplier booleanSupplier) { // Paper + return CompletableFuture.supplyAsync(() -> { + if (booleanSupplier.getAsBoolean()) { + return Optional.empty(); + } else { +- ProfileResult profileResult = apiServices.sessionService().fetchProfile(uuid, true); ++ // Paper start - fill player profile events ++ if (apiServices.sessionService() instanceof com.destroystokyo.paper.profile.PaperMinecraftSessionService paperService) { ++ final GameProfile profile = pair.getSecond() != null ? pair.getSecond() : new com.mojang.authlib.GameProfile(pair.getFirst(), ""); ++ return Optional.ofNullable(paperService.fetchProfile(profile, true)).map(ProfileResult::profile); ++ } ++ ProfileResult profileResult = apiServices.sessionService().fetchProfile(pair.getFirst(), true); ++ // Paper end - fill player profile events return Optional.ofNullable(profileResult).map(ProfileResult::profile); } - }, Util.backgroundExecutor().forName("fetchProfile")); @@ -9,3 +56,18 @@ } public static void clear() { +@@ -210,9 +216,11 @@ + : CompletableFuture.completedFuture(Optional.empty()); + } + +- public static CompletableFuture> fetchGameProfile(UUID uuid) { +- LoadingCache>> loadingCache = profileCacheById; +- return loadingCache != null ? loadingCache.getUnchecked(uuid) : CompletableFuture.completedFuture(Optional.empty()); ++ // Paper start - player profile events ++ public static CompletableFuture> fetchGameProfile(UUID uuid, @Nullable String name) { ++ LoadingCache, CompletableFuture>> loadingCache = profileCacheById; ++ return loadingCache != null ? loadingCache.getUnchecked(new com.mojang.datafixers.util.Pair<>(uuid, name != null ? new com.mojang.authlib.GameProfile(uuid, name) : null)) : CompletableFuture.completedFuture(Optional.empty()); ++ // Paper end - player profile events + } + + @Override diff --git a/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java b/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java index 985e6fc43a..d577384797 100644 --- a/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java +++ b/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java @@ -1,6 +1,7 @@ package com.destroystokyo.paper.profile; import com.mojang.authlib.Environment; +import com.mojang.authlib.GameProfile; import com.mojang.authlib.yggdrasil.ProfileResult; import com.mojang.authlib.yggdrasil.ServicesKeySet; import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService; @@ -15,7 +16,21 @@ public class PaperMinecraftSessionService extends YggdrasilMinecraftSessionServi super(servicesKeySet, proxy, environment); } - @Override + public @Nullable ProfileResult fetchProfile(GameProfile profile, final boolean requireSecure) { + CraftPlayerProfile playerProfile = (CraftPlayerProfile) CraftPlayerProfile.asBukkitMirror(profile); + new com.destroystokyo.paper.event.profile.PreFillProfileEvent(playerProfile).callEvent(); + profile = playerProfile.getGameProfile(); + if (profile.getProperties().containsKey("textures")) { + return new ProfileResult(profile, java.util.Collections.emptySet()); + } + ProfileResult result = super.fetchProfile(profile.getId(), requireSecure); + if (result != null) { + new com.destroystokyo.paper.event.profile.FillProfileEvent(CraftPlayerProfile.asBukkitMirror(result.profile())).callEvent(); + } + return result; + } + + @Override @io.papermc.paper.annotation.DoNotUse @Deprecated public @Nullable ProfileResult fetchProfile(final UUID profileId, final boolean requireSecure) { return super.fetchProfile(profileId, requireSecure); }