diff options
author | Kevinthegreat <92656833+kevinthegreat1@users.noreply.github.com> | 2023-07-22 11:56:58 +0800 |
---|---|---|
committer | Kevinthegreat <92656833+kevinthegreat1@users.noreply.github.com> | 2023-08-30 22:49:52 -0400 |
commit | d531919154c78783b8423299083bbc9a34a8b617 (patch) | |
tree | 91abf963b9a809c3b377a1788d06a2057db5a702 /src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets | |
parent | ad5dac4b45a86c11e11cace97b314c3cc38cbf23 (diff) | |
download | Skyblocker-d531919154c78783b8423299083bbc9a34a8b617.tar.gz Skyblocker-d531919154c78783b8423299083bbc9a34a8b617.tar.bz2 Skyblocker-d531919154c78783b8423299083bbc9a34a8b617.zip |
Add room type and segments detection
Diffstat (limited to 'src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets')
3 files changed, 127 insertions, 44 deletions
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 index 4ce9be06..75bcf86a 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java @@ -11,6 +11,8 @@ 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); @@ -34,16 +36,17 @@ public class DungeonMapUtils { // noinspection StatementWithEmptyBody, DataFlowIssue while (isEntranceColor(getColor(map, mapPos.sub(1, 0)))) { } + mapPos.add(1, 0); //noinspection StatementWithEmptyBody while (isEntranceColor(getColor(map, mapPos.sub(0, 1)))) { } - return mapPos; + return mapPos.add(0, 1); } - public static int getMapRoomWidth(MapState map, Vector2ic entrancePos) { - int i = 0; + public static int getMapRoomSize(MapState map, Vector2ic mapEntrancePos) { + int i = -1; //noinspection StatementWithEmptyBody - while (isEntranceColor(getColor(map, entrancePos.x() + i++, entrancePos.y()))) { + while (isEntranceColor(getColor(map, mapEntrancePos.x() + ++i, mapEntrancePos.y()))) { } return i; } @@ -51,9 +54,9 @@ public class DungeonMapUtils { /** * Gets the map position of the top left corner of the room the player is in. * - * @param map the map - * @param entrancePos the map position of the top left corner of the entrance - * @param mapRoomWidth the width of a room on the map + * @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) @@ -61,14 +64,14 @@ public class DungeonMapUtils { * Finally, {@code mapPos} is shifted back by {@code offset} to its intended position. */ @Nullable - public static Vector2ic getMapRoomPos(MapState map, Vector2ic entrancePos, int mapRoomWidth) { - int mapRoomWidthWithGap = mapRoomWidth + 4; + 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(entrancePos.x() % mapRoomWidthWithGap, entrancePos.y() % mapRoomWidthWithGap); - return mapPos.add(2, 2).sub(offset).sub(mapPos.x() % mapRoomWidthWithGap, mapPos.y() % mapRoomWidthWithGap).add(offset); + 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); } /** @@ -76,12 +79,12 @@ public class DungeonMapUtils { * * @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 mapRoomWidth the width of a room on the map + * @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 mapRoomWidth, Vector2ic physicalPos) { - return new Vector2i(physicalPos).sub(physicalEntrancePos).div(32).mul(mapRoomWidth + 4).add(mapEntrancePos); + 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); } @Nullable @@ -107,31 +110,84 @@ public class DungeonMapUtils { 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 mapRoomWidth the width of a room on the map + * @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 mapRoomWidth, Vector2ic physicalEntrancePos, Vector2ic mapPos) { - return new Vector2i(mapPos).sub(mapEntrancePos).div(mapRoomWidth + 4).mul(32).add(physicalEntrancePos); + 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 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); } - private static byte getColor(MapState map, @Nullable Vector2ic pos) { + public static byte getColor(MapState map, @Nullable Vector2ic pos) { return pos == null ? -1 : getColor(map, pos.x(), pos.y()); } - private static byte getColor(MapState map, int x, int z) { + 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)]; } - private static boolean isEntranceColor(byte color) { - return color == Room.RoomType.ENTRANCE.color; + public static boolean isEntranceColor(byte color) { + return color == Room.Type.ENTRANCE.color; } } 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 index c74898ff..ac92e834 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java @@ -33,6 +33,11 @@ import java.util.zip.InflaterInputStream; public class DungeonSecrets { protected static final Logger LOGGER = LoggerFactory.getLogger(DungeonSecrets.class); private static final String DUNGEONS_DATA_DIR = "/assets/skyblocker/dungeons"; + /** + * 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") private static final HashMap<String, HashMap<String, HashMap<String, int[]>>> ROOMS_DATA = new HashMap<>(); /** * Maps the block identifier string to a custom numeric block id used in dungeon rooms data. @@ -52,7 +57,7 @@ public class DungeonSecrets { /** * The width of a room on the map. */ - private static int mapRoomWidth; + private static int mapRoomSize; /** * The physical position of the northwest corner of the entrance room. */ @@ -80,7 +85,7 @@ public class DungeonSecrets { List<CompletableFuture<Void>> dungeonFutures = new ArrayList<>(); URL dungeonsURL = SkyblockerMod.class.getResource(DUNGEONS_DATA_DIR); if (dungeonsURL == null) { - LOGGER.error("Failed to load dungeon secrets, unable to find dungeon rooms data directory"); + LOGGER.error("[Skyblocker] Failed to load dungeon secrets, unable to find dungeon rooms data directory"); return; } Path dungeonsDir = Path.of(dungeonsURL.getPath()); @@ -88,7 +93,7 @@ public class DungeonSecrets { try { dungeonsDir = FileSystems.getFileSystem(dungeonsURL.toURI()).getPath(DUNGEONS_DATA_DIR); } catch (URISyntaxException e) { - LOGGER.error("Failed to load dungeon secrets, unable to open dungeon rooms data directory", e); + LOGGER.error("[Skyblocker] Failed to load dungeon secrets, unable to open dungeon rooms data directory", e); return; } } @@ -102,22 +107,22 @@ public class DungeonSecrets { roomShapeFutures.add(CompletableFuture.supplyAsync(() -> readRooms(roomShape, resourcePathIndex)).thenAccept(rooms -> roomShapesMap.put(roomShape.getFileName().toString(), rooms))); } ROOMS_DATA.put(dungeon.getFileName().toString(), roomShapesMap); - dungeonFutures.add(CompletableFuture.allOf(roomShapeFutures.toArray(CompletableFuture[]::new)).thenRun(() -> LOGGER.debug("Loaded dungeon secrets for dungeon {} with {} room shapes and {} rooms total", dungeon.getFileName(), roomShapesMap.size(), roomShapesMap.values().stream().mapToInt(HashMap::size).sum()))); + dungeonFutures.add(CompletableFuture.allOf(roomShapeFutures.toArray(CompletableFuture[]::new)).thenRun(() -> LOGGER.debug("[Skyblocker] Loaded dungeon secrets for dungeon {} with {} room shapes and {} rooms total", dungeon.getFileName(), roomShapesMap.size(), roomShapesMap.values().stream().mapToInt(HashMap::size).sum()))); } catch (IOException e) { - LOGGER.error("Failed to load dungeon secrets for dungeon " + dungeon.getFileName(), e); + LOGGER.error("[Skyblocker] Failed to load dungeon secrets for dungeon " + dungeon.getFileName(), e); } } } catch (IOException e) { - LOGGER.error("Failed to load dungeon secrets", e); + LOGGER.error("[Skyblocker] Failed to load dungeon secrets", e); } // Execute with MinecraftClient as executor since we need to wait for MinecraftClient#resourceManager to be set 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"))) { roomsJson = SkyblockerMod.GSON.fromJson(roomsReader, JsonObject.class); waypointsJson = SkyblockerMod.GSON.fromJson(waypointsReader, JsonObject.class); - LOGGER.debug("Loaded dungeon secrets json"); + LOGGER.debug("[Skyblocker] Loaded dungeon secrets json"); } catch (Exception e) { - LOGGER.error("Failed to load dungeon secrets json", e); + LOGGER.error("[Skyblocker] Failed to load dungeon secrets json", e); } }, MinecraftClient.getInstance())); roomsLoaded = CompletableFuture.allOf(dungeonFutures.toArray(CompletableFuture[]::new)).thenRun(() -> LOGGER.info("[Skyblocker] Loaded dungeon secrets for {} dungeon(s), {} room shapes, and {} rooms total", ROOMS_DATA.size(), ROOMS_DATA.values().stream().mapToInt(HashMap::size).sum(), ROOMS_DATA.values().stream().map(HashMap::values).flatMap(Collection::stream).mapToInt(HashMap::size).sum())); @@ -131,15 +136,15 @@ public class DungeonSecrets { //noinspection DataFlowIssue try (ObjectInputStream in = new ObjectInputStream(new InflaterInputStream(SkyblockerMod.class.getResourceAsStream(room.toString().substring(resourcePathIndex))))) { roomsData.put(name.substring(0, name.length() - 9), (int[]) in.readObject()); - LOGGER.debug("Loaded dungeon secrets room {}", name); + LOGGER.debug("[Skyblocker] Loaded dungeon secrets room {}", name); } catch (NullPointerException | IOException | ClassNotFoundException e) { - LOGGER.error("Failed to load dungeon secrets room " + name, e); + LOGGER.error("[Skyblocker] Failed to load dungeon secrets room " + name, e); } } - LOGGER.debug("Loaded dungeon secrets room shape {} with {} rooms", roomShape.getFileName(), roomsData.size()); + LOGGER.debug("[Skyblocker] Loaded dungeon secrets room shape {} with {} rooms", roomShape.getFileName(), roomsData.size()); return roomsData; } catch (IOException e) { - LOGGER.error("Failed to load dungeon secrets room shape " + roomShape.getFileName(), e); + LOGGER.error("[Skyblocker] Failed to load dungeon secrets room shape " + roomShape.getFileName(), e); } return null; } @@ -164,7 +169,7 @@ public class DungeonSecrets { if (mapEntrancePos == null && (mapEntrancePos = DungeonMapUtils.getMapEntrancePos(map)) == null) { return; } - if (mapRoomWidth == 0 && (mapRoomWidth = DungeonMapUtils.getMapRoomWidth(map, mapEntrancePos)) == 0) { + if (mapRoomSize == 0 && (mapRoomSize = DungeonMapUtils.getMapRoomSize(map, mapEntrancePos)) == 0) { return; } if (physicalEntrancePos == null) { @@ -173,11 +178,26 @@ public class DungeonSecrets { player.sendMessage(Text.translatable("skyblocker.dungeons.secrets.physicalEntranceNotFound")); return; } else { - currentRoom = newRoom(Room.RoomType.ENTRANCE, physicalEntrancePos); - LOGGER.info("[Skyblocker] Started dungeon with map room width {} and entrance at map pos {} and physical pos {}", mapRoomWidth, mapEntrancePos, physicalEntrancePos); + currentRoom = newRoom(Room.Type.ENTRANCE, physicalEntrancePos); + LOGGER.info("[Skyblocker] Started dungeon with map room width {}, map entrance pos {}, player pos {}, and physical entrance pos {}", mapRoomSize, mapEntrancePos, client.player.getPos(), physicalEntrancePos); } - } else { - LOGGER.info("[Skyblocker] Processing dungeon with map room width {} and entrance at map pos {} and physical pos {}", mapRoomWidth, mapEntrancePos, physicalEntrancePos); + } + + Vector2ic physicalPos = DungeonMapUtils.getPhysicalRoomPos(client.player.getPos()); + Vector2ic mapPos = DungeonMapUtils.getMapPosFromPhysical(physicalEntrancePos, mapEntrancePos, mapRoomSize, physicalPos); + Room.Type type = DungeonMapUtils.getRoomType(map, mapPos); + if (type == null) { + return; + } + Room room = rooms.get(physicalPos); + if (room == null) { + 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; } } @@ -188,7 +208,7 @@ public class DungeonSecrets { * @param type the type of room to create * @param physicalPositions the physical positions of the room */ - private static Room newRoom(Room.RoomType type, Vector2ic... physicalPositions) { + private static Room newRoom(Room.Type type, Vector2ic... physicalPositions) { Room newRoom = new Room(type, physicalPositions); for (Vector2ic physicalPos : physicalPositions) { rooms.put(physicalPos, newRoom); 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 index 87094d58..4be60c52 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/Room.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/Room.java @@ -3,19 +3,20 @@ package me.xmrvizzy.skyblocker.skyblock.dungeon.secrets; import net.minecraft.block.MapColor; import org.joml.Vector2ic; +import java.util.Arrays; import java.util.Set; public class Room { - private final RoomType type; + private final Type type; private String name; private final Set<Vector2ic> segments; - public Room(RoomType type, Vector2ic... physicalPositions) { + public Room(Type type, Vector2ic... physicalPositions) { this.type = type; this.segments = Set.of(physicalPositions); } - public RoomType getType() { + public Type getType() { return type; } @@ -23,17 +24,23 @@ public class Room { return segments.contains(segment); } - public enum RoomType { + @Override + public String toString() { + return "Room{type=" + type + ", name='" + name + "'" + ", segments=" + Arrays.toString(segments.toArray()) + "}"; + } + + 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; - RoomType(byte color) { + Type(byte color) { this.color = color; } } |