2021-06-11 12:02:28 +00:00
|
|
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
|
|
From: Aikar <aikar@aikar.co>
|
|
|
|
Date: Mon, 15 Jan 2018 22:11:48 -0500
|
|
|
|
Subject: [PATCH] Basic PlayerProfile API
|
|
|
|
|
|
|
|
Establishes base extension of profile systems for future edits too
|
|
|
|
|
|
|
|
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
|
2022-02-12 18:29:41 +00:00
|
|
|
index 0000000000000000000000000000000000000000..2041376dfd5520776f7e32c1828973f2b719d82a
|
2021-06-11 12:02:28 +00:00
|
|
|
--- /dev/null
|
|
|
|
+++ b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java
|
2022-02-12 18:29:41 +00:00
|
|
|
@@ -0,0 +1,399 @@
|
2021-06-11 12:02:28 +00:00
|
|
|
+package com.destroystokyo.paper.profile;
|
|
|
|
+
|
|
|
|
+import com.destroystokyo.paper.PaperConfig;
|
|
|
|
+import com.google.common.base.Charsets;
|
2022-02-12 13:20:33 +00:00
|
|
|
+import com.google.common.collect.Iterables;
|
2021-06-11 12:02:28 +00:00
|
|
|
+import com.mojang.authlib.GameProfile;
|
|
|
|
+import com.mojang.authlib.properties.Property;
|
|
|
|
+import com.mojang.authlib.properties.PropertyMap;
|
2022-02-12 13:20:33 +00:00
|
|
|
+import net.minecraft.Util;
|
2021-06-11 12:02:28 +00:00
|
|
|
+import net.minecraft.server.MinecraftServer;
|
|
|
|
+import net.minecraft.server.players.GameProfileCache;
|
|
|
|
+import org.apache.commons.lang3.Validate;
|
2022-02-12 18:29:41 +00:00
|
|
|
+import org.bukkit.configuration.serialization.SerializableAs;
|
|
|
|
+import org.bukkit.craftbukkit.configuration.ConfigSerializationUtil;
|
2021-06-11 12:02:28 +00:00
|
|
|
+import org.bukkit.craftbukkit.entity.CraftPlayer;
|
2022-02-12 13:20:33 +00:00
|
|
|
+import org.bukkit.craftbukkit.profile.CraftPlayerTextures;
|
|
|
|
+import org.bukkit.craftbukkit.profile.CraftProfileProperty;
|
|
|
|
+import org.bukkit.profile.PlayerTextures;
|
|
|
|
+import org.jetbrains.annotations.NotNull;
|
2021-06-11 12:02:28 +00:00
|
|
|
+
|
|
|
|
+import javax.annotation.Nonnull;
|
|
|
|
+import javax.annotation.Nullable;
|
2022-02-12 13:20:33 +00:00
|
|
|
+import java.util.*;
|
|
|
|
+import java.util.concurrent.CompletableFuture;
|
2021-06-11 12:02:28 +00:00
|
|
|
+
|
2022-02-12 18:29:41 +00:00
|
|
|
+@SerializableAs("PlayerProfile")
|
2022-02-12 13:20:33 +00:00
|
|
|
+public class CraftPlayerProfile implements PlayerProfile, SharedPlayerProfile {
|
2021-06-11 12:02:28 +00:00
|
|
|
+
|
|
|
|
+ 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 = new GameProfile(id, name);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public CraftPlayerProfile(GameProfile profile) {
|
|
|
|
+ Validate.notNull(profile, "GameProfile cannot be null!");
|
|
|
|
+ this.profile = 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);
|
|
|
|
+ properties.put(name, new Property(name, property.getValue(), property.getSignature()));
|
|
|
|
+ }
|
|
|
|
+
|
2022-02-12 13:20:33 +00:00
|
|
|
+ @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();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
2021-06-11 12:02:28 +00:00
|
|
|
+ public GameProfile getGameProfile() {
|
|
|
|
+ return profile;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Nullable
|
|
|
|
+ @Override
|
|
|
|
+ public UUID getId() {
|
|
|
|
+ return profile.getId();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
2022-02-12 18:29:41 +00:00
|
|
|
+ @Deprecated(forRemoval = true)
|
2021-06-11 12:02:28 +00:00
|
|
|
+ public UUID setId(@Nullable UUID uuid) {
|
|
|
|
+ GameProfile prev = this.profile;
|
|
|
|
+ this.profile = new GameProfile(uuid, prev.getName());
|
|
|
|
+ copyProfileProperties(prev, this.profile);
|
|
|
|
+ return prev.getId();
|
|
|
|
+ }
|
|
|
|
+
|
2022-02-12 13:20:33 +00:00
|
|
|
+ @Override
|
|
|
|
+ public UUID getUniqueId() {
|
|
|
|
+ return getId();
|
|
|
|
+ }
|
|
|
|
+
|
2021-06-11 12:02:28 +00:00
|
|
|
+ @Nullable
|
|
|
|
+ @Override
|
|
|
|
+ public String getName() {
|
|
|
|
+ return profile.getName();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
2022-02-12 18:29:41 +00:00
|
|
|
+ @Deprecated(forRemoval = true)
|
2021-06-11 12:02:28 +00:00
|
|
|
+ public String setName(@Nullable String name) {
|
|
|
|
+ GameProfile prev = this.profile;
|
|
|
|
+ this.profile = new GameProfile(prev.getId(), name);
|
|
|
|
+ copyProfileProperties(prev, this.profile);
|
|
|
|
+ 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();
|
|
|
|
+ }
|
|
|
|
+
|
2022-02-12 13:20:33 +00:00
|
|
|
+ @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) {
|
|
|
|
+ PropertyMap properties = profile.getProperties();
|
|
|
|
+ properties.removeAll(propertyName);
|
|
|
|
+ if (property != null) {
|
|
|
|
+ properties.put(propertyName, property);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public @NotNull GameProfile buildGameProfile() {
|
|
|
|
+ GameProfile profile = new GameProfile(this.profile.getId(), this.profile.getName());
|
|
|
|
+ profile.getProperties().putAll(this.profile.getProperties());
|
|
|
|
+ return profile;
|
|
|
|
+ }
|
|
|
|
+
|
2021-06-11 12:02:28 +00:00
|
|
|
+ @Override
|
|
|
|
+ public CraftPlayerProfile clone() {
|
|
|
|
+ CraftPlayerProfile clone = new CraftPlayerProfile(this.getId(), this.getName());
|
|
|
|
+ clone.setProperties(getProperties());
|
|
|
|
+ return clone;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public boolean isComplete() {
|
|
|
|
+ return profile.isComplete();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
2022-02-12 13:20:33 +00:00
|
|
|
+ public @NotNull CompletableFuture<org.bukkit.profile.PlayerProfile> update() {
|
|
|
|
+ return CompletableFuture.supplyAsync(() -> {
|
|
|
|
+ final CraftPlayerProfile clone = clone();
|
|
|
|
+ clone.complete(true);
|
|
|
|
+ return clone;
|
|
|
|
+ }, Util.backgroundExecutor());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
2021-06-11 12:02:28 +00:00
|
|
|
+ public boolean completeFromCache() {
|
|
|
|
+ return completeFromCache(false, PaperConfig.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 (profile.getId() == null) {
|
|
|
|
+ final GameProfile profile;
|
|
|
|
+ if (onlineMode) {
|
2021-07-07 06:52:40 +00:00
|
|
|
+ profile = lookupUUID ? userCache.get(name).orElse(null) : userCache.getProfileIfCached(name);
|
2021-06-11 12:02:28 +00:00
|
|
|
+ } 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;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ((profile.getName() == null || !hasTextures()) && profile.getId() != null) {
|
2021-07-07 06:52:40 +00:00
|
|
|
+ Optional<GameProfile> optProfile = userCache.get(this.profile.getId());
|
|
|
|
+ if (optProfile.isPresent()) {
|
|
|
|
+ GameProfile profile = optProfile.get();
|
2021-06-11 12:02:28 +00:00
|
|
|
+ if (this.profile.getName() == 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;
|
|
|
|
+ } else {
|
|
|
|
+ copyProfileProperties(profile, this.profile);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return this.profile.isComplete();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public boolean complete(boolean textures) {
|
|
|
|
+ return complete(textures, PaperConfig.isProxyOnlineMode());
|
|
|
|
+ }
|
|
|
|
+ public boolean complete(boolean textures, boolean onlineMode) {
|
|
|
|
+ MinecraftServer server = MinecraftServer.getServer();
|
|
|
|
+ boolean isCompleteFromCache = this.completeFromCache(true, onlineMode);
|
|
|
|
+ if (onlineMode && (!isCompleteFromCache || textures && !hasTextures())) {
|
|
|
|
+ GameProfile result = server.getSessionService().fillProfileProperties(profile, true);
|
|
|
|
+ if (result != null) {
|
|
|
|
+ copyProfileProperties(result, this.profile, true);
|
|
|
|
+ }
|
|
|
|
+ if (this.profile.isComplete()) {
|
2021-06-12 11:18:01 +00:00
|
|
|
+ server.getProfileCache().add(this.profile);
|
2021-06-11 12:02:28 +00:00
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return profile.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) {
|
|
|
|
+ PropertyMap sourceProperties = source.getProperties();
|
|
|
|
+ PropertyMap targetProperties = target.getProperties();
|
|
|
|
+ if (clearTarget) targetProperties.clear();
|
|
|
|
+ if (sourceProperties.isEmpty()) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for (Property property : sourceProperties.values()) {
|
|
|
|
+ targetProperties.removeAll(property.getName());
|
|
|
|
+ targetProperties.put(property.getName(), property);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static ProfileProperty toBukkit(Property property) {
|
|
|
|
+ return new ProfileProperty(property.getName(), property.getValue(), property.getSignature());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 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();
|
|
|
|
+ }
|
|
|
|
+
|
2022-02-12 13:20:33 +00:00
|
|
|
+ @Override
|
|
|
|
+ public @NotNull Map<String, Object> serialize() {
|
|
|
|
+ Map<String, Object> map = new LinkedHashMap<>();
|
|
|
|
+ if (this.getId() != null) {
|
|
|
|
+ map.put("uniqueId", this.getId().toString());
|
|
|
|
+ }
|
|
|
|
+ if (this.getName() != null) {
|
|
|
|
+ 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;
|
|
|
|
+ }
|
|
|
|
+
|
2022-02-12 18:29:41 +00:00
|
|
|
+ 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.getName(), property);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return profile;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public boolean equals(Object obj) {
|
|
|
|
+ if (this == obj) return true;
|
|
|
|
+ if (!(obj instanceof CraftPlayerProfile otherProfile)) return false;
|
|
|
|
+ return Objects.equals(this.profile, otherProfile.profile);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public String toString() {
|
|
|
|
+ return "CraftPlayerProfile [uniqueId=" + getId() +
|
|
|
|
+ ", name=" + getName() +
|
|
|
|
+ ", properties=" + org.bukkit.craftbukkit.profile.CraftPlayerProfile.toString(this.profile.getProperties()) +
|
|
|
|
+ "]";
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public int hashCode() {
|
|
|
|
+ return this.profile.hashCode();
|
|
|
|
+ }
|
|
|
|
+
|
2021-06-11 12:02:28 +00:00
|
|
|
+ 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
|
2021-11-23 14:03:50 +00:00
|
|
|
index 0000000000000000000000000000000000000000..1459a1f99fe614d072a087cda18788cf13102645
|
2021-06-11 12:02:28 +00:00
|
|
|
--- /dev/null
|
|
|
|
+++ b/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java
|
|
|
|
@@ -0,0 +1,31 @@
|
|
|
|
+package com.destroystokyo.paper.profile;
|
|
|
|
+
|
|
|
|
+import com.mojang.authlib.*;
|
|
|
|
+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);
|
2021-11-23 14:03:50 +00:00
|
|
|
+ this.environment = EnvironmentParser.getEnvironmentFromProperties().orElse(YggdrasilEnvironment.PROD.getEnvironment());
|
2021-06-11 12:02:28 +00:00
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public UserAuthentication createUserAuthentication(Agent agent) {
|
|
|
|
+ return new PaperUserAuthentication(this, agent);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public MinecraftSessionService createMinecraftSessionService() {
|
|
|
|
+ return new PaperMinecraftSessionService(this, this.environment);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public GameProfileRepository createProfileRepository() {
|
|
|
|
+ return new PaperGameProfileRepository(this, 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..582c169c85ac66f1f9430f79042e4655f776c157
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java
|
|
|
|
@@ -0,0 +1,18 @@
|
|
|
|
+package com.destroystokyo.paper.profile;
|
|
|
|
+
|
|
|
|
+import com.mojang.authlib.Agent;
|
|
|
|
+import com.mojang.authlib.Environment;
|
|
|
|
+import com.mojang.authlib.ProfileLookupCallback;
|
|
|
|
+import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
|
|
|
|
+import com.mojang.authlib.yggdrasil.YggdrasilGameProfileRepository;
|
|
|
|
+
|
|
|
|
+public class PaperGameProfileRepository extends YggdrasilGameProfileRepository {
|
|
|
|
+ public PaperGameProfileRepository(YggdrasilAuthenticationService authenticationService, Environment environment) {
|
|
|
|
+ super(authenticationService, environment);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void findProfilesByNames(String[] names, Agent agent, ProfileLookupCallback callback) {
|
|
|
|
+ super.findProfilesByNames(names, agent, 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..93d73c27340645c7502acafdc0b2cfbc1a759dd8
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java
|
|
|
|
@@ -0,0 +1,30 @@
|
|
|
|
+package com.destroystokyo.paper.profile;
|
|
|
|
+
|
|
|
|
+import com.mojang.authlib.Environment;
|
|
|
|
+import com.mojang.authlib.GameProfile;
|
|
|
|
+import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
|
|
|
+import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
|
|
|
|
+import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService;
|
|
|
|
+
|
|
|
|
+import java.util.Map;
|
|
|
|
+
|
|
|
|
+public class PaperMinecraftSessionService extends YggdrasilMinecraftSessionService {
|
|
|
|
+ protected PaperMinecraftSessionService(YggdrasilAuthenticationService authenticationService, Environment environment) {
|
|
|
|
+ super(authenticationService, environment);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getTextures(GameProfile profile, boolean requireSecure) {
|
|
|
|
+ return super.getTextures(profile, requireSecure);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public GameProfile fillProfileProperties(GameProfile profile, boolean requireSecure) {
|
|
|
|
+ return super.fillProfileProperties(profile, requireSecure);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ protected GameProfile fillGameProfile(GameProfile profile, boolean requireSecure) {
|
|
|
|
+ return super.fillGameProfile(profile, requireSecure);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperUserAuthentication.java b/src/main/java/com/destroystokyo/paper/profile/PaperUserAuthentication.java
|
|
|
|
new file mode 100644
|
|
|
|
index 0000000000000000000000000000000000000000..3cdd06d3af7ff94f1fe1a11b9a9275e17c695a38
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/src/main/java/com/destroystokyo/paper/profile/PaperUserAuthentication.java
|
|
|
|
@@ -0,0 +1,12 @@
|
|
|
|
+package com.destroystokyo.paper.profile;
|
|
|
|
+
|
|
|
|
+import com.mojang.authlib.Agent;
|
|
|
|
+import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
|
|
|
|
+import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication;
|
|
|
|
+import java.util.UUID;
|
|
|
|
+
|
|
|
|
+public class PaperUserAuthentication extends YggdrasilUserAuthentication {
|
|
|
|
+ public PaperUserAuthentication(YggdrasilAuthenticationService authenticationService, Agent agent) {
|
|
|
|
+ super(authenticationService, UUID.randomUUID().toString(), agent);
|
|
|
|
+ }
|
|
|
|
+}
|
2022-02-12 13:20:33 +00:00
|
|
|
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..7ac27392a8647ef7d0dc78efe78703e993885017
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/src/main/java/com/destroystokyo/paper/profile/SharedPlayerProfile.java
|
|
|
|
@@ -0,0 +1,23 @@
|
|
|
|
+package com.destroystokyo.paper.profile;
|
|
|
|
+
|
|
|
|
+import com.mojang.authlib.GameProfile;
|
|
|
|
+import com.mojang.authlib.properties.Property;
|
|
|
|
+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();
|
|
|
|
+}
|
2021-06-11 12:02:28 +00:00
|
|
|
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
|
2021-12-23 10:32:26 +00:00
|
|
|
index 9f292deee1b793d52b5774304318e940128d1e26..0cf818fceddd76e7704fdc6625456787856b2815 100644
|
2021-06-11 12:02:28 +00:00
|
|
|
--- a/src/main/java/net/minecraft/server/MCUtil.java
|
|
|
|
+++ b/src/main/java/net/minecraft/server/MCUtil.java
|
2021-06-12 11:18:01 +00:00
|
|
|
@@ -1,5 +1,7 @@
|
2021-06-11 12:02:28 +00:00
|
|
|
package net.minecraft.server;
|
|
|
|
|
|
|
|
+import com.destroystokyo.paper.profile.CraftPlayerProfile;
|
|
|
|
+import com.destroystokyo.paper.profile.PlayerProfile;
|
|
|
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|
|
|
import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet;
|
2021-11-04 02:42:18 +00:00
|
|
|
import java.lang.ref.Cleaner;
|
|
|
|
@@ -11,6 +13,7 @@ import net.minecraft.world.level.ChunkPos;
|
2021-06-11 12:02:28 +00:00
|
|
|
import net.minecraft.world.level.ClipContext;
|
|
|
|
import net.minecraft.world.level.Level;
|
|
|
|
import org.apache.commons.lang.exception.ExceptionUtils;
|
|
|
|
+import com.mojang.authlib.GameProfile;
|
|
|
|
import org.bukkit.Location;
|
|
|
|
import org.bukkit.block.BlockFace;
|
|
|
|
import org.bukkit.craftbukkit.CraftWorld;
|
2021-12-23 10:32:26 +00:00
|
|
|
@@ -355,6 +358,10 @@ public final class MCUtil {
|
2021-06-11 12:02:28 +00:00
|
|
|
return run.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
+ public static PlayerProfile toBukkit(GameProfile profile) {
|
|
|
|
+ return CraftPlayerProfile.asBukkitMirror(profile);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
/**
|
|
|
|
* Calculates distance between 2 entities
|
|
|
|
* @param e1
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java
|
2022-06-07 20:12:48 +00:00
|
|
|
index 97dc1d188a57b9f499c9cdc2ec54535af380e5cb..455a8d824540c66cf50b6440000b807bc1c71025 100644
|
2021-06-11 12:02:28 +00:00
|
|
|
--- a/src/main/java/net/minecraft/server/Main.java
|
|
|
|
+++ b/src/main/java/net/minecraft/server/Main.java
|
2022-06-07 20:12:48 +00:00
|
|
|
@@ -139,7 +139,7 @@ public class Main {
|
2021-06-11 12:02:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
File file = (File) optionset.valueOf("universe"); // CraftBukkit
|
2022-06-07 20:12:48 +00:00
|
|
|
- Services services = Services.create(new YggdrasilAuthenticationService(Proxy.NO_PROXY), file);
|
|
|
|
+ Services services = Services.create(new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY), file); // Paper
|
|
|
|
// CraftBukkit start
|
|
|
|
String s = (String) Optional.ofNullable((String) optionset.valueOf("world")).orElse(dedicatedserversettings.getProperties().levelName);
|
|
|
|
LevelStorageSource convertable = LevelStorageSource.createDefault(file.toPath());
|
2021-06-11 12:02:28 +00:00
|
|
|
diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java
|
2022-06-07 20:12:48 +00:00
|
|
|
index 6087fff889458dc09d5a3eb52e7d4b0a77bde809..ddd78b2836c1f4a6b4fcd532153f5d3e17f91ea8 100644
|
2021-06-11 12:02:28 +00:00
|
|
|
--- a/src/main/java/net/minecraft/server/players/GameProfileCache.java
|
|
|
|
+++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java
|
2022-03-18 02:44:45 +00:00
|
|
|
@@ -135,6 +135,17 @@ public class GameProfileCache {
|
2021-07-07 06:52:40 +00:00
|
|
|
return this.operationCount.incrementAndGet();
|
2021-06-11 12:02:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
+ // Paper start
|
2022-03-18 03:53:36 +00:00
|
|
|
+ public @Nullable GameProfile getProfileIfCached(String name) {
|
2021-06-12 11:18:01 +00:00
|
|
|
+ GameProfileCache.GameProfileInfo entry = this.profilesByName.get(name.toLowerCase(Locale.ROOT));
|
2022-03-18 02:44:45 +00:00
|
|
|
+ if (entry == null) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ entry.setLastAccess(this.getNextOperation());
|
|
|
|
+ return entry.getProfile();
|
2021-06-11 12:02:28 +00:00
|
|
|
+ }
|
|
|
|
+ // Paper end
|
|
|
|
+
|
2022-03-18 03:53:36 +00:00
|
|
|
public Optional<GameProfile> get(String name) {
|
2021-07-07 06:52:40 +00:00
|
|
|
String s1 = name.toLowerCase(Locale.ROOT);
|
|
|
|
GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByName.get(s1);
|
2021-06-11 12:02:28 +00:00
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
2022-06-07 20:12:48 +00:00
|
|
|
index 9c522e7aa2f374543090cff7a49d06a7c15e450d..fbe26f8a592ed0a3b970b6f02126b6e3e454d8af 100644
|
2021-06-11 12:02:28 +00:00
|
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
2022-06-07 20:12:48 +00:00
|
|
|
@@ -239,6 +239,9 @@ import org.yaml.snakeyaml.error.MarkedYAMLException;
|
2021-06-11 12:02:28 +00:00
|
|
|
|
|
|
|
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 = "Paper"; // Paper
|
|
|
|
private final String serverVersion;
|
2022-06-07 20:12:48 +00:00
|
|
|
@@ -278,6 +281,7 @@ public final class CraftServer implements Server {
|
2022-02-12 18:29:41 +00:00
|
|
|
static {
|
|
|
|
ConfigurationSerialization.registerClass(CraftOfflinePlayer.class);
|
|
|
|
ConfigurationSerialization.registerClass(CraftPlayerProfile.class);
|
|
|
|
+ ConfigurationSerialization.registerClass(com.destroystokyo.paper.profile.CraftPlayerProfile.class); // Paper
|
|
|
|
CraftItemFactory.instance();
|
|
|
|
}
|
|
|
|
|
2022-06-07 20:12:48 +00:00
|
|
|
@@ -2563,5 +2567,37 @@ public final class CraftServer implements Server {
|
2021-06-11 12:02:28 +00:00
|
|
|
public boolean suggestPlayerNamesWhenNullTabCompletions() {
|
|
|
|
return com.destroystokyo.paper.PaperConfig.suggestPlayersWhenNullTabCompletions;
|
|
|
|
}
|
|
|
|
+
|
|
|
|
+ @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);
|
2022-04-22 18:54:08 +00:00
|
|
|
+ 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);
|
2022-03-19 13:39:38 +00:00
|
|
|
+ if (player == null) return new com.destroystokyo.paper.profile.CraftPlayerProfile(uuid, name);
|
|
|
|
+
|
|
|
|
+ if (Objects.equals(uuid, player.getUniqueId()) && Objects.equals(name, player.getName())) {
|
|
|
|
+ return new com.destroystokyo.paper.profile.CraftPlayerProfile((CraftPlayer) player);
|
2021-06-11 12:02:28 +00:00
|
|
|
+ }
|
2022-03-19 13:39:38 +00:00
|
|
|
+
|
|
|
|
+ final com.mojang.authlib.GameProfile profile = new com.mojang.authlib.GameProfile(uuid, name);
|
|
|
|
+ profile.getProperties().putAll(((CraftPlayer)player).getHandle().getGameProfile().getProperties());
|
|
|
|
+ return new com.destroystokyo.paper.profile.CraftPlayerProfile(profile);
|
2021-06-11 12:02:28 +00:00
|
|
|
+ }
|
|
|
|
// Paper end
|
|
|
|
}
|
2022-02-12 13:20:33 +00:00
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java
|
2022-02-18 17:30:37 +00:00
|
|
|
index 2d49bd6f3f017d43dfaa23cedf35040b64bcdcf8..661d8e9882509700faa9bc10a2e3074c5553af44 100644
|
2022-02-12 13:20:33 +00:00
|
|
|
--- a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java
|
|
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java
|
|
|
|
@@ -27,7 +27,7 @@ import org.bukkit.profile.PlayerProfile;
|
|
|
|
import org.bukkit.profile.PlayerTextures;
|
|
|
|
|
|
|
|
@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) {
|
|
|
|
@@ -92,8 +92,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();
|
2021-06-11 12:02:28 +00:00
|
|
|
+ // Paper end
|
2022-02-12 13:20:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void rebuildDirtyProperties() {
|
2022-02-12 18:29:41 +00:00
|
|
|
@@ -236,6 +238,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());
|
|
|
|
@@ -251,10 +254,12 @@ public final class CraftPlayerProfile implements PlayerProfile {
|
|
|
|
});
|
|
|
|
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);
|
|
|
|
|
|
|
|
@@ -270,7 +275,7 @@ public final class CraftPlayerProfile implements PlayerProfile {
|
|
|
|
profile.properties.put(property.getName(), property);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
-
|
|
|
|
+ // Paper - diff on change
|
|
|
|
return profile;
|
|
|
|
}
|
|
|
|
}
|
2022-02-12 13:20:33 +00:00
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java
|
2022-02-18 17:30:37 +00:00
|
|
|
index e5b61bc1f3a4bfccca386360c4920ffb8b768308..ab1fd3fb39bd40fb867432861462db5f866bce6f 100644
|
2022-02-12 13:20:33 +00:00
|
|
|
--- a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java
|
|
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java
|
2022-02-18 17:30:37 +00:00
|
|
|
@@ -48,7 +48,7 @@ public final class CraftPlayerTextures implements PlayerTextures {
|
2022-02-12 13:20:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- private final CraftPlayerProfile profile;
|
|
|
|
+ private final com.destroystokyo.paper.profile.SharedPlayerProfile profile; // Paper
|
|
|
|
|
|
|
|
// The textures data is loaded lazily:
|
|
|
|
private boolean loaded = false;
|
2022-02-18 17:30:37 +00:00
|
|
|
@@ -67,7 +67,7 @@ public final class CraftPlayerTextures implements PlayerTextures {
|
2022-02-12 13:20:33 +00:00
|
|
|
// 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
|
2021-06-11 12:02:28 +00:00
|
|
|
this.profile = profile;
|
|
|
|
}
|
2022-02-12 13:20:33 +00:00
|
|
|
|