aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets
diff options
context:
space:
mode:
authorKevinthegreat <92656833+kevinthegreat1@users.noreply.github.com>2023-07-22 11:56:58 +0800
committerKevinthegreat <92656833+kevinthegreat1@users.noreply.github.com>2023-08-30 22:49:52 -0400
commitd531919154c78783b8423299083bbc9a34a8b617 (patch)
tree91abf963b9a809c3b377a1788d06a2057db5a702 /src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets
parentad5dac4b45a86c11e11cace97b314c3cc38cbf23 (diff)
downloadSkyblocker-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')
-rw-r--r--src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java98
-rw-r--r--src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java56
-rw-r--r--src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/secrets/Room.java17
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;
}
}