From 0f2c29fe6cc57cae766fa0d862cda6c26f1b0741 Mon Sep 17 00:00:00 2001 From: nmccullagh Date: Mon, 13 May 2024 15:54:01 +0100 Subject: profile viewer skill page --- .../java/de/hysky/skyblocker/SkyblockerMod.java | 2 + .../de/hysky/skyblocker/mixins/ItemStackMixin.java | 6 +- .../de/hysky/skyblocker/skyblock/PetCache.java | 27 +- .../skyblocker/skyblock/dungeon/DungeonScore.java | 2 +- .../skyblock/itemlist/ItemStackBuilder.java | 6 +- .../profileviewer/ProfileViewerNavButton.java | 65 +++++ .../skyblock/profileviewer/ProfileViewerPage.java | 19 ++ .../profileviewer/ProfileViewerScreen.java | 256 +++++++++++++++++ .../profileviewer/ProfileViewerTextWidget.java | 55 ++++ .../profileviewer/collections/CollectionsPage.java | 100 +++++++ .../profileviewer/collections/GenericCategory.java | 136 +++++++++ .../profileviewer/dungeons/DungeonClassWidget.java | 62 +++++ .../dungeons/DungeonFloorRunsWidget.java | 55 ++++ .../dungeons/DungeonHeaderWidget.java | 46 +++ .../dungeons/DungeonMiscStatsWidgets.java | 61 ++++ .../profileviewer/dungeons/DungeonsPage.java | 39 +++ .../profileviewer/inventory/Inventory.java | 120 ++++++++ .../profileviewer/inventory/InventoryPage.java | 113 ++++++++ .../profileviewer/inventory/PaginationButton.java | 46 +++ .../skyblock/profileviewer/inventory/Pet.java | 186 +++++++++++++ .../profileviewer/inventory/PlayerInventory.java | 73 +++++ .../inventory/itemLoaders/BackpackItemLoader.java | 34 +++ .../inventory/itemLoaders/InventoryItemLoader.java | 30 ++ .../inventory/itemLoaders/ItemLoader.java | 139 ++++++++++ .../itemLoaders/PetsInventoryItemLoader.java | 42 +++ .../itemLoaders/WardrobeInventoryItemLoader.java | 39 +++ .../skyblock/profileviewer/skills/SkillWidget.java | 95 +++++++ .../skyblock/profileviewer/skills/SkillsPage.java | 93 +++++++ .../profileviewer/slayers/SlayerWidget.java | 93 +++++++ .../profileviewer/slayers/SlayersPage.java | 41 +++ .../skyblock/profileviewer/utils/LevelFinder.java | 307 +++++++++++++++++++++ .../skyblock/profileviewer/utils/SkullCreator.java | 27 ++ .../profileviewer/utils/SubPageSelectButton.java | 65 +++++ .../skyblocker/skyblock/shortcut/Shortcuts.java | 1 - .../hysky/skyblocker/skyblock/tabhud/util/Ico.java | 38 ++- .../skyblock/tabhud/widget/DungeonDeathWidget.java | 2 +- .../skyblock/tabhud/widget/ElectionWidget.java | 4 +- .../skyblock/tabhud/widget/GardenSkillsWidget.java | 4 +- .../skyblock/tabhud/widget/ReputationWidget.java | 2 +- .../skyblock/tabhud/widget/SkillsWidget.java | 8 +- .../java/de/hysky/skyblocker/utils/ApiUtils.java | 6 +- src/main/java/de/hysky/skyblocker/utils/Http.java | 11 +- .../de/hysky/skyblocker/utils/ProfileUtils.java | 37 +-- 43 files changed, 2522 insertions(+), 71 deletions(-) create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerPage.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/CollectionsPage.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonClassWidget.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonFloorRunsWidget.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonHeaderWidget.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonMiscStatsWidgets.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonsPage.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Inventory.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/InventoryPage.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PaginationButton.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PlayerInventory.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/BackpackItemLoader.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/InventoryItemLoader.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/ItemLoader.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/PetsInventoryItemLoader.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/WardrobeInventoryItemLoader.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillWidget.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillsPage.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayerWidget.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayersPage.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/LevelFinder.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SkullCreator.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SubPageSelectButton.java (limited to 'src/main/java') 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 3abbfbcd..c0186fee 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 uuid) { + public record PetInfo(String type, double exp, String tier, Optional uuid, Optional item) { public static final Codec 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>> 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:\\[(?[0-9]+)]\\}"); private static Map> petNums; @@ -138,7 +138,7 @@ public class ItemStackBuilder { return list; } - private static String injectData(String string, List> injectors) { + public static String injectData(String string, List> injectors) { for (Pair 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 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 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> 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 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 skillsFuture = CompletableFuture.runAsync(() -> profileViewerPages[0] = new SkillsPage(hypixelProfile, playerProfile)); + CompletableFuture slayersFuture = CompletableFuture.runAsync(() -> profileViewerPages[1] = new SlayersPage(playerProfile)); + CompletableFuture dungeonsFuture = CompletableFuture.runAsync(() -> profileViewerPages[2] = new DungeonsPage(playerProfile)); + CompletableFuture inventoriesFuture = CompletableFuture.runAsync(() -> profileViewerPages[3] = new InventoryPage(playerProfile)); + CompletableFuture 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 fetchPlayerData(String username) { + CompletableFuture 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 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 cmd = Scheduler.queueOpenScreenCommand(() -> new ProfileViewerScreen(username)); + return cmd.run(context); + }) + ) + .executes(context -> { + String username = MinecraftClient.getInstance().getSession().getUsername(); + Command 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 cmd = Scheduler.queueOpenScreenCommand(() -> new ProfileViewerScreen(username)); + return cmd.run(context); + }) + ) + .executes(context -> { + String username = MinecraftClient.getInstance().getSession().getUsername(); + Command cmd = Scheduler.queueOpenScreenCommand(() -> new ProfileViewerScreen(username)); + return cmd.run(context); + }) + ); + }); + } + + @NotNull + public static Map> fetchCollectionsData() { + if (!COLLECTIONS_CACHE.isEmpty()) return COLLECTIONS_CACHE; + try { + JsonObject jsonObject = JsonParser.parseString(Http.sendGetRequest(HYPIXEL_COLLECTIONS)).getAsJsonObject(); + if (jsonObject.get("success").getAsBoolean()) { + Map collectionsMap = new HashMap<>(); + Map> 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 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 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 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 getButtons() { + List 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 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 collectionsMap; + private final Map> tierRequirementsMap; + private final Map ICON_TRANSLATION = Map.ofEntries( + Map.entry("MUSHROOM_COLLECTION", "RED_MUSHROOM")); + private final String[] ROMAN_NUMERALS = {"-", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII", "XIII", "XIV", "XV", "XVI", "XVII", "XVIII", "XIX", "XX"}; + + public GenericCategory(JsonObject hProfile, JsonObject pProfile, String collection) { + Map> fetchedData = fetchCollectionsData(); + //noinspection unchecked + collectionsMap = (Map) fetchedData.get("COLLECTIONS"); + //noinspection unchecked + tierRequirementsMap = (Map>) fetchedData.get("TIER_REQS"); + this.category = collection; + setupItemStacks(hProfile, pProfile); + } + + private int calculateTier(int achieved, List requirements) { + return (int) requirements.stream().filter(req -> achieved >= req).count(); + } + + private void setupItemStacks(JsonObject hProfile, JsonObject pProfile) { + JsonObject playerCollection = pProfile.getAsJsonObject("collection"); + + for (String collection : collectionsMap.get(this.category)) { + Map items = NEURepoManager.NEU_REPO.getItems().getItems(); + ItemStack itemStack = items.values().stream() + .filter(i -> Formatting.strip(i.getSkyblockItemId()).equals(ICON_TRANSLATION.getOrDefault(collection, collection).replace(':', '-'))) + .findFirst() + .map(NEUItem::getSkyblockItemId) + .map(ItemRepository::getItemStack) + .map(ItemStack::copy) + .orElse(Ico.BARRIER.copy()); + + if (itemStack.getItem().getName().getString().equals("Barrier")) itemStack.set(DataComponentTypes.ITEM_NAME, Text.of(collection)); + + int personalColl = playerCollection != null && playerCollection.has(collection) ? playerCollection.get(collection).getAsInt() : 0; + + int coopColl = 0; + for (String member : hProfile.get("members").getAsJsonObject().keySet()) { + if (!hProfile.getAsJsonObject("members").getAsJsonObject(member).has("collection")) continue; + JsonObject memberColl = hProfile.getAsJsonObject("members").getAsJsonObject(member).getAsJsonObject("collection"); + coopColl += memberColl.has(collection) ? memberColl.get(collection).getAsInt() : 0; + } + + int collectionTier = calculateTier(coopColl, tierRequirementsMap.get(collection)); + List tierRequirements = tierRequirementsMap.get(collection); + + List lore = new ArrayList<>(); + Style style = Style.EMPTY.withItalic(false); + lore.add(Text.literal("Collection: " + FORMATTER.format(personalColl)).setStyle(style).formatted(Formatting.YELLOW)); + if (hProfile.get("members").getAsJsonObject().keySet().size() > 1) { + lore.add(Text.literal("Co-op Collection: " + FORMATTER.format(coopColl)).setStyle(style).formatted(Formatting.AQUA)); + } + lore.add(Text.literal("Collection Tier: " + collectionTier).setStyle(style).formatted(Formatting.LIGHT_PURPLE)); + itemStack.set(DataComponentTypes.LORE, new LoreComponent(lore)); + + if (collectionTier == tierRequirements.size()) itemStack.set(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true); + + collections.add(itemStack); + } + } + + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) { + Text categoryTitle = Text.literal(category.charAt(0) + category.substring(1).toLowerCase() + " Collections").formatted(Formatting.BOLD); + context.drawText(textRenderer, categoryTitle, rootX + 88 - (textRenderer.getWidth(categoryTitle) / 2), rootY, Color.DARK_GRAY.getRGB(), false); + + for (int i = 0; i < collections.size(); i++) { + int x = rootX + 2 + (i % COLUMNS) * COLUMN_GAP; + int y = rootY + 19 + (i / COLUMNS) * ROW_GAP; + + context.fill(x - 3, y - 3, x + 19, y + 19, Color.BLACK.getRGB()); + context.drawTexture(BUTTON_TEXTURE, x - 2, y - 2, 0, 0, 20, 20, 20, 20); + context.drawItem(collections.get(i), x, y); + + ItemStack itemStack = collections.get(i); + List lore = itemStack.getOrDefault(DataComponentTypes.LORE, LoreComponent.DEFAULT).lines(); + for (Text text : lore) { + if (!text.getString().startsWith("Collection Tier: ")) continue; + int cTier = Integer.parseInt(text.getString().substring("Collection Tier: ".length())); + Color colour = Boolean.TRUE.equals(itemStack.get(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE)) ? Color.MAGENTA : Color.darkGray; + context.drawText(textRenderer, Text.literal(toRomanNumerals(cTier)), x + 9 - (textRenderer.getWidth(toRomanNumerals(cTier)) / 2), y + 21, colour.getRGB(), false); + break; + } + + if (mouseX > x && mouseX < x + 16 && mouseY > y && mouseY < y + 16) { + List tooltip = collections.get(i).getTooltip(Item.TooltipContext.DEFAULT, MinecraftClient.getInstance().player, TooltipType.BASIC); + context.drawTooltip(textRenderer, tooltip, mouseX, mouseY); + } + } + } + + private String toRomanNumerals(int number) { + return number <= ROMAN_NUMERALS.length ? ROMAN_NUMERALS[number] : "Err"; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonClassWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonClassWidget.java new file mode 100644 index 00000000..3b847b1b --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonClassWidget.java @@ -0,0 +1,62 @@ +package de.hysky.skyblocker.skyblock.profileviewer.dungeons; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.skyblock.profileviewer.utils.LevelFinder; +import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.utils.render.RenderHelper; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; + +import java.awt.*; +import java.util.Map; + +public class DungeonClassWidget { + private final String className; + private LevelFinder.LevelInfo classLevel; + private static final int CLASS_CAP = 50; + private JsonObject classData; + private final ItemStack stack; + private boolean active = false; + + private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/icon_data_widget.png"); + private static final Identifier ACTIVE_TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/item_protection.png"); + private static final Identifier BAR_FILL = Identifier.of(SkyblockerMod.NAMESPACE, "bars/bar_fill"); + private static final Identifier BAR_BACK = Identifier.of(SkyblockerMod.NAMESPACE, "bars/bar_back"); + + private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + private static final Map CLASS_ICON = Map.ofEntries( + Map.entry("Healer", Ico.S_POTION), + Map.entry("Mage", Ico.B_ROD), + Map.entry("Berserk", Ico.IRON_SWORD), + Map.entry("Archer", Ico.BOW), + Map.entry("Tank", Ico.CHESTPLATE) + ); + + public DungeonClassWidget(String className, JsonObject playerProfile) { + this.className = className; + stack = CLASS_ICON.getOrDefault(className, Ico.BARRIER); + try { + classData = playerProfile.getAsJsonObject("dungeons").getAsJsonObject("player_classes").getAsJsonObject(this.className.toLowerCase()); + classLevel = LevelFinder.getLevelInfo("Catacombs", classData.get("experience").getAsLong()); + active = playerProfile.getAsJsonObject("dungeons").get("selected_dungeon_class").getAsString().equals(className.toLowerCase()); + } catch (Exception ignored) { + classLevel = LevelFinder.getLevelInfo("", 0); + } + } + + public void render(DrawContext context, int x, int y) { + context.drawTexture(TEXTURE, x, y, 0, 0, 109, 26, 109, 26); + context.drawItem(stack, x + 3, y + 5); + if (active) context.drawTexture(ACTIVE_TEXTURE, x + 3, y + 5, 0, 0, 16, 16, 16, 16); + + context.drawText(textRenderer, className + " " + classLevel.level, x + 31, y + 5, Color.WHITE.getRGB(), false); + Color fillColor = classLevel.level >= CLASS_CAP ? Color.MAGENTA : Color.GREEN; + context.drawGuiTexture(BAR_BACK, x + 30, y + 15, 75, 6); + RenderHelper.renderNineSliceColored(context, BAR_FILL, x + 30, y + 15, (int) (75 * classLevel.fill), 6, fillColor); + } + +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonFloorRunsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonFloorRunsWidget.java new file mode 100644 index 00000000..7c9206c0 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonFloorRunsWidget.java @@ -0,0 +1,55 @@ +package de.hysky.skyblocker.skyblock.profileviewer.dungeons; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import de.hysky.skyblocker.SkyblockerMod; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; + +import java.awt.*; +import java.util.Map; + +public class DungeonFloorRunsWidget { + private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/dungeons_body.png"); + + private static final String[] DUNGEONS = {"catacombs", "master_catacombs"}; + private JsonObject dungeonsStats; + + public DungeonFloorRunsWidget(JsonObject pProfile) { + try { + dungeonsStats = pProfile.getAsJsonObject("dungeons").getAsJsonObject("dungeon_types"); + } catch (Exception ignored) {} + } + + // TODO: Hovering on each floor should probably showcase best run times in a tooltip + public void render(DrawContext context, int x, int y) { + context.drawTexture(TEXTURE, x, y, 0, 0, 109, 110, 109, 110); + context.drawText(textRenderer, Text.literal("Floor Runs").formatted(Formatting.BOLD), x + 6, y + 4, Color.WHITE.getRGB(), true); + + int columnX = x + 4; + int elementY = y + 15; + for (String dungeon : DUNGEONS) { + JsonObject dungeonData; + try { + dungeonData = dungeonsStats.getAsJsonObject(dungeon).getAsJsonObject(dungeon.equals("catacombs") ? "times_played" : "tier_completions"); + for (Map.Entry entry : dungeonData.entrySet()) { + if (entry.getKey().equals("total")) continue; + + String textToRender = String.format((dungeon.equals("catacombs") ? "§aF" : "§cM") + "%s§r %s", entry.getKey(), entry.getValue().getAsInt()); + context.drawText(textRenderer, textToRender, columnX + 2, elementY + 2, Color.WHITE.getRGB(), true); + + elementY += 11; + } + columnX += 52; + elementY = y + 26; + } catch (Exception e) { + return; + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonHeaderWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonHeaderWidget.java new file mode 100644 index 00000000..1a62a47b --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonHeaderWidget.java @@ -0,0 +1,46 @@ +package de.hysky.skyblocker.skyblock.profileviewer.dungeons; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.skyblock.profileviewer.utils.LevelFinder; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.util.Identifier; + +import java.awt.*; +import java.text.DecimalFormat; + +public class DungeonHeaderWidget { + private LevelFinder.LevelInfo classLevel; + private float classAvg; + + private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + private static final DecimalFormat DF = new DecimalFormat("#.##"); + private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/dungeons_header.png"); + + public DungeonHeaderWidget(JsonObject playerProfile, String[] classes) { + try { + JsonObject DUNGEONS_PROFILE = playerProfile.getAsJsonObject("dungeons").getAsJsonObject("dungeon_types").getAsJsonObject("catacombs"); + this.classLevel = LevelFinder.getLevelInfo("Catacombs", DUNGEONS_PROFILE.get("experience").getAsLong()); + + float avg = 0; + JsonObject CLASS_DATA = playerProfile.getAsJsonObject("dungeons").getAsJsonObject("player_classes"); + for (String element : classes) { + avg += LevelFinder.getLevelInfo("Catacombs", CLASS_DATA.getAsJsonObject(element.toLowerCase()).get("experience").getAsLong()).level; + } + classAvg = avg/classes.length; + } catch (Exception ignored) { + this.classLevel = LevelFinder.getLevelInfo("", 0); + classAvg = 0; + } + } + + public void render(DrawContext context, int x, int y) { + context.drawTexture(TEXTURE, x, y, 0, 0, 109, 26, 109, 26); + + context.drawText(textRenderer, "§i§6§lCatacombs §r" + this.classLevel.level, x + 3, y + 4, Color.WHITE.getRGB(), true); + + context.drawText(textRenderer, "§eClass Average §r" + DF.format(this.classAvg), x + 3, y + 14, Color.WHITE.getRGB(), true); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonMiscStatsWidgets.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonMiscStatsWidgets.java new file mode 100644 index 00000000..679cc575 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonMiscStatsWidgets.java @@ -0,0 +1,61 @@ +package de.hysky.skyblocker.skyblock.profileviewer.dungeons; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import de.hysky.skyblocker.SkyblockerMod; +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.util.Identifier; + +import java.awt.*; +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.Map; + +public class DungeonMiscStatsWidgets { + private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/icon_data_widget.png"); + private static final Identifier RUN_ICON = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/run_icon.png"); + private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + private static final DecimalFormat DF = new DecimalFormat("#.##"); + private static final String[] DUNGEONS = {"catacombs", "master_catacombs"}; + + private final Map dungeonRuns = new HashMap<>(); + private int secrets = 0; + private int totalRuns = 0; + + public DungeonMiscStatsWidgets(JsonObject pProfile) { + JsonObject DUNGEONS_DATA = pProfile.getAsJsonObject("dungeons"); + try { + secrets = DUNGEONS_DATA.get("secrets").getAsInt(); + + for (String dungeon : DUNGEONS) { + JsonObject dungeonData = DUNGEONS_DATA.getAsJsonObject("dungeon_types").getAsJsonObject(dungeon).getAsJsonObject(dungeon.equals("catacombs") ? "times_played" : "tier_completions"); + int runs = 0; + for (Map.Entry entry : dungeonData.entrySet()) { + String key = entry.getKey(); + if (key.equals("total")) continue; + runs += entry.getValue().getAsInt(); + } + dungeonRuns.put(dungeon, runs); + totalRuns += runs; + } + + } catch (Exception ignored) {} + } + + public void render(DrawContext context, int x, int y) { + context.drawTexture(TEXTURE, x, y, 0, 0, 109, 26, 109, 26); + context.drawItem(Ico.FEATHER, x + 2, y + 4); + + context.drawText(textRenderer, "Secrets " + secrets, x + 30, y + 4, Color.WHITE.getRGB(), true); + context.drawText(textRenderer, "Avg " + (totalRuns > 0 ? DF.format(secrets / (float) totalRuns) : 0) + "/Run", x + 30, y + 14, Color.WHITE.getRGB(), true); + + context.drawTexture(TEXTURE, x, y + 28, 0, 0, 109, 26, 109, 26); + context.drawTexture(RUN_ICON, x + 4, y + 33, 0, 0, 14, 16, 14, 16); + + context.drawText(textRenderer, "§aNormal §r" + dungeonRuns.getOrDefault("catacombs", 0), x + 30, y + 32, Color.WHITE.getRGB(), true); + context.drawText(textRenderer, "§cMaster §r" + dungeonRuns.getOrDefault("master_catacombs", 0), x + 30, y + 42, Color.WHITE.getRGB(), true); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonsPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonsPage.java new file mode 100644 index 00000000..b1398661 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonsPage.java @@ -0,0 +1,39 @@ +package de.hysky.skyblocker.skyblock.profileviewer.dungeons; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage; +import de.hysky.skyblocker.utils.ProfileUtils; +import net.minecraft.client.gui.DrawContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +public class DungeonsPage implements ProfileViewerPage { + public static final Logger LOGGER = LoggerFactory.getLogger(ProfileUtils.class); + private static final String[] CLASSES = {"Healer", "Mage", "Berserk", "Archer", "Tank"}; + + private final DungeonHeaderWidget dungeonHeaderWidget; + private final List dungeonClassWidgetsList = new ArrayList<>(); + private final DungeonFloorRunsWidget dungeonFloorRunsWidget; + private final DungeonMiscStatsWidgets dungeonMiscStatsWidgets; + + public DungeonsPage(JsonObject pProfile) { + dungeonHeaderWidget = new DungeonHeaderWidget(pProfile, CLASSES); + dungeonFloorRunsWidget = new DungeonFloorRunsWidget(pProfile); + dungeonMiscStatsWidgets = new DungeonMiscStatsWidgets(pProfile); + for (String element : CLASSES) { + dungeonClassWidgetsList.add(new DungeonClassWidget(element, pProfile)); + } + } + + public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) { + dungeonHeaderWidget.render(context, rootX, rootY); + dungeonFloorRunsWidget.render(context, rootX + 113, rootY + 56); + dungeonMiscStatsWidgets.render(context, rootX + 113, rootY); + for (int i = 0; i < dungeonClassWidgetsList.size(); i++) { + dungeonClassWidgetsList.get(i).render(context, rootX, rootY + 28 + i * 28); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Inventory.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Inventory.java new file mode 100644 index 00000000..a2f7d9d6 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Inventory.java @@ -0,0 +1,120 @@ +package de.hysky.skyblocker.skyblock.profileviewer.inventory; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage; +import de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders.ItemLoader; +import it.unimi.dsi.fastutil.ints.IntIntPair; +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.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.tooltip.TooltipType; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +public class Inventory implements ProfileViewerPage { + private static final Identifier TEXTURE = Identifier.of("textures/gui/container/generic_54.png"); + private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + private final IntIntPair dimensions; + private final int itemsPerPage; + private final List containerList; + private final String containerName; + private int activePage = 0; + private int totalPages = 1; + private final PaginationButton previousPage = new PaginationButton(this, -1000, 0, false); + private final PaginationButton nextPage = new PaginationButton(this, -1000, 0, true); + + public Inventory(String name, IntIntPair dimensions, JsonObject inventory) { + this(name, dimensions, inventory, new ItemLoader()); + } + + public Inventory(String name, IntIntPair dimensions, JsonObject inventory, ItemLoader itemLoader) { + containerName = name; + this.dimensions = dimensions; + itemsPerPage = dimensions.rightInt() * dimensions.leftInt(); + this.containerList = itemLoader.loadItems(inventory); + this.totalPages = (int) Math.ceil((double) containerList.size() / itemsPerPage); + } + + public void render(DrawContex