diff options
Diffstat (limited to 'src/main/java/de/hysky')
44 files changed, 1188 insertions, 146 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 56dbca94..ec2c561c 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java @@ -2,6 +2,7 @@ package de.hysky.skyblocker.config.categories; import de.hysky.skyblocker.config.ConfigUtils; import de.hysky.skyblocker.config.SkyblockerConfig; +import de.hysky.skyblocker.skyblock.bazaar.BazaarHelper; import de.hysky.skyblocker.utils.waypoint.Waypoint; import dev.isxander.yacl3.api.ConfigCategory; import dev.isxander.yacl3.api.Option; @@ -206,6 +207,20 @@ public class HelperCategory { .build()) .build()) + //Bazaar + .group(OptionGroup.createBuilder() + .name(Text.translatable("skyblocker.config.helpers.bazaar")) + .collapsed(true) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.helpers.bazaar.enableBazaarHelper")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.helpers.bazaar.enableBazaarHelper.@Tooltip", BazaarHelper.getExpiringIcon(), BazaarHelper.getExpiredIcon(), BazaarHelper.getFilledIcon(69), BazaarHelper.getFilledIcon(100)))) + .binding(defaults.helpers.bazaar.enableBazaarHelper, + () -> config.helpers.bazaar.enableBazaarHelper, + newValue -> config.helpers.bazaar.enableBazaarHelper = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .build()) + .build(); } } 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 636d76da..e009f680 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java @@ -26,6 +26,9 @@ public class HelperConfig { @SerialEntry public ChocolateFactory chocolateFactory = new ChocolateFactory(); + @SerialEntry + public Bazaar bazaar = new Bazaar(); + public static class MythologicalRitual { @SerialEntry public boolean enableMythologicalRitualHelper = true; @@ -94,4 +97,9 @@ public class HelperConfig { @SerialEntry public boolean straySound = true; } + + public static class Bazaar { + @SerialEntry + public boolean enableBazaarHelper = true; + } } 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/bazaar/BazaarHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/bazaar/BazaarHelper.java new file mode 100644 index 00000000..8b83b06b --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/bazaar/BazaarHelper.java @@ -0,0 +1,73 @@ +package de.hysky.skyblocker.skyblock.bazaar; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.math.NumberUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class BazaarHelper extends SlotTextAdder { + private static final Pattern FILLED_PATTERN = Pattern.compile("Filled: \\S+ \\(?([\\d.]+)%\\)?!?"); + private static final int RED = 0xe60b1e; + private static final int YELLOW = 0xe6ba0b; + private static final int GREEN = 0x1ee60b; + + public BazaarHelper() { + super("(?:Co-op|Your) Bazaar Orders"); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + if (!SkyblockerConfigManager.get().helpers.bazaar.enableBazaarHelper) return List.of(); + // Skip the first row as it's always glass panes. + if (slot.id < 10) return List.of(); + // Skip the last 10 items. 11 is subtracted because size is 1-based so the last slot is size - 1. + if (slot.id > slot.inventory.size() - 11) return List.of(); //Note that this also skips the slots in player's inventory (anything above 36/45/54 depending on the order count) + + int column = slot.id % 9; + if (column == 0 || column == 8) return List.of(); // Skip the first and last column as those are always glass panes as well. + + ItemStack item = slot.getStack(); + if (item.isEmpty()) return List.of(); //We've skipped all invalid slots, so we can just check if it's not air here. + + Matcher matcher = ItemUtils.getLoreLineIfMatch(item, FILLED_PATTERN); + if (matcher != null) { + List<Text> lore = ItemUtils.getLore(item); + if (!lore.isEmpty() && lore.getLast().getString().equals("Click to claim!")) { //Only show the filled icon when there are items to claim + int filled = NumberUtils.toInt(matcher.group(1)); + return SlotText.topLeftList(getFilledIcon(filled)); + } + } + + if (ItemUtils.getLoreLineIf(item, str -> str.equals("Expired!")) != null) { + return SlotText.topLeftList(getExpiredIcon()); + } else if (ItemUtils.getLoreLineIf(item, str -> str.startsWith("Expires in")) != null) { + return SlotText.topLeftList(getExpiringIcon()); + } + + return List.of(); + } + + public static @NotNull MutableText getExpiredIcon() { + return Text.literal("⏰").withColor(RED).formatted(Formatting.BOLD); + } + + public static @NotNull MutableText getExpiringIcon() { + return Text.literal("⏰").withColor(YELLOW).formatted(Formatting.BOLD); + } + + public static @NotNull MutableText getFilledIcon(int filled) { + if (filled < 100) return Text.literal("%").withColor(YELLOW).formatted(Formatting.BOLD); + return Text.literal("✅").withColor(GREEN).formatted(Formatting.BOLD); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/bazaar/ReorderHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/bazaar/ReorderHelper.java new file mode 100644 index 00000000..c2b11926 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/bazaar/ReorderHelper.java @@ -0,0 +1,73 @@ +package de.hysky.skyblocker.skyblock.bazaar; + +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.render.gui.ColorHighlight; +import de.hysky.skyblocker.utils.render.gui.ContainerSolver; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.util.InputUtil; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.glfw.GLFW; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ReorderHelper extends ContainerSolver { + private static final Pattern BUY_PATTERN = Pattern.compile("([\\d,]+)x missing items\\."); + private static final Pattern SELL_PATTERN = Pattern.compile("([\\d,]+)x items\\."); + + public ReorderHelper() { + super("^Order options"); + } + + @Override + protected boolean isEnabled() { + return true; + } + + @Override + protected boolean onClickSlot(int slot, ItemStack stack, int screenId, String[] groups) { + // V This part is so that it short-circuits if not necessary + if ((slot == 11 || slot == 13) && stack.isOf(Items.GREEN_TERRACOTTA) && InputUtil.isKeyPressed(MinecraftClient.getInstance().getWindow().getHandle(), GLFW.GLFW_KEY_LEFT_CONTROL)) { + Matcher matcher; + // The terracotta is at slot 13 on sell orders and at slot 11 on buy orders + if (slot == 13) matcher = ItemUtils.getLoreLineIfContainsMatch(stack, SELL_PATTERN); + else matcher = ItemUtils.getLoreLineIfContainsMatch(stack, BUY_PATTERN); + if (matcher != null) { + MinecraftClient.getInstance().keyboard.setClipboard(matcher.group(1).replace(",", "")); + return false; + } + } + return false; + } + + @Override + protected List<ColorHighlight> getColors(String[] groups, Int2ObjectMap<ItemStack> slots) { + return List.of(); + } + + public static class Tooltip extends TooltipAdder { + public Tooltip() { + super("^Order options", Integer.MIN_VALUE); + } + + @Override + public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) { + if (focusedSlot == null || !stack.isOf(Items.GREEN_TERRACOTTA)) return; + switch (focusedSlot.id) { + case 11, 13 -> { + lines.add(Text.empty()); + lines.add(Text.empty().append(Text.translatable("skyblocker.reorderHelper.tooltip.line1")).formatted(Formatting.DARK_GRAY, Formatting.ITALIC)); + lines.add(Text.empty().append(Text.translatable("skyblocker.reorderHelper.tooltip.line2")).formatted(Formatting.DARK_GRAY, Formatting.ITALIC)); + } + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java index 620da37c..6926fda8 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java @@ -1,16 +1,16 @@ package de.hysky.skyblocker.skyblock.chocolatefactory; import com.mojang.brigadier.Command; -import com.mojang.brigadier.arguments.IntegerArgumentType; -import com.mojang.brigadier.arguments.StringArgumentType; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.events.SkyblockEvents; import de.hysky.skyblocker.utils.*; +import de.hysky.skyblocker.utils.command.argumenttypes.EggTypeArgumentType; +import de.hysky.skyblocker.utils.command.argumenttypes.blockpos.ClientBlockPosArgumentType; +import de.hysky.skyblocker.utils.command.argumenttypes.blockpos.ClientPosArgument; import de.hysky.skyblocker.utils.scheduler.MessageScheduler; import de.hysky.skyblocker.utils.waypoint.Waypoint; import it.unimi.dsi.fastutil.objects.ObjectImmutableList; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; @@ -24,6 +24,7 @@ import net.minecraft.text.ClickEvent; import net.minecraft.text.HoverEvent; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import org.apache.commons.lang3.text.WordUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,6 +32,9 @@ import java.util.LinkedList; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; + public class EggFinder { private static final Pattern eggFoundPattern = Pattern.compile("^(?:HOPPITY'S HUNT You found a Chocolate|You have already collected this Chocolate) (Breakfast|Lunch|Dinner)"); private static final Pattern newEggPattern = Pattern.compile("^HOPPITY'S HUNT A Chocolate (Breakfast|Lunch|Dinner) Egg has appeared!$"); @@ -47,17 +51,15 @@ public class EggFinder { SkyblockEvents.LOCATION_CHANGE.register(EggFinder::handleLocationChange); ClientReceiveMessageEvents.GAME.register(EggFinder::onChatMessage); WorldRenderEvents.AFTER_TRANSLUCENT.register(EggFinder::renderWaypoints); - ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal(SkyblockerMod.NAMESPACE) - .then(ClientCommandManager.literal("eggFinder") - .then(ClientCommandManager.literal("shareLocation") - .then(ClientCommandManager.argument("x", IntegerArgumentType.integer()) - .then(ClientCommandManager.argument("y", IntegerArgumentType.integer()) - .then(ClientCommandManager.argument("z", IntegerArgumentType.integer()) - .then(ClientCommandManager.argument("eggType", StringArgumentType.word()) - .executes(context -> { - MessageScheduler.INSTANCE.sendMessageAfterCooldown("[Skyblocker] Chocolate " + context.getArgument("eggType", String.class) + " Egg found at " + context.getArgument("x", Integer.class) + " " + context.getArgument("y", Integer.class) + " " + context.getArgument("z", Integer.class) + "!"); - return Command.SINGLE_SUCCESS; - }))))))))); + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE) + .then(literal("eggFinder") + .then(literal("shareLocation") + .then(argument("blockPos", ClientBlockPosArgumentType.blockPos()) + .then(argument("eggType", EggTypeArgumentType.eggType()) + .executes(context -> { + MessageScheduler.INSTANCE.sendMessageAfterCooldown("[Skyblocker] Chocolate " + context.getArgument("eggType", EggType.class) + " Egg found at " + context.getArgument("blockPos", ClientPosArgument.class).toAbsoluteBlockPos(context.getSource()).toShortString() + "!"); + return Command.SINGLE_SUCCESS; + }))))))); } private static void handleLocationChange(Location location) { @@ -161,7 +163,7 @@ public class EggFinder { record Egg(ArmorStandEntity entity, Waypoint waypoint) {} @SuppressWarnings("DataFlowIssue") //Removes that pesky "unboxing of Integer might cause NPE" warning when we already know it's not null - enum EggType { + public enum EggType { LUNCH(Formatting.BLUE.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjU2ODExMiwKICAicHJvZmlsZUlkIiA6ICI3NzUwYzFhNTM5M2Q0ZWQ0Yjc2NmQ4ZGUwOWY4MjU0NiIsCiAgInByb2ZpbGVOYW1lIiA6ICJSZWVkcmVsIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdhZTZkMmQzMWQ4MTY3YmNhZjk1MjkzYjY4YTRhY2Q4NzJkNjZlNzUxZGI1YTM0ZjJjYmM2NzY2YTAzNTZkMGEiCiAgICB9CiAgfQp9"), DINNER(Formatting.GREEN.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjY0OTcwMSwKICAicHJvZmlsZUlkIiA6ICI3NGEwMzQxNWY1OTI0ZTA4YjMyMGM2MmU1NGE3ZjJhYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNZXp6aXIiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTVlMzYxNjU4MTlmZDI4NTBmOTg1NTJlZGNkNzYzZmY5ODYzMTMxMTkyODNjMTI2YWNlMGM0Y2M0OTVlNzZhOCIKICAgIH0KICB9Cn0"), BREAKFAST(Formatting.GOLD.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjY3MzE0OSwKICAicHJvZmlsZUlkIiA6ICJiN2I4ZTlhZjEwZGE0NjFmOTY2YTQxM2RmOWJiM2U4OCIsCiAgInByb2ZpbGVOYW1lIiA6ICJBbmFiYW5hbmFZZzciLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTQ5MzMzZDg1YjhhMzE1ZDAzMzZlYjJkZjM3ZDhhNzE0Y2EyNGM1MWI4YzYwNzRmMWI1YjkyN2RlYjUxNmMyNCIKICAgIH0KICB9Cn0"); @@ -179,7 +181,7 @@ public class EggFinder { private long messageLastSent = 0; //This is to not create an array each time we iterate over the values - public static final ObjectImmutableList<EggType> entries = ObjectImmutableList.of(BREAKFAST, LUNCH, DINNER); + public static final ObjectImmutableList<EggType> entries = ObjectImmutableList.of(EggType.values()); EggType(int color, String texture) { this.color = color; @@ -187,12 +189,9 @@ public class EggFinder { } @Override + @SuppressWarnings("deprecation") // It's either a new dependency or a deprecated method, and I'd rather use the deprecated method public String toString() { - return switch (this) { - case LUNCH -> "Lunch"; - case DINNER -> "Dinner"; - case BREAKFAST -> "Breakfast"; - }; + return WordUtils.capitalizeFully(this.name()); } } } 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/dungeon/secrets/DungeonManager.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java index b9986731..11f31f34 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java @@ -12,14 +12,16 @@ import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.serialization.JsonOps; import de.hysky.skyblocker.SkyblockerMod; -import de.hysky.skyblocker.config.configs.DungeonsConfig; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.config.configs.DungeonsConfig; import de.hysky.skyblocker.debug.Debug; import de.hysky.skyblocker.skyblock.dungeon.DungeonBoss; import de.hysky.skyblocker.skyblock.dungeon.DungeonMap; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.Tickable; import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.command.argumenttypes.blockpos.ClientBlockPosArgumentType; +import de.hysky.skyblocker.utils.command.argumenttypes.blockpos.ClientPosArgument; import de.hysky.skyblocker.utils.scheduler.Scheduler; import it.unimi.dsi.fastutil.objects.Object2ByteMap; import it.unimi.dsi.fastutil.objects.Object2ByteMaps; @@ -37,8 +39,6 @@ import net.minecraft.block.Blocks; import net.minecraft.client.MinecraftClient; import net.minecraft.command.CommandRegistryAccess; import net.minecraft.command.CommandSource; -import net.minecraft.command.argument.BlockPosArgumentType; -import net.minecraft.command.argument.PosArgument; import net.minecraft.command.argument.TextArgumentType; import net.minecraft.component.DataComponentTypes; import net.minecraft.entity.Entity; @@ -53,7 +53,6 @@ import net.minecraft.item.Items; import net.minecraft.item.map.MapState; import net.minecraft.registry.Registry; import net.minecraft.resource.Resource; -import net.minecraft.server.command.ServerCommandSource; import net.minecraft.text.Text; import net.minecraft.util.ActionResult; import net.minecraft.util.Identifier; @@ -387,13 +386,12 @@ public class DungeonManager { return Command.SINGLE_SUCCESS; } - private static RequiredArgumentBuilder<FabricClientCommandSource, PosArgument> addCustomWaypointCommand(boolean relative, CommandRegistryAccess registryAccess) { - return argument("pos", BlockPosArgumentType.blockPos()) + private static RequiredArgumentBuilder<FabricClientCommandSource, ClientPosArgument> addCustomWaypointCommand(boolean relative, CommandRegistryAccess registryAccess) { + return argument("pos", ClientBlockPosArgumentType.blockPos()) .then(argument("secretIndex", IntegerArgumentType.integer()) .then(argument("category", SecretWaypoint.Category.CategoryArgumentType.category()) .then(argument("name", TextArgumentType.text(registryAccess)).executes(context -> { - // TODO Less hacky way with custom ClientBlockPosArgumentType - BlockPos pos = context.getArgument("pos", PosArgument.class).toAbsoluteBlockPos(new ServerCommandSource(null, context.getSource().getPosition(), context.getSource().getRotation(), null, 0, null, null, null, null)); + BlockPos pos = context.getArgument("pos", ClientPosArgument.class).toAbsoluteBlockPos(context.getSource()); return relative ? addCustomWaypointRelative(context, pos) : addCustomWaypoint(context, pos); })) ) @@ -419,11 +417,10 @@ public class DungeonManager { return Command.SINGLE_SUCCESS; } - private static RequiredArgumentBuilder<FabricClientCommandSource, PosArgument> removeCustomWaypointCommand(boolean relative) { - return argument("pos", BlockPosArgumentType.blockPos()) + private static RequiredArgumentBuilder<FabricClientCommandSource, ClientPosArgument> removeCustomWaypointCommand(boolean relative) { + return argument("pos", ClientBlockPosArgumentType.blockPos()) .executes(context -> { - // TODO Less hacky way with custom ClientBlockPosArgumentType - BlockPos pos = context.getArgument("pos", PosArgument.class).toAbsoluteBlockPos(new ServerCommandSource(null, context.getSource().getPosition(), context.getSource().getRotation(), null, 0, null, null, null, null)); + BlockPos pos = context.getArgument("pos", ClientPosArgument.class).toAbsoluteBlockPos(context.getSource()); return relative ? removeCustomWaypointRelative(context, pos) : removeCustomWaypoint(context, pos); }); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java index d709181f..83167c18 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java @@ -8,6 +8,8 @@ import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.command.argumenttypes.blockpos.ClientBlockPosArgumentType; +import de.hysky.skyblocker.utils.command.argumenttypes.blockpos.ClientPosArgument; import de.hysky.skyblocker.utils.scheduler.MessageScheduler; import de.hysky.skyblocker.utils.scheduler.Scheduler; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; @@ -18,9 +20,6 @@ import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; import net.minecraft.client.MinecraftClient; import net.minecraft.command.CommandRegistryAccess; -import net.minecraft.command.argument.BlockPosArgumentType; -import net.minecraft.command.argument.PosArgument; -import net.minecraft.server.command.ServerCommandSource; import net.minecraft.text.ClickEvent; import net.minecraft.text.MutableText; import net.minecraft.text.Text; @@ -123,10 +122,10 @@ public class CrystalsLocationsManager { private static void registerWaypointLocationCommands(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) { dispatcher.register(literal(SkyblockerMod.NAMESPACE) .then(literal("crystalWaypoints") - .then(argument("pos", BlockPosArgumentType.blockPos()) + .then(argument("pos", ClientBlockPosArgumentType.blockPos()) .then(argument("place", StringArgumentType.greedyString()) .suggests((context, builder) -> suggestMatching(WAYPOINT_LOCATIONS.keySet(), builder)) - .executes(context -> addWaypointFromCommand(context.getSource(), getString(context, "place"), context.getArgument("pos", PosArgument.class))) + .executes(context -> addWaypointFromCommand(context.getSource(), getString(context, "place"), context.getArgument("pos", ClientPosArgument.class))) ) ) .then(literal("share") @@ -160,9 +159,8 @@ public class CrystalsLocationsManager { return text; } - public static int addWaypointFromCommand(FabricClientCommandSource source, String place, PosArgument location) { - // TODO Less hacky way with custom ClientBlockPosArgumentType - BlockPos blockPos = location.toAbsoluteBlockPos(new ServerCommandSource(null, source.getPosition(), source.getRotation(), null, 0, null, null, null, null)); + public static int addWaypointFromCommand(FabricClientCommandSource source, String place, ClientPosArgument location) { + BlockPos blockPos = location.toAbsoluteBlockPos(source); if (WAYPOINT_LOCATIONS.containsKey(place)) { addCustomWaypoint(place, blockPos); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/filters/SimpleChatFilter.java b/src/main/java/de/hysky/skyblocker/skyblock/filters/SimpleChatFilter.java index 025b3dce..2521b3a9 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/filters/SimpleChatFilter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/filters/SimpleChatFilter.java @@ -2,11 +2,12 @@ package de.hysky.skyblocker.skyblock.filters; import de.hysky.skyblocker.utils.chat.ChatPatternListener; import net.minecraft.text.Text; +import org.intellij.lang.annotations.Language; import java.util.regex.Matcher; public abstract class SimpleChatFilter extends ChatPatternListener { - public SimpleChatFilter(String pattern) { + protected SimpleChatFilter(@Language("RegExp") String pattern) { super(pattern); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorAnimatedDyes.java b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorAnimatedDyes.java index 76e7f02c..56ce7bf8 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorAnimatedDyes.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorAnimatedDyes.java @@ -1,20 +1,15 @@ package de.hysky.skyblocker.skyblock.item; -import static com.mojang.brigadier.arguments.StringArgumentType.getString; -import static com.mojang.brigadier.arguments.StringArgumentType.word; -import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument; -import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; - import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType; - import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.command.argumenttypes.color.ColorArgumentType; import dev.isxander.yacl3.config.v2.api.SerialEntry; import it.unimi.dsi.fastutil.objects.Object2ObjectFunction; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -27,6 +22,9 @@ import net.minecraft.registry.tag.ItemTags; import net.minecraft.text.Text; import net.minecraft.util.math.MathHelper; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; + public class CustomArmorAnimatedDyes { private static final Object2ObjectOpenHashMap<AnimatedDye, AnimatedDyeStateTracker> STATE_TRACKER_MAP = new Object2ObjectOpenHashMap<>(); private static final Object2ObjectFunction<AnimatedDye, AnimatedDyeStateTracker> NEW_STATE_TRACKER = _dye -> AnimatedDyeStateTracker.create(); @@ -42,23 +40,17 @@ public class CustomArmorAnimatedDyes { dispatcher.register(literal(SkyblockerMod.NAMESPACE) .then(literal("custom") .then(literal("animatedDye") - .executes(context -> customizeAnimatedDye(context.getSource(), null, null, 0, false, 0)) - .then(argument("hex1", word()) - .then(argument("hex2", word()) + .executes(context -> customizeAnimatedDye(context.getSource(), Integer.MIN_VALUE, Integer.MIN_VALUE, 0, false, 0)) + .then(argument("hex1", ColorArgumentType.hex()) + .then(argument("hex2", ColorArgumentType.hex()) .then(argument("samples", IntegerArgumentType.integer(1)) .then(argument("cycleBack", BoolArgumentType.bool()) - .executes(context -> customizeAnimatedDye(context.getSource(), getString(context, "hex1"), getString(context, "hex2"), IntegerArgumentType.getInteger(context, "samples"), BoolArgumentType.getBool(context, "cycleBack"), DEFAULT_TICK_DELAY)) + .executes(context -> customizeAnimatedDye(context.getSource(), ColorArgumentType.getIntFromHex(context, "hex1"), ColorArgumentType.getIntFromHex(context, "hex2"), IntegerArgumentType.getInteger(context, "samples"), BoolArgumentType.getBool(context, "cycleBack"), DEFAULT_TICK_DELAY)) .then(argument("tickDelay", IntegerArgumentType.integer(0, 20)) - .executes(context ->customizeAnimatedDye(context.getSource(), getString(context, "hex1"), getString(context, "hex2"), IntegerArgumentType.getInteger(context, "samples"), BoolArgumentType.getBool(context, "cycleBack"), IntegerArgumentType.getInteger(context, "tickDelay"))))))))))); + .executes(context ->customizeAnimatedDye(context.getSource(), ColorArgumentType.getIntFromHex(context, "hex1"), ColorArgumentType.getIntFromHex(context, "hex2"), IntegerArgumentType.getInteger(context, "samples"), BoolArgumentType.getBool(context, "cycleBack"), IntegerArgumentType.getInteger(context, "tickDelay"))))))))))); } - private static int customizeAnimatedDye(FabricClientCommandSource source, String hex1, String hex2, int samples, boolean cycleBack, int tickDelay) { - if (hex1 != null && hex2 != null && (!CustomArmorDyeColors.isHexadecimalColor(hex1) || !CustomArmorDyeColors.isHexadecimalColor(hex2))) { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customAnimatedDyes.invalidHex"))); - - return Command.SINGLE_SUCCESS; - } - + private static int customizeAnimatedDye(FabricClientCommandSource source, int color1, int color2, int samples, boolean cycleBack, int tickDelay) { ItemStack heldItem = source.getPlayer().getMainHandStack(); if (Utils.isOnSkyblock() && heldItem != null && !heldItem.isEmpty()) { @@ -68,7 +60,7 @@ public class CustomArmorAnimatedDyes { if (!itemUuid.isEmpty()) { Object2ObjectOpenHashMap<String, AnimatedDye> customAnimatedDyes = SkyblockerConfigManager.get().general.customAnimatedDyes; - if (hex1 == null && hex2 == null) { + if (color1 == Integer.MIN_VALUE && color2 == Integer.MIN_VALUE) { if (customAnimatedDyes.containsKey(itemUuid)) { customAnimatedDyes.remove(itemUuid); SkyblockerConfigManager.save(); @@ -77,7 +69,7 @@ public class CustomArmorAnimatedDyes { source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customAnimatedDyes.neverHad"))); } } else { - AnimatedDye animatedDye = new AnimatedDye(Integer.decode("0x" + hex1.replace("#", "")), Integer.decode("0x" + hex2.replace("#", "")), samples, cycleBack, tickDelay); + AnimatedDye animatedDye = new AnimatedDye(color1, color2, samples, cycleBack, tickDelay); customAnimatedDyes.put(itemUuid, animatedDye); SkyblockerConfigManager.save(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java index 97311220..62ffcf73 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java @@ -2,11 +2,11 @@ package de.hysky.skyblocker.skyblock.item; import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.arguments.StringArgumentType; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.command.argumenttypes.color.ColorArgumentType; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; @@ -25,28 +25,23 @@ public class CustomArmorDyeColors { dispatcher.register(ClientCommandManager.literal("skyblocker") .then(ClientCommandManager.literal("custom") .then(ClientCommandManager.literal("dyeColor") - .executes(context -> customizeDyeColor(context.getSource(), null)) - .then(ClientCommandManager.argument("hexCode", StringArgumentType.string()) - .executes(context -> customizeDyeColor(context.getSource(), StringArgumentType.getString(context, "hexCode"))))))); + .executes(context -> customizeDyeColor(context.getSource(), Integer.MIN_VALUE)) + .then(ClientCommandManager.argument("hexCode", ColorArgumentType.hex()) + .executes(context -> customizeDyeColor(context.getSource(), ColorArgumentType.getIntFromHex(context, "hexCode"))))))); } @SuppressWarnings("SameReturnValue") - private static int customizeDyeColor(FabricClientCommandSource source, String hex) { + private static int customizeDyeColor(FabricClientCommandSource source, int color) { ItemStack heldItem = source.getPlayer().getMainHandStack(); - if (hex != null && !isHexadecimalColor(hex)) { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.invalidHex"))); - return Command.SINGLE_SUCCESS; - } - - if (Utils.isOnSkyblock() && heldItem != null) { + if (Utils.isOnSkyblock() && heldItem != null) { if (heldItem.isIn(ItemTags.DYEABLE)) { String itemUuid = ItemUtils.getItemUuid(heldItem); if (!itemUuid.isEmpty()) { Object2IntOpenHashMap<String> customDyeColors = SkyblockerConfigManager.get().general.customDyeColors; - if (hex == null) { + if (color == Integer.MIN_VALUE) { if (customDyeColors.containsKey(itemUuid)) { customDyeColors.removeInt(itemUuid); SkyblockerConfigManager.save(); @@ -55,7 +50,7 @@ public class CustomArmorDyeColors { source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.neverHad"))); } } else { - customDyeColors.put(itemUuid, Integer.decode("0x" + hex.replace("#", "")).intValue()); + customDyeColors.put(itemUuid, color); SkyblockerConfigManager.save(); source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.added"))); } @@ -72,8 +67,4 @@ public class CustomArmorDyeColors { return Command.SINGLE_SUCCESS; } - - public static boolean isHexadecimalColor(String s) { - return s.replace("#", "").chars().allMatch(c -> "0123456789ABCDEFabcdef".indexOf(c) >= 0) && s.replace("#", "").length() == 6; - } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotText.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotText.java index 66c02ca1..73224509 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotText.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotText.java @@ -1,7 +1,10 @@ package de.hysky.skyblocker.skyblock.item.slottext; +import it.unimi.dsi.fastutil.objects.ObjectLists; import net.minecraft.text.Text; +import java.util.List; + public record SlotText(Text text, TextPosition position) { public static SlotText bottomLeft(Text text) { return new SlotText(text, TextPosition.BOTTOM_LEFT); @@ -18,4 +21,20 @@ public record SlotText(Text text, TextPosition position) { public static SlotText topRight(Text text) { return new SlotText(text, TextPosition.TOP_RIGHT); } + + public static List<SlotText> topLeftList(Text text) { + return ObjectLists.singleton(topLeft(text)); + } + + public static List<SlotText> topRightList(Text text) { + return ObjectLists.singleton(topRight(text)); + } + + public static List<SlotText> bottomLeftList(Text text) { + return ObjectLists.singleton(bottomLeft(text)); + } + + public static List<SlotText> bottomRightList(Text text) { + return ObjectLists.singleton(bottomRight(text)); + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextManager.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextManager.java index d3941d77..aa9bf939 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextManager.java @@ -1,5 +1,6 @@ package de.hysky.skyblocker.skyblock.item.slottext; +import de.hysky.skyblocker.skyblock.bazaar.BazaarHelper; import de.hysky.skyblocker.skyblock.item.slottext.adders.*; import de.hysky.skyblocker.utils.Utils; import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; @@ -30,6 +31,7 @@ public class SlotTextManager { new CommunityShopAdder(), new YourEssenceAdder(), new PowerStonesGuideAdder(), + new BazaarHelper(), new StatsTuningAdder() }; private static final ArrayList<SlotTextAdder> currentScreenAdders = new ArrayList<>(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CollectionAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CollectionAdder.java index 207190c2..d6ced22a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CollectionAdder.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CollectionAdder.java @@ -2,6 +2,7 @@ package de.hysky.skyblocker.skyblock.item.slottext.adders; import de.hysky.skyblocker.skyblock.item.slottext.SlotText; import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.RomanNumerals; import net.minecraft.item.ItemStack; import net.minecraft.screen.slot.Slot; @@ -25,7 +26,11 @@ public class CollectionAdder extends SlotTextAdder { Matcher matcher = COLLECTION.matcher(stack.getName().getString()); if (matcher.matches()) { int level = RomanNumerals.romanToDecimal(matcher.group("level")); - return List.of(SlotText.bottomRight(Text.literal(String.valueOf(level)).withColor(0xFFDDC1))); + if (ItemUtils.getLoreLineIf(stack, s -> s.contains("Progress to ")) != null) { + return List.of(SlotText.bottomRight(Text.literal(String.valueOf(level)).withColor(0xFFDDC1))); + } else { + return List.of(SlotText.bottomRight(Text.literal(String.valueOf(level)).withColor(0xE5B80B))); + } } return List.of(); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java index 07b8dd9b..18dbd2f7 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java @@ -2,7 +2,9 @@ package de.hysky.skyblocker.skyblock.item.slottext.adders; import de.hysky.skyblocker.skyblock.item.slottext.SlotText; import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.RomanNumerals; +import net.minecraft.item.ItemStack; import net.minecraft.screen.slot.Slot; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -20,11 +22,16 @@ public class SkillLevelAdder extends SlotTextAdder { switch (slot.id) { case 19, 20, 21, 22, 23, 24, 25, 29, 30, 31, 32 -> { //These are the slots that contain the skill items. Note that they aren't continuous, as there are 2 rows. String name = slot.getStack().getName().getString(); + final ItemStack stack = slot.getStack(); int lastIndex = name.lastIndexOf(' '); if (lastIndex == -1) return List.of(SlotText.bottomLeft(Text.literal("0").formatted(Formatting.LIGHT_PURPLE))); //Skills without any levels don't display any roman numerals. Probably because 0 doesn't exist. String romanNumeral = name.substring(lastIndex + 1); //+1 because we don't need the space itself //The "romanNumeral" might be a latin numeral, too. There's a skyblock setting for this, so we have to do it this way V - return List.of(SlotText.bottomLeft(Text.literal(String.valueOf(RomanNumerals.isValidRomanNumeral(romanNumeral) ? RomanNumerals.romanToDecimal(romanNumeral) : Integer.parseInt(romanNumeral))).withColor(0xFFDDC1))); + if (ItemUtils.getLoreLineIf(stack, s -> s.contains("Max Skill level reached!")) != null) { + return List.of(SlotText.bottomLeft(Text.literal(String.valueOf(RomanNumerals.isValidRomanNumeral(romanNumeral) ? RomanNumerals.romanToDecimal(romanNumeral) : Integer.parseInt(romanNumeral))).withColor(0xE5B80B))); + } else { + return List.of(SlotText.bottomLeft(Text.literal(String.valueOf(RomanNumerals.isValidRomanNumeral(romanNumeral) ? RomanNumerals.romanToDecimal(romanNumeral) : Integer.parseInt(romanNumeral))).withColor(0xFFDDC1))); + } } default -> { return List.of(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipAdder.java index 9bd63adc..f3395def 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipAdder.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipAdder.java @@ -4,6 +4,7 @@ import de.hysky.skyblocker.utils.render.gui.AbstractContainerMatcher; import net.minecraft.item.ItemStack; import net.minecraft.screen.slot.Slot; import net.minecraft.text.Text; +import org.intellij.lang.annotations.Language; import org.jetbrains.annotations.Nullable; import java.util.List; @@ -19,7 +20,7 @@ public abstract class TooltipAdder extends AbstractContainerMatcher { */ public final int priority; - protected TooltipAdder(String titlePattern, int priority) { + protected TooltipAdder(@Language("RegExp") String titlePattern, int priority) { super(titlePattern); this.priority = priority; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java index cb8efb0c..bd06acba 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java @@ -1,6 +1,7 @@ package de.hysky.skyblocker.skyblock.item.tooltip; import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor; +import de.hysky.skyblocker.skyblock.bazaar.ReorderHelper; import de.hysky.skyblocker.skyblock.chocolatefactory.ChocolateFactorySolver; import de.hysky.skyblocker.skyblock.item.tooltip.adders.*; import de.hysky.skyblocker.skyblock.item.tooltip.adders.CraftPriceTooltip; @@ -24,6 +25,7 @@ public class TooltipManager { new LineSmoothener(), // Applies before anything else new SupercraftReminder(), new ChocolateFactorySolver.Tooltip(), + new ReorderHelper.Tooltip(), new NpcPriceTooltip(1), new BazaarPriceTooltip(2), new LBinTooltip(3), 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/collections/GenericCategory.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java index 306fe279..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 @@ -132,6 +132,8 @@ public class GenericCategory implements ProfileViewerPage { 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; 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 f3045c11..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,6 +11,7 @@ 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.*; @@ -21,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.*; @@ -79,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 @@ -111,6 +114,9 @@ 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/waypoint/MythologicalRitual.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java index ffeba7ea..2b064770 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java @@ -5,7 +5,10 @@ import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.command.argumenttypes.blockpos.ClientBlockPosArgumentType; +import de.hysky.skyblocker.utils.command.argumenttypes.blockpos.ClientPosArgument; import de.hysky.skyblocker.utils.render.RenderHelper; import de.hysky.skyblocker.utils.waypoint.Waypoint; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; @@ -19,7 +22,6 @@ import net.fabricmc.fabric.api.event.player.UseItemCallback; import net.fabricmc.fabric.api.util.TriState; import net.minecraft.block.Blocks; import net.minecraft.client.MinecraftClient; -import net.minecraft.command.argument.BlockPosArgumentType; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.network.packet.s2c.play.ParticleS2CPacket; @@ -67,8 +69,8 @@ public class MythologicalRitual { return Command.SINGLE_SUCCESS; })) .then(literal("clearGriffinBurrow") - .then(argument("pos", BlockPosArgumentType.blockPos()).executes(context -> { - griffinBurrows.remove(context.getArgument("pos", BlockPos.class)); + .then(argument("position", ClientBlockPosArgumentType.blockPos()).executes(context -> { + griffinBurrows.remove(context.getArgument("position", ClientPosArgument.class).toAbsoluteBlockPos(context.getSource())); return Command.SINGLE_SUCCESS; })) ) @@ -190,7 +192,7 @@ public class MythologicalRitual { } private static boolean isActive() { - return SkyblockerConfigManager.get().helpers.mythologicalRitual.enableMythologicalRitualHelper && Utils.getLocationRaw().equals("hub"); + return SkyblockerConfigManager.get().helpers.mythologicalRitual.enableMythologicalRitualHelper && Utils.getLocation() == Location.HUB; } private static void reset() { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java index 11ec1b8d..b88eb38f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java @@ -12,9 +12,12 @@ import com.mojang.serialization.JsonOps; import com.mojang.serialization.codecs.RecordCodecBuilder; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.skyblock.item.CustomArmorDyeColors; +import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.command.argumenttypes.blockpos.ClientBlockPosArgumentType; +import de.hysky.skyblocker.utils.command.argumenttypes.blockpos.ClientPosArgument; +import de.hysky.skyblocker.utils.command.argumenttypes.color.ColorArgumentType; import de.hysky.skyblocker.utils.render.RenderHelper; import de.hysky.skyblocker.utils.waypoint.Waypoint; import it.unimi.dsi.fastutil.floats.FloatArrayList; @@ -30,9 +33,6 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.command.CommandRegistryAccess; import net.minecraft.command.CommandSource; -import net.minecraft.command.argument.BlockPosArgumentType; -import net.minecraft.command.argument.PosArgument; -import net.minecraft.server.command.ServerCommandSource; import net.minecraft.text.Text; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; @@ -87,24 +87,24 @@ public class OrderedWaypoints { .then(literal("add") .then(argument("groupName", word()) .suggests((source, builder) -> CommandSource.suggestMatching(WAYPOINTS.keySet(), builder)) - .then(argument("pos", BlockPosArgumentType.blockPos()) - .executes(context -> addWaypoint(context.getSource(), getString(context, "groupName"), context.getArgument("pos", PosArgument.class), Integer.MIN_VALUE, null)) - .then(argument("hex", word()) - .executes(context -> addWaypoint(context.getSource(), getString(context, "groupName"), context.getArgument("pos", PosArgument.class), Integer.MIN_VALUE, getString(context, "hex"))))))) + .then(argument("pos", ClientBlockPosArgumentType.blockPos()) + .executes(context -> addWaypoint(context.getSource(), getString(context, "groupName"), context.getArgument("pos", ClientPosArgument.class), Integer.MIN_VALUE, Integer.MIN_VALUE)) + .then(argument("hex", ColorArgumentType.hex()) + .executes(context -> addWaypoint(context.getSource(), getString(context, "groupName"), context.getArgument("pos", ClientPosArgument.class), Integer.MIN_VALUE, ColorArgumentType.getIntFromHex(context, "hex"))))))) .then(literal("addAt") .then(argument("groupName", word()) .suggests((source, builder) -> CommandSource.suggestMatching(WAYPOINTS.keySet(), builder)) .then(argument("index", IntegerArgumentType.integer(0)) - .then(argument("pos", BlockPosArgumentType.blockPos()) - .executes(context -> addWaypoint(context.getSource(), getString(context, "groupName"), context.getArgument("pos", PosArgument.class), IntegerArgumentType.getInteger(context, "index"), null)) - .then(argument("hex", word()) - .executes(context -> addWaypoint(context.getSource(), getString(context, "groupName"), context.getArgument("pos", PosArgument.class), IntegerArgumentType.getInteger(context, "index"), getString(context, "hex")))))))) + .then(argument("pos", ClientBlockPosArgumentType.blockPos()) + .executes(context -> addWaypoint(context.getSource(), getString(context, "groupName"), context.getArgument("pos", ClientPosArgument.class), IntegerArgumentType.getInteger(context, "index"), Integer.MIN_VALUE)) + .then(argument("hex", ColorArgumentType.hex()) + .executes(context -> addWaypoint(context.getSource(), getString(context, "groupName"), context.getArgument("pos", ClientPosArgument.class), IntegerArgumentType.getInteger(context, "index"), ColorArgumentType.getIntFromHex(context, "hex")))))))) .then(literal("remove") .then(argument("groupName", word()) .suggests((source, builder) -> CommandSource.suggestMatching(WAYPOINTS.keySet(), builder)) .executes(context -> removeWaypointGroup(context.getSource(), getString(context, "groupName"))) - .then(argument("pos", BlockPosArgumentType.blockPos()) - .executes(context -> removeWaypoint(context.getSource(), getString(context, "groupName"), context.getArgument("pos", PosArgument.class), Integer.MIN_VALUE))))) + .then(argument("pos", ClientBlockPosArgumentType.blockPos()) + .executes(context -> removeWaypoint(context.getSource(), getString(context, "groupName"), context.getArgument("pos", ClientPosArgument.class), Integer.MIN_VALUE))))) .then(literal("removeAt") .then(argument("groupName", word()) .suggests((source, builder) -> CommandSource.suggestMatching(WAYPOINTS.keySet(), builder)) @@ -126,20 +126,12 @@ public class OrderedWaypoints { .executes(context -> export(context.getSource())))))); } - private static int addWaypoint(FabricClientCommandSource source, String groupName, PosArgument posArgument, int index, String hex) { - BlockPos pos = posArgument.toAbsoluteBlockPos(new ServerCommandSource(null, source.getPosition(), source.getRotation(), null, 0, null, null, null, null)); + private static int addWaypoint(FabricClientCommandSource source, String groupName, ClientPosArgument posArgument, int index, int color) { + BlockPos pos = posArgument.toAbsoluteBlockPos(source); SEMAPHORE.acquireUninterruptibly(); - if (hex != null && !CustomArmorDyeColors.isHexadecimalColor(hex)) { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.add.invalidHexColor"))); - SEMAPHORE.release(); - - return Command.SINGLE_SUCCESS; - } - - int rgb = hex != null ? Integer.decode("0x" + hex.replace("#", "")) : Integer.MIN_VALUE; - float[] colorComponents = rgb != Integer.MIN_VALUE ? new float[] { ((rgb >> 16) & 0xFF) / 255f, ((rgb >> 8) & 0xFF) / 255f, (rgb & 0xFF) / 255f } : new float[0]; + float[] colorComponents = color != Integer.MIN_VALUE ? ColorUtils.getFloatComponents(color) : new float[0]; OrderedWaypointGroup group = WAYPOINTS.computeIfAbsent(groupName, name -> new OrderedWaypointGroup(name, true, new ObjectArrayList<>())); OrderedWaypoint waypoint = new OrderedWaypoint(pos, colorComponents); @@ -175,13 +167,13 @@ public class OrderedWaypoints { return Command.SINGLE_SUCCESS; } - private static int removeWaypoint(FabricClientCommandSource source, String groupName, PosArgument posArgument, int index) { + private static int removeWaypoint(FabricClientCommandSource source, String groupName, ClientPosArgument posArgument, int index) { if (WAYPOINTS.containsKey(groupName)) { SEMAPHORE.acquireUninterruptibly(); OrderedWaypointGroup group = WAYPOINTS.get(groupName); if (posArgument != null) { - BlockPos pos = posArgument.toAbsoluteBlockPos(new ServerCommandSource(null, source.getPosition(), source.getRotation(), null, 0, null, null, null, null)); + BlockPos pos = posArgument.toAbsoluteBlockPos(source); group.waypoints().removeIf(waypoint -> waypoint.getPos().equals(pos)); INDEX_STORE.removeInt(group.name()); diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java index e43ce783..65807886 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java @@ -43,7 +43,7 @@ import java.util.regex.Pattern; import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; -public class ItemUtils { +public final class ItemUtils { public static final String ID = "id"; public static final String UUID = "uuid"; public static final Pattern NOT_DURABILITY = Pattern.compile("[^0-9 /]"); @@ -55,6 +55,8 @@ public class ItemUtils { ComponentChanges.CODEC.optionalFieldOf("components", ComponentChanges.EMPTY).forGetter(ItemStack::getComponentChanges) ).apply(instance, ItemStack::new))); + private ItemUtils() {} + public static LiteralArgumentBuilder<FabricClientCommandSource> dumpHeldItemCommand() { return literal("dumpHeldItem").executes(context -> { context.getSource().sendFeedback(Text.literal("[Skyblocker Debug] Held Item: " + SkyblockerMod.GSON_COMPACT.toJson(ItemStack.CODEC.encodeStart(JsonOps.INSTANCE, context.getSource().getPlayer().getMainHandStack()).getOrThrow()))); @@ -198,9 +200,13 @@ public class ItemUtils { return null; } + /** + * Gets the first line of the lore that matches the specified predicate. + * @return The first line of the lore that matches the predicate, or {@code null} if no line matches. + */ @Nullable - public static String getLoreLineIf(ItemStack item, Predicate<String> predicate) { - for (Text line : getLore(item)) { + public static String getLoreLineIf(ItemStack stack, Predicate<String> predicate) { + for (Text line : getLore(stack)) { String string = line.getString(); if (predicate.test(string)) { return string; @@ -210,21 +216,40 @@ public class ItemUtils { return null; } + /** + * Gets the first line of the lore that matches the specified pattern, using {@link Matcher#matches()}. + * @return A matcher that contains match results if the pattern was found in the lore, otherwise {@code null}. + */ @Nullable - public static Matcher getLoreLineIfMatch(ItemStack item, Pattern pattern) { - for (Text line : getLore(item)) { - String string = line.getString(); - Matcher matcher = pattern.matcher(string); - if (matcher.matches()) { + public static Matcher getLoreLineIfMatch(ItemStack stack, Pattern pattern) { + Matcher matcher = pattern.matcher(""); + for (Text line : getLore(stack)) { + if (matcher.reset(line.getString()).matches()) { return matcher; } } - return null; } - public static @NotNull List<Text> getLore(ItemStack item) { - return item.getOrDefault(DataComponentTypes.LORE, LoreComponent.DEFAULT).styledLines(); + /** + * Gets the first line of the lore that matches the specified pattern, using {@link Matcher#find()}. + * @param pattern the pattern to search for + * @param stack the stack to search the lore of + * @return A {@link Matcher matcher} that contains match results if the pattern was found in the lore, otherwise {@code null}. + */ + @Nullable + public static Matcher getLoreLineIfContainsMatch(ItemStack stack, Pattern pattern) { + Matcher matcher = pattern.matcher(""); + for (Text line : getLore(stack)) { + if (matcher.reset(line.getString()).find()) { + return matcher; + } + } + return null; + } + + public static @NotNull List<Text> getLore(ItemStack stack) { + return stack.getOrDefault(DataComponentTypes.LORE, LoreComponent.DEFAULT).styledLines(); } public static @NotNull PropertyMap propertyMapWithTexture(String textureValue) { 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/chat/ChatPatternListener.java b/src/main/java/de/hysky/skyblocker/utils/chat/ChatPatternListener.java index 708af280..e701e24c 100644 --- a/src/main/java/de/hysky/skyblocker/utils/chat/ChatPatternListener.java +++ b/src/main/java/de/hysky/skyblocker/utils/chat/ChatPatternListener.java @@ -1,6 +1,7 @@ package de.hysky.skyblocker.utils.chat; import net.minecraft.text.Text; +import org.intellij.lang.annotations.Language; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -9,7 +10,7 @@ public abstract class ChatPatternListener implements ChatMessageListener { protected static final String NUMBER = "-?[0-9]{1,3}(?>,[0-9]{3})*(?:\\.[1-9])?"; public final Pattern pattern; - public ChatPatternListener(String pattern) { + protected ChatPatternListener(@Language("RegExp") String pattern) { this.pattern = Pattern.compile(pattern); } diff --git a/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/EggTypeArgumentType.java b/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/EggTypeArgumentType.java new file mode 100644 index 00000000..a6532ff8 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/EggTypeArgumentType.java @@ -0,0 +1,40 @@ +package de.hysky.skyblocker.utils.command.argumenttypes; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import de.hysky.skyblocker.skyblock.chocolatefactory.EggFinder; +import net.minecraft.command.CommandSource; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +public final class EggTypeArgumentType implements ArgumentType<EggFinder.EggType> { + @Override + public EggFinder.EggType parse(StringReader reader) throws CommandSyntaxException { + String name = reader.readUnquotedString(); + for (EggFinder.EggType type : EggFinder.EggType.entries) { + if (type.name().equalsIgnoreCase(name)) return type; + } + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create(); + } + + @Override + public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) { + return context.getSource() instanceof CommandSource + ? CommandSource.suggestMatching(EggFinder.EggType.entries.stream().map(EggFinder.EggType::name).map(String::toLowerCase), builder) + : Suggestions.empty(); + } + + @Override + public Collection<String> getExamples() { + return EggFinder.EggType.entries.stream().map(EggFinder.EggType::name).map(String::toLowerCase).toList(); + } + + public static EggTypeArgumentType eggType() { + return new EggTypeArgumentType(); + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/blockpos/ClientBlockPosArgumentType.java b/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/blockpos/ClientBlockPosArgumentType.java new file mode 100644 index 00000000..3a226414 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/blockpos/ClientBlockPosArgumentType.java @@ -0,0 +1,72 @@ +package de.hysky.skyblocker.utils.command.argumenttypes.blockpos; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.command.CommandSource; +import net.minecraft.command.argument.BlockPosArgumentType; +import net.minecraft.server.command.CommandManager; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.CompletableFuture; + +import static net.minecraft.command.argument.BlockPosArgumentType.*; + +// Uses the static fields of BlockPosArgumentType to not create the same field twice +public class ClientBlockPosArgumentType implements ArgumentType<ClientPosArgument> { + public static ClientBlockPosArgumentType blockPos() { + return new ClientBlockPosArgumentType(); + } + + public static BlockPos getLoadedBlockPos(CommandContext<FabricClientCommandSource> context, String name) throws CommandSyntaxException { + return getLoadedBlockPos(context, context.getSource().getWorld(), name); + } + + public static BlockPos getLoadedBlockPos(CommandContext<FabricClientCommandSource> context, ClientWorld world, String name) throws CommandSyntaxException { + BlockPos blockPos = getBlockPos(context, name); + if (!world.isChunkLoaded(blockPos)) throw UNLOADED_EXCEPTION.create(); + if (!world.isInBuildLimit(blockPos)) throw OUT_OF_WORLD_EXCEPTION.create(); + + return blockPos; + } + + public static BlockPos getBlockPos(CommandContext<FabricClientCommandSource> context, String name) { + return context.getArgument(name, ClientPosArgument.class).toAbsoluteBlockPos(context.getSource()); + } + + public static BlockPos getValidBlockPos(CommandContext<FabricClientCommandSource> context, String name) throws CommandSyntaxException { + BlockPos blockPos = getBlockPos(context, name); + if (!World.isValid(blockPos)) { + throw OUT_OF_BOUNDS_EXCEPTION.create(); + } else { + return blockPos; + } + } + + public ClientPosArgument parse(StringReader stringReader) throws CommandSyntaxException { + return stringReader.canRead() && stringReader.peek() == '^' ? LookingClientPosArgument.parse(stringReader) : DefaultClientPosArgument.parse(stringReader); + } + + @Override + public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) { + if (!(context.getSource() instanceof CommandSource commandSource)) return Suggestions.empty(); + + String string = builder.getRemaining(); + Collection<CommandSource.RelativePosition> collection = !string.isEmpty() && string.charAt(0) == '^' ? Collections.singleton(CommandSource.RelativePosition.ZERO_LOCAL) : commandSource.getBlockPositionSuggestions(); + + return CommandSource.suggestPositions(string, collection, builder, CommandManager.getCommandValidator(this::parse)); + } + + @Override + public Collection<String> getExamples() { + return BlockPosArgumentType.EXAMPLES; + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/blockpos/ClientPosArgument.java b/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/blockpos/ClientPosArgument.java new file mode 100644 index 00000000..3dff86ab --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/blockpos/ClientPosArgument.java @@ -0,0 +1,27 @@ +package de.hysky.skyblocker.utils.command.argumenttypes.blockpos; + +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec2f; +import net.minecraft.util.math.Vec3d; + +/** + * This interface, its 2 implementations and ClientBlockPosArgumentType are all copied from minecraft + * and converted to use FabricClientCommandSource instead of ServerCommandSource. + * This removes the need for hacky workarounds such as creating new ServerCommandSources with null or 0 on every argument. + */ +public interface ClientPosArgument { + Vec3d toAbsolutePos(FabricClientCommandSource source); + + Vec2f toAbsoluteRotation(FabricClientCommandSource source); + + default BlockPos toAbsoluteBlockPos(FabricClientCommandSource source) { + return BlockPos.ofFloored(this.toAbsolutePos(source)); + } + + boolean isXRelative(); + + boolean isYRelative(); + + boolean isZRelative(); +} diff --git a/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/blockpos/DefaultClientPosArgument.java b/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/blockpos/DefaultClientPosArgument.java new file mode 100644 index 00000000..47258b9f --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/blockpos/DefaultClientPosArgument.java @@ -0,0 +1,113 @@ +package de.hysky.skyblocker.utils.command.argumenttypes.blockpos; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.command.argument.CoordinateArgument; +import net.minecraft.command.argument.Vec3ArgumentType; +import net.minecraft.util.math.Vec2f; +import net.minecraft.util.math.Vec3d; + +public class DefaultClientPosArgument implements ClientPosArgument { + private final CoordinateArgument x; + private final CoordinateArgument y; + private final CoordinateArgument z; + + public DefaultClientPosArgument(CoordinateArgument x, CoordinateArgument y, CoordinateArgument z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public Vec3d toAbsolutePos(FabricClientCommandSource source) { + Vec3d vec3d = source.getPosition(); + return new Vec3d(this.x.toAbsoluteCoordinate(vec3d.x), this.y.toAbsoluteCoordinate(vec3d.y), this.z.toAbsoluteCoordinate(vec3d.z)); + } + + @Override + public Vec2f toAbsoluteRotation(FabricClientCommandSource source) { + Vec2f vec2f = source.getRotation(); + return new Vec2f((float)this.x.toAbsoluteCoordinate(vec2f.x), (float)this.y.toAbsoluteCoordinate(vec2f.y)); + } + + @Override + public boolean isXRelative() { + return this.x.isRelative(); + } + + @Override + public boolean isYRelative() { + return this.y.isRelative(); + } + + @Override + public boolean isZRelative() { + return this.z.isRelative(); + } + + public boolean equals(Object o) { + if (this == o) return true; + + return o instanceof DefaultClientPosArgument defaultPosArgument && + this.x.equals(defaultPosArgument.x) && this.y.equals(defaultPosArgument.y) && this.z.equals(defaultPosArgument.z); + } + + public static DefaultClientPosArgument parse(StringReader reader) throws CommandSyntaxException { + int i = reader.getCursor(); + CoordinateArgument coordinateArgument = CoordinateArgument.parse(reader); + if (reader.canRead() && reader.peek() == ' ') { + reader.skip(); + CoordinateArgument coordinateArgument2 = CoordinateArgument.parse(reader); + if (reader.canRead() && reader.peek() == ' ') { + reader.skip(); + CoordinateArgument coordinateArgument3 = CoordinateArgument.parse(reader); + return new DefaultClientPosArgument(coordinateArgument, coordinateArgument2, coordinateArgument3); + } else { + reader.setCursor(i); + throw Vec3ArgumentType.INCOMPLETE_EXCEPTION.createWithContext(reader); + } + } else { + reader.setCursor(i); + throw Vec3ArgumentType.INCOMPLETE_EXCEPTION.createWithContext(reader); + } + } + + public static DefaultClientPosArgument parse(StringReader reader, boolean centerIntegers) throws CommandSyntaxException { + int i = reader.getCursor(); + CoordinateArgument coordinateArgument = CoordinateArgument.parse(reader, centerIntegers); + if (reader.canRead() && reader.peek() == ' ') { + reader.skip(); + CoordinateArgument coordinateArgument2 = CoordinateArgument.parse(reader, false); + if (reader.canRead() && reader.peek() == ' ') { + reader.skip(); + CoordinateArgument coordinateArgument3 = CoordinateArgument.parse(reader, centerIntegers); + return new DefaultClientPosArgument(coordinateArgument, coordinateArgument2, coordinateArgument3); + } else { + reader.setCursor(i); + throw Vec3ArgumentType.INCOMPLETE_EXCEPTION.createWithContext(reader); + } + } else { + reader.setCursor(i); + throw Vec3ArgumentType.INCOMPLETE_EXCEPTION.createWithContext(reader); + } + } + + public static DefaultClientPosArgument absolute(double x, double y, double z) { + return new DefaultClientPosArgument(new CoordinateArgument(false, x), new CoordinateArgument(false, y), new CoordinateArgument(false, z)); + } + + public static DefaultClientPosArgument absolute(Vec2f vec) { + return new DefaultClientPosArgument(new CoordinateArgument(false, vec.x), new CoordinateArgument(false, vec.y), new CoordinateArgument(true, 0.0)); + } + + public static DefaultClientPosArgument zero() { + return new DefaultClientPosArgument(new CoordinateArgument(true, 0.0), new CoordinateArgument(true, 0.0), new CoordinateArgument(true, 0.0)); + } + + public int hashCode() { + int i = this.x.hashCode(); + i = 31 * i + this.y.hashCode(); + return 31 * i + this.z.hashCode(); + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/blockpos/LookingClientPosArgument.java b/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/blockpos/LookingClientPosArgument.java new file mode 100644 index 00000000..3f89f9c7 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/blockpos/LookingClientPosArgument.java @@ -0,0 +1,106 @@ +package de.hysky.skyblocker.utils.command.argumenttypes.blockpos; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.command.argument.CoordinateArgument; +import net.minecraft.command.argument.Vec3ArgumentType; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec2f; +import net.minecraft.util.math.Vec3d; + +import java.util.Objects; + +public class LookingClientPosArgument implements ClientPosArgument { + private final double x; + private final double y; + private final double z; + + public LookingClientPosArgument(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public Vec3d toAbsolutePos(FabricClientCommandSource source) { + Vec2f vec2f = source.getRotation(); + Vec3d vec3d = source.getPlayer().getPos(); + float f = MathHelper.cos((vec2f.y + 90.0F) * (float) (Math.PI / 180.0)); + float g = MathHelper.sin((vec2f.y + 90.0F) * (float) (Math.PI / 180.0)); + float h = MathHelper.cos(-vec2f.x * (float) (Math.PI / 180.0)); + float i = MathHelper.sin(-vec2f.x * (float) (Math.PI / 180.0)); + float j = MathHelper.cos((-vec2f.x + 90.0F) * (float) (Math.PI / 180.0)); + float k = MathHelper.sin((-vec2f.x + 90.0F) * (float) (Math.PI / 180.0)); + Vec3d vec3d2 = new Vec3d(f * h, i, g * h); + Vec3d vec3d3 = new Vec3d(f * j, k, g * j); + Vec3d vec3d4 = vec3d2.crossProduct(vec3d3).multiply(-1.0); + double d = vec3d2.x * this.z + vec3d3.x * this.y + vec3d4.x * this.x; + double e = vec3d2.y * this.z + vec3d3.y * this.y + vec3d4.y * this.x; + double l = vec3d2.z * this.z + vec3d3.z * this.y + vec3d4.z * this.x; + return new Vec3d(vec3d.x + d, vec3d.y + e, vec3d.z + l); + } + + @Override + public Vec2f toAbsoluteRotation(FabricClientCommandSource source) { + return Vec2f.ZERO; + } + + @Override + public boolean isXRelative() { + return true; + } + + @Override + public boolean isYRelative() { + return true; + } + + @Override + public boolean isZRelative() { + return true; + } + + public static LookingClientPosArgument parse(StringReader reader) throws CommandSyntaxException { + int i = reader.getCursor(); + double d = readCoordinate(reader, i); + if (reader.canRead() && reader.peek() == ' ') { + reader.skip(); + double e = readCoordinate(reader, i); + if (reader.canRead() && reader.peek() == ' ') { + reader.skip(); + double f = readCoordinate(reader, i); + return new LookingClientPosArgument(d, e, f); + } else { + reader.setCursor(i); + throw Vec3ArgumentType.INCOMPLETE_EXCEPTION.createWithContext(reader); + } + } else { + reader.setCursor(i); + throw Vec3ArgumentType.INCOMPLETE_EXCEPTION.createWithContext(reader); + } + } + + private static double readCoordinate(StringReader reader, int startingCursorPos) throws CommandSyntaxException { + if (!reader.canRead()) { + throw CoordinateArgument.MISSING_COORDINATE.createWithContext(reader); + } else if (reader.peek() != '^') { + reader.setCursor(startingCursorPos); + throw Vec3ArgumentType.MIXED_COORDINATE_EXCEPTION.createWithContext(reader); + } else { + reader.skip(); + return reader.canRead() && reader.peek() != ' ' ? reader.readDouble() : 0.0; + } + } + + public boolean equals(Object o) { + if (this == o) return true; + + return o instanceof LookingClientPosArgument lookingPosArgument + && this.x == lookingPosArgument.x && this.y == lookingPosArgument.y && this.z == lookingPosArgument.z; + } + + public int hashCode() { + return Objects.hash(x, y, z); + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/color/ColorArgumentType.java b/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/color/ColorArgumentType.java new file mode 100644 index 00000000..a6dc8510 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/color/ColorArgumentType.java @@ -0,0 +1,28 @@ +package de.hysky.skyblocker.utils.command.argumenttypes.color; + +import com.mojang.brigadier.context.CommandContext; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; + +/** + * Utility class that provides static methods for abstracting away the actual argument type classes. + */ +public final class ColorArgumentType { + private ColorArgumentType() {} // Prevent instantiation + + public static RgbColorArgumentType rgb() { + return new RgbColorArgumentType(); + } + + public static HexColorArgumentType hex() { + return new HexColorArgumentType(); + } + + public static int getIntFromHex(CommandContext<FabricClientCommandSource> context, String name) { + return HexColorArgumentType.getInt(context, name); + } + + public static int getIntFromRgb(CommandContext<FabricClientCommandSource> context, String name) { + return RgbColorArgumentType.getInt(context, name); + } +} + diff --git a/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/color/HexColorArgumentType.java b/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/color/HexColorArgumentType.java new file mode 100644 index 00000000..516eb7b9 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/color/HexColorArgumentType.java @@ -0,0 +1,38 @@ +package de.hysky.skyblocker.utils.command.argumenttypes.color; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.text.Text; +import org.apache.commons.lang3.StringUtils; + +@SuppressWarnings("RedundantCast") +public final class HexColorArgumentType implements ArgumentType<Integer> { + public static final DynamicCommandExceptionType WRONG_INPUT_WIDTH = new DynamicCommandExceptionType(found -> Text.translatable("argument.color.hex.invalidString", ((String) found).length())); + public static final DynamicCommandExceptionType INVALID_CHARACTER = new DynamicCommandExceptionType(character -> Text.translatable("argument.color.hex.invalidChar", (String) character)); + + @Override + public Integer parse(StringReader reader) throws CommandSyntaxException { + String input = reader.readString(); + if (StringUtils.startsWithIgnoreCase(input, "0x")) input = input.substring(2); +// else if (input.startsWith("#")) input = input.substring(1); // This doesn't work because minecraft has the # prefix reserved for tags, so inputs with that prefix never reach this reader + + if (input.length() != 6) throw WRONG_INPUT_WIDTH.create(input); + + for (int i = 0; i < input.length(); i++) { + char character = input.charAt(i); + if ((character < '0' || character > '9') && (character < 'a' || character > 'f') && (character < 'A' || character > 'F')) { + throw INVALID_CHARACTER.create(String.valueOf(character)); //Have to wrap character in a string, because mcdev doesn't appreciate chars and I cba to suppress the warnings + } + } + + return Integer.decode("#" + input); + } + + public static int getInt(CommandContext<FabricClientCommandSource> context, String name) { + return context.getArgument(name, Integer.class); + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/color/RgbColorArgumentType.java b/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/color/RgbColorArgumentType.java new file mode 100644 index 00000000..d9a99fae --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/command/argumenttypes/color/RgbColorArgumentType.java @@ -0,0 +1,39 @@ +package de.hysky.skyblocker.utils.command.argumenttypes.color; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.text.Text; + +public final class RgbColorArgumentType implements ArgumentType<Integer> { + public static final SimpleCommandExceptionType INCOMPLETE_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("argument.color.rgb.incomplete")); + + @Override + public Integer parse(StringReader reader) throws CommandSyntaxException { + int i = reader.getCursor(); + int redArgument = IntegerArgumentType.integer(0x00, 0xFF).parse(reader); + if (reader.canRead() && reader.peek() == ' ') { + reader.skip(); + int greenArgument = IntegerArgumentType.integer(0x00, 0xFF).parse(reader); + if (reader.canRead() && reader.peek() == ' ') { + reader.skip(); + int blueArgument = IntegerArgumentType.integer(0x00, 0xFF).parse(reader); + return redArgument << 16 | greenArgument << 8 | blueArgument; + } else { + reader.setCursor(i); + throw INCOMPLETE_EXCEPTION.createWithContext(reader); + } + } else { + reader.setCursor(i); + throw INCOMPLETE_EXCEPTION.createWithContext(reader); + } + } + + public static int getInt(CommandContext<FabricClientCommandSource> context, String name) { + return context.getArgument(name, Integer.class); + } +} 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/render/gui/ContainerSolver.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolver.java index 81c9ebec..9a9d0907 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolver.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolver.java @@ -4,6 +4,7 @@ import de.hysky.skyblocker.SkyblockerMod; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; import net.minecraft.item.ItemStack; +import org.intellij.lang.annotations.Language; import java.util.List; import java.util.regex.Pattern; @@ -12,7 +13,7 @@ import java.util.regex.Pattern; * Abstract class for gui solvers. Extend this class to add a new gui solver, like terminal solvers or experiment solvers. */ public abstract class ContainerSolver extends AbstractContainerMatcher { - protected ContainerSolver(String titlePattern) { + protected ContainerSolver(@Language("RegExp") String titlePattern) { super(titlePattern); } diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java index 9a1e3072..79cc78f5 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java @@ -1,10 +1,10 @@ package de.hysky.skyblocker.utils.render.gui; import com.mojang.blaze3d.systems.RenderSystem; - import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor; import de.hysky.skyblocker.skyblock.accessories.newyearcakes.NewYearCakeBagHelper; import de.hysky.skyblocker.skyblock.accessories.newyearcakes.NewYearCakesHelper; +import de.hysky.skyblocker.skyblock.bazaar.ReorderHelper; import de.hysky.skyblocker.skyblock.chocolatefactory.ChocolateFactorySolver; import de.hysky.skyblocker.skyblock.dungeon.CroesusHelper; import de.hysky.skyblocker.skyblock.dungeon.CroesusProfit; @@ -59,7 +59,8 @@ public class ContainerSolverManager { UltrasequencerSolver.INSTANCE, new NewYearCakeBagHelper(), NewYearCakesHelper.INSTANCE, - new ChocolateFactorySolver() + new ChocolateFactorySolver(), + new ReorderHelper() }; } |