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
+ * 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();
+}