Paper/patches/api/0060-Basic-PlayerProfile-AP...

503 lines
20 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Mon, 15 Jan 2018 21:46:46 -0500
Subject: [PATCH] Basic PlayerProfile API
Provides basic elements of a PlayerProfile to be used by future API/events
diff --git a/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java b/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java
new file mode 100644
index 0000000000000000000000000000000000000000..b76f13a0266806544bde13952476d4867caaf25b
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java
@@ -0,0 +1,231 @@
+package com.destroystokyo.paper.profile;
+
+import java.util.Collection;
+import java.util.Set;
+import java.util.UUID;
+
+import java.util.concurrent.CompletableFuture;
+import org.bukkit.profile.PlayerTextures;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Represents a players profile for the game, such as UUID, Name, and textures.
+ */
+public interface PlayerProfile extends org.bukkit.profile.PlayerProfile {
+
+ /**
+ * @return The players name, if set
+ */
+ @Nullable
+ String getName();
+
+ /**
+ * Sets this profiles Name
+ *
+ * @param name The new Name
+ * @return The previous Name
+ */
+ @NotNull
+ @Deprecated(forRemoval = true)
+ String setName(@Nullable String name);
+
+ /**
+ * @return The players unique identifier, if set
+ */
+ @Nullable UUID getId();
+
+ /**
+ * Sets this profiles UUID
+ *
+ * @param uuid The new UUID
+ * @return The previous UUID
+ */
+ @Nullable
+ @Deprecated(forRemoval = true)
+ UUID setId(@Nullable UUID uuid);
+
+ /**
+ * Gets the {@link PlayerTextures} of this profile.
+ * This will build a snapshot of the current texture data once
+ * requested inside PlayerTextures.
+ *
+ * @return the textures, not <code>null</code>
+ */
+ @NotNull
+ PlayerTextures getTextures();
+
+ /**
+ * Copies the given textures.
+ *
+ * @param textures the textures to copy, or <code>null</code> to clear the
+ * textures
+ */
+ void setTextures(@Nullable PlayerTextures textures);
+
+ /**
+ * @return A Mutable set of this players properties, such as textures.
+ * Values specified here are subject to implementation details.
+ */
+ @NotNull Set<ProfileProperty> getProperties();
+
+ /**
+ * Check if the Profile has the specified property
+ * @param property Property name to check
+ * @return If the property is set
+ */
+ boolean hasProperty(@Nullable String property);
+
+ /**
+ * Sets a property. If the property already exists, the previous one will be replaced
+ * @param property Property to set.
+ */
+ void setProperty(@NotNull ProfileProperty property);
+
+ /**
+ * Sets multiple properties. If any of the set properties already exist, it will be replaced
+ * @param properties The properties to set
+ */
+ void setProperties(@NotNull Collection<ProfileProperty> properties);
+
+ /**
+ * Removes a specific property from this profile
+ * @param property The property to remove
+ * @return If a property was removed
+ */
+ boolean removeProperty(@Nullable String property);
+
+ /**
+ * Removes a specific property from this profile
+ * @param property The property to remove
+ * @return If a property was removed
+ */
+ default boolean removeProperty(@NotNull ProfileProperty property) {
+ return removeProperty(property.getName());
+ }
+
+ /**
+ * Removes all properties in the collection
+ * @param properties The properties to remove
+ * @return If any property was removed
+ */
+ default boolean removeProperties(@NotNull Collection<ProfileProperty> properties) {
+ boolean removed = false;
+ for (ProfileProperty property : properties) {
+ if (removeProperty(property)) {
+ removed = true;
+ }
+ }
+ return removed;
+ }
+
+ /**
+ * Clears all properties on this profile
+ */
+ void clearProperties();
+
+ /**
+ * @return If the profile is now complete (has UUID and Name)
+ */
+ boolean isComplete();
+
+ /**
+ * Like {@link #complete(boolean)} but will try only from cache, and not make network calls
+ * Does not account for textures.
+ *
+ * @return If the profile is now complete (has UUID and Name)
+ */
+ boolean completeFromCache();
+
+ /**
+ * Like {@link #complete(boolean)} but will try only from cache, and not make network calls
+ * Does not account for textures.
+ *
+ * @param onlineMode Treat this as online mode or not
+ * @return If the profile is now complete (has UUID and Name)
+ */
+ boolean completeFromCache(boolean onlineMode);
+
+ /**
+ * Like {@link #complete(boolean)} but will try only from cache, and not make network calls
+ * Does not account for textures.
+ *
+ * @param lookupUUID If only name is supplied, should we do a UUID lookup
+ * @param onlineMode Treat this as online mode or not
+ * @return If the profile is now complete (has UUID and Name)
+ */
+ boolean completeFromCache(boolean lookupUUID, boolean onlineMode);
+
+ /**
+ * If this profile is not complete, then make the API call to complete it.
+ * This is a blocking operation and should be done asynchronously.
+ *
+ * This will also complete textures. If you do not want to load textures, use {{@link #complete(boolean)}}
+ * @return If the profile is now complete (has UUID and Name) (if you get rate limited, this operation may fail)
+ */
+ default boolean complete() {
+ return complete(true);
+ }
+
+ /**
+ * If this profile is not complete, then make the API call to complete it.
+ * This is a blocking operation and should be done asynchronously.
+ *
+ * Optionally will also fill textures.
+ *
+ * Online mode will be automatically determined
+ * @param textures controls if we should fill the profile with texture properties
+ * @return If the profile is now complete (has UUID and Name) (if you get rate limited, this operation may fail)
+ */
+ boolean complete(boolean textures);
+
+ /**
+ * If this profile is not complete, then make the API call to complete it.
+ * This is a blocking operation and should be done asynchronously.
+ *
+ * Optionally will also fill textures.
+ * @param textures controls if we should fill the profile with texture properties
+ * @param onlineMode Treat this server as online mode or not
+ * @return If the profile is now complete (has UUID and Name) (if you get rate limited, this operation may fail)
+ */
+ boolean complete(boolean textures, boolean onlineMode);
+
+ /**
+ * Produces an updated player profile based on this profile.
+ * <p>
+ * This tries to produce a completed profile by filling in missing
+ * properties (name, unique id, textures, etc.), and updates existing
+ * properties (e.g. name, textures, etc.) to their official and up-to-date
+ * values. This operation does not alter the current profile, but produces a
+ * new updated {@link PlayerProfile}.
+ * <p>
+ * If no player exists for the unique id or name of this profile, this
+ * operation yields a profile that is equal to the current profile, which
+ * might not be complete.
+ * <p>
+ * This is an asynchronous operation: Updating the profile can result in an
+ * outgoing connection in another thread in order to fetch the latest
+ * profile properties. The returned {@link CompletableFuture} will be
+ * completed once the updated profile is available. In order to not block
+ * the server's main thread, you should not wait for the result of the
+ * returned CompletableFuture on the server's main thread. Instead, if you
+ * want to do something with the updated player profile on the server's main
+ * thread once it is available, you could do something like this:
+ * <pre>
+ * profile.update().thenAcceptAsync(updatedProfile -> {
+ * // Do something with the updated profile:
+ * // ...
+ * }, runnable -> Bukkit.getScheduler().runTask(plugin, runnable));
+ * </pre>
+ */
+ @Override
+ @NotNull CompletableFuture<PlayerProfile> update();
+
+ /**
+ * Whether this Profile has textures associated to it
+ * @return If it has a textures property
+ */
+ default boolean hasTextures() {
+ return hasProperty("textures");
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/profile/ProfileProperty.java b/src/main/java/com/destroystokyo/paper/profile/ProfileProperty.java
new file mode 100644
index 0000000000000000000000000000000000000000..7b3b6ef533d32169fbeca389bd61cfc6b0e0faee
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/profile/ProfileProperty.java
@@ -0,0 +1,72 @@
+package com.destroystokyo.paper.profile;
+
+import com.google.common.base.Preconditions;
+
+import java.util.Objects;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Represents a property on a {@link PlayerProfile}
+ */
+public class ProfileProperty {
+ private final String name;
+ private final String value;
+ private final String signature;
+
+ public ProfileProperty(@NotNull String name, @NotNull String value) {
+ this(name, value, null);
+ }
+
+ public ProfileProperty(@NotNull String name, @NotNull String value, @Nullable String signature) {
+ this.name = Preconditions.checkNotNull(name, "ProfileProperty name can not be null");
+ this.value = Preconditions.checkNotNull(value, "ProfileProperty value can not be null");
+ this.signature = signature;
+ }
+
+ /**
+ * @return The property name, ie "textures"
+ */
+ @NotNull
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return The property value, likely to be base64 encoded
+ */
+ @NotNull
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * @return A signature from Mojang for signed properties
+ */
+ @Nullable
+ public String getSignature() {
+ return signature;
+ }
+
+ /**
+ * @return If this property has a signature or not
+ */
+ public boolean isSigned() {
+ return this.signature != null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ProfileProperty that = (ProfileProperty) o;
+ return Objects.equals(name, that.name) &&
+ Objects.equals(value, that.value) &&
+ Objects.equals(signature, that.signature);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name);
+ }
+}
diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
index 3b7087d5c71a498f513f67514db9e118780363c7..b165a4f99802ced243f1fb56af2bcf2c2ab7abf1 100644
--- a/src/main/java/org/bukkit/Bukkit.java
+++ b/src/main/java/org/bukkit/Bukkit.java
@@ -2357,6 +2357,83 @@ public final class Bukkit {
public static boolean suggestPlayerNamesWhenNullTabCompletions() {
return server.suggestPlayerNamesWhenNullTabCompletions();
}
+
+ /**
+ * Creates a PlayerProfile for the specified uuid, with name as null.
+ *
+ * If a player with the passed uuid exists on the server at the time of creation, the returned player profile will
+ * be populated with the properties of said player (including their uuid and name).
+ *
+ * @param uuid UUID to create profile for
+ * @return A PlayerProfile object
+ */
+ @NotNull
+ public static com.destroystokyo.paper.profile.PlayerProfile createProfile(@NotNull UUID uuid) {
+ return server.createProfile(uuid);
+ }
+
+ /**
+ * Creates a PlayerProfile for the specified name, with UUID as null.
+ *
+ * If a player with the passed name exists on the server at the time of creation, the returned player profile will
+ * be populated with the properties of said player (including their uuid and name).
+ * <p>
+ * E.g. if the player 'jeb_' is currently playing on the server, calling {@code createProfile("JEB_")} will
+ * yield a profile with the name 'jeb_', their uuid and their textures.
+ * To bypass this pre-population on a case-insensitive name match, see {@link #createProfileExact(UUID, String)}.
+ * <p>
+ *
+ * @param name Name to create profile for
+ * @return A PlayerProfile object
+ */
+ @NotNull
+ public static com.destroystokyo.paper.profile.PlayerProfile createProfile(@NotNull String name) {
+ return server.createProfile(name);
+ }
+
+ /**
+ * Creates a PlayerProfile for the specified name/uuid
+ *
+ * Both UUID and Name can not be null at same time. One must be supplied.
+ * If a player with the passed uuid or name exists on the server at the time of creation, the returned player
+ * profile will be populated with the properties of said player (including their uuid and name).
+ * <p>
+ * E.g. if the player 'jeb_' is currently playing on the server, calling {@code createProfile(null, "JEB_")} will
+ * yield a profile with the name 'jeb_', their uuid and their textures.
+ * To bypass this pre-population on an case-insensitive name match, see {@link #createProfileExact(UUID, String)}.
+ * <p>
+ *
+ * The name comparison will compare the {@link String#toLowerCase()} version of both the passed name parameter and
+ * a players name to honour the case-insensitive nature of a mojang profile lookup.
+ *
+ * @param uuid UUID to create profile for
+ * @param name Name to create profile for
+ * @return A PlayerProfile object
+ */
+ @NotNull
+ public static com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nullable UUID uuid, @Nullable String name) {
+ return server.createProfile(uuid, name);
+ }
+
+ /**
+ * Creates an exact PlayerProfile for the specified name/uuid
+ *
+ * Both UUID and Name can not be null at same time. One must be supplied.
+ * If a player with the passed uuid or name exists on the server at the time of creation, the returned player
+ * profile will be populated with the properties of said player.
+ * <p>
+ * Compared to {@link #createProfile(UUID, String)}, this method will never mutate the passed uuid or name.
+ * If a player with either the same uuid or a matching name (case-insensitive) is found on the server, their
+ * properties, such as textures, will be pre-populated in the profile, however the passed uuid and name stay intact.
+ *
+ * @param uuid UUID to create profile for
+ * @param name Name to create profile for
+ * @return A PlayerProfile object
+ */
+ @NotNull
+ public static com.destroystokyo.paper.profile.PlayerProfile createProfileExact(@Nullable UUID uuid, @Nullable String name) {
+ return server.createProfileExact(uuid, name);
+ }
// Paper end
@NotNull
diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
index 012b5954a2f9dc61fb8ad29c4b8bce2648ddc681..8e4bf531c0a2f7101c2a3733fe33733d31c611fd 100644
--- a/src/main/java/org/bukkit/Server.java
+++ b/src/main/java/org/bukkit/Server.java
@@ -2067,5 +2067,74 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
* @return true if player names should be suggested
*/
boolean suggestPlayerNamesWhenNullTabCompletions();
+
+ /**
+ * Creates a PlayerProfile for the specified uuid, with name as null.
+ *
+ * If a player with the passed uuid exists on the server at the time of creation, the returned player profile will
+ * be populated with the properties of said player (including their uuid and name).
+ *
+ * @param uuid UUID to create profile for
+ * @return A PlayerProfile object
+ */
+ @NotNull
+ com.destroystokyo.paper.profile.PlayerProfile createProfile(@NotNull UUID uuid);
+
+ /**
+ * Creates a PlayerProfile for the specified name, with UUID as null.
+ *
+ * If a player with the passed name exists on the server at the time of creation, the returned player profile will
+ * be populated with the properties of said player (including their uuid and name).
+ * <p>
+ * E.g. if the player 'jeb_' is currently playing on the server, calling {@code createProfile("JEB_")} will
+ * yield a profile with the name 'jeb_', their uuid and their textures.
+ * To bypass this pre-population on a case-insensitive name match, see {@link #createProfileExact(UUID, String)}.
+ * <p>
+ *
+ * @param name Name to create profile for
+ * @return A PlayerProfile object
+ */
+ @NotNull
+ com.destroystokyo.paper.profile.PlayerProfile createProfile(@NotNull String name);
+
+ /**
+ * Creates a PlayerProfile for the specified name/uuid
+ *
+ * Both UUID and Name can not be null at same time. One must be supplied.
+ * If a player with the passed uuid or name exists on the server at the time of creation, the returned player
+ * profile will be populated with the properties of said player (including their uuid and name).
+ * <p>
+ * E.g. if the player 'jeb_' is currently playing on the server, calling {@code createProfile(null, "JEB_")} will
+ * yield a profile with the name 'jeb_', their uuid and their textures.
+ * To bypass this pre-population on an case-insensitive name match, see {@link #createProfileExact(UUID, String)}.
+ * <p>
+ *
+ * The name comparison will compare the {@link String#toLowerCase()} version of both the passed name parameter and
+ * a players name to honour the case-insensitive nature of a mojang profile lookup.
+ *
+ * @param uuid UUID to create profile for
+ * @param name Name to create profile for
+ * @return A PlayerProfile object
+ */
+ @NotNull
+ com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nullable UUID uuid, @Nullable String name);
+
+ /**
+ * Creates an exact PlayerProfile for the specified name/uuid
+ *
+ * Both UUID and Name can not be null at same time. One must be supplied.
+ * If a player with the passed uuid or name exists on the server at the time of creation, the returned player
+ * profile will be populated with the properties of said player.
+ * <p>
+ * Compared to {@link #createProfile(UUID, String)}, this method will never mutate the passed uuid or name.
+ * If a player with either the same uuid or a matching name (case-insensitive) is found on the server, their
+ * properties, such as textures, will be pre-populated in the profile, however the passed uuid and name stay intact.
+ *
+ * @param uuid UUID to create profile for
+ * @param name Name to create profile for
+ * @return A PlayerProfile object
+ */
+ @NotNull
+ com.destroystokyo.paper.profile.PlayerProfile createProfileExact(@Nullable UUID uuid, @Nullable String name);
// Paper end
}
diff --git a/src/main/java/org/bukkit/profile/PlayerProfile.java b/src/main/java/org/bukkit/profile/PlayerProfile.java
index 16ae1282f3178e8873483a25a5d5cce16b2c21a9..fc46add38bf59dc1a04ea566fd230dcd8ae2708c 100644
--- a/src/main/java/org/bukkit/profile/PlayerProfile.java
+++ b/src/main/java/org/bukkit/profile/PlayerProfile.java
@@ -93,7 +93,7 @@ public interface PlayerProfile extends Cloneable, ConfigurationSerializable {
* PlayerProfile once it is available
*/
@NotNull
- CompletableFuture<PlayerProfile> update();
+ CompletableFuture<? extends PlayerProfile> update(); // Paper
@NotNull
PlayerProfile clone();