aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky/skyblocker
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/hysky/skyblocker')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerScreen.java33
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java18
-rw-r--r--src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/accessors/MinecraftClientAccessor.java12
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/PetCache.java27
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/Tips.java102
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java10
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/CommissionHighlight.java38
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java10
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/NucleusWaypoints.java61
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/events/EventNotifications.java32
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CommunityShopAdder.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/ApiAuthentication.java5
-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/Utils.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/scheduler/Scheduler.java46
59 files changed, 2813 insertions, 143 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 28722558..c3b0e0f2 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -37,6 +37,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;
@@ -106,6 +107,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/SkyblockerScreen.java b/src/main/java/de/hysky/skyblocker/SkyblockerScreen.java
index 0196a37a..9686c6d8 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerScreen.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerScreen.java
@@ -1,7 +1,5 @@
package de.hysky.skyblocker;
-import java.time.LocalDate;
-
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.skyblock.Tips;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
@@ -11,11 +9,7 @@ import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.ConfirmLinkScreen;
import net.minecraft.client.gui.screen.Screen;
-import net.minecraft.client.gui.widget.ButtonWidget;
-import net.minecraft.client.gui.widget.GridWidget;
-import net.minecraft.client.gui.widget.MultilineTextWidget;
-import net.minecraft.client.gui.widget.TextWidget;
-import net.minecraft.client.gui.widget.ThreePartsLayoutWidget;
+import net.minecraft.client.gui.widget.*;
import net.minecraft.screen.ScreenTexts;
import net.minecraft.text.OrderedText;
import net.minecraft.text.StringVisitable;
@@ -23,6 +17,8 @@ import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.Language;
+import java.time.LocalDate;
+
public class SkyblockerScreen extends Screen {
private static final int SPACING = 8;
private static final int BUTTON_WIDTH = 210;
@@ -36,7 +32,8 @@ public class SkyblockerScreen extends Screen {
private static final Text TRANSLATE_TEXT = Text.translatable("text.skyblocker.translate");
private static final Text MODRINTH_TEXT = Text.translatable("text.skyblocker.modrinth");
private static final Text DISCORD_TEXT = Text.translatable("text.skyblocker.discord");
- private final ThreePartsLayoutWidget layout = new ThreePartsLayoutWidget(this);
+ private ThreePartsLayoutWidget layout;
+ private MultilineTextWidget tip;
static {
LocalDate date = LocalDate.now();
@@ -44,7 +41,7 @@ public class SkyblockerScreen extends Screen {
ICON = date.getMonthValue() == 4 && date.getDayOfMonth() == 1 ? Identifier.of(SkyblockerMod.NAMESPACE, "icons.png") : Identifier.of(SkyblockerMod.NAMESPACE, "icon.png");
}
- private SkyblockerScreen() {
+ public SkyblockerScreen() {
super(TITLE);
}
@@ -57,6 +54,7 @@ public class SkyblockerScreen extends Screen {
@Override
protected void init() {
+ this.layout = new ThreePartsLayoutWidget(this, 50, 100);
this.layout.addHeader(new IconTextWidget(this.getTitle(), this.textRenderer, ICON));
GridWidget gridWidget = this.layout.addBody(new GridWidget()).setSpacing(SPACING);
@@ -72,17 +70,26 @@ public class SkyblockerScreen extends Screen {
adder.add(ButtonWidget.builder(DISCORD_TEXT, ConfirmLinkScreen.opening(this, "https://discord.gg/aNNJHQykck")).width(HALF_BUTTON_WIDTH).build());
adder.add(ButtonWidget.builder(ScreenTexts.DONE, button -> this.close()).width(BUTTON_WIDTH).build(), 2);
- MultilineTextWidget tip = new MultilineTextWidget(Text.translatable("skyblocker.tips.tip", Tips.nextTipInternal()), this.textRenderer)
- .setCentered(true)
- .setMaxWidth((int) (this.width * 0.7));
+ GridWidget footerGridWidget = this.layout.addFooter(new GridWidget()).setSpacing(SPACING).setRowSpacing(0);
+ footerGridWidget.getMainPositioner().alignHorizontalCenter();
+ GridWidget.Adder footerAdder = footerGridWidget.createAdder(2);
+ footerAdder.add(tip = new MultilineTextWidget(Tips.nextTip(), this.textRenderer).setCentered(true).setMaxWidth((int) (this.width * 0.7)), 2);
+ footerAdder.add(ButtonWidget.builder(Text.translatable("skyblocker.tips.previous"), button -> {
+ tip.setMessage(Tips.previousTip());
+ layout.refreshPositions();
+ }).build());
+ footerAdder.add(ButtonWidget.builder(Text.translatable("skyblocker.tips.next"), button -> {
+ tip.setMessage(Tips.nextTip());
+ layout.refreshPositions();
+ }).build());
- this.layout.addFooter(tip);
this.layout.refreshPositions();
this.layout.forEachChild(this::addDrawableChild);
}
@Override
protected void initTabNavigation() {
+ super.initTabNavigation();
this.layout.refreshPositions();
}
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
index fa87be3d..bf6eefd5 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
@@ -1,5 +1,6 @@
package de.hysky.skyblocker.config.categories;
+import de.hysky.skyblocker.SkyblockerScreen;
import de.hysky.skyblocker.config.ConfigUtils;
import de.hysky.skyblocker.config.SkyblockerConfig;
import de.hysky.skyblocker.config.configs.GeneralConfig;
@@ -16,6 +17,13 @@ public class GeneralCategory {
return ConfigCategory.createBuilder()
.name(Text.translatable("skyblocker.config.general"))
+ //Skyblocker Screen
+ .option(ButtonOption.createBuilder()
+ .name(Text.translatable("skyblocker.skyblockerScreen"))
+ .text(Text.translatable("text.skyblocker.open"))
+ .action((screen, opt) -> MinecraftClient.getInstance().setScreen(new SkyblockerScreen()))
+ .build())
+
//Ungrouped Options
.option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.general.enableTips"))
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java
index e77e9f4b..4e11d869 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java
@@ -30,6 +30,14 @@ public class MiningCategory {
.controller(ConfigUtils::createBooleanController)
.build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("skyblocker.config.mining.commissionHighlight"))
+ .binding(defaults.mining.commissionHighlight,
+ () -> config.mining.commissionHighlight,
+ newValue -> config.mining.commissionHighlight = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+
//Dwarven Mines
.group(OptionGroup.createBuilder()
.name(Text.translatable("skyblocker.config.mining.dwarvenMines"))
@@ -95,7 +103,15 @@ public class MiningCategory {
newValue -> config.mining.crystalHollows.metalDetectorHelper = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
- .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("skyblocker.config.mining.crystalHollows.nucleusWaypoints"))
+ .description(OptionDescription.of(Text.translatable("skyblocker.config.mining.crystalHollows.nucleusWaypoints.@Tooltip")))
+ .binding(defaults.mining.crystalHollows.nucleusWaypoints,
+ () -> config.mining.crystalHollows.nucleusWaypoints,
+ newValue -> config.mining.crystalHollows.nucleusWaypoints = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .build())
//Crystal Hollows Map
.group(OptionGroup.createBuilder()
diff --git a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java
index a2a9bcf7..d71f57b6 100644
--- a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java
@@ -27,6 +27,9 @@ public class MiningConfig {
@SerialEntry
public Glacite glacite = new Glacite();
+ @SerialEntry
+ public boolean commissionHighlight = true;
+
public static class DwarvenMines {
@SerialEntry
public boolean solveFetchur = true;
@@ -61,6 +64,9 @@ public class MiningConfig {
public static class CrystalHollows {
@SerialEntry
public boolean metalDetectorHelper = true;
+
+ @SerialEntry
+ public boolean nucleusWaypoints = false;
}
public static class CrystalsHud {
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/mixins/accessors/MinecraftClientAccessor.java b/src/main/java/de/hysky/skyblocker/mixins/accessors/MinecraftClientAccessor.java
new file mode 100644
index 00000000..a750ded2
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/mixins/accessors/MinecraftClientAccessor.java
@@ -0,0 +1,12 @@
+package de.hysky.skyblocker.mixins.accessors;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.session.ProfileKeys;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(MinecraftClient.class)
+public interface MinecraftClientAccessor {
+ @Accessor
+ ProfileKeys getProfileKeys();
+}
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/Tips.java b/src/main/java/de/hysky/skyblocker/skyblock/Tips.java
index 513dc4b7..5d983e20 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/Tips.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/Tips.java
@@ -1,5 +1,6 @@
package de.hysky.skyblocker.skyblock;
+import com.demonwav.mcdev.annotations.Translatable;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
@@ -14,16 +15,16 @@ import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.Text;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
-import java.util.Random;
import java.util.function.Supplier;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
public class Tips {
- private static final Random RANDOM = new Random();
- private static int previousTipIndex = -1;
- private static final List<Supplier<Text>> TIPS = List.of(
+ private static int currentTipIndex = 0;
+ private static final List<Supplier<Text>> TIPS = new ArrayList<>(List.of(
getTipFactory("skyblocker.tips.customItemNames", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker custom renameItem"),
getTipFactory("skyblocker.tips.customArmorDyeColors", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker custom dyeColor"),
getTipFactory("skyblocker.tips.customArmorTrims", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker custom armorTrim"),
@@ -36,47 +37,60 @@ public class Tips {
getTipFactory("skyblocker.tips.gallery", ClickEvent.Action.OPEN_URL, "https://hysky.de/skyblocker/gallery"),
getTipFactory("skyblocker.tips.itemRarityBackground", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker config"),
getTipFactory("skyblocker.tips.modMenuUpdate"),
- getTipFactory("skyblocker.tips.issues", ClickEvent.Action.OPEN_URL, "https://github.com/SkyblockerMod/Skyblocker"),
+ getTipFactory("skyblocker.tips.issues", ClickEvent.Action.OPEN_URL, "https://github.com/SkyblockerMod/Skyblocker/issues"),
getTipFactory("skyblocker.tips.beta", ClickEvent.Action.OPEN_URL, "https://github.com/SkyblockerMod/Skyblocker/actions"),
+ getTipFactory("skyblocker.tips.contribute", ClickEvent.Action.OPEN_URL, "https://github.com/SkyblockerMod/Skyblocker/wiki/contribute"),
getTipFactory("skyblocker.tips.discord", ClickEvent.Action.OPEN_URL, "https://discord.gg/aNNJHQykck"),
getTipFactory("skyblocker.tips.flameOverlay", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker config"),
getTipFactory("skyblocker.tips.wikiLookup", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker config"),
getTipFactory("skyblocker.tips.protectItem", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker protectItem"),
getTipFactory("skyblocker.tips.fairySoulsEnigmaSoulsRelics", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker fairySouls"),
- getTipFactory("skyblocker.tips.quickNav", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker config")
- );
+ getTipFactory("skyblocker.tips.quickNav", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker config"),
+ getTipFactory("skyblocker.tips.waypoints", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker waypoint"),
+ getTipFactory("skyblocker.tips.orderedWaypoints", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker waypoint ordered"),
+ getTipFactory("skyblocker.tips.visitorHelper"),
+ getTipFactory("skyblocker.tips.slotText"),
+ getTipFactory("skyblocker.tips.profileViewer", ClickEvent.Action.SUGGEST_COMMAND, "/pv"),
+ getTipFactory("skyblocker.tips.configSearch", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker config"),
+ getTipFactory("skyblocker.tips.compactDamage", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker config"),
+ getTipFactory("skyblocker.tips.skyblockerScreen", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker"),
+ getTipFactory("skyblocker.tips.tipsClick", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker tips next"),
+ getTipFactory("skyblocker.tips.eventNotifications", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker config"),
+ getTipFactory("skyblocker.tips.signCalculator"),
+ getTipFactory("skyblocker.tips.calculateCommand", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker calculate"),
+ getTipFactory("skyblocker.tips.fancierBars", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker bars"),
+ getTipFactory("skyblocker.tips.crystalWaypointsShare", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker crystalWaypoints share"),
+ getTipFactory("skyblocker.tips.gardenMouseLock", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker config"),
+ getTipFactory("skyblocker.tips.newYearCakesHelper"),
+ getTipFactory("skyblocker.tips.accessoryHelper"),
+ getTipFactory("skyblocker.tips.fancyAuctionHouseCheapHighlight")
+ ));
private static boolean sentTip = false;
- private static Supplier<Text> getTipFactory(String key) {
+ private static Supplier<Text> getTipFactory(@Translatable String key) {
return () -> Text.translatable(key);
}
- private static Supplier<Text> getTipFactory(String key, ClickEvent.Action clickAction, String value) {
+ private static Supplier<Text> getTipFactory(@Translatable String key, ClickEvent.Action clickAction, String value) {
return () -> Text.translatable(key).styled(style -> style.withClickEvent(new ClickEvent(clickAction, value)));
}
public static void init() {
ClientCommandRegistrationCallback.EVENT.register(Tips::registerTipsCommand);
SkyblockEvents.JOIN.register(Tips::sendNextTip);
+ Collections.shuffle(TIPS);
}
private static void registerTipsCommand(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) {
dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("tips")
.then(literal("enable").executes(Tips::enableTips))
.then(literal("disable").executes(Tips::disableTips))
- .then(literal("next").executes(Tips::nextTip))
+ .then(literal("previous").executes(Tips::sendPreviousTipCommand))
+ .then(literal("next").executes(Tips::sendNextTipCommand))
));
}
- private static void sendNextTip() {
- MinecraftClient client = MinecraftClient.getInstance();
- if (client.player != null && SkyblockerConfigManager.get().general.enableTips && !sentTip) {
- client.player.sendMessage(nextTip(), false);
- sentTip = true;
- }
- }
-
private static int enableTips(CommandContext<FabricClientCommandSource> context) {
SkyblockerConfigManager.get().general.enableTips = true;
SkyblockerConfigManager.save();
@@ -91,22 +105,52 @@ public class Tips {
return Command.SINGLE_SUCCESS;
}
- private static int nextTip(CommandContext<FabricClientCommandSource> context) {
- context.getSource().sendFeedback(nextTip());
+ private static void sendNextTip() {
+ MinecraftClient client = MinecraftClient.getInstance();
+ if (client.player != null && SkyblockerConfigManager.get().general.enableTips && !sentTip) {
+ client.player.sendMessage(tipMessage(nextTip()), false);
+ sentTip = true;
+ }
+ }
+
+ private static int sendNextTipCommand(CommandContext<FabricClientCommandSource> context) {
+ context.getSource().sendFeedback(tipMessage(nextTip()));
return Command.SINGLE_SUCCESS;
}
- private static Text nextTip() {
- return Constants.PREFIX.get().append(Text.translatable("skyblocker.tips.tip", nextTipInternal()))
+ public static Text nextTip() {
+ return Text.translatable("skyblocker.tips.tip", nextTipInternal());
+ }
+
+ private static Text nextTipInternal() {
+ currentTipIndex++;
+ currentTipIndex %= TIPS.size();
+ return TIPS.get(currentTipIndex).get();
+ }
+
+ private static int sendPreviousTipCommand(CommandContext<FabricClientCommandSource> context) {
+ context.getSource().sendFeedback(tipMessage(previousTip()));
+ return Command.SINGLE_SUCCESS;
+ }
+
+ public static Text previousTip() {
+ return Text.translatable("skyblocker.tips.tip", previousTipInternal());
+ }
+
+ private static Text previousTipInternal() {
+ currentTipIndex--;
+ currentTipIndex += TIPS.size();
+ currentTipIndex %= TIPS.size();
+ return TIPS.get(currentTipIndex).get();
+ }
+
+ private static Text tipMessage(Text tip) {
+ return Constants.PREFIX.get().append(tip)
+ .append(" ")
+ .append(Text.translatable("skyblocker.tips.clickPreviousTip").styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker tips previous"))))
+ .append(" ")
.append(Text.translatable("skyblocker.tips.clickNextTip").styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker tips next"))))
.append(" ")
.append(Text.translatable("skyblocker.tips.clickDisable").styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker tips disable"))));
}
-
- public static Text nextTipInternal() {
- int randomInt = RANDOM.nextInt(TIPS.size());
- while (randomInt == previousTipIndex) randomInt = RANDOM.nextInt(TIPS.size());
- previousTipIndex = randomInt;
- return TIPS.get(randomInt).get();
- }
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java
index f9c1f7ae..f984d751 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java
@@ -209,10 +209,10 @@ public class ChocolateFactorySolver extends ContainerSolver {
}
Matcher costMatcher = COST_PATTERN.matcher(coachLore);
- OptionalInt cost = RegexUtils.getIntFromMatcher(costMatcher, multiplierIncreaseMatcher.hasMatch() ? multiplierIncreaseMatcher.end() : 0); //Cost comes after the multiplier line
+ OptionalLong cost = RegexUtils.getLongFromMatcher(costMatcher, multiplierIncreaseMatcher.hasMatch() ? multiplierIncreaseMatcher.end() : 0); //Cost comes after the multiplier line
if (cost.isEmpty()) return Optional.empty();
- return Optional.of(new Rabbit(totalCps / totalCpsMultiplier * (nextCpsMultiplier.getAsDouble() - currentCpsMultiplier.getAsDouble()), cost.getAsInt(), COACH_SLOT));
+ return Optional.of(new Rabbit(totalCps / totalCpsMultiplier * (nextCpsMultiplier.getAsDouble() - currentCpsMultiplier.getAsDouble()), cost.getAsLong(), COACH_SLOT));
}
private static Optional<Rabbit> getRabbit(ItemStack item, int slot) {
@@ -227,9 +227,9 @@ public class ChocolateFactorySolver extends ContainerSolver {
}
Matcher costMatcher = COST_PATTERN.matcher(lore);
- OptionalInt cost = RegexUtils.getIntFromMatcher(costMatcher, cpsMatcher.hasMatch() ? cpsMatcher.end() : 0); //Cost comes after the cps line
+ OptionalLong cost = RegexUtils.getLongFromMatcher(costMatcher, cpsMatcher.hasMatch() ? cpsMatcher.end() : 0); //Cost comes after the cps line
if (cost.isEmpty()) return Optional.empty();
- return Optional.of(new Rabbit((nextCps.getAsInt() - currentCps.getAsInt())*(totalCpsMultiplier < 0 ? 1 : totalCpsMultiplier), cost.getAsInt(), slot));
+ return Optional.of(new Rabbit((nextCps.getAsInt() - currentCps.getAsInt())*(totalCpsMultiplier < 0 ? 1 : totalCpsMultiplier), cost.getAsLong(), slot));
}
private static Optional<ColorHighlight> getPrestigeHighlight() {
@@ -251,7 +251,7 @@ public class ChocolateFactorySolver extends ContainerSolver {
return highlights;
}
- private record Rabbit(double cpsIncrease, int cost, int slot) { }
+ private record Rabbit(double cpsIncrease, long cost, int slot) { }
public static final class Tooltip extends TooltipAdder {
public Tooltip() {
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/dwarven/CommissionHighlight.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CommissionHighlight.java
new file mode 100644
index 00000000..de26809c
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CommissionHighlight.java
@@ -0,0 +1,38 @@
+package de.hysky.skyblocker.skyblock.dwarven;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.ItemUtils;
+import de.hysky.skyblocker.utils.render.gui.ColorHighlight;
+import de.hysky.skyblocker.utils.render.gui.ContainerSolver;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.item.ItemStack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CommissionHighlight extends ContainerSolver {
+
+ public CommissionHighlight() {
+ super("^Commissions$");
+ }
+
+ @Override
+ protected boolean isEnabled() {
+ return SkyblockerConfigManager.get().mining.commissionHighlight;
+ }
+
+ @Override
+ protected List<ColorHighlight> getColors(String[] groups, Int2ObjectMap<ItemStack> slots) {
+ List<ColorHighlight> highlights = new ArrayList<>();
+ for (Int2ObjectMap.Entry<ItemStack> entry : slots.int2ObjectEntrySet()) {
+ ItemStack stack = entry.getValue();
+ if (stack != null && stack.contains(DataComponentTypes.LORE)) {
+ if (ItemUtils.getLoreLineIf(stack, s -> s.contains("COMPLETED")) != null) {
+ highlights.add(ColorHighlight.green(entry.getIntKey()));
+ }
+ }
+ }
+ return highlights;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java
index 6f4c86a7..d709181f 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java
@@ -42,6 +42,12 @@ import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.arg
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
import static net.minecraft.command.CommandSource.suggestMatching;
+/**
+ * Manager for Crystal Hollows waypoints that handles {@link #update() location detection},
+ * {@link #extractLocationFromMessage(Text, Boolean) waypoints receiving}, {@link #shareWaypoint(String) sharing},
+ * {@link #registerWaypointLocationCommands(CommandDispatcher, CommandRegistryAccess) commands}, and
+ * {@link #render(WorldRenderContext) rendering}.
+ */
public class CrystalsLocationsManager {
private static final Logger LOGGER = LogUtils.getLogger();
private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
@@ -55,11 +61,15 @@ public class CrystalsLocationsManager {
protected static Map<String, CrystalsWaypoint> activeWaypoints = new HashMap<>();
public static void init() {
+ // Crystal Hollows Waypoints
Scheduler.INSTANCE.scheduleCyclic(CrystalsLocationsManager::update, 40);
WorldRenderEvents.AFTER_TRANSLUCENT.register(CrystalsLocationsManager::render);
ClientReceiveMessageEvents.GAME.register(CrystalsLocationsManager::extractLocationFromMessage);
ClientCommandRegistrationCallback.EVENT.register(CrystalsLocationsManager::registerWaypointLocationCommands);
ClientPlayConnectionEvents.JOIN.register((_handler, _sender, _client) -> reset());
+
+ // Nucleus Waypoints
+ WorldRenderEvents.AFTER_TRANSLUCENT.register(NucleusWaypoints::render);
}
private static void extractLocationFromMessage(Text message, Boolean overlay) {
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/NucleusWaypoints.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/NucleusWaypoints.java
new file mode 100644
index 00000000..8046ed19
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/NucleusWaypoints.java
@@ -0,0 +1,61 @@
+package de.hysky.skyblocker.skyblock.dwarven;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Utils;
+import de.hysky.skyblocker.utils.render.RenderHelper;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.text.TextColor;
+import net.minecraft.text.Style;
+import net.minecraft.util.DyeColor;
+import net.minecraft.util.math.BlockPos;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+public class NucleusWaypoints {
+ private static final Logger LOGGER = LoggerFactory.getLogger(NucleusWaypoints.class);
+
+ private static class Waypoint {
+ BlockPos position;
+ String name;
+ DyeColor color;
+
+ Waypoint(BlockPos position, String name, DyeColor color) {
+ this.position = position;
+ this.name = name;
+ this.color = color;
+ }
+ }
+
+ private static final List<Waypoint> WAYPOINTS = List.of(
+ new Waypoint(new BlockPos(551, 116, 551), "Precursor Remnants", DyeColor.LIGHT_BLUE),
+ new Waypoint(new BlockPos(551, 116, 475), "Mithril Deposits", DyeColor.LIME),
+ new Waypoint(new BlockPos(475, 116, 551), "Goblin Holdout", DyeColor.ORANGE),
+ new Waypoint(new BlockPos(475, 116, 475), "Jungle", DyeColor.PURPLE),
+ new Waypoint(new BlockPos(513, 106, 524), "Nucleus", DyeColor.RED)
+ );
+
+ public static void render(WorldRenderContext context) {
+ try {
+ boolean enabled = SkyblockerConfigManager.get().mining.crystalHollows.nucleusWaypoints;
+ boolean inCrystalHollows = Utils.isInCrystalHollows();
+
+ if (enabled && inCrystalHollows) {
+ for (Waypoint waypoint : WAYPOINTS) {
+
+ int rgb = waypoint.color.getFireworkColor();
+ TextColor textColor = TextColor.fromRgb(rgb);
+
+ MutableText text = Text.literal(waypoint.name).setStyle(Style.EMPTY.withColor(textColor));
+
+ RenderHelper.renderText(context, text, waypoint.position.toCenterPos().add(0, 5, 0), 8, true);
+ }
+ }
+ } catch (Exception e) {
+ LOGGER.error("[{}] Error occurred while rendering Nucleus waypoints. {}", LOGGER.getName(), e);
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/events/EventNotifications.java b/src/main/java/de/hysky/skyblocker/skyblock/events/EventNotifications.java
index da2a0c2f..e846a88a 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/events/EventNotifications.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/events/EventNotifications.java
@@ -13,7 +13,6 @@ import de.hysky.skyblocker.utils.Http;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
import it.unimi.dsi.fastutil.ints.IntList;
-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.minecraft.client.MinecraftClient;
@@ -29,6 +28,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
public class EventNotifications {
private static final Logger LOGGER = LogUtils.getLogger();
@@ -39,21 +39,19 @@ public class EventNotifications {
public static final IntList DEFAULT_REMINDERS = IntList.of(60, 60 * 5);
- public static final Map<String, ItemStack> eventIcons = new Object2ObjectOpenHashMap<>();
-
- static {
- eventIcons.put("Dark Auction", new ItemStack(Items.NETHER_BRICK));
- eventIcons.put("Bonus Fishing Festival", new ItemStack(Items.FISHING_ROD));
- eventIcons.put("Bonus Mining Fiesta", new ItemStack(Items.IRON_PICKAXE));
- eventIcons.put(JACOBS, new ItemStack(Items.IRON_HOE));
- eventIcons.put("New Year Celebration", new ItemStack(Items.CAKE));
- eventIcons.put("Election Over!", new ItemStack(Items.JUKEBOX));
- eventIcons.put("Election Booth Opens", new ItemStack(Items.JUKEBOX));
- eventIcons.put("Spooky Festival", new ItemStack(Items.JACK_O_LANTERN));
- eventIcons.put("Season of Jerry", new ItemStack(Items.SNOWBALL));
- eventIcons.put("Jerry's Workshop Opens", new ItemStack(Items.SNOW_BLOCK));
- eventIcons.put("Traveling Zoo", new ItemStack(Items.HAY_BLOCK)); // change to the custom head one day
- }
+ public static final Map<String, ItemStack> eventIcons = Map.ofEntries(
+ Map.entry("Dark Auction", new ItemStack(Items.NETHER_BRICK)),
+ Map.entry("Bonus Fishing Festival", new ItemStack(Items.FISHING_ROD)),
+ Map.entry("Bonus Mining Fiesta", new ItemStack(Items.IRON_PICKAXE)),
+ Map.entry(JACOBS, new ItemStack(Items.IRON_HOE)),
+ Map.entry("New Year Celebration", new ItemStack(Items.CAKE)),
+ Map.entry("Election Over!", new ItemStack(Items.JUKEBOX)),
+ Map.entry("Election Booth Opens", new ItemStack(Items.JUKEBOX)),
+ Map.entry("Spooky Festival", new ItemStack(Items.JACK_O_LANTERN)),
+ Map.entry("Season of Jerry", new ItemStack(Items.SNOWBALL)),
+ Map.entry("Jerry's Workshop Opens", new ItemStack(Items.SNOW_BLOCK)),
+ Map.entry("Traveling Zoo", new ItemStack(Items.HAY_BLOCK)) // change to the custom head one day
+ );
public static void init() {
Scheduler.INSTANCE.scheduleCyclic(EventNotifications::timeUpdate, 20);
@@ -85,7 +83,7 @@ public class EventNotifications {
));
}
- private static final Map<String, LinkedList<SkyblockEvent>> events = new Object2ObjectOpenHashMap<>();
+ private static final Map<String, LinkedList<SkyblockEvent>> events = new ConcurrentHashMap<>();
public static Map<String, LinkedList<SkyblockEvent>> getEvents() {
return events;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CommunityShopAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CommunityShopAdder.java
index c7ea17dc..d94d6405 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CommunityShopAdder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CommunityShopAdder.java
@@ -48,7 +48,7 @@ public class CommunityShopAdder extends SlotTextAdder {
String lastLine = lore.getLast().getString();
return List.of(SlotText.bottomLeft(switch (lastLine) {
case "Maxed out!" -> Text.literal("Max").withColor(0xfab387);
- case "Currently upgrading!" -> Text.literal("⏰").withColor(0xf9e2af).formatted(Formatting.BOLD);
+ case "Currently upgrading!", "Click to instantly upgrade!" -> Text.literal("⏰").withColor(0xf9e2af).formatted(Formatting.BOLD);
case "Click to claim!" -> Text.literal("✅").withColor(0xa6e3a1).formatted(Formatting.BOLD);
default -> Text.literal(String.valueOf(RomanNumerals.romanToDecimal(roman))).withColor(0xcba6f7);
}));
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));
+ });
+ }
+
+ @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_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<String, Map<String, ?>> fetchedData = fetchCollectionsData();
+ //noinspection unchecked
+ collectionsMap = (Map<String, String[]>) fetchedData.get("COLLECTIONS");
+ //noinspection unchecked
+ tierRequirementsMap = (Map<String, List<Integer>>) fetchedData.get("TIER_REQS");
+ this.category = collection;
+ setupItemStacks(hProfile, pProfile);
+ }
+
+ private int calculateTier(int achieved, List<Integer> 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<String, NEUItem> 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<Integer> tierRequirements = tierRequirementsMap.get(collection);
+
+ List<Text> 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<Text> 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<Text> 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<String, ItemStack> 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<String, JsonElement> 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<String, Integer> 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<String, JsonElement> 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<DungeonClassWidget> 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<ItemStack> 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(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) {
+ int rootYAdjusted = rootY + (26 - dimensions.leftInt() * 3);
+ context.drawTexture(TEXTURE, rootX, rootYAdjusted, 0, 0, dimensions.rightInt() * 18 + 7, dimensions.leftInt() * 18 + 17);
+ context.drawTexture(TEXTURE, rootX + dimensions.rightInt() * 18 + 7, rootYAdjusted, 169, 0, 7, dimensions.leftInt() * 18 + 17);
+ context.drawTexture(TEXTURE, rootX, rootYAdjusted + dimensions.leftInt() * 18 + 17, 0, 215, dimensions.rightInt() * 18 + 7, 7);
+ context.drawTexture(TEXTURE, rootX + dimensions.rightInt() * 18 + 7, rootYAdjusted + dimensions.leftInt() * 18 + 17, 169, 215, 7, 7);
+
+ context.drawText(textRenderer, containerName, rootX + 7, rootYAdjusted + 7, Color.DARK_GRAY.getRGB(), false);
+
+ if (containerList.size() > itemsPerPage) {
+ previousPage.setX(rootX + 44);
+ previousPage.setY(rootY + 136);
+ previousPage.render(context, mouseX, mouseY, delta);
+
+ context.drawCenteredTextWithShadow(textRenderer, "Page: " + (activePage + 1) + "/" + totalPages, rootX + 88, rootY + 140, Color.WHITE.getRGB());
+
+ nextPage.setX(rootX + 121);
+ nextPage.setY(rootY + 136);
+ nextPage.render(context, mouseX, mouseY, delta);
+ }
+
+ int startIndex = activePage * itemsPerPage;
+ int endIndex = Math.min(startIndex + itemsPerPage, containerList.size());
+ for (int i = 0; i < endIndex - startIndex; i++) {
+ if (containerList.get(startIndex + i) == ItemStack.EMPTY) continue;
+ int column = i % dimensions.rightInt();
+ int row = i / dimensions.rightInt();
+
+ int x = rootX + 8 + column * 18;
+ int y = rootYAdjusted + 18 + row * 18;
+ context.drawItem(containerList.get(startIndex + i), x, y);
+ context.drawItemInSlot(textRenderer, containerList.get(startIndex + i), x, y);
+
+ if (mouseX > x && mouseX < x + 16 && mouseY > y && mouseY < y + 16) {
+ List<Text> tooltip = containerList.get(startIndex + i).getTooltip(Item.TooltipContext.DEFAULT, MinecraftClient.getInstance().player, TooltipType.BASIC);
+ context.drawTooltip(textRenderer, tooltip, mouseX, mouseY);
+ }
+ }
+ }
+
+ public void nextPage() {
+ if (activePage < totalPages - 1) {
+ activePage++;
+ }
+ }
+
+ public void previousPage() {
+ if (activePage > 0) {
+ activePage--;
+ }
+ }
+
+ @Override
+ public void markWidgetsAsVisible() {
+ nextPage.visible = true;
+ previousPage.visible = true;
+ nextPage.active = true;
+ previousPage.active = true;
+ }
+
+ @Override
+ public void markWidgetsAsInvisible() {
+ nextPage.visible = false;
+ previousPage.visible = false;
+ nextPage.active = false;
+ previousPage.active = false;
+ }
+
+ @Override
+ public List<ClickableWidget> getButtons() {
+ List<ClickableWidget> buttons = new ArrayList<>();
+ buttons.add(nextPage);
+ buttons.add(previousPage);
+ return buttons;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/InventoryPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/InventoryPage.java
new file mode 100644
index 00000000..8b0cbefc
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/InventoryPage.java
@@ -0,0 +1,113 @@
+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.ProfileViewerScreen;
+import de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders.BackpackItemLoader;
+import de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders.PetsInventoryItemLoader;
+import de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders.WardrobeInventoryItemLoader;
+import de.hysky.skyblocker.skyblock.profileviewer.utils.SkullCreator;
+import de.hysky.skyblocker.skyblock.profileviewer.utils.SubPageSelectButton;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+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.ItemStack;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class InventoryPage implements ProfileViewerPage {
+ private static final String[] INVENTORY_PAGES = {"Inventory", "Enderchest", "Backpack", "Wardrobe", "Pets", "Accessory Bag"};
+ private static final int TOTAL_HEIGHT = 165;
+ private static final Map<String, ItemStack> ICON_MAP = Map.ofEntries(
+ Map.entry("Wardrobe", Ico.L_CHESTPLATE),
+ Map.entry("Inventory", Ico.CHEST),
+ Map.entry("Backpack", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzYyZjNiM2EwNTQ4MWNkZTc3MjQwMDA1YzBkZGNlZTFjMDY5ZTU1MDRhNjJjZTA5Nzc4NzlmNTVhMzkzOTYxNDYifX19")),
+ Map.entry("Pets", Ico.BONE),
+ Map.entry("Enderchest", Ico.E_CHEST),
+ Map.entry("Accessory Bag", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTYxYTkxOGMwYzQ5YmE4ZDA1M2U1MjJjYjkxYWJjNzQ2ODkzNjdiNGQ4YWEwNmJmYzFiYTkxNTQ3MzA5ODVmZiJ9fX0="))
+ );
+
+ private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+ private final ProfileViewerPage[] inventorySubPages = new ProfileViewerPage[6];
+ private final List<SubPageSelectButton> inventorySelectButtons = new ArrayList<>();
+ private int activePage = 0;
+
+ public InventoryPage(JsonObject pProfile) {
+ for (int i = 0; i < INVENTORY_PAGES.length; i++) {
+ inventorySelectButtons.add(new SubPageSelectButton(this, -100, 0, i, ICON_MAP.getOrDefault(INVENTORY_PAGES[i], Ico.BARRIER)));
+ }
+
+ try {
+ JsonObject inventoryData = pProfile.getAsJsonObject("inventory");
+ if (inventoryData == null) return;
+ inventorySubPages[0] = new PlayerInventory(inventoryData);
+ inventorySubPages[1] = new Inventory(INVENTORY_PAGES[1], IntIntPair.of(5, 9), inventoryData.getAsJsonObject("ender_chest_contents"));
+ inventorySubPages[2] = new Inventory(INVENTORY_PAGES[2], IntIntPair.of(5, 9), inventoryData.getAsJsonObject("backpack_contents"), new BackpackItemLoader());
+ inventorySubPages[3] = new Inventory(INVENTORY_PAGES[3], IntIntPair.of(4, 9), inventoryData.getAsJsonObject("wardrobe_contents"), new WardrobeInventoryItemLoader(inventoryData));
+ inventorySubPages[4] = new Inventory(INVENTORY_PAGES[4], IntIntPair.of(4, 9), pProfile, new PetsInventoryItemLoader());
+ inventorySubPages[5] = new Inventory(INVENTORY_PAGES[5], IntIntPair.of(5, 9), inventoryData.getAsJsonObject("bag_contents").getAsJsonObject("talisman_bag"));
+ } catch (Exception e) {
+ ProfileViewerScreen.LOGGER.error("[Skyblocker Profile Viewer] Error while loading inventory data: ", e);
+ }
+ }
+
+ @Override
+ public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) {
+ int startingY = rootY + (TOTAL_HEIGHT - inventorySelectButtons.size() * 21) / 2;
+ for (int i = 0; i < inventorySelectButtons.size(); i++) {
+ inventorySelectButtons.get(i).setX(rootX);
+ inventorySelectButtons.get(i).setY(startingY + i * 21);
+ inventorySelectButtons.get(i).render(context, mouseX, mouseY, delta);
+ }
+
+ if (inventorySubPages[activePage] == null) {
+ context.drawText(textRenderer, "No data...", rootX + 92, rootY + 72, Color.DARK_GRAY.getRGB(), false);
+ return;
+ }
+
+ inventorySubPages[activePage].markWidgetsAsVisible();
+ inventorySubPages[activePage].render(context, mouseX, mouseY, delta, rootX + 35, rootY + 6);
+ }
+
+ public void onNavButtonClick(SubPageSelectButton clickedButton) {
+ if (inventorySubPages[activePage] != null) inventorySubPages[activePage].markWidgetsAsInvisible();
+ for (SubPageSelectButton button : inventorySelectButtons) {
+ button.setToggled(false);
+ }
+ activePage = clickedButton.getIndex();
+ clickedButton.setToggled(true);
+ }
+
+ @Override
+ public List<ClickableWidget> getButtons() {
+ List<ClickableWidget> clickableWidgets = new ArrayList<>(inventorySelectButtons);
+ for (ProfileViewerPage page : inventorySubPages) {
+ if (page != null && page.getButtons() != null) clickableWidgets.addAll(page.getButtons());
+ }
+ return clickableWidgets;
+ }
+
+ @Override
+ public void markWidgetsAsVisible() {
+ if (inventorySubPages[activePage] != null) inventorySubPages[activePage].markWidgetsAsVisible();
+ for (SubPageSelectButton button : inventorySelectButtons) {
+ button.visible = true;
+ button.active = true;
+ }
+ }
+
+ @Override
+ public void markWidgetsAsInvisible() {
+ if (inventorySubPages[activePage] != null) inventorySubPages[activePage].markWidgetsAsInvisible();
+ for (SubPageSelectButton button : inventorySelectButtons) {
+ button.visible = false;
+ button.active = false;
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PaginationButton.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PaginationButton.java
new file mode 100644
index 00000000..1a725e1a
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PaginationButton.java
@@ -0,0 +1,46 @@
+package de.hysky.skyblocker.skyblock.profileviewer.inventory;
+
+import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
+import net.minecraft.client.gui.widget.ClickableWidget;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+
+public class PaginationButton extends ClickableWidget {
+ private final ProfileViewerPage screen;
+ private final boolean isNextButton;
+ private final Identifier TEXTURE;
+ private final Identifier HIGHLIGHT;
+
+ public PaginationButton(ProfileViewerPage screen, int x, int y, boolean isNextButton) {
+ super(x, y, 12, 17, Text.empty());
+ this.screen = screen;
+ this.isNextButton = isNextButton;
+ if (isNextButton) {
+ TEXTURE = Identifier.of("minecraft", "textures/gui/sprites/recipe_book/page_forward.png");
+ HIGHLIGHT = Identifier.of("minecraft", "textures/gui/sprites/recipe_book/page_forward_highlighted.png");
+ } else {
+ TEXTURE = Identifier.of("minecraft", "textures/gui/sprites/recipe_book/page_backward.png");
+ HIGHLIGHT = Identifier.of("minecraft", "textures/gui/sprites/recipe_book/page_backward_highlighted.png");
+ }
+ }
+
+ @Override
+ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
+ context.drawTexture(TEXTURE, this.getX(), this.getY(), 0, 0, 12, 17, 12, 17);
+ if (isMouseOver(mouseX, mouseY)) context.drawTexture(HIGHLIGHT, this.getX(), this.getY(), 0, 0, 12, 17, 12, 17);
+ }
+
+ @Override
+ public void onClick(double mouseX, double mouseY) {
+ if (isNextButton) {
+ screen.nextPage();
+ } else {
+ screen.previousPage();
+ }
+ }
+
+ @Override
+ protected void appendClickableNarrations(NarrationMessageBuilder builder) {}
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java
new file mode 100644
index 00000000..b3389d39
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java
@@ -0,0 +1,186 @@
+package de.hysky.skyblocker.skyblock.profileviewer.inventory;
+
+import de.hysky.skyblocker.skyblock.PetCache;
+import de.hysky.skyblocker.skyblock.itemlist.ItemFixerUpper;
+import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
+import de.hysky.skyblocker.skyblock.profileviewer.utils.LevelFinder;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.utils.ItemUtils;
+import de.hysky.skyblocker.utils.NEURepoManager;
+import io.github.moulberry.repo.constants.PetNumbers;
+import io.github.moulberry.repo.data.NEUItem;
+import io.github.moulberry.repo.data.Rarity;
+import io.github.moulberry.repo.util.PetId;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.LoreComponent;
+import net.minecraft.component.type.ProfileComponent;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.nbt.NbtString;
+import net.minecraft.registry.Registries;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.Pair;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.stream.Collectors;
+
+import static de.hysky.skyblocker.skyblock.itemlist.ItemStackBuilder.SKULL_TEXTURE_PATTERN;
+import static de.hysky.skyblocker.skyblock.itemlist.ItemStackBuilder.SKULL_UUID_PATTERN;
+
+public class Pet {
+ private final String name;
+ private final double xp;
+ private final String tier;
+ private final Optional<String> heldItem;
+ private final int level;
+ private final ItemStack icon;
+
+ private static final Map<String, Integer> TIER_MAP = Map.of(
+ "COMMON", 0, "UNCOMMON", 1, "RARE", 2, "EPIC", 3, "LEGENDARY", 4, "MYTHIC", 5
+ );
+
+ public Pet(PetCache.PetInfo petData) {
+ this.name = petData.type();
+ this.xp = petData.exp();
+ this.heldItem = petData.item();
+ if ((heldItem.isPresent() && heldItem.get().equals("PET_ITEM_TIER_BOOST"))) {
+ this.tier = switch (petData.tier()) {
+ case "COMMON" -> "UNCOMMON";
+ case "UNCOMMON" -> "RARE";
+ case "RARE" -> "EPIC";
+ case "EPIC" -> "LEGENDARY";
+ case "LEGENDARY" -> "MYTHIC";
+ default -> petData.tier();
+ };
+ } else {
+ this.tier = petData.tier();
+ }
+ this.level = LevelFinder.getLevelInfo(this.name.equals("GOLDEN_DRAGON") ? "PET_GREG" : "PET_" + this.tier, (long) xp).level;
+ this.icon = createIcon();
+ }
+
+ public String getName() { return name; }
+ public long getXP() { return (long) xp; }
+ public int getTier() { return TIER_MAP.getOrDefault(tier, 0); }
+ public String getTierAsString() { return tier; }
+ public String getSkin() { return null; }
+ public int getLevel() { return level; }
+ public ItemStack getIcon() { return icon; }
+
+
+ private ItemStack createIcon() {
+ if (NEURepoManager.isLoading() || !ItemRepository.filesImported()) return Ico.BARRIER;
+ Map<String, NEUItem> items = NEURepoManager.NEU_REPO.getItems().getItems();
+ if (items == null) return Ico.BARRIER;
+
+ String targetItemId = this.getName() + ";" + this.getTier();
+ NEUItem item = items.values().stream()
+ .filter(i -> Formatting.strip(i.getSkyblockItemId()).equals(targetItemId))
+ .findFirst().orElse(null);
+
+ NEUItem petItem = null;
+ if (this.heldItem.isPresent()) {
+ petItem = items.values().stream()
+ .filter(i -> Formatting.strip(i.getSkyblockItemId()).equals(this.heldItem.get()))
+ .findFirst().orElse(null);
+ }
+
+ return fromNEUItem(item, petItem);
+ }
+
+ /**
+ * Converts NEU item data into an ItemStack.
+ * <p> This method converts NEU item data into a Pet by using the placeholder
+ * information from NEU-REPO and injecting the player's calculated pet stats into the lore and transforming
+ * the NBT Data into modern DataComponentTypes before returning the final ItemStack </p
+ *
+ * @param item The NEUItem representing the pet.
+ * @param helditem The NEUItem representing the held item, if any.
+ * @return The ItemStack representing the pet with all its properties set.
+ */
+ private ItemStack fromNEUItem(NEUItem item, NEUItem helditem) {
+ if (item == null) return Ico.BARRIER;
+ List<Pair<String, String>> injectors = new ArrayList<>(createLoreReplacers(item.getSkyblockItemId(), helditem));
+ Identifier itemId = Identifier.of(ItemFixerUpper.convertItemId(item.getMinecraftItemId(), item.getDamage()));
+ ItemStack stack = new ItemStack(Registries.ITEM.get(itemId));
+
+ NbtCompound customData = new NbtCompound();
+ customData.put(ItemUtils.ID, NbtString.of(item.getSkyblockItemId()));
+ stack.set(DataComponentTypes.CUSTOM_NAME, Text.of(injectData(item.getDisplayName(), injectors)));
+
+ stack.set(DataComponentTypes.LORE, new LoreComponent(
+ item.getLore().stream().map(line -> injectData(line, injectors))
+ .filter(line -> !line.contains("SKIP")).map(Text::of)
+ .collect(Collectors.toList())));
+
+ Matcher skullUuid = SKULL_UUID_PATTERN.matcher(item.getNbttag());
+ Matcher skullTexture = SKULL_TEXTURE_PATTERN.matcher(item.getNbttag());
+ if (skullUuid.find() && skullTexture.find()) {
+ UUID uuid = UUID.fromString(skullUuid.group(1));
+ String textureValue = this.getSkin() == null ? skullTexture.group(1) : this.getSkin();
+ stack.set(DataComponentTypes.PROFILE, new ProfileComponent(
+ Optional.of(item.getSkyblockItemId()), Optional.of(uuid),
+ ItemUtils.propertyMapWithTexture(textureValue)));
+ }
+ return stack;
+ }
+
+ /**
+ * Generates a list of placeholder-replacement pairs for the itemName of a pet item.
+ * <p> This method uses the pet's data from the NEU repository and uses PetInfo to generate replacers, and optionally
+ * includes data about a held item. </p>
+ *
+ * @param itemSkyblockID The initial itemName string containing the pet's name and tier separated by a semicolon.
+ * @param helditem The NEUItem representing the held item, if any.
+ * @return A list of placeholder-replacement pairs to be used for injecting data into the pet item's itemName.
+ */
+ private List<Pair<String, String>> createLoreReplacers(String itemSkyblockID, NEUItem helditem) {
+ List<Pair<String, String>> list = new ArrayList<>();
+ Map<@PetId String, Map<Rarity, PetNumbers>> petNums = NEURepoManager.NEU_REPO.getConstants().getPetNumbers();
+ String petName = itemSkyblockID.split(";")[0];
+ if (!itemSkyblockID.contains(";") || !petNums.containsKey(petName)) return list;
+
+ Rarity rarity = Rarity.values()[Integer.parseInt(itemSkyblockID.split(";")[1])];
+ try {
+ PetNumbers data = petNums.get(petName).get(rarity);
+ list.add(new Pair<>("\\{LVL\\}", String.valueOf(this.level)));
+ data.interpolatedStatsAtLevel(this.level).getStatNumbers().forEach((key, value) ->
+ list.add(new Pair<>("\\{" + key + "\\}", fixDecimals(value, true))));
+
+ List<Double> otherNumsMin = data.interpolatedStatsAtLevel(this.level).getOtherNumbers();
+ for (int i = 0; i < otherNumsMin.size(); ++i) {
+ list.add(new Pair<>("\\{" + i + "\\}", fixDecimals(otherNumsMin.get(i), false)));
+ }
+
+ list.add(new Pair<>("Right-click to add this pet to",
+ helditem != null ? "§r§6Held Item: " + helditem.getDisplayName() : "SKIP"));
+ list.add(new Pair<>("pet menu!", "SKIP"));
+ } catch (Exception e) {
+ if (petName.equals("GOLDEN_DRAGON")) {
+ list.add(new Pair<>("Golden Dragon",
+ "§r§7[Lvl " + this.level + "] " + "§6Golden Dragon Egg §c[Not Supported by NEU-Repo]"));
+ }
+ }
+ return list;
+ }
+
+ private String injectData(String string, List<Pair<String, String>> injectors) {
+ for (Pair<String, String> injector : injectors) {
+ if (string.contains(injector.getLeft())) return injector.getRight();
+ string = string.replaceAll(injector.getLeft(), injector.getRight());
+ }
+ return string;
+ }
+
+ private String fixDecimals(double num, boolean truncate) {
+ if (num % 1 == 0) return String.valueOf((int) num);
+ BigDecimal roundedNum = new BigDecimal(num).setScale(3, RoundingMode.HALF_UP);
+ return truncate && num > 1 ? String.valueOf(roundedNum.intValue())
+ : roundedNum.stripTrailingZeros().toPlainString();
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PlayerInventory.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PlayerInventory.java
new file mode 100644
index 00000000..26673693
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PlayerInventory.java
@@ -0,0 +1,73 @@
+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.InventoryItemLoader;
+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.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.List;
+
+public class PlayerInventory 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 List<ItemStack> containerList;
+
+ public PlayerInventory(JsonObject inventory) {
+ this.containerList = new InventoryItemLoader().loadItems(inventory);
+ }
+
+ // Z-STACKING forces this nonsense of separating the Background texture and Item Drawing :(
+ public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) {
+ drawContainerTextures(context, "Armour", rootX, rootY + 108, IntIntPair.of(1, 4));
+ drawContainerTextures(context, "Inventory", rootX, rootY + 2, IntIntPair.of(4, 9));
+ drawContainerTextures(context, "Equipment", rootX + 90, rootY + 108, IntIntPair.of(1, 4));
+
+ drawContainerItems(context, rootX, rootY + 108, IntIntPair.of(1, 4), 36, 40, mouseX, mouseY);
+ drawContainerItems(context, rootX, rootY + 2, IntIntPair.of(4, 9), 0, 36, mouseX, mouseY);
+ drawContainerItems(context, rootX + 90, rootY + 108, IntIntPair.of(1, 4), 40, containerList.size(), mouseX, mouseY);
+ }
+
+ private void drawContainerTextures(DrawContext context, String containerName, int rootX, int rootY, IntIntPair dimensions) {
+ if (containerName.equals("Inventory")) {
+ context.drawTexture(TEXTURE, rootX, rootY + dimensions.leftInt() + 10, 0, 136, dimensions.rightInt() * 18 + 7, dimensions.leftInt() * 18 + 17);
+ context.drawTexture(TEXTURE, rootX + dimensions.rightInt() * 18 + 7, rootY, 169, 0, 7, dimensions.leftInt() * 18 + 21);
+ context.drawTexture(TEXTURE, rootX, rootY, 0, 0, dimensions.rightInt() * 18 + 7, 14);
+ context.drawTexture(TEXTURE, rootX + dimensions.rightInt() * 18 + 7, rootY + dimensions.leftInt() * 18 + 21, 169, 215, 7, 7);
+ } else {
+ context.drawTexture(TEXTURE, rootX, rootY, 0, 0, dimensions.rightInt() * 18 + 7, dimensions.leftInt() * 18 + 17);
+ context.drawTexture(TEXTURE, rootX + dimensions.rightInt() * 18 + 7, rootY, 169, 0, 7, dimensions.leftInt() * 18 + 17);
+ context.drawTexture(TEXTURE, rootX, rootY + dimensions.leftInt() * 18 + 17, 0, 215, dimensions.rightInt() * 18 + 7, 7);
+ context.drawTexture(TEXTURE, rootX + dimensions.rightInt() * 18 + 7, rootY + dimensions.leftInt() * 18 + 17, 169, 215, 7, 7);
+ }
+
+ context.drawText(textRenderer, containerName, rootX + 7, rootY + 7, Color.DARK_GRAY.getRGB(), false);
+ }
+
+ private void drawContainerItems(DrawContext context, int rootX, int rootY, IntIntPair dimensions, int startIndex, int endIndex, int mouseX, int mouseY) {
+ for (int i = 0; i < endIndex - startIndex; i++) {
+ if (containerList.get(startIndex + i) == ItemStack.EMPTY) continue;
+ int column = i % dimensions.rightInt();
+ int row = i / dimensions.rightInt();
+
+ int x = rootX + 8 + column * 18;
+ int y = (rootY + 18 + row * 18) + (dimensions.leftInt() > 1 && row + 1 == dimensions.leftInt() ? 4 : 0);
+
+ context.drawItem(containerList.get(startIndex + i), x, y);
+ context.drawItemInSlot(textRenderer, containerList.get(startIndex + i), x, y);
+
+ if (mouseX > x && mouseX < x + 16 && mouseY > y && mouseY < y + 16) {
+ List<Text> tooltip = containerList.get(startIndex + i).getTooltip(Item.TooltipContext.DEFAULT, MinecraftClient.getInstance().player, TooltipType.BASIC);
+ context.drawTooltip(textRenderer, tooltip, mouseX, mouseY);
+ }
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/BackpackItemLoader.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/BackpackItemLoader.java
new file mode 100644
index 00000000..99e728be
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/BackpackItemLoader.java
@@ -0,0 +1,34 @@
+package de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import net.minecraft.item.ItemStack;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class BackpackItemLoader extends ItemLoader {
+ @Override
+ public List<ItemStack> loadItems(JsonObject data) {
+ List<ItemStack> backpackItems = new ArrayList<>();
+
+ // Sort the data by keys numerically
+ List<Map.Entry<String, JsonElement>> sortedEntries = data.entrySet().stream()
+ .sorted((e1, e2) -> {
+ int key1 = Integer.parseInt(e1.getKey());
+ int key2 = Integer.parseInt(e2.getKey());
+ return Integer.compare(key1, key2);
+ }).toList();
+
+ for (int i = 0; i < sortedEntries.size(); i++) {
+ backpackItems.addAll(super.loadItems(sortedEntries.get(i).getValue().getAsJsonObject()));
+ int padding = (i + 1) * 45 % (backpackItems.isEmpty() ? 1 : backpackItems.size());
+ for (int j = 0; j < padding; j++) {
+ backpackItems.add(ItemStack.EMPTY);
+ }
+ }
+
+ return backpackItems;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/InventoryItemLoader.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/InventoryItemLoader.java
new file mode 100644
index 00000000..f73661a1
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/InventoryItemLoader.java
@@ -0,0 +1,29 @@
+package de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders;
+
+import com.google.gson.JsonObject;
+import net.minecraft.item.ItemStack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class InventoryItemLoader extends ItemLoader {
+
+ private static final String[] INVENTORIES = {"inv_contents", "inv_armor", "equipment_contents"};
+
+ @Override
+ public List<ItemStack> loadItems(JsonObject data) {
+ List<ItemStack> inventoryItems = new ArrayList<>();
+ for (String inventory : INVENTORIES) {
+ List<ItemStack> inv = super.loadItems(data.getAsJsonObject(inventory));
+ switch (inventory) {
+ case "inv_armor" -> inventoryItems.addAll(inv.reversed());
+ case "inv_contents" -> {
+ inventoryItems.addAll(inv.subList(9,inv.size()));
+ inventoryItems.addAll(inv.subList(0, 9));
+ }
+ default -> inventoryItems.addAll(inv);
+ }
+ }
+ return inventoryItems;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/ItemLoader.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/ItemLoader.java
new file mode 100644
index 00000000..9d9b1b07
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/ItemLoader.java
@@ -0,0 +1,139 @@
+package de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.mojang.serialization.JsonOps;
+import de.hysky.skyblocker.skyblock.PetCache;
+import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
+import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen;
+import de.hysky.skyblocker.skyblock.profileviewer.inventory.Pet;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.utils.ItemUtils;
+import de.hysky.skyblocker.utils.NEURepoManager;
+import io.github.moulberry.repo.data.NEUItem;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.AttributeModifiersComponent;
+import net.minecraft.component.type.DyedColorComponent;
+import net.minecraft.component.type.LoreComponent;
+import net.minecraft.component.type.ProfileComponent;
+import net.minecraft.datafixer.fix.ItemIdFix;
+import net.minecraft.datafixer.fix.ItemInstanceTheFlatteningFix;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.*;
+import net.minecraft.registry.Registries;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.Identifier;
+
+import java.io.ByteArrayInputStream;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static de.hysky.skyblocker.skyblock.itemlist.ItemRepository.getItemStack;
+
+public class ItemLoader {
+
+ public List<ItemStack> loadItems(JsonObject data) {
+ NbtList containerContent = decompress(data);
+ List<ItemStack> itemList = new ArrayList<>();
+
+ for (int i = 0; i < containerContent.size(); i++) {
+ if (containerContent.getCompound(i).getInt("id") == 0) {
+ itemList.add(ItemStack.EMPTY);
+ continue;
+ }
+
+ NbtCompound nbttag = containerContent.getCompound(i).getCompound("tag");
+ String internalName = nbttag.getCompound("ExtraAttributes").getString("id");
+ if (internalName.equals("PET")) {
+ NbtCompound extraAttributes = nbttag .getCompound("ExtraAttributes");
+ PetCache.PetInfo petInfo = PetCache.PetInfo.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString(extraAttributes.getString("petInfo"))).getOrThrow();
+ Pet pet = new Pet(petInfo);
+ itemList.add(pet.getIcon());
+ continue;
+ }
+
+ Identifier itemId = identifierFromOldId(containerContent.getCompound(i).getInt("id"), containerContent.getCompound(i).getInt("Damage"));
+ ItemStack stack = itemId.toString().equals("minecraft:air") ? getItemStack(internalName) : new ItemStack(Registries.ITEM.get(itemId));
+
+ if (stack == null || stack.isEmpty() || stack.getItem().equals(Ico.BARRIER.getItem())) {
+ // Last ditch effort to find item in NEU REPO
+ Map<String, NEUItem> items = NEURepoManager.NEU_REPO.getItems().getItems();
+ stack = items.values().stream()
+ .filter(j -> Formatting.strip(j.getSkyblockItemId()).equals(Formatting.strip(internalName).replace(":", "-")))
+ .findFirst()
+ .map(NEUItem::getSkyblockItemId)
+ .map(ItemRepository::getItemStack)
+ .orElse(Ico.BARRIER.copy());
+
+
+ if (stack.getName().getString().contains("barrier")) {
+ stack.set(DataComponentTypes.CUSTOM_NAME, Text.literal("Err: " + internalName));
+ itemList.add(stack);
+ continue;
+ }
+ }
+
+ // Custom Data
+ NbtCompound customData = new NbtCompound();
+
+ // Add Skyblock Item Id
+ customData.put(ItemUtils.ID, NbtString.of(internalName));
+
+
+ // Item Name
+ stack.set(DataComponentTypes.CUSTOM_NAME, Text.of(nbttag.getCompound("display").getString("Name")));
+
+ // Lore
+ NbtList loreList = nbttag.getCompound("display").getList("Lore", 8);
+ stack.set(DataComponentTypes.LORE, new LoreComponent(loreList.stream()
+ .map(NbtElement::asString)
+ .map(Text::literal)
+ .collect(Collectors.toList())));
+
+ // add skull texture
+ NbtList texture = nbttag.getCompound("SkullOwner").getCompound("Properties").getList("textures", 10);
+ if (!texture.isEmpty()) {
+ stack.set(DataComponentTypes.PROFILE, new ProfileComponent(Optional.of(internalName), Optional.of(UUID.fromString(nbttag.getCompound("SkullOwner").get("Id").asString())), ItemUtils.propertyMapWithTexture(texture.getCompound(0).getString("Value"))));
+ }
+
+ // Colour
+ if (nbttag.getCompound("display").contains("color")) {
+ int color = nbttag.getCompound("display").getInt("color");
+ stack.set(DataComponentTypes.DYED_COLOR, new DyedColorComponent(color, false));
+ }
+
+ // add enchantment glint
+ if (nbttag.getKeys().contains("ench")) {
+ stack.set(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true);
+ }
+
+ // Hide weapon damage and other useless info
+ stack.set(DataComponentTypes.ATTRIBUTE_MODIFIERS, new AttributeModifiersComponent(List.of(), false));
+
+ // Set Count
+ stack.setCount(containerContent.getCompound(i).getInt("Count"));
+
+ itemList.add(stack);
+ }
+
+ return itemList;
+ }
+
+ private static Identifier identifierFromOldId(int id, int damage) {
+ try {
+ return damage != 0 ? Identifier.of(ItemInstanceTheFlatteningFix.getItem(ItemIdFix.fromId(id), damage)) : Identifier.of(ItemIdFix.fromId(id));
+ } catch (Exception e) {
+ return Identifier.of("air");
+ }
+ }
+
+ private static NbtList decompress(JsonObject data) {
+ try {
+ return NbtIo.readCompressed(new ByteArrayInputStream(Base64.getDecoder().decode(data.get("data").getAsString())), NbtSizeTracker.ofUnlimitedBytes()).getList("i", NbtElement.COMPOUND_TYPE);
+ } catch (Exception e) {
+ ProfileViewerScreen.LOGGER.error("[Skyblocker Profile Viewer] Failed to decompress item data", e);
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/PetsInventoryItemLoader.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/PetsInventoryItemLoader.java
new file mode 100644
index 00000000..cd3b7a26
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/PetsInventoryItemLoader.java
@@ -0,0 +1,42 @@
+package de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.mojang.serialization.JsonOps;
+import de.hysky.skyblocker.skyblock.PetCache;
+import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen;
+import de.hysky.skyblocker.skyblock.profileviewer.inventory.Pet;
+import net.minecraft.item.ItemStack;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+public class PetsInventoryItemLoader extends ItemLoader {
+ private static final List<String> TIER_ORDER = List.of("MYTHIC", "LEGENDARY", "EPIC", "RARE", "UNCOMMON", "COMMON");
+
+ @Override
+ public List<ItemStack> loadItems(JsonObject data) {
+ List<Pet> petList = new ArrayList<>();
+ try {
+ JsonObject petsData = data.getAsJsonObject("pets_data");
+ if (petsData != null && petsData.has("pets")) {
+ for (var petElement : petsData.get("pets").getAsJsonArray()) {
+ PetCache.PetInfo petInfo = PetCache.PetInfo.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString(petElement.toString())).getOrThrow();
+ petList.add(new Pet(petInfo));
+ }
+ }
+ } catch (Exception e) {
+ ProfileViewerScreen.LOGGER.error("[Skyblocker Profile Viewer] Failed to load pets", e);
+ }
+
+ // Sort pets by tier (in reverse order) and level (in reverse order)
+ petList.sort(Comparator.comparingInt((Pet pet) -> TIER_ORDER.indexOf(pet.getTierAsString())).reversed().thenComparingInt(Pet::getLevel).reversed());
+
+ List<ItemStack> itemList = new ArrayList<>();
+ for (Pet pet : petList) {
+ itemList.add(pet.getIcon());
+ }
+ return itemList;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/WardrobeInventoryItemLoader.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/WardrobeInventoryItemLoader.java
new file mode 100644
index 00000000..9d434726
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/WardrobeInventoryItemLoader.java
@@ -0,0 +1,40 @@
+package de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders;
+
+import com.google.gson.JsonObject;
+import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen;
+import net.minecraft.item.ItemStack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class WardrobeInventoryItemLoader extends ItemLoader {
+ private final int activeSlot;
+ private final JsonObject activeArmorSet;
+
+ public WardrobeInventoryItemLoader(JsonObject inventory) {
+ this.activeSlot = inventory.get("wardrobe_equipped_slot").getAsInt();
+ this.activeArmorSet = inventory.get("inv_armor").getAsJsonObject();
+ }
+
+ @Override
+ public List<ItemStack> loadItems(JsonObject data) {
+ List<ItemStack> itemList = new ArrayList<>();
+
+ try {
+ itemList.addAll(super.loadItems(data));
+ if (activeSlot != -1) {
+ List<ItemStack> activeArmour = super.loadItems(activeArmorSet).reversed();
+ for (int i = 0; i < 4; i++) {
+ int baseIndex = activeSlot % 9;
+ int page = activeSlot / 9;
+ int slotIndex = (page * 36) + (i * 9) + baseIndex - 1;
+ itemList.set(slotIndex, activeArmour.get(i));
+ }
+ }
+ } catch (Exception e) {
+ ProfileViewerScreen.LOGGER.error("[Skyblocker Profile Viewer] Failed to load wardrobe items", e);
+ }
+
+ return itemList;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillWidget.java
new file mode 100644
index 00000000..3a3870f3
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillWidget.java
@@ -0,0 +1,95 @@
+package de.hysky.skyblocker.skyblock.profileviewer.skills;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.skyblock.profileviewer.utils.LevelFinder;
+import de.hysky.skyblocker.skyblock.profileviewer.utils.SkullCreator;
+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 SkillWidget {
+ private final String SKILL_NAME;
+ private final LevelFinder.LevelInfo SKILL_LEVEL;
+
+ 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 final ItemStack stack;
+ private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+ private static final Map<String, ItemStack> SKILL_LOGO = Map.ofEntries(
+ Map.entry("Combat", Ico.STONE_SWORD),
+ Map.entry("Farming", Ico.GOLDEN_HOE),
+ Map.entry("Mining", Ico.STONE_PICKAXE),
+ Map.entry("Foraging", Ico.JUNGLE_SAPLING),
+ Map.entry("Fishing", Ico.FISH_ROD),
+ Map.entry("Enchanting", Ico.ENCHANTING_TABLE),
+ Map.entry("Alchemy", Ico.BREWING_STAND),
+ Map.entry("Taming", Ico.SPAWN_EGG),
+ Map.entry("Carpentry", Ico.CRAFTING_TABLE),
+ Map.entry("Catacombs", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzliNTY4OTViOTY1OTg5NmFkNjQ3ZjU4NTk5MjM4YWY1MzJkNDZkYjljMWIwMzg5YjhiYmViNzA5OTlkYWIzM2QifX19")),
+ Map.entry("Runecraft", Ico.MAGMA_CREAM),
+ Map.entry("Social", Ico.EMERALD)
+ );
+ private static final Map<String, Integer> SKILL_CAP = Map.ofEntries(
+ Map.entry("Combat", 60),
+ Map.entry("Farming", 60),
+ Map.entry("Mining", 60),
+ Map.entry("Foraging", 50),
+ Map.entry("Fishing", 50),
+ Map.entry("Enchanting", 60),
+ Map.entry("Alchemy", 50),
+ Map.entry("Taming", 60),
+ Map.entry("Carpentry", 50),
+ Map.entry("Catacombs", 50),
+ Map.entry("Runecraft", 25),
+ Map.entry("Social", 25)
+ );
+ private static final Map<String, Integer> SOFT_SKILL_CAP = Map.of(
+ "Taming", 50,
+ "Farming", 50
+ );
+
+ private static final Map<String, Integer> INFINITE = Map.of(
+ "Catacombs", 0
+ );
+
+ public SkillWidget(String skill, long xp, int playerCap) {
+ this.SKILL_NAME = skill;
+ this.SKILL_LEVEL = LevelFinder.getLevelInfo(skill, xp);
+ if (SKILL_LEVEL.level >= SKILL_CAP.get(skill) && !INFINITE.containsKey(skill)) {
+ SKILL_LEVEL.fill = 1;
+ SKILL_LEVEL.level = SKILL_CAP.get(skill);
+ }
+
+ this.stack = SKILL_LOGO.getOrDefault(skill, Ico.BARRIER);
+ if (playerCap != -1) {
+ this.SKILL_LEVEL.level = Math.min(SKILL_LEVEL.level, (SOFT_SKILL_CAP.get(this.SKILL_NAME) + playerCap));
+ }
+
+ }
+
+ public void render(DrawContext context, int x, int y) {
+ context.drawItem(this.stack, x + 3, y + 2);
+ context.drawText(textRenderer, SKILL_NAME + " " + SKILL_LEVEL.level, x + 31, y + 2, Color.white.hashCode(), false);
+
+ Color fillColor = Color.green;
+ if (SKILL_LEVEL.level >= SKILL_CAP.get(SKILL_NAME)) {
+ fillColor = Color.MAGENTA;
+ }
+
+ if ((SOFT_SKILL_CAP.containsKey(SKILL_NAME) && SKILL_LEVEL.level > SOFT_SKILL_CAP.get(SKILL_NAME)) && SKILL_LEVEL.level < SKILL_CAP.get(SKILL_NAME) && SKILL_LEVEL.fill == 1 ||
+ (SKILL_NAME.equals("Taming") && SKILL_LEVEL.level >= SOFT_SKILL_CAP.get(SKILL_NAME))) {
+ fillColor = Color.YELLOW;
+ }
+
+ context.drawGuiTexture(BAR_BACK, x + 30, y + 12, 75, 6);
+ RenderHelper.renderNineSliceColored(context, BAR_FILL, x + 30, y + 12, (int) (75 * SKILL_LEVEL.fill), 6, fillColor);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillsPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillsPage.java
new file mode 100644
index 00000000..c331bbdd
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillsPage.java
@@ -0,0 +1,93 @@
+package de.hysky.skyblocker.skyblock.profileviewer.skills;
+
+import com.google.gson.JsonObject;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage;
+import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.util.Identifier;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SkillsPage implements ProfileViewerPage {
+ private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/icon_data_widget.png");
+ private static final String[] SKILLS = {"Combat", "Mining", "Farming", "Foraging", "Fishing", "Enchanting", "Alchemy", "Taming", "Carpentry", "Catacombs", "Runecraft", "Social"};
+ private static final int ROW_GAP = 28;
+
+ private final JsonObject HYPIXEL_PROFILE;
+ private final JsonObject PLAYER_PROFILE;
+
+ private final List<SkillWidget> skillWidgets = new ArrayList<>();
+ private JsonObject skills;
+
+ public SkillsPage(JsonObject hProfile, JsonObject pProfile) {
+ this.HYPIXEL_PROFILE = hProfile;
+ this.PLAYER_PROFILE = pProfile;
+
+ try {
+ this.skills = this.PLAYER_PROFILE.getAsJsonObject("player_data").getAsJsonObject("experience");
+ for (String skill : SKILLS) {
+ skillWidgets.add(new SkillWidget(skill, getSkillXP("SKILL_" + skill.toUpperCase()), getSkillCap(skill)));
+ }
+ } catch (Exception e) {
+ ProfileViewerScreen.LOGGER.error("[Skyblocker Profile Viewer] Error creating widgets.", e);
+ }
+ }
+
+ public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) {
+ int column2 = rootX + 113;
+ for (int i = 0; i < skillWidgets.size(); i++) {
+ int x = (i < 6) ? rootX : column2;
+ int y = rootY + (i % 6) * ROW_GAP;
+ context.drawTexture(TEXTURE, x, y, 0, 0, 109, 26, 109, 26);
+ skillWidgets.get(i).render(context, x, y + 3);
+ }
+ }
+
+ private int getSkillCap(String skill) {
+ try {
+ return switch (skill) {
+ case "Farming" -> this.PLAYER_PROFILE.getAsJsonObject("jacobs_contest").getAsJsonObject("perks").get("farming_level_cap").getAsInt();
+ default -> -1;
+ };
+ } catch (Exception e) {
+ return 0;
+ }
+ }
+
+ private long getSkillXP(String skill) {
+ try {
+ return switch (skill) {
+ case "SKILL_CATACOMBS" -> getCatacombsXP();
+ case "SKILL_SOCIAL" -> getCoopSocialXP();
+ case "SKILL_RUNECRAFT" -> this.skills.get("SKILL_RUNECRAFTING").getAsLong();
+ default -> this.skills.get(skill).getAsLong();
+ };
+ } catch (Exception e) {
+ return 0;
+ }
+ }
+
+ private long getCatacombsXP() {
+ try {
+ JsonObject dungeonSkills = this.PLAYER_PROFILE.getAsJsonObject("dungeons").getAsJsonObject("dungeon_types");
+ return dungeonSkills.getAsJsonObject("catacombs").get("experience").getAsLong();
+ } catch (Exception e) {
+ return 0;
+ }
+ }
+
+ private long getCoopSocialXP() {
+ long socialXP = 0;
+ JsonObject members = HYPIXEL_PROFILE.getAsJsonObject("members");
+ for (String memberId : members.keySet()) {
+ try {
+ socialXP += members.getAsJsonObject(memberId).getAsJsonObject("player_data").getAsJsonObject("experience").get("SKILL_SOCIAL").getAsLong();
+ } catch (Exception e) {
+ ProfileViewerScreen.LOGGER.warn("[Skyblocker Profile Viewer] Error calculating co-op social xp", e);
+ }
+ }
+ return socialXP;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayerWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayerWidget.java
new file mode 100644
index 00000000..a9c05c11
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayerWidget.java
@@ -0,0 +1,93 @@
+package de.hysky.skyblocker.skyblock.profileviewer.slayers;
+
+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 SlayerWidget {
+ private final String slayerName;
+ private final LevelFinder.LevelInfo slayerLevel;
+ private JsonObject slayerData = null;
+
+ private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/icon_data_widget.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 final Identifier item;
+ private final ItemStack drop;
+ private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+ private static final Map<String, Identifier> HEAD_ICON = Map.ofEntries(
+ Map.entry("Zombie", Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/zombie.png")),
+ Map.entry("Spider", Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/spider.png")),
+ Map.entry("Wolf", Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/wolf.png")),
+ Map.entry("Enderman", Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/enderman.png")),
+ Map.entry("Vampire", Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/vampire.png")),
+ Map.entry("Blaze", Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/blaze.png"))
+ );
+
+ private static final Map<String, ItemStack> DROP_ICON = Map.ofEntries(
+ Map.entry("Zombie", Ico.FLESH),
+ Map.entry("Spider", Ico.STRING),
+ Map.entry("Wolf", Ico.MUTTON),
+ Map.entry("Enderman", Ico.E_PEARL),
+ Map.entry("Vampire", Ico.REDSTONE),
+ Map.entry("Blaze", Ico.B_POWDER)
+ );
+
+ public SlayerWidget(String slayer, long xp, JsonObject playerProfile) {
+ this.slayerName = slayer;
+ this.slayerLevel = LevelFinder.getLevelInfo(slayer, xp);
+ this.item = HEAD_ICON.get(slayer);
+ this.drop = DROP_ICON.getOrDefault(slayer, Ico.BARRIER);
+ try {
+ this.slayerData = playerProfile.getAsJsonObject("slayer").getAsJsonObject("slayer_bosses").getAsJsonObject(this.slayerName.toLowerCase());
+ } catch (Exception ignored) {}
+ }
+
+ public void render(DrawContext context, int x, int y) {
+ context.drawTexture(TEXTURE, x, y, 0, 0, 109, 26, 109, 26);
+ context.drawTexture(this.item, x + 1, y + 3, 0, 0, 20, 20, 20, 20);
+ context.drawText(textRenderer, slayerName + " " + slayerLevel.level, x + 31, y + 5, Color.white.hashCode(), false);
+
+ int col2 = x + 113;
+ context.drawTexture(TEXTURE, col2, y, 0, 0, 109, 26, 109, 26);
+ context.drawItem(this.drop, col2 + 3, y + 5);
+ context.drawText(textRenderer, "§aKills: §r" + findTotalKills(), col2 + 30, y + 4, Color.white.hashCode(), true);
+ context.drawText(textRenderer, findTopTierKills(), findTopTierKills().equals("No Data") ? col2 + 30 : col2 + 29, y + 15, Color.white.hashCode(), true);
+
+ context.drawGuiTexture(BAR_BACK, x + 30, y + 15, 75, 6);
+ Color fillColor = slayerLevel.fill == 1 ? Color.MAGENTA : Color.green;
+ RenderHelper.renderNineSliceColored(context, BAR_FILL, x + 30, y + 15, (int) (75 * slayerLevel.fill), 6, fillColor);
+ }
+
+ private int findTotalKills() {
+ try {
+ int totalKills = 0;
+ for (String key : this.slayerData.keySet()) {
+ if (key.startsWith("boss_kills_tier_")) totalKills += this.slayerData.get(key).getAsInt();
+ }
+ return totalKills;
+ } catch (Exception e) {
+ return 0;
+ }
+ }
+
+ private String findTopTierKills() {
+ try {
+ for (int tier = 4; tier >= 0; tier--) {
+ String key = "boss_kills_tier_" + tier;
+ if (this.slayerData.has(key)) return "§cT" + (tier + 1) + " Kills: §r" + this.slayerData.get(key).getAsInt();
+ }
+ } catch (Exception ignored) {}
+ return "No Data";
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayersPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayersPage.java
new file mode 100644
index 00000000..08e2ca06
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayersPage.java
@@ -0,0 +1,41 @@
+package de.hysky.skyblocker.skyblock.profileviewer.slayers;
+
+import com.google.gson.JsonObject;
+import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage;
+import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen;
+import net.minecraft.client.gui.DrawContext;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SlayersPage implements ProfileViewerPage {
+ private static final String[] SLAYERS = {"Zombie", "Spider", "Wolf", "Enderman", "Vampire", "Blaze"};
+ private static final int ROW_GAP = 28;
+
+ private final List<SlayerWidget> slayerWidgets = new ArrayList<>();
+
+ public SlayersPage(JsonObject pProfile) {
+ try {
+ for (String slayer : SLAYERS) {
+ slayerWidgets.add(new SlayerWidget(slayer, getSlayerXP(slayer.toLowerCase(), pProfile), pProfile));
+ }
+ } catch (Exception e) {
+ ProfileViewerScreen.LOGGER.error("[Skyblocker Profile Viewer] Error creating slayer widgets", e);
+ }
+ }
+
+ public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) {
+ for (int i = 0; i < slayerWidgets.size(); i++) {
+ slayerWidgets.get(i).render(context, rootX, rootY + i * ROW_GAP);
+ }
+ }
+
+ private long getSlayerXP(String slayer, JsonObject pProfile) {
+ try {
+ return pProfile.getAsJsonObject("slayer").getAsJsonObject("slayer_bosses")
+ .getAsJsonObject(slayer).get("xp").getAsLong();
+ } catch (Exception e) {
+ return 0;
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/LevelFinder.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/LevelFinder.java
new file mode 100644
index 00000000..b52fd579
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/LevelFinder.java
@@ -0,0 +1,307 @@
+package de.hysky.skyblocker.skyblock.profileviewer.utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class LevelFinder {
+ public static class LevelInfo {
+ public long xp;
+ public int level;
+ public double fill;
+
+ public LevelInfo(long xp, int level) {
+ this.xp = xp;
+ this.level = level;
+ }
+
+ public LevelInfo(int level, double fill) {
+ this.level = level;
+ this.fill = fill;
+ }
+ }
+
+ private static final long CATA_XP_PER_LEVEL = 200_000_000;
+ private static final List<LevelInfo> GENERIC_SKILL_BOUNDARIES = createGenericSkillBoundaries();
+ private static final List<LevelInfo> CATACOMBS_SKILL_BOUNDARIES = createCatacombsSkillBoundaries();
+ private static final List<LevelInfo> RUNECRAFT_SKILL_BOUNDARIES = createRunecraftSkillBoundaries();
+ private static final List<LevelInfo> SOCIAL_SKILL_BOUNDARIES = createSocialSkillBoundaries();
+
+ private static final List<LevelInfo> COMMON_PET_BOUNDARIES = createCommonPetBoundaries();
+ private static final List<LevelInfo> UNCOMMON_PET_BOUNDARIES = createUncommonPetBoundaries();
+ private static final List<LevelInfo> RARE_PET_BOUNDARIES = createRarePetBoundaries();
+ private static final List<LevelInfo> EPIC_PET_BOUNDARIES = createEpicPetBoundaries();
+ private static final List<LevelInfo> LEGENDARY_PET_BOUNDARIES = createLegendaryPetBoundaries();
+
+
+ private static final List<LevelInfo> GENERIC_SLAYER_BOUNDARIES = createGenericSlayerBoundaries();
+ private static final List<LevelInfo> VAMPIRE_SLAYER_BOUNDARIES = createVampireSlayerBoundaries();
+
+ private static List<LevelInfo> createGenericSkillBoundaries() {
+ List<LevelInfo> boundaries = new ArrayList<>();
+ long[] cumulativeXp = {
+ 0L, 50L, 175L, 375L, 675L, 1175L, 1925L, 2925L, 4425L, 6425L,
+ 9925L, 14925L, 22425L, 32425L, 47425L, 67425L, 97425L, 147425L,
+ 222425L, 322425L, 522425L, 822425L, 1222425L, 1722425L, 2322425L,
+ 3022425L, 3822425L, 4722425L, 5722425L, 6822425L, 8022425L,
+ 9322425L, 10722425L, 12222425L, 13822425L, 15522425L, 17322425L,
+ 19222425L, 21222425L, 23322425L, 25522425L, 27822425L, 30222425L,
+ 32722425L, 35322425L, 38072425L, 40972425L, 44072425L, 47472425L,
+ 51172425L, 55172425L, 59472425L, 64072425L, 68972425L, 74172425L,
+ 79672425L, 85472425L, 91572425L, 97972425L, 104672425L, 111672425L
+ };
+ for (int i = 0; i < cumulativeXp.length; i++) {
+ boundaries.add(new LevelInfo(cumulativeXp[i], i));
+ }
+ return boundaries;
+ }
+
+ private static List<LevelInfo> createCatacombsSkillBoundaries() {
+ List<LevelInfo> boundaries = new ArrayList<>();
+ long[] cumulativeXp = {
+ 0L, 50L, 125L, 235L, 395L, 625L, 955L, 1425L, 2095L, 3045L,
+ 4385L, 6275L, 8940L, 12700L, 17960L, 25340L, 35640L, 50040L,
+ 70040L, 97640L, 135640L, 188140L, 259640L, 356640L, 488640L,
+ 668640L, 911640L, 1239640L, 1684640L, 2284640L, 3084640L,
+ 4149640L, 5559640L, 7459640L, 9959640L, 13259640L, 17559640L,
+ 23159640L, 30359640L, 39359640L, 51359640L, 66359640L, 85359640L,
+ 109559640L, 139559640L, 177559640L, 225559640L, 295559640L,
+ 360559640L, 453559640L, 569809640L
+ };
+ for (int i = 0; i < cumulativeXp.length; i++) {
+ boundaries.add(new LevelInfo(cumulativeXp[i], i));
+ }
+ return boundaries;
+ }
+
+ private static List<LevelInfo> createRunecraftSkillBoundaries() {
+ List<LevelInfo> boundaries = new ArrayList<>();
+ long[] cumulativeXp = {
+ 0L, 50L, 150L, 275L, 435L, 635L, 885L, 1200L, 1600L, 2100L,
+ 2725L, 3150L, 4510L, 5760L, 7325L, 9325L, 11825L, 14950L,
+ 18950L, 23950L, 30200L, 38050L, 47850L, 60100L, 75400L, 94500L
+ };
+ for (int i = 0; i < cumulativeXp.length; i++) {
+ boundaries.add(new LevelInfo(cumulativeXp[i], i));
+ }
+ return boundaries;
+ }
+
+ private static List<LevelInfo> createSocialSkillBoundaries() {
+ List<LevelInfo> boundaries = new ArrayList<>();
+ long[] cumulativeXp = {
+ 0L, 50L, 150L, 300L, 550L, 1050L, 1800L, 2800L, 4050L, 5550L,
+ 7550L, 10050L, 13050L, 16800L, 21300L, 27300L, 35300L, 45300L,
+ 57800L, 72800L, 92800L, 117800L, 147800L, 182800L, 222800L,
+ 272800L
+ };
+ for (int i = 0; i < cumulativeXp.length; i++) {
+ boundaries.add(new LevelInfo(cumulativeXp[i], i));
+ }
+ return boundaries;
+ }
+
+ private static List<LevelInfo> createGenericSlayerBoundaries() {
+ List<LevelInfo> boundaries = new ArrayList<>();
+ long[] cumulativeXp = {0L, 5L, 15L, 200L, 1000L, 5000L,20000L,100000L,400000L,1000000L};
+ for (int i = 0; i < cumulativeXp.length; i++) {
+ boundaries.add(new LevelInfo(cumulativeXp[i], i));
+ }
+ return boundaries;
+ }
+
+ private static List<LevelInfo> createVampireSlayerBoundaries() {
+ List<LevelInfo> boundaries = new ArrayList<>();
+ long[] cumulativeXp = {0L, 20L, 75L, 240L, 840L, 2400L};
+ for (int i = 0; i < cumulativeXp.length; i++) {
+ boundaries.add(new LevelInfo(cumulativeXp[i], i));
+ }
+
+ return boundaries;
+ }
+
+ private static List<LevelInfo> createCommonPetBoundaries() {
+ List<LevelInfo> boundaries = new ArrayList<>();
+ long[] cumulativeXp = {
+ 0L, 0L, 100L, 210L, 330L, 460L, 605L, 765L, 940L, 1130L, 1340L, 1570L, 1820L, 2095L,
+ 2395L, 2725L, 3085L, 3485L, 3925L, 4415L, 4955L, 5555L, 6215L, 6945L, 7745L,
+ 8625L, 9585L, 10635L, 11785L, 13045L, 14425L, 15935L, 17585L, 19385L, 21345L,
+ 23475L, 25785L, 28285L, 30985L, 33905L, 37065L, 40485L, 44185L, 48185L, 52535L,
+ 57285L, 62485L, 68185L, 74485L, 81485L, 89285L, 97985L, 107685L, 118485L, 130485L,
+ 143785L, 158485L, 174685L, 192485L, 211985L, 233285L, 256485L, 281685L, 309085L,
+ 338885L, 371285L, 406485L, 444685L, 486085L, 530885L, 579285L, 631485L, 687685L,
+ 748085L, 812885L, 882285L, 956485L, 1035685L, 1120385L, 1211085L, 1308285L,
+ 1412485L, 1524185L, 1643885L, 1772085L, 1909285L, 2055985L, 2212685L, 2380385L,
+ 2560085L, 2752785L, 2959485L, 3181185L, 3418885L, 3673585L, 3946285L, 4237985L,
+ 4549685L, 4883385L, 5241085L, 5624785L
+ };
+
+ for (int i = 0; i < cumulativeXp.length; i++) {
+ boundaries.add(new LevelInfo(cumulativeXp[i], i));
+ }
+
+ return boundaries;
+ }
+
+ private static List<LevelInfo> createUncommonPetBoundaries() {
+ List<LevelInfo> boundaries = new ArrayList<>();
+ long[] cumulativeXp = {
+ 0L, 0L, 175L, 365L, 575L, 805L, 1055L, 1330L, 1630L, 1960L, 2320L, 2720L, 3160L,
+ 3650L, 4190L, 4790L, 5450L, 6180L, 6980L, 7860L, 8820L, 9870L, 11020L, 12280L,
+ 13660L, 15170L, 16820L, 18620L, 20580L, 22710L, 25020L, 27520L, 30220L, 33140L,
+ 36300L, 39720L, 43420L, 47420L, 51770L, 56520L, 61720L, 67420L, 73720L, 80720L,
+ 88520L, 97220L, 106920L, 117720L, 129720L, 143020L, 157720L, 173920L, 191720L,
+ 211220L, 232520L, 255720L, 280920L, 308320L, 338120L, 370520L, 405720L, 443920L,
+ 485320L, 530120L, 578520L, 630720L, 686920L, 747320L, 812120L, 881520L, 955720L,
+ 1034920L, 1119620L, 1210320L, 1307520L, 1411720L, 1523420L, 1643120L, 1771320L,
+ 1908520L, 2055220L, 2211920L, 2379620L, 2559320L, 2752020L, 2958720L, 3180420L,
+ 3418120L, 3672820L, 3945520L, 4237220L, 4548920L, 4882620L, 5240320L, 5624020L,
+ 6035720L, 6477420L, 6954120L, 7470820L, 8032520L, 8644220L
+ };
+
+ for (int i = 0; i < cumulativeXp.length; i++) {
+ boundaries.add(new LevelInfo(cumulativeXp[i], i));
+ }
+
+ return boundaries;
+ }
+
+ private static List<LevelInfo> createRarePetBoundaries() {
+ List<LevelInfo> boundaries = new ArrayList<>();
+ long[] cumulativeXp = {
+ 0L, 0L, 275L, 575L, 905L, 1265L, 1665L, 2105L, 2595L, 3135L, 3735L, 4395L, 5125L,
+ 5925L, 6805L, 7765L, 8815L, 9965L, 11225L, 12605L, 14115L, 15765L, 17565L, 19525L,
+ 21655L, 23965L, 26465L, 29165L, 32085L, 35245L, 38665L, 42365L, 46365L, 50715L,
+ 55465L, 60665L, 66365L, 72665L, 79665L, 87465L, 96165L, 105865L, 116665L, 128665L,
+ 141965L, 156665L, 172865L, 190665L, 210165L, 231465L, 254665L, 279865L, 307265L,
+ 337065L, 369465L, 404665L, 442865L, 484265L, 529065L, 577465L, 629665L, 685865L,
+ 746265L, 811065L, 880465L, 954665L, 1033865L, 1118565L, 1209265L, 1306465L,
+ 1410665L, 1522365L, 1642065L, 1770265L, 1907465L, 2054165L, 2210865L, 2378565L,
+ 2558265L, 2750965L, 2957665L, 3179365L, 3417065L, 3671765L, 3944465L, 4236165L,
+ 4547865L, 4881565L, 5239265L, 5622965L, 6034665L, 6476365L, 6953065L, 7469765L,
+ 8031465L, 8643165L, 9309865L, 10036565L, 10828265L, 11689965L, 12626665L
+ };
+
+ for (int i = 0; i < cumulativeXp.length; i++) {
+ boundaries.add(new LevelInfo(cumulativeXp[i], i));
+ }
+
+ return boundaries;
+ }
+
+ private static List<LevelInfo> createEpicPetBoundaries() {
+ List<LevelInfo> boundaries = new ArrayList<>();
+ long[] cumulativeXp = {
+ 0L, 0L, 440L, 930L, 1470L, 2070L, 2730L, 3460L, 4260L, 5140L, 6100L, 7150L, 8300L,
+ 9560L, 10940L, 12450L, 14100L, 15900L, 17860L, 19990L, 22300L, 24800L, 27500L, 30420L,
+ 33580L, 37000L, 40700L, 44700L, 49050L, 53800L, 59000L, 64700L, 71000L, 78000L, 85800L,
+ 94500L, 104200L, 115000L, 127000L, 140300L, 155000L, 171200L, 189000L, 208500L, 229800L,
+ 253000L, 278200L, 305600L, 335400L, 367800L, 403000L, 441200L, 482600L, 527400L, 575800L,
+ 628000L, 684200L, 744600L, 809400L, 878800L, 953000L, 1032200L, 1116900L, 1207600L, 1304800L,
+ 1409000L, 1520700L, 1640400L, 1768600L, 1905800L, 2052500L, 2209200L, 2376900L, 2556600L,
+ 2749300L, 2956000L, 3177700L, 3415400L, 3670100L, 3942800L, 4234500L, 4546200L, 4879900L,
+ 5237600L, 5621300L, 6033000L, 6474700L, 6951400L, 7468100L, 8029800L, 8641500L, 9308200L,
+ 10034900L, 10826600L, 11688300L, 12625000L, 13641700L, 14743400L, 15935100L, 17221800L,
+ 18608500L
+ };
+
+ for (int i = 0; i < cumulativeXp.length; i++) {
+ boundaries.add(new LevelInfo(cumulativeXp[i], i));
+ }
+
+ return boundaries;
+ }
+
+ private static List<LevelInfo> createLegendaryPetBoundaries() {
+ List<LevelInfo> boundaries = new ArrayList<>();
+ Long[] cumulativeXp = {
+ 0L, 0L, 660L, 1390L, 2190L, 3070L, 4030L, 5080L, 6230L, 7490L,
+ 8870L, 10380L, 12030L, 13830L, 15790L, 17920L, 20230L, 22730L,
+ 25430L, 28350L, 31510L, 34930L, 38630L, 42630L, 46980L, 51730L,
+ 56930L, 62630L, 68930L, 75930L, 83730L, 92430L, 102130L, 112930L,
+ 124930L, 138230L, 152930L, 169130L, 186930L, 206430L, 227730L,
+ 250930L, 276130L, 303530L, 333330L, 365730L, 400930L, 439130L,
+ 480530L, 525330L, 573730L, 625930L, 682130L, 742530L, 807330L,
+ 876730L, 950930L, 1030130L, 1114830L, 1205530L, 1302730L, 1406930L,
+ 1518630L, 1638330L, 1766530L, 1903730L, 2050430L, 2207130L, 2374830L,
+ 2554530L, 2747230L, 2953930L, 3175630L, 3413330L, 3668030L, 3940730L,
+ 4232430L, 4544130L, 4877830L, 5235530L, 5619230L, 6030930L, 6472630L,
+ 6949330L, 7466030L, 8027730L, 8639430L, 9306130L, 10032830L, 10824530L,
+ 11686230L, 12622930L, 13639630L, 14741330L, 15933030L, 17219730L, 18606430L,
+ 20103130L, 21719830L, 23466530L, 25353230L, 25353230L, 25358785L, 27245485L,
+ 29132185L, 31018885L, 32905585L, 34792285L, 36678985L, 38565685L, 40452385L,
+ 42339085L, 44225785L, 46112485L, 47999185L, 49885885L, 51772585L, 53659285L,
+ 55545985L, 57432685L, 59319385L, 61206085L, 63092785L, 64979485L, 66866185L,
+ 68752885L, 70639585L, 72526285L, 74412985L, 76299685L, 78186385L, 80073085L,
+ 81959785L, 83846485L, 85733185L, 87619885L, 89506585L, 91393285L, 93279985L,
+ 95166685L, 97053385L, 98940085L, 100826785L, 102713485L, 104600185L, 106486885L,
+ 108373585L, 110260285L, 112146985L, 114033685L, 115920385L, 117807085L, 119693785L,
+ 121580485L, 123467185L, 125353885L, 127240585L, 129127285L, 131013985L, 132900685L,
+ 134787385L, 136674085L, 138560785L, 140447485L, 142334185L, 144220885L, 146107585L,
+ 147994285L, 149880985L, 151767685L, 153654385L, 155541085L, 157427785L, 159314485L,
+ 161201185L, 163087885L, 164974585L, 166861285L, 168747985L, 170634685L, 172521385L,
+ 174408085L, 176294785L, 178181485L, 180068185L, 181954885L, 183841585L, 185728285L,
+ 187614985L, 189501685L, 191388385L, 193275085L, 195161785L, 197048485L, 198935185L,
+ 200821885L, 202708585L, 204595285L, 206481985L, 208368685L, 210255385L
+ };
+ for (int i = 0; i < cumulativeXp.length; i++) {
+ boundaries.add(new LevelInfo(cumulativeXp[i], i));
+ }
+
+ return boundaries;
+ }
+
+ public static LevelInfo getLevelInfo(String name, long xp) {
+ List<LevelInfo> boundaries = getLevelBoundaries(name, xp);
+ for (int i = boundaries.size() - 1; i >= 0 ; i--) {
+ if (xp >= boundaries.get(i).xp) {
+ double fill;
+ if (i < boundaries.getLast().level) {
+ double currentLevelXP = boundaries.get(i).xp;
+ double nextLevelXP = boundaries.get(i + 1).xp;
+ double levelXPRange = nextLevelXP - currentLevelXP;
+ double xpInCurrentLevel = xp - currentLevelXP;
+ fill = xpInCurrentLevel / levelXPRange;
+ } else {
+ fill = 1.0;
+ }
+ return new LevelInfo(boundaries.get(i).level, fill);
+ }
+ }
+ return new LevelInfo(0L, 0);
+ }
+
+
+ private static List<LevelInfo> getLevelBoundaries(String levelName, long xp) {
+ return switch (levelName) {
+ case "Vampire" -> VAMPIRE_SLAYER_BOUNDARIES;
+ case "Zombie", "Spider", "Wolf", "Enderman", "Blaze" -> GENERIC_SLAYER_BOUNDARIES;
+ case "PET_COMMON" -> COMMON_PET_BOUNDARIES;
+ case "PET_UNCOMMON" -> UNCOMMON_PET_BOUNDARIES;
+ case "PET_RARE" -> RARE_PET_BOUNDARIES;
+ case "PET_EPIC" -> EPIC_PET_BOUNDARIES;
+ case "PET_LEGENDARY", "PET_MYTHIC" -> LEGENDARY_PET_BOUNDARIES.subList(0,101);
+ case "PET_GREG" -> LEGENDARY_PET_BOUNDARIES;
+ case "Social" -> SOCIAL_SKILL_BOUNDARIES;
+ case "Runecraft" -> RUNECRAFT_SKILL_BOUNDARIES;
+ case "Catacombs" -> calculateCatacombsSkillBoundaries(xp);
+ default -> GENERIC_SKILL_BOUNDARIES;
+ };
+ }
+
+ private static List<LevelInfo> calculateCatacombsSkillBoundaries(long xp) {
+ if (xp >= CATACOMBS_SKILL_BOUNDARIES.getLast().xp) {
+ int additionalLevels = (int) ((xp - CATACOMBS_SKILL_BOUNDARIES.getLast().xp) / CATA_XP_PER_LEVEL) ;
+
+ List<LevelInfo> updatedBoundaries = new ArrayList<>(CATACOMBS_SKILL_BOUNDARIES);
+ for (int i = 0; i <= additionalLevels; i++) {
+ int level = CATACOMBS_SKILL_BOUNDARIES.getLast().level + i + 1;
+ long nextLevelXP = updatedBoundaries.getLast().xp + CATA_XP_PER_LEVEL;
+ updatedBoundaries.add(new LevelInfo(nextLevelXP, level));
+ }
+
+ return updatedBoundaries;
+ }
+
+ return CATACOMBS_SKILL_BOUNDARIES;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SkullCreator.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SkullCreator.java
new file mode 100644
index 00000000..b074952c
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SkullCreator.java
@@ -0,0 +1,27 @@
+package de.hysky.skyblocker.skyblock.profileviewer.utils;
+
+import com.mojang.authlib.properties.Property;
+import com.mojang.authlib.properties.PropertyMap;
+import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.ProfileComponent;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+
+import java.util.Optional;
+import java.util.UUID;
+
+public class SkullCreator {
+ public static ItemStack createSkull(String textureB64) {
+ ItemStack skull = new ItemStack(Items.PLAYER_HEAD);
+ try {
+ PropertyMap map = new PropertyMap();
+ map.put("textures", new Property("textures", textureB64));
+ ProfileComponent profile = new ProfileComponent(Optional.of("skull"), Optional.of(UUID.randomUUID()), map);
+ skull.set(DataComponentTypes.PROFILE, profile);
+ } catch (Exception e) {
+ ProfileViewerScreen.LOGGER.error("[Skyblocker Profile Viewer] Failed to create skull", e);
+ }
+ return skull;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SubPageSelectButton.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SubPageSelectButton.java
new file mode 100644
index 00000000..4c9dcda4
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SubPageSelectButton.java
@@ -0,0 +1,65 @@
+package de.hysky.skyblocker.skyblock.profileviewer.utils;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.ButtonTextures;
+import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
+import net.minecraft.client.gui.widget.ClickableWidget;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.LoreComponent;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.Identifier;
+
+import java.awt.*;
+
+public class SubPageSelectButton extends ClickableWidget {
+ private final ProfileViewerPage page;
+ private final int index;
+ private boolean toggled;
+
+ private static final ButtonTextures TEXTURES = new ButtonTextures(Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/button_icon_toggled.png"), Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/button_icon.png"), Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/button_icon_toggled_highlighted.png"), Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/button_icon_highlighted.png"));
+ private final ItemStack ICON;
+
+ public SubPageSelectButton(ProfileViewerPage page, int x, int y, int index, ItemStack item) {
+ super(x, y, 22, 22, item.getName());
+ this.ICON = item;
+ this.toggled = index == 0;
+ this.index = index;
+ this.page = page;
+ visible = false;
+ }
+
+ @Override
+ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
+ context.fill(this.getX(), this.getY(), this.getX() + 20, this.getY() + 20, Color.BLACK.getRGB());
+ context.drawTexture(TEXTURES.get(toggled, isHovered()), this.getX() + 1, this.getY() + 1,0, 0, 18, 18, 18, 18);
+ context.drawItem(ICON, this.getX() + 2, this.getY() + 2);
+ if ((mouseX > getX() + 1 && mouseX < getX() + 19 && mouseY > getY() + 1 && mouseY < getY() + 19)) {
+ LoreComponent lore = ICON.get(DataComponentTypes.LORE);
+ if (lore != null) context.drawTooltip(MinecraftClient.getInstance().textRenderer, lore.lines(), mouseX, mouseY + 10);
+ }
+ }
+
+ @Override
+ protected boolean clicked(double mouseX, double mouseY) {
+ return this.active && this.visible &&(mouseX > getX() + 1 && mouseX < getX() + 19 && mouseY > getY() + 1 && mouseY < getY() + 19);
+ }
+
+ @Override
+ protected void appendClickableNarrations(NarrationMessageBuilder builder) {}
+
+ public void setToggled(boolean toggled) {
+ this.toggled = toggled;
+ }
+
+ @Override
+ public void onClick(double mouseX, double mouseY) {
+ page.onNavButtonClick(this);
+ }
+
+ public int getIndex() {
+ return index;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java
index c2c952cf..21d66805 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java
@@ -97,7 +97,6 @@ public class Shortcuts {
// Party
commandArgs.put("/pa", "/p accept");
- commands.put("/pv", "/p leave");
commands.put("/pd", "/p disband");
commands.put("/rp", "/reparty");
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java
index 818056f0..b37a3883 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java
@@ -1,8 +1,11 @@
package de.hysky.skyblocker.skyblock.tabhud.util;
+import net.minecraft.enchantment.Enchantment;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
+import static net.minecraft.enchantment.Enchantments.PROTECTION;
+
/**
* Stores convenient shorthands for common ItemStack definitions
*/
@@ -10,23 +13,29 @@ public class Ico {
public static final ItemStack MAP = new ItemStack(Items.FILLED_MAP);
public static final ItemStack NTAG = new ItemStack(Items.NAME_TAG);
public static final ItemStack EMERALD = new ItemStack(Items.EMERALD);
+ public static final ItemStack MAGMA_CREAM = new ItemStack(Items.MAGMA_CREAM);
public static final ItemStack AMETHYST_SHARD = new ItemStack(Items.AMETHYST_SHARD);
public static final ItemStack CLOCK = new ItemStack(Items.CLOCK);
public static final ItemStack DIASWORD = new ItemStack(Items.DIAMOND_SWORD);
public static final ItemStack DBUSH = new ItemStack(Items.DEAD_BUSH);
public static final ItemStack VILLAGER = new ItemStack(Items.VILLAGER_SPAWN_EGG);
+ public static final ItemStack SPAWN_EGG = new ItemStack(Items.GHAST_SPAWN_EGG);
public static final ItemStack MOREGOLD = new ItemStack(Items.GOLDEN_APPLE);
public static final ItemStack COMPASS = new ItemStack(Items.COMPASS);
public static final ItemStack SUGAR = new ItemStack(Items.SUGAR);
- public static final ItemStack HOE = new ItemStack(Items.IRON_HOE);
+ public static final ItemStack IRON_HOE = new ItemStack(Items.IRON_HOE);
+ public static final ItemStack GOLDEN_HOE = new ItemStack(Items.GOLDEN_HOE);
public static final ItemStack GOLD = new ItemStack(Items.GOLD_INGOT);
+ public static final ItemStack IRON = new ItemStack(Items.IRON_INGOT);
public static final ItemStack BONE = new ItemStack(Items.BONE);
public static final ItemStack SIGN = new ItemStack(Items.OAK_SIGN);
public static final ItemStack FISH_ROD = new ItemStack(Items.FISHING_ROD);
- public static final ItemStack SWORD = new ItemStack(Items.IRON_SWORD);
+ public static final ItemStack STONE_SWORD = new ItemStack(Items.STONE_SWORD);
+ public static final ItemStack IRON_SWORD = new ItemStack(Items.IRON_SWORD);
public static final ItemStack LANTERN = new ItemStack(Items.LANTERN);
public static final ItemStack COOKIE = new ItemStack(Items.COOKIE);
public static final ItemStack POTION = new ItemStack(Items.POTION);
+ public static final ItemStack S_POTION = new ItemStack(Items.SPLASH_POTION);
public static final ItemStack BARRIER = new ItemStack(Items.BARRIER);
public static final ItemStack PLAYER = new ItemStack(Items.PLAYER_HEAD);
public static final ItemStack WATER = new ItemStack(Items.WATER_BUCKET);
@@ -37,6 +46,7 @@ public class Ico {
public static final ItemStack STRING = new ItemStack(Items.STRING);
public static final ItemStack WITHER = new ItemStack(Items.WITHER_SKELETON_SKULL);
public static final ItemStack FLESH = new ItemStack(Items.ROTTEN_FLESH);
+ public static final ItemStack MUTTON = new ItemStack(Items.MUTTON);
public static final ItemStack DRAGON = new ItemStack(Items.DRAGON_HEAD);
public static final ItemStack DIAMOND = new ItemStack(Items.DIAMOND);
public static final ItemStack ICE = new ItemStack(Items.ICE);
@@ -46,25 +56,18 @@ public class Ico {
public static final ItemStack BOOK = new ItemStack(Items.WRITABLE_BOOK);
public static final ItemStack FURNACE = new ItemStack(Items.FURNACE);
public static final ItemStack CHESTPLATE = new ItemStack(Items.IRON_CHESTPLATE);
+ public static final ItemStack L_CHESTPLATE = new ItemStack(Items.LEATHER_CHESTPLATE);
public static final ItemStack B_ROD = new ItemStack(Items.BLAZE_ROD);
+ public static final ItemStack B_POWDER = new ItemStack(Items.BLAZE_POWDER);
public static final ItemStack BOW = new ItemStack(Items.BOW);
public static final ItemStack COPPER = new ItemStack(Items.COPPER_INGOT);
public static final ItemStack NETHERITE_UPGRADE_ST = new ItemStack(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE);
public static final ItemStack COMPOSTER = new ItemStack(Items.COMPOSTER);
public static final ItemStack SAPLING = new ItemStack(Items.OAK_SAPLING);
public static final ItemStack SEEDS = new ItemStack(Items.WHEAT_SEEDS);
- public static final ItemStack WHEAT = new ItemStack(Items.WHEAT);
- public static final ItemStack CARROT = new ItemStack(Items.CARROT);
- public static final ItemStack POTATO = new ItemStack(Items.POTATO);
- public static final ItemStack SUGAR_CANE = new ItemStack(Items.SUGAR_CANE);
- public static final ItemStack NETHER_WART = new ItemStack(Items.NETHER_WART);
- public static final ItemStack MUSHROOM = new ItemStack(Items.RED_MUSHROOM);
- public static final ItemStack CACTUS = new ItemStack(Items.CACTUS);
- public static final ItemStack MELON = new ItemStack(Items.MELON);
- public static final ItemStack PUMPKIN = new ItemStack(Items.PUMPKIN);
- public static final ItemStack COCOA_BEANS = new ItemStack(Items.COCOA_BEANS);
public static final ItemStack MILESTONE = new ItemStack(Items.LODESTONE);
- public static final ItemStack PICKAXE = new ItemStack(Items.IRON_PICKAXE);
+ public static final ItemStack STONE_PICKAXE = new ItemStack(Items.STONE_PICKAXE);
+ public static final ItemStack IRON_PICKAXE = new ItemStack(Items.IRON_PICKAXE);
public static final ItemStack NETHER_STAR = new ItemStack(Items.NETHER_STAR);
public static final ItemStack HEART_OF_THE_SEA = new ItemStack(Items.HEART_OF_THE_SEA);
public static final ItemStack EXPERIENCE_BOTTLE = new ItemStack(Items.EXPERIENCE_BOTTLE);
@@ -73,4 +76,13 @@ public class Ico {
public static final ItemStack ENCHANTED_BOOK = new ItemStack(Items.ENCHANTED_BOOK);
public static final ItemStack SPIDER_EYE = new ItemStack(Items.SPIDER_EYE);
public static final ItemStack BLUE_ICE = new ItemStack(Items.BLUE_ICE);
+ public static final ItemStack JUNGLE_SAPLING = new ItemStack(Items.JUNGLE_SAPLING);
+ public static final ItemStack ENCHANTING_TABLE = new ItemStack(Items.ENCHANTING_TABLE);
+ public static final ItemStack BREWING_STAND = new ItemStack(Items.BREWING_STAND);
+ public static final ItemStack CRAFTING_TABLE = new ItemStack(Items.CRAFTING_TABLE);
+ public static final ItemStack PAINTING = new ItemStack(Items.PAINTING);
+ public static final ItemStack E_PEARL = new ItemStack(Items.ENDER_PEARL);
+ public static final ItemStack FEATHER = new ItemStack(Items.FEATHER);
+ public static final ItemStack E_CHEST = new ItemStack(Items.ENDER_CHEST);
+ public static final ItemStack MYCELIUM = new ItemStack(Items.MYCELIUM);
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDeathWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDeathWidget.java
index 9c299210..79193b51 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDeathWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDeathWidget.java
@@ -38,7 +38,7 @@ public class DungeonDeathWidget extends Widget {
this.addComponent(deaths);
}
- this.addSimpleIcoText(Ico.SWORD, "Damage Dealt:", Formatting.RED, 26);
+ this.addSimpleIcoText(Ico.IRON_SWORD, "Damage Dealt:", Formatting.RED, 26);
this.addSimpleIcoText(Ico.POTION, "Healing Done:", Formatting.RED, 27);
this.addSimpleIcoText(Ico.NTAG, "Milestone:", Formatting.YELLOW, 28);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java
index ec935faf..8f50f9ff 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java
@@ -32,10 +32,10 @@ public class ElectionWidget extends Widget {
static {
MAYOR_DATA.put("Aatrox", Ico.DIASWORD);
- MAYOR_DATA.put("Cole", Ico.PICKAXE);
+ MAYOR_DATA.put("Cole", Ico.IRON_PICKAXE);
MAYOR_DATA.put("Diana", Ico.BONE);
MAYOR_DATA.put("Diaz", Ico.GOLD);
- MAYOR_DATA.put("Finnegan", Ico.HOE);
+ MAYOR_DATA.put("Finnegan", Ico.IRON_HOE);
MAYOR_DATA.put("Foxy", Ico.SUGAR);
MAYOR_DATA.put("Paul", Ico.COMPASS);
MAYOR_DATA.put("Scorpius", Ico.MOREGOLD);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java
index 75652b33..9e1f3989 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java
@@ -82,14 +82,14 @@ public class GardenSkillsWidget extends Widget {
Text speed = Widget.simpleEntryText(67, "SPD", Formatting.WHITE);
IcoTextComponent spd = new IcoTextComponent(Ico.SUGAR, speed);
Text farmfort = Widget.simpleEntryText(68, "FFO", Formatting.GOLD);
- IcoTextComponent ffo = new IcoTextComponent(Ico.HOE, farmfort);
+ IcoTextComponent ffo = new IcoTextComponent(Ico.IRON_HOE, farmfort);
TableComponent tc = new TableComponent(2, 1, Formatting.YELLOW.getColorValue());
tc.addToCell(0, 0, spd);
tc.addToCell(1, 0, ffo);
this.addComponent(tc);
- this.addComponent(new IcoTextComponent(Ico.HOE, PlayerListMgr.textAt(70)));
+ this.addComponent(new IcoTextComponent(Ico.IRON_HOE, PlayerListMgr.textAt(70)));
ProgressComponent pc2;
Matcher milestoneMatcher = PlayerListMgr.regexAt(69, MS_PATTERN);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ReputationWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ReputationWidget.java
index 3c218fb1..34d15a28 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ReputationWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ReputationWidget.java
@@ -44,7 +44,7 @@ public class ReputationWidget extends Widget {
if (fname.equals("Mage")) {
faction = new IcoTextComponent(Ico.POTION, Text.literal(fname).formatted(Formatting.DARK_AQUA));
} else {
- faction = new IcoTextComponent(Ico.SWORD, Text.literal(fname).formatted(Formatting.RED));
+ faction = new IcoTextComponent(Ico.IRON_SWORD, Text.literal(fname).formatted(Formatting.RED));
}
}
this.addComponent(faction);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SkillsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SkillsWidget.java
index 379fbb62..c9cf61aa 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SkillsWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SkillsWidget.java
@@ -58,13 +58,13 @@ public class SkillsWidget extends Widget {
Text speed = Widget.simpleEntryText(67, "SPD", Formatting.WHITE);
IcoTextComponent spd = new IcoTextComponent(Ico.SUGAR, speed);
Text strength = Widget.simpleEntryText(68, "STR", Formatting.RED);
- IcoTextComponent str = new IcoTextComponent(Ico.SWORD, strength);
+ IcoTextComponent str = new IcoTextComponent(Ico.IRON_SWORD, strength);
Text critDmg = Widget.simpleEntryText(69, "CCH", Formatting.BLUE);
- IcoTextComponent cdg = new IcoTextComponent(Ico.SWORD, critDmg);
+ IcoTextComponent cdg = new IcoTextComponent(Ico.IRON_SWORD, critDmg);
Text critCh = Widget.simpleEntryText(70, "CDG", Formatting.BLUE);
- IcoTextComponent cch = new IcoTextComponent(Ico.SWORD, critCh);
+ IcoTextComponent cch = new IcoTextComponent(Ico.IRON_SWORD, critCh);
Text aSpeed = Widget.simpleEntryText(71, "ASP", Formatting.YELLOW);
- IcoTextComponent asp = new IcoTextComponent(Ico.HOE, aSpeed);
+ IcoTextComponent asp = new IcoTextComponent(Ico.IRON_HOE, aSpeed);
TableComponent tc = new TableComponent(2, 3, Formatting.YELLOW.getColorValue());
tc.addToCell(0, 0, spd);
diff --git a/src/main/java/de/hysky/skyblocker/utils/ApiAuthentication.java b/src/main/java/de/hysky/skyblocker/utils/ApiAuthentication.java
index fbf814ee..5f65c336 100644
--- a/src/main/java/de/hysky/skyblocker/utils/ApiAuthentication.java
+++ b/src/main/java/de/hysky/skyblocker/utils/ApiAuthentication.java
@@ -7,6 +7,8 @@ import java.util.Base64;
import java.util.Objects;
import java.util.UUID;
+import de.hysky.skyblocker.mixins.accessors.MinecraftClientAccessor;
+import net.minecraft.client.session.ProfileKeys;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -56,8 +58,9 @@ public class ApiAuthentication {
* was generated by Mojang and is tied to said player. For information about what the randomly signed data is used for and why see {@link #getRandomSignedData(PrivateKey)}
*/
private static void updateToken() {
+ ProfileKeys profileKeys = ((MinecraftClientAccessor) CLIENT).getProfileKeys();
//The fetching runs async in ProfileKeysImpl#getKeyPair
- CLIENT.getProfileKeys().fetchKeyPair().thenAcceptAsync(playerKeypairOpt -> {
+ profileKeys.fetchKeyPair().thenAcceptAsync(playerKeypairOpt -> {
if (playerKeypairOpt.isPresent()) {
PlayerKeyPair playerKeyPair = playerKeypairOpt.get();
diff --git a/src/main/java/de/hysky/skyblocker/utils/ApiUtils.java b/src/main/java/de/hysky/skyblocker/utils/ApiUtils.java
index c63af3ba..93e314a7 100644
--- a/src/main/java/de/hysky/skyblocker/utils/ApiUtils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/ApiUtils.java
@@ -1,16 +1,14 @@
package de.hysky.skyblocker.utils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import com.google.gson.JsonParser;
import com.mojang.util.UndashedUuid;
-
import de.hysky.skyblocker.utils.Http.ApiResponse;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.session.Session;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/*
* Contains only basic helpers for using Http APIs
diff --git a/src/main/java/de/hysky/skyblocker/utils/Http.java b/src/main/java/de/hysky/skyblocker/utils/Http.java
index 361bab14..1adf75d3 100644
--- a/src/main/java/de/hysky/skyblocker/utils/Http.java
+++ b/src/main/java/de/hysky/skyblocker/utils/Http.java
@@ -1,5 +1,10 @@
package de.hysky.skyblocker.utils;
+import de.hysky.skyblocker.SkyblockerMod;
+import net.minecraft.SharedConstants;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
@@ -16,12 +21,6 @@ import java.time.Duration;
import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import de.hysky.skyblocker.SkyblockerMod;
-import net.minecraft.SharedConstants;
-
/**
* @implNote All http requests are sent using HTTP 2
*/
diff --git a/src/main/java/de/hysky/skyblocker/utils/ProfileUtils.java b/src/main/java/de/hysky/skyblocker/utils/ProfileUtils.java
index a786e79f..aa7a0492 100644
--- a/src/main/java/de/hysky/skyblocker/utils/ProfileUtils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/ProfileUtils.java
@@ -4,7 +4,6 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import de.hysky.skyblocker.SkyblockerMod;
import it.unimi.dsi.fastutil.objects.ObjectLongPair;
-import net.minecraft.client.MinecraftClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -17,13 +16,24 @@ public class ProfileUtils {
private static final long HYPIXEL_API_COOLDOWN = 300000; // 5min = 300000
public static Map<String, ObjectLongPair<JsonObject>> players = new HashMap<>();
-
- public static CompletableFuture<JsonObject> updateProfile() {
- return updateProfile(MinecraftClient.getInstance().getSession().getUsername());
+ public static Map<String, ObjectLongPair<JsonObject>> profiles = new HashMap<>();
+
+ public static CompletableFuture<JsonObject> updateProfileByName(String name) {
+ return fetchFullProfile(name).thenApply(profile -> {
+ JsonObject player = profile.getAsJsonArray("profiles").asList().stream()
+ .map(JsonElement::getAsJsonObject)
+ .filter(profileObj -> profileObj.getAsJsonPrimitive("selected").getAsBoolean())
+ .findFirst()
+ .orElseThrow(() -> new IllegalStateException("No selected profile found!?"))
+ .getAsJsonObject("members").get(name).getAsJsonObject();
+
+ players.put(name, ObjectLongPair.of(player, System.currentTimeMillis()));
+ return player;
+ });
}
- public static CompletableFuture<JsonObject> updateProfile(String name) {
- ObjectLongPair<JsonObject> playerCache = players.get(name);
+ public static CompletableFuture<JsonObject> fetchFullProfile(String name) {
+ ObjectLongPair<JsonObject> playerCache = profiles.get(name);
if (playerCache != null && playerCache.rightLong() + HYPIXEL_API_COOLDOWN > System.currentTimeMillis()) {
return CompletableFuture.completedFuture(playerCache.left());
}
@@ -32,19 +42,12 @@ public class ProfileUtils {
String uuid = ApiUtils.name2Uuid(name);
try (Http.ApiResponse response = Http.sendHypixelRequest("skyblock/profiles", "?uuid=" + uuid)) {
if (!response.ok()) {
- throw new IllegalStateException("Failed to get profile uuid for players " + name + "! Response: " + response.content());
+ throw new IllegalStateException("Failed to get profile uuid for player: " + name + "! Response: " + response.content());
}
- JsonObject responseJson = SkyblockerMod.GSON.fromJson(response.content(), JsonObject.class);
-
- JsonObject player = responseJson.getAsJsonArray("profiles").asList().stream()
- .map(JsonElement::getAsJsonObject)
- .filter(profile -> profile.getAsJsonPrimitive("selected").getAsBoolean())
- .findFirst()
- .orElseThrow(() -> new IllegalStateException("No selected profile found!?"))
- .getAsJsonObject("members").get(uuid).getAsJsonObject();
+ JsonObject profile = SkyblockerMod.GSON.fromJson(response.content(), JsonObject.class);
+ profiles.put(name, ObjectLongPair.of(profile, System.currentTimeMillis()));
- players.put(name, ObjectLongPair.of(player, System.currentTimeMillis()));
- return player;
+ return profile;
} catch (Exception e) {
LOGGER.error("[Skyblocker Profile Utils] Failed to get Player Profile Data for players {}, is the API Down/Limited?", name, e);
}
diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java
index ad0643f2..dad7ec02 100644
--- a/src/main/java/de/hysky/skyblocker/utils/Utils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java
@@ -99,11 +99,11 @@ public class Utils {
}
public static boolean isInCrystalHollows() {
- return location == Location.CRYSTAL_HOLLOWS || FabricLoader.getInstance().isDevelopmentEnvironment();
+ return location == Location.CRYSTAL_HOLLOWS;
}
public static boolean isInDwarvenMines() {
- return location == Location.DWARVEN_MINES || location == Location.GLACITE_MINESHAFT || FabricLoader.getInstance().isDevelopmentEnvironment();
+ return location == Location.DWARVEN_MINES || location == Location.GLACITE_MINESHAFT;
}
public static boolean isInTheRift() {
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java
index 8a5d32be..9a1e3072 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java
@@ -12,6 +12,7 @@ import de.hysky.skyblocker.skyblock.dungeon.terminal.ColorTerminal;
import de.hysky.skyblocker.skyblock.dungeon.terminal.LightsOnTerminal;
import de.hysky.skyblocker.skyblock.dungeon.terminal.OrderTerminal;
import de.hysky.skyblocker.skyblock.dungeon.terminal.StartsWithTerminal;
+import de.hysky.skyblocker.skyblock.dwarven.CommissionHighlight;
import de.hysky.skyblocker.skyblock.experiment.ChronomatronSolver;
import de.hysky.skyblocker.skyblock.experiment.SuperpairsSolver;
import de.hysky.skyblocker.skyblock.experiment.UltrasequencerSolver;
@@ -51,6 +52,7 @@ public class ContainerSolverManager {
new StartsWithTerminal(),
new LightsOnTerminal(),
new CroesusHelper(),
+ new CommissionHighlight(),
new CroesusProfit(),
new ChronomatronSolver(),
new SuperpairsSolver(),
diff --git a/src/main/java/de/hysky/skyblocker/utils/scheduler/Scheduler.java b/src/main/java/de/hysky/skyblocker/utils/scheduler/Scheduler.java
index 2f5375fe..547adc5c 100644
--- a/src/main/java/de/hysky/skyblocker/utils/scheduler/Scheduler.java
+++ b/src/main/java/de/hysky/skyblocker/utils/scheduler/Scheduler.java
@@ -2,6 +2,7 @@ package de.hysky.skyblocker.utils.scheduler;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.mojang.brigadier.Command;
+import com.mojang.brigadier.context.CommandContext;
import it.unimi.dsi.fastutil.ints.AbstractInt2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
@@ -14,6 +15,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.function.Function;
import java.util.function.Supplier;
/**
@@ -73,18 +75,56 @@ public class Scheduler {
}
}
+ /**
+ * Creates a command that queues a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed.
+ *
+ * @param screenFactory the factory of the screen to open
+ * @return the command
+ */
+ public static Command<FabricClientCommandSource> queueOpenScreenFactoryCommand(Function<CommandContext<FabricClientCommandSource>, Screen> screenFactory) {
+ return context -> queueOpenScreen(screenFactory.apply(context));
+ }
+
+ /**
+ * Creates a command that queues a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed.
+ *
+ * @param screenSupplier the supplier of the screen to open
+ * @return the command
+ */
public static Command<FabricClientCommandSource> queueOpenScreenCommand(Supplier<Screen> screenSupplier) {
- return context -> INSTANCE.queueOpenScreen(screenSupplier);
+ return context -> queueOpenScreen(screenSupplier.get());
+ }
+
+ /**
+ * Creates a command that queues a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed.
+ *
+ * @param screen the screen to open
+ * @return the command
+ */
+ public static Command<FabricClientCommandSource> queueOpenScreenCommand(Screen screen) {
+ return context -> queueOpenScreen(screen);
}
/**
* Schedules a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed.
*
+ * @deprecated Use {@link #queueOpenScreen(Screen)} instead
* @param screenSupplier the supplier of the screen to open
* @see #queueOpenScreenCommand(Supplier)
*/
- public int queueOpenScreen(Supplier<Screen> screenSupplier) {
- MinecraftClient.getInstance().send(() -> MinecraftClient.getInstance().setScreen(screenSupplier.get()));
+ @Deprecated(forRemoval = true)
+ public static int queueOpenScreen(Supplier<Screen> screenSupplier) {
+ return queueOpenScreen(screenSupplier.get());
+ }
+
+ /**
+ * Schedules a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed.
+ *
+ * @param screen the screen to open
+ * @see #queueOpenScreenFactoryCommand(Function)
+ */
+ public static int queueOpenScreen(Screen screen) {
+ MinecraftClient.getInstance().send(() -> MinecraftClient.getInstance().setScreen(screen));
return Command.SINGLE_SUCCESS;
}