diff options
Diffstat (limited to 'src/main/java')
46 files changed, 1067 insertions, 377 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index f1eb4321..8dd1419d 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java @@ -14,6 +14,8 @@ import de.hysky.skyblocker.skyblock.chocolatefactory.TimeTowerReminder; import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager; import de.hysky.skyblocker.skyblock.crimson.kuudra.Kuudra; import de.hysky.skyblocker.skyblock.dungeon.*; +import de.hysky.skyblocker.skyblock.dungeon.device.LightsOn; +import de.hysky.skyblocker.skyblock.dungeon.device.SimonSays; import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen; import de.hysky.skyblocker.skyblock.dungeon.puzzle.*; import de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder.Boulder; @@ -151,6 +153,8 @@ public class SkyblockerMod implements ClientModInitializer { Silverfish.init(); IceFill.init(); DungeonScore.init(); + SimonSays.init(); + LightsOn.init(); PartyFinderScreen.initClass(); ChestValue.init(); FireFreezeStaffTimer.init(); diff --git a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java index 28ace441..017e9186 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java @@ -269,6 +269,28 @@ public class DungeonsCategory { .build()) .build()) + // Devices (F7/M7) + .group(OptionGroup.createBuilder() + .name(Text.translatable("skyblocker.config.dungeons.devices")) + .collapsed(true) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.dungeons.devices.solveSimonSays")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.dungeons.devices.solveSimonSays.@Tooltip"))) + .binding(defaults.dungeons.devices.solveSimonSays, + () -> config.dungeons.devices.solveSimonSays, + newValue -> config.dungeons.devices.solveSimonSays = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.dungeons.devices.solveLightsOn")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.dungeons.devices.solveLightsOn.@Tooltip"))) + .binding(defaults.dungeons.devices.solveLightsOn, + () -> config.dungeons.devices.solveLightsOn, + newValue -> config.dungeons.devices.solveLightsOn = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .build()) + // Dungeon Secret Waypoints .group(OptionGroup.createBuilder() .name(Text.translatable("skyblocker.config.dungeons.secretWaypoints")) diff --git a/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java index 56e2ed4d..ec2c561c 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java @@ -197,6 +197,14 @@ public class HelperCategory { newValue -> config.helpers.chocolateFactory.enableTimeTowerReminder = newValue) .controller(ConfigUtils::createBooleanController) .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.helpers.chocolateFactory.straySound")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.helpers.chocolateFactory.straySound.@Tooltip"))) + .binding(defaults.helpers.chocolateFactory.straySound, + () -> config.helpers.chocolateFactory.straySound, + newValue -> config.helpers.chocolateFactory.straySound = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) .build()) //Bazaar diff --git a/src/main/java/de/hysky/skyblocker/config/configs/DungeonsConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/DungeonsConfig.java index 7b394b53..1a0cad9d 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/DungeonsConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/DungeonsConfig.java @@ -42,6 +42,9 @@ public class DungeonsConfig { public Terminals terminals = new Terminals(); @SerialEntry + public Devices devices = new Devices(); + + @SerialEntry public SecretWaypoints secretWaypoints = new SecretWaypoints(); @SerialEntry @@ -135,6 +138,14 @@ public class DungeonsConfig { public boolean blockIncorrectClicks = false; } + public static class Devices { + @SerialEntry + public boolean solveSimonSays = true; + + @SerialEntry + public boolean solveLightsOn = true; + } + public static class SecretWaypoints { @SerialEntry public boolean enableRoomMatching = true; diff --git a/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java index 6ddb1a74..e009f680 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java @@ -93,6 +93,9 @@ public class HelperConfig { @SerialEntry public boolean enableTimeTowerReminder = true; + + @SerialEntry + public boolean straySound = true; } public static class Bazaar { diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java index 6c10e5d2..a2d7887b 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java @@ -1,7 +1,7 @@ package de.hysky.skyblocker.mixins; - import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager; +import de.hysky.skyblocker.skyblock.dungeon.device.SimonSays; import de.hysky.skyblocker.utils.Utils; import net.minecraft.block.BlockState; import net.minecraft.client.world.ClientWorld; @@ -11,13 +11,21 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import com.llamalad7.mixinextras.sugar.Local; + @Mixin(ClientWorld.class) public class ClientWorldMixin { + /** + * @implNote The {@code pos} can be mutable when this is called by chunk delta updates, so if you want to copy it into memory + * (e.g. store it in a field/list/map) make sure to duplicate it via {@link BlockPos#toImmutable()}. + */ @Inject(method = "handleBlockUpdate", at = @At("RETURN")) - private void skyblocker$handleBlockUpdate(BlockPos pos, BlockState state, int flags, CallbackInfo ci) { + private void skyblocker$handleBlockUpdate(CallbackInfo ci, @Local(argsOnly = true) BlockPos pos, @Local(argsOnly = true) BlockState state) { if (Utils.isInCrimson()) { DojoManager.onBlockUpdate(pos.toImmutable(), state); } + + SimonSays.onBlockUpdate(pos, state); } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java index f2e3e907..709b8697 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java @@ -247,10 +247,21 @@ public abstract class HandledScreenMixin<T extends ScreenHandler> extends Screen return; } // Prevent salvaging + // TODO in future maybe also block clicking the salvage button if a protected item manages to get into the menu if (title.equals("Salvage Items") && ItemProtection.isItemProtected(stack)) { ci.cancel(); return; } + // Prevent Trading + if (title.startsWith("You ") && ItemProtection.isItemProtected(stack)) { //Terrible way to detect the trade menu lol + ci.cancel(); + return; + } + // Prevent Auctioning + if ((title.equals("Create BIN Auction") || title.equals("Create Auction")) && ItemProtection.isItemProtected(stack)) { + ci.cancel(); + return; + } switch (this.handler) { case GenericContainerScreenHandler genericContainerScreenHandler when genericContainerScreenHandler.getRows() == 6 -> { diff --git a/src/main/java/de/hysky/skyblocker/mixins/PlayerSkinTextureMixin.java b/src/main/java/de/hysky/skyblocker/mixins/PlayerSkinTextureMixin.java index 828d32e3..9b9691c5 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/PlayerSkinTextureMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/PlayerSkinTextureMixin.java @@ -1,5 +1,8 @@ package de.hysky.skyblocker.mixins; +import java.awt.Color; +import java.util.Set; + import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -9,32 +12,62 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.item.PlayerHeadHashCache; import de.hysky.skyblocker.utils.Utils; import net.minecraft.client.texture.NativeImage; import net.minecraft.client.texture.PlayerSkinTexture; +import net.minecraft.util.math.ColorHelper; @Mixin(PlayerSkinTexture.class) public class PlayerSkinTextureMixin { + @Unique + private static final Set<String> STRIP_DE_FACTO_TRANSPARENT_PIXELS = Set.of( + "4f3b91b6aa7124f30ed4ad1b2bb012a82985a33640555e18e792f96af8f58ec6", /*Titanium Necklace*/ + "49821410631186c6f3fbbae5f0ef5b947f475eb32027a8aad0a456512547c209", /*Titanium Cloak*/ + "4162303bcdd770aebe7fd19fa26371390a7515140358548084361b5056cdc4e6" /*Titanium Belt*/); + @Unique + private static final float BRIGHTNESS_THRESHOLD = 0.1f; + @Shadow @Final private String url; - @Unique - private boolean isSkyblockSkinTexture; - @Inject(method = "remapTexture", at = @At("HEAD")) - private void skyblocker$determineSkinSource(CallbackInfoReturnable<NativeImage> cir) { - if (Utils.isOnSkyblock()) { - int skinHash = PlayerHeadHashCache.getSkinHash(this.url).hashCode(); - this.isSkyblockSkinTexture = PlayerHeadHashCache.contains(skinHash); + private void skyblocker$determineSkinSource(NativeImage image, CallbackInfoReturnable<NativeImage> cir, @Share("isSkyblockSkinTexture") LocalBooleanRef isSkyblockSkinTexture) { + if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().uiAndVisuals.dontStripSkinAlphaValues) { + String skinTextureHash = PlayerHeadHashCache.getSkinHash(this.url); + int skinHash = skinTextureHash.hashCode(); + isSkyblockSkinTexture.set(PlayerHeadHashCache.contains(skinHash)); + + //Hypixel had the grand idea of using black pixels in place of actual transparent pixels on the titanium equipment so here we go! + if (STRIP_DE_FACTO_TRANSPARENT_PIXELS.contains(skinTextureHash)) { + stripDeFactoTransparentPixels(image); + } } } @WrapWithCondition(method = "remapTexture", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/texture/PlayerSkinTexture;stripAlpha(Lnet/minecraft/client/texture/NativeImage;IIII)V")) - private boolean skyblocker$dontStripAlphaValues(NativeImage image, int x1, int y1, int x2, int y2) { - return !(SkyblockerConfigManager.get().uiAndVisuals.dontStripSkinAlphaValues && this.isSkyblockSkinTexture); + private boolean skyblocker$dontStripAlphaValues(NativeImage image, int x1, int y1, int x2, int y2, @Share("isSkyblockSkinTexture") LocalBooleanRef isSkyblockSkinTexture) { + return !isSkyblockSkinTexture.get(); + } + + @Unique + private static void stripDeFactoTransparentPixels(NativeImage image) { + int height = image.getHeight(); + int width = image.getWidth(); + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int color = image.getColor(x, y); + float[] hsb = Color.RGBtoHSB(ColorHelper.Abgr.getRed(color), ColorHelper.Abgr.getGreen(color), ColorHelper.Abgr.getBlue(color), null); + + //Work around "fake" transparent pixels - Thanks Hypixel I totally appreciate this! + if (hsb[2] <= BRIGHTNESS_THRESHOLD) image.setColor(x, y, ColorHelper.Abgr.withAlpha(0x00, color & 0x00FFFFFF)); + } + } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java b/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java index 8d0406cb..8ddcd60e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java @@ -133,13 +133,14 @@ 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, Optional<String> item) { + public record PetInfo(String type, double exp, String tier, Optional<String> uuid, Optional<String> item, Optional<String> skin) { 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("heldItem").forGetter(PetInfo::item)) + Codec.STRING.optionalFieldOf("heldItem").forGetter(PetInfo::item), + Codec.STRING.optionalFieldOf("skin").forGetter(PetInfo::skin)) .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/chocolatefactory/ChocolateFactorySolver.java b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java index f984d751..db81382c 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java @@ -9,9 +9,13 @@ import de.hysky.skyblocker.utils.render.gui.ColorHighlight; import de.hysky.skyblocker.utils.render.gui.ContainerSolver; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.sound.PositionedSoundInstance; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.screen.slot.Slot; +import net.minecraft.sound.SoundEvents; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -49,6 +53,7 @@ public class ChocolateFactorySolver extends ContainerSolver { private static boolean isTimeTowerActive = false; private static int bestUpgrade = -1; private static int bestAffordableUpgrade = -1; + private static StraySound ding = StraySound.NONE; private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#,###.#", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); @Override @@ -65,6 +70,7 @@ public class ChocolateFactorySolver extends ContainerSolver { isTimeTowerActive = false; bestUpgrade = -1; bestAffordableUpgrade = -1; + ding = StraySound.NONE; } //Slots, for ease of maintenance rather than using magic numbers everywhere. @@ -78,8 +84,20 @@ public class ChocolateFactorySolver extends ContainerSolver { private static final byte STRAY_RABBIT_START = 0; private static final byte STRAY_RABBIT_END = 26; + private static int dingTick = 0; + public ChocolateFactorySolver() { super("^Chocolate Factory$"); //There are multiple screens that fit the pattern `^Chocolate Factory`, so the $ is required + ClientTickEvents.START_CLIENT_TICK.register(ChocolateFactorySolver::onTick); + } + + private static void onTick(MinecraftClient client) { + if (ding != StraySound.NONE) { + dingTick = (++dingTick) % (ding == StraySound.NORMAL ? 5 : 3); + if (dingTick == 0) { + client.getSoundManager().play(PositionedSoundInstance.master(ding == StraySound.NORMAL ? SoundEvents.BLOCK_NOTE_BLOCK_PLING.value() : SoundEvents.BLOCK_NOTE_BLOCK_HARP.value(), 1.f, 1.f)); + } + } } @Override @@ -239,12 +257,14 @@ public class ChocolateFactorySolver extends ContainerSolver { } private static List<ColorHighlight> getStrayRabbitHighlight(Int2ObjectMap<ItemStack> slots) { + ding = StraySound.NONE; final List<ColorHighlight> highlights = new ArrayList<>(); for (byte i = STRAY_RABBIT_START; i <= STRAY_RABBIT_END; i++) { ItemStack item = slots.get(i); if (!item.isOf(Items.PLAYER_HEAD)) continue; String name = item.getName().getString(); if (name.equals("CLICK ME!") || name.startsWith("Golden Rabbit - ")) { + if (SkyblockerConfigManager.get().helpers.chocolateFactory.straySound) ding = name.startsWith("Golden") ? StraySound.GOLDEN : StraySound.NORMAL; highlights.add(ColorHighlight.green(i)); } } @@ -253,6 +273,12 @@ public class ChocolateFactorySolver extends ContainerSolver { private record Rabbit(double cpsIncrease, long cost, int slot) { } + private enum StraySound { + NONE, + NORMAL, + GOLDEN + } + public static final class Tooltip extends TooltipAdder { public Tooltip() { super("^Chocolate Factory$", 0); //The priority doesn't really matter here as this is the only tooltip adder for the Chocolate Factory. diff --git a/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/ControlTestHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/ControlTestHelper.java index 2f616a1e..f63d2fa2 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/ControlTestHelper.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/ControlTestHelper.java @@ -67,7 +67,7 @@ public class ControlTestHelper { float tickDelta = context.tickCounter().getTickDelta(false); //how long until net update double updatePercent = (double) (System.currentTimeMillis() - lastUpdate) / 150; - Vec3d aimPos = correctWitherSkeleton.getEyePos().add(pingOffset.multiply(updatePercent)).add(lastPingOffset.multiply(1 - updatePercent)); + Vec3d aimPos = correctWitherSkeleton.getCameraPosVec(tickDelta).add(pingOffset.multiply(updatePercent)).add(lastPingOffset.multiply(1 - updatePercent)); Box targetBox = new Box(aimPos.add(-0.5, -0.5, -0.5), aimPos.add(0.5, 0.5, 0.5)); boolean playerLookingAtBox = targetBox.raycast(CLIENT.player.getCameraPosVec(tickDelta), CLIENT.player.getCameraPosVec(tickDelta).add(CLIENT.player.getRotationVec(tickDelta).multiply(30))).isPresent(); float[] boxColor = playerLookingAtBox ? Color.GREEN.getColorComponents(new float[]{0, 0, 0}) : Color.LIGHT_GRAY.getColorComponents(new float[]{0, 0, 0}); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/Reparty.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/Reparty.java index 6165ac6a..80753c1d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/Reparty.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/Reparty.java @@ -1,94 +1,112 @@ package de.hysky.skyblocker.skyblock.dungeon; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.chat.ChatFilterResult; import de.hysky.skyblocker.utils.chat.ChatPatternListener; import de.hysky.skyblocker.utils.scheduler.MessageScheduler; import de.hysky.skyblocker.utils.scheduler.Scheduler; +import net.azureaaron.hmapi.data.party.PartyRole; +import net.azureaaron.hmapi.events.HypixelPacketEvents; +import net.azureaaron.hmapi.network.HypixelNetworking; +import net.azureaaron.hmapi.network.packet.s2c.ErrorS2CPacket; +import net.azureaaron.hmapi.network.packet.s2c.HypixelS2CPacket; +import net.azureaaron.hmapi.network.packet.v2.s2c.PartyInfoS2CPacket; import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.text.Text; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; import java.util.regex.Matcher; -import java.util.regex.Pattern; + +import org.slf4j.Logger; + +import com.mojang.brigadier.Command; +import com.mojang.logging.LogUtils; public class Reparty extends ChatPatternListener { - private static final MinecraftClient client = MinecraftClient.getInstance(); - public static final Pattern PLAYER = Pattern.compile(" ([a-zA-Z0-9_]{2,16}) ●"); - private static final int BASE_DELAY = 10; - - private String[] players; - private int playersSoFar; - private boolean repartying; - private String partyLeader; - - public Reparty() { - super("^(?:You are not currently in a party\\." + - "|Party (?:Membe|Moderato)rs(?: \\(([0-9]+)\\)|:( .*))" + - "|([\\[A-z+\\]]* )?(?<disband>.*) has disbanded .*" + - "|.*\n([\\[A-z+\\]]* )?(?<invite>.*) has invited you to join their party!" + - "\nYou have 60 seconds to accept. Click here to join!\n.*)$"); - - this.repartying = false; - ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("rp").executes(context -> { - if (!Utils.isOnSkyblock() || this.repartying || client.player == null) return 0; - this.repartying = true; - MessageScheduler.INSTANCE.sendMessageAfterCooldown("/p list"); - return 0; - }))); - } - - @Override - public ChatFilterResult state() { - return (SkyblockerConfigManager.get().general.acceptReparty || this.repartying) ? ChatFilterResult.FILTER : ChatFilterResult.PASS; - } - - @Override - public boolean onMatch(Text message, Matcher matcher) { - if (matcher.group(1) != null && repartying) { - this.playersSoFar = 0; - this.players = new String[Integer.parseInt(matcher.group(1)) - 1]; - } else if (matcher.group(2) != null && repartying) { - Matcher m = PLAYER.matcher(matcher.group(2)); - while (m.find()) { - this.players[playersSoFar++] = m.group(1); - } - } else if (matcher.group("disband") != null && !matcher.group("disband").equals(client.getSession().getUsername())) { - partyLeader = matcher.group("disband"); - Scheduler.INSTANCE.schedule(() -> partyLeader = null, 61); - return false; - } else if (matcher.group("invite") != null && matcher.group("invite").equals(partyLeader)) { - String command = "/party accept " + partyLeader; - sendCommand(command, 0); - return false; - } else { - this.repartying = false; - return false; - } - if (this.playersSoFar == this.players.length) { - reparty(); - } - return false; - } - - private void reparty() { - ClientPlayerEntity playerEntity = client.player; - if (playerEntity == null) { - this.repartying = false; - return; - } - sendCommand("/p disband", 1); - for (int i = 0; i < this.players.length; ++i) { - String command = "/p invite " + this.players[i]; - sendCommand(command, i + 2); - } - Scheduler.INSTANCE.schedule(() -> this.repartying = false, this.players.length + 2); - } - - private void sendCommand(String command, int delay) { - MessageScheduler.INSTANCE.queueMessage(command, delay * BASE_DELAY); - } + private static final Logger LOGGER = LogUtils.getLogger(); + private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); + private static final int BASE_DELAY = 10; + + private boolean repartying; + private String partyLeader; + + public Reparty() { + super("^(?:([\\[A-z+\\]]* )?(?<disband>.*) has disbanded .*" + + "|.*\n([\\[A-z+\\]]* )?(?<invite>.*) has invited you to join their party!" + + "\nYou have 60 seconds to accept. Click here to join!\n.*)$"); + + this.repartying = false; + HypixelPacketEvents.PARTY_INFO.register(this::onPacket); + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("rp").executes(context -> { + if (!Utils.isOnSkyblock() || this.repartying || CLIENT.player == null) return 0; + + this.repartying = true; + HypixelNetworking.sendPartyInfoC2SPacket(2); + + return Command.SINGLE_SUCCESS; + }))); + } + + private void onPacket(HypixelS2CPacket packet) { + switch (packet) { + case PartyInfoS2CPacket(var inParty, var members) when this.repartying -> { + UUID ourUuid = Objects.requireNonNull(CLIENT.getSession().getUuidOrNull()); + + if (inParty && members.get(ourUuid) == PartyRole.LEADER) { + sendCommand("/p disband", 1); + int count = 0; + + for (Map.Entry<UUID, PartyRole> entry : members.entrySet()) { + UUID uuid = entry.getKey(); + PartyRole role = entry.getValue(); + + //Don't invite ourself + if (role != PartyRole.LEADER) sendCommand("/p " + uuid.toString(), ++count + 2); + } + + Scheduler.INSTANCE.schedule(() -> this.repartying = false, count * BASE_DELAY); + } else { + CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.reparty.notInPartyOrNotLeader"))); + this.repartying = false; + } + } + + case ErrorS2CPacket(var id, var error) when id.equals(PartyInfoS2CPacket.ID) && this.repartying -> { + CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.reparty.error"))); + LOGGER.error("[Skyblocker Reparty] The party info packet returned an unexpected error! {}", error); + + this.repartying = false; + } + + default -> {} //Do nothing + } + } + + @Override + public ChatFilterResult state() { + return SkyblockerConfigManager.get().general.acceptReparty ? ChatFilterResult.FILTER : ChatFilterResult.PASS; + } + + @Override + public boolean onMatch(Text message, Matcher matcher) { + if (matcher.group("disband") != null && !matcher.group("disband").equals(CLIENT.getSession().getUsername())) { + partyLeader = matcher.group("disband"); + Scheduler.INSTANCE.schedule(() -> partyLeader = null, 61); + } else if (matcher.group("invite") != null && matcher.group("invite").equals(partyLeader)) { + String command = "/party accept " + partyLeader; + sendCommand(command, 0); + } + + return false; + } + + private void sendCommand(String command, int delay) { + MessageScheduler.INSTANCE.queueMessage(command, delay * BASE_DELAY); + } }
\ No newline at end of file diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/LightsOn.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/LightsOn.java new file mode 100644 index 00000000..555a8e4b --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/LightsOn.java @@ -0,0 +1,46 @@ +package de.hysky.skyblocker.skyblock.dungeon.device; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.dungeon.DungeonBoss; +import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; +import de.hysky.skyblocker.utils.ColorUtils; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.render.RenderHelper; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.state.property.Properties; +import net.minecraft.util.DyeColor; +import net.minecraft.util.math.BlockPos; + +public class LightsOn { + private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); + private static final BlockPos TOP_LEFT = new BlockPos(62, 136, 142); + private static final BlockPos TOP_RIGHT = new BlockPos(58, 136, 142); + private static final BlockPos MIDDLE_TOP = new BlockPos(60, 135, 142); + private static final BlockPos MIDDLE_BOTTOM = new BlockPos(60, 134, 142); + private static final BlockPos BOTTOM_LEFT = new BlockPos(62, 133, 142); + private static final BlockPos BOTTOM_RIGHT = new BlockPos(58, 133, 142); + private static final BlockPos[] LEVERS = { TOP_LEFT, TOP_RIGHT, MIDDLE_TOP, MIDDLE_BOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT }; + private static final float[] RED = ColorUtils.getFloatComponents(DyeColor.RED); + + public static void init() { + WorldRenderEvents.AFTER_TRANSLUCENT.register(LightsOn::render); + } + + private static void render(WorldRenderContext context) { + if (SkyblockerConfigManager.get().dungeons.devices.solveLightsOn && Utils.isInDungeons() && DungeonManager.isInBoss() && DungeonManager.getBoss() == DungeonBoss.MAXOR) { + for (BlockPos lever : LEVERS) { + ClientWorld world = CLIENT.world; + BlockState state = world.getBlockState(lever); + + if (state.getBlock().equals(Blocks.LEVER) && state.contains(Properties.POWERED) && !state.get(Properties.POWERED)) { + RenderHelper.renderFilled(context, lever, RED, 0.5f, false); + } + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java new file mode 100644 index 00000000..5aa97dd9 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java @@ -0,0 +1,122 @@ +package de.hysky.skyblocker.skyblock.dungeon.device; + +import java.util.Objects; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.dungeon.DungeonBoss; +import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; +import de.hysky.skyblocker.utils.Boxes; +import de.hysky.skyblocker.utils.ColorUtils; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.render.RenderHelper; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.fabricmc.fabric.api.event.player.UseBlockCallback; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.DyeColor; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class SimonSays { + private static final Box BOARD_AREA = Box.enclosing(new BlockPos(111, 123, 92), new BlockPos(111, 120, 95)); + private static final Box BUTTONS_AREA = Box.enclosing(new BlockPos(110, 123, 92), new BlockPos(110, 120, 95)); + private static final BlockPos START_BUTTON = new BlockPos(110, 121, 91); + private static final float[] GREEN = ColorUtils.getFloatComponents(DyeColor.LIME); + private static final float[] YELLOW = ColorUtils.getFloatComponents(DyeColor.YELLOW); + private static final ObjectSet<BlockPos> CLICKED_BUTTONS = new ObjectOpenHashSet<>(); + private static final ObjectList<BlockPos> SIMON_PATTERN = new ObjectArrayList<>(); + + public static void init() { + UseBlockCallback.EVENT.register(SimonSays::onBlockInteract); + ClientPlayConnectionEvents.JOIN.register((_handler, _sender, _client) -> reset()); + WorldRenderEvents.AFTER_TRANSLUCENT.register(SimonSays::render); + } + + //When another player is pressing the buttons hypixel doesnt send block or block state updates + //so you can't see it which means the solver can only count the buttons you press yourself + private static ActionResult onBlockInteract(PlayerEntity player, World world, Hand hand, BlockHitResult hitResult) { + if (shouldProcess()) { + BlockPos pos = hitResult.getBlockPos(); + Block block = world.getBlockState(pos).getBlock(); + + if (block.equals(Blocks.STONE_BUTTON)) { + if (BUTTONS_AREA.contains(Vec3d.of(pos))) { + CLICKED_BUTTONS.add(new BlockPos(pos)); //Copy just in case it becomes mutable in the future + } else if (pos.equals(START_BUTTON)) { + reset(); + } + } + } + + //This could also be used to cancel incorrect clicks in the future + return ActionResult.PASS; + } + + //If the player goes out of the range required to receive block/chunk updates then their solver won't detect stuff but that + //doesn't matter because if they're doing pre-4 or something they won't be doing the ss, and if they end up needing to they can + //just reset it or have the other person finish the current sequence first then let them do it. + public static void onBlockUpdate(BlockPos pos, BlockState state) { + if (shouldProcess()) { + Vec3d posVec = Vec3d.of(pos); + Block block = state.getBlock(); + + if (BOARD_AREA.contains(posVec) && block.equals(Blocks.SEA_LANTERN)) { + SIMON_PATTERN.add(pos.toImmutable()); //Convert to immutable because chunk delta updates use the mutable variant + } else if (BUTTONS_AREA.contains(posVec) && block.equals(Blocks.AIR)) { + //Upon reaching the showing of the next sequence we need to reset the state so that we don't show old data + //Otherwise, the nextIndex will go beyond 5 and that can cause bugs, it also helps with the other case noted above + reset(); + } + } + } + + private static void render(WorldRenderContext context) { + if (shouldProcess()) { + int buttonsRendered = 0; + + for (BlockPos pos : SIMON_PATTERN) { + //Offset to west (x - 1) to get the position of the button from the sea lantern block + BlockPos buttonPos = pos.west(); + ClientWorld world = Objects.requireNonNull(MinecraftClient.getInstance().world); //Should never be null here + BlockState state = world.getBlockState(buttonPos); + + //If the button hasn't been clicked yet + //Also don't do anything if the button isn't there which means the device is showing the sequence + if (!CLICKED_BUTTONS.contains(buttonPos) && state.getBlock().equals(Blocks.STONE_BUTTON)) { + Box outline = RenderHelper.getBlockBoundingBox(world, state, buttonPos); + float[] colour = buttonsRendered == 0 ? GREEN : YELLOW; + + RenderHelper.renderFilled(context, Boxes.getMinVec(outline), Boxes.getLengthVec(outline), colour, 0.5f, true); + RenderHelper.renderOutline(context, outline, colour, 5f, true); + + if (++buttonsRendered == 2) return; + } + } + } + } + + private static boolean shouldProcess() { + return SkyblockerConfigManager.get().dungeons.devices.solveSimonSays && + Utils.isInDungeons() && DungeonManager.isInBoss() && DungeonManager.getBoss() == DungeonBoss.MAXOR; + } + + private static void reset() { + CLICKED_BUTTONS.clear(); + SIMON_PATTERN.clear(); + } +} 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 01ffc144..ab61b684 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java @@ -2,6 +2,7 @@ package de.hysky.skyblocker.skyblock.itemlist; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.NEURepoManager; +import de.hysky.skyblocker.utils.TextTransformer; import io.github.moulberry.repo.constants.PetNumbers; import io.github.moulberry.repo.data.NEUItem; import io.github.moulberry.repo.data.Rarity; @@ -53,10 +54,10 @@ public class ItemStackBuilder { // Item Name String name = injectData(item.getDisplayName(), injectors); - stack.set(DataComponentTypes.CUSTOM_NAME, Text.of(name)); + stack.set(DataComponentTypes.CUSTOM_NAME, TextTransformer.fromLegacy(name)); // Lore - stack.set(DataComponentTypes.LORE, new LoreComponent(item.getLore().stream().map(line -> Text.of(injectData(line, injectors))).toList())); + stack.set(DataComponentTypes.LORE, new LoreComponent(item.getLore().stream().map(line -> TextTransformer.fromLegacy(injectData(line, injectors))).map(Text.class::cast).toList())); String nbttag = item.getNbttag(); // add skull texture diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java index dfb53628..bb236526 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java @@ -73,6 +73,8 @@ public class SearchResultsWidget implements Drawable, Element { } protected void updateSearchResult(String searchText) { + searchText = searchText.toLowerCase(Locale.ENGLISH); + if (!searchText.equals(this.searchText)) { this.searchText = searchText; this.searchResults.clear(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java index d867a0e6..16f7eb28 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java @@ -1,7 +1,7 @@ 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.profileviewer.utils.ProfileViewerUtils; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; @@ -22,9 +22,9 @@ public class ProfileViewerNavButton extends ClickableWidget { private static final Map<String, ItemStack> HEAD_ICON = Map.ofEntries( Map.entry("Skills", Ico.IRON_SWORD), - Map.entry("Slayers", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzkzMzZkN2NjOTVjYmY2Njg5ZjVlOGM5NTQyOTRlYzhkMWVmYzQ5NGE0MDMxMzI1YmI0MjdiYzgxZDU2YTQ4NGQifX19")), + Map.entry("Slayers", ProfileViewerUtils.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzkzMzZkN2NjOTVjYmY2Njg5ZjVlOGM5NTQyOTRlYzhkMWVmYzQ5NGE0MDMxMzI1YmI0MjdiYzgxZDU2YTQ4NGQifX19")), Map.entry("Pets", Ico.BONE), - Map.entry("Dungeons", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzliNTY4OTViOTY1OTg5NmFkNjQ3ZjU4NTk5MjM4YWY1MzJkNDZkYjljMWIwMzg5YjhiYmViNzA5OTlkYWIzM2QifX19")), + Map.entry("Dungeons", ProfileViewerUtils.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzliNTY4OTViOTY1OTg5NmFkNjQ3ZjU4NTk5MjM4YWY1MzJkNDZkYjljMWIwMzg5YjhiYmViNzA5OTlkYWIzM2QifX19")), Map.entry("Inventories", Ico.E_CHEST), Map.entry("Collections", Ico.PAINTING) ); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java index 1d0b21ca..f74526a4 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java @@ -27,6 +27,7 @@ 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.command.CommandSource; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerModelPart; import net.minecraft.text.Text; @@ -55,6 +56,7 @@ public class ProfileViewerScreen extends Screen { private String playerName; private JsonObject hypixelProfile; private JsonObject playerProfile; + private boolean profileNotFound = false; private int activePage = 0; private static final String[] PAGE_NAMES = {"Skills", "Slayers", "Dungeons", "Inventories", "Collections"}; @@ -73,6 +75,8 @@ public class ProfileViewerScreen extends Screen { } private void initialisePagesAndWidgets() { + if (profileNotFound) return; + textWidget = new ProfileViewerTextWidget(hypixelProfile, playerProfile); CompletableFuture<Void> skillsFuture = CompletableFuture.runAsync(() -> profileViewerPages[0] = new SkillsPage(hypixelProfile, playerProfile)); @@ -105,6 +109,7 @@ public class ProfileViewerScreen extends Screen { 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); @@ -112,7 +117,7 @@ public class ProfileViewerScreen extends Screen { 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); + context.drawText(textRenderer, profileNotFound ? "No Profile" : "Loading...", rootX + 180, rootY + 80, Color.WHITE.getRGB(), true); } } @@ -120,18 +125,24 @@ public class ProfileViewerScreen extends Screen { 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(); + try { + Optional<JsonObject> selectedProfile = profiles.getAsJsonArray("profiles").asList().stream() + .map(JsonElement::getAsJsonObject) + .filter(profile -> profile.getAsJsonPrimitive("selected").getAsBoolean()) + .findFirst(); + + if (selectedProfile.isPresent()) { + this.hypixelProfile = selectedProfile.get(); + this.playerProfile = hypixelProfile.getAsJsonObject("members").get(ApiUtils.name2Uuid(username)).getAsJsonObject(); + } + } catch (Exception e) { + this.profileNotFound = true; + LOGGER.warn("[Skyblocker Profile Viewer] Error while looking for profile", e); + } }); CompletableFuture<Void> minecraftProfileFuture = SkullBlockEntityAccessor.invokeFetchProfileByName(username).thenAccept(profile -> { @@ -156,12 +167,14 @@ public class ProfileViewerScreen extends Screen { entity.setCustomNameVisible(false); }).exceptionally(ex -> { this.playerName = "User not found"; + this.profileNotFound = true; return null; }); return CompletableFuture.allOf(profileFuture, minecraftProfileFuture); } + public void onNavButtonClick(ProfileViewerNavButton clickedButton) { if (profileViewerPages[activePage] != null) profileViewerPages[activePage].markWidgetsAsInvisible(); for (ProfileViewerNavButton button : profileViewerNavButtons) { @@ -188,10 +201,11 @@ public class ProfileViewerScreen extends Screen { 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()))); + .then(ClientCommandManager.argument("username", StringArgumentType.string()) + .suggests((source, builder) -> CommandSource.suggestMatching(getPlayerSuggestions(source.getSource()), builder)) + .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)); }); @@ -227,4 +241,11 @@ public class ProfileViewerScreen extends Screen { } return Collections.emptyMap(); } + + /** + * Ensures that "dummy" players aren't included in command suggestions + */ + private static String[] getPlayerSuggestions(FabricClientCommandSource source) { + return source.getPlayerNames().stream().filter(playerName -> playerName.matches("[A-Za-z0-9_]+")).toArray(String[]::new); + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java index 4ee2dbba..58c238f8 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java @@ -1,6 +1,7 @@ package de.hysky.skyblocker.skyblock.profileviewer; import com.google.gson.JsonObject; +import de.hysky.skyblocker.skyblock.profileviewer.utils.ProfileViewerUtils; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; @@ -36,20 +37,8 @@ public class ProfileViewerTextWidget { 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, "§6Purse:§r " + ProfileViewerUtils.numLetterFormat(PURSE), root_x + 2, root_y + 6 + ROW_GAP * 2, Colors.WHITE, true); + context.drawText(textRenderer, "§6Bank:§r " + ProfileViewerUtils.numLetterFormat(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/GenericCategory.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java index ef26332e..a9f83ca8 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java @@ -5,13 +5,12 @@ 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.component.type.NbtComponent; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.tooltip.TooltipType; @@ -21,17 +20,18 @@ import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import java.awt.*; -import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; -import java.util.*; +import java.util.Map; import static de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen.fetchCollectionsData; +import static de.hysky.skyblocker.skyblock.profileviewer.utils.ProfileViewerUtils.COMMA_FORMATTER; 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; @@ -61,40 +61,50 @@ public class GenericCategory implements ProfileViewerPage { 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()); + ItemStack itemStack = ItemRepository.getItemStack(ICON_TRANSLATION.getOrDefault(collection, collection).replace(':', '-')); + itemStack = itemStack == null ? Ico.BARRIER.copy() : itemStack.copy(); + + if (itemStack.getItem().getName().getString().equals("Barrier")) { + itemStack.set(DataComponentTypes.CUSTOM_NAME, Text.of(collection)); + System.out.println(collection); + System.out.println(this.category); + } + + Style style = Style.EMPTY.withColor(Formatting.WHITE).withItalic(false); + itemStack.set(DataComponentTypes.CUSTOM_NAME, Text.literal(Formatting.strip(itemStack.getComponents().get(DataComponentTypes.CUSTOM_NAME).getString())).setStyle(style)); - 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; + int totalCollection = 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; + totalCollection += memberColl.has(collection) ? memberColl.get(collection).getAsInt() : 0; } - int collectionTier = calculateTier(coopColl, tierRequirementsMap.get(collection)); + int collectionTier = calculateTier(totalCollection, 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)); + lore.add(Text.literal("Collection Item").setStyle(style).formatted(Formatting.DARK_GRAY)); + lore.add(Text.empty()); + 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("Personal: " + COMMA_FORMATTER.format(personalColl)).setStyle(style).formatted(Formatting.GOLD)); + lore.add(Text.literal("Co-op Collection: " + COMMA_FORMATTER.format(totalCollection-personalColl)).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)); + lore.add(Text.literal("Collection: " + COMMA_FORMATTER.format(totalCollection)).setStyle(style).formatted(Formatting.YELLOW)); + + lore.add(Text.empty()); + lore.add(Text.literal("Collection Tier: " + collectionTier + "/" + tierRequirements.size()).setStyle(style).formatted(Formatting.LIGHT_PURPLE)); if (collectionTier == tierRequirements.size()) itemStack.set(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true); + itemStack.set(DataComponentTypes.LORE, new LoreComponent(lore)); + + itemStack.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT); + collections.add(itemStack); } } @@ -116,11 +126,18 @@ public class GenericCategory implements ProfileViewerPage { 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 (text.getString().startsWith("Collection Tier: ")) { + String tierText = text.getString().substring("Collection Tier: ".length()); + if (tierText.contains("/")) { + String[] parts = tierText.split("/"); + int cTier = Integer.parseInt(parts[0].trim()); + Color colour = itemStack.hasGlint() ? Color.MAGENTA : Color.darkGray; + //DO NOT CHANGE THIS METHOD CALL! Aaron's Mod mixes in here to provide chroma text for max collections + //and changing the method called here will break that! Consult Aaron before making any changes :) + 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) { 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 index 3b847b1b..37953a2b 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonClassWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonClassWidget.java @@ -3,15 +3,20 @@ 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.profileviewer.utils.ProfileViewerUtils; 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.text.Text; +import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import java.awt.*; +import java.util.ArrayList; +import java.util.List; import java.util.Map; public class DungeonClassWidget { @@ -48,7 +53,7 @@ public class DungeonClassWidget { } } - public void render(DrawContext context, int x, int y) { + public void render(DrawContext context, int mouseX, int mouseY, 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); @@ -57,6 +62,12 @@ public class DungeonClassWidget { 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); - } + if (mouseX > x + 30 && mouseX < x + 105 && mouseY > y + 12 && mouseY < y + 22){ + List<Text> tooltipText = new ArrayList<>(); + tooltipText.add(Text.literal(this.className).formatted(Formatting.GREEN)); + tooltipText.add(Text.literal("XP: " + ProfileViewerUtils.COMMA_FORMATTER.format(this.classLevel.xp)).formatted(Formatting.GOLD)); + context.drawTooltip(textRenderer, tooltipText, mouseX, mouseY); + } + } } 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 index 7c9206c0..b592266a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonFloorRunsWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonFloorRunsWidget.java @@ -11,6 +11,8 @@ import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import java.awt.*; +import java.util.ArrayList; +import java.util.List; import java.util.Map; public class DungeonFloorRunsWidget { @@ -26,8 +28,7 @@ public class DungeonFloorRunsWidget { } 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) { + public void render(DrawContext context, int mouseX ,int mouseY, 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); @@ -36,12 +37,33 @@ public class DungeonFloorRunsWidget { for (String dungeon : DUNGEONS) { JsonObject dungeonData; try { - dungeonData = dungeonsStats.getAsJsonObject(dungeon).getAsJsonObject(dungeon.equals("catacombs") ? "times_played" : "tier_completions"); + dungeonData = dungeonsStats.getAsJsonObject(dungeon).getAsJsonObject("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); + if (!entry.getKey().equals("0") && mouseX >= columnX && mouseX <= columnX + 40 && mouseY >= elementY && mouseY <= elementY + 9) { + List<Text> tooltipText = new ArrayList<>(); + tooltipText.add(Text.literal("Personal Bests").formatted(Formatting.BOLD, Formatting.LIGHT_PURPLE)); + + JsonObject fastestTimes = dungeonsStats.getAsJsonObject(dungeon).getAsJsonObject("fastest_time_s"); + if (fastestTimes != null && fastestTimes.has(entry.getKey())) { + tooltipText.add(Text.literal("S Run: " + formatTime(fastestTimes.get(entry.getKey()).getAsLong())).formatted(Formatting.GOLD)); + } + + fastestTimes = dungeonsStats.getAsJsonObject(dungeon).getAsJsonObject("fastest_time_s_plus"); + if (fastestTimes != null && fastestTimes.has(entry.getKey())) { + tooltipText.add(Text.literal("S+ Run: " + formatTime(fastestTimes.get(entry.getKey()).getAsLong())).formatted(Formatting.GOLD)); + } + + fastestTimes = dungeonsStats.getAsJsonObject(dungeon).getAsJsonObject("fastest_time"); + if (fastestTimes != null && fastestTimes.has(entry.getKey()) && tooltipText.size() == 1) { + tooltipText.add(Text.literal("Completion: " + formatTime(fastestTimes.get(entry.getKey()).getAsLong())).formatted(Formatting.GOLD)); + } + + context.drawTooltip(textRenderer, tooltipText, mouseX, mouseY); + } elementY += 11; } @@ -52,4 +74,11 @@ public class DungeonFloorRunsWidget { } } } + + private String formatTime(long milliseconds) { + long seconds = milliseconds / 1000; + long minutes = seconds / 60; + seconds %= 60; + return String.format("%2d:%02d", minutes, seconds); + } } 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 index 679cc575..780eec24 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonMiscStatsWidgets.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonMiscStatsWidgets.java @@ -31,7 +31,7 @@ public class DungeonMiscStatsWidgets { 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"); + JsonObject dungeonData = DUNGEONS_DATA.getAsJsonObject("dungeon_types").getAsJsonObject(dungeon).getAsJsonObject("tier_completions"); int runs = 0; for (Map.Entry<String, JsonElement> entry : dungeonData.entrySet()) { String key = entry.getKey(); 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 index b1398661..e0051c88 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonsPage.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonsPage.java @@ -30,10 +30,10 @@ public class DungeonsPage implements ProfileViewerPage { 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); + dungeonFloorRunsWidget.render(context, mouseX, mouseY, 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); + dungeonClassWidgetsList.get(i).render(context, mouseX, mouseY, 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 index a2f7d9d6..126c55ec 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Inventory.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Inventory.java @@ -1,6 +1,8 @@ package de.hysky.skyblocker.skyblock.profileviewer.inventory; import com.google.gson.JsonObject; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.ItemRarityBackgrounds; import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage; import de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders.ItemLoader; import it.unimi.dsi.fastutil.ints.IntIntPair; @@ -8,6 +10,7 @@ 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.client.resource.language.I18n; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.tooltip.TooltipType; @@ -16,6 +19,7 @@ import net.minecraft.util.Identifier; import java.awt.*; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class Inventory implements ProfileViewerPage { @@ -49,7 +53,7 @@ public class Inventory implements ProfileViewerPage { 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); + context.drawText(textRenderer, I18n.translate("skyblocker.profileviewer.inventory." + containerName), rootX + 7, rootYAdjusted + 7, Color.DARK_GRAY.getRGB(), false); if (containerList.size() > itemsPerPage) { previousPage.setX(rootX + 44); @@ -65,6 +69,7 @@ public class Inventory implements ProfileViewerPage { int startIndex = activePage * itemsPerPage; int endIndex = Math.min(startIndex + itemsPerPage, containerList.size()); + List<Text> tooltip = Collections.emptyList(); for (int i = 0; i < endIndex - startIndex; i++) { if (containerList.get(startIndex + i) == ItemStack.EMPTY) continue; int column = i % dimensions.rightInt(); @@ -72,14 +77,20 @@ public class Inventory implements ProfileViewerPage { int x = rootX + 8 + column * 18; int y = rootYAdjusted + 18 + row * 18; + + if (SkyblockerConfigManager.get().general.itemInfoDisplay.itemRarityBackgrounds) { + ItemRarityBackgrounds.tryDraw(containerList.get(startIndex + i), context, x, y); + } + 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); + if (mouseX > x -1 && mouseX < x + 16 && mouseY > y - 1 && mouseY < y + 16) { + tooltip = containerList.get(startIndex + i).getTooltip(Item.TooltipContext.DEFAULT, MinecraftClient.getInstance().player, TooltipType.BASIC); } } + + if (!tooltip.isEmpty()) context.drawTooltip(textRenderer, tooltip, mouseX, mouseY); } public void nextPage() { 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 index 8b0cbefc..6aa92ef6 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/InventoryPage.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/InventoryPage.java @@ -6,7 +6,7 @@ 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.ProfileViewerUtils; import de.hysky.skyblocker.skyblock.profileviewer.utils.SubPageSelectButton; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import it.unimi.dsi.fastutil.ints.IntIntPair; @@ -22,15 +22,15 @@ 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 String[] INVENTORY_PAGES = {"inventory", "enderchest", "backpack", "wardrobe", "pets", "accessoryBag"}; 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=")) + Map.entry("wardrobe", Ico.L_CHESTPLATE), + Map.entry("inventory", Ico.CHEST), + Map.entry("backpack", ProfileViewerUtils.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzYyZjNiM2EwNTQ4MWNkZTc3MjQwMDA1YzBkZGNlZTFjMDY5ZTU1MDRhNjJjZTA5Nzc4NzlmNTVhMzkzOTYxNDYifX19")), + Map.entry("pets", Ico.BONE), + Map.entry("enderchest", Ico.E_CHEST), + Map.entry("accessoryBag", ProfileViewerUtils.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTYxYTkxOGMwYzQ5YmE4ZDA1M2U1MjJjYjkxYWJjNzQ2ODkzNjdiNGQ4YWEwNmJmYzFiYTkxNTQ3MzA5ODVmZiJ9fX0=")) ); private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; 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 index b3389d39..857198e1 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java @@ -4,71 +4,107 @@ 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.profileviewer.utils.ProfileViewerUtils; 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 it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; 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.Style; 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 java.util.regex.Pattern; import static de.hysky.skyblocker.skyblock.itemlist.ItemStackBuilder.SKULL_TEXTURE_PATTERN; -import static de.hysky.skyblocker.skyblock.itemlist.ItemStackBuilder.SKULL_UUID_PATTERN; +import static de.hysky.skyblocker.skyblock.profileviewer.utils.ProfileViewerUtils.numLetterFormat; public class Pet { private final String name; private final double xp; private final String tier; private final Optional<String> heldItem; + private final Optional<String> skin; + private final Optional<String> skinTexture; private final int level; + private final double perecentageToLevel; + private final long levelXP; + private final long nextLevelXP; private final ItemStack icon; + private final Pattern statsMatcher = Pattern.compile("\\{[A-Za-z_]+}"); + private final Pattern numberMatcher = Pattern.compile("\\{\\d+}"); + + + private static final Map<String, Integer> TIER_MAP = Map.of( "COMMON", 0, "UNCOMMON", 1, "RARE", 2, "EPIC", 3, "LEGENDARY", 4, "MYTHIC", 5 ); + private static final Int2ObjectMap<Formatting> RARITY_COLOR_MAP = Int2ObjectMaps.unmodifiable(new Int2ObjectOpenHashMap<>(Map.of( + 0, Formatting.WHITE, // COMMON + 1, Formatting.GREEN, // UNCOMMON + 2, Formatting.BLUE, // RARE + 3, Formatting.DARK_PURPLE, // EPIC + 4, Formatting.GOLD, // LEGENDARY + 5, Formatting.LIGHT_PURPLE, // MYTHIC + 6, Formatting.AQUA // DIVINE (future proofing, because why not) + ))); + public Pet(PetCache.PetInfo petData) { + LevelFinder.LevelInfo info = LevelFinder.getLevelInfo(petData.type().equals("GOLDEN_DRAGON") ? "PET_GREG" : "PET_" + petData.tier(), (long) petData.exp()); 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.skin = petData.skin(); + this.skinTexture = calculateSkinTexture(); + this.tier = petData.tier(); + this.level = info.level; + this.perecentageToLevel = info.fill; + this.levelXP = info.levelXP; + this.nextLevelXP = info.nextLevelXP; 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; } + private String getName() { + return name; + } + + public long getXP() { + return (long) xp; + } + + private int getTier() { + return TIER_MAP.getOrDefault(tier, 0); + } + + public String getTierAsString() { + return tier; + } + + private Optional<String> calculateSkinTexture() { + if (this.skin.isPresent()) { + NEUItem item = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId("PET_SKIN_" + this.skin.get()); + if (item == null) return Optional.empty(); + Matcher skullTexture = SKULL_TEXTURE_PATTERN.matcher(item.getNbttag()); + if (skullTexture.find()) return Optional.of(skullTexture.group(1)); + } + return Optional.empty(); + } public int getLevel() { return level; } public ItemStack getIcon() { return icon; } @@ -78,19 +114,15 @@ public class Pet { 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); + String targetItemId = this.getName() + ";" + (this.getTier() + (heldItem.isPresent() && heldItem.get().equals("PET_ITEM_TIER_BOOST") ? 1 : 0)); + NEUItem item = NEURepoManager.NEU_REPO.getItems().getItems().get(targetItemId); - NEUItem petItem = null; - if (this.heldItem.isPresent()) { - petItem = items.values().stream() - .filter(i -> Formatting.strip(i.getSkyblockItemId()).equals(this.heldItem.get())) - .findFirst().orElse(null); + // For cases life RIFT_FERRET Where it can be tier boosted into a pet that otherwise can't exist + if (item == null && heldItem.isPresent() && heldItem.get().equals("PET_ITEM_TIER_BOOST")) { + item = NEURepoManager.NEU_REPO.getItems().getItems().get(getName() + ";" + getTier()); } - return fromNEUItem(item, petItem); + return fromNEUItem(item, this.heldItem.map(ItemRepository::getItemStack).orElse(null)); } /** @@ -100,87 +132,134 @@ public class Pet { * 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. + * @param heldItem The ItemStack of the pet's 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)); + private ItemStack fromNEUItem(NEUItem item, ItemStack heldItem) { + if (item == null) { + ItemStack errIcon = Ico.BARRIER.copy(); + errIcon.set(DataComponentTypes.CUSTOM_NAME, Text.of(this.getName())); + return errIcon; + } + 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))); + ItemStack petStack = new ItemStack(Registries.ITEM.get(itemId)).copy(); + + List<Text> formattedLore = !(name.equals("GOLDEN_DRAGON") && level < 101) ? processLore(item.getLore(), heldItem) : buildGoldenDragonEggLore(item.getLore()); + + // Calculate and display XP for level + Style style = Style.EMPTY.withItalic(false); + if (level != 100 && level != 200) { + String progress = "Progress to Level " + this.level + ": §e" + fixDecimals(this.perecentageToLevel * 100, true) + "%"; + formattedLore.add(formattedLore.size() - 1, Text.literal(progress).setStyle(style).formatted(Formatting.GRAY)); + String string = "§2§m ".repeat((int) Math.round(perecentageToLevel * 30)) + "§f§m ".repeat(30 - (int) Math.round(perecentageToLevel * 30)); + formattedLore.add(formattedLore.size() - 1, Text.literal(string + "§r§e " + numLetterFormat(levelXP) + "§6/§e" + numLetterFormat(nextLevelXP)).setStyle(style)); + formattedLore.add(formattedLore.size() - 1, Text.empty()); + } else { + formattedLore.add(formattedLore.size() - 1, Text.literal("MAX LEVEL").setStyle(style).formatted(Formatting.AQUA, Formatting.BOLD)); + formattedLore.add(formattedLore.size() - 1, Text.literal("▸ " + ProfileViewerUtils.COMMA_FORMATTER.format((long) xp) + " XP").setStyle(style).formatted(Formatting.DARK_GRAY)); + formattedLore.add(formattedLore.size() - 1, Text.empty()); } - return stack; + + // Skin Head Texture + if (skinTexture.isPresent()) { + formattedLore.set(0, Text.of(formattedLore.getFirst().getString() + ", " + Formatting.strip(NEURepoManager.NEU_REPO.getItems().getItems().get("PET_SKIN_" + skin.get()).getDisplayName()))); + petStack.set(DataComponentTypes.PROFILE, new ProfileComponent( + Optional.of(item.getSkyblockItemId()), Optional.of(UUID.randomUUID()), + ItemUtils.propertyMapWithTexture(this.skinTexture.get()))); + } else { + Matcher skullTexture = SKULL_TEXTURE_PATTERN.matcher(item.getNbttag()); + if (skullTexture.find()) { + petStack.set(DataComponentTypes.PROFILE, new ProfileComponent( + Optional.of(item.getSkyblockItemId()), Optional.of(UUID.randomUUID()), + ItemUtils.propertyMapWithTexture(skullTexture.group(1)))); + } + } + + if ((boosted())) formattedLore.set(formattedLore.size() - 1, Text.literal(Rarity.values()[getTier() + 1].toString()).setStyle(style).formatted(Formatting.BOLD, RARITY_COLOR_MAP.get(getTier() + 1))); + + // Update the lore and name + petStack.set(DataComponentTypes.LORE, new LoreComponent(formattedLore)); + String displayName = Formatting.strip(item.getDisplayName()).replace("[Lvl {LVL}]", "§7[Lvl " + this.level + "]§r"); + petStack.set(DataComponentTypes.CUSTOM_NAME, Text.literal(displayName).setStyle(style).formatted(RARITY_COLOR_MAP.get(this.getTier() + (boosted() ? 1 : 0)))); + return petStack; } /** - * 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> + * Iterates through a Pet's lore injecting interpolated stat numbers based on pet level * - * @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. + * @param lore the raw lore data stored in NEU Repo + * @param heldItem the pet's held item, if any + * @return Formatted lore with injected stats inserted into the tooltip */ - 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))); + private List<Text> processLore(List<String> lore, ItemStack heldItem) { + Map<String, Map<Rarity, PetNumbers>> petNums = NEURepoManager.NEU_REPO.getConstants().getPetNumbers(); + Rarity rarity = Rarity.values()[getTier()]; + PetNumbers data = petNums.get(getName()).get(rarity); + List<Text> formattedLore = new ArrayList<>(); + + for (String line : lore) { + if (line.contains("Right-click to add this") || line.contains("pet menu!")) continue; + + String formattedLine = line; + + Matcher stats = statsMatcher.matcher(formattedLine); + Matcher other = numberMatcher.matcher(formattedLine); + + while (stats.find()) { + String placeholder = stats.group(); + String statKey = placeholder.substring(1, placeholder.length() - 1); + String statValue = String.valueOf(fixDecimals(data.interpolatedStatsAtLevel(this.level).getStatNumbers().get(statKey), true)); + formattedLine = formattedLine.replace(placeholder, statValue); } - 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]")); + while (other.find()) { + String placeholder = other.group(); + int numberKey = Integer.parseInt(placeholder.substring(1, placeholder.length() - 1)); + String statValue = String.valueOf(fixDecimals(data.interpolatedStatsAtLevel(this.level).getOtherNumbers().get(numberKey), false)); + formattedLine = formattedLine.replace(placeholder, statValue); } + + formattedLore.add(Text.of(formattedLine)); } - 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()); + + if (heldItem != null) { + formattedLore.set(formattedLore.size() - 2, Text.of("§r§6Held Item: " + heldItem.getName().getString())); + formattedLore.add(formattedLore.size() - 1, Text.empty()); } - return string; + + return formattedLore; + } + + /** + * NEU Repo doesn't distinguish between the Egg and the hatched GoldenDragon pet so hardcoded lore :eues: + * @param lore the existing lore + * @return Fully formatted GoldenDragonEgg Lore + */ + private List<Text> buildGoldenDragonEggLore(List<String> lore) { + List<Text> formattedLore = new ArrayList<>(); + Style style = Style.EMPTY.withItalic(false); + + formattedLore.add(Text.of(lore.getFirst())); + formattedLore.add(Text.empty()); + formattedLore.add(Text.literal("Perks:").setStyle(style).formatted(Formatting.GRAY)); + formattedLore.add(Text.literal("???").setStyle(style).formatted(Formatting.RED, Formatting.BOLD)); + formattedLore.add(Text.empty()); + formattedLore.add(Text.literal("Hatches at level §b100").setStyle(style).formatted(Formatting.GRAY)); + formattedLore.add(Text.empty()); + formattedLore.add(Text.of(lore.getLast())); + + return formattedLore; } 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(); + if (num % 1 == 0) return String.valueOf((int) (num)); + BigDecimal roundedNum = new BigDecimal(num).setScale(truncate ? 1 : 3, RoundingMode.HALF_UP); + return roundedNum.stripTrailingZeros().toPlainString(); + } + + private boolean boosted() { + return this.heldItem.isPresent() && this.heldItem.get().equals("PET_ITEM_TIER_BOOST"); } } 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 index 26673693..e210ca9a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PlayerInventory.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PlayerInventory.java @@ -1,12 +1,15 @@ package de.hysky.skyblocker.skyblock.profileviewer.inventory; import com.google.gson.JsonObject; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.ItemRarityBackgrounds; 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.client.resource.language.I18n; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.tooltip.TooltipType; @@ -14,12 +17,14 @@ import net.minecraft.text.Text; import net.minecraft.util.Identifier; import java.awt.*; +import java.util.Collections; 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; + private List<Text> tooltip = Collections.emptyList(); public PlayerInventory(JsonObject inventory) { this.containerList = new InventoryItemLoader().loadItems(inventory); @@ -27,17 +32,19 @@ public class PlayerInventory implements ProfileViewerPage { // 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)); + drawContainerTextures(context, "armor", 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)); + tooltip.clear(); 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); + if (!tooltip.isEmpty()) context.drawTooltip(textRenderer, tooltip, mouseX, mouseY); } private void drawContainerTextures(DrawContext context, String containerName, int rootX, int rootY, IntIntPair dimensions) { - if (containerName.equals("Inventory")) { + 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); @@ -48,8 +55,7 @@ public class PlayerInventory implements ProfileViewerPage { 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); + context.drawText(textRenderer, I18n.translate("skyblocker.profileviewer.inventory." + 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) { @@ -61,12 +67,15 @@ public class PlayerInventory implements ProfileViewerPage { int x = rootX + 8 + column * 18; int y = (rootY + 18 + row * 18) + (dimensions.leftInt() > 1 && row + 1 == dimensions.leftInt() ? 4 : 0); + if (SkyblockerConfigManager.get().general.itemInfoDisplay.itemRarityBackgrounds) { + ItemRarityBackgrounds.tryDraw(containerList.get(startIndex + i), context, x, y); + } + 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); + if (mouseX > x -1 && mouseX < x + 16 && mouseY > y - 1 && mouseY < y + 16) { + tooltip = containerList.get(startIndex + i).getTooltip(Item.TooltipContext.DEFAULT, MinecraftClient.getInstance().player, TooltipType.BASIC); } } } 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 index 99e728be..dee2bfaf 100644 --- 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 @@ -2,7 +2,11 @@ package de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.LoreComponent; import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; import java.util.ArrayList; import java.util.List; @@ -23,9 +27,12 @@ public class BackpackItemLoader extends ItemLoader { 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); + int paddingNeeded = (45 - (backpackItems.size() % 45)) % 45; + for (int j = 0; j < paddingNeeded; j++) { + ItemStack paddingItem = Ico.GRAY_DYE.copy(); + paddingItem.set(DataComponentTypes.CUSTOM_NAME, Text.translatable("skyblocker.profileviewer.inventory.inactive")); + paddingItem.set(DataComponentTypes.LORE, new LoreComponent(List.of(Text.translatable("skyblocker.profileviewer.inventory.inactive.description.backpack"),Text.translatable("skyblocker.profileviewer.inventory.inactive.description.general")))); + backpackItems.add(paddingItem); } } 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 index 9d9b1b07..11280af1 100644 --- 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 @@ -3,6 +3,7 @@ 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; @@ -10,12 +11,10 @@ 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 de.hysky.skyblocker.utils.TextTransformer; 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.component.type.*; import net.minecraft.datafixer.fix.ItemIdFix; import net.minecraft.datafixer.fix.ItemInstanceTheFlatteningFix; import net.minecraft.item.ItemStack; @@ -24,6 +23,7 @@ import net.minecraft.registry.Registries; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; +import net.minecraft.util.Util; import java.io.ByteArrayInputStream; import java.util.*; @@ -44,9 +44,9 @@ public class ItemLoader { } NbtCompound nbttag = containerContent.getCompound(i).getCompound("tag"); - String internalName = nbttag.getCompound("ExtraAttributes").getString("id"); + NbtCompound extraAttributes = nbttag.getCompound("ExtraAttributes"); + String internalName = 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()); @@ -82,13 +82,13 @@ public class ItemLoader { // Item Name - stack.set(DataComponentTypes.CUSTOM_NAME, Text.of(nbttag.getCompound("display").getString("Name"))); + stack.set(DataComponentTypes.CUSTOM_NAME, TextTransformer.fromLegacy(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) + .map(TextTransformer::fromLegacy) .collect(Collectors.toList()))); // add skull texture @@ -114,6 +114,11 @@ public class ItemLoader { // Set Count stack.setCount(containerContent.getCompound(i).getInt("Count")); + // Attach an override for Aaron's Mod so that these ItemStacks will work with the mod's features even when not in Skyblock + extraAttributes.put("aaron-mod", Util.make(new NbtCompound(), comp -> comp.putBoolean("alwaysDisplaySkyblockInfo", true))); + + stack.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(extraAttributes)); + itemList.add(stack); } 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 index 3a3870f3..51f5e5f4 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillWidget.java @@ -2,16 +2,20 @@ 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.profileviewer.utils.ProfileViewerUtils; 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.text.Text; +import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import java.awt.*; +import java.util.ArrayList; +import java.util.List; import java.util.Map; public class SkillWidget { @@ -33,7 +37,7 @@ public class SkillWidget { 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("Catacombs", ProfileViewerUtils.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzliNTY4OTViOTY1OTg5NmFkNjQ3ZjU4NTk5MjM4YWY1MzJkNDZkYjljMWIwMzg5YjhiYmViNzA5OTlkYWIzM2QifX19")), Map.entry("Runecraft", Ico.MAGMA_CREAM), Map.entry("Social", Ico.EMERALD) ); @@ -75,7 +79,7 @@ public class SkillWidget { } - public void render(DrawContext context, int x, int y) { + public void render(DrawContext context, int mouseX, int mouseY, 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); @@ -91,5 +95,12 @@ public class SkillWidget { 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); + + if (mouseX > x + 30 && mouseX < x + 105 && mouseY > y + 10 && mouseY < y + 19){ + List<Text> tooltipText = new ArrayList<>(); + tooltipText.add(Text.literal(this.SKILL_NAME).formatted(Formatting.GREEN)); + tooltipText.add(Text.literal("XP: " + ProfileViewerUtils.COMMA_FORMATTER.format(this.SKILL_LEVEL.xp)).formatted(Formatting.GOLD)); + context.drawTooltip(textRenderer, tooltipText, mouseX, mouseY); + } } }
\ 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 index c331bbdd..952e5620 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillsPage.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillsPage.java @@ -41,7 +41,7 @@ public class SkillsPage implements ProfileViewerPage { 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); + skillWidgets.get(i).render(context, mouseX, mouseY, x, y + 3); } } 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 index a9c05c11..978c58c4 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayerWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayerWidget.java @@ -3,15 +3,20 @@ 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.profileviewer.utils.ProfileViewerUtils; 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.text.Text; +import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import java.awt.*; +import java.util.ArrayList; +import java.util.List; import java.util.Map; public class SlayerWidget { @@ -53,7 +58,7 @@ public class SlayerWidget { } catch (Exception ignored) {} } - public void render(DrawContext context, int x, int y) { + public void render(DrawContext context, int mouseX, int mouseY, 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); @@ -67,6 +72,13 @@ public class SlayerWidget { 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); + + if (mouseX > x + 30 && mouseX < x + 105 && mouseY > y + 12 && mouseY < y + 22){ + List<Text> tooltipText = new ArrayList<>(); + tooltipText.add(Text.literal(this.slayerName).formatted(Formatting.GREEN)); + tooltipText.add(Text.literal("XP: " + ProfileViewerUtils.COMMA_FORMATTER.format(this.slayerLevel.xp)).formatted(Formatting.GOLD)); + context.drawTooltip(textRenderer, tooltipText, mouseX, mouseY); + } } private int findTotalKills() { 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 index 08e2ca06..528bd3ac 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayersPage.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayersPage.java @@ -26,7 +26,7 @@ public class SlayersPage implements ProfileViewerPage { 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); + slayerWidgets.get(i).render(context, mouseX, mouseY, rootX, rootY + i * ROW_GAP); } } 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 index b52fd579..f53df0f5 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/LevelFinder.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/LevelFinder.java @@ -8,15 +8,20 @@ public class LevelFinder { public long xp; public int level; public double fill; + public long levelXP; + public long nextLevelXP; public LevelInfo(long xp, int level) { this.xp = xp; this.level = level; } - public LevelInfo(int level, double fill) { + public LevelInfo(long xp, int level, double fill, double levelXP, double nextLevelXP) { + this.xp = xp; this.level = level; this.fill = fill; + this.levelXP = (long) levelXP; + this.nextLevelXP = (long) nextLevelXP; } } @@ -255,16 +260,20 @@ public class LevelFinder { for (int i = boundaries.size() - 1; i >= 0 ; i--) { if (xp >= boundaries.get(i).xp) { double fill; + double xpInCurrentLevel; + double levelXPRange; 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; + levelXPRange = nextLevelXP - currentLevelXP; + xpInCurrentLevel = xp - currentLevelXP; fill = xpInCurrentLevel / levelXPRange; } else { fill = 1.0; + xpInCurrentLevel = xp - boundaries.getLast().xp; + levelXPRange = boundaries.getLast().xp - boundaries.get(boundaries.size()-2).xp; } - return new LevelInfo(boundaries.get(i).level, fill); + return new LevelInfo(xp, boundaries.get(i).level, fill, xpInCurrentLevel, levelXPRange); } } return new LevelInfo(0L, 0); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SkullCreator.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/ProfileViewerUtils.java index b074952c..8dadedaf 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SkullCreator.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/ProfileViewerUtils.java @@ -8,10 +8,14 @@ import net.minecraft.component.type.ProfileComponent; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; +import java.text.NumberFormat; +import java.util.Locale; import java.util.Optional; import java.util.UUID; -public class SkullCreator { +public class ProfileViewerUtils { + public static final NumberFormat COMMA_FORMATTER = NumberFormat.getNumberInstance(Locale.US); + public static ItemStack createSkull(String textureB64) { ItemStack skull = new ItemStack(Items.PLAYER_HEAD); try { @@ -24,4 +28,16 @@ public class SkullCreator { } return skull; } + + public static String numLetterFormat(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/utils/SubPageSelectButton.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SubPageSelectButton.java index 4c9dcda4..8398747d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SubPageSelectButton.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SubPageSelectButton.java @@ -34,7 +34,7 @@ public class SubPageSelectButton extends ClickableWidget { @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.drawTexture(TEXTURES.get(toggled, (mouseX > getX() && mouseX < getX() + 19 && mouseY > getY() && mouseY < getY() + 19)), 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); 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 21d66805..dfde789e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java @@ -5,6 +5,7 @@ import com.google.gson.reflect.TypeToken; import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.tree.CommandNode; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.utils.scheduler.Scheduler; @@ -156,9 +157,21 @@ public class Shortcuts { dispatcher.register(literal(key.substring(1))); } } - for (String key : commandArgs.keySet()) { - if (key.startsWith("/")) { - dispatcher.register(literal(key.substring(1)).then(argument("args", StringArgumentType.greedyString()))); + for (Map.Entry<String, String> set : commandArgs.entrySet()) { + if (set.getKey().startsWith("/")) { + CommandNode<FabricClientCommandSource> redirectLocation = dispatcher.getRoot(); + for (String word : set.getValue().substring(1).split(" ")) { + redirectLocation = redirectLocation.getChild(word); + if (redirectLocation == null) { + break; + } + } + if (redirectLocation == null) { + dispatcher.register(literal(set.getKey().substring(1)).then(argument("args", StringArgumentType.greedyString()))); + } + else { + dispatcher.register(literal(set.getKey().substring(1)).redirect(redirectLocation)); + } } } dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("help").executes(context -> { 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 b37a3883..f182a949 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 @@ -73,6 +73,7 @@ public class Ico { public static final ItemStack EXPERIENCE_BOTTLE = new ItemStack(Items.EXPERIENCE_BOTTLE); public static final ItemStack PINK_DYE = new ItemStack(Items.PINK_DYE); public static final ItemStack LIME_DYE = new ItemStack(Items.LIME_DYE); + public static final ItemStack GRAY_DYE = new ItemStack(Items.GRAY_DYE); 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); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Waypoints.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Waypoints.java index 18096117..6866aba3 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Waypoints.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Waypoints.java @@ -11,6 +11,7 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.JsonOps; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.scheduler.Scheduler; import de.hysky.skyblocker.utils.waypoint.WaypointCategory; @@ -21,11 +22,14 @@ import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; import net.minecraft.client.toast.SystemToast; +import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Base64; @@ -33,6 +37,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.function.Function; +import java.util.zip.GZIPInputStream; import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; @@ -62,37 +67,42 @@ public class Waypoints { } } - public static List<WaypointCategory> fromSkytilsBase64(String base64, String defaultIsland) { + public static List<WaypointCategory> fromSkytils(String waypointsString, String defaultIsland) { try { - if (base64.startsWith("<Skytils-Waypoint-Data>(V")) { - int version = Integer.parseInt(base64.substring(26, base64.indexOf(')'))); + if (waypointsString.startsWith("<Skytils-Waypoint-Data>(V")) { + int version = Integer.parseInt(waypointsString.substring(25, waypointsString.indexOf(')'))); + waypointsString = waypointsString.substring(waypointsString.indexOf(':') + 1); if (version == 1) { - return fromSkytilsJson(new String(Base64.getDecoder().decode(base64.substring(base64.indexOf(':') + 1))), defaultIsland); + try (GZIPInputStream reader = new GZIPInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(waypointsString)))) { + return fromSkytilsJson(IOUtils.toString(reader, StandardCharsets.UTF_8), defaultIsland); + } } else { - LOGGER.error("[Skyblocker Waypoints] Unknown Skytils waypoint data version: " + version); + LOGGER.error("[Skyblocker Waypoints] Unknown Skytils waypoint data version: {}", version); } - } else return fromSkytilsJson(new String(Base64.getDecoder().decode(base64)), defaultIsland); + } else return fromSkytilsJson(new String(Base64.getDecoder().decode(waypointsString)), defaultIsland); } catch (NumberFormatException e) { LOGGER.error("[Skyblocker Waypoints] Encountered exception while parsing Skytils waypoint data version", e); - } catch (IllegalArgumentException e) { + } catch (Exception e) { LOGGER.error("[Skyblocker Waypoints] Encountered exception while decoding Skytils waypoint data", e); } return Collections.emptyList(); } - public static List<WaypointCategory> fromSkytilsJson(String waypointCategories, String defaultIsland) { + public static List<WaypointCategory> fromSkytilsJson(String waypointCategoriesString, String defaultIsland) { JsonArray waypointCategoriesJson; try { - waypointCategoriesJson = SkyblockerMod.GSON.fromJson(waypointCategories, JsonObject.class).getAsJsonArray("categories"); + waypointCategoriesJson = SkyblockerMod.GSON.fromJson(waypointCategoriesString, JsonObject.class).getAsJsonArray("categories"); } catch (JsonSyntaxException e) { + // Handle the case where there is only a single json list of waypoints and no category data. JsonObject waypointCategoryJson = new JsonObject(); waypointCategoryJson.addProperty("name", "New Category"); waypointCategoryJson.addProperty("island", defaultIsland); - waypointCategoryJson.add("waypoints", SkyblockerMod.GSON.fromJson(waypointCategories, JsonArray.class)); + waypointCategoryJson.add("waypoints", SkyblockerMod.GSON.fromJson(waypointCategoriesString, JsonArray.class)); waypointCategoriesJson = new JsonArray(); waypointCategoriesJson.add(waypointCategoryJson); } - return SKYTILS_CODEC.parse(JsonOps.INSTANCE, waypointCategoriesJson).resultOrPartial(LOGGER::error).orElseThrow(); + List<WaypointCategory> waypointCategories = SKYTILS_CODEC.parse(JsonOps.INSTANCE, waypointCategoriesJson).resultOrPartial(LOGGER::error).orElseThrow(); + return waypointCategories.stream().map(waypointCategory -> Location.from(waypointCategory.island()) == Location.UNKNOWN ? waypointCategory.withIsland(defaultIsland) : waypointCategory).toList(); } public static String toSkytilsBase64(List<WaypointCategory> waypointCategories) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsShareScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsShareScreen.java index aee21ec8..eaca845a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsShareScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsShareScreen.java @@ -31,7 +31,7 @@ public class WaypointsShareScreen extends AbstractWaypointsScreen<WaypointsScree GridWidget.Adder adder = gridWidget.createAdder(2); adder.add(ButtonWidget.builder(Text.translatable("skyblocker.waypoints.importWaypointsSkytils"), buttonImport -> { try { - List<WaypointCategory> waypointCategories = Waypoints.fromSkytilsBase64(client.keyboard.getClipboard(), island); + List<WaypointCategory> waypointCategories = Waypoints.fromSkytils(client.keyboard.getClipboard(), island); for (WaypointCategory waypointCategory : waypointCategories) { selectedWaypoints.addAll(waypointCategory.waypoints()); waypoints.put(waypointCategory.island(), waypointCategory); diff --git a/src/main/java/de/hysky/skyblocker/utils/TextTransformer.java b/src/main/java/de/hysky/skyblocker/utils/TextTransformer.java new file mode 100644 index 00000000..b8fb5101 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/TextTransformer.java @@ -0,0 +1,92 @@ +package de.hysky.skyblocker.utils; + +import org.jetbrains.annotations.NotNull; + +import it.unimi.dsi.fastutil.chars.CharList; +import net.minecraft.text.MutableText; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +/** + * Contains utilities for transforming text. These methods are from Aaron's Mod. + * + * @author AzureAaron + */ +public class TextTransformer { + private static final CharList FORMAT_CODES = CharList.of('4', 'c', '6', 'e', '2', 'a','b', '3', '1', '9', 'd', '5', 'f', '7', '8', '0', 'r', 'k', 'l', 'm', 'n', 'o'); + + /** + * Converts strings with section symbol/legacy formatting to MutableText objects. + * + * @param legacy The string with legacy formatting to be transformed + * @return A {@link MutableText} object matching the exact formatting of the input + * + * @author AzureAaron + */ + public static MutableText fromLegacy(@NotNull String legacy) { + MutableText newText = Text.empty(); + StringBuilder builder = new StringBuilder(); + Formatting formatting = null; + boolean bold = false; + boolean italic = false; + boolean underline = false; + boolean strikethrough = false; + boolean obfuscated = false; + + for (int i = 0; i < legacy.length(); i++) { + //If we've encountered a new formatting code then append the text from the previous "sequence" and reset state + if (i != 0 && legacy.charAt(i - 1) == '§' && FORMAT_CODES.contains(Character.toLowerCase(legacy.charAt(i))) && !builder.isEmpty()) { + newText.append(Text.literal(builder.toString()).setStyle(Style.EMPTY + .withColor(formatting) + .withBold(bold) + .withItalic(italic) + .withUnderline(underline) + .withStrikethrough(strikethrough) + .withObfuscated(obfuscated))); + + //Erase all characters in the builder so we can reuse it, also clear formatting + builder.delete(0, builder.length()); + formatting = null; + bold = false; + italic = false; + underline = false; + strikethrough = false; + obfuscated = false; + } + + if (i != 0 && legacy.charAt(i - 1) == '§') { + Formatting fmt = Formatting.byCode(legacy.charAt(i)); + + switch (fmt) { + case BOLD -> bold = true; + case ITALIC -> italic = true; + case UNDERLINE -> underline = true; + case STRIKETHROUGH -> strikethrough = true; + case OBFUSCATED -> obfuscated = true; + + default -> formatting = fmt; + } + + continue; + } + + //This character isn't the start of a formatting sequence or this character isn't part of a formatting sequence + if (legacy.charAt(i) != '§' && (i == 0 || (i != 0 && legacy.charAt(i - 1) != '§'))) { + builder.append(legacy.charAt(i)); + } + + // We've read the last character so append the last text with all of the formatting + if (i == legacy.length() - 1) { + newText.append(Text.literal(builder.toString()).setStyle(Style.EMPTY + .withColor(formatting) + .withBold(bold) + .withItalic(italic) + .withUnderline(underline) + .withStrikethrough(strikethrough) + .withObfuscated(obfuscated))); + } + } + return newText; + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java index 051110b2..bbeb11b5 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Utils.java +++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java @@ -6,21 +6,27 @@ import com.mojang.util.UndashedUuid; import de.hysky.skyblocker.events.SkyblockEvents; import de.hysky.skyblocker.mixins.accessors.MessageHandlerAccessor; import de.hysky.skyblocker.skyblock.item.MuseumItemCache; -import de.hysky.skyblocker.utils.scheduler.MessageScheduler; import de.hysky.skyblocker.utils.scheduler.Scheduler; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.azureaaron.hmapi.data.server.Environment; +import net.azureaaron.hmapi.events.HypixelPacketEvents; +import net.azureaaron.hmapi.network.HypixelNetworking; +import net.azureaaron.hmapi.network.packet.s2c.ErrorS2CPacket; +import net.azureaaron.hmapi.network.packet.s2c.HelloS2CPacket; +import net.azureaaron.hmapi.network.packet.s2c.HypixelS2CPacket; +import net.azureaaron.hmapi.network.packet.v1.s2c.LocationUpdateS2CPacket; import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; -import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.network.PlayerListEntry; import net.minecraft.scoreboard.*; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import net.minecraft.util.Util; + import org.apache.http.client.HttpResponseException; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; @@ -45,7 +51,7 @@ public class Utils { private static boolean isOnSkyblock = false; private static boolean isInjected = false; /** - * Current Skyblock location (from /locraw) + * Current Skyblock location (from the Mod API) */ @NotNull private static Location location = Location.UNKNOWN; @@ -60,10 +66,12 @@ public class Utils { @NotNull private static String profileId = ""; /** - * The following fields store data returned from /locraw: {@link #server}, {@link #gameType}, {@link #locationRaw}, and {@link #map}. + * The following fields store data returned from the Mod API: {@link #environment}, {@link #server}, {@link #gameType}, {@link #locationRaw}, and {@link #map}. */ @SuppressWarnings("JavadocDeclaration") @NotNull + private static Environment environment = Environment.PRODUCTION; + @NotNull private static String server = ""; @NotNull private static String gameType = ""; @@ -71,11 +79,6 @@ public class Utils { private static String locationRaw = ""; @NotNull private static String map = ""; - private static long clientWorldJoinTime = 0; - private static boolean sentLocRaw = false; - private static boolean canSendLocRaw = false; - //This is required to prevent the location change event from being fired twice. - private static boolean locationChanged = true; private static boolean mayorTickScheduled = false; private static int mayorTickRetryAttempts = 0; private static String mayor = ""; @@ -146,7 +149,7 @@ public class Utils { } /** - * @return the location parsed from /locraw. + * @return the location parsed from the Mod API. */ @NotNull public static Location getLocation() { @@ -154,7 +157,17 @@ public class Utils { } /** - * @return the server parsed from /locraw. + * Can be used to restrict features to being active only on the Alpha network. + * + * @return the current environment parsed from the Mod API. + */ + @NotNull + public static Environment getEnvironment() { + return environment; + } + + /** + * @return the server parsed from the Mod API. */ @NotNull public static String getServer() { @@ -162,7 +175,7 @@ public class Utils { } /** - * @return the game type parsed from /locraw. + * @return the game type parsed from the Mod API. */ @NotNull public static String getGameType() { @@ -170,7 +183,7 @@ public class Utils { } /** - * @return the location raw parsed from /locraw. + * @return the location raw parsed from the the Mod API. */ @NotNull public static String getLocationRaw() { @@ -178,7 +191,7 @@ public class Utils { } /** - * @return the map parsed from /locraw. + * @return the map parsed from the Mod API. */ @NotNull public static String getMap() { @@ -201,20 +214,23 @@ public class Utils { mayorTickScheduled = true; } }); - ClientPlayConnectionEvents.JOIN.register(Utils::onClientWorldJoin); ClientReceiveMessageEvents.ALLOW_GAME.register(Utils::onChatMessage); ClientReceiveMessageEvents.GAME_CANCELED.register(Utils::onChatMessage); // Somehow this works even though onChatMessage returns a boolean + + //Register Mod API stuff + HypixelNetworking.registerToEvents(Util.make(new Object2IntOpenHashMap<>(), map -> map.put(LocationUpdateS2CPacket.ID, 1))); + HypixelPacketEvents.HELLO.register(Utils::onPacket); + HypixelPacketEvents.LOCATION_UPDATE.register(Utils::onPacket); } /** - * Updates all the fields stored in this class from the sidebar, player list, and /locraw. + * Updates all the fields stored in this class from the sidebar, and player list. */ public static void update() { MinecraftClient client = MinecraftClient.getInstance(); updateScoreboard(client); updatePlayerPresenceFromScoreboard(client); updateFromPlayerList(client); - updateLocRaw(); } /** @@ -244,7 +260,7 @@ public class Utils { if (!isInjected) { isInjected = true; } - isOnSkyblock = true; + isOnSkyblock = true; //TODO in the future we can probably replace these skyblock checks entirely with the Mod API SkyblockEvents.JOIN.invoker().onSkyblockJoin(); } } else { @@ -260,7 +276,7 @@ public class Utils { String serverAddress = (client.getCurrentServerEntry() != null) ? client.getCurrentServerEntry().address.toLowerCase() : ""; String serverBrand = (client.player != null && client.player.networkHandler != null && client.player.networkHandler.getBrand() != null) ? client.player.networkHandler.getBrand() : ""; - return serverAddress.equalsIgnoreCase(ALTERNATE_HYPIXEL_ADDRESS) || serverAddress.contains("hypixel.net") || serverAddress.contains("hypixel.io") || serverBrand.contains("Hypixel BungeeCord"); + return (!serverAddress.isEmpty() && serverAddress.equalsIgnoreCase(ALTERNATE_HYPIXEL_ADDRESS)) || serverAddress.contains("hypixel.net") || serverAddress.contains("hypixel.io") || serverBrand.contains("Hypixel BungeeCord"); } private static void onLeaveSkyblock() { @@ -395,25 +411,39 @@ public class Utils { } } - public static void onClientWorldJoin(ClientPlayNetworkHandler handler, PacketSender sender, MinecraftClient client) { - clientWorldJoinTime = System.currentTimeMillis(); - resetLocRawInfo(); - } + private static void onPacket(HypixelS2CPacket packet) { + switch (packet) { + case HelloS2CPacket(var environment) -> { + Utils.environment = environment; + } - /** - * Sends /locraw to the server if the player is on skyblock and on a new island. - */ - private static void updateLocRaw() { - if (isOnSkyblock) { - long currentTime = System.currentTimeMillis(); - if (!sentLocRaw && canSendLocRaw && currentTime > clientWorldJoinTime + 1000) { - MessageScheduler.INSTANCE.sendMessageAfterCooldown("/locraw"); - sentLocRaw = true; - canSendLocRaw = false; - locationChanged = true; + case LocationUpdateS2CPacket(var serverName, var serverType, var _lobbyName, var mode, var map) -> { + Utils.server = serverName; + Utils.gameType = serverType.orElse(""); + Utils.locationRaw = mode.orElse(""); + Utils.location = Location.from(locationRaw); + Utils.map = map.orElse(""); + + SkyblockEvents.LOCATION_CHANGE.invoker().onSkyblockLocationChange(location); } - } else { - resetLocRawInfo(); + + case ErrorS2CPacket(var id, var error) when id.equals(LocationUpdateS2CPacket.ID) -> { + server = ""; + gameType = ""; + locationRaw = ""; + location = Location.UNKNOWN; + map = ""; + + ClientPlayerEntity player = MinecraftClient.getInstance().player; + + if (player != null) { + player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.utils.locationUpdateError").formatted(Formatting.RED))); + } + + LOGGER.error("[Skyblocker] Failed to update your current location! Some features of the mod may not work correctly :( - Error: {}", error); + } + + default -> {} //Do Nothing } } @@ -423,6 +453,7 @@ public class Utils { * * @param message json message from chat */ + @Deprecated private static void parseLocRaw(String message) { JsonObject locRaw = JsonParser.parseString(message).getAsJsonObject(); @@ -441,11 +472,6 @@ public class Utils { if (locRaw.has("map")) { map = locRaw.get("map").getAsString(); } - - if (locationChanged) { - SkyblockEvents.LOCATION_CHANGE.invoker().onSkyblockLocationChange(location); - locationChanged = false; - } } /** @@ -458,10 +484,6 @@ public class Utils { if (message.startsWith("{\"server\":") && message.endsWith("}")) { parseLocRaw(message); - boolean shouldFilter = !sentLocRaw; - sentLocRaw = false; - - return shouldFilter; } if (isOnSkyblock) { @@ -481,16 +503,6 @@ public class Utils { return true; } - private static void resetLocRawInfo() { - sentLocRaw = false; - canSendLocRaw = true; - server = ""; - gameType = ""; - locationRaw = ""; - map = ""; - location = Location.UNKNOWN; - } - private static void scheduleMayorTick() { long currentYearMillis = SkyblockTime.getSkyblockMillis() % 446400000L; //446400000ms is 1 year, 105600000ms is the amount of time from early spring 1st to late spring 27th // If current time is past late spring 27th, the next mayor change is at next year's spring 27th, otherwise it's at this year's spring 27th diff --git a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java index a5b9bf6b..1b16b138 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java @@ -11,6 +11,7 @@ import de.hysky.skyblocker.utils.render.title.TitleContainer; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; import net.fabricmc.fabric.api.event.Event; +import net.minecraft.block.BlockState; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; @@ -20,6 +21,7 @@ import net.minecraft.client.texture.Scaling; import net.minecraft.client.texture.Sprite; import net.minecraft.client.util.BufferAllocator; import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.client.world.ClientWorld; import net.minecraft.sound.SoundEvents; import net.minecraft.text.OrderedText; import net.minecraft.text.Text; @@ -64,18 +66,22 @@ public class RenderHelper { } public static void renderFilled(WorldRenderContext context, BlockPos pos, Vec3d dimensions, float[] colorComponents, float alpha, boolean throughWalls) { + renderFilled(context, Vec3d.of(pos), dimensions, colorComponents, alpha, throughWalls); + } + + public static void renderFilled(WorldRenderContext context, Vec3d pos, Vec3d dimensions, float[] colorComponents, float alpha, boolean throughWalls) { if (throughWalls) { if (FrustumUtils.isVisible(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + dimensions.x, pos.getY() + dimensions.y, pos.getZ() + dimensions.z)) { - renderFilled(context, Vec3d.of(pos), dimensions, colorComponents, alpha, true); + renderFilledInternal(context, pos, dimensions, colorComponents, alpha, true); } } else { if (OcclusionCulling.getRegularCuller().isVisible(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + dimensions.x, pos.getY() + dimensions.y, pos.getZ() + dimensions.z)) { - renderFilled(context, Vec3d.of(pos), dimensions, colorComponents, alpha, false); + renderFilledInternal(context, pos, dimensions, colorComponents, alpha, false); } } } - private static void renderFilled(WorldRenderContext context, Vec3d pos, Vec3d dimensions, float[] colorComponents, float alpha, boolean throughWalls) { + private static void renderFilledInternal(WorldRenderContext context, Vec3d pos, Vec3d dimensions, float[] colorComponents, float alpha, boolean throughWalls) { MatrixStack matrices = context.matrixStack(); Vec3d camera = context.camera().getPos(); @@ -330,6 +336,14 @@ public class RenderHelper { } } + public static Box getBlockBoundingBox(ClientWorld world, BlockPos pos) { + return getBlockBoundingBox(world, world.getBlockState(pos), pos); + } + + public static Box getBlockBoundingBox(ClientWorld world, BlockState state, BlockPos pos) { + return state.getOutlineShape(world, pos).asCuboid().getBoundingBox().offset(pos); + } + /** * Adds the title to {@link TitleContainer} and {@link #playNotificationSound() plays the notification sound} if the title is not in the {@link TitleContainer} already. * No checking needs to be done on whether the title is in the {@link TitleContainer} already by the caller. diff --git a/src/main/java/de/hysky/skyblocker/utils/waypoint/NamedWaypoint.java b/src/main/java/de/hysky/skyblocker/utils/waypoint/NamedWaypoint.java index 2f02b51f..5ba920c0 100644 --- a/src/main/java/de/hysky/skyblocker/utils/waypoint/NamedWaypoint.java +++ b/src/main/java/de/hysky/skyblocker/utils/waypoint/NamedWaypoint.java @@ -6,11 +6,13 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.codecs.RecordCodecBuilder; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.render.RenderHelper; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; import net.minecraft.text.Text; import net.minecraft.text.TextCodecs; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ColorHelper; import net.minecraft.util.math.Vec3d; import java.util.Objects; @@ -32,7 +34,7 @@ public class NamedWaypoint extends Waypoint { Codec.INT.fieldOf("y").forGetter(waypoint -> waypoint.pos.getY()), Codec.INT.fieldOf("z").forGetter(waypoint -> waypoint.pos.getZ()), Codec.either(Codec.STRING, Codec.INT).xmap(either -> either.map(str -> str, Object::toString), Either::left).fieldOf("name").forGetter(waypoint -> waypoint.name.getString()), - Codec.INT.fieldOf("color").forGetter(waypoint -> (int) (waypoint.alpha * 255) << 24 | (int) (waypoint.colorComponents[0] * 255) << 16 | (int) (waypoint.colorComponents[1] * 255) << 8 | (int) (waypoint.colorComponents[2] * 255)), + Codec.INT.optionalFieldOf("color", ColorHelper.Argb.getArgb(128, 0, 255, 0)).forGetter(waypoint -> (int) (waypoint.alpha * 255) << 24 | (int) (waypoint.colorComponents[0] * 255) << 16 | (int) (waypoint.colorComponents[1] * 255) << 8 | (int) (waypoint.colorComponents[2] * 255)), Codec.BOOL.fieldOf("enabled").forGetter(Waypoint::shouldRender) ).apply(instance, NamedWaypoint::fromSkytils)); public final Text name; @@ -69,7 +71,7 @@ public class NamedWaypoint extends Waypoint { if (alpha == 0) { alpha = DEFAULT_HIGHLIGHT_ALPHA; } - return new NamedWaypoint(new BlockPos(x, y, z), name, new float[]{((color & 0x00FF0000) >> 16) / 255f, ((color & 0x0000FF00) >> 8) / 255f, (color & 0x000000FF) / 255f}, alpha, enabled); + return new NamedWaypoint(new BlockPos(x, y, z), name, ColorUtils.getFloatComponents(color), alpha, enabled); } public NamedWaypoint copy() { diff --git a/src/main/java/de/hysky/skyblocker/utils/waypoint/WaypointCategory.java b/src/main/java/de/hysky/skyblocker/utils/waypoint/WaypointCategory.java index db2a6d82..8bfef7f4 100644 --- a/src/main/java/de/hysky/skyblocker/utils/waypoint/WaypointCategory.java +++ b/src/main/java/de/hysky/skyblocker/utils/waypoint/WaypointCategory.java @@ -29,6 +29,10 @@ public record WaypointCategory(String name, String island, List<NamedWaypoint> w return new WaypointCategory(name, island(), waypoints()); } + public WaypointCategory withIsland(String island) { + return new WaypointCategory(name(), island, waypoints()); + } + public WaypointCategory deepCopy() { return new WaypointCategory(name(), island(), waypoints().stream().map(NamedWaypoint::copy).collect(Collectors.toList())); } |