diff options
Diffstat (limited to 'src/main')
77 files changed, 2845 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; } diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index cd8fff39..8fa87e3b 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -19,6 +19,8 @@ "text.skyblocker.modrinth": "Modrinth", "text.skyblocker.discord": "Discord", + "skyblocker.skyblockerScreen": "Skyblocker Main Screen", + "skyblocker.config.title": "Skyblocker Settings", "skyblocker.config.crimsonIsle": "Crimson Isle", @@ -428,6 +430,8 @@ "skyblocker.config.mining.crystalHollows": "Crystal Hollows", "skyblocker.config.mining.crystalHollows.metalDetectorHelper": "Metal Detector Helper", "skyblocker.config.mining.crystalHollows.metalDetectorHelper.@Tooltip": "Helper for the metal detector puzzle in the Mines of Divan.", + "skyblocker.config.mining.crystalHollows.nucleusWaypoints": "Nucleus Waypoints", + "skyblocker.config.mining.crystalHollows.nucleusWaypoints.@Tooltip": "Show waypoints to the Nucleus in the Crystal Hollows.", "skyblocker.config.mining.crystalsHud": "Crystal Hollows Map", "skyblocker.config.mining.crystalsHud.enabled": "Enabled", @@ -460,6 +464,8 @@ "skyblocker.config.mining.enableDrillFuel": "Enable Drill Fuel", + "skyblocker.config.mining.commissionHighlight": "Highlights Completed Commissions", + "skyblocker.config.mining.glacite": "Glacite Tunnels", "skyblocker.config.mining.glacite.coldOverlay": "Cold Overlay", "skyblocker.config.mining.glacite.coldOverlay@Tooltip": "Shows a frosty overlay in the Glacite mines that gets stronger as you get colder.", @@ -811,8 +817,11 @@ "skyblocker.tips.enabled": "§aEnabled Tips.", "skyblocker.tips.disabled": "§cDisabled Tips.", + "skyblocker.tips.previous": "Previous Tip", + "skyblocker.tips.next": "Next Tip", "skyblocker.tips.clickEnable": "§a[Click to Enable Tips]", "skyblocker.tips.clickDisable": "§c[Click to Disable Tips]", + "skyblocker.tips.clickPreviousTip": "§b[Click for Previous Tip]", "skyblocker.tips.clickNextTip": "§a[Click for Next Tip]", "skyblocker.tips.tip": "§aTip: %s\n", "skyblocker.tips.customItemNames": "Customize the names of your items with /skyblocker custom renameItem", @@ -829,12 +838,31 @@ "skyblocker.tips.modMenuUpdate": "ModMenu will let you know if there's an update available for Skyblocker for your game version.", "skyblocker.tips.issues": "Submit bug reports and feature requests to https://github.com/SkyblockerMod/Skyblocker.", "skyblocker.tips.beta": "We often have beta versions available from GitHub Actions that contain new and experimental features.", + "skyblocker.tips.contribute": "We welcome contributions to the mod! See https://github.com/SkyblockerMod/Skyblocker/wiki/contribute for more info.", "skyblocker.tips.discord": "Join our discord at https://discord.gg/aNNJHQykck to keep up with the latest news about Skyblocker!", "skyblocker.tips.flameOverlay": "Find that the flame overlay takes up too much screen space? Check out the config to make it smaller", "skyblocker.tips.wikiLookup": "Press F4 while hovering over an item to open its wiki page in your web browser.", "skyblocker.tips.protectItem": "Prevent accidentally dropping your items with /skyblocker protectItem.", "skyblocker.tips.fairySoulsEnigmaSoulsRelics": "Don't know where to find Fairy Souls, Enigma Souls, or Relics? Enable the helpers to aid your exploration, they'll remember which souls you've already found.", "skyblocker.tips.quickNav": "You can customize the QuickNav buttons in the config.", + "skyblocker.tips.waypoints": "You can import (including from skytils), add, remove, and toggle waypoints on any island with /skyblocker waypoints.", + "skyblocker.tips.orderedWaypoints": "You can import (including from coleweight), add, remove, and toggle ordered waypoints with /skyblocker waypoints ordered.", + "skyblocker.tips.visitorHelper": "Click on the item name in the visitor helper to buy from the bazaar or click on [Copy Amount] to copy the amount to your clipboard.", + "skyblocker.tips.slotText": "Slot text shows you the attribute shard info, catacombs level, collection level, enchantment book level, minion level, pet level, potion level, prehistoric egg blocks walked, rancher's boots speed cap, skill level, skyblock level in the slot.", + "skyblocker.tips.profileViewer": "You can view other player's profiles with /pv.", + "skyblocker.tips.configSearch": "You can search the entire config using the search bar on the bottom right of the config screen.", + "skyblocker.tips.compactDamage": "Customize your damage value with Compact Damage in the config.", + "skyblocker.tips.skyblockerScreen": "You can access the Skyblocker screen to open the config or checkout helpful links for the mod with /skyblocker.", + "skyblocker.tips.tipsClick": "Click on a tip chat message to run its suggestion!", + "skyblocker.tips.eventNotifications": "Check out the customizable event notifications in the config.", + "skyblocker.tips.signCalculator": "Type an math expression in a sign to have the mod calculate it for you.", + "skyblocker.tips.calculateCommand": "Enter an expression after /skyblocker calculate to have the mod calculate it for you.", + "skyblocker.tips.fancierBars": "Customize the look of your health, mana, defense, and experience bars with /skyblocker bars. You can snap and resize bars too!", + "skyblocker.tips.crystalWaypointsShare": "Share Crystal Hollows Waypoints with /skyblocker crystalWaypoints share.", + "skyblocker.tips.gardenMouseLock": "Lock your mouse while farming in the Skyblocker Garden config.", + "skyblocker.tips.newYearCakesHelper": "Open your New Year Cake Bag and Skyblocker will remember them and highlight duplicate cakes red and missing cakes green.", + "skyblocker.tips.accessoryHelper": "Open your accessory bag and Skyblocker will remember them. The accessory helper will tell you what accessories you have and what accessories are missing.", + "skyblocker.tips.fancyAuctionHouseCheapHighlight": "Cheap BINs are highlighted in green in the Fancy Auction House.", "skyblocker.partyFinder.tabs.partyFinder": "Party Finder", "skyblocker.partyFinder.tabs.searchSettings": "Search Filters", diff --git a/src/main/resources/assets/skyblocker/lang/lol_us.json b/src/main/resources/assets/skyblocker/lang/lol_us.json new file mode 100644 index 00000000..4cda6799 --- /dev/null +++ b/src/main/resources/assets/skyblocker/lang/lol_us.json @@ -0,0 +1,3 @@ +{ + "key.categories.skyblocker": "Skibidiblocker" +} diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/base_plate.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/base_plate.png Binary files differnew file mode 100644 index 00000000..0edf5705 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/base_plate.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/blaze.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/blaze.png Binary files differnew file mode 100644 index 00000000..ed168ddf --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/blaze.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon.png Binary files differnew file mode 100644 index 00000000..d29969be --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_highlighted.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_highlighted.png Binary files differnew file mode 100644 index 00000000..11340a55 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_highlighted.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_toggled.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_toggled.png Binary files differnew file mode 100644 index 00000000..109cd859 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_toggled.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_toggled_highlighted.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_toggled_highlighted.png Binary files differnew file mode 100644 index 00000000..581cdefe --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/button_icon_toggled_highlighted.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/dungeons_body.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/dungeons_body.png Binary files differnew file mode 100644 index 00000000..379557e4 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/dungeons_body.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/dungeons_header.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/dungeons_header.png Binary files differnew file mode 100644 index 00000000..384dc0e3 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/dungeons_header.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/enderman.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/enderman.png Binary files differnew file mode 100644 index 00000000..84650c7f --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/enderman.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/icon_data_widget.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/icon_data_widget.png Binary files differnew file mode 100644 index 00000000..c28aeed4 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/icon_data_widget.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/run_icon.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/run_icon.png Binary files differnew file mode 100644 index 00000000..d83fad70 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/run_icon.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/spider.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/spider.png Binary files differnew file mode 100644 index 00000000..a5daa3d6 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/spider.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/vampire.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/vampire.png Binary files differnew file mode 100644 index 00000000..efbe985d --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/vampire.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/wolf.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/wolf.png Binary files differnew file mode 100644 index 00000000..f7bec9a4 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/wolf.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/zombie.png b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/zombie.png Binary files differnew file mode 100644 index 00000000..37ea069f --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/profile_viewer/zombie.png diff --git a/src/main/resources/skyblocker.mixins.json b/src/main/resources/skyblocker.mixins.json index fa23abb7..685758f7 100644 --- a/src/main/resources/skyblocker.mixins.json +++ b/src/main/resources/skyblocker.mixins.json @@ -47,6 +47,7 @@ "accessors.FrustumInvoker", "accessors.HandledScreenAccessor", "accessors.MessageHandlerAccessor", + "accessors.MinecraftClientAccessor", "accessors.PlayerListHudAccessor", "accessors.RecipeBookWidgetAccessor", "accessors.ScreenAccessor", |