aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/kr/syeyoung/dungeonsguide/features/impl
diff options
context:
space:
mode:
authorsyeyoung <cyong06@naver.com>2021-04-24 17:51:21 +0900
committersyeyoung <cyong06@naver.com>2021-04-24 17:51:21 +0900
commit60f06415a290a659b5c8a56b1d9f879bbea91d10 (patch)
tree470e368a5eb9790b7cb4af0eb1d394723e4c1177 /src/main/java/kr/syeyoung/dungeonsguide/features/impl
parente146d7f64df7782bebdb238b52bd67ed916abd6b (diff)
downloadSkyblock-Dungeons-Guide-60f06415a290a659b5c8a56b1d9f879bbea91d10.tar.gz
Skyblock-Dungeons-Guide-60f06415a290a659b5c8a56b1d9f879bbea91d10.tar.bz2
Skyblock-Dungeons-Guide-60f06415a290a659b5c8a56b1d9f879bbea91d10.zip
commit #2
- YAY PARTY KCIKER!!@!@!
Diffstat (limited to 'src/main/java/kr/syeyoung/dungeonsguide/features/impl')
-rw-r--r--src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/FeatureViewPlayerOnJoin.java164
-rw-r--r--src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/api/ApiFetchur.java136
-rw-r--r--src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/api/CachedData.java11
-rw-r--r--src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/api/SkinFetchur.java71
4 files changed, 344 insertions, 38 deletions
diff --git a/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/FeatureViewPlayerOnJoin.java b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/FeatureViewPlayerOnJoin.java
index f7e2f020..ceaaec84 100644
--- a/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/FeatureViewPlayerOnJoin.java
+++ b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/FeatureViewPlayerOnJoin.java
@@ -1,25 +1,37 @@
package kr.syeyoung.dungeonsguide.features.impl.party;
+import com.mojang.authlib.GameProfile;
+import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import io.github.moulberry.hychat.HyChat;
import io.github.moulberry.hychat.chat.ChatManager;
import io.github.moulberry.hychat.gui.GuiChatBox;
+import kr.syeyoung.dungeonsguide.config.guiconfig.FeatureEditPane;
import kr.syeyoung.dungeonsguide.features.FeatureRegistry;
import kr.syeyoung.dungeonsguide.features.SimpleFeature;
import kr.syeyoung.dungeonsguide.features.impl.party.api.ApiFetchur;
import kr.syeyoung.dungeonsguide.features.impl.party.api.PlayerProfile;
+import kr.syeyoung.dungeonsguide.features.impl.party.api.SkinFetchur;
import kr.syeyoung.dungeonsguide.features.listener.ChatListener;
import kr.syeyoung.dungeonsguide.features.listener.GuiPostRenderListener;
import kr.syeyoung.dungeonsguide.gui.MPanel;
import lombok.Getter;
+import lombok.Setter;
import lombok.SneakyThrows;
import net.minecraft.client.Minecraft;
+import net.minecraft.client.entity.AbstractClientPlayer;
+import net.minecraft.client.entity.EntityOtherPlayerMP;
import net.minecraft.client.gui.*;
+import net.minecraft.client.gui.inventory.GuiInventory;
+import net.minecraft.client.network.NetworkPlayerInfo;
import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.OpenGlHelper;
+import net.minecraft.client.renderer.texture.TextureManager;
+import net.minecraft.client.resources.DefaultPlayerSkin;
+import net.minecraft.client.resources.SkinManager;
import net.minecraft.event.HoverEvent;
-import net.minecraft.util.ChatComponentStyle;
-import net.minecraft.util.ChatComponentText;
-import net.minecraft.util.ChatStyle;
-import net.minecraft.util.IChatComponent;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.*;
+import net.minecraft.world.World;
import net.minecraftforge.client.event.ClientChatReceivedEvent;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.fml.common.Loader;
@@ -27,6 +39,7 @@ import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
import java.awt.*;
+import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
@@ -41,12 +54,18 @@ public class FeatureViewPlayerOnJoin extends SimpleFeature implements GuiPostRen
private Rectangle popupRect;
private String lastuid; // actually current uid
private Future<Optional<PlayerProfile>> profileFuture;
+ private Future<Optional<GameProfile>> gfFuture;
+ private Future<SkinFetchur.SkinSet> skinFuture;
+ private FakePlayer fakePlayer;
@SneakyThrows
@Override
public void onGuiPostRender(GuiScreenEvent.DrawScreenEvent.Post rendered) {
if (!(Minecraft.getMinecraft().currentScreen instanceof GuiChat)) {
popupRect = null;
profileFuture = null;
+ gfFuture = null;
+ skinFuture= null;
+ fakePlayer= null;
return;
}
ScaledResolution scaledResolution = new ScaledResolution(Minecraft.getMinecraft());
@@ -65,18 +84,24 @@ public class FeatureViewPlayerOnJoin extends SimpleFeature implements GuiPostRen
popupRect = null;
profileFuture = null;
lastuid = null;
+ gfFuture = null;
+ skinFuture= null;
+ fakePlayer= null;
}
if (uid != null && !uid.equals(lastuid) && (popupRect==null || !popupRect.contains(mouseX, mouseY))) {
popupRect = null;
profileFuture = null;
+ gfFuture = null;
+ skinFuture= null;
+ fakePlayer= null;
lastuid = uid;
}
if (lastuid == null) return;
if (popupRect == null) {
- popupRect = new Rectangle(mouseX, mouseY, 100, 200);
+ popupRect = new Rectangle(mouseX, mouseY, 150, 200);
if (popupRect.y + popupRect.height > scaledResolution.getScaledHeight()) {
popupRect.y -= popupRect.y + popupRect.height - scaledResolution.getScaledHeight();
}
@@ -86,25 +111,96 @@ public class FeatureViewPlayerOnJoin extends SimpleFeature implements GuiPostRen
profileFuture = ApiFetchur.fetchMostRecentProfileAsync(lastuid, FeatureRegistry.PARTYKICKER_APIKEY.getAPIKey());
}
- MPanel.clip(scaledResolution, popupRect.x, popupRect.y, popupRect.width, popupRect.height);
+ if (gfFuture == null) {
+ gfFuture = ApiFetchur.getSkinGameProfileByUUIDAsync(lastuid);
+ }
+ if (skinFuture == null && gfFuture.isDone()) {
+ skinFuture = SkinFetchur.getSkinSet(gfFuture.get().orElse(null));
+ }
+
+ if (fakePlayer == null && skinFuture != null && profileFuture != null && skinFuture.isDone() && profileFuture.isDone()) {
+ fakePlayer = new FakePlayer(gfFuture.get().orElse(null), skinFuture.get(), profileFuture.get().orElse(null));
+ }
+
+
+ render(popupRect, scaledResolution, mouseX, mouseY, profileFuture.isDone() ? profileFuture.get() : null);
+ }
+
+ public void render(Rectangle popupRect, ScaledResolution scaledResolution, int mouseX, int mouseY, Optional<PlayerProfile> playerProfile) {
- GL11.glEnable(GL11.GL_SCISSOR_TEST);
GlStateManager.pushMatrix();
GlStateManager.translate(popupRect.x, popupRect.y, 0);
Gui.drawRect(0,0, popupRect.width, popupRect.height, 0xFF000000);
- System.out.println(lastuid + " - "+uid);
- if (!profileFuture.isDone()) {
+ Gui.drawRect(1,1, popupRect.width-1, popupRect.height-1, 0xFFAAAAAA);
+ if (playerProfile == null) {
Minecraft.getMinecraft().fontRendererObj.drawString("Fetching data...", 5,5, 0xFFFFFFFF);
- } else {
- Optional<PlayerProfile> playerProfile = profileFuture.get();
- if (playerProfile.isPresent()) {
- Minecraft.getMinecraft().fontRendererObj.drawString(playerProfile.get().getMemberUID(), 5,5, 0xFFFFFFFF);
- } else {
- Minecraft.getMinecraft().fontRendererObj.drawString("User could not be found", 5,5, 0xFFFFFFFF);
+ GlStateManager.popMatrix();
+ return;
+ }
+ if (!playerProfile.isPresent()) {
+ Minecraft.getMinecraft().fontRendererObj.drawString("User could not be found", 5,5, 0xFFFFFFFF);
+ GlStateManager.popMatrix();
+ return;
+ }
+
+
+ GL11.glEnable(GL11.GL_SCISSOR_TEST);
+ MPanel.clip(scaledResolution, popupRect.x, popupRect.y, popupRect.width, popupRect.height);
+ Gui.drawRect(0,0, 80, popupRect.height-40, 0xFF000000);
+ Gui.drawRect(1,1, 79, popupRect.height-41, 0xFF444444);
+ GlStateManager.color(1, 1, 1, 1.0F);
+
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+ if (fakePlayer != null) {
+ GuiInventory.drawEntityOnScreen(40, 150, 60, -(mouseX - popupRect.x - 75), 0, fakePlayer);
+ fr.drawString(fakePlayer.getName(), (80 - fr.getStringWidth(fakePlayer.getName())) / 2, 15, 0xFFEFFF00);
+
+ int relX = mouseX - popupRect.x;
+ int relY = mouseY - popupRect.y;
+ ItemStack toHover = null;
+ System.out.println(relX + " , "+relY);
+ if (relX > 5 && relX < 75) {
+ if (33<=relY && relY <= 66) {
+ toHover = fakePlayer.getInventory()[3];
+ } else if (66 <= relY && relY <= 108) {
+ toHover = fakePlayer.getInventory()[2];
+ } else if (108 <= relY && relY <= 130) {
+ toHover = fakePlayer.getInventory()[1];
+ } else if (130 <= relY && relY <= 154) {
+ toHover = fakePlayer.getInventory()[0];
+ }
}
+
+ if (toHover != null) {
+ List<String> list = toHover.getTooltip(Minecraft.getMinecraft().thePlayer, Minecraft.getMinecraft().gameSettings.advancedItemTooltips);
+
+ for (int i = 0; i < list.size(); ++i)
+ {
+ if (i == 0)
+ {
+ list.set(i, toHover.getRarity().rarityColor + (String)list.get(i));
+ }
+ else
+ {
+ list.set(i, EnumChatFormatting.GRAY + (String)list.get(i));
+ }
+ }
+
+ FontRenderer font = toHover.getItem().getFontRenderer(toHover);
+ System.out.println(list);
+ GL11.glDisable(GL11.GL_SCISSOR_TEST);
+ FontRenderer theRenderer = (font == null ? fr : font);
+ int minY = scaledResolution.getScaledHeight() - (list.size()+4) * theRenderer.FONT_HEIGHT - popupRect.y;
+
+ FeatureEditPane.drawHoveringText(list,relX, Math.min(minY, relY), theRenderer);
+ GL11.glEnable(GL11.GL_SCISSOR_TEST);
+ }
+ } else {
+ fr.drawString("Loading", 5,35, 0xFFEFFF00);
}
- GlStateManager.popMatrix();
+
GL11.glDisable(GL11.GL_SCISSOR_TEST);
+ GlStateManager.popMatrix(); // 33 66 108 130 154 // 5 75
}
public IChatComponent getHoveredComponent(ScaledResolution scaledResolution) {
@@ -160,4 +256,40 @@ public class FeatureViewPlayerOnJoin extends SimpleFeature implements GuiPostRen
return cached;
}
}
+
+ public static class FakePlayer extends EntityOtherPlayerMP {
+ @Setter
+ @Getter
+ private PlayerProfile skyblockProfile;
+ private SkinFetchur.SkinSet skinSet;
+ private PlayerProfile.Armor armor;
+ private FakePlayer(World w) {
+ super(w, null);
+ throw new UnsupportedOperationException("what");
+ }
+ public FakePlayer(GameProfile playerProfile, SkinFetchur.SkinSet skinSet, PlayerProfile skyblockProfile) {
+ super(Minecraft.getMinecraft().theWorld, playerProfile);
+ this.skyblockProfile = skyblockProfile;
+ this.skinSet = skinSet;
+ armor= skyblockProfile.getCurrentArmor();
+ this.inventory.armorInventory = skyblockProfile.getCurrentArmor().getArmorSlots();
+ }
+
+ public String getSkinType() {
+ return this.skinSet == null ? DefaultPlayerSkin.getSkinType(getGameProfile().getId()) : this.skinSet.getSkinType();
+ }
+
+ public ResourceLocation getLocationSkin() {
+ return com.google.common.base.Objects.firstNonNull(skinSet.getSkinLoc(), DefaultPlayerSkin.getDefaultSkin(getGameProfile().getId()));
+ }
+
+ public ResourceLocation getLocationCape() {
+ return skinSet.getCapeLoc();
+ }
+
+ @Override
+ public ItemStack[] getInventory() {
+ return this.inventory.armorInventory;
+ }
+ }
}
diff --git a/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/api/ApiFetchur.java b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/api/ApiFetchur.java
index 00ede47d..adc15d43 100644
--- a/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/api/ApiFetchur.java
+++ b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/api/ApiFetchur.java
@@ -1,32 +1,32 @@
package kr.syeyoung.dungeonsguide.features.impl.party.api;
import com.google.gson.*;
+import com.mojang.authlib.GameProfile;
import kr.syeyoung.dungeonsguide.utils.TextUtils;
-import lombok.AllArgsConstructor;
-import lombok.Data;
+import net.minecraft.client.Minecraft;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompressedStreamTools;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
-import org.apache.commons.io.IOUtils;
+import scala.tools.cmd.Opt;
import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
-import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.*;
public class ApiFetchur {
private static final Gson gson = new Gson();
- private static final Map<String, CachedData<PlayerProfile>> playerProfileCache = new HashMap<>();
- private static final Map<String, CachedData<String>> nicknameToUID = new HashMap<>();
+ private static final Map<String, CachedData<PlayerProfile>> playerProfileCache = new ConcurrentHashMap<>();
+ private static final Map<String, CachedData<String>> nicknameToUID = new ConcurrentHashMap<>();
+ private static final Map<String, CachedData<String>> UIDtoNickname = new ConcurrentHashMap<>();
+ private static final Map<String, CachedData<GameProfile>> UIDtoGameProfile = new ConcurrentHashMap<>();
+
private static final ExecutorService ex = Executors.newFixedThreadPool(4);
public static void purgeCache() {
@@ -34,28 +34,71 @@ public class ApiFetchur {
nicknameToUID.clear();
}
- @Data
- @AllArgsConstructor
- public static class CachedData<T> {
- private final long expire;
- private final T data;
- }
-
public static JsonObject getJson(String url) throws IOException {
URLConnection connection = new URL(url).openConnection();
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
return gson.fromJson(new InputStreamReader(connection.getInputStream()), JsonObject.class);
}
+ public static JsonArray getJsonArr(String url) throws IOException {
+ URLConnection connection = new URL(url).openConnection();
+ connection.setConnectTimeout(10000);
+ connection.setReadTimeout(10000);
+ return gson.fromJson(new InputStreamReader(connection.getInputStream()), JsonArray.class);
+ }
+ private static final Map<String, CompletableFuture<Optional<GameProfile>>> completableFutureMap4 = new ConcurrentHashMap<>();
+ public static CompletableFuture<Optional<GameProfile>> getSkinGameProfileByUUIDAsync(String uid) {
+ if (UIDtoGameProfile.containsKey(uid)) {
+ CachedData<GameProfile> cachedData = UIDtoGameProfile.get(uid);
+ if (cachedData.getExpire() > System.currentTimeMillis()) {
+ return CompletableFuture.completedFuture(Optional.ofNullable(cachedData.getData()));
+ }
+ UIDtoGameProfile.remove(uid);
+ }
+ if (completableFutureMap4.containsKey(uid)) return completableFutureMap4.get(uid);
+
+ CompletableFuture<Optional<GameProfile>> completableFuture = new CompletableFuture<>();
+ fetchNicknameAsync(uid).thenAccept(nick -> {
+ if (!nick.isPresent()) {
+ completableFuture.complete(Optional.empty());
+ return;
+ }
+ ex.submit(() -> {
+ try {
+ Optional<GameProfile> playerProfile = getSkinGameProfileByUUID(uid,nick.get());
+ UIDtoGameProfile.put(uid, new CachedData<GameProfile>(System.currentTimeMillis()+1000*60*30, playerProfile.orElse(null)));
+ completableFuture.complete(playerProfile);
+ completableFutureMap4.remove(uid);
+ return;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ completableFuture.complete(Optional.empty());
+ completableFutureMap4.remove(uid);
+ });
+ });
+ completableFutureMap4.put(uid, completableFuture);
+ return completableFuture;
+ }
+
+ public static Optional<GameProfile> getSkinGameProfileByUUID(String uid, String nickname) throws IOException {
+ GameProfile gameProfile = new GameProfile(UUID.fromString(uid), nickname);
+ GameProfile newProf = Minecraft.getMinecraft().getSessionService().fillProfileProperties(gameProfile, true);
+ return newProf == gameProfile ? Optional.empty() : Optional.of(newProf);
+ }
+
+
+ private static final Map<String, CompletableFuture<Optional<PlayerProfile>>> completableFutureMap = new ConcurrentHashMap<>();
public static CompletableFuture<Optional<PlayerProfile>> fetchMostRecentProfileAsync(String uid, String apiKey) {
if (playerProfileCache.containsKey(uid)) {
CachedData<PlayerProfile> cachedData = playerProfileCache.get(uid);
- if (cachedData.expire > System.currentTimeMillis()) {
- return CompletableFuture.completedFuture(Optional.ofNullable(cachedData.data));
+ if (cachedData.getExpire() > System.currentTimeMillis()) {
+ return CompletableFuture.completedFuture(Optional.ofNullable(cachedData.getData()));
}
playerProfileCache.remove(uid);
}
+ if (completableFutureMap.containsKey(uid)) return completableFutureMap.get(uid);
CompletableFuture<Optional<PlayerProfile>> completableFuture = new CompletableFuture<>();
ex.submit(() -> {
@@ -63,24 +106,62 @@ public class ApiFetchur {
Optional<PlayerProfile> playerProfile = fetchMostRecentProfile(uid, apiKey);
playerProfileCache.put(uid, new CachedData<PlayerProfile>(System.currentTimeMillis()+1000*60*30, playerProfile.orElse(null)));
completableFuture.complete(playerProfile);
+ completableFutureMap.remove(uid);
return;
} catch (IOException e) {
e.printStackTrace();
}
- playerProfileCache.put(uid, new CachedData<PlayerProfile>(System.currentTimeMillis()+1000*60*30, null));
completableFuture.complete(Optional.empty());
+ completableFutureMap.remove(uid);
});
+ completableFutureMap.put(uid, completableFuture);
return completableFuture;
}
+ private static final Map<String, CompletableFuture<Optional<String>>> completableFutureMap3 = new ConcurrentHashMap<>();
+ public static CompletableFuture<Optional<String>> fetchNicknameAsync(String uid) {
+ if (UIDtoNickname.containsKey(uid)) {
+ CachedData<String> cachedData = UIDtoNickname.get(uid);
+ if (cachedData.getExpire() > System.currentTimeMillis()) {
+ return CompletableFuture.completedFuture(Optional.ofNullable(cachedData.getData()));
+ }
+ UIDtoNickname.remove(uid);
+ }
+ if (completableFutureMap3.containsKey(uid)) return completableFutureMap3.get(uid);
+
+
+ CompletableFuture<Optional<String>> completableFuture = new CompletableFuture<>();
+
+ ex.submit(() -> {
+ try {
+ Optional<String> playerProfile = fetchNickname(uid);
+ UIDtoNickname.put(uid, new CachedData<String>(System.currentTimeMillis()+1000*60*60*12,playerProfile.orElse(null)));
+ if (playerProfile.isPresent())
+ nicknameToUID.put(playerProfile.orElse(null), new CachedData<>(System.currentTimeMillis()+1000*60*60*12, uid));
+ completableFuture.complete(playerProfile);
+ completableFutureMap3.remove(uid);
+ return;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ completableFuture.complete(Optional.empty());
+ completableFutureMap3.remove(uid);
+ });
+ completableFutureMap3.put(uid, completableFuture);
+
+ return completableFuture;
+ }
+
+ private static final Map<String, CompletableFuture<Optional<String>>> completableFutureMap2 = new ConcurrentHashMap<>();
public static CompletableFuture<Optional<String>> fetchUUIDAsync(String nickname) {
if (nicknameToUID.containsKey(nickname)) {
CachedData<String> cachedData = nicknameToUID.get(nickname);
- if (cachedData.expire > System.currentTimeMillis()) {
- return CompletableFuture.completedFuture(Optional.ofNullable(cachedData.data));
+ if (cachedData.getExpire() > System.currentTimeMillis()) {
+ return CompletableFuture.completedFuture(Optional.ofNullable(cachedData.getData()));
}
nicknameToUID.remove(nickname);
}
+ if (completableFutureMap2.containsKey(nickname)) return completableFutureMap2.get(nickname);
CompletableFuture<Optional<String>> completableFuture = new CompletableFuture<>();
@@ -88,15 +169,20 @@ public class ApiFetchur {
ex.submit(() -> {
try {
Optional<String> playerProfile = fetchUUID(nickname);
- nicknameToUID.put(nickname, new CachedData<String>(System.currentTimeMillis()+1000*60*60,playerProfile.orElse(null)));
+ nicknameToUID.put(nickname, new CachedData<String>(System.currentTimeMillis()+1000*60*60*12,playerProfile.orElse(null)));
+ if (playerProfile.isPresent())
+ UIDtoNickname.put(playerProfile.orElse(null), new CachedData<>(System.currentTimeMillis()+1000*60*60*12, nickname));
+
completableFuture.complete(playerProfile);
+ completableFutureMap2.remove(nickname);
return;
} catch (IOException e) {
e.printStackTrace();
}
- nicknameToUID.put(nickname, new CachedData<String>(System.currentTimeMillis()+1000*60*30, null));
completableFuture.complete(Optional.empty());
+ completableFutureMap2.remove(nickname);
});
+ completableFutureMap2.put(nickname, completableFuture);
return completableFuture;
}
@@ -106,6 +192,12 @@ public class ApiFetchur {
if (json.has("error")) return Optional.empty();
return Optional.of(TextUtils.insertDashUUID(json.get("id").getAsString()));
}
+ public static Optional<String> fetchNickname(String uuid) throws IOException {
+ try {
+ JsonArray json = getJsonArr("https://api.mojang.com/user/profiles/" + uuid.replace("-", "") + "/names");
+ return Optional.of(json.get(json.size()-1).getAsJsonObject().get("name").getAsString());
+ } catch (Exception e) {return Optional.empty();}
+ }
public static List<PlayerProfile> fetchPlayerProfiles(String uid, String apiKey) throws IOException {
JsonObject json = getJson("https://api.hypixel.net/skyblock/profiles?uuid="+uid+"&key="+apiKey);
diff --git a/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/api/CachedData.java b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/api/CachedData.java
new file mode 100644
index 00000000..2d5e2bc7
--- /dev/null
+++ b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/api/CachedData.java
@@ -0,0 +1,11 @@
+package kr.syeyoung.dungeonsguide.features.impl.party.api;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+public class CachedData<T> {
+ private final long expire;
+ private final T data;
+}
diff --git a/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/api/SkinFetchur.java b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/api/SkinFetchur.java
new file mode 100644
index 00000000..977aec47
--- /dev/null
+++ b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/party/api/SkinFetchur.java
@@ -0,0 +1,71 @@
+package kr.syeyoung.dungeonsguide.features.impl.party.api;
+
+import com.mojang.authlib.GameProfile;
+import com.mojang.authlib.minecraft.MinecraftProfileTexture;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.resources.DefaultPlayerSkin;
+import net.minecraft.client.resources.SkinManager;
+import net.minecraft.util.ResourceLocation;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class SkinFetchur {
+
+ private static Map<String, CachedData<SkinSet>> skinSetMap = new ConcurrentHashMap<>();
+
+ private static Map<String, CompletableFuture<SkinSet>> currentReq = new HashMap<>();
+
+ public static CompletableFuture<SkinSet> getSkinSet(GameProfile gameProfile) {
+ if (gameProfile == null) {
+ return CompletableFuture.completedFuture(new SkinSet(DefaultPlayerSkin.getDefaultSkinLegacy(), null, "default"));
+ }
+ if (skinSetMap.containsKey(gameProfile.getId().toString())) {
+ CachedData<SkinSet> ss = skinSetMap.get(gameProfile.getId().toString());
+ if (ss.getExpire() > System.currentTimeMillis())
+ CompletableFuture.completedFuture(skinSetMap.get(gameProfile.getId().toString()).getData());
+ skinSetMap.remove(gameProfile.getId().toString());
+ }
+ if (currentReq.containsKey(gameProfile.getId().toString()))
+ return currentReq.get(gameProfile.getId().toString());
+
+ SkinSet skinSet = new SkinSet();
+ CompletableFuture<SkinSet> skinSet2 = new CompletableFuture<>();
+ currentReq.put(gameProfile.getId().toString(), skinSet2);
+ Minecraft.getMinecraft().getSkinManager().loadProfileTextures(gameProfile, new SkinManager.SkinAvailableCallback() {
+ public void skinAvailable(MinecraftProfileTexture.Type p_180521_1_, ResourceLocation location, MinecraftProfileTexture profileTexture) {
+ switch (p_180521_1_) {
+ case SKIN:
+ skinSet.setSkinLoc(location);
+ skinSet.setSkinType(profileTexture.getMetadata("model"));
+ if (skinSet.getSkinType() == null) {
+ skinSet.setSkinType("default");
+ }
+ skinSet2.complete(skinSet);
+ skinSetMap.put(gameProfile.getId().toString(), new CachedData<>(System.currentTimeMillis() + 1000*60*60*3, skinSet));
+ currentReq.get(gameProfile.getId().toString());
+ break;
+ case CAPE:
+ skinSet.setCapeLoc(location);
+ }
+ }
+ }, true);
+
+ return skinSet2;
+ }
+
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class SkinSet {
+ private ResourceLocation skinLoc;
+ private ResourceLocation capeLoc;
+ private String skinType;
+ }
+}