diff options
Diffstat (limited to 'patches/server/0135-Basic-PlayerProfile-API.patch')
-rw-r--r-- | patches/server/0135-Basic-PlayerProfile-API.patch | 767 |
1 files changed, 767 insertions, 0 deletions
diff --git a/patches/server/0135-Basic-PlayerProfile-API.patch b/patches/server/0135-Basic-PlayerProfile-API.patch new file mode 100644 index 0000000000..420fb11d4c --- /dev/null +++ b/patches/server/0135-Basic-PlayerProfile-API.patch @@ -0,0 +1,767 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar <[email protected]> +Date: Mon, 15 Jan 2018 22:11:48 -0500 +Subject: [PATCH] Basic PlayerProfile API + +Establishes base extension of profile systems for future edits too + +== AT == +public org.bukkit.craftbukkit.profile.CraftProfileProperty +public org.bukkit.craftbukkit.profile.CraftPlayerTextures +public org.bukkit.craftbukkit.profile.CraftPlayerTextures copyFrom(Lorg/bukkit/profile/PlayerTextures;)V +public org.bukkit.craftbukkit.profile.CraftPlayerTextures rebuildPropertyIfDirty()V +public org.bukkit.craftbukkit.profile.CraftPlayerProfile toString(Lcom/mojang/authlib/properties/PropertyMap;)Ljava/lang/String; +public org.bukkit.craftbukkit.profile.CraftPlayerProfile getProperty(Ljava/lang/String;)Lcom/mojang/authlib/properties/Property; +public org.bukkit.craftbukkit.profile.CraftPlayerProfile setProperty(Ljava/lang/String;Lcom/mojang/authlib/properties/Property;)V + +diff --git a/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8849862b45ccbbc635a1c316e9870bca81e55c04 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java +@@ -0,0 +1,451 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.google.common.base.Preconditions; ++import com.mojang.authlib.yggdrasil.ProfileResult; ++import io.papermc.paper.configuration.GlobalConfiguration; ++import com.google.common.base.Charsets; ++import com.google.common.collect.Iterables; ++import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.properties.Property; ++import com.mojang.authlib.properties.PropertyMap; ++import net.minecraft.Util; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.players.GameProfileCache; ++import net.minecraft.util.StringUtil; ++import net.minecraft.world.item.component.ResolvableProfile; ++import org.apache.commons.lang3.StringUtils; ++import org.apache.commons.lang3.Validate; ++import org.bukkit.configuration.serialization.SerializableAs; ++import org.bukkit.craftbukkit.configuration.ConfigSerializationUtil; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.profile.CraftPlayerTextures; ++import org.bukkit.craftbukkit.profile.CraftProfileProperty; ++import org.bukkit.profile.PlayerTextures; ++import org.jetbrains.annotations.NotNull; ++ ++import javax.annotation.Nonnull; ++import javax.annotation.Nullable; ++import java.util.*; ++import java.util.concurrent.CompletableFuture; ++ ++@SerializableAs("PlayerProfile") ++public class CraftPlayerProfile implements PlayerProfile, SharedPlayerProfile { ++ ++ private boolean emptyName; ++ private boolean emptyUUID; ++ private GameProfile profile; ++ private final PropertySet properties = new PropertySet(); ++ ++ public CraftPlayerProfile(CraftPlayer player) { ++ this.profile = player.getHandle().getGameProfile(); ++ } ++ ++ public CraftPlayerProfile(UUID id, String name) { ++ this.profile = createAuthLibProfile(id, name); ++ this.emptyName = name == null; ++ this.emptyUUID = id == null; ++ } ++ ++ public CraftPlayerProfile(GameProfile profile) { ++ Validate.notNull(profile, "GameProfile cannot be null!"); ++ this.profile = profile; ++ } ++ ++ public CraftPlayerProfile(ResolvableProfile resolvableProfile) { ++ this(resolvableProfile.id().orElse(null), resolvableProfile.name().orElse(null)); ++ copyProfileProperties(resolvableProfile.gameProfile(), this.profile); ++ } ++ ++ @Override ++ public boolean hasProperty(String property) { ++ return profile.getProperties().containsKey(property); ++ } ++ ++ @Override ++ public void setProperty(ProfileProperty property) { ++ String name = property.getName(); ++ PropertyMap properties = profile.getProperties(); ++ properties.removeAll(name); ++ ++ Preconditions.checkArgument(properties.size() < 16, "Cannot add more than 16 properties to a profile"); ++ properties.put(name, new Property(name, property.getValue(), property.getSignature())); ++ } ++ ++ @Override ++ public CraftPlayerTextures getTextures() { ++ return new CraftPlayerTextures(this); ++ } ++ ++ @Override ++ public void setTextures(@Nullable PlayerTextures textures) { ++ if (textures == null) { ++ this.removeProperty("textures"); ++ } else { ++ CraftPlayerTextures craftPlayerTextures = new CraftPlayerTextures(this); ++ craftPlayerTextures.copyFrom(textures); ++ craftPlayerTextures.rebuildPropertyIfDirty(); ++ } ++ } ++ ++ public GameProfile getGameProfile() { ++ return profile; ++ } ++ ++ @Nullable ++ @Override ++ public UUID getId() { ++ return this.emptyUUID ? null : this.profile.getId(); ++ } ++ ++ @Override ++ @Deprecated(forRemoval = true) ++ public UUID setId(@Nullable UUID uuid) { ++ final GameProfile previousProfile = this.profile; ++ final UUID previousId = this.getId(); ++ this.profile = createAuthLibProfile(uuid, previousProfile.getName()); ++ copyProfileProperties(previousProfile, this.profile); ++ this.emptyUUID = uuid == null; ++ return previousId; ++ } ++ ++ @Override ++ public UUID getUniqueId() { ++ return getId(); ++ } ++ ++ @Nullable ++ @Override ++ public String getName() { ++ return this.emptyName ? null : this.profile.getName(); ++ } ++ ++ @Override ++ @Deprecated(forRemoval = true) ++ public String setName(@Nullable String name) { ++ GameProfile prev = this.profile; ++ this.profile = createAuthLibProfile(prev.getId(), name); ++ copyProfileProperties(prev, this.profile); ++ this.emptyName = name == null; ++ return prev.getName(); ++ } ++ ++ @Nonnull ++ @Override ++ public Set<ProfileProperty> getProperties() { ++ return properties; ++ } ++ ++ @Override ++ public void setProperties(Collection<ProfileProperty> properties) { ++ properties.forEach(this::setProperty); ++ } ++ ++ @Override ++ public void clearProperties() { ++ profile.getProperties().clear(); ++ } ++ ++ @Override ++ public boolean removeProperty(String property) { ++ return !profile.getProperties().removeAll(property).isEmpty(); ++ } ++ ++ @Nullable ++ @Override ++ public Property getProperty(String property) { ++ return Iterables.getFirst(this.profile.getProperties().get(property), null); ++ } ++ ++ @Nullable ++ @Override ++ public void setProperty(@NotNull String propertyName, @Nullable Property property) { ++ if (property != null) { ++ this.setProperty(new ProfileProperty(propertyName, property.value(), property.signature())); ++ } else { ++ profile.getProperties().removeAll(propertyName); ++ } ++ } ++ ++ @Override ++ public @NotNull GameProfile buildGameProfile() { ++ GameProfile profile = new GameProfile(this.profile.getId(), this.profile.getName()); ++ profile.getProperties().putAll(this.profile.getProperties()); ++ return profile; ++ } ++ ++ @Override ++ public @NotNull ResolvableProfile buildResolvableProfile() { ++ if (this.emptyName || this.emptyUUID) { ++ return new ResolvableProfile(this.emptyName ? Optional.empty() : Optional.of(this.profile.getName()), this.emptyUUID ? Optional.empty() : Optional.of(this.profile.getId()), this.profile.getProperties()); ++ } else { ++ return new ResolvableProfile(this.buildGameProfile()); ++ } ++ } ++ ++ @Override ++ public CraftPlayerProfile clone() { ++ CraftPlayerProfile clone = new CraftPlayerProfile(this.getId(), this.getName()); ++ clone.setProperties(getProperties()); ++ return clone; ++ } ++ ++ @Override ++ public boolean isComplete() { ++ return this.getId() != null && StringUtils.isNotBlank(this.getName()); ++ } ++ ++ @Override ++ public @NotNull CompletableFuture<PlayerProfile> update() { ++ return CompletableFuture.supplyAsync(() -> { ++ final CraftPlayerProfile clone = clone(); ++ clone.complete(true); ++ return clone; ++ }, Util.PROFILE_EXECUTOR); ++ } ++ ++ @Override ++ public boolean completeFromCache() { ++ return completeFromCache(false, GlobalConfiguration.get().proxies.isProxyOnlineMode()); ++ } ++ ++ public boolean completeFromCache(boolean onlineMode) { ++ return completeFromCache(false, onlineMode); ++ } ++ ++ public boolean completeFromCache(boolean lookupUUID, boolean onlineMode) { ++ MinecraftServer server = MinecraftServer.getServer(); ++ String name = profile.getName(); ++ GameProfileCache userCache = server.getProfileCache(); ++ if (this.getId() == null) { ++ final GameProfile profile; ++ if (onlineMode) { ++ profile = lookupUUID ? userCache.get(name).orElse(null) : userCache.getProfileIfCached(name); ++ } else { ++ // Make an OfflinePlayer using an offline mode UUID since the name has no profile ++ profile = new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)), name); ++ } ++ if (profile != null) { ++ // if old has it, assume its newer, so overwrite, else use cached if it was set and ours wasn't ++ copyProfileProperties(this.profile, profile); ++ this.profile = profile; ++ this.emptyUUID = false; // UUID was just retrieved from user cache and profile isn't null (so a completed profile was found) ++ } ++ } ++ ++ if ((profile.getName().isEmpty() || !hasTextures()) && this.getId() != null) { ++ Optional<GameProfile> optProfile = userCache.get(this.profile.getId()); ++ if (optProfile.isPresent()) { ++ GameProfile profile = optProfile.get(); ++ if (this.profile.getName().isEmpty()) { ++ // if old has it, assume its newer, so overwrite, else use cached if it was set and ours wasn't ++ copyProfileProperties(this.profile, profile); ++ this.profile = profile; ++ this.emptyName = false; // Name was just retrieved via the userCache ++ } else if (profile != this.profile) { ++ copyProfileProperties(profile, this.profile); ++ } ++ } ++ } ++ return this.isComplete(); ++ } ++ ++ public boolean complete(boolean textures) { ++ return complete(textures, GlobalConfiguration.get().proxies.isProxyOnlineMode()); ++ } ++ ++ public boolean complete(boolean textures, boolean onlineMode) { ++ if (this.isComplete() && (!textures || hasTextures())) { // Don't do lookup if we already have everything ++ return true; ++ } ++ ++ MinecraftServer server = MinecraftServer.getServer(); ++ boolean isCompleteFromCache = this.completeFromCache(true, onlineMode); ++ if (onlineMode && (!isCompleteFromCache || (textures && !hasTextures()))) { ++ ProfileResult result = server.getSessionService().fetchProfile(this.profile.getId(), true); ++ if (result != null && result.profile() != null) { ++ copyProfileProperties(result.profile(), this.profile, true); ++ } ++ if (this.isComplete()) { ++ server.getProfileCache().add(this.profile); ++ } ++ } ++ return this.isComplete() && (!onlineMode || !textures || hasTextures()); ++ } ++ ++ private static void copyProfileProperties(GameProfile source, GameProfile target) { ++ copyProfileProperties(source, target, false); ++ } ++ ++ private static void copyProfileProperties(GameProfile source, GameProfile target, boolean clearTarget) { ++ if (source == target) { ++ throw new IllegalArgumentException("Source and target profiles are the same (" + source + ")"); ++ } ++ PropertyMap sourceProperties = source.getProperties(); ++ PropertyMap targetProperties = target.getProperties(); ++ if (clearTarget) targetProperties.clear(); ++ if (sourceProperties.isEmpty()) { ++ return; ++ } ++ ++ for (Property property : sourceProperties.values()) { ++ targetProperties.removeAll(property.name()); ++ targetProperties.put(property.name(), property); ++ } ++ } ++ ++ private static GameProfile createAuthLibProfile(UUID uniqueId, String name) { ++ Preconditions.checkArgument(name == null || name.length() <= 16, "Name cannot be longer than 16 characters"); ++ Preconditions.checkArgument(name == null || StringUtil.isValidPlayerName(name), "The name of the profile contains invalid characters: %s", name); ++ return new GameProfile( ++ uniqueId != null ? uniqueId : Util.NIL_UUID, ++ name != null ? name : "" ++ ); ++ } ++ ++ private static ProfileProperty toBukkit(Property property) { ++ return new ProfileProperty(property.name(), property.value(), property.signature()); ++ } ++ ++ public static PlayerProfile asBukkitCopy(GameProfile gameProfile) { ++ CraftPlayerProfile profile = new CraftPlayerProfile(gameProfile.getId(), gameProfile.getName()); ++ copyProfileProperties(gameProfile, profile.profile); ++ return profile; ++ } ++ ++ public static PlayerProfile asBukkitMirror(GameProfile profile) { ++ return new CraftPlayerProfile(profile); ++ } ++ ++ public static Property asAuthlib(ProfileProperty property) { ++ return new Property(property.getName(), property.getValue(), property.getSignature()); ++ } ++ ++ public static GameProfile asAuthlibCopy(PlayerProfile profile) { ++ CraftPlayerProfile craft = ((CraftPlayerProfile) profile); ++ return asAuthlib(craft.clone()); ++ } ++ ++ public static GameProfile asAuthlib(PlayerProfile profile) { ++ CraftPlayerProfile craft = ((CraftPlayerProfile) profile); ++ return craft.getGameProfile(); ++ } ++ ++ public static ResolvableProfile asResolvableProfileCopy(PlayerProfile profile) { ++ return ((SharedPlayerProfile) profile).buildResolvableProfile(); ++ } ++ ++ @Override ++ public @NotNull Map<String, Object> serialize() { ++ Map<String, Object> map = new LinkedHashMap<>(); ++ if (!this.emptyUUID) { ++ map.put("uniqueId", this.getId().toString()); ++ } ++ if (!this.emptyName) { ++ map.put("name", getName()); ++ } ++ if (!this.properties.isEmpty()) { ++ List<Object> propertiesData = new ArrayList<>(); ++ for (ProfileProperty property : properties) { ++ propertiesData.add(CraftProfileProperty.serialize(new Property(property.getName(), property.getValue(), property.getSignature()))); ++ } ++ map.put("properties", propertiesData); ++ } ++ return map; ++ } ++ ++ public static CraftPlayerProfile deserialize(Map<String, Object> map) { ++ UUID uniqueId = ConfigSerializationUtil.getUuid(map, "uniqueId", true); ++ String name = ConfigSerializationUtil.getString(map, "name", true); ++ ++ // This also validates the deserialized unique id and name (ensures that not both are null): ++ CraftPlayerProfile profile = new CraftPlayerProfile(uniqueId, name); ++ ++ if (map.containsKey("properties")) { ++ for (Object propertyData : (List<?>) map.get("properties")) { ++ if (!(propertyData instanceof Map)) { ++ throw new IllegalArgumentException("Property data (" + propertyData + ") is not a valid Map"); ++ } ++ Property property = CraftProfileProperty.deserialize((Map<?, ?>) propertyData); ++ profile.profile.getProperties().put(property.name(), property); ++ } ++ } ++ ++ return profile; ++ } ++ ++ @Override ++ public boolean equals(final Object o) { ++ if (this == o) return true; ++ if (o == null || this.getClass() != o.getClass()) return false; ++ final CraftPlayerProfile that = (CraftPlayerProfile) o; ++ return this.emptyName == that.emptyName && this.emptyUUID == that.emptyUUID && Objects.equals(this.profile, that.profile); ++ } ++ ++ @Override ++ public int hashCode() { ++ return Objects.hash(this.emptyName, this.emptyUUID, this.profile); ++ } ++ ++ @Override ++ public String toString() { ++ return "CraftPlayerProfile [uniqueId=" + getId() + ++ ", name=" + getName() + ++ ", properties=" + org.bukkit.craftbukkit.profile.CraftPlayerProfile.toString(this.profile.getProperties()) + ++ "]"; ++ } ++ ++ private class PropertySet extends AbstractSet<ProfileProperty> { ++ ++ @Override ++ @Nonnull ++ public Iterator<ProfileProperty> iterator() { ++ return new ProfilePropertyIterator(profile.getProperties().values().iterator()); ++ } ++ ++ @Override ++ public int size() { ++ return profile.getProperties().size(); ++ } ++ ++ @Override ++ public boolean add(ProfileProperty property) { ++ setProperty(property); ++ return true; ++ } ++ ++ @Override ++ public boolean addAll(Collection<? extends ProfileProperty> c) { ++ //noinspection unchecked ++ setProperties((Collection<ProfileProperty>) c); ++ return true; ++ } ++ ++ @Override ++ public boolean contains(Object o) { ++ return o instanceof ProfileProperty && profile.getProperties().containsKey(((ProfileProperty) o).getName()); ++ } ++ ++ private class ProfilePropertyIterator implements Iterator<ProfileProperty> { ++ private final Iterator<Property> iterator; ++ ++ ProfilePropertyIterator(Iterator<Property> iterator) { ++ this.iterator = iterator; ++ } ++ ++ @Override ++ public boolean hasNext() { ++ return iterator.hasNext(); ++ } ++ ++ @Override ++ public ProfileProperty next() { ++ return toBukkit(iterator.next()); ++ } ++ ++ @Override ++ public void remove() { ++ iterator.remove(); ++ } ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java b/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java +new file mode 100644 +index 0000000000000000000000000000000000000000..48e774677edf17d4a99ae9ed23d1b371dab41abb +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java +@@ -0,0 +1,30 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.Environment; ++import com.mojang.authlib.EnvironmentParser; ++import com.mojang.authlib.GameProfileRepository; ++import com.mojang.authlib.minecraft.MinecraftSessionService; ++import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; ++import com.mojang.authlib.yggdrasil.YggdrasilEnvironment; ++ ++import java.net.Proxy; ++ ++public class PaperAuthenticationService extends YggdrasilAuthenticationService { ++ ++ private final Environment environment; ++ ++ public PaperAuthenticationService(Proxy proxy) { ++ super(proxy); ++ this.environment = EnvironmentParser.getEnvironmentFromProperties().orElse(YggdrasilEnvironment.PROD.getEnvironment()); ++ } ++ ++ @Override ++ public MinecraftSessionService createMinecraftSessionService() { ++ return new PaperMinecraftSessionService(this.getServicesKeySet(), this.getProxy(), this.environment); ++ } ++ ++ @Override ++ public GameProfileRepository createProfileRepository() { ++ return new PaperGameProfileRepository(this.getProxy(), this.environment); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7b9e797b42c88b17d6a7c590a423f4e85d99a59d +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java +@@ -0,0 +1,17 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.Environment; ++import com.mojang.authlib.ProfileLookupCallback; ++import com.mojang.authlib.yggdrasil.YggdrasilGameProfileRepository; ++import java.net.Proxy; ++ ++public class PaperGameProfileRepository extends YggdrasilGameProfileRepository { ++ public PaperGameProfileRepository(Proxy proxy, Environment environment) { ++ super(proxy, environment); ++ } ++ ++ @Override ++ public void findProfilesByNames(String[] names, ProfileLookupCallback callback) { ++ super.findProfilesByNames(names, callback); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java +new file mode 100644 +index 0000000000000000000000000000000000000000..985e6fc43a0946943847e0c283426242ef594a26 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java +@@ -0,0 +1,22 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.Environment; ++import com.mojang.authlib.yggdrasil.ProfileResult; ++import com.mojang.authlib.yggdrasil.ServicesKeySet; ++import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService; ++ ++import java.net.Proxy; ++import java.util.UUID; ++import org.jetbrains.annotations.Nullable; ++ ++public class PaperMinecraftSessionService extends YggdrasilMinecraftSessionService { ++ ++ protected PaperMinecraftSessionService(ServicesKeySet servicesKeySet, Proxy proxy, Environment environment) { ++ super(servicesKeySet, proxy, environment); ++ } ++ ++ @Override ++ public @Nullable ProfileResult fetchProfile(final UUID profileId, final boolean requireSecure) { ++ return super.fetchProfile(profileId, requireSecure); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/profile/SharedPlayerProfile.java b/src/main/java/com/destroystokyo/paper/profile/SharedPlayerProfile.java +new file mode 100644 +index 0000000000000000000000000000000000000000..332700f84c5587e47a4d2056bfbb5413de97e9f2 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/SharedPlayerProfile.java +@@ -0,0 +1,26 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.properties.Property; ++import net.minecraft.world.item.component.ResolvableProfile; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.util.UUID; ++ ++public interface SharedPlayerProfile { ++ ++ @Nullable UUID getUniqueId(); ++ ++ @Nullable String getName(); ++ ++ boolean removeProperty(@NotNull String property); ++ ++ @Nullable Property getProperty(@NotNull String propertyName); ++ ++ @Nullable void setProperty(@NotNull String propertyName, @Nullable Property property); ++ ++ @NotNull GameProfile buildGameProfile(); ++ ++ @NotNull ResolvableProfile buildResolvableProfile(); ++} +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index a9f09f2939a0fbf20be7f8bc27a6d8b961fb748a..577140660c96da69f68ec27ad5ab7da2c836974b 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -170,7 +170,7 @@ public class Main { + } + + File file = (File) optionset.valueOf("universe"); // CraftBukkit +- Services services = Services.create(new YggdrasilAuthenticationService(Proxy.NO_PROXY), file, optionset); // Paper - pass OptionSet to load paper config files ++ Services services = Services.create(new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY), file, optionset); // Paper - pass OptionSet to load paper config files; override authentication service + // CraftBukkit start + String s = (String) Optional.ofNullable((String) optionset.valueOf("world")).orElse(dedicatedserversettings.getProperties().levelName); + LevelStorageSource convertable = LevelStorageSource.createDefault(file.toPath()); +diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java +index 416b26c2ab62b29d640169166980e398d5824b14..774d81c702edb76a2f6184d4dc53687de6734a79 100644 +--- a/src/main/java/net/minecraft/server/players/GameProfileCache.java ++++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java +@@ -126,6 +126,17 @@ public class GameProfileCache { + return this.operationCount.incrementAndGet(); + } + ++ // Paper start ++ public @Nullable GameProfile getProfileIfCached(String name) { ++ GameProfileCache.GameProfileInfo entry = this.profilesByName.get(name.toLowerCase(Locale.ROOT)); ++ if (entry == null) { ++ return null; ++ } ++ entry.setLastAccess(this.getNextOperation()); ++ return entry.getProfile(); ++ } ++ // Paper end ++ + public Optional<GameProfile> get(String name) { + String s1 = name.toLowerCase(Locale.ROOT); + GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByName.get(s1); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index f578d18feb0497cc1a67415aed44f6d0e5029a01..267df83abe7f677d48d63432f9dba64781e777e2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -265,6 +265,9 @@ import org.yaml.snakeyaml.error.MarkedYAMLException; + + import net.md_5.bungee.api.chat.BaseComponent; // Spigot + ++import javax.annotation.Nullable; // Paper ++import javax.annotation.Nonnull; // Paper ++ + public final class CraftServer implements Server { + private final String serverName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); // Paper + private final String serverVersion; +@@ -310,6 +313,7 @@ public final class CraftServer implements Server { + static { + ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); + ConfigurationSerialization.registerClass(CraftPlayerProfile.class); ++ ConfigurationSerialization.registerClass(com.destroystokyo.paper.profile.CraftPlayerProfile.class); // Paper + CraftItemFactory.instance(); + CraftEntityFactory.instance(); + } +@@ -2868,5 +2872,39 @@ public final class CraftServer implements Server { + public boolean suggestPlayerNamesWhenNullTabCompletions() { + return io.papermc.paper.configuration.GlobalConfiguration.get().commands.suggestPlayerNamesWhenNullTabCompletions; + } ++ ++ @Override ++ public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull UUID uuid) { ++ return createProfile(uuid, null); ++ } ++ ++ @Override ++ public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull String name) { ++ return createProfile(null, name); ++ } ++ ++ @Override ++ public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nullable UUID uuid, @Nullable String name) { ++ Player player = uuid != null ? Bukkit.getPlayer(uuid) : (name != null ? Bukkit.getPlayerExact(name) : null); ++ if (player != null) return new com.destroystokyo.paper.profile.CraftPlayerProfile((CraftPlayer) player); ++ ++ return new com.destroystokyo.paper.profile.CraftPlayerProfile(uuid, name); ++ } ++ ++ @Override ++ public com.destroystokyo.paper.profile.PlayerProfile createProfileExact(@Nullable UUID uuid, @Nullable String name) { ++ Player player = uuid != null ? Bukkit.getPlayer(uuid) : (name != null ? Bukkit.getPlayerExact(name) : null); ++ if (player == null) { ++ return new com.destroystokyo.paper.profile.CraftPlayerProfile(uuid, name); ++ } ++ ++ if (java.util.Objects.equals(uuid, player.getUniqueId()) && java.util.Objects.equals(name, player.getName())) { ++ return new com.destroystokyo.paper.profile.CraftPlayerProfile((CraftPlayer) player); ++ } ++ ++ final com.destroystokyo.paper.profile.CraftPlayerProfile profile = new com.destroystokyo.paper.profile.CraftPlayerProfile(uuid, name); ++ profile.getGameProfile().getProperties().putAll(((CraftPlayer) player).getHandle().getGameProfile().getProperties()); ++ return profile; ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java +index 210fdad5f2041368fc359b275f1cbf05b70b6989..d9cc76d7e60001957c0f59fdc32d016f3589aa08 100644 +--- a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java ++++ b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java +@@ -31,7 +31,7 @@ import org.bukkit.profile.PlayerTextures; + import org.jetbrains.annotations.ApiStatus; + + @SerializableAs("PlayerProfile") +-public final class CraftPlayerProfile implements PlayerProfile { ++public final class CraftPlayerProfile implements PlayerProfile, com.destroystokyo.paper.profile.SharedPlayerProfile { // Paper + + @Nonnull + public static GameProfile validateSkullProfile(@Nonnull GameProfile gameProfile) { +@@ -132,8 +132,10 @@ public final class CraftPlayerProfile implements PlayerProfile { + } + } + +- void removeProperty(String propertyName) { +- this.properties.removeAll(propertyName); ++ // Paper start - change return value for shared interface ++ public boolean removeProperty(String propertyName) { ++ return !this.properties.removeAll(propertyName).isEmpty(); ++ // Paper end + } + + void rebuildDirtyProperties() { +@@ -283,6 +285,7 @@ public final class CraftPlayerProfile implements PlayerProfile { + + @Override + public Map<String, Object> serialize() { ++ // Paper - diff on change + Map<String, Object> map = new LinkedHashMap<>(); + if (this.uniqueId != null) { + map.put("uniqueId", this.uniqueId.toString()); +@@ -296,10 +299,12 @@ public final class CraftPlayerProfile implements PlayerProfile { + this.properties.forEach((propertyName, property) -> propertiesData.add(CraftProfileProperty.serialize(property))); + map.put("properties", propertiesData); + } ++ // Paper - diff on change + return map; + } + + public static CraftPlayerProfile deserialize(Map<String, Object> map) { ++ // Paper - diff on change + UUID uniqueId = ConfigSerializationUtil.getUuid(map, "uniqueId", true); + String name = ConfigSerializationUtil.getString(map, "name", true); + +@@ -313,7 +318,7 @@ public final class CraftPlayerProfile implements PlayerProfile { + profile.properties.put(property.name(), property); + } + } +- ++ // Paper - diff on change + return profile; + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java +index 78e47fec27f4dd955632d03f7181682af0429961..0dce455fb47b3f5a2eb2b15a1cdbc4c6a54b7b69 100644 +--- a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java ++++ b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java +@@ -48,7 +48,7 @@ public final class CraftPlayerTextures implements PlayerTextures { + } + } + +- private final CraftPlayerProfile profile; ++ private final com.destroystokyo.paper.profile.SharedPlayerProfile profile; // Paper + + // The textures data is loaded lazily: + private boolean loaded = false; +@@ -67,7 +67,7 @@ public final class CraftPlayerTextures implements PlayerTextures { + // GameProfiles (even if these modifications are later reverted). + private boolean dirty = false; + +- CraftPlayerTextures(@Nonnull CraftPlayerProfile profile) { ++ public CraftPlayerTextures(@Nonnull com.destroystokyo.paper.profile.SharedPlayerProfile profile) { // Paper + this.profile = profile; + } + |