diff --git a/paper-api/src/main/java/org/bukkit/Bukkit.java b/paper-api/src/main/java/org/bukkit/Bukkit.java index 47f1ff8f83..c328d2fde8 100644 --- a/paper-api/src/main/java/org/bukkit/Bukkit.java +++ b/paper-api/src/main/java/org/bukkit/Bukkit.java @@ -44,6 +44,7 @@ import org.bukkit.permissions.Permissible; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.ServicesManager; import org.bukkit.plugin.messaging.Messenger; +import org.bukkit.profile.PlayerProfile; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scoreboard.ScoreboardManager; import org.bukkit.structure.StructureManager; @@ -1048,6 +1049,45 @@ public final class Bukkit { return server.getOfflinePlayer(id); } + /** + * Creates a new {@link PlayerProfile}. + * + * @param uniqueId the unique id + * @param name the name + * @return the new PlayerProfile + * @throws IllegalArgumentException if both the unique id is + * null and the name is null or blank + */ + @NotNull + public static PlayerProfile createPlayerProfile(@Nullable UUID uniqueId, @Nullable String name) { + return server.createPlayerProfile(uniqueId, name); + } + + /** + * Creates a new {@link PlayerProfile}. + * + * @param uniqueId the unique id + * @return the new PlayerProfile + * @throws IllegalArgumentException if the unique id is null + */ + @NotNull + public static PlayerProfile createPlayerProfile(@NotNull UUID uniqueId) { + return server.createPlayerProfile(uniqueId); + } + + /** + * Creates a new {@link PlayerProfile}. + * + * @param name the name + * @return the new PlayerProfile + * @throws IllegalArgumentException if the name is null or + * blank + */ + @NotNull + public static PlayerProfile createPlayerProfile(@NotNull String name) { + return server.createPlayerProfile(name); + } + /** * Gets a set containing all current IPs that are banned. * diff --git a/paper-api/src/main/java/org/bukkit/OfflinePlayer.java b/paper-api/src/main/java/org/bukkit/OfflinePlayer.java index 58313929f8..76e511e7f6 100644 --- a/paper-api/src/main/java/org/bukkit/OfflinePlayer.java +++ b/paper-api/src/main/java/org/bukkit/OfflinePlayer.java @@ -6,6 +6,7 @@ import org.bukkit.entity.AnimalTamer; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.permissions.ServerOperator; +import org.bukkit.profile.PlayerProfile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -39,6 +40,18 @@ public interface OfflinePlayer extends ServerOperator, AnimalTamer, Configuratio @NotNull public UUID getUniqueId(); + /** + * Gets a copy of the player's profile. + *

+ * If the player is online, the returned profile will be complete. + * Otherwise, only the unique id is guaranteed to be present. You can use + * {@link PlayerProfile#update()} to complete the returned profile. + * + * @return the player's profile + */ + @NotNull + PlayerProfile getPlayerProfile(); + /** * Checks if this player is banned or not * diff --git a/paper-api/src/main/java/org/bukkit/Server.java b/paper-api/src/main/java/org/bukkit/Server.java index 8a6a3867ac..1997dfd72b 100644 --- a/paper-api/src/main/java/org/bukkit/Server.java +++ b/paper-api/src/main/java/org/bukkit/Server.java @@ -45,6 +45,7 @@ import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.ServicesManager; import org.bukkit.plugin.messaging.Messenger; import org.bukkit.plugin.messaging.PluginMessageRecipient; +import org.bukkit.profile.PlayerProfile; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scoreboard.ScoreboardManager; import org.bukkit.structure.StructureManager; @@ -880,6 +881,39 @@ public interface Server extends PluginMessageRecipient { @NotNull public OfflinePlayer getOfflinePlayer(@NotNull UUID id); + /** + * Creates a new {@link PlayerProfile}. + * + * @param uniqueId the unique id + * @param name the name + * @return the new PlayerProfile + * @throws IllegalArgumentException if both the unique id is + * null and the name is null or blank + */ + @NotNull + PlayerProfile createPlayerProfile(@Nullable UUID uniqueId, @Nullable String name); + + /** + * Creates a new {@link PlayerProfile}. + * + * @param uniqueId the unique id + * @return the new PlayerProfile + * @throws IllegalArgumentException if the unique id is null + */ + @NotNull + PlayerProfile createPlayerProfile(@NotNull UUID uniqueId); + + /** + * Creates a new {@link PlayerProfile}. + * + * @param name the name + * @return the new PlayerProfile + * @throws IllegalArgumentException if the name is null or + * blank + */ + @NotNull + PlayerProfile createPlayerProfile(@NotNull String name); + /** * Gets a set containing all current IPs that are banned. * diff --git a/paper-api/src/main/java/org/bukkit/block/Skull.java b/paper-api/src/main/java/org/bukkit/block/Skull.java index 943d751fb3..83ca284e02 100644 --- a/paper-api/src/main/java/org/bukkit/block/Skull.java +++ b/paper-api/src/main/java/org/bukkit/block/Skull.java @@ -4,6 +4,7 @@ import org.bukkit.Material; import org.bukkit.OfflinePlayer; import org.bukkit.SkullType; import org.bukkit.block.data.BlockData; +import org.bukkit.profile.PlayerProfile; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -61,6 +62,29 @@ public interface Skull extends TileState { */ public void setOwningPlayer(@NotNull OfflinePlayer player); + /** + * Gets the profile of the player who owns the skull. This player profile + * may appear as the texture depending on skull type. + * + * @return the profile of the owning player + */ + @Nullable + PlayerProfile getOwnerProfile(); + + /** + * Sets the profile of the player who owns the skull. This player profile + * may appear as the texture depending on skull type. + *

+ * The profile must contain both a unique id and a skin texture. If either + * of these is missing, the profile must contain a name by which the server + * will then attempt to look up the unique id and skin texture. + * + * @param profile the profile of the owning player + * @throws IllegalArgumentException if the profile does not contain the + * necessary information + */ + void setOwnerProfile(@Nullable PlayerProfile profile); + /** * Gets the rotation of the skull in the world (or facing direction if this * is a wall mounted skull). diff --git a/paper-api/src/main/java/org/bukkit/inventory/meta/SkullMeta.java b/paper-api/src/main/java/org/bukkit/inventory/meta/SkullMeta.java index 496254f959..dcefd0eea9 100644 --- a/paper-api/src/main/java/org/bukkit/inventory/meta/SkullMeta.java +++ b/paper-api/src/main/java/org/bukkit/inventory/meta/SkullMeta.java @@ -1,6 +1,7 @@ package org.bukkit.inventory.meta; import org.bukkit.OfflinePlayer; +import org.bukkit.profile.PlayerProfile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -55,6 +56,29 @@ public interface SkullMeta extends ItemMeta { */ boolean setOwningPlayer(@Nullable OfflinePlayer owner); + /** + * Gets the profile of the player who owns the skull. This player profile + * may appear as the texture depending on skull type. + * + * @return the profile of the owning player + */ + @Nullable + PlayerProfile getOwnerProfile(); + + /** + * Sets the profile of the player who owns the skull. This player profile + * may appear as the texture depending on skull type. + *

+ * The profile must contain both a unique id and a skin texture. If either + * of these is missing, the profile must contain a name by which the server + * will then attempt to look up the unique id and skin texture. + * + * @param profile the profile of the owning player + * @throws IllegalArgumentException if the profile does not contain the + * necessary information + */ + void setOwnerProfile(@Nullable PlayerProfile profile); + @Override @NotNull SkullMeta clone(); diff --git a/paper-api/src/main/java/org/bukkit/profile/PlayerProfile.java b/paper-api/src/main/java/org/bukkit/profile/PlayerProfile.java new file mode 100644 index 0000000000..16ae1282f3 --- /dev/null +++ b/paper-api/src/main/java/org/bukkit/profile/PlayerProfile.java @@ -0,0 +1,100 @@ +package org.bukkit.profile; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import org.bukkit.Server; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A player profile. + *

+ * A player profile always provides a unique id, a non-empty name, or both. Its + * unique id and name are immutable, but other properties (such as its textures) + * can be altered. + *

+ * New profiles can be created via + * {@link Server#createPlayerProfile(UUID, String)}. + */ +public interface PlayerProfile extends Cloneable, ConfigurationSerializable { + + /** + * Gets the player's unique id. + * + * @return the player's unique id, or null if not available + */ + @Nullable + UUID getUniqueId(); + + /** + * Gets the player name. + * + * @return the player name, or null if not available + */ + @Nullable + String getName(); + + /** + * Gets the {@link PlayerTextures} of this profile. + * + * @return the textures, not null + */ + @NotNull + PlayerTextures getTextures(); + + /** + * Copies the given textures. + * + * @param textures the textures to copy, or null to clear the + * textures + */ + void setTextures(@Nullable PlayerTextures textures); + + /** + * Checks whether this profile is complete. + *

+ * A profile is currently considered complete if it has a name, a unique id, + * and textures. + * + * @return true if this profile is complete + */ + boolean isComplete(); + + /** + * Produces an updated player profile based on this profile. + *

+ * 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}. + *

+ * 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. + *

+ * 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: + *

+     * profile.update().thenAcceptAsync(updatedProfile -> {
+     *     // Do something with the updated profile:
+     *     // ...
+     * }, runnable -> Bukkit.getScheduler().runTask(plugin, runnable));
+     * 
+ * + * @return a completable future that gets completed with the updated + * PlayerProfile once it is available + */ + @NotNull + CompletableFuture update(); + + @NotNull + PlayerProfile clone(); +} diff --git a/paper-api/src/main/java/org/bukkit/profile/PlayerTextures.java b/paper-api/src/main/java/org/bukkit/profile/PlayerTextures.java new file mode 100644 index 0000000000..cb2bd70931 --- /dev/null +++ b/paper-api/src/main/java/org/bukkit/profile/PlayerTextures.java @@ -0,0 +1,127 @@ +package org.bukkit.profile; + +import java.net.URL; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Provides access to the textures stored inside a {@link PlayerProfile}. + *

+ * Modifying the textures immediately invalidates and clears any previously + * present attributes that are specific to official player profiles, such as the + * {@link #getTimestamp() timestamp} and {@link #isSigned() signature}. + */ +public interface PlayerTextures { + + /** + * The different Minecraft skin models. + */ + enum SkinModel { + /** + * The classic Minecraft skin model. + */ + CLASSIC, + /** + * The slim model has slimmer arms than the classic model. + */ + SLIM; + } + + /** + * Checks if the profile stores no textures. + * + * @return true if the profile stores no textures + */ + boolean isEmpty(); + + /** + * Clears the textures. + */ + void clear(); + + /** + * Gets the URL that points to the player's skin. + * + * @return the URL of the player's skin, or null if not set + */ + @Nullable + URL getSkin(); + + /** + * Sets the player's skin to the specified URL, and the skin model to + * {@link SkinModel#CLASSIC}. + *

+ * The URL must point to the Minecraft texture server. Example URL: + *

+     * http://textures.minecraft.net/texture/b3fbd454b599df593f57101bfca34e67d292a8861213d2202bb575da7fd091ac
+     * 
+ * + * @param skinUrl the URL of the player's skin, or null to + * unset it + */ + void setSkin(@Nullable URL skinUrl); + + /** + * Sets the player's skin and {@link SkinModel}. + *

+ * The URL must point to the Minecraft texture server. Example URL: + *

+     * http://textures.minecraft.net/texture/b3fbd454b599df593f57101bfca34e67d292a8861213d2202bb575da7fd091ac
+     * 
+ *

+ * A skin model of null results in {@link SkinModel#CLASSIC} to + * be used. + * + * @param skinUrl the URL of the player's skin, or null to + * unset it + * @param skinModel the skin model, ignored if the skin URL is + * null + */ + void setSkin(@Nullable URL skinUrl, @Nullable SkinModel skinModel); + + /** + * Gets the model of the player's skin. + *

+ * This returns {@link SkinModel#CLASSIC} if no skin is set. + * + * @return the model of the player's skin + */ + @NotNull + SkinModel getSkinModel(); + + /** + * Gets the URL that points to the player's cape. + * + * @return the URL of the player's cape, or null if not set + */ + @Nullable + URL getCape(); + + /** + * Sets the URL that points to the player's cape. + *

+ * The URL must point to the Minecraft texture server. Example URL: + *

+     * http://textures.minecraft.net/texture/2340c0e03dd24a11b15a8b33c2a7e9e32abb2051b2481d0ba7defd635ca7a933
+     * 
+ * + * @param capeUrl the URL of the player's cape, or null to + * unset it + */ + void setCape(@Nullable URL capeUrl); + + /** + * Gets the timestamp at which the profile was last updated. + * + * @return the timestamp, or 0 if unknown + */ + long getTimestamp(); + + /** + * Checks if the textures are signed and the signature is valid. + * + * @return true if the textures are signed and the signature is + * valid + */ + boolean isSigned(); +}