From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Aikar 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 getProperties() { + return properties; + } + + @Override + public void setProperties(Collection 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 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 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 serialize() { + Map map = new LinkedHashMap<>(); + if (!this.emptyUUID) { + map.put("uniqueId", this.getId().toString()); + } + if (!this.emptyName) { + map.put("name", getName()); + } + if (!this.properties.isEmpty()) { + List 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 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 { + + @Override + @Nonnull + public Iterator 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 c) { + //noinspection unchecked + setProperties((Collection) c); + return true; + } + + @Override + public boolean contains(Object o) { + return o instanceof ProfileProperty && profile.getProperties().containsKey(((ProfileProperty) o).getName()); + } + + private class ProfilePropertyIterator implements Iterator { + private final Iterator iterator; + + ProfilePropertyIterator(Iterator 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 7399358f18dc7869fbfe414186cf18414c1eaafc..a9dadd106ff0ac0f788c16048a73dfc3320b4944 100644 --- a/src/main/java/net/minecraft/server/Main.java +++ b/src/main/java/net/minecraft/server/Main.java @@ -168,7 +168,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 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 3f14b4bf9b986cce9abded06d1cfe72ca3c9bcaf..cc71aac4de75eafd7b7128b06b46d0439b77c486 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(); } @@ -2858,5 +2862,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 serialize() { + // Paper - diff on change Map 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 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; }