aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de
diff options
context:
space:
mode:
authorKevin <92656833+kevinthegreat1@users.noreply.github.com>2024-06-30 14:35:01 +0800
committerGitHub <noreply@github.com>2024-06-30 14:35:01 +0800
commitb75bfdbeae151253d60c01c2c268dfa511997883 (patch)
tree11735fc95f58a6d4e85bd0a44cb009ca418924e5 /src/main/java/de
parentbc773c76b3362af799577c89fa6f7b79c1ee013b (diff)
parentd824171262521557c73688068804c5d9119fbc7c (diff)
downloadSkyblocker-b75bfdbeae151253d60c01c2c268dfa511997883.tar.gz
Skyblocker-b75bfdbeae151253d60c01c2c268dfa511997883.tar.bz2
Skyblocker-b75bfdbeae151253d60c01c2c268dfa511997883.zip
Merge pull request #708 from BigloBot/main
Profile Viewer
Diffstat (limited to 'src/main/java/de')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/PetCache.java27
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java65
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerPage.java19
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java230
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java55
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/CollectionsPage.java100
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java136
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonClassWidget.java62
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonFloorRunsWidget.java55
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonHeaderWidget.java46
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonMiscStatsWidgets.java61
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonsPage.java39
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Inventory.java120
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/InventoryPage.java113
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PaginationButton.java46
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java186
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PlayerInventory.java73
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/BackpackItemLoader.java34
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/InventoryItemLoader.java29
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/ItemLoader.java139
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/PetsInventoryItemLoader.java42
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/WardrobeInventoryItemLoader.java40
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillWidget.java95
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillsPage.java93
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayerWidget.java93
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayersPage.java41
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/LevelFinder.java307
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SkullCreator.java27
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SubPageSelectButton.java65
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java1
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java38
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDeathWidget.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ReputationWidget.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SkillsWidget.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/ApiUtils.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Http.java11
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/ProfileUtils.java37
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/scheduler/Scheduler.java46
44 files changed, 2539 insertions, 74 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index d793e73d..05c7fb1e 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -36,6 +36,7 @@ import de.hysky.skyblocker.skyblock.item.tooltip.BackpackPreview;
import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
import de.hysky.skyblocker.skyblock.item.tooltip.TooltipManager;
import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
+import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen;
import de.hysky.skyblocker.skyblock.rift.TheRift;
import de.hysky.skyblocker.skyblock.searchoverlay.SearchOverManager;
import de.hysky.skyblocker.skyblock.shortcut.Shortcuts;
@@ -105,6 +106,7 @@ public class SkyblockerMod implements ClientModInitializer {
Utils.init();
SkyblockerConfigManager.init();
SkyblockerScreen.initClass();
+ ProfileViewerScreen.initClass();
Tips.init();
NEURepoManager.init();
//ImageRepoLoader.init();
diff --git a/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java
index dfee0d24..1493cf26 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java
@@ -8,9 +8,11 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.injected.SkyblockerStack;
import de.hysky.skyblocker.skyblock.PetCache.PetInfo;
import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
+import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
import it.unimi.dsi.fastutil.ints.IntIntPair;
+import net.minecraft.client.MinecraftClient;
import net.minecraft.component.ComponentHolder;
import net.minecraft.component.type.ItemEnchantmentsComponent;
import net.minecraft.item.ItemStack;
@@ -108,8 +110,8 @@ public abstract class ItemStackMixin implements ComponentHolder, SkyblockerStack
}
@Unique
- private boolean skyblocker$shouldProcess() {
- return Utils.isOnSkyblock() && SkyblockerConfigManager.get().mining.enableDrillFuel && ItemUtils.hasCustomDurability((ItemStack) (Object) this);
+ private boolean skyblocker$shouldProcess() { // Durability bar renders atop of tooltips in ProfileViewer so disable on this screen
+ return !(MinecraftClient.getInstance().currentScreen instanceof ProfileViewerScreen) && Utils.isOnSkyblock() && SkyblockerConfigManager.get().mining.enableDrillFuel && ItemUtils.hasCustomDurability((ItemStack) (Object) this);
}
@Unique
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java b/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java
index d8cd6e48..8d0406cb 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java
@@ -1,22 +1,10 @@
package de.hysky.skyblocker.skyblock;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.util.concurrent.CompletableFuture;
-import java.util.Optional;
-
-import org.jetbrains.annotations.Nullable;
-import org.slf4j.Logger;
-
import com.google.gson.JsonParser;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.codecs.RecordCodecBuilder;
-
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
@@ -26,6 +14,16 @@ import net.minecraft.client.gui.screen.ingame.GenericContainerScreen;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.screen.slot.Slot;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
/**
* Doesn't work with auto pet right now because thats complicated.
@@ -135,12 +133,13 @@ public class PetCache {
return CACHED_PETS.containsKey(uuid) && CACHED_PETS.get(uuid).containsKey(profileId) ? CACHED_PETS.get(uuid).get(profileId) : null;
}
- public record PetInfo(String type, double exp, String tier, Optional<String> uuid) {
+ public record PetInfo(String type, double exp, String tier, Optional<String> uuid, Optional<String> item) {
public static final Codec<PetInfo> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.STRING.fieldOf("type").forGetter(PetInfo::type),
Codec.DOUBLE.fieldOf("exp").forGetter(PetInfo::exp),
Codec.STRING.fieldOf("tier").forGetter(PetInfo::tier),
- Codec.STRING.optionalFieldOf("uuid").forGetter(PetInfo::uuid))
+ Codec.STRING.optionalFieldOf("uuid").forGetter(PetInfo::uuid),
+ Codec.STRING.optionalFieldOf("heldItem").forGetter(PetInfo::item))
.apply(instance, PetInfo::new));
private static final Codec<Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, PetInfo>>> SERIALIZATION_CODEC = Codec.unboundedMap(Codec.STRING,
Codec.unboundedMap(Codec.STRING, CODEC).xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new)
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java
index 7a5abed1..d1fc08ec 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java
@@ -304,7 +304,7 @@ public class DungeonScore {
if (s.equals("You")) return MinecraftClient.getInstance().getSession().getUsername(); //This will be wrong if the dead player is called 'You' but that's unlikely
else return s;
});
- ProfileUtils.updateProfile(whoDied).thenAccept(player -> firstDeathHasSpiritPet = hasSpiritPet(player, whoDied));
+ ProfileUtils.updateProfileByName(whoDied).thenAccept(player -> firstDeathHasSpiritPet = hasSpiritPet(player, whoDied));
}
private static void checkMessageForWatcher(String message) {
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java
index 7eda7646..01ffc144 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java
@@ -21,8 +21,8 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ItemStackBuilder {
- private static final Pattern SKULL_UUID_PATTERN = Pattern.compile("(?<=SkullOwner:\\{)Id:\"(.{36})\"");
- private static final Pattern SKULL_TEXTURE_PATTERN = Pattern.compile("(?<=Properties:\\{textures:\\[0:\\{Value:)\"(.+?)\"");
+ public static final Pattern SKULL_UUID_PATTERN = Pattern.compile("(?<=SkullOwner:\\{)Id:\"(.{36})\"");
+ public static final Pattern SKULL_TEXTURE_PATTERN = Pattern.compile("(?<=Properties:\\{textures:\\[0:\\{Value:)\"(.+?)\"");
private static final Pattern COLOR_PATTERN = Pattern.compile("color:(\\d+)");
private static final Pattern EXPLOSION_COLOR_PATTERN = Pattern.compile("\\{Explosion:\\{(?:Type:[0-9a-z]+,)?Colors:\\[(?<color>[0-9]+)]\\}");
private static Map<String, Map<Rarity, PetNumbers>> petNums;
@@ -138,7 +138,7 @@ public class ItemStackBuilder {
return list;
}
- private static String injectData(String string, List<Pair<String, String>> injectors) {
+ public static String injectData(String string, List<Pair<String, String>> injectors) {
for (Pair<String, String> injector : injectors) {
string = string.replaceAll(injector.getLeft(), injector.getRight());
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java
new file mode 100644
index 00000000..d867a0e6
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java
@@ -0,0 +1,65 @@
+package de.hysky.skyblocker.skyblock.profileviewer;
+
+import com.mojang.blaze3d.systems.RenderSystem;
+import de.hysky.skyblocker.skyblock.profileviewer.utils.SkullCreator;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
+import net.minecraft.client.gui.widget.ClickableWidget;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+
+import java.util.Map;
+
+public class ProfileViewerNavButton extends ClickableWidget {
+ private final static Identifier BUTTON_TEXTURES_TOGGLED = Identifier.of("container/creative_inventory/tab_top_selected_2");
+ private final static Identifier BUTTON_TEXTURES = Identifier.of("container/creative_inventory/tab_top_unselected_2");
+ private boolean toggled;
+ private final int index;
+ private final ProfileViewerScreen screen;
+ private final ItemStack icon;
+
+ private static final Map<String, ItemStack> HEAD_ICON = Map.ofEntries(
+ Map.entry("Skills", Ico.IRON_SWORD),
+ Map.entry("Slayers", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzkzMzZkN2NjOTVjYmY2Njg5ZjVlOGM5NTQyOTRlYzhkMWVmYzQ5NGE0MDMxMzI1YmI0MjdiYzgxZDU2YTQ4NGQifX19")),
+ Map.entry("Pets", Ico.BONE),
+ Map.entry("Dungeons", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzliNTY4OTViOTY1OTg5NmFkNjQ3ZjU4NTk5MjM4YWY1MzJkNDZkYjljMWIwMzg5YjhiYmViNzA5OTlkYWIzM2QifX19")),
+ Map.entry("Inventories", Ico.E_CHEST),
+ Map.entry("Collections", Ico.PAINTING)
+ );
+
+ public ProfileViewerNavButton(ProfileViewerScreen screen, String tabName, int index, boolean toggled) {
+ super(-100, -100, 28, 32, Text.empty());
+ this.screen = screen;
+ this.toggled = toggled;
+ this.index = index;
+ this.icon = HEAD_ICON.getOrDefault(tabName, Ico.BARRIER);
+ }
+
+ @Override
+ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
+ RenderSystem.disableDepthTest();
+
+ context.drawGuiTexture(toggled ? BUTTON_TEXTURES_TOGGLED : BUTTON_TEXTURES, this.getX(), this.getY(), this.width, this.height - ((this.toggled) ? 0 : 4));
+ context.drawItem(this.icon, this.getX() + 6, this.getY() + (this.toggled ? 7 : 9));
+
+ RenderSystem.enableDepthTest();
+ }
+
+ @Override
+ public void onClick(double mouseX, double mouseY) {
+ screen.onNavButtonClick(this);
+ }
+
+ @Override
+ protected void appendClickableNarrations(NarrationMessageBuilder builder) {}
+
+ public void setToggled(boolean toggled) {
+ this.toggled = toggled;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerPage.java
new file mode 100644
index 00000000..f5a5ec40
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerPage.java
@@ -0,0 +1,19 @@
+package de.hysky.skyblocker.skyblock.profileviewer;
+
+import de.hysky.skyblocker.skyblock.profileviewer.utils.SubPageSelectButton;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.widget.ClickableWidget;
+
+import java.util.List;
+
+public interface ProfileViewerPage {
+ void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY);
+ default List<ClickableWidget> getButtons() {
+ return null;
+ }
+ default void onNavButtonClick(SubPageSelectButton selectButton) {}
+ default void markWidgetsAsVisible() {}
+ default void markWidgetsAsInvisible() {}
+ default void nextPage() {}
+ default void previousPage() {}
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java
new file mode 100644
index 00000000..1d0b21ca
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java
@@ -0,0 +1,230 @@
+package de.hysky.skyblocker.skyblock.profileviewer;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.mixins.accessors.SkullBlockEntityAccessor;
+import de.hysky.skyblocker.skyblock.profileviewer.collections.CollectionsPage;
+import de.hysky.skyblocker.skyblock.profileviewer.dungeons.DungeonsPage;
+import de.hysky.skyblocker.skyblock.profileviewer.inventory.InventoryPage;
+import de.hysky.skyblocker.skyblock.profileviewer.skills.SkillsPage;
+import de.hysky.skyblocker.skyblock.profileviewer.slayers.SlayersPage;
+import de.hysky.skyblocker.utils.ApiUtils;
+import de.hysky.skyblocker.utils.Http;
+import de.hysky.skyblocker.utils.ProfileUtils;
+import de.hysky.skyblocker.utils.scheduler.Scheduler;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.widget.ClickableWidget;
+import net.minecraft.client.network.OtherClientPlayerEntity;
+import net.minecraft.client.network.PlayerListEntry;
+import net.minecraft.client.util.SkinTextures;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.entity.player.PlayerModelPart;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.awt.*;
+import java.io.IOException;
+import java.util.List;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+
+import static net.minecraft.client.gui.screen.ingame.InventoryScreen.drawEntity;
+
+public class ProfileViewerScreen extends Screen {
+ public static final Logger LOGGER = LoggerFactory.getLogger(ProfileViewerScreen.class);
+ private static final Text TITLE = Text.of("Skyblocker Profile Viewer");
+ private static final String HYPIXEL_COLLECTIONS = "https://api.hypixel.net/v2/resources/skyblock/collections";
+ private static final Object2ObjectOpenHashMap<String, Map<String, ?>> COLLECTIONS_CACHE = new Object2ObjectOpenHashMap<>();
+ private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/base_plate.png");
+ private static final int GUI_WIDTH = 322;
+ private static final int GUI_HEIGHT = 180;
+
+ private String playerName;
+ private JsonObject hypixelProfile;
+ private JsonObject playerProfile;
+
+ private int activePage = 0;
+ private static final String[] PAGE_NAMES = {"Skills", "Slayers", "Dungeons", "Inventories", "Collections"};
+ private final ProfileViewerPage[] profileViewerPages = new ProfileViewerPage[PAGE_NAMES.length];
+ private final List<ProfileViewerNavButton> profileViewerNavButtons = new ArrayList<>();
+ private OtherClientPlayerEntity entity;
+ private ProfileViewerTextWidget textWidget;
+
+ public ProfileViewerScreen(String username) {
+ super(TITLE);
+ fetchPlayerData(username).thenRun(this::initialisePagesAndWidgets);
+
+ for (int i = 0; i < PAGE_NAMES.length; i++) {
+ profileViewerNavButtons.add(new ProfileViewerNavButton(this, PAGE_NAMES[i], i, i == 0));
+ }
+ }
+
+ private void initialisePagesAndWidgets() {
+ textWidget = new ProfileViewerTextWidget(hypixelProfile, playerProfile);
+
+ CompletableFuture<Void> skillsFuture = CompletableFuture.runAsync(() -> profileViewerPages[0] = new SkillsPage(hypixelProfile, playerProfile));
+ CompletableFuture<Void> slayersFuture = CompletableFuture.runAsync(() -> profileViewerPages[1] = new SlayersPage(playerProfile));
+ CompletableFuture<Void> dungeonsFuture = CompletableFuture.runAsync(() -> profileViewerPages[2] = new DungeonsPage(playerProfile));
+ CompletableFuture<Void> inventoriesFuture = CompletableFuture.runAsync(() -> profileViewerPages[3] = new InventoryPage(playerProfile));
+ CompletableFuture<Void> collectionsFuture = CompletableFuture.runAsync(() -> profileViewerPages[4] = new CollectionsPage(hypixelProfile, playerProfile));
+
+ CompletableFuture.allOf(skillsFuture, slayersFuture, dungeonsFuture, inventoriesFuture, collectionsFuture)
+ .thenRun(() -> {
+ synchronized (this) {
+ clearAndInit();
+ }
+ });
+ }
+
+ @Override
+ public void render(DrawContext context, int mouseX, int mouseY, float delta) {
+ synchronized (this) {
+ super.render(context, mouseX, mouseY, delta);
+ }
+
+ int rootX = width / 2 - GUI_WIDTH / 2;
+ int rootY = height / 2 - GUI_HEIGHT / 2 + 5;
+
+ context.drawTexture(TEXTURE, rootX, rootY, 0, 0, GUI_WIDTH, GUI_HEIGHT, GUI_WIDTH, GUI_HEIGHT);
+ for (ProfileViewerNavButton button : profileViewerNavButtons) {
+ button.setX(rootX + button.getIndex() * 28 + 4);
+ button.setY(rootY - 28);
+ button.render(context, mouseX, mouseY, delta);
+ }
+
+ if (textWidget != null) textWidget.render(context, textRenderer, rootX + 8, rootY + 120);
+ drawPlayerEntity(context, playerName != null ? playerName : "Loading...", rootX, rootY, mouseX, mouseY);
+
+ if (profileViewerPages[activePage] != null) {
+ profileViewerPages[activePage].markWidgetsAsVisible();
+ profileViewerPages[activePage].render(context, mouseX, mouseY, delta, rootX + 93, rootY + 7);
+ } else {
+ context.drawText(textRenderer, "Loading...", rootX + 180, rootY + 80, Color.WHITE.getRGB(), true);
+ }
+ }
+
+ private void drawPlayerEntity(DrawContext context, String username, int rootX, int rootY, int mouseX, int mouseY) {
+ if (entity != null)
+ drawEntity(context, rootX + 9, rootY + 16, rootX + 89, rootY + 124, 42, 0.0625F, mouseX, mouseY, entity);
+ context.drawCenteredTextWithShadow(textRenderer, username.length() > 15 ? username.substring(0, 15) : username, rootX + 47, rootY + 14, Color.WHITE.getRGB());
+
+ }
+
+ private CompletableFuture<Void> fetchPlayerData(String username) {
+ CompletableFuture<Void> profileFuture = ProfileUtils.fetchFullProfile(username).thenAccept(profiles -> {
+ this.hypixelProfile = profiles.getAsJsonArray("profiles").asList().stream()
+ .map(JsonElement::getAsJsonObject)
+ .filter(profile -> profile.getAsJsonPrimitive("selected").getAsBoolean())
+ .findFirst()
+ .orElseThrow(() -> new IllegalStateException("No selected profile found!"));
+
+ this.playerProfile = hypixelProfile.getAsJsonObject("members").get(ApiUtils.name2Uuid(username)).getAsJsonObject();
+ });
+
+ CompletableFuture<Void> minecraftProfileFuture = SkullBlockEntityAccessor.invokeFetchProfileByName(username).thenAccept(profile -> {
+ this.playerName = profile.get().getName();
+ entity = new OtherClientPlayerEntity(MinecraftClient.getInstance().world, profile.get()) {
+ @Override
+ public SkinTextures getSkinTextures() {
+ PlayerListEntry playerListEntry = new PlayerListEntry(profile.get(), false);
+ return playerListEntry.getSkinTextures();
+ }
+
+ @Override
+ public boolean isPartVisible(PlayerModelPart modelPart) {
+ return !(modelPart.getName().equals(PlayerModelPart.CAPE.getName()));
+ }
+
+ @Override
+ public boolean isInvisibleTo(PlayerEntity player) {
+ return true;
+ }
+ };
+ entity.setCustomNameVisible(false);
+ }).exceptionally(ex -> {
+ this.playerName = "User not found";
+ return null;
+ });
+
+ return CompletableFuture.allOf(profileFuture, minecraftProfileFuture);
+ }
+
+ public void onNavButtonClick(ProfileViewerNavButton clickedButton) {
+ if (profileViewerPages[activePage] != null) profileViewerPages[activePage].markWidgetsAsInvisible();
+ for (ProfileViewerNavButton button : profileViewerNavButtons) {
+ button.setToggled(false);
+ }
+ activePage = clickedButton.getIndex();
+ clickedButton.setToggled(true);
+ }
+
+ @Override
+ public void init() {
+ profileViewerNavButtons.forEach(this::addDrawableChild);
+ for (ProfileViewerPage profileViewerPage : profileViewerPages) {
+ if (profileViewerPage != null && profileViewerPage.getButtons() != null) {
+ for (ClickableWidget button : profileViewerPage.getButtons()) {
+ if (button != null) addDrawableChild(button);
+ }
+ }
+ }
+ }
+
+ public static void initClass() {
+ fetchCollectionsData(); // caching on launch
+
+ ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
+ LiteralArgumentBuilder<FabricClientCommandSource> literalArgumentBuilder = ClientCommandManager.literal("pv")
+ .then(ClientCommandManager.argument("username", StringArgumentType.string())
+ .executes(Scheduler.queueOpenScreenFactoryCommand(context -> new ProfileViewerScreen(StringArgumentType.getString(context, "username"))))
+ )
+ .executes(Scheduler.queueOpenScreenCommand(() -> new ProfileViewerScreen(MinecraftClient.getInstance().getSession().getUsername())));
+ dispatcher.register(literalArgumentBuilder);
+ dispatcher.register(ClientCommandManager.literal(SkyblockerMod.NAMESPACE).then(literalArgumentBuilder));
+ });
+ }