aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky/skyblocker/skyblock/profileviewer
diff options
context:
space:
mode:
authornmccullagh <narhanael64@gmail.com>2024-05-13 15:54:01 +0100
committernmccullagh <narhanael64@gmail.com>2024-06-29 00:44:04 +0100
commit0f2c29fe6cc57cae766fa0d862cda6c26f1b0741 (patch)
treed9f5dcd7d97aeea52e3fff165c6c525193e72cfd /src/main/java/de/hysky/skyblocker/skyblock/profileviewer
parentd12504ef9992a72de9eb3c3a930c9f3c855c0acc (diff)
downloadSkyblocker-0f2c29fe6cc57cae766fa0d862cda6c26f1b0741.tar.gz
Skyblocker-0f2c29fe6cc57cae766fa0d862cda6c26f1b0741.tar.bz2
Skyblocker-0f2c29fe6cc57cae766fa0d862cda6c26f1b0741.zip
profile viewer skill page
Diffstat (limited to 'src/main/java/de/hysky/skyblocker/skyblock/profileviewer')
-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.java256
-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.java30
-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.java39
-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
28 files changed, 2437 insertions, 0 deletions
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..939d2bd1
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java
@@ -0,0 +1,256 @@
+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.Command;
+import com.mojang.brigadier.arguments.StringArgumentType;
+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) -> {
+ dispatcher.register(
+ ClientCommandManager.literal(SkyblockerMod.NAMESPACE)
+ .then(ClientCommandManager.literal("pv")
+ .then(ClientCommandManager.argument("username", StringArgumentType.string())
+ .executes(context -> {
+ String username = StringArgumentType.getString(context, "username");
+ Command<FabricClientCommandSource> cmd = Scheduler.queueOpenScreenCommand(() -> new ProfileViewerScreen(username));
+ return cmd.run(context);
+ })
+ )
+ .executes(context -> {
+ String username = MinecraftClient.getInstance().getSession().getUsername();
+ Command<FabricClientCommandSource> cmd = Scheduler.queueOpenScreenCommand(() -> new ProfileViewerScreen(username));
+ return cmd.run(context);
+ })
+ )
+ );
+
+ dispatcher.register(
+ ClientCommandManager.literal("pv")
+ .then(ClientCommandManager.argument("username", StringArgumentType.string())
+ .executes(context -> {
+ String username = StringArgumentType.getString(context, "username");
+ Command<FabricClientCommandSource> cmd = Scheduler.queueOpenScreenCommand(() -> new ProfileViewerScreen(username));
+ return cmd.run(context);
+ })
+ )
+ .executes(context -> {
+ String username = MinecraftClient.getInstance().getSession().getUsername();
+ Command<FabricClientCommandSource> cmd = Scheduler.queueOpenScreenCommand(() -> new ProfileViewerScreen(username));
+ return cmd.run(context);
+ })
+ );
+ });
+ }
+
+ @NotNull
+ public static Map<String, Map<String, ?>> fetchCollectionsData() {
+ if (!COLLECTIONS_CACHE.isEmpty()) return COLLECTIONS_CACHE;
+ try {
+ JsonObject jsonObject = JsonParser.parseString(Http.sendGetRequest(HYPIXEL_COLLECTIONS)).getAsJsonObject();
+ if (jsonObject.get("success").getAsBoolean()) {
+ Map<String, String[]> collectionsMap = new HashMap<>();
+ Map<String, List<Integer>> tierRequirementsMap = new HashMap<>();
+ JsonObject collections = jsonObject.getAsJsonObject("collections");
+ collections.entrySet().forEach(entry -> {
+ String category = entry.getKey();
+ JsonObject itemsObject = entry.getValue().getAsJsonObject().getAsJsonObject("items");
+ String[] items = itemsObject.keySet().toArray(new String[0]);
+ collectionsMap.put(category, items);
+ itemsObject.entrySet().forEach(itemEntry -> {
+ List<Integer> tierReqs = new ArrayList<>();
+ itemEntry.getValue().getAsJsonObject().getAsJsonArray("tiers").forEach(req ->
+ tierReqs.add(req.getAsJsonObject().get("amountRequired").getAsInt()));
+ tierRequirementsMap.put(itemEntry.getKey(), tierReqs);
+ });
+ });
+ COLLECTIONS_CACHE.put("COLLECTIONS", collectionsMap);
+ COLLECTIONS_CACHE.put("TIER_REQS", tierRequirementsMap);
+ return COLLECTIONS_CACHE;
+ }
+ } catch (IOException | InterruptedException e) {
+ LOGGER.error("[Skyblocker Profile Viewer] Failed to fetch collections data", e);
+ }
+ return Collections.emptyMap();
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java
new file mode 100644
index 00000000..4ee2dbba
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java
@@ -0,0 +1,55 @@
+package de.hysky.skyblocker.skyblock.profileviewer;
+
+import com.google.gson.JsonObject;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.util.Colors;
+
+public class ProfileViewerTextWidget {
+ private static final int ROW_GAP = 9;
+
+ private String PROFILE_NAME = "UNKNOWN";
+ private int SKYBLOCK_LEVEL = 0;
+ private double PURSE = 0;
+ private double BANK = 0;
+
+ public ProfileViewerTextWidget(JsonObject hypixelProfile, JsonObject playerProfile){
+ try {
+ this.PROFILE_NAME = hypixelProfile.get("cute_name").getAsString();
+ this.SKYBLOCK_LEVEL = playerProfile.getAsJsonObject("leveling").get("experience").getAsInt() / 100;
+ this.PURSE = playerProfile.getAsJsonObject("currencies").get("coin_purse").getAsDouble();
+ this.BANK = hypixelProfile.getAsJsonObject("banking").get("balance").getAsDouble();
+ } catch (Exception ignored) {}
+ }
+
+ public void render(DrawContext context, TextRenderer textRenderer, int root_x, int root_y){
+ // Profile Icon
+ MatrixStack matrices = context.getMatrices();
+ matrices.push();
+ matrices.scale(0.75f, 0.75f, 1);
+ int rootAdjustedX = (int) ((root_x) / 0.75f);
+ int rootAdjustedY = (int) ((root_y) / 0.75f);
+ context.drawItem(Ico.PAINTING, rootAdjustedX, rootAdjustedY);
+ matrices.pop();
+
+ context.drawText(textRenderer, "§n"+PROFILE_NAME, root_x + 14, root_y + 3, Colors.WHITE, true);
+ context.drawText(textRenderer, "§aLevel:§r " + SKYBLOCK_LEVEL, root_x + 2, root_y + 6 + ROW_GAP, Colors.WHITE, true);
+ context.drawText(textRenderer, "§6Purse:§r " + formatCoins(PURSE), root_x + 2, root_y + 6 + ROW_GAP * 2, Colors.WHITE, true);
+ context.drawText(textRenderer, "§6Bank:§r " + formatCoins(BANK), root_x + 2, root_y + 6 + ROW_GAP * 3, Colors.WHITE, true);
+ context.drawText(textRenderer, "§6NW:§r " + "Soon™", root_x + 2, root_y + 6 + ROW_GAP * 4, Colors.WHITE, true );
+ }
+
+ private String formatCoins(double amount) {
+ if (amount >= 1_000_000_000) {
+ return String.format("%.4gB", amount / 1_000_000_000);
+ } else if (amount >= 1_000_000) {
+ return String.format("%.4gM", amount / 1_000_000);
+ } else if (amount >= 1_000) {
+ return String.format("%.4gK", amount / 1_000);
+ } else {
+ return String.valueOf((int)(amount));
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/CollectionsPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/CollectionsPage.java
new file mode 100644
index 00000000..b77c3e7a
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/CollectionsPage.java
@@ -0,0 +1,100 @@
+package de.hysky.skyblocker.skyblock.profileviewer.collections;
+
+import com.google.gson.JsonObject;
+import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage;
+import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen;
+import de.hysky.skyblocker.skyblock.profileviewer.utils.SubPageSelectButton;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.widget.ClickableWidget;
+import net.minecraft.item.ItemStack;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class CollectionsPage implements ProfileViewerPage {
+ private static final String[] COLLECTION_CATEGORIES = {"MINING", "FARMING", "COMBAT", "FISHING", "FORAGING", "RIFT"};
+ private static final int TOTAL_HEIGHT = 165;
+ private static final Map<String, ItemStack> ICON_MAP = Map.ofEntries(
+ Map.entry("MINING", Ico.STONE_PICKAXE),
+ Map.entry("FARMING", Ico.GOLDEN_HOE),
+ Map.entry("COMBAT", Ico.STONE_SWORD),
+ Map.entry("FISHING", Ico.FISH_ROD),
+ Map.entry("FORAGING", Ico.JUNGLE_SAPLING),
+ // Map.entry("BOSS", Ico.WITHER), Not currently part of Collections API so skipping for now
+ Map.entry("RIFT", Ico.MYCELIUM)
+ );
+ private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+
+ private final GenericCategory[] collections = new GenericCategory[COLLECTION_CATEGORIES.length];
+ private final List<SubPageSelectButton> collectionSelectButtons = new ArrayList<>();
+ private int activePage = 0;
+
+
+ public CollectionsPage(JsonObject hProfile, JsonObject pProfile) {
+ for (int i = 0; i < COLLECTION_CATEGORIES.length; i++) {
+ try {
+ collectionSelectButtons.add(new SubPageSelectButton(this, -100, 0, i, ICON_MAP.getOrDefault(COLLECTION_CATEGORIES[i], Ico.BARRIER)));
+ collections[i] = new GenericCategory(hProfile, pProfile, COLLECTION_CATEGORIES[i]);
+ } catch (Exception e) {
+ ProfileViewerScreen.LOGGER.error("[Skyblocker Profile Viewer] Error creating Collections Page", e);
+ }
+ }
+ }
+
+ @Override
+ public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) {
+ int startingY = rootY + (TOTAL_HEIGHT - collectionSelectButtons.size() * 21) / 2;
+ for (int i = 0; i < collectionSelectButtons.size(); i++) {
+ collectionSelectButtons.get(i).setX(rootX);
+ collectionSelectButtons.get(i).setY(startingY + i * 21);
+ collectionSelectButtons.get(i).render(context, mouseX, mouseY, delta);
+ }
+
+ if (collections[activePage] == null) {
+ context.drawText(textRenderer, "No data...", rootX + 92, rootY + 72, Color.DARK_GRAY.getRGB(), false);
+ return;
+ }
+
+ collections[activePage].markWidgetsAsVisible();
+ collections[activePage].render(context, mouseX, mouseY, delta, rootX + 35, rootY + 6);
+ }
+
+ public void onNavButtonClick(SubPageSelectButton selectButton) {
+ if (collections[activePage] != null) collections[activePage].markWidgetsAsInvisible();
+ for (SubPageSelectButton button : collectionSelectButtons) {
+ button.setToggled(false);
+ }
+ activePage = selectButton.getIndex();
+ selectButton.setToggled(true);
+ }
+
+ @Override
+ public List<ClickableWidget> getButtons() {
+ List<ClickableWidget> clickableWidgets = new ArrayList<>(collectionSelectButtons);
+ for (ProfileViewerPage page : collections) {
+ if (page != null && page.getButtons() != null) clickableWidgets.addAll(page.getButtons());
+ }
+ return clickableWidgets;
+ }
+
+ @Override
+ public void markWidgetsAsVisible() {
+ for (SubPageSelectButton button : collectionSelectButtons) {
+ button.visible = true;
+ button.active = true;
+ }
+ }
+
+ @Override
+ public void markWidgetsAsInvisible() {
+ for (SubPageSelectButton button : collectionSelectButtons) {
+ button.visible = false;
+ button.active = false;
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java
new file mode 100644
index 00000000..ef26332e
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java
@@ -0,0 +1,136 @@
+package de.hysky.skyblocker.skyblock.profileviewer.collections;
+
+import com.google.gson.JsonObject;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
+import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.utils.NEURepoManager;
+import io.github.moulberry.repo.data.NEUItem;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.LoreComponent;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.tooltip.TooltipType;
+import net.minecraft.text.Style;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.Identifier;
+
+import java.awt.*;
+import java.text.NumberFormat;
+import java.util.List;
+import java.util.*;
+
+import static de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen.fetchCollectionsData;
+
+public class GenericCategory implements ProfileViewerPage {
+ private final String category;
+ private final LinkedList<ItemStack> collections = new LinkedList<>();
+ private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+ private static final NumberFormat FORMATTER = NumberFormat.getInstance(Locale.US);
+ private static final Identifier BUTTON_TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/button_icon_toggled.png");
+ private static final int COLUMN_GAP = 26;
+ private static final int ROW_GAP = 34;
+ private static final int COLUMNS = 7;
+
+ private final Map<String, String[]> collectionsMap;
+ private final Map<String, List<Integer>> tierRequirementsMap;
+ private final Map<String, String> ICON_TRAN