diff options
| author | Kevinthegreat <92656833+kevinthegreat1@users.noreply.github.com> | 2023-08-31 20:21:30 -0400 |
|---|---|---|
| committer | Kevinthegreat <92656833+kevinthegreat1@users.noreply.github.com> | 2023-08-31 20:40:44 -0400 |
| commit | aa2abcf644bf58adb3330e87d870ce56a09ce3f5 (patch) | |
| tree | f9e9114f9b0031ce61dcfba05a030453f540d6b1 /src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon | |
| parent | fea919794608128b6c436f1a64d1be38e935241d (diff) | |
| parent | 1370097922f19815e14bdfd8c9e606cde2bc8f39 (diff) | |
| download | Skyblocker-aa2abcf644bf58adb3330e87d870ce56a09ce3f5.tar.gz Skyblocker-aa2abcf644bf58adb3330e87d870ce56a09ce3f5.tar.bz2 Skyblocker-aa2abcf644bf58adb3330e87d870ce56a09ce3f5.zip | |
Merge branch 'master' into utils-cleanup
# Conflicts:
# src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java
# src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonBlaze.java
# src/main/java/me/xmrvizzy/skyblocker/skyblock/dwarven/DwarvenHud.java
# src/main/java/me/xmrvizzy/skyblocker/skyblock/dwarven/DwarvenHudConfigScreen.java
# src/main/java/me/xmrvizzy/skyblocker/utils/RenderHelper.java
Diffstat (limited to 'src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon')
8 files changed, 1575 insertions, 48 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 index 0e56ece0..1e949cd1 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/CroesusHelper.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/CroesusHelper.java @@ -25,7 +25,7 @@ public class CroesusHelper extends ContainerSolver { 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!")) { + if (stack != null && stack.getNbt() != null && stack.getNbt().toString().contains("Opened Chest:")) { highlights.add(ColorHighlight.gray(entry.getKey())); } } diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonBlaze.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonBlaze.java index ef94d9ec..09bef802 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonBlaze.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonBlaze.java @@ -8,8 +8,10 @@ import me.xmrvizzy.skyblocker.utils.render.RenderHelper; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.world.ClientWorld; -import net.minecraft.entity.Entity; +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; @@ -19,89 +21,132 @@ 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 }; - static Entity highestBlaze = null; - static Entity lowestBlaze = null; - static Entity nextHighestBlaze = null; - static Entity nextLowestBlaze = null; + 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() { SkyblockerMod.getInstance().scheduler.scheduleCyclic(DungeonBlaze::update, 10); WorldRenderEvents.BEFORE_DEBUG_RENDER.register(DungeonBlaze::blazeRenderer); } + /** + * Updates the state of Blaze entities and triggers the rendering process if necessary. + */ public static void update() { - if (!SkyblockerConfig.get().locations.dungeons.blazesolver) return; ClientWorld world = MinecraftClient.getInstance().world; - if (world == null || !Utils.isInDungeons()) return; - Iterable<Entity> entities = world.getEntities(); - List<ObjectIntPair<Entity>> blazes = new ArrayList<>(); - - for (Entity entity : entities) { - String blazeName = entity.getName().getString(); + 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(entity, health)); + blazes.add(ObjectIntPair.of(blaze, health)); } catch (NumberFormatException e) { - LOGGER.error("[Skyblocker DungeonBlazeSolver] Failed to parse blaze health: " + blazeName, e); + handleException(e); } } } + return blazes; + } - // Order the blazes in the list from the lowest health to the highest health + /** + * 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)); + } - // Ensure that there are blazes in the list + /** + * 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 there's more than 1 blaze if (blazes.size() > 1) { - nextLowestBlaze = blazes.get(1).left(); - nextHighestBlaze = blazes.get(highestIndex - 1).left(); + nextLowestBlaze = blazes.get(1).left(); + nextHighestBlaze = blazes.get(highestIndex - 1).left(); } } - } - public static void blazeRenderer(WorldRenderContext context) { + /** + * 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 (SkyblockerConfig.get().locations.dungeons.blazesolver && highestBlaze != null && lowestBlaze != null && highestBlaze.isAlive() && lowestBlaze.isAlive()) { - /* Outline */ + if (highestBlaze != null && lowestBlaze != null && highestBlaze.isAlive() && lowestBlaze.isAlive() && SkyblockerConfig.get().locations.dungeons.blazesolver) { if (highestBlaze.getY() < 69) { - Box blaze = highestBlaze.getBoundingBox().expand(0.3, 0.9, 0.3).offset(0, -1.1, 0); - RenderHelper.renderOutline(context, blaze, GREEN_COLOR_COMPONENTS, 5f); - - if (nextHighestBlaze != null && nextHighestBlaze.isAlive() && nextHighestBlaze != highestBlaze) { - Box nextBlaze = nextHighestBlaze.getBoundingBox().expand(0.3, 0.9, 0.3).offset(0, -1.1, 0); - RenderHelper.renderOutline(context, nextBlaze, WHITE_COLOR_COMPONENTS, 5f); - RenderHelper.renderLinesFromPoints(context, new Vec3d[] { blaze.getCenter(), nextBlaze.getCenter() }, WHITE_COLOR_COMPONENTS, 1f, 5f); - } + renderBlazeOutline(highestBlaze, nextHighestBlaze, wrc); } - - /* Outline */ if (lowestBlaze.getY() > 69) { - Box blaze = lowestBlaze.getBoundingBox().expand(0.3, 0.9, 0.3).offset(0, -1.1, 0); - RenderHelper.renderOutline(context, blaze, GREEN_COLOR_COMPONENTS, 5f); - - if (nextLowestBlaze != null && nextLowestBlaze.isAlive() && nextLowestBlaze != lowestBlaze) { - Box nextBlaze = nextLowestBlaze.getBoundingBox().expand(0.3, 0.9, 0.3).offset(0, -1.1, 0); - RenderHelper.renderOutline(context, nextBlaze, WHITE_COLOR_COMPONENTS, 5f); - RenderHelper.renderLinesFromPoints(context, new Vec3d[] { blaze.getCenter(), nextBlaze.getCenter() }, WHITE_COLOR_COMPONENTS, 1f, 5f); - } + renderBlazeOutline(lowestBlaze, nextLowestBlaze, wrc); } } } catch (Exception e) { - LOGGER.warn("[Skyblocker DungeonBlazeRenderer] Failed to render blaze boxes", 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); } } -}
\ No newline at end of file + + /** + * 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/StarredMobGlow.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/StarredMobGlow.java new file mode 100644 index 00000000..64a28712 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/StarredMobGlow.java @@ -0,0 +1,51 @@ +package me.xmrvizzy.skyblocker.skyblock.dungeon; + +import java.util.List; + +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.player.PlayerEntity; +import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.util.math.Box; + +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": return true; + case "Shadow Assassin": return true; + case "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; + } + } + + return false; + } + + public static int getGlowColor(Entity entity) { + if (entity instanceof PlayerEntity) { + switch (entity.getName().getString()) { + case "Lost Adventurer": return 0xfee15c; + case "Shadow Assassin": return 0x5b2cb2; + case "Diamond Guy": return 0x57c2f7; + } + } + + return 0xf57738; + } +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/TicTacToe.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/TicTacToe.java new file mode 100644 index 00000000..a97b6752 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/TicTacToe.java @@ -0,0 +1,136 @@ +package me.xmrvizzy.skyblocker.skyblock.dungeon; + +import me.xmrvizzy.skyblocker.config.SkyblockerConfig; +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 (SkyblockerConfig.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/secrets/DungeonMapUtils.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java new file mode 100644 index 00000000..b56b2827 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java @@ -0,0 +1,275 @@ +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.getType() == MapIcon.Type.FRAME) { + return new Vector2i((icon.getX() >> 1) + 64, (icon.getZ() >> 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 new file mode 100644 index 00000000..c916a5e4 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java @@ -0,0 +1,427 @@ +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.SkyblockerConfig; +import me.xmrvizzy.skyblocker.utils.Utils; +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.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 n |
