diff options
Diffstat (limited to 'src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon')
19 files changed, 0 insertions, 2491 deletions
diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/CroesusHelper.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/CroesusHelper.java deleted file mode 100644 index 67e6d7f4..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/CroesusHelper.java +++ /dev/null @@ -1,34 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon; - -import me.xmrvizzy.skyblocker.config.SkyblockerConfigManager; -import me.xmrvizzy.skyblocker.utils.render.gui.ColorHighlight; -import me.xmrvizzy.skyblocker.utils.render.gui.ContainerSolver; -import net.minecraft.item.ItemStack; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class CroesusHelper extends ContainerSolver { - - public CroesusHelper() { - super("^Croesus$"); - } - - @Override - protected boolean isEnabled() { - return SkyblockerConfigManager.get().locations.dungeons.croesusHelper; - } - - @Override - protected List<ColorHighlight> getColors(String[] groups, Map<Integer, ItemStack> slots) { - List<ColorHighlight> highlights = new ArrayList<>(); - for (Map.Entry<Integer, ItemStack> entry : slots.entrySet()) { - ItemStack stack = entry.getValue(); - if (stack != null && stack.getNbt() != null && (stack.getNbt().toString().contains("No more Chests to open!") || stack.getNbt().toString().contains("Opened Chest:"))) { - highlights.add(ColorHighlight.gray(entry.getKey())); - } - } - return highlights; - } -} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonBlaze.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonBlaze.java deleted file mode 100644 index f39684eb..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonBlaze.java +++ /dev/null @@ -1,152 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon; - -import it.unimi.dsi.fastutil.objects.ObjectIntPair; -import me.xmrvizzy.skyblocker.config.SkyblockerConfigManager; -import me.xmrvizzy.skyblocker.utils.Utils; -import me.xmrvizzy.skyblocker.utils.render.RenderHelper; -import me.xmrvizzy.skyblocker.utils.scheduler.Scheduler; -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.client.network.ClientPlayerEntity; -import net.minecraft.client.world.ClientWorld; -import net.minecraft.entity.decoration.ArmorStandEntity; -import net.minecraft.predicate.entity.EntityPredicates; -import net.minecraft.util.math.Box; -import net.minecraft.util.math.Vec3d; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; - -/** - * This class provides functionality to render outlines around Blaze entities - */ -public class DungeonBlaze { - private static final Logger LOGGER = LoggerFactory.getLogger(DungeonBlaze.class.getName()); - private static final float[] GREEN_COLOR_COMPONENTS = {0.0F, 1.0F, 0.0F}; - private static final float[] WHITE_COLOR_COMPONENTS = {1.0f, 1.0f, 1.0f}; - - private static ArmorStandEntity highestBlaze = null; - private static ArmorStandEntity lowestBlaze = null; - private static ArmorStandEntity nextHighestBlaze = null; - private static ArmorStandEntity nextLowestBlaze = null; - - public static void init() { - Scheduler.INSTANCE.scheduleCyclic(DungeonBlaze::update, 4); - WorldRenderEvents.BEFORE_DEBUG_RENDER.register(DungeonBlaze::blazeRenderer); - } - - /** - * Updates the state of Blaze entities and triggers the rendering process if necessary. - */ - public static void update() { - ClientWorld world = MinecraftClient.getInstance().world; - ClientPlayerEntity player = MinecraftClient.getInstance().player; - if (world == null || player == null || !Utils.isInDungeons()) return; - List<ObjectIntPair<ArmorStandEntity>> blazes = getBlazesInWorld(world, player); - sortBlazes(blazes); - updateBlazeEntities(blazes); - } - - /** - * Retrieves Blaze entities in the world and parses their health information. - * - * @param world The client world to search for Blaze entities. - * @return A list of Blaze entities and their associated health. - */ - private static List<ObjectIntPair<ArmorStandEntity>> getBlazesInWorld(ClientWorld world, ClientPlayerEntity player) { - List<ObjectIntPair<ArmorStandEntity>> blazes = new ArrayList<>(); - for (ArmorStandEntity blaze : world.getEntitiesByClass(ArmorStandEntity.class, player.getBoundingBox().expand(500D), EntityPredicates.NOT_MOUNTED)) { - String blazeName = blaze.getName().getString(); - if (blazeName.contains("Blaze") && blazeName.contains("/")) { - try { - int health = Integer.parseInt(blazeName.substring(blazeName.indexOf("/") + 1, blazeName.length() - 1)); - blazes.add(ObjectIntPair.of(blaze, health)); - } catch (NumberFormatException e) { - handleException(e); - } - } - } - return blazes; - } - - /** - * Sorts the Blaze entities based on their health values. - * - * @param blazes The list of Blaze entities to be sorted. - */ - private static void sortBlazes(List<ObjectIntPair<ArmorStandEntity>> blazes) { - blazes.sort(Comparator.comparingInt(ObjectIntPair::rightInt)); - } - - /** - * Updates information about Blaze entities based on sorted list. - * - * @param blazes The sorted list of Blaze entities with associated health values. - */ - private static void updateBlazeEntities(List<ObjectIntPair<ArmorStandEntity>> blazes) { - if (!blazes.isEmpty()) { - lowestBlaze = blazes.get(0).left(); - int highestIndex = blazes.size() - 1; - highestBlaze = blazes.get(highestIndex).left(); - if (blazes.size() > 1) { - nextLowestBlaze = blazes.get(1).left(); - nextHighestBlaze = blazes.get(highestIndex - 1).left(); - } - } - } - - /** - * Renders outlines for Blaze entities based on health and position. - * - * @param wrc The WorldRenderContext used for rendering. - */ - public static void blazeRenderer(WorldRenderContext wrc) { - try { - if (highestBlaze != null && lowestBlaze != null && highestBlaze.isAlive() && lowestBlaze.isAlive() && SkyblockerConfigManager.get().locations.dungeons.blazesolver) { - if (highestBlaze.getY() < 69) { - renderBlazeOutline(highestBlaze, nextHighestBlaze, wrc); - } - if (lowestBlaze.getY() > 69) { - renderBlazeOutline(lowestBlaze, nextLowestBlaze, wrc); - } - } - } catch (Exception e) { - handleException(e); - } - } - - /** - * Renders outlines for Blaze entities and connections between them. - * - * @param blaze The Blaze entity for which to render an outline. - * @param nextBlaze The next Blaze entity for connection rendering. - * @param wrc The WorldRenderContext used for rendering. - */ - private static void renderBlazeOutline(ArmorStandEntity blaze, ArmorStandEntity nextBlaze, WorldRenderContext wrc) { - Box blazeBox = blaze.getBoundingBox().expand(0.3, 0.9, 0.3).offset(0, -1.1, 0); - RenderHelper.renderOutline(wrc, blazeBox, GREEN_COLOR_COMPONENTS, 5f); - - if (nextBlaze != null && nextBlaze.isAlive() && nextBlaze != blaze) { - Box nextBlazeBox = nextBlaze.getBoundingBox().expand(0.3, 0.9, 0.3).offset(0, -1.1, 0); - RenderHelper.renderOutline(wrc, nextBlazeBox, WHITE_COLOR_COMPONENTS, 5f); - - Vec3d blazeCenter = blazeBox.getCenter(); - Vec3d nextBlazeCenter = nextBlazeBox.getCenter(); - - RenderHelper.renderLinesFromPoints(wrc, new Vec3d[]{blazeCenter, nextBlazeCenter}, WHITE_COLOR_COMPONENTS, 1f, 5f); - } - } - - /** - * Handles exceptions by logging and printing stack traces. - * - * @param e The exception to handle. - */ - private static void handleException(Exception e) { - LOGGER.warn("[Skyblocker BlazeRenderer] Encountered an unknown exception", e); - } -} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java deleted file mode 100644 index ea54ff32..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java +++ /dev/null @@ -1,169 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon; - -import com.google.gson.JsonObject; -import it.unimi.dsi.fastutil.ints.IntBooleanPair; -import me.xmrvizzy.skyblocker.config.SkyblockerConfig; -import me.xmrvizzy.skyblocker.config.SkyblockerConfigManager; -import me.xmrvizzy.skyblocker.mixin.accessor.ScreenAccessor; -import me.xmrvizzy.skyblocker.skyblock.item.PriceInfoTooltip; -import me.xmrvizzy.skyblocker.utils.Utils; -import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; -import net.minecraft.client.item.TooltipContext; -import net.minecraft.item.ItemStack; -import net.minecraft.screen.GenericContainerScreenHandler; -import net.minecraft.screen.ScreenHandlerType; -import net.minecraft.screen.slot.Slot; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.text.DecimalFormat; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class DungeonChestProfit { - private static final Logger LOGGER = LoggerFactory.getLogger(DungeonChestProfit.class); - private static final Pattern ESSENCE_PATTERN = Pattern.compile("(?<type>[A-Za-z]+) Essence x(?<amount>[0-9]+)"); - private static final DecimalFormat FORMATTER = new DecimalFormat("#,###"); - - public static void init() { - ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> ScreenEvents.afterTick(screen).register(screen1 -> { - if (Utils.isOnSkyblock() && screen instanceof GenericContainerScreen genericContainerScreen && genericContainerScreen.getScreenHandler().getType() == ScreenHandlerType.GENERIC_9X6) { - ((ScreenAccessor) screen).setTitle(getChestProfit(genericContainerScreen.getScreenHandler(), screen.getTitle(), client)); - } - })); - } - - public static Text getChestProfit(GenericContainerScreenHandler handler, Text title, MinecraftClient client) { - try { - if (SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator && isDungeonChest(title.getString())) { - int profit = 0; - boolean hasIncompleteData = false, usedKismet = false; - List<Slot> slots = handler.slots.subList(0, handler.getRows() * 9); - - //If the item stack for the "Open Reward Chest" button or the kismet button hasn't been sent to the client yet - if (slots.get(31).getStack().isEmpty() || slots.get(50).getStack().isEmpty()) return title; - - for (Slot slot : slots) { - ItemStack stack = slot.getStack(); - - if (!stack.isEmpty()) { - String name = stack.getName().getString(); - String id = PriceInfoTooltip.getInternalNameFromNBT(stack, false); - - //Regular item price - if (id != null) { - IntBooleanPair priceData = getItemPrice(id); - - if (!priceData.rightBoolean()) hasIncompleteData = true; - - //Add the item price to the profit - profit += priceData.leftInt(); - - continue; - } - - //Essence price - if (name.contains("Essence") && SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.includeEssence) { - Matcher matcher = ESSENCE_PATTERN.matcher(name); - - if (matcher.matches()) { - String type = matcher.group("type"); - int amount = Integer.parseInt(matcher.group("amount")); - - IntBooleanPair priceData = getItemPrice(("ESSENCE_" + type).toUpperCase()); - - if (!priceData.rightBoolean()) hasIncompleteData = true; - - //Add the price of the essence to the profit - profit += priceData.leftInt() * amount; - - continue; - } - } - - //Determine the cost of the chest - if (name.contains("Open Reward Chest")) { - String foundString = searchLoreFor(stack, client, "Coins"); - - //Incase we're searching the free chest - if (!StringUtils.isBlank(foundString)) { - profit -= Integer.parseInt(foundString.replaceAll("[^0-9]", "")); - } - - continue; - } - - //Determine if a kismet was used or not - if (name.contains("Reroll Chest")) { - usedKismet = !StringUtils.isBlank(searchLoreFor(stack, client, "You already rerolled a chest!")); - } - } - } - - if (SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.includeKismet && usedKismet) { - IntBooleanPair kismetPriceData = getItemPrice("KISMET_FEATHER"); - - if (!kismetPriceData.rightBoolean()) hasIncompleteData = true; - - profit -= kismetPriceData.leftInt(); - } - - return Text.literal(title.getString()).append(getProfitText(profit, hasIncompleteData)); - } - } catch (Exception e) { - LOGGER.error("[Skyblocker Profit Calculator] Failed to calculate dungeon chest profit! ", e); - } - - return title; - } - - /** - * @return An {@link IntBooleanPair} with the {@code left int} representing the item's price, and the {@code right boolean} indicating if the price - * was based on complete data. - */ - private static IntBooleanPair getItemPrice(String id) { - JsonObject bazaarPrices = PriceInfoTooltip.getBazaarPrices(); - JsonObject lbinPrices = PriceInfoTooltip.getLBINPrices(); - - if (bazaarPrices == null || lbinPrices == null) return IntBooleanPair.of(0, false); - - if (bazaarPrices.has(id)) { - JsonObject item = bazaarPrices.get(id).getAsJsonObject(); - boolean isPriceNull = item.get("sellPrice").isJsonNull(); - - return IntBooleanPair.of(isPriceNull ? 0 : (int) item.get("sellPrice").getAsDouble(), !isPriceNull); - } - - if (lbinPrices.has(id)) { - return IntBooleanPair.of((int) lbinPrices.get(id).getAsDouble(), true); - } - - return IntBooleanPair.of(0, false); - } - - /** - * Searches for a specific string of characters in the name and lore of an item - */ - private static String searchLoreFor(ItemStack stack, MinecraftClient client, String searchString) { - return stack.getTooltip(client.player, TooltipContext.BASIC).stream().map(Text::getString).filter(line -> line.contains(searchString)).findAny().orElse(null); - } - - private static boolean isDungeonChest(String name) { - return name.equals("Wood Chest") || name.equals("Gold Chest") || name.equals("Diamond Chest") || name.equals("Emerald Chest") || name.equals("Obsidian Chest") || name.equals("Bedrock Chest"); - } - - private static Text getProfitText(int profit, boolean hasIncompleteData) { - SkyblockerConfig.DungeonChestProfit config = SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit; - return getProfitText(profit, hasIncompleteData, config.neutralThreshold, config.neutralColor, config.profitColor, config.lossColor, config.incompleteColor); - } - - static Text getProfitText(int profit, boolean hasIncompleteData, int neutralThreshold, Formatting neutralColor, Formatting profitColor, Formatting lossColor, Formatting incompleteColor) { - return Text.literal((profit > 0 ? " +" : " ") + FORMATTER.format(profit)).formatted(hasIncompleteData ? incompleteColor : (Math.abs(profit) < neutralThreshold) ? neutralColor : (profit > 0) ? profitColor : lossColor); - } -} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonMap.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonMap.java deleted file mode 100644 index 53cc4fac..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonMap.java +++ /dev/null @@ -1,61 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon; - -import me.xmrvizzy.skyblocker.config.SkyblockerConfigManager; -import me.xmrvizzy.skyblocker.utils.scheduler.Scheduler; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.render.MapRenderer; -import net.minecraft.client.render.VertexConsumerProvider; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.item.FilledMapItem; -import net.minecraft.item.ItemStack; -import net.minecraft.item.map.MapState; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.util.Identifier; -import org.apache.commons.lang3.StringUtils; - -public class DungeonMap { - private static final Identifier MAP_BACKGROUND = new Identifier("textures/map/map_background.png"); - - public static void render(MatrixStack matrices) { - MinecraftClient client = MinecraftClient.getInstance(); - if (client.player == null || client.world == null) return; - ItemStack item = client.player.getInventory().main.get(8); - NbtCompound tag = item.getNbt(); - - if (tag != null && tag.contains("map")) { - String tag2 = tag.asString(); - tag2 = StringUtils.substringBetween(tag2, "map:", "}"); - int mapid = Integer.parseInt(tag2); - VertexConsumerProvider.Immediate vertices = client.getBufferBuilders().getEffectVertexConsumers(); - MapRenderer map = client.gameRenderer.getMapRenderer(); - MapState state = FilledMapItem.getMapState(mapid, client.world); - float scaling = SkyblockerConfigManager.get().locations.dungeons.mapScaling; - int x = SkyblockerConfigManager.get().locations.dungeons.mapX; - int y = SkyblockerConfigManager.get().locations.dungeons.mapY; - - if (state == null) return; - matrices.push(); - matrices.translate(x, y, 0); - matrices.scale(scaling, scaling, 0f); - map.draw(matrices, vertices, mapid, state, false, 15728880); - vertices.draw(); - matrices.pop(); - } - } - - public static void renderHUDMap(DrawContext context, int x, int y) { - float scaling = SkyblockerConfigManager.get().locations.dungeons.mapScaling; - int size = (int) (128 * scaling); - context.drawTexture(MAP_BACKGROUND, x, y, 0, 0, size, size, size, size); - } - - public static void init() { - ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("skyblocker") - .then(ClientCommandManager.literal("hud") - .then(ClientCommandManager.literal("dungeonmap") - .executes(Scheduler.queueOpenScreenCommand(DungeonMapConfigScreen::new)))))); - } -}
\ No newline at end of file diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java deleted file mode 100644 index 94b05e86..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java +++ /dev/null @@ -1,62 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon; - -import me.xmrvizzy.skyblocker.config.SkyblockerConfigManager; -import me.xmrvizzy.skyblocker.utils.render.RenderHelper; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.text.Text; - -import java.awt.*; - -public class DungeonMapConfigScreen extends Screen { - - private int hudX = SkyblockerConfigManager.get().locations.dungeons.mapX; - private int hudY = SkyblockerConfigManager.get().locations.dungeons.mapY; - private final Screen parent; - - protected DungeonMapConfigScreen() { - this(null); - } - - public DungeonMapConfigScreen(Screen parent) { - super(Text.literal("Dungeon Map Config")); - this.parent = parent; - } - - @Override - public void render(DrawContext context, int mouseX, int mouseY, float delta) { - super.render(context, mouseX, mouseY, delta); - renderBackground(context, mouseX, mouseY, delta); - DungeonMap.renderHUDMap(context, hudX, hudY); - context.drawCenteredTextWithShadow(textRenderer, "Right Click To Reset Position", width >> 1, height >> 1, Color.GRAY.getRGB()); - } - - @Override - public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - float scaling = SkyblockerConfigManager.get().locations.dungeons.mapScaling; - int size = (int) (128 * scaling); - if (RenderHelper.pointIsInArea(mouseX, mouseY, hudX, hudY, hudX + size, hudY + size) && button == 0) { - hudX = (int) Math.max(Math.min(mouseX - (size >> 1), this.width - size), 0); - hudY = (int) Math.max(Math.min(mouseY - (size >> 1), this.height - size), 0); - } - return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (button == 1) { - hudX = 2; - hudY = 2; - } - - return super.mouseClicked(mouseX, mouseY, button); - } - - @Override - public void close() { - SkyblockerConfigManager.get().locations.dungeons.mapX = hudX; - SkyblockerConfigManager.get().locations.dungeons.mapY = hudY; - SkyblockerConfigManager.save(); - this.client.setScreen(parent); - } -} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/LividColor.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/LividColor.java deleted file mode 100644 index 2df8192d..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/LividColor.java +++ /dev/null @@ -1,42 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon; - -import me.xmrvizzy.skyblocker.config.SkyblockerConfigManager; -import me.xmrvizzy.skyblocker.utils.Utils; -import me.xmrvizzy.skyblocker.utils.scheduler.MessageScheduler; -import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; -import net.minecraft.client.MinecraftClient; -import net.minecraft.util.math.BlockPos; - -public class LividColor { - private static int tenTicks = 0; - - public static void init() { - ClientReceiveMessageEvents.GAME.register((message, overlay) -> { - if (SkyblockerConfigManager.get().locations.dungeons.lividColor.enableLividColor && message.getString().equals("[BOSS] Livid: I respect you for making it to here, but I'll be your undoing.")) { - tenTicks = 8; - } - }); - } - - public static void update() { - MinecraftClient client = MinecraftClient.getInstance(); - if (tenTicks != 0) { - if (SkyblockerConfigManager.get().locations.dungeons.lividColor.enableLividColor && Utils.isInDungeons() && client.world != null) { - if (tenTicks == 1) { - MessageScheduler.INSTANCE.sendMessageAfterCooldown(SkyblockerConfigManager.get().locations.dungeons.lividColor.lividColorText.replace("[color]", "red")); - tenTicks = 0; - return; - } - String key = client.world.getBlockState(new BlockPos(5, 110, 42)).getBlock().getTranslationKey(); - if (key.startsWith("block.minecraft.") && key.endsWith("wool") && !key.endsWith("red_wool")) { - MessageScheduler.INSTANCE.sendMessageAfterCooldown(SkyblockerConfigManager.get().locations.dungeons.lividColor.lividColorText.replace("[color]", key.substring(16, key.length() - 5))); - tenTicks = 0; - return; - } - tenTicks--; - } else { - tenTicks = 0; - } - } - } -} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/OldLever.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/OldLever.java deleted file mode 100644 index b9fba7be..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/OldLever.java +++ /dev/null @@ -1,40 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon; - -import me.xmrvizzy.skyblocker.config.SkyblockerConfigManager; -import net.minecraft.block.Block; -import net.minecraft.block.enums.BlockFace; -import net.minecraft.util.math.Direction; -import net.minecraft.util.shape.VoxelShape; - -public class OldLever { - protected static final VoxelShape FLOOR_SHAPE = Block.createCuboidShape(4.0D, 0.0D, 4.0D, 12.0D, 10.0D, 12.0D); - protected static final VoxelShape NORTH_SHAPE = Block.createCuboidShape(5.0D, 3.0D, 10.0D, 11.0D, 13.0D, 16.0D); - protected static final VoxelShape SOUTH_SHAPE = Block.createCuboidShape(5.0D, 3.0D, 0.0D, 11.0D, 13.0D, 6.0D); - protected static final VoxelShape EAST_SHAPE = Block.createCuboidShape(0.0D, 3.0D, 5.0D, 6.0D, 13.0D, 11.0D); - protected static final VoxelShape WEST_SHAPE = Block.createCuboidShape(10.0D, 3.0D, 5.0D, 16.0D, 13.0D, 11.0D); - - public static VoxelShape getShape(BlockFace face, Direction direction) { - if (!SkyblockerConfigManager.get().general.hitbox.oldLeverHitbox) - return null; - - if (face == BlockFace.FLOOR) { - return FLOOR_SHAPE; - } else if (face == BlockFace.WALL) { - switch (direction) { - case EAST -> { - return EAST_SHAPE; - } - case WEST -> { - return WEST_SHAPE; - } - case SOUTH -> { - return SOUTH_SHAPE; - } - case NORTH -> { - return NORTH_SHAPE; - } - } - } - return null; - } -} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/Reparty.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/Reparty.java deleted file mode 100644 index 288a8b5a..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/Reparty.java +++ /dev/null @@ -1,94 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon; - -import me.xmrvizzy.skyblocker.config.SkyblockerConfigManager; -import me.xmrvizzy.skyblocker.utils.Utils; -import me.xmrvizzy.skyblocker.utils.chat.ChatFilterResult; -import me.xmrvizzy.skyblocker.utils.chat.ChatPatternListener; -import me.xmrvizzy.skyblocker.utils.scheduler.MessageScheduler; -import me.xmrvizzy.skyblocker.utils.scheduler.Scheduler; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayerEntity; -import net.minecraft.text.Text; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class Reparty extends ChatPatternListener { - private static final MinecraftClient client = MinecraftClient.getInstance(); - public static final Pattern PLAYER = Pattern.compile(" ([a-zA-Z0-9_]{2,16}) ●"); - private static final int BASE_DELAY = 10; - - private String[] players; - private int playersSoFar; - private boolean repartying; - private String partyLeader; - - public Reparty() { - super("^(?:You are not currently in a party\\." + - "|Party (?:Membe|Moderato)rs(?: \\(([0-9]+)\\)|:( .*))" + - "|([\\[A-z+\\]]* )?(?<disband>.*) has disbanded .*" + - "|.*\n([\\[A-z+\\]]* )?(?<invite>.*) has invited you to join their party!" + - "\nYou have 60 seconds to accept. Click here to join!\n.*)$"); - - this.repartying = false; - ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("rp").executes(context -> { - if (!Utils.isOnSkyblock() || this.repartying || client.player == null) return 0; - this.repartying = true; - MessageScheduler.INSTANCE.sendMessageAfterCooldown("/p list"); - return 0; - }))); - } - - @Override - public ChatFilterResult state() { - return (SkyblockerConfigManager.get().general.acceptReparty || this.repartying) ? ChatFilterResult.FILTER : ChatFilterResult.PASS; - } - - @Override - public boolean onMatch(Text message, Matcher matcher) { - if (matcher.group(1) != null && repartying) { - this.playersSoFar = 0; - this.players = new String[Integer.parseInt(matcher.group(1)) - 1]; - } else if (matcher.group(2) != null && repartying) { - Matcher m = PLAYER.matcher(matcher.group(2)); - while (m.find()) { - this.players[playersSoFar++] = m.group(1); - } - } else if (matcher.group("disband") != null && !matcher.group("disband").equals(client.getSession().getUsername())) { - partyLeader = matcher.group("disband"); - Scheduler.INSTANCE.schedule(() -> partyLeader = null, 61); - return false; - } else if (matcher.group("invite") != null && matcher.group("invite").equals(partyLeader)) { - String command = "/party accept " + partyLeader; - sendCommand(command, 0); - return false; - } else { - this.repartying = false; - return false; - } - if (this.playersSoFar == this.players.length) { - reparty(); - } - return false; - } - - private void reparty() { - ClientPlayerEntity playerEntity = client.player; - if (playerEntity == null) { - this.repartying = false; - return; - } - sendCommand("/p disband", 1); - for (int i = 0; i < this.players.length; ++i) { - String command = "/p invite " + this.players[i]; - sendCommand(command, i + 2); - } - Scheduler.INSTANCE.schedule(() -> this.repartying = false, this.players.length + 2); - } - - private void sendCommand(String command, int delay) { - MessageScheduler.INSTANCE.queueMessage(command, delay * BASE_DELAY); - } -}
\ No newline at end of file diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/StarredMobGlow.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/StarredMobGlow.java deleted file mode 100644 index f614662b..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/StarredMobGlow.java +++ /dev/null @@ -1,56 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon; - -import me.xmrvizzy.skyblocker.utils.Utils; -import me.xmrvizzy.skyblocker.utils.render.culling.OcclusionCulling; -import net.minecraft.entity.Entity; -import net.minecraft.entity.decoration.ArmorStandEntity; -import net.minecraft.entity.passive.BatEntity; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.predicate.entity.EntityPredicates; -import net.minecraft.util.math.Box; - -import java.util.List; - -public class StarredMobGlow { - - public static boolean shouldMobGlow(Entity entity) { - Box box = entity.getBoundingBox(); - - if (Utils.isInDungeons() && !entity.isInvisible() && OcclusionCulling.isVisible(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ)) { - // Minibosses - if (entity instanceof PlayerEntity) { - switch (entity.getName().getString()) { - case "Lost Adventurer", "Shadow Assassin", "Diamond Guy" -> { - return true; - } - } - } - - // Regular Mobs - if (!(entity instanceof ArmorStandEntity)) { - Box searchBox = box.expand(0, 2, 0); - List<ArmorStandEntity> armorStands = entity.getWorld().getEntitiesByClass(ArmorStandEntity.class, searchBox, EntityPredicates.NOT_MOUNTED); - - if (!armorStands.isEmpty() && armorStands.get(0).getName().getString().contains("✯")) return true; - } - - // Bats - return entity instanceof BatEntity; - } - - return false; - } - - public static int getGlowColor(Entity entity) { - if (entity instanceof PlayerEntity) { - return switch (entity.getName().getString()) { - case "Lost Adventurer" -> 0xfee15c; - case "Shadow Assassin" -> 0x5b2cb2; - case "Diamond Guy" -> 0x57c2f7; - default -> 0xf57738; - }; - } - - return 0xf57738; - } -} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/ThreeWeirdos.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/ThreeWeirdos.java deleted file mode 100644 index 05ed4d94..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/ThreeWeirdos.java +++ /dev/null @@ -1,39 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon; - -import me.xmrvizzy.skyblocker.config.SkyblockerConfigManager; -import me.xmrvizzy.skyblocker.utils.chat.ChatFilterResult; -import me.xmrvizzy.skyblocker.utils.chat.ChatPatternListener; -import net.minecraft.client.MinecraftClient; -import net.minecraft.entity.decoration.ArmorStandEntity; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; - -import java.util.regex.Matcher; - -public class ThreeWeirdos extends ChatPatternListener { - public ThreeWeirdos() { - super("^§e\\[NPC] §c([A-Z][a-z]+)§f: (?:The reward is(?: not in my chest!|n't in any of our chests\\.)|My chest (?:doesn't have the reward\\. We are all telling the truth\\.|has the reward and I'm telling the truth!)|At least one of them is lying, and the reward is not in §c§c[A-Z][a-z]+'s §rchest\\!|Both of them are telling the truth\\. Also, §c§c[A-Z][a-z]+ §rhas the reward in their chest\\!)$"); - } - - @Override - public ChatFilterResult state() { - return SkyblockerConfigManager.get().locations.dungeons.solveThreeWeirdos ? null : ChatFilterResult.PASS; - } - - @Override - public boolean onMatch(Text message, Matcher matcher) { - MinecraftClient client = MinecraftClient.getInstance(); - if (client.player == null || client.world == null) return false; - client.world.getEntitiesByClass( - ArmorStandEntity.class, - client.player.getBoundingBox().expand(3), - entity -> { - Text customName = entity.getCustomName(); - return customName != null && customName.getString().equals(matcher.group(1)); - } - ).forEach( - entity -> entity.setCustomName(Text.of(Formatting.GREEN + matcher.group(1))) - ); - return false; - } -} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/TicTacToe.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/TicTacToe.java deleted file mode 100644 index 21493da7..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/TicTacToe.java +++ /dev/null @@ -1,136 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon; - -import me.xmrvizzy.skyblocker.config.SkyblockerConfigManager; -import me.xmrvizzy.skyblocker.utils.Utils; -import me.xmrvizzy.skyblocker.utils.render.RenderHelper; -import me.xmrvizzy.skyblocker.utils.tictactoe.TicTacToeUtils; -import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; -import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; -import net.minecraft.block.Block; -import net.minecraft.block.Blocks; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayerEntity; -import net.minecraft.client.world.ClientWorld; -import net.minecraft.entity.decoration.ItemFrameEntity; -import net.minecraft.item.FilledMapItem; -import net.minecraft.item.map.MapState; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Box; -import net.minecraft.util.math.Direction; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; - -/** - * Thanks to Danker for a reference implementation! - */ -public class TicTacToe { - private static final Logger LOGGER = LoggerFactory.getLogger(TicTacToe.class); - private static final float[] RED_COLOR_COMPONENTS = {1.0F, 0.0F, 0.0F}; - private static Box nextBestMoveToMake = null; - - public static void init() { - WorldRenderEvents.BEFORE_DEBUG_RENDER.register(TicTacToe::solutionRenderer); - } - - public static void tick() { - MinecraftClient client = MinecraftClient.getInstance(); - ClientWorld world = client.world; - ClientPlayerEntity player = client.player; - - nextBestMoveToMake = null; - - if (world == null || player == null || !Utils.isInDungeons()) return; - - //Search within 21 blocks for item frames that contain maps - Box searchBox = new Box(player.getX() - 21, player.getY() - 21, player.getZ() - 21, player.getX() + 21, player.getY() + 21, player.getZ() + 21); - List<ItemFrameEntity> itemFramesThatHoldMaps = world.getEntitiesByClass(ItemFrameEntity.class, searchBox, ItemFrameEntity::containsMap); - - try { - //Only attempt to solve if its the player's turn - if (itemFramesThatHoldMaps.size() != 9 && itemFramesThatHoldMaps.size() % 2 == 1) { - char[][] board = new char[3][3]; - BlockPos leftmostRow = null; - int sign = 1; - char facing = 'X'; - - for (ItemFrameEntity itemFrame : itemFramesThatHoldMaps) { - MapState mapState = world.getMapState(FilledMapItem.getMapName(itemFrame.getMapId().getAsInt())); - - if (mapState == null) continue; - - int column = 0, row; - sign = 1; - - //Find position of the item frame relative to where it is on the tic tac toe board - if (itemFrame.getHorizontalFacing() == Direction.SOUTH || itemFrame.getHorizontalFacing() == Direction.WEST) sign = -1; - BlockPos itemFramePos = BlockPos.ofFloored(itemFrame.getX(), itemFrame.getY(), itemFrame.getZ()); - - for (int i = 2; i >= 0; i--) { - int realI = i * sign; - BlockPos blockPos = itemFramePos; - - if (itemFrame.getX() % 0.5 == 0) { - blockPos = itemFramePos.add(realI, 0, 0); - } else if (itemFrame.getZ() % 0.5 == 0) { - blockPos = itemFramePos.add(0, 0, realI); - facing = 'Z'; - } - - Block block = world.getBlockState(blockPos).getBlock(); - if (block == Blocks.AIR || block == Blocks.STONE_BUTTON) { - leftmostRow = blockPos; - column = i; - - break; - } - } - - //Determine the row of the item frame - if (itemFrame.getY() == 72.5) { - row = 0; - } else if (itemFrame.getY() == 71.5) { - row = 1; - } else if (itemFrame.getY() == 70.5) { - row = 2; - } else { - continue; - } - - - //Get the color of the middle pixel of the map which determines whether its X or O - int middleColor = mapState.colors[8256] & 255; - - if (middleColor == 114) { - board[row][column] = 'X'; - } else if (middleColor == 33) { - board[row][column] = 'O'; - } - - int bestMove = TicTacToeUtils.getBestMove(board) - 1; - - if (leftmostRow != null) { - double drawX = facing == 'X' ? leftmostRow.getX() - sign * (bestMove % 3) : leftmostRow.getX(); - double drawY = 72 - (double) (bestMove / 3); - double drawZ = facing == 'Z' ? leftmostRow.getZ() - sign * (bestMove % 3) : leftmostRow.getZ(); - - nextBestMoveToMake = new Box(drawX, drawY, drawZ, drawX + 1, drawY + 1, drawZ + 1); - } - } - } - } catch (Exception e) { - LOGGER.error("[Skyblocker Tic Tac Toe] Encountered an exception while determining a tic tac toe solution!", e); - } - } - - private static void solutionRenderer(WorldRenderContext context) { - try { - if (SkyblockerConfigManager.get().locations.dungeons.solveTicTacToe && nextBestMoveToMake != null) { - RenderHelper.renderOutline(context, nextBestMoveToMake, RED_COLOR_COMPONENTS, 5); - } - } catch (Exception e) { - LOGGER.error("[Skyblocker Tic Tac Toe] Encountered an exception while rendering the tic tac toe solution!", e); - } - } -} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/Trivia.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/Trivia.java deleted file mode 100644 index b1e51aa6..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/Trivia.java +++ /dev/null @@ -1,100 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon; - -import me.xmrvizzy.skyblocker.config.SkyblockerConfigManager; -import me.xmrvizzy.skyblocker.utils.chat.ChatFilterResult; -import me.xmrvizzy.skyblocker.utils.chat.ChatPatternListener; -import me.xmrvizzy.skyblocker.skyblock.FairySouls; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayerEntity; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; -import java.util.regex.Matcher; - -public class Trivia extends ChatPatternListener { - private static final Map<String, String[]> answers; - private List<String> solutions = Collections.emptyList(); - - public Trivia() { - super("^ +(?:([A-Za-z,' ]*\\?)|§6 ([ⓐⓑⓒ]) §a([a-zA-Z0-9 ]+))$"); - } - - @Override - public ChatFilterResult state() { - return SkyblockerConfigManager.get().locations.dungeons.solveTrivia ? ChatFilterResult.FILTER : ChatFilterResult.PASS; - } - - @Override - public boolean onMatch(Text message, Matcher matcher) { - String riddle = matcher.group(3); - if (riddle != null) { - if (!solutions.contains(riddle)) { - ClientPlayerEntity player = MinecraftClient.getInstance().player; - if (player != null) - MinecraftClient.getInstance().player.sendMessage(Text.of(" " + Formatting.GOLD + matcher.group(2) + Formatting.RED + " " + riddle), false); - return player != null; - } - } else updateSolutions(matcher.group(0)); - return false; - } - - private void updateSolutions(String question) { - String trimmedQuestion = question.trim(); - if (trimmedQuestion.equals("What SkyBlock year is it?")) { - long currentTime = System.currentTimeMillis() / 1000L; - long diff = currentTime - 1560276000; - int year = (int) (diff / 446400 + 1); - solutions = Collections.singletonList("Year " + year); - } else { - solutions = Arrays.asList(answers.get(trimmedQuestion)); - } - } - - static { - answers = Collections.synchronizedMap(new HashMap<>()); - answers.put("What is the status of The Watcher?", new String[]{"Stalker"}); - answers.put("What is the status of Bonzo?", new String[]{"New Necromancer"}); - answers.put("What is the status of Scarf?", new String[]{"Apprentice Necromancer"}); - answers.put("What is the status of The Professor?", new String[]{"Professor"}); - answers.put("What is the status of Thorn?", new String[]{"Shaman Necromancer"}); - answers.put("What is the status of Livid?", new String[]{"Master Necromancer"}); - answers.put("What is the status of Sadan?", new String[]{"Necromancer Lord"}); - answers.put("What is the status of Maxor?", new String[]{"The Wither Lords"}); - answers.put("What is the status of Goldor?", new String[]{"The Wither Lords"}); - answers.put("What is the status of Storm?", new String[]{"The Wither Lords"}); - answers.put("What is the status of Necron?", new String[]{"The Wither Lords"}); - answers.put("What is the status of Maxor, Storm, Goldor and Necron?", new String[]{"The Wither Lords"}); - answers.put("Which brother is on the Spider's Den?", new String[]{"Rick"}); - answers.put("What is the name of Rick's brother?", new String[]{"Pat"}); - answers.put("What is the name of the Painter in the Hub?", new String[]{"Marco"}); - answers.put("What is the name of the person that upgrades pets?", new String[]{"Kat"}); - answers.put("What is the name of the lady of the Nether?", new String[]{"Elle"}); - answers.put("Which villager in the Village gives you a Rogue Sword?", new String[]{"Jamie"}); - answers.put("How many unique minions are there?", new String[]{"59 Minions"}); - answers.put("Which of these enemies does not spawn in the Spider's Den?", new String[]{"Zombie Spider", "Cave Spider", "Wither Skeleton", "Dashing Spooder", "Broodfather", "Night Spider"}); - answers.put("Which of these monsters only spawns at night?", new String[]{"Zombie Villager", "Ghast"}); - answers.put("Which of these is not a dragon in The End?", new String[]{"Zoomer Dragon", "Weak Dragon", "Stonk Dragon", "Holy Dragon", "Boomer Dragon", "Booger Dragon", "Older Dragon", "Elder Dragon", "Stable Dragon", "Professor Dragon"}); - FairySouls.runAsyncAfterFairySoulsLoad(() -> { - answers.put("How many total Fairy Souls are there?", getFairySoulsSizeString(null)); - answers.put("How many Fairy Souls are there in Spider's Den?", getFairySoulsSizeString("combat_1")); - answers.put("How many Fairy Souls are there in The End?", getFairySoulsSizeString("combat_3")); - answers.put("How many Fairy Souls are there in The Farming Islands?", getFairySoulsSizeString("farming_1")); - answers.put("How many Fairy Souls are there in Crimson Isle?", getFairySoulsSizeString("crimson_isle")); - answers.put("How many Fairy Souls are there in The Park?", getFairySoulsSizeString("foraging_1")); - answers.put("How many Fairy Souls are there in Jerry's Workshop?", getFairySoulsSizeString("winter")); - answers.put("How many Fairy Souls are there in Hub?", getFairySoulsSizeString("hub")); - answers.put("How many Fairy Souls are there in The Hub?", getFairySoulsSizeString("hub")); - answers.put("How many Fairy Souls are there in Deep Caverns?", getFairySoulsSizeString("mining_2")); - answers.put("How many Fairy Souls are there in Gold Mine?", getFairySoulsSizeString("mining_1")); - answers.put("How many Fairy Souls are there in Dungeon Hub?", getFairySoulsSizeString("dungeon_hub")); - }); - } - - @NotNull - private static String[] getFairySoulsSizeString(@Nullable String location) { - return new String[]{"%d Fairy Souls".formatted(FairySouls.getFairySoulsSize(location))}; - } -} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java deleted file mode 100644 index 04cde662..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java +++ /dev/null @@ -1,275 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon.secrets; - -import com.google.gson.JsonObject; -import it.unimi.dsi.fastutil.ints.IntSortedSet; -import it.unimi.dsi.fastutil.objects.ObjectIntPair; -import net.minecraft.block.MapColor; -import net.minecraft.item.map.MapIcon; -import net.minecraft.item.map.MapState; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.Vec3d; -import net.minecraft.util.math.Vec3i; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.joml.RoundingMode; -import org.joml.Vector2i; -import org.joml.Vector2ic; - -import java.util.*; - -public class DungeonMapUtils { - public static final byte BLACK_COLOR = MapColor.BLACK.getRenderColorByte(MapColor.Brightness.LOWEST); - public static final byte WHITE_COLOR = MapColor.WHITE.getRenderColorByte(MapColor.Brightness.HIGH); - - public static byte getColor(MapState map, @Nullable Vector2ic pos) { - return pos == null ? -1 : getColor(map, pos.x(), pos.y()); - } - - public static byte getColor(MapState map, int x, int z) { - if (x < 0 || z < 0 || x >= 128 || z >= 128) { - return -1; - } - return map.colors[x + (z << 7)]; - } - - public static boolean isEntranceColor(MapState map, int x, int z) { - return getColor(map, x, z) == Room.Type.ENTRANCE.color; - } - - public static boolean isEntranceColor(MapState map, @Nullable Vector2ic pos) { - return getColor(map, pos) == Room.Type.ENTRANCE.color; - } - - @Nullable - private static Vector2i getMapPlayerPos(MapState map) { - for (MapIcon icon : map.getIcons()) { - if (icon.type() == MapIcon.Type.FRAME) { - return new Vector2i((icon.x() >> 1) + 64, (icon.z() >> 1) + 64); - } - } - return null; - } - - @Nullable - public static ObjectIntPair<Vector2ic> getMapEntrancePosAndRoomSize(@NotNull MapState map) { - Vector2ic mapPos = getMapPlayerPos(map); - if (mapPos == null) { - return null; - } - Queue<Vector2ic> posToCheck = new ArrayDeque<>(); - Set<Vector2ic> checked = new HashSet<>(); - posToCheck.add(mapPos); - checked.add(mapPos); - while ((mapPos = posToCheck.poll()) != null) { - if (isEntranceColor(map, mapPos)) { - ObjectIntPair<Vector2ic> mapEntranceAndRoomSizePos = getMapEntrancePosAndRoomSizeAt(map, mapPos); - if (mapEntranceAndRoomSizePos.rightInt() > 0) { - return mapEntranceAndRoomSizePos; - } - } - Vector2ic pos = new Vector2i(mapPos).sub(10, 0); - if (checked.add(pos)) { - posToCheck.add(pos); - } - pos = new Vector2i(mapPos).sub(0, 10); - if (checked.add(pos)) { - posToCheck.add(pos); - } - pos = new Vector2i(mapPos).add(10, 0); - if (checked.add(pos)) { - posToCheck.add(pos); - } - pos = new Vector2i(mapPos).add(0, 10); - if (checked.add(pos)) { - posToCheck.add(pos); - } - } - return null; - } - - private static ObjectIntPair<Vector2ic> getMapEntrancePosAndRoomSizeAt(MapState map, Vector2ic mapPosImmutable) { - Vector2i mapPos = new Vector2i(mapPosImmutable); - // noinspection StatementWithEmptyBody - while (isEntranceColor(map, mapPos.sub(1, 0))) { - } - mapPos.add(1, 0); - //noinspection StatementWithEmptyBody - while (isEntranceColor(map, mapPos.sub(0, 1))) { - } - return ObjectIntPair.of(mapPos.add(0, 1), getMapRoomSize(map, mapPos)); - } - - public static int getMapRoomSize(MapState map, Vector2ic mapEntrancePos) { - int i = -1; - //noinspection StatementWithEmptyBody - while (isEntranceColor(map, mapEntrancePos.x() + ++i, mapEntrancePos.y())) { - } - return i > 5 ? i : 0; - } - - /** - * Gets the map position of the top left corner of the room the player is in. - * - * @param map the map - * @param mapEntrancePos the map position of the top left corner of the entrance - * @param mapRoomSize the size of a room on the map - * @return the map position of the top left corner of the room the player is in - * @implNote {@code mapPos} is shifted by 2 so room borders are evenly split. - * {@code mapPos} is then shifted by {@code offset} to align the top left most room at (0, 0) - * so subtracting the modulo will give the top left corner of the room shifted by {@code offset}. - * Finally, {@code mapPos} is shifted back by {@code offset} to its intended position. - */ - @Nullable - public static Vector2ic getMapRoomPos(MapState map, Vector2ic mapEntrancePos, int mapRoomSize) { - int mapRoomSizeWithGap = mapRoomSize + 4; - Vector2i mapPos = getMapPlayerPos(map); - if (mapPos == null) { - return null; - } - Vector2ic offset = new Vector2i(mapEntrancePos.x() % mapRoomSizeWithGap, mapEntrancePos.y() % mapRoomSizeWithGap); - return mapPos.add(2, 2).sub(offset).sub(mapPos.x() % mapRoomSizeWithGap, mapPos.y() % mapRoomSizeWithGap).add(offset); - } - - /** - * Gets the map position of the top left corner of the room corresponding to the physical position of the northwest corner of a room. - * - * @param physicalEntrancePos the physical position of the northwest corner of the entrance room - * @param mapEntrancePos the map position of the top left corner of the entrance room - * @param mapRoomSize the size of a room on the map - * @param physicalPos the physical position of the northwest corner of the room - * @return the map position of the top left corner of the room corresponding to the physical position of the northwest corner of a room - */ - public static Vector2ic getMapPosFromPhysical(Vector2ic physicalEntrancePos, Vector2ic mapEntrancePos, int mapRoomSize, Vector2ic physicalPos) { - return new Vector2i(physicalPos).sub(physicalEntrancePos).div(32).mul(mapRoomSize + 4).add(mapEntrancePos); - } - - /** - * @see #getPhysicalRoomPos(double, double) - */ - @NotNull - public static Vector2ic getPhysicalRoomPos(@NotNull Vec3d pos) { - return getPhysicalRoomPos(pos.getX(), pos.getZ()); - } - - /** - * @see #getPhysicalRoomPos(double, double) - */ - @NotNull - public static Vector2ic getPhysicalRoomPos(@NotNull Vec3i pos) { - return getPhysicalRoomPos(pos.getX(), pos.getZ()); - } - - /** - * Gets the physical position of the northwest corner of the room the given coordinate is in. Hypixel Skyblock Dungeons are aligned to a 32 by 32 blocks grid, allowing corners to be calculated through math. - * - * @param x the x position of the coordinate to calculate - * @param z the z position of the coordinate to calculate - * @return the physical position of the northwest corner of the room the player is in - * @implNote {@code physicalPos} is shifted by 0.5 so room borders are evenly split. - * {@code physicalPos} is further shifted by 8 because Hypixel offset dungeons by 8 blocks in Skyblock 0.12.3. - * Subtracting the modulo gives the northwest corner of the room shifted by 8. Finally, {@code physicalPos} is shifted back by 8 to its intended position. - */ - @NotNull - public static Vector2ic getPhysicalRoomPos(double x, double z) { - Vector2i physicalPos = new Vector2i(x + 8.5, z + 8.5, RoundingMode.TRUNCATE); - return physicalPos.sub(MathHelper.floorMod(physicalPos.x(), 32), MathHelper.floorMod(physicalPos.y(), 32)).sub(8, 8); - } - - public static Vector2ic[] getPhysicalPosFromMap(Vector2ic mapEntrancePos, int mapRoomSize, Vector2ic physicalEntrancePos, Vector2ic... mapPositions) { - for (int i = 0; i < mapPositions.length; i++) { - mapPositions[i] = getPhysicalPosFromMap(mapEntrancePos, mapRoomSize, physicalEntrancePos, mapPositions[i]); - } - return mapPositions; - } - - /** - * Gets the physical position of the northwest corner of the room corresponding to the map position of the top left corner of a room. - * - * @param mapEntrancePos the map position of the top left corner of the entrance room - * @param mapRoomSize the size of a room on the map - * @param physicalEntrancePos the physical position of the northwest corner of the entrance room - * @param mapPos the map position of the top left corner of the room - * @return the physical position of the northwest corner of the room corresponding to the map position of the top left corner of a room - */ - public static Vector2ic getPhysicalPosFromMap(Vector2ic mapEntrancePos, int mapRoomSize, Vector2ic physicalEntrancePos, Vector2ic mapPos) { - return new Vector2i(mapPos).sub(mapEntrancePos).div(mapRoomSize + 4).mul(32).add(physicalEntrancePos); - } - - public static Vector2ic getPhysicalCornerPos(Room.Direction direction, IntSortedSet segmentsX, IntSortedSet segmentsY) { - return switch (direction) { - case NW -> new Vector2i(segmentsX.firstInt(), segmentsY.firstInt()); - case NE -> new Vector2i(segmentsX.lastInt() + 30, segmentsY.firstInt()); - case SW -> new Vector2i(segmentsX.firstInt(), segmentsY.lastInt() + 30); - case SE -> new Vector2i(segmentsX.lastInt() + 30, segmentsY.lastInt() + 30); - }; - } - - public static BlockPos actualToRelative(Room.Direction direction, Vector2ic physicalCornerPos, BlockPos pos) { - return switch (direction) { - case NW -> new BlockPos(pos.getX() - physicalCornerPos.x(), pos.getY(), pos.getZ() - physicalCornerPos.y()); - case NE -> new BlockPos(pos.getZ() - physicalCornerPos.y(), pos.getY(), -pos.getX() + physicalCornerPos.x()); - case SW -> new BlockPos(-pos.getZ() + physicalCornerPos.y(), pos.getY(), pos.getX() - physicalCornerPos.x()); - case SE -> new BlockPos(-pos.getX() + physicalCornerPos.x(), pos.getY(), -pos.getZ() + physicalCornerPos.y()); - }; - } - - public static BlockPos relativeToActual(Room.Direction direction, Vector2ic physicalCornerPos, JsonObject posJson) { - return relativeToActual(direction, physicalCornerPos, new BlockPos(posJson.get("x").getAsInt(), posJson.get("y").getAsInt(), posJson.get("z").getAsInt())); - } - - public static BlockPos relativeToActual(Room.Direction direction, Vector2ic physicalCornerPos, BlockPos pos) { - return switch (direction) { - case NW -> new BlockPos(pos.getX() + physicalCornerPos.x(), pos.getY(), pos.getZ() + physicalCornerPos.y()); - case NE -> new BlockPos(-pos.getZ() + physicalCornerPos.x(), pos.getY(), pos.getX() + physicalCornerPos.y()); - case SW -> new BlockPos(pos.getZ() + physicalCornerPos.x(), pos.getY(), -pos.getX() + physicalCornerPos.y()); - case SE -> new BlockPos(-pos.getX() + physicalCornerPos.x(), pos.getY(), -pos.getZ() + physicalCornerPos.y()); - }; - } - - public static Room.Type getRoomType(MapState map, Vector2ic mapPos) { - return switch (getColor(map, mapPos)) { - case 30 -> Room.Type.ENTRANCE; - case 63 -> Room.Type.ROOM; - case 66 -> Room.Type.PUZZLE; - case 62 -> Room.Type.TRAP; - case 74 -> Room.Type.MINIBOSS; - case 82 -> Room.Type.FAIRY; - case 18 -> Room.Type.BLOOD; - case 85 -> Room.Type.UNKNOWN; - default -> null; - }; - } - - public static Vector2ic[] getRoomSegments(MapState map, Vector2ic mapPos, int mapRoomSize, byte color) { - Set<Vector2ic> segments = new HashSet<>(); - Queue<Vector2ic> queue = new ArrayDeque<>(); - segments.add(mapPos); - queue.add(mapPos); - while (!queue.isEmpty()) { - Vector2ic curMapPos = queue.poll(); - Vector2i newMapPos = new Vector2i(); - if (getColor(map, newMapPos.set(curMapPos).sub(1, 0)) == color && !segments.contains(newMapPos.sub(mapRoomSize + 3, 0))) { - segments.add(newMapPos); - queue.add(newMapPos); - newMapPos = new Vector2i(); - } - if (getColor(map, newMapPos.set(curMapPos).sub(0, 1)) == color && !segments.contains(newMapPos.sub(0, mapRoomSize + 3))) { - segments.add(newMapPos); - queue.add(newMapPos); - newMapPos = new Vector2i(); - } - if (getColor(map, newMapPos.set(curMapPos).add(mapRoomSize, 0)) == color && !segments.contains(newMapPos.add(4, 0))) { - segments.add(newMapPos); - queue.add(newMapPos); - newMapPos = new Vector2i(); - } - if (getColor(map, newMapPos.set(curMapPos).add(0, mapRoomSize)) == color && !segments.contains(newMapPos.add(0, 4))) { - segments.add(newMapPos); - queue.add(newMapPos); - } - } - DungeonSecrets.LOGGER.debug("[Skyblocker] Found dungeon room segments: {}", Arrays.toString(segments.toArray())); - return segments.toArray(Vector2ic[]::new); - } -} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java deleted file mode 100644 index 4e4e9565..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java +++ /dev/null @@ -1,451 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon.secrets; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.mojang.brigadier.Command; -import com.mojang.brigadier.arguments.IntegerArgumentType; -import com.mojang.brigadier.builder.ArgumentBuilder; -import com.mojang.brigadier.builder.RequiredArgumentBuilder; -import it.unimi.dsi.fastutil.objects.Object2ByteMap; -import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectIntPair; -import me.xmrvizzy.skyblocker.SkyblockerMod; -import me.xmrvizzy.skyblocker.config.SkyblockerConfigManager; -import me.xmrvizzy.skyblocker.utils.Utils; -import me.xmrvizzy.skyblocker.utils.scheduler.Scheduler; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; -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.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayerEntity; -import net.minecraft.entity.ItemEntity; -import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.mob.AmbientEntity; -import net.minecraft.entity.passive.BatEntity; -import net.minecraft.item.FilledMapItem; -import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; -import net.minecraft.item.map.MapState; -import net.minecraft.resource.Resource; -import net.minecraft.text.Text; -import net.minecraft.util.ActionResult; -import net.minecraft.util.Identifier; -import net.minecraft.util.hit.BlockHitResult; -import net.minecraft.util.math.Vec3d; -import net.minecraft.world.World; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.joml.Vector2ic; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.zip.InflaterInputStream; - -import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument; -import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; - -public class DungeonSecrets { - protected static final Logger LOGGER = LoggerFactory.getLogger(DungeonSecrets.class); - private static final String DUNGEONS_PATH = "dungeons"; - /** - * Maps the block identifier string to a custom numeric block id used in dungeon rooms data. - * - * @implNote Not using {@link net.minecraft.registry.Registry#getId(Object) Registry#getId(Block)} and {@link net.minecraft.block.Blocks Blocks} since this is also used by {@link me.xmrvizzy.skyblocker.skyblock.dungeon.secrets.DungeonRoomsDFU DungeonRoomsDFU}, which runs outside of Minecraft. - */ - @SuppressWarnings("JavadocReference") - protected static final Object2ByteMap<String> NUMERIC_ID = new Object2ByteOpenHashMap<>(Map.ofEntries( - Map.entry("minecraft:stone", (byte) 1), - Map.entry("minecraft:diorite", (byte) 2), - Map.entry("minecraft:polished_diorite", (byte) 3), - Map.entry("minecraft:andesite", (byte) 4), - Map.entry("minecraft:polished_andesite", (byte) 5), - Map.entry("minecraft:grass_block", (byte) 6), - Map.entry("minecraft:dirt", (byte) 7), - Map.entry("minecraft:coarse_dirt", (byte) 8), - Map.entry("minecraft:cobblestone", (byte) 9), - Map.entry("minecraft:bedrock", (byte) 10), - Map.entry("minecraft:oak_leaves", (byte) 11), - Map.entry("minecraft:gray_wool", (byte) 12), - Map.entry("minecraft:double_stone_slab", (byte) 13), - Map.entry("minecraft:mossy_cobblestone", (byte) 14), - Map.entry("minecraft:clay", (byte) 15), - Map.entry("minecraft:stone_bricks", (byte) 16), - Map.entry("minecraft:mossy_stone_bricks", (byte) 17), - Map.entry("minecraft:chiseled_stone_bricks", (byte) 18), - Map.entry("minecraft:gray_terracotta", (byte) 19), - Map.entry("minecraft:cyan_terracotta", (byte) 20), - Map.entry("minecraft:black_terracotta", (byte) 21) - )); - /** - * Block data for dungeon rooms. See {@link me.xmrvizzy.skyblocker.skyblock.dungeon.secrets.DungeonRoomsDFU DungeonRoomsDFU} for format details and how it's generated. - * All access to this map must check {@link #isRoomsLoaded()} to prevent concurrent modification. - */ - @SuppressWarnings("JavadocReference") - protected static final HashMap<String, Map<String, Map<String, int[]>>> ROOMS_DATA = new HashMap<>(); - @NotNull - private static final Map<Vector2ic, Room> rooms = new HashMap<>(); - private static final Map<String, JsonElement> roomsJson = new HashMap<>(); - private static final Map<String, JsonElement> waypointsJson = new HashMap<>(); - @Nullable - private static CompletableFuture<Void> roomsLoaded; - /** - * The map position of the top left corner of the entrance room. - */ - @Nullable - private static Vector2ic mapEntrancePos; - /** - * The size of a room on the map. - */ - private static int mapRoomSize; - /** - * The physical position of the northwest corner of the entrance room. - */ - @Nullable - private static Vector2ic physicalEntrancePos; - private static Room currentRoom; - - public static boolean isRoomsLoaded() { - return roomsLoaded != null && roomsLoaded.isDone(); - } - - @SuppressWarnings("unused") - public static JsonObject getRoomMetadata(String room) { - return roomsJson.get(room).getAsJsonObject(); - } - - public static JsonArray getRoomWaypoints(String room) { - return waypointsJson.get(room).getAsJsonArray(); - } - - /** - * Loads the dungeon secrets asynchronously from {@code /assets/skyblocker/dungeons}. - * Use {@link #isRoomsLoaded()} to check for completion of loading. - */ - public static void init() { - if (SkyblockerConfigManager.get().locations.dungeons.secretWaypoints.noInitSecretWaypoints) { - return; - } - // Execute with MinecraftClient as executor since we need to wait for MinecraftClient#resourceManager to be set - CompletableFuture.runAsync(DungeonSecrets::load, MinecraftClient.getInstance()).exceptionally(e -> { - LOGGER.error("[Skyblocker] Failed to load dungeon secrets", e); - return null; - }); - Scheduler.INSTANCE.scheduleCyclic(DungeonSecrets::update, 10); - WorldRenderEvents.AFTER_TRANSLUCENT.register(DungeonSecrets::render); - ClientReceiveMessageEvents.GAME.register(DungeonSecrets::onChatMessage); - ClientReceiveMessageEvents.GAME_CANCELED.register(DungeonSecrets::onChatMessage); - UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> onUseBlock(world, hitResult)); - ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("dungeons").then(literal("secrets") - .then(literal("markAsFound").then(markSecretsCommand(true))) - .then(literal("markAsMissing").then(markSecretsCommand(false))))))); - ClientPlayConnectionEvents.JOIN.register(((handler, sender, client) -> reset())); - } - - private static void load() { - long startTime = System.currentTimeMillis(); - List<CompletableFuture<Void>> dungeonFutures = new ArrayList<>(); - for (Map.Entry<Identifier, Resource> resourceEntry : MinecraftClient.getInstance().getResourceManager().findResources(DUNGEONS_PATH, id -> id.getPath().endsWith(".skeleton")).entrySet()) { - String[] path = resourceEntry.getKey().getPath().split("/"); - if (path.length != 4) { - LOGGER.error("[Skyblocker] Failed to load dungeon secrets, invalid resource identifier {}", resourceEntry.getKey()); - break; - } - String dungeon = path[1]; - String roomShape = path[2]; - String room = path[3].substring(0, path[3].length() - ".skeleton".length()); - ROOMS_DATA.computeIfAbsent(dungeon, dungeonKey -> new HashMap<>()); - ROOMS_DATA.get(dungeon).computeIfAbsent(roomShape, roomShapeKey -> new HashMap<>()); - dungeonFutures.add(CompletableFuture.supplyAsync(() -> readRoom(resourceEntry.getValue())).thenAcceptAsync(rooms -> { - Map<String, int[]> roomsMap = ROOMS_DATA.get(dungeon).get(roomShape); - synchronized (roomsMap) { - roomsMap.put(room, rooms); - } - LOGGER.debug("[Skyblocker] Loaded dungeon secrets dungeon {} room shape {} room {}", dungeon, roomShape, room); - }).exceptionally(e -> { - LOGGER.error("[Skyblocker] Failed to load dungeon secrets dungeon {} room shape {} room {}", dungeon, roomShape, room, e); - return null; - })); - } - dungeonFutures.add(CompletableFuture.runAsync(() -> { - try (BufferedReader roomsReader = MinecraftClient.getInstance().getResourceManager().openAsReader(new Identifier(SkyblockerMod.NAMESPACE, "dungeons/dungeonrooms.json")); BufferedReader waypointsReader = MinecraftClient.getInstance().getResourceManager().openAsReader(new Identifier(SkyblockerMod.NAMESPACE, "dungeons/secretlocations.json"))) { - loadJson(roomsReader, roomsJson); - loadJson(waypointsReader, waypointsJson); - LOGGER.debug("[Skyblocker] Loaded dungeon secrets json"); - } catch (Exception e) { - LOGGER.error("[Skyblocker] Failed to load dungeon secrets json", e); - } - })); - roomsLoaded = CompletableFuture.allOf(dungeonFutures.toArray(CompletableFuture[]::new)).thenRun(() -> LOGGER.info("[Skyblocker] Loaded dungeon secrets for {} dungeon(s), {} room shapes, and {} rooms total in {} ms", ROOMS_DATA.size(), ROOMS_DATA.values().stream().mapToInt(Map::size).sum(), ROOMS_DATA.values().stream().map(Map::values).flatMap(Collection::stream).mapToInt(Map::size).sum(), System.currentTimeMillis() - startTime)).exceptionally(e -> { - LOGGER.error("[Skyblocker] Failed to load dungeon secrets", e); - return null; - }); - LOGGER.info("[Skyblocker] Started loading dungeon secrets in (blocked main thread for) {} ms", System.currentTimeMillis() - startTime); - } - - private static int[] readRoom(Resource resource) throws RuntimeException { - try (ObjectInputStream in = new ObjectInputStream(new InflaterInputStream(resource.getInputStream()))) { - return (int[]) in.readObject(); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - - /** - * Loads the json from the given {@link BufferedReader} into the given {@link Map}. - * @param reader the reader to read the json from - * @param map the map to load into - */ - private static void loadJson(BufferedReader reader, Map<String, JsonElement> map) { - SkyblockerMod.GSON.fromJson(reader, JsonObject.class).asMap().forEach((room, jsonElement) -> map.put(room.toLowerCase().replaceAll(" ", "-"), jsonElement)); - } - - private static ArgumentBuilder<FabricClientCommandSource, RequiredArgumentBuilder<FabricClientCommandSource, Integer>> markSecretsCommand(boolean found) { - return argument("secret", IntegerArgumentType.integer()).executes(context -> { - int secretIndex = IntegerArgumentType.getInteger(context, "secret"); - if (markSecrets(secretIndex, found)) { - context.getSource().sendFeedback(Text.translatable(found ? "skyblocker.dungeons.secrets.markSecretFound" : "skyblocker.dungeons.secrets.markSecretMissing", secretIndex)); - } else { - context.getSource().sendError(Text.translatable(found ? "skyblocker.dungeons.secrets.markSecretFoundUnable" : "skyblocker.dungeons.secrets.markSecretMissingUnable", secretIndex)); - } - return Command.SINGLE_SUCCESS; - }); - } - - /** - * Updates the dungeon. The general idea is similar to the Dungeon Rooms Mod. - * <p></p> - * When entering a new dungeon, this method: - * <ul> - * <li> Gets the physical northwest corner position of the entrance room and saves it in {@link #physicalEntrancePos}. </li> - * <li> Do nothing until the dungeon map exists. </li> - * <li> Gets the upper left corner of entrance room on the map and saves it in {@link #mapEntrancePos}. </li> - * <li> Gets the size of a room on the map in pixels and saves it in {@link #mapRoomSize}. </li> - * <li> Creates a new {@link Room} with {@link Room.Type} {@link Room.Type.ENTRANCE ENTRANCE} and sets {@link #currentRoom}. </li> - * </ul> - * When processing an existing dungeon, this method: - * <ul> - * <li> Calculates the physical northwest corner and upper left corner on the map of the room the player is currently in. </li> - * <li> Gets the room type based on the map color. </li> - * <li> If the room has not been created (when the physical northwest corner is not in {@link #rooms}):</li> - * <ul> - * <li> If the room type is {@link Room.Type.ROOM}, gets the northwest corner of all connected room segments with {@link DungeonMapUtils#getRoomSegments(MapState, Vector2ic, int, byte)}. (For example, a 1x2 room has two room segments.) </li> - * <li> Create a new room. </li> - * </ul> - * <li> Sets {@link #currentRoom} to the current room, either created from the previous step or from {@link #rooms}. </li> - * <li> Calls {@link Room#update()} on {@link #currentRoom}. </li> - * </ul> - */ - @SuppressWarnings("JavadocReference") - private static void update() { - if (!SkyblockerConfigManager.get().locations.dungeons.secretWaypoints.enableSecretWaypoints) { - return; - } - if (!Utils.isInDungeons()) { - if (mapEntrancePos != null) { - reset(); - } - return; - } - MinecraftClient client = MinecraftClient.getInstance(); - ClientPlayerEntity player = client.player; - if (player == null || client.world == null) { - return; - } - if (physicalEntrancePos == null) { - Vec3d playerPos = player.getPos(); - physicalEntrancePos = DungeonMapUtils.getPhysicalRoomPos(playerPos); - currentRoom = newRoom(Room.Type.ENTRANCE, physicalEntrancePos); - } - ItemStack stack = player.getInventory().main.get(8); - if (!stack.isOf(Items.FILLED_MAP)) { - return; - } - MapState map = FilledMapItem.getMapState(FilledMapItem.getMapId(stack), client.world); - if (map == null) { - return; - } - if (mapEntrancePos == null || mapRoomSize == 0) { - ObjectIntPair<Vector2ic> mapEntrancePosAndSize = DungeonMapUtils.getMapEntrancePosAndRoomSize(map); - if (mapEntrancePosAndSize == null) { - return; - } - mapEntrancePos = mapEntrancePosAndSize.left(); - mapRoomSize = mapEntrancePosAndSize.rightInt(); - LOGGER.info("[Skyblocker] Started dungeon with map room size {}, map entrance pos {}, player pos {}, and physical entrance pos {}", mapRoomSize, mapEntrancePos, client.player.getPos(), physicalEntrancePos); - } - - Vector2ic physicalPos = DungeonMapUtils.getPhysicalRoomPos(client.player.getPos()); - Vector2ic mapPos = DungeonMapUtils.getMapPosFromPhysical(physicalEntrancePos, mapEntrancePos, mapRoomSize, physicalPos); - Room room = rooms.get(physicalPos); - if (room == null) { - Room.Type type = DungeonMapUtils.getRoomType(map, mapPos); - if (type == null || type == Room.Type.UNKNOWN) { - return; - } - switch (type) { - case ENTRANCE, PUZZLE, TRAP, MINIBOSS, FAIRY, BLOOD -> room = newRoom(type, physicalPos); - case ROOM -> room = newRoom(type, DungeonMapUtils.getPhysicalPosFromMap(mapEntrancePos, mapRoomSize, physicalEntrancePos, DungeonMapUtils.getRoomSegments(map, mapPos, mapRoomSize, type.color))); - } - } - if (room != null && currentRoom != room) { - currentRoom = room; - } - currentRoom.update(); - } - - /** - * Creates a new room with the given type and physical positions, - * adds the room to {@link #rooms}, and sets {@link #currentRoom} to the new room. - * - * @param type the type of room to create - * @param physicalPositions the physical positions of the room - */ - @Nullable - private static Room newRoom(Room.Type type, Vector2ic... physicalPositions) { - try { - Room newRoom = new Room(type, physicalPositions); - for (Vector2ic physicalPos : physicalPositions) { - rooms.put(physicalPos, newRoom); - } - return newRoom; - } catch (IllegalArgumentException e) { - LOGGER.error("[Skyblocker] Failed to create room", e); - } - return null; - } - - /** - * Renders the secret waypoints in {@link #currentRoom} if {@link #isCurrentRoomMatched()}. - */ - private static void render(WorldRenderContext context) { - if (isCurrentRoomMatched()) { - currentRoom.render(context); - } - } - - /** - * Calls {@link Room#onChatMessage(String)} on {@link #currentRoom} if the message is an overlay message and {@link #isCurrentRoomMatched()}. - * Used to detect when all secrets in a room are found. - */ - private static void onChatMessage(Text text, boolean overlay) { - if (overlay && isCurrentRoomMatched()) { - currentRoom.onChatMessage(text.getString()); - } - } - - /** - * Calls {@link Room#onUseBlock(World, BlockHitResult)} on {@link #currentRoom} if {@link #isCurrentRoomMatched()}. - * Used to detect finding {@link SecretWaypoint.Category.CHEST} and {@link SecretWaypoint.Category.WITHER} secrets. - * - * @return {@link ActionResult#PASS} - */ - @SuppressWarnings("JavadocReference") - private static ActionResult onUseBlock(World world, BlockHitResult hitResult) { - if (isCurrentRoomMatched()) { - currentRoom.onUseBlock(world, hitResult); - } - return ActionResult.PASS; - } - - /** - * Calls {@link Room#onItemPickup(ItemEntity, LivingEntity)} on the room the {@code collector} is in if that room {@link #isRoomMatched(Room)}. - * Used to detect finding {@link SecretWaypoint.Category.ITEM} secrets. - * If the collector is the player, {@link #currentRoom} is used as an optimization. - */ - @SuppressWarnings("JavadocReference") - public static void onItemPickup(ItemEntity itemEntity, LivingEntity collector, boolean isPlayer) { - if (isPlayer) { - if (isCurrentRoomMatched()) { - currentRoom.onItemPickup(itemEntity, collector); - } - } else { - Room room = getRoomAtPhysical(collector.getPos()); - if (isRoomMatched(room)) { - room.onItemPickup(itemEntity, collector); - } - } - } - - /** - * Calls {@link Room#onBatRemoved(BatEntity)} on the room the {@code bat} is in if that room {@link #isRoomMatched(Room)}. - * Used to detect finding {@link SecretWaypoint.Category.BAT} secrets. - */ - @SuppressWarnings("JavadocReference") - public static void onBatRemoved(AmbientEntity bat) { - Room room = getRoomAtPhysical(bat.getPos()); - if (isRoomMatched(room)) { - room.onBatRemoved(bat); - } - } - - public static boolean markSecrets(int secretIndex, boolean found) { - if (isCurrentRoomMatched()) { - return currentRoom.markSecrets(secretIndex, found); - } - return false; - } - - /** - * Gets the room at the given physical position. - * - * @param pos the physical position - * @return the room at the given physical position, or null if there is no room at the given physical position - * @see #rooms - * @see DungeonMapUtils#getPhysicalRoomPos(Vec3d) - */ - @Nullable - private static Room getRoomAtPhysical(Vec3d pos) { - return rooms.get(DungeonMapUtils.getPhysicalRoomPos(pos)); - } - - /** - * Calls {@link #isRoomMatched(Room)} on {@link #currentRoom}. - * - * @return {@code true} if {@link #currentRoom} is not null and {@link #isRoomMatched(Room)} - */ - private static boolean isCurrentRoomMatched() { - return isRoomMatched(currentRoom); - } - - /** - * Calls {@link #shouldProcess()} and {@link Room#isMatched()} on the given room. - * - * @param room the room to check - * @return {@code true} if {@link #shouldProcess()}, the given room is not null, and {@link Room#isMatched()} on the given room - */ - @Contract("null -> false") - private static boolean isRoomMatched(@Nullable Room room) { - return shouldProcess() && room != null && room.isMatched(); - } - - /** - * Checks if the player is in a dungeon and {@link me.xmrvizzy.skyblocker.config.SkyblockerConfigManager.Dungeons#secretWaypoints Secret Waypoints} is enabled. - * - * @return whether dungeon secrets should be processed - */ - private static boolean shouldProcess() { - return SkyblockerConfigManager.get().locations.dungeons.secretWaypoints.enableSecretWaypoints && Utils.isInDungeons(); - } - - /** - * Resets fields when leaving a dungeon. - */ - private static void reset() { - mapEntrancePos = null; - mapRoomSize = 0; - physicalEntrancePos = null; - rooms.clear(); - currentRoom = null; - } -} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/Room.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/Room.java deleted file mode 100644 index ae5afaa3..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/Room.java +++ /dev/null @@ -1,473 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon.secrets; - -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.ImmutableTable; -import com.google.common.collect.Table; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import it.unimi.dsi.fastutil.ints.IntRBTreeSet; -import it.unimi.dsi.fastutil.ints.IntSortedSet; -import it.unimi.dsi.fastutil.ints.IntSortedSets; -import me.xmrvizzy.skyblocker.utils.scheduler.Scheduler; -import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; -import net.fabricmc.fabric.api.util.TriState; -import net.minecraft.block.BlockState; -import net.minecraft.block.Blocks; -import net.minecraft.block.MapColor; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayerEntity; -import net.minecraft.client.world.ClientWorld; -import net.minecraft.entity.ItemEntity; -import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.mob.AmbientEntity; -import net.minecraft.registry.Registries; -import net.minecraft.util.hit.BlockHitResult; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.MathHelper; -import net.minecraft.world.World; -import org.apache.commons.lang3.tuple.MutableTriple; -import org.apache.commons.lang3.tuple.Triple; -import org.jetbrains.annotations.NotNull; -import org.joml.Vector2i; -import org.joml.Vector2ic; - -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class Room { - private static final Pattern SECRETS = Pattern.compile("§7(\\d{1,2})/(\\d{1,2}) Secrets"); - @NotNull - private final Type type; - @NotNull - private final Set<Vector2ic> segments; - /** - * The shape of the room. See {@link #getShape(IntSortedSet, IntSortedSet)}. - */ - @NotNull - private final Shape shape; - /** - * The room data containing all rooms for a specific dungeon and {@link #shape}. - */ - private Map<String, int[]> roomsData; - /** - * Contains all possible dungeon rooms for this room. The list is gradually shrunk by checking blocks until only one room is left. - */ - private List<MutableTriple<Direction, Vector2ic, List<String>>> possibleRooms; - /** - * Contains all blocks that have been checked to prevent checking the same block multiple times. - */ - private Set<BlockPos> checkedBlocks = new HashSet<>(); - /** - * The task that is used to check blocks. This is used to ensure only one such task can run at a time. - */ - private CompletableFuture<Void> findRoom; - private int doubleCheckBlocks; - /** - * Represents the matching state of the room with the following possible values: - * <li>{@link TriState#DEFAULT} means that the room has not been checked, is being processed, or does not {@link Type#needsScanning() need to be processed}. - * <li>{@link TriState#FALSE} means that the room has been checked and there is no match. - * <li>{@link TriState#TRUE} means that the room has been checked and there is a match. - */ - private TriState matched = TriState.DEFAULT; - private Table<Integer, BlockPos, SecretWaypoint> secretWaypoints; - - public Room(@NotNull Type type, @NotNull Vector2ic... physicalPositions) { - this.type = type; - segments = Set.of(physicalPositions); - IntSortedSet segmentsX = IntSortedSets.unmodifiable(new IntRBTreeSet(segments.stream().mapToInt(Vector2ic::x).toArray())); - IntSortedSet segmentsY = IntSortedSets.unmodifiable(new IntRBTreeSet(segments.stream().mapToInt(Vector2ic::y).toArray())); - shape = getShape(segmentsX, segmentsY); - roomsData = DungeonSecrets.ROOMS_DATA.getOrDefault("catacombs", Collections.emptyMap()).getOrDefault(shape.shape.toLowerCase(), Collections.emptyMap()); - possibleRooms = getPossibleRooms(segmentsX, segmentsY); - } - - @NotNull - public Type getType() { - return type; - } - - public boolean isMatched() { - return matched == TriState.TRUE; - } - - @Override - public String toString() { - return "Room{type=" + type + ", shape=" + shape + ", matched=" + matched + ", segments=" + Arrays.toString(segments.toArray()) + "}"; - } - - @NotNull - private Shape getShape(IntSortedSet segmentsX, IntSortedSet segmentsY) { - return switch (segments.size()) { - case 1 -> Shape.ONE_BY_ONE; - case 2 -> Shape.ONE_BY_TWO; - case 3 -> segmentsX.size() == 2 && segmentsY.size() == 2 ? Shape.L_SHAPE : Shape.ONE_BY_THREE; - case 4 -> segmentsX.size() == 2 && segmentsY.size() == 2 ? Shape.TWO_BY_TWO : Shape.ONE_BY_FOUR; - default -> throw new IllegalArgumentException("There are no matching room shapes with this set of physical positions: " + Arrays.toString(segments.toArray())); - }; - } - - private List<MutableTriple<Direction, Vector2ic, List<String>>> getPossibleRooms(IntSortedSet segmentsX, IntSortedSet segmentsY) { - List<String> possibleDirectionRooms = new ArrayList<>(roomsData.keySet()); - List<MutableTriple<Direction, Vector2ic, List<String>>> possibleRooms = new ArrayList<>(); - for (Direction direction : getPossibleDirections(segmentsX, segmentsY)) { - possibleRooms.add(MutableTriple.of(direction, DungeonMapUtils.getPhysicalCornerPos(direction, segmentsX, segmentsY), possibleDirectionRooms)); - } - return possibleRooms; - } - - @NotNull - private Direction[] getPossibleDirections(IntSortedSet segmentsX, IntSortedSet segmentsY) { - return switch (shape) { - case ONE_BY_ONE, TWO_BY_TWO -> Direction.values(); - case ONE_BY_TWO, ONE_BY_THREE, ONE_BY_FOUR -> { - if (segmentsX.size() > 1 && segmentsY.size() == 1) { - yield new Direction[]{Direction.NW, Direction.SE}; - } else if (segmentsX.size() == 1 && segmentsY.size() > 1) { - yield new Direction[]{Direction.NE, Direction.SW}; - } - throw new IllegalArgumentException("Shape " + shape.shape + " does not match segments: " + Arrays.toString(segments.toArray())); - } - case L_SHAPE -> { - if (!segments.contains(new Vector2i(segmentsX.firstInt(), segmentsY.firstInt()))) { - yield new Direction[]{Direction.SW}; - } else if (!segments.contains(new Vector2i(segmentsX.firstInt(), segmentsY.lastInt()))) { - yield new Direction[]{Direction.SE}; - } else if (!segments.contains(new Vector2i(segmentsX.lastInt(), segmentsY.firstInt()))) { - yield new Direction[]{Direction.NW}; - } else if (!segments.contains(new Vector2i(segmentsX.lastInt(), segmentsY.lastInt()))) { - yield new Direction[]{Direction.NE}; - } - throw new IllegalArgumentException("Shape " + shape.shape + " does not match segments: " + Arrays.toString(segments.toArray())); - } - }; - } - - /** - * Updates the room. - * <p></p> - * This method returns immediately if any of the following conditions are met: - * <ul> - * <li> The room does not need to be scanned and matched. (When the room is not of type {@link Type.ROOM}, {@link Type.PUZZLE}, or {@link Type.TRAP}. See {@link Type#needsScanning()}) </li> - * <li> The room has been matched or failed to match and is on cooldown. See {@link #matched}. </li> - * <li> {@link #findRoom The previous update} has not completed. </li> - * </ul> - * Then this method tries to match this room through: - * <ul> - * <li> Iterate over a 11 by 11 by 11 box around the player. </li> - * <li> Check it the block is part of this room and not part of a doorway. See {@link #segments} and {@link #notInDoorway(BlockPos)}. </li> - * <li> Checks if the position has been checked and adds it to {@link #checkedBlocks}. </li> - * <li> Calls {@link #checkBlock(ClientWorld, BlockPos)} </li> - * </ul> - */ - @SuppressWarnings("JavadocReference") - protected void update() { - // Logical AND has higher precedence than logical OR - if (!type.needsScanning() || matched != TriState.DEFAULT || !DungeonSecrets.isRoomsLoaded() || findRoom != null && !findRoom.isDone()) { - return; - } - MinecraftClient client = MinecraftClient.getInstance(); - ClientPlayerEntity player = client.player; - ClientWorld world = client.world; - if (player == null || world == null) { - return; - } - findRoom = CompletableFuture.runAsync(() -> { - for (BlockPos pos : BlockPos.iterate(player.getBlockPos().add(-5, -5, -5), player.getBlockPos().add(5, 5, 5))) { - if (segments.contains(DungeonMapUtils.getPhysicalRoomPos(pos)) && notInDoorway(pos) && checkedBlocks.add(pos) && checkBlock(world, pos)) { - break; - } - } - }); - } - - private static boolean notInDoorway(BlockPos pos) { - if (pos.getY() < 66 || pos.getY() > 73) { - return true; - } - int x = MathHelper.floorMod(pos.getX() - 8, 32); - int z = MathHelper.floorMod(pos.getZ() - 8, 32); - return (x < 13 || x > 17 || z > 2 && z < 28) && (z < 13 || z > 17 || x > 2 && x < 28); - } - - /** - * Filters out dungeon rooms which does not contain the block at the given position. - * <p></p> - * This method: - * <ul> - * <li> Checks if the block type is included in the dungeon rooms data. See {@link DungeonSecrets#NUMERIC_ID}. </li> - * <li> For each possible direction: </li> - * <ul> - * <li> Rotate and convert the position to a relative position. See {@link DungeonMapUtils#actualToRelative(Direction, Vector2ic, BlockPos)}. </li> - * <li> Encode the block based on the relative position and the custom numeric block id. See {@link #posIdToInt(BlockPos, byte)}. </li> - * <li> For each possible room in the current direction: </li> - * <ul> - * <li> Check if {@link #roomsData} contains the encoded block. </li> - * <li> If so, add the room to the new list of possible rooms for this direction. </li> - * </ul> - * <li> Replace the old possible room list for the current direction with the new one. </li> - * </ul> - * <li> If there are no matching rooms left: </li> - * <ul> - * <li> Terminate matching by setting {@link #matched} to {@link TriState#FALSE}. </li> - * <li> Schedule another matching attempt in 50 ticks (2.5 seconds). </li> - * <li> Reset {@link #possibleRooms} and {@link #checkedBlocks} with {@link #reset()}. </li> - * <li> Return {@code true} </li> - * </ul> - * <li> If there are exactly one room matching: </li> - * <ul> - * <li> Call {@link #roomMatched(String, Direction, Vector2ic)}. </li> - * <li> Discard the no longer needed fields to save memory. </li> - * <li> Return {@code true} </li> - * </ul> - * <li> Return {@code false} </li> - * </ul> - * - * @param world the world to get the block from - * @param pos the position of the block to check - * @return whether room matching should end. Either a match is found or there are no valid rooms left - */ - private boolean checkBlock(ClientWorld world, BlockPos pos) { - byte id = DungeonSecrets.NUMERIC_ID.getByte(Registries.BLOCK.getId(world.getBlockState(pos).getBlock()).toString()); - if (id == 0) { - return false; - } - for (MutableTriple<Direction, Vector2ic, List<String>> directionRooms : possibleRooms) { - int block = posIdToInt(DungeonMapUtils.actualToRelative(directionRooms.getLeft(), directionRooms.getMiddle(), pos), id); - List<String> possibleDirectionRooms = new ArrayList<>(); - for (String room : directionRooms.getRight()) { - if (Arrays.binarySearch(roomsData.get(room), block) >= 0) { - possibleDirectionRooms.add(room); - } - } - directionRooms.setRight(possibleDirectionRooms); - } - - int matchingRoomsSize = possibleRooms.stream().map(Triple::getRight).mapToInt(Collection::size).sum(); - if (matchingRoomsSize == 0) { - // If no rooms match, reset the fields and scan again after 50 ticks. - matched = TriState.FALSE; - DungeonSecrets.LOGGER.warn("[Skyblocker] No dungeon room matches after checking {} block(s)", checkedBlocks.size()); - Scheduler.INSTANCE.schedule(() -> matched = TriState.DEFAULT, 50); - reset(); - return true; - } else if (matchingRoomsSize == 1 && ++doubleCheckBlocks >= 10) { - // If one room matches, load the secrets for that room and discard the no longer needed fields. - for (Triple<Direction, Vector2ic, List<String>> directionRooms : possibleRooms) { - if (directionRooms.getRight().size() == 1) { - roomMatched(directionRooms.getRight().get(0), directionRooms.getLeft(), directionRooms.getMiddle()); - discard(); - return true; - } - } - return false; // This should never happen, we just checked that there is one possible room, and the return true in the loop should activate - } else { - DungeonSecrets.LOGGER.debug("[Skyblocker] {} room(s) remaining after checking {} block(s)", matchingRoomsSize, checkedBlocks.size()); - return false; - } - } - - /** - * Encodes a {@link BlockPos} and the custom numeric block id into an integer. - * - * @param pos the position of the block - * @param id the custom numeric block id - * @return the encoded integer - */ - private int posIdToInt(BlockPos pos, byte id) { - return pos.getX() << 24 | pos.getY() << 16 | pos.getZ() << 8 | id; - } - - /** - * Loads the secret waypoints for the room from {@link DungeonSecrets#waypointsJson} once it has been matched - * and sets {@link #matched} to {@link TriState#TRUE}. - * - * @param directionRooms the direction, position, and name of the room - */ - @SuppressWarnings("JavadocReference") - private void roomMatched(String name, Direction direction, Vector2ic physicalCornerPos) { - Table<Integer, BlockPos, SecretWaypoint> secretWaypointsMutable = HashBasedTable.create(); - for (JsonElement waypointElement : DungeonSecrets.getRoomWaypoints(name)) { - JsonObject waypoint = waypointElement.getAsJsonObject(); - String secretName = waypoint.get("secretName").getAsString(); - int secretIndex = Integer.parseInt(secretName.substring(0, Character.isDigit(secretName.charAt(1)) ? 2 : 1)); - BlockPos pos = DungeonMapUtils.relativeToActual(direction, physicalCornerPos, waypoint); - secretWaypointsMutable.put(secretIndex, pos, new SecretWaypoint(secretIndex, waypoint, secretName, pos)); - } - secretWaypoints = ImmutableTable.copyOf(secretWaypointsMutable); - matched = TriState.TRUE; - DungeonSecrets.LOGGER.info("[Skyblocker] Room {} matched after checking {} block(s)", name, checkedBlocks.size()); - } - - /** - * Resets fields for another round of matching after room matching fails. - */ - private void reset() { - IntSortedSet segmentsX = IntSortedSets.unmodifiable(new IntRBTreeSet(segments.stream().mapToInt(Vector2ic::x).toArray())); - IntSortedSet segmentsY = IntSortedSets.unmodifiable(new IntRBTreeSet(segments.stream().mapToInt(Vector2ic::y).toArray())); - possibleRooms = getPossibleRooms(segmentsX, segmentsY); - checkedBlocks = new HashSet<>(); - doubleCheckBlocks = 0; - } - - /** - * Discards fields after room matching completes when a room is found. - * These fields are no longer needed and are discarded to save memory. - */ - private void discard() { - roomsData = null; - possibleRooms = null; - checkedBlocks = null; - doubleCheckBlocks = 0; - } - - /** - * Calls {@link SecretWaypoint#render(WorldRenderContext)} on {@link #secretWaypoints all secret waypoints}. - */ - protected void render(WorldRenderContext context) { - for (SecretWaypoint secretWaypoint : secretWaypoints.values()) { - if (secretWaypoint.shouldRender()) { - secretWaypoint.render(context); - } - } - } - - /** - * Sets all secrets as found if {@link #isAllSecretsFound(String)}. - */ - protected void onChatMessage(String message) { - if (isAllSecretsFound(message)) { - secretWaypoints.values().forEach(SecretWaypoint::setFound); - } - } - - /** - * Checks if the number of found secrets is equals or greater than the total number of secrets in the room. - * - * @param message the message to check in - * @return whether the number of found secrets is equals or greater than the total number of secrets in the room - */ - protected static boolean isAllSecretsFound(String message) { - Matcher matcher = SECRETS.matcher(message); - if (matcher.find()) { - return Integer.parseInt(matcher.group(1)) >= Integer.parseInt(matcher.group(2)); - } - return false; - } - - /** - * Marks the secret at the interaction position as found when the player interacts with a chest or a player head, - * if there is a secret at the interaction position. - * - * @param world the world to get the block from - * @param hitResult the block being interacted with - * @see #onSecretFound(SecretWaypoint, String, Object...) - */ - protected void onUseBlock(World world, BlockHitResult hitResult) { - BlockState state = world.getBlockState(hitResult.getBlockPos()); - if (state.isOf(Blocks.CHEST) || state.isOf(Blocks.PLAYER_HEAD) || state.isOf(Blocks.PLAYER_WALL_HEAD)) { - secretWaypoints.column(hitResult.getBlockPos()).values().stream().filter(SecretWaypoint::needsInteraction).findAny() - .ifPresent(secretWaypoint -> onSecretFound(secretWaypoint, "[Skyblocker] Detected {} interaction, setting secret #{} as found", secretWaypoint.category, secretWaypoint.secretIndex)); - } else if (state.isOf(Blocks.LEVER)) { - secretWaypoints.column(hitResult.getBlockPos()).values().stream().filter(SecretWaypoint::isLever).forEach(SecretWaypoint::setFound); - } - } - - /** - * Marks the closest secret that requires item pickup no greater than 6 blocks away as found when the player picks up a secret item. - * - * @param itemEntity the item entity being picked up - * @param collector the collector of the item - * @see #onSecretFound(SecretWaypoint, String, Object...) - */ - protected void onItemPickup(ItemEntity itemEntity, LivingEntity collector) { - if (SecretWaypoint.SECRET_ITEMS.stream().noneMatch(itemEntity.getStack().getName().getString()::contains)) { - return; - } - secretWaypoints.values().stream().filter(SecretWaypoint::needsItemPickup).min(Comparator.comparingDouble(SecretWaypoint.getSquaredDistanceToFunction(collector))).filter(SecretWaypoint.getRangePredicate(collector)) - .ifPresent(secretWaypoint -> onSecretFound(secretWaypoint, "[Skyblocker] Detected {} picked up a {} from a {} secret, setting secret #{} as found", collector.getName().getString(), itemEntity.getName().getString(), secretWaypoint.category, secretWaypoint.secretIndex)); - } - - /** - * Marks the closest bat secret as found when a bat is killed. - * - * @param bat the bat being killed - * @see #onSecretFound(SecretWaypoint, String, Object...) - */ - protected void onBatRemoved(AmbientEntity bat) { - secretWaypoints.values().stream().filter(SecretWaypoint::isBat).min(Comparator.comparingDouble(SecretWaypoint.getSquaredDistanceToFunction(bat))) - .ifPresent(secretWaypoint -> onSecretFound(secretWaypoint, "[Skyblocker] Detected {} killed for a {} secret, setting secret #{} as found", bat.getName().getString(), secretWaypoint.category, secretWaypoint.secretIndex)); - } - - /** - * Marks all secret waypoints with the same index as the given {@link SecretWaypoint} as found. - * - * @param secretWaypoint the secret waypoint to read the index from. - * @param msg the message to log - * @param args the args for the {@link org.slf4j.Logger#info(String, Object...) Logger#info(String, Object...)} call - */ - private void onSecretFound(SecretWaypoint secretWaypoint, String msg, Object... args) { - secretWaypoints.row(secretWaypoint.secretIndex).values().forEach(SecretWaypoint::setFound); - DungeonSecrets.LOGGER.info(msg, args); - } - - protected boolean markSecrets(int secretIndex, boolean found) { - Map<BlockPos, SecretWaypoint> secret = secretWaypoints.row(secretIndex); - if (secret.isEmpty()) { - return false; - } else { - secret.values().forEach(found ? SecretWaypoint::setFound : SecretWaypoint::setMissing); - return true; - } - } - - public enum Type { - ENTRANCE(MapColor.DARK_GREEN.getRenderColorByte(MapColor.Brightness.HIGH)), - ROOM(MapColor.ORANGE.getRenderColorByte(MapColor.Brightness.LOWEST)), - PUZZLE(MapColor.MAGENTA.getRenderColorByte(MapColor.Brightness.HIGH)), - TRAP(MapColor.ORANGE.getRenderColorByte(MapColor.Brightness.HIGH)), - MINIBOSS(MapColor.YELLOW.getRenderColorByte(MapColor.Brightness.HIGH)), - FAIRY(MapColor.PINK.getRenderColorByte(MapColor.Brightness.HIGH)), - BLOOD(MapColor.BRIGHT_RED.getRenderColorByte(MapColor.Brightness.HIGH)), - UNKNOWN(MapColor.GRAY.getRenderColorByte(MapColor.Brightness.NORMAL)); - final byte color; - - Type(byte color) { - this.color = color; - } - - /** - * @return whether this room type has secrets and needs to be scanned and matched. - */ - private boolean needsScanning() { - return switch (this) { - case ROOM, PUZZLE, TRAP -> true; - default -> false; - }; - } - } - - private enum Shape { - ONE_BY_ONE("1x1"), - ONE_BY_TWO("1x2"), - ONE_BY_THREE("1x3"), - ONE_BY_FOUR("1x4"), - L_SHAPE("L-shape"), - TWO_BY_TWO("2x2"); - final String shape; - - Shape(String shape) { - this.shape = shape; - } - - @Override - public String toString() { - return shape; - } - } - - public enum Direction { - NW, NE, SW, SE - } -} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java deleted file mode 100644 index 73ac1883..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java +++ /dev/null @@ -1,142 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon.secrets; - -import com.google.gson.JsonObject; - -import me.xmrvizzy.skyblocker.config.SkyblockerConfig; -import me.xmrvizzy.skyblocker.config.SkyblockerConfigManager; -import me.xmrvizzy.skyblocker.utils.render.RenderHelper; -import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; -import net.minecraft.client.MinecraftClient; -import net.minecraft.entity.Entity; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Vec3d; - -import java.util.List; -import java.util.function.Predicate; -import java.util.function.ToDoubleFunction; - -public class SecretWaypoint { - static final List<String> SECRET_ITEMS = List.of("Decoy", "Defuse Kit", "Dungeon Chest Key", "Healing VIII", "Inflatable Jerry", "Spirit Leap", "Training Weights", "Trap", "Treasure Talisman"); - final int secretIndex; - final Category category; - private final Text name; - private final BlockPos pos; - private final Vec3d centerPos; - private boolean missing; - - SecretWaypoint(int secretIndex, JsonObject waypoint, String name, BlockPos pos) { - this.secretIndex = secretIndex; - this.category = Category.get(waypoint); - this.name = Text.of(name); - this.pos = pos; - this.centerPos = pos.toCenterPos(); - this.missing = true; - } - - static ToDoubleFunction<SecretWaypoint> getSquaredDistanceToFunction(Entity entity) { - return secretWaypoint -> entity.squaredDistanceTo(secretWaypoint.centerPos); - } - - static Predicate<SecretWaypoint> getRangePredicate(Entity entity) { - return secretWaypoint -> entity.squaredDistanceTo(secretWaypoint.centerPos) <= 36D; - } - - boolean shouldRender() { - return category.isEnabled() && missing; - } - - boolean needsInteraction() { - return category.needsInteraction(); - } - - boolean isLever() { - return category.isLever(); - } - - boolean needsItemPickup() { - return category.needsItemPickup(); - } - - boolean isBat() { - return category.isBat(); - } - - void setFound() { - this.missing = false; - } - - void setMissing() { - this.missing = true; - } - - /** - * Renders the secret waypoint, including a filled cube, a beacon beam, the name, and the distance from the player. - */ - void render(WorldRenderContext context) { - RenderHelper.renderFilledThroughWallsWithBeaconBeam(context, pos, category.colorComponents, 0.5F); - Vec3d posUp = centerPos.add(0, 1, 0); - RenderHelper.renderText(context, name, posUp, true); - double distance = context.camera().getPos().distanceTo(centerPos); - RenderHelper.renderText(context, Text.literal(Math.round(distance) + "m").formatted(Formatting.YELLOW), posUp, 1, MinecraftClient.getInstance().textRenderer.fontHeight + 1, true); - } - - enum Category { - ENTRANCE(secretWaypoints -> secretWaypoints.enableEntranceWaypoints, 0, 255, 0), - SUPERBOOM(secretWaypoints -> secretWaypoints.enableSuperboomWaypoints, 255, 0, 0), - CHEST(secretWaypoints -> secretWaypoints.enableChestWaypoints, 2, 213, 250), - ITEM(secretWaypoints -> secretWaypoints.enableItemWaypoints, 2, 64, 250), - BAT(secretWaypoints -> secretWaypoints.enableBatWaypoints, 142, 66, 0), - WITHER(secretWaypoints -> secretWaypoints.enableWitherWaypoints, 30, 30, 30), - LEVER(secretWaypoints -> secretWaypoints.enableLeverWaypoints, 250, 217, 2), - FAIRYSOUL(secretWaypoints -> secretWaypoints.enableFairySoulWaypoints, 255, 85, 255), - STONK(secretWaypoints -> secretWaypoints.enableStonkWaypoints, 146, 52, 235), - DEFAULT(secretWaypoints -> secretWaypoints.enableDefaultWaypoints, 190, 255, 252); - private final Predicate<SkyblockerConfig.SecretWaypoints> enabledPredicate; - private final float[] colorComponents; - - Category(Predicate<SkyblockerConfig.SecretWaypoints> enabledPredicate, int... intColorComponents) { - this.enabledPredicate = enabledPredicate; - colorComponents = new float[intColorComponents.length]; - for (int i = 0; i < intColorComponents.length; i++) { - colorComponents[i] = intColorComponents[i] / 255F; - } - } - - private static Category get(JsonObject categoryJson) { - return switch (categoryJson.get("category").getAsString()) { - case "entrance" -> Category.ENTRANCE; - case "superboom" -> Category.SUPERBOOM; - case "chest" -> Category.CHEST; - case "item" -> Category.ITEM; - case "bat" -> Category.BAT; - case "wither" -> Category.WITHER; - case "lever" -> Category.LEVER; - case "fairysoul" -> Category.FAIRYSOUL; - case "stonk" -> Category.STONK; - default -> Category.DEFAULT; - }; - } - - boolean needsInteraction() { - return this == CHEST || this == WITHER; - } - - boolean isLever() { - return this == LEVER; - } - - boolean needsItemPickup() { - return this == ITEM; - } - - boolean isBat() { - return this == BAT; - } - - boolean isEnabled() { - return enabledPredicate.test(SkyblockerConfigManager.get().locations.dungeons.secretWaypoints); - } - } -} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/terminal/ColorTerminal.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/terminal/ColorTerminal.java deleted file mode 100644 index 787d696e..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/terminal/ColorTerminal.java +++ /dev/null @@ -1,72 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon.terminal; - -import me.xmrvizzy.skyblocker.config.SkyblockerConfigManager; -import me.xmrvizzy.skyblocker.utils.render.gui.ColorHighlight; -import me.xmrvizzy.skyblocker.utils.render.gui.ContainerSolver; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; -import net.minecraft.registry.Registries; -import net.minecraft.util.DyeColor; -import net.minecraft.util.Identifier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; - - -public class ColorTerminal extends ContainerSolver { - private static final Logger LOGGER = LoggerFactory.getLogger(ColorTerminal.class.getName()); - private static final Map<String, DyeColor> colorFromName; - private DyeColor targetColor; - private static final Map<Item, DyeColor> itemColor; - - public ColorTerminal() { - super("^Select all the ([A-Z ]+) items!$"); - } - - @Override - protected boolean isEnabled() { - targetColor = null; - return SkyblockerConfigManager.get().locations.dungeons.terminals.solveColor; - } - - @Override - protected List<ColorHighlight> getColors(String[] groups, Map<Integer, ItemStack> slots) { - trimEdges(slots, 6); - List<ColorHighlight> highlights = new ArrayList<>(); - String colorString = groups[0]; - if (targetColor == null) { - targetColor = colorFromName.get(colorString); - if (targetColor == null) { - LOGGER.error("[Skyblocker] Couldn't find dye color corresponding to \"" + colorString + "\""); - return Collections.emptyList(); - } - } - for (Map.Entry<Integer, ItemStack> slot : slots.entrySet()) { - ItemStack itemStack = slot.getValue(); - if (!itemStack.hasEnchantments() && targetColor.equals(itemColor.get(itemStack.getItem()))) { - highlights.add(ColorHighlight.green(slot.getKey())); - } - } - return highlights; - } - - - static { - colorFromName = new HashMap<>(); - for (DyeColor color : DyeColor.values()) - colorFromName.put(color.getName().toUpperCase(Locale.ENGLISH), color); - colorFromName.put("SILVER", DyeColor.LIGHT_GRAY); - colorFromName.put("LIGHT BLUE", DyeColor.LIGHT_BLUE); - - itemColor = new HashMap<>(); - for (DyeColor color : DyeColor.values()) - for (String item : new String[]{"dye", "wool", "stained_glass", "terracotta"}) - itemColor.put(Registries.ITEM.get(new Identifier(color.getName() + '_' + item)), color); - itemColor.put(Items.BONE_MEAL, DyeColor.WHITE); - itemColor.put(Items.LAPIS_LAZULI, DyeColor.BLUE); - itemColor.put(Items.COCOA_BEANS, DyeColor.BROWN); - itemColor.put(Items.INK_SAC, DyeColor.BLACK); - } -}
\ No newline at end of file diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/terminal/OrderTerminal.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/terminal/OrderTerminal.java deleted file mode 100644 index afb07b4b..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/terminal/OrderTerminal.java +++ /dev/null @@ -1,58 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon.terminal; - -import me.xmrvizzy.skyblocker.config.SkyblockerConfigManager; -import me.xmrvizzy.skyblocker.utils.render.gui.ColorHighlight; -import me.xmrvizzy.skyblocker.utils.render.gui.ContainerSolver; -import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -public class OrderTerminal extends ContainerSolver { - private final int PANES_NUM = 14; - private int[] orderedSlots; - private int currentNum = Integer.MAX_VALUE; - - public OrderTerminal() { - super("^Click in order!$"); - } - - @Override - protected boolean isEnabled() { - orderedSlots = null; - currentNum = 0; - return SkyblockerConfigManager.get().locations.dungeons.terminals.solveOrder; - } - - @Override - protected List<ColorHighlight> getColors(String[] groups, Map<Integer, ItemStack> slots) { - if(orderedSlots == null && !orderSlots(slots)) - return Collections.emptyList(); - while(currentNum < PANES_NUM && Items.LIME_STAINED_GLASS_PANE.equals(slots.get(orderedSlots[currentNum]).getItem())) - currentNum++; - List<ColorHighlight> highlights = new ArrayList<>(3); - int last = Integer.min(3, PANES_NUM - currentNum); - for(int i = 0; i < last; i++) { - highlights.add(new ColorHighlight(orderedSlots[currentNum + i], (224 - 64 * i) << 24 | 64 << 16 | 96 << 8 | 255)); - } - return highlights; - } - - public boolean orderSlots(Map<Integer, ItemStack> slots) { - trimEdges(slots, 4); - orderedSlots = new int[PANES_NUM]; - for(Map.Entry<Integer, ItemStack> slot : slots.entrySet()) { - if(Items.AIR.equals(slot.getValue().getItem())) { - orderedSlots = null; - return false; - } - else - orderedSlots[slot.getValue().getCount() - 1] = slot.getKey(); - } - currentNum = 0; - return true; - } -}
\ No newline at end of file diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/terminal/StartsWithTerminal.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/terminal/StartsWithTerminal.java deleted file mode 100644 index fa9fa324..00000000 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/terminal/StartsWithTerminal.java +++ /dev/null @@ -1,35 +0,0 @@ -package me.xmrvizzy.skyblocker.skyblock.dungeon.terminal; - -import me.xmrvizzy.skyblocker.config.SkyblockerConfigManager; -import me.xmrvizzy.skyblocker.utils.render.gui.ColorHighlight; -import me.xmrvizzy.skyblocker.utils.render.gui.ContainerSolver; -import net.minecraft.item.ItemStack; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class StartsWithTerminal extends ContainerSolver { - public StartsWithTerminal() { - super("^What starts with: '([A-Z])'\\?$"); - } - - @Override - protected boolean isEnabled() { - return SkyblockerConfigManager.get().locations.dungeons.terminals.solveStartsWith; - } - - @Override - protected List<ColorHighlight> getColors(String[] groups, Map<Integer, ItemStack> slots) { - trimEdges(slots, 6); - String prefix = groups[0]; - List<ColorHighlight> highlights = new ArrayList<>(); - for (Map.Entry<Integer, ItemStack> slot : slots.entrySet()) { - ItemStack stack = slot.getValue(); - if (!stack.hasEnchantments() && stack.getName().getString().startsWith(prefix)) { - highlights.add(ColorHighlight.green(slot.getKey())); - } - } - return highlights; - } -}
\ No newline at end of file |