From c8136497ef6198e6b8a426fc23ccadeefe27ebdb Mon Sep 17 00:00:00 2001 From: Kevinthegreat <92656833+kevinthegreat1@users.noreply.github.com> Date: Tue, 19 Dec 2023 16:39:45 +0800 Subject: Rename Dungeon Manager --- .../skyblock/dungeon/secrets/DungeonManager.java | 681 +++++++++++++++++++++ .../skyblock/dungeon/secrets/DungeonMapUtils.java | 2 +- .../skyblock/dungeon/secrets/DungeonSecrets.java | 681 --------------------- .../skyblocker/skyblock/dungeon/secrets/Room.java | 38 +- 4 files changed, 701 insertions(+), 701 deletions(-) create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java delete mode 100644 src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java (limited to 'src/main/java/de/hysky/skyblocker/skyblock') diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java new file mode 100644 index 00000000..7705ca46 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java @@ -0,0 +1,681 @@ +package de.hysky.skyblocker.skyblock.dungeon.secrets; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +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 com.mojang.brigadier.context.CommandContext; +import com.mojang.serialization.JsonOps; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.Constants; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.scheduler.Scheduler; +import it.unimi.dsi.fastutil.objects.Object2ByteMap; +import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectIntPair; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +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.command.argument.BlockPosArgumentType; +import net.minecraft.command.argument.PosArgument; +import net.minecraft.command.argument.TextArgumentType; +import net.minecraft.entity.Entity; +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.server.command.ServerCommandSource; +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.hit.HitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.Vec3i; +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.BufferedWriter; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; +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 DungeonManager { + protected static final Logger LOGGER = LoggerFactory.getLogger(DungeonManager.class); + private static final String DUNGEONS_PATH = "dungeons"; + private static final Path CUSTOM_WAYPOINTS_DIR = SkyblockerMod.CONFIG_DIR.resolve("custom_secret_waypoints.json"); + private static final Pattern KEY_FOUND = Pattern.compile("^(?:\\[.+] )?(?\\w+) has obtained (?Wither|Blood) Key!$"); + /** + * 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 de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonRoomsDFU DungeonRoomsDFU}, which runs outside of Minecraft. + */ + @SuppressWarnings("JavadocReference") + protected static final Object2ByteMap 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 de.hysky.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>> ROOMS_DATA = new HashMap<>(); + @NotNull + private static final Map rooms = new HashMap<>(); + private static final Map roomsJson = new HashMap<>(); + private static final Map waypointsJson = new HashMap<>(); + /** + * The map of dungeon room names to custom waypoints relative to the room. + */ + private static final Table customWaypoints = HashBasedTable.create(); + @Nullable + private static CompletableFuture 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(); + } + + public static Stream getRoomsStream() { + return rooms.values().stream(); + } + + @SuppressWarnings("unused") + public static JsonObject getRoomMetadata(String room) { + return roomsJson.get(room).getAsJsonObject(); + } + + public static JsonArray getRoomWaypoints(String room) { + return waypointsJson.get(room).getAsJsonArray(); + } + + /** + * @see #customWaypoints + */ + public static Map getCustomWaypoints(String room) { + return customWaypoints.row(room); + } + + /** + * @see #customWaypoints + */ + @SuppressWarnings("UnusedReturnValue") + public static SecretWaypoint addCustomWaypoint(String room, SecretWaypoint waypoint) { + return customWaypoints.put(room, waypoint.pos, waypoint); + } + + /** + * @see #customWaypoints + */ + public static void addCustomWaypoints(String room, Collection waypoints) { + for (SecretWaypoint waypoint : waypoints) { + addCustomWaypoint(room, waypoint); + } + } + + /** + * @see #customWaypoints + */ + @Nullable + public static SecretWaypoint removeCustomWaypoint(String room, BlockPos pos) { + return customWaypoints.remove(room, pos); + } + + /** + * 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.enableRoomMatching) { + return; + } + // Execute with MinecraftClient as executor since we need to wait for MinecraftClient#resourceManager to be set + CompletableFuture.runAsync(DungeonManager::load, MinecraftClient.getInstance()).exceptionally(e -> { + LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load dungeon secrets", e); + return null; + }); + ClientLifecycleEvents.CLIENT_STOPPING.register(DungeonManager::saveCustomWaypoints); + Scheduler.INSTANCE.scheduleCyclic(DungeonManager::update, 10); + WorldRenderEvents.AFTER_TRANSLUCENT.register(DungeonManager::render); + ClientReceiveMessageEvents.GAME.register(DungeonManager::onChatMessage); + ClientReceiveMessageEvents.GAME_CANCELED.register(DungeonManager::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))) + .then(literal("getRelativePos").executes(DungeonManager::getRelativePos)) + .then(literal("getRelativeTargetPos").executes(DungeonManager::getRelativeTargetPos)) + .then(literal("addWaypoint").then(addCustomWaypointCommand(false))) + .then(literal("addWaypointRelatively").then(addCustomWaypointCommand(true))) + .then(literal("removeWaypoint").then(removeCustomWaypointCommand(false))) + .then(literal("removeWaypointRelatively").then(removeCustomWaypointCommand(true))) + )))); + ClientPlayConnectionEvents.JOIN.register(((handler, sender, client) -> reset())); + } + + private static void load() { + long startTime = System.currentTimeMillis(); + List> dungeonFutures = new ArrayList<>(); + for (Map.Entry 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 Dungeon Secrets] 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 roomsMap = ROOMS_DATA.get(dungeon).get(roomShape); + synchronized (roomsMap) { + roomsMap.put(room, rooms); + } + LOGGER.debug("[Skyblocker Dungeon Secrets] Loaded dungeon secrets dungeon {} room shape {} room {}", dungeon, roomShape, room); + }).exceptionally(e -> { + LOGGER.error("[Skyblocker Dungeon Secrets] 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 Dungeon Secrets] Loaded dungeon secret waypoints json"); + } catch (Exception e) { + LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load dungeon secret waypoints json", e); + } + })); + dungeonFutures.add(CompletableFuture.runAsync(() -> { + try (BufferedReader customWaypointsReader = Files.newBufferedReader(CUSTOM_WAYPOINTS_DIR)) { + SkyblockerMod.GSON.fromJson(customWaypointsReader, JsonObject.class).asMap().forEach((room, waypointsJson) -> + addCustomWaypoints(room, SecretWaypoint.LIST_CODEC.parse(JsonOps.INSTANCE, waypointsJson).resultOrPartial(LOGGER::error).orElseGet(ArrayList::new)) + ); + LOGGER.debug("[Skyblocker Dungeon Secrets] Loaded custom dungeon secret waypoints"); + } catch (Exception e) { + LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load custom dungeon secret waypoints", e); + } + })); + roomsLoaded = CompletableFuture.allOf(dungeonFutures.toArray(CompletableFuture[]::new)).thenRun(() -> LOGGER.info("[Skyblocker Dungeon Secrets] Loaded dungeon secrets for {} dungeon(s), {} room shapes, {} rooms, and {} custom secret waypoints 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(), customWaypoints.size(), System.currentTimeMillis() - startTime)).exceptionally(e -> { + LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load dungeon secrets", e); + return null; + }); + LOGGER.info("[Skyblocker Dungeon Secrets] Started loading dungeon secrets in (blocked main thread for) {} ms", System.currentTimeMillis() - startTime); + } + + private static void saveCustomWaypoints(MinecraftClient client) { + try (BufferedWriter writer = Files.newBufferedWriter(CUSTOM_WAYPOINTS_DIR)) { + JsonObject customWaypointsJson = new JsonObject(); + customWaypoints.rowMap().forEach((room, waypoints) -> + customWaypointsJson.add(room, SecretWaypoint.LIST_CODEC.encodeStart(JsonOps.INSTANCE, new ArrayList<>(waypoints.values())).resultOrPartial(LOGGER::error).orElseGet(JsonArray::new)) + ); + SkyblockerMod.GSON.toJson(customWaypointsJson, writer); + LOGGER.info("[Skyblocker Dungeon Secrets] Saved custom dungeon secret waypoints"); + } catch (Exception e) { + LOGGER.error("[Skyblocker Dungeon Secrets] Failed to save custom dungeon secret waypoints", e); + } + } + + 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 map) { + SkyblockerMod.GSON.fromJson(reader, JsonObject.class).asMap().forEach((room, jsonElement) -> map.put(room.toLowerCase().replaceAll(" ", "-"), jsonElement)); + } + + private static ArgumentBuilder> markSecretsCommand(boolean found) { + return argument("secretIndex", IntegerArgumentType.integer()).executes(context -> { + int secretIndex = IntegerArgumentType.getInteger(context, "secretIndex"); + if (markSecrets(secretIndex, found)) { + context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable(found ? "skyblocker.dungeons.secrets.markSecretFound" : "skyblocker.dungeons.secrets.markSecretMissing", secretIndex))); + } else { + context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable(found ? "skyblocker.dungeons.secrets.markSecretFoundUnable" : "skyblocker.dungeons.secrets.markSecretMissingUnable", secretIndex))); + } + return Command.SINGLE_SUCCESS; + }); + } + + private static int getRelativePos(CommandContext context) { + return getRelativePos(context.getSource(), context.getSource().getPlayer().getBlockPos()); + } + + private static int getRelativeTargetPos(CommandContext context) { + if (MinecraftClient.getInstance().crosshairTarget instanceof BlockHitResult blockHitResult && blockHitResult.getType() == HitResult.Type.BLOCK) { + return getRelativePos(context.getSource(), blockHitResult.getBlockPos()); + } else { + context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.noTarget"))); + } + return Command.SINGLE_SUCCESS; + } + + private static int getRelativePos(FabricClientCommandSource source, BlockPos pos) { + Room room = getRoomAtPhysical(pos); + if (isRoomMatched(room)) { + BlockPos relativePos = currentRoom.actualToRelative(pos); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.posMessage", currentRoom.getName(), relativePos.getX(), relativePos.getY(), relativePos.getZ()))); + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched"))); + } + return Command.SINGLE_SUCCESS; + } + + private static ArgumentBuilder> addCustomWaypointCommand(boolean relative) { + return argument("pos", BlockPosArgumentType.blockPos()) + .then(argument("secretIndex", IntegerArgumentType.integer()) + .then(argument("category", SecretWaypoint.Category.CategoryArgumentType.category()) + .then(argument("name", TextArgumentType.text()).executes(context -> { + // TODO Less hacky way with custom ClientBlockPosArgumentType + BlockPos pos = context.getArgument("pos", PosArgument.class).toAbsoluteBlockPos(new ServerCommandSource(null, context.getSource().getPosition(), context.getSource().getRotation(), null, 0, null, null, null, null)); + return relative ? addCustomWaypointRelative(context, pos) : addCustomWaypoint(context, pos); + })) + ) + ); + } + + private static int addCustomWaypoint(CommandContext context, BlockPos pos) { + Room room = getRoomAtPhysical(pos); + if (isRoomMatched(room)) { + room.addCustomWaypoint(context, room.actualToRelative(pos)); + } else { + context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched"))); + } + return Command.SINGLE_SUCCESS; + } + + private static int addCustomWaypointRelative(CommandContext context, BlockPos pos) { + if (isCurrentRoomMatched()) { + currentRoom.addCustomWaypoint(context, pos); + } else { + context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched"))); + } + return Command.SINGLE_SUCCESS; + } + + private static ArgumentBuilder> removeCustomWaypointCommand(boolean relative) { + return argument("pos", BlockPosArgumentType.blockPos()) + .executes(context -> { + // TODO Less hacky way with custom ClientBlockPosArgumentType + BlockPos pos = context.getArgument("pos", PosArgument.class).toAbsoluteBlockPos(new ServerCommandSource(null, context.getSource().getPosition(), context.getSource().getRotation(), null, 0, null, null, null, null)); + return relative ? removeCustomWaypointRelative(context, pos) : removeCustomWaypoint(context, pos); + }); + } + + private static int removeCustomWaypoint(CommandContext context, BlockPos pos) { + Room room = getRoomAtPhysical(pos); + if (isRoomMatched(room)) { + room.removeCustomWaypoint(context, room.actualToRelative(pos)); + } else { + context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched"))); + } + return Command.SINGLE_SUCCESS; + } + + private static int removeCustomWaypointRelative(CommandContext context, BlockPos pos) { + if (isCurrentRoomMatched()) { + currentRoom.removeCustomWaypoint(context, pos); + } else { + context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched"))); + } + return Command.SINGLE_SUCCESS; + } + + /** + * Updates the dungeon. The general idea is similar to the Dungeon Rooms Mod. + *

+ * When entering a new dungeon, this method: + *
    + *
  • Gets the physical northwest corner position of the entrance room and saves it in {@link #physicalEntrancePos}.
  • + *
  • Do nothing until the dungeon map exists.
  • + *
  • Gets the upper left corner of entrance room on the map and saves it in {@link #mapEntrancePos}.
  • + *
  • Gets the size of a room on the map in pixels and saves it in {@link #mapRoomSize}.
  • + *
  • Creates a new {@link Room} with {@link Room.Type} {@link Room.Type.ENTRANCE ENTRANCE} and sets {@link #currentRoom}.
  • + *
+ * When processing an existing dungeon, this method: + *
    + *
  • Calculates the physical northwest corner and upper left corner on the map of the room the player is currently in.
  • + *
  • Gets the room type based on the map color.
  • + *
  • If the room has not been created (when the physical northwest corner is not in {@link #rooms}):
  • + *
      + *
    • 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.)
    • + *
    • Create a new room.
    • + *
    + *
  • Sets {@link #currentRoom} to the current room, either created from the previous step or from {@link #rooms}.
  • + *
  • Calls {@link Room#update()} on {@link #currentRoom}.
  • + *
+ */ + @SuppressWarnings("JavadocReference") + private static void update() { + 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 mapEntrancePosAndSize = DungeonMapUtils.getMapEntrancePosAndRoomSize(map); + if (mapEntrancePosAndSize == null) { + return; + } + mapEntrancePos = mapEntrancePosAndSize.left(); + mapRoomSize = mapEntrancePosAndSize.rightInt(); + LOGGER.info("[Skyblocker Dungeon Secrets] 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 Dungeon Secrets] Failed to create room", e); + } + return null; + } + + /** + * Renders the secret waypoints in {@link #currentRoom} if {@link #shouldProcess()} and {@link #currentRoom} is not null. + */ + private static void render(WorldRenderContext context) { + if (shouldProcess() && currentRoom != null) { + currentRoom.render(context); + } + } + + /** + * Calls {@link Room#onChatMessage(String)} on {@link #currentRoom} if the message is an overlay message and {@link #isCurrentRoomMatched()} and processes key obtained messages. + *

Used to detect when all secrets in a room are found and detect when a wither or blood door is unlocked. + * To process key obtained messages, this method checks if door highlight is enabled and if the message matches a key obtained message. + * Then, it calls {@link Room#keyFound()} on {@link #currentRoom} if the client's player is the one who obtained the key. + * Otherwise, it calls {@link Room#keyFound()} on the room the player who obtained the key is in. + */ + private static void onChatMessage(Text text, boolean overlay) { + if (!shouldProcess()) { + return; + } + + String message = text.getString(); + + if (overlay && isCurrentRoomMatched()) { + currentRoom.onChatMessage(message); + } + + // Process key found messages for door highlight + if (SkyblockerConfigManager.get().locations.dungeons.doorHighlight.enableDoorHighlight) { + Matcher matcher = KEY_FOUND.matcher(message); + if (matcher.matches()) { + String name = matcher.group("name"); + MinecraftClient client = MinecraftClient.getInstance(); + if (client.player != null && client.player.getGameProfile().getName().equals(name)) { + if (currentRoom != null) { + currentRoom.keyFound(); + } else { + LOGGER.warn("[Skyblocker Dungeon Door] The current room at the current player {} does not exist", name); + } + } else if (client.world != null) { + Optional posOptional = client.world.getPlayers().stream().filter(player -> player.getGameProfile().getName().equals(name)).findAny().map(Entity::getPos); + if (posOptional.isPresent()) { + Room room = getRoomAtPhysical(posOptional.get()); + if (room != null) { + room.keyFound(); + } else { + LOGGER.warn("[Skyblocker Dungeon Door] Failed to find room at player {} with position {}", name, posOptional.get()); + } + } else { + LOGGER.warn("[Skyblocker Dungeon Door] Failed to find player {}", name); + } + } + } + } + + if (message.equals("[BOSS] Bonzo: Gratz for making it this far, but I'm basically unbeatable.") || message.equals("[BOSS] Scarf: This is where the journey ends for you, Adventurers.") + || message.equals("[BOSS] The Professor: I was burdened with terrible news recently...") || message.equals("[BOSS] Thorn: Welcome Adventurers! I am Thorn, the Spirit! And host of the Vegan Trials!") + || message.equals("[BOSS] Livid: Welcome, you've arrived right on time. I am Livid, the Master of Shadows.") || message.equals("[BOSS] Sadan: So you made it all the way here... Now you wish to defy me? Sadan?!") + || message.equals("[BOSS] Maxor: WELL! WELL! WELL! LOOK WHO'S HERE!")) reset(); + } + + /** + * 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)); + } + + /** + * 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(Vec3i) + */ + @Nullable + private static Room getRoomAtPhysical(Vec3i 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 {@link de.hysky.skyblocker.config.SkyblockerConfig.SecretWaypoints#enableRoomMatching room matching} is enabled and the player is in a dungeon. + * + * @return whether room matching and dungeon secrets should be processed + */ + private static boolean shouldProcess() { + return SkyblockerConfigManager.get().locations.dungeons.secretWaypoints.enableRoomMatching && Utils.isInDungeons(); + } + + /** + * Resets fields when leaving a dungeon or entering boss. + */ + private static void reset() { + mapEntrancePos = null; + mapRoomSize = 0; + physicalEntrancePos = null; + rooms.clear(); + currentRoom = null; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java index 01f2c9fc..516c3bad 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java @@ -271,7 +271,7 @@ public class DungeonMapUtils { queue.add(newMapPos); } } - DungeonSecrets.LOGGER.debug("[Skyblocker] Found dungeon room segments: {}", Arrays.toString(segments.toArray())); + DungeonManager.LOGGER.debug("[Skyblocker] Found dungeon room segments: {}", Arrays.toString(segments.toArray())); return segments.toArray(Vector2ic[]::new); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java deleted file mode 100644 index b9e149c6..00000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java +++ /dev/null @@ -1,681 +0,0 @@ -package de.hysky.skyblocker.skyblock.dungeon.secrets; - -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.Table; -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 com.mojang.brigadier.context.CommandContext; -import com.mojang.serialization.JsonOps; -import de.hysky.skyblocker.SkyblockerMod; -import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.utils.Constants; -import de.hysky.skyblocker.utils.Utils; -import de.hysky.skyblocker.utils.scheduler.Scheduler; -import it.unimi.dsi.fastutil.objects.Object2ByteMap; -import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectIntPair; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; -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.command.argument.BlockPosArgumentType; -import net.minecraft.command.argument.PosArgument; -import net.minecraft.command.argument.TextArgumentType; -import net.minecraft.entity.Entity; -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.server.command.ServerCommandSource; -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.hit.HitResult; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Vec3d; -import net.minecraft.util.math.Vec3i; -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.BufferedWriter; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Stream; -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"; - private static final Path CUSTOM_WAYPOINTS_DIR = SkyblockerMod.CONFIG_DIR.resolve("custom_secret_waypoints.json"); - private static final Pattern KEY_FOUND = Pattern.compile("^(?:\\[.+] )?(?\\w+) has obtained (?Wither|Blood) Key!$"); - /** - * 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 de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonRoomsDFU DungeonRoomsDFU}, which runs outside of Minecraft. - */ - @SuppressWarnings("JavadocReference") - protected static final Object2ByteMap 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 de.hysky.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>> ROOMS_DATA = new HashMap<>(); - @NotNull - private static final Map rooms = new HashMap<>(); - private static final Map roomsJson = new HashMap<>(); - private static final Map waypointsJson = new HashMap<>(); - /** - * The map of dungeon room names to custom waypoints relative to the room. - */ - private static final Table customWaypoints = HashBasedTable.create(); - @Nullable - private static CompletableFuture 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(); - } - - public static Stream getRoomsStream() { - return rooms.values().stream(); - } - - @SuppressWarnings("unused") - public static JsonObject getRoomMetadata(String room) { - return roomsJson.get(room).getAsJsonObject(); - } - - public static JsonArray getRoomWaypoints(String room) { - return waypointsJson.get(room).getAsJsonArray(); - } - - /** - * @see #customWaypoints - */ - public static Map getCustomWaypoints(String room) { - return customWaypoints.row(room); - } - - /** - * @see #customWaypoints - */ - @SuppressWarnings("UnusedReturnValue") - public static SecretWaypoint addCustomWaypoint(String room, SecretWaypoint waypoint) { - return customWaypoints.put(room, waypoint.pos, waypoint); - } - - /** - * @see #customWaypoints - */ - public static void addCustomWaypoints(String room, Collection waypoints) { - for (SecretWaypoint waypoint : waypoints) { - addCustomWaypoint(room, waypoint); - } - } - - /** - * @see #customWaypoints - */ - @Nullable - public static SecretWaypoint removeCustomWaypoint(String room, BlockPos pos) { - return customWaypoints.remove(room, pos); - } - - /** - * 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.enableRoomMatching) { - 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 Dungeon Secrets] Failed to load dungeon secrets", e); - return null; - }); - ClientLifecycleEvents.CLIENT_STOPPING.register(DungeonSecrets::saveCustomWaypoints); - 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))) - .then(literal("getRelativePos").executes(DungeonSecrets::getRelativePos)) - .then(literal("getRelativeTargetPos").executes(DungeonSecrets::getRelativeTargetPos)) - .then(literal("addWaypoint").then(addCustomWaypointCommand(false))) - .then(literal("addWaypointRelatively").then(addCustomWaypointCommand(true))) - .then(literal("removeWaypoint").then(removeCustomWaypointCommand(false))) - .then(literal("removeWaypointRelatively").then(removeCustomWaypointCommand(true))) - )))); - ClientPlayConnectionEvents.JOIN.register(((handler, sender, client) -> reset())); - } - - private static void load() { - long startTime = System.currentTimeMillis(); - List> dungeonFutures = new ArrayList<>(); - for (Map.Entry 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 Dungeon Secrets] 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 roomsMap = ROOMS_DATA.get(dungeon).get(roomShape); - synchronized (roomsMap) { - roomsMap.put(room, rooms); - } - LOGGER.debug("[Skyblocker Dungeon Secrets] Loaded dungeon secrets dungeon {} room shape {} room {}", dungeon, roomShape, room); - }).exceptionally(e -> { - LOGGER.error("[Skyblocker Dungeon Secrets] 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 Dungeon Secrets] Loaded dungeon secret waypoints json"); - } catch (Exception e) { - LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load dungeon secret waypoints json", e); - } - })); - dungeonFutures.add(CompletableFuture.runAsync(() -> { - try (BufferedReader customWaypointsReader = Files.newBufferedReader(CUSTOM_WAYPOINTS_DIR)) { - SkyblockerMod.GSON.fromJson(customWaypointsReader, JsonObject.class).asMap().forEach((room, waypointsJson) -> - addCustomWaypoints(room, SecretWaypoint.LIST_CODEC.parse(JsonOps.INSTANCE, waypointsJson).resultOrPartial(LOGGER::error).orElseGet(ArrayList::new)) - ); - LOGGER.debug("[Skyblocker Dungeon Secrets] Loaded custom dungeon secret waypoints"); - } catch (Exception e) { - LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load custom dungeon secret waypoints", e); - } - })); - roomsLoaded = CompletableFuture.allOf(dungeonFutures.toArray(CompletableFuture[]::new)).thenRun(() -> LOGGER.info("[Skyblocker Dungeon Secrets] Loaded dungeon secrets for {} dungeon(s), {} room shapes, {} rooms, and {} custom secret waypoints 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(), customWaypoints.size(), System.currentTimeMillis() - startTime)).exceptionally(e -> { - LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load dungeon secrets", e); - return null; - }); - LOGGER.info("[Skyblocker Dungeon Secrets] Started loading dungeon secrets in (blocked main thread for) {} ms", System.currentTimeMillis() - startTime); - } - - private static void saveCustomWaypoints(MinecraftClient client) { - try (BufferedWriter writer = Files.newBufferedWriter(CUSTOM_WAYPOINTS_DIR)) { - JsonObject customWaypointsJson = new JsonObject(); - customWaypoints.rowMap().forEach((room, waypoints) -> - customWaypointsJson.add(room, SecretWaypoint.LIST_CODEC.encodeStart(JsonOps.INSTANCE, new ArrayList<>(waypoints.values())).resultOrPartial(LOGGER::error).orElseGet(JsonArray::new)) - ); - SkyblockerMod.GSON.toJson(customWaypointsJson, writer); - LOGGER.info("[Skyblocker Dungeon Secrets] Saved custom dungeon secret waypoints"); - } catch (Exception e) { - LOGGER.error("[Skyblocker Dungeon Secrets] Failed to save custom dungeon secret waypoints", e); - } - } - - 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 map) { - SkyblockerMod.GSON.fromJson(reader, JsonObject.class).asMap().forEach((room, jsonElement) -> map.put(room.toLowerCase().replaceAll(" ", "-"), jsonElement)); - } - - private static ArgumentBuilder> markSecretsCommand(boolean found) { - return argument("secretIndex", IntegerArgumentType.integer()).executes(context -> { - int secretIndex = IntegerArgumentType.getInteger(context, "secretIndex"); - if (markSecrets(secretIndex, found)) { - context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable(found ? "skyblocker.dungeons.secrets.markSecretFound" : "skyblocker.dungeons.secrets.markSecretMissing", secretIndex))); - } else { - context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable(found ? "skyblocker.dungeons.secrets.markSecretFoundUnable" : "skyblocker.dungeons.secrets.markSecretMissingUnable", secretIndex))); - } - return Command.SINGLE_SUCCESS; - }); - } - - private static int getRelativePos(CommandContext context) { - return getRelativePos(context.getSource(), context.getSource().getPlayer().getBlockPos()); - } - - private static int getRelativeTargetPos(CommandContext context) { - if (MinecraftClient.getInstance().crosshairTarget instanceof BlockHitResult blockHitResult && blockHitResult.getType() == HitResult.Type.BLOCK) { - return getRelativePos(context.getSource(), blockHitResult.getBlockPos()); - } else { - context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.noTarget"))); - } - return Command.SINGLE_SUCCESS; - } - - private static int getRelativePos(FabricClientCommandSource source, BlockPos pos) { - Room room = getRoomAtPhysical(pos); - if (isRoomMatched(room)) { - BlockPos relativePos = currentRoom.actualToRelative(pos); - source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.posMessage", currentRoom.getName(), relativePos.getX(), relativePos.getY(), relativePos.getZ()))); - } else { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched"))); - } - return Command.SINGLE_SUCCESS; - } - - private static ArgumentBuilder> addCustomWaypointCommand(boolean relative) { - return argument("pos", BlockPosArgumentType.blockPos()) - .then(argument("secretIndex", IntegerArgumentType.integer()) - .then(argument("category", SecretWaypoint.Category.CategoryArgumentType.category()) - .then(argument("name", TextArgumentType.text()).executes(context -> { - // TODO Less hacky way with custom ClientBlockPosArgumentType - BlockPos pos = context.getArgument("pos", PosArgument.class).toAbsoluteBlockPos(new ServerCommandSource(null, context.getSource().getPosition(), context.getSource().getRotation(), null, 0, null, null, null, null)); - return relative ? addCustomWaypointRelative(context, pos) : addCustomWaypoint(context, pos); - })) - ) - ); - } - - private static int addCustomWaypoint(CommandContext context, BlockPos pos) { - Room room = getRoomAtPhysical(pos); - if (isRoomMatched(room)) { - room.addCustomWaypoint(context, room.actualToRelative(pos)); - } else { - context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched"))); - } - return Command.SINGLE_SUCCESS; - } - - private static int addCustomWaypointRelative(CommandContext context, BlockPos pos) { - if (isCurrentRoomMatched()) { - currentRoom.addCustomWaypoint(context, pos); - } else { - context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched"))); - } - return Command.SINGLE_SUCCESS; - } - - private static ArgumentBuilder> removeCustomWaypointCommand(boolean relative) { - return argument("pos", BlockPosArgumentType.blockPos()) - .executes(context -> { - // TODO Less hacky way with custom ClientBlockPosArgumentType - BlockPos pos = context.getArgument("pos", PosArgument.class).toAbsoluteBlockPos(new ServerCommandSource(null, context.getSource().getPosition(), context.getSource().getRotation(), null, 0, null, null, null, null)); - return relative ? removeCustomWaypointRelative(context, pos) : removeCustomWaypoint(context, pos); - }); - } - - private static int removeCustomWaypoint(CommandContext context, BlockPos pos) { - Room room = getRoomAtPhysical(pos); - if (isRoomMatched(room)) { - room.removeCustomWaypoint(context, room.actualToRelative(pos)); - } else { - context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched"))); - } - return Command.SINGLE_SUCCESS; - } - - private static int removeCustomWaypointRelative(CommandContext context, BlockPos pos) { - if (isCurrentRoomMatched()) { - currentRoom.removeCustomWaypoint(context, pos); - } else { - context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched"))); - } - return Command.SINGLE_SUCCESS; - } - - /** - * Updates the dungeon. The general idea is similar to the Dungeon Rooms Mod. - *

- * When entering a new dungeon, this method: - *
    - *
  • Gets the physical northwest corner position of the entrance room and saves it in {@link #physicalEntrancePos}.
  • - *
  • Do nothing until the dungeon map exists.
  • - *
  • Gets the upper left corner of entrance room on the map and saves it in {@link #mapEntrancePos}.
  • - *
  • Gets the size of a room on the map in pixels and saves it in {@link #mapRoomSize}.
  • - *
  • Creates a new {@link Room} with {@link Room.Type} {@link Room.Type.ENTRANCE ENTRANCE} and sets {@link #currentRoom}.
  • - *
- * When processing an existing dungeon, this method: - *
    - *
  • Calculates the physical northwest corner and upper left corner on the map of the room the player is currently in.
  • - *
  • Gets the room type based on the map color.
  • - *
  • If the room has not been created (when the physical northwest corner is not in {@link #rooms}):
  • - *
      - *
    • 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.)
    • - *
    • Create a new room.
    • - *
    - *
  • Sets {@link #currentRoom} to the current room, either created from the previous step or from {@link #rooms}.
  • - *
  • Calls {@link Room#update()} on {@link #currentRoom}.
  • - *
- */ - @SuppressWarnings("JavadocReference") - private static void update() { - 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 mapEntrancePosAndSize = DungeonMapUtils.getMapEntrancePosAndRoomSize(map); - if (mapEntrancePosAndSize == null) { - return; - } - mapEntrancePos = mapEntrancePosAndSize.left(); - mapRoomSize = mapEntrancePosAndSize.rightInt(); - LOGGER.info("[Skyblocker Dungeon Secrets] 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 Dungeon Secrets] Failed to create room", e); - } - return null; - } - - /** - * Renders the secret waypoints in {@link #currentRoom} if {@link #shouldProcess()} and {@link #currentRoom} is not null. - */ - private static void render(WorldRenderContext context) { - if (shouldProcess() && currentRoom != null) { - currentRoom.render(context); - } - } - - /** - * Calls {@link Room#onChatMessage(String)} on {@link #currentRoom} if the message is an overlay message and {@link #isCurrentRoomMatched()} and processes key obtained messages. - *

Used to detect when all secrets in a room are found and detect when a wither or blood door is unlocked. - * To process key obtained messages, this method checks if door highlight is enabled and if the message matches a key obtained message. - * Then, it calls {@link Room#keyFound()} on {@link #currentRoom} if the client's player is the one who obtained the key. - * Otherwise, it calls {@link Room#keyFound()} on the room the player who obtained the key is in. - */ - private static void onChatMessage(Text text, boolean overlay) { - if (!shouldProcess()) { - return; - } - - String message = text.getString(); - - if (overlay && isCurrentRoomMatched()) { - currentRoom.onChatMessage(message); - } - - // Process key found messages for door highlight - if (SkyblockerConfigManager.get().locations.dungeons.doorHighlight.enableDoorHighlight) { - Matcher matcher = KEY_FOUND.matcher(message); - if (matcher.matches()) { - String name = matcher.group("name"); - MinecraftClient client = MinecraftClient.getInstance(); - if (client.player != null && client.player.getGameProfile().getName().equals(name)) { - if (currentRoom != null) { - currentRoom.keyFound(); - } else { - LOGGER.warn("[Skyblocker Dungeon Door] The current room at the current player {} does not exist", name); - } - } else if (client.world != null) { - Optional posOptional = client.world.getPlayers().stream().filter(player -> player.getGameProfile().getName().equals(name)).findAny().map(Entity::getPos); - if (posOptional.isPresent()) { - Room room = getRoomAtPhysical(posOptional.get()); - if (room != null) { - room.keyFound(); - } else { - LOGGER.warn("[Skyblocker Dungeon Door] Failed to find room at player {} with position {}", name, posOptional.get()); - } - } else { - LOGGER.warn("[Skyblocker Dungeon Door] Failed to find player {}", name); - } - } - } - } - - if (message.equals("[BOSS] Bonzo: Gratz for making it this far, but I'm basically unbeatable.") || message.equals("[BOSS] Scarf: This is where the journey ends for you, Adventurers.") - || message.equals("[BOSS] The Professor: I was burdened with terrible news recently...") || message.equals("[BOSS] Thorn: Welcome Adventurers! I am Thorn, the Spirit! And host of the Vegan Trials!") - || message.equals("[BOSS] Livid: Welcome, you've arrived right on time. I am Livid, the Master of Shadows.") || message.equals("[BOSS] Sadan: So you made it all the way here... Now you wish to defy me? Sadan?!") - || message.equals("[BOSS] Maxor: WELL! WELL! WELL! LOOK WHO'S HERE!")) reset(); - } - - /** - * 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)); - } - - /** - * 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(Vec3i) - */ - @Nullable - private static Room getRoomAtPhysical(Vec3i 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 {@link de.hysky.skyblocker.config.SkyblockerConfig.SecretWaypoints#enableRoomMatching room matching} is enabled and the player is in a dungeon. - * - * @return whether room matching and dungeon secrets should be processed - */ - private static boolean shouldProcess() { - return SkyblockerConfigManager.get().locations.dungeons.secretWaypoints.enableRoomMatching && Utils.isInDungeons(); - } - - /** - * Resets fields when leaving a dungeon or entering boss. - */ - private static void reset() { - mapEntrancePos = null; - mapRoomSize = 0; - physicalEntrancePos = null; - rooms.clear(); - currentRoom = null; - } -} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java index 0d3c6a87..fffe1bd2 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java @@ -103,7 +103,7 @@ public class Room { 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()); + roomsData = DungeonManager.ROOMS_DATA.getOrDefault("catacombs", Collections.emptyMap()).getOrDefault(shape.shape.toLowerCase(), Collections.emptyMap()); possibleRooms = getPossibleRooms(segmentsX, segmentsY); } @@ -191,7 +191,7 @@ public class Room { } /** - * Adds a custom waypoint relative to this room to {@link DungeonSecrets#customWaypoints} and all existing instances of this room. + * Adds a custom waypoint relative to this room to {@link DungeonManager#customWaypoints} and all existing instances of this room. * * @param secretIndex the index of the secret waypoint * @param category the category of the secret waypoint @@ -201,8 +201,8 @@ public class Room { @SuppressWarnings("JavadocReference") private void addCustomWaypoint(int secretIndex, SecretWaypoint.Category category, Text waypointName, BlockPos pos) { SecretWaypoint waypoint = new SecretWaypoint(secretIndex, category, waypointName, pos); - DungeonSecrets.addCustomWaypoint(name, waypoint); - DungeonSecrets.getRoomsStream().filter(r -> name.equals(r.getName())).forEach(r -> r.addCustomWaypoint(waypoint)); + DungeonManager.addCustomWaypoint(name, waypoint); + DungeonManager.getRoomsStream().filter(r -> name.equals(r.getName())).forEach(r -> r.addCustomWaypoint(waypoint)); } /** @@ -228,7 +228,7 @@ public class Room { } /** - * Removes a custom waypoint relative to this room from {@link DungeonSecrets#customWaypoints} and all existing instances of this room. + * Removes a custom waypoint relative to this room from {@link DungeonManager#customWaypoints} and all existing instances of this room. * * @param pos the position of the secret waypoint relative to this room * @return the removed secret waypoint or {@code null} if there was no secret waypoint at the given position @@ -236,9 +236,9 @@ public class Room { @SuppressWarnings("JavadocReference") @Nullable private SecretWaypoint removeCustomWaypoint(BlockPos pos) { - SecretWaypoint waypoint = DungeonSecrets.removeCustomWaypoint(name, pos); + SecretWaypoint waypoint = DungeonManager.removeCustomWaypoint(name, pos); if (waypoint != null) { - DungeonSecrets.getRoomsStream().filter(r -> name.equals(r.getName())).forEach(r -> r.removeCustomWaypoint(waypoint.secretIndex, pos)); + DungeonManager.getRoomsStream().filter(r -> name.equals(r.getName())).forEach(r -> r.removeCustomWaypoint(waypoint.secretIndex, pos)); } return waypoint; } @@ -290,7 +290,7 @@ public class Room { // Room scanning and matching // Logical AND has higher precedence than logical OR - if (!type.needsScanning() || matchState != MatchState.MATCHING && matchState != MatchState.DOUBLE_CHECKING || !DungeonSecrets.isRoomsLoaded() || findRoom != null && !findRoom.isDone()) { + if (!type.needsScanning() || matchState != MatchState.MATCHING && matchState != MatchState.DOUBLE_CHECKING || !DungeonManager.isRoomsLoaded() || findRoom != null && !findRoom.isDone()) { return; } ClientPlayerEntity player = client.player; @@ -304,7 +304,7 @@ public class Room { } } }).exceptionally(e -> { - DungeonSecrets.LOGGER.error("[Skyblocker Dungeon Secrets] Encountered an unknown exception while matching room {}", this, e); + DungeonManager.LOGGER.error("[Skyblocker Dungeon Secrets] Encountered an unknown exception while matching room {}", this, e); return null; }); } @@ -323,7 +323,7 @@ public class Room { *

* This method: *
    - *
  • Checks if the block type is included in the dungeon rooms data. See {@link DungeonSecrets#NUMERIC_ID}.
  • + *
  • Checks if the block type is included in the dungeon rooms data. See {@link DungeonManager#NUMERIC_ID}.
  • *
  • For each possible direction:
  • *
      *
    • Rotate and convert the position to a relative position. See {@link DungeonMapUtils#actualToRelative(Direction, Vector2ic, BlockPos)}.
    • @@ -364,7 +364,7 @@ public class Room { * @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()); + byte id = DungeonManager.NUMERIC_ID.getByte(Registries.BLOCK.getId(world.getBlockState(pos).getBlock()).toString()); if (id == 0) { return false; } @@ -383,7 +383,7 @@ public class Room { if (matchingRoomsSize == 0) { // If no rooms match, reset the fields and scan again after 50 ticks. matchState = MatchState.FAILED; - DungeonSecrets.LOGGER.warn("[Skyblocker Dungeon Secrets] No dungeon room matched after checking {} block(s) including double checking {} block(s)", checkedBlocks.size(), doubleCheckBlocks); + DungeonManager.LOGGER.warn("[Skyblocker Dungeon Secrets] No dungeon room matched after checking {} block(s) including double checking {} block(s)", checkedBlocks.size(), doubleCheckBlocks); Scheduler.INSTANCE.schedule(() -> matchState = MatchState.MATCHING, 50); reset(); return true; @@ -394,20 +394,20 @@ public class Room { name = directionRoom.getRight().get(0); direction = directionRoom.getLeft(); physicalCornerPos = directionRoom.getMiddle(); - DungeonSecrets.LOGGER.info("[Skyblocker Dungeon Secrets] Room {} matched after checking {} block(s), starting double checking", name, checkedBlocks.size()); + DungeonManager.LOGGER.info("[Skyblocker Dungeon Secrets] Room {} matched after checking {} block(s), starting double checking", name, checkedBlocks.size()); roomMatched(); return false; } else if (matchState == MatchState.DOUBLE_CHECKING && ++doubleCheckBlocks >= 10) { // If double-checked, set state to matched and discard the no longer needed fields. matchState = MatchState.MATCHED; DungeonEvents.ROOM_MATCHED.invoker().onRoomMatched(this); - DungeonSecrets.LOGGER.info("[Skyblocker Dungeon Secrets] Room {} confirmed after checking {} block(s) including double checking {} block(s)", name, checkedBlocks.size(), doubleCheckBlocks); + DungeonManager.LOGGER.info("[Skyblocker Dungeon Secrets] Room {} confirmed after checking {} block(s) including double checking {} block(s)", name, checkedBlocks.size(), doubleCheckBlocks); discard(); return true; } return false; } else { - DungeonSecrets.LOGGER.debug("[Skyblocker Dungeon Secrets] {} room(s) remaining after checking {} block(s)", matchingRoomsSize, checkedBlocks.size()); + DungeonManager.LOGGER.debug("[Skyblocker Dungeon Secrets] {} room(s) remaining after checking {} block(s)", matchingRoomsSize, checkedBlocks.size()); return false; } } @@ -424,7 +424,7 @@ public class Room { } /** - * Loads the secret waypoints for the room from {@link DungeonSecrets#waypointsJson} once it has been matched + * Loads the secret waypoints for the room from {@link DungeonManager#waypointsJson} once it has been matched * and sets {@link #matchState} to {@link MatchState#DOUBLE_CHECKING}. * * @param directionRooms the direction, position, and name of the room @@ -432,7 +432,7 @@ public class Room { @SuppressWarnings("JavadocReference") private void roomMatched() { secretWaypoints = HashBasedTable.create(); - for (JsonElement waypointElement : DungeonSecrets.getRoomWaypoints(name)) { + for (JsonElement waypointElement : DungeonManager.getRoomWaypoints(name)) { JsonObject waypoint = waypointElement.getAsJsonObject(); String secretName = waypoint.get("secretName").getAsString(); Matcher secretIndexMatcher = SECRET_INDEX.matcher(secretName); @@ -440,7 +440,7 @@ public class Room { BlockPos pos = DungeonMapUtils.relativeToActual(direction, physicalCornerPos, waypoint); secretWaypoints.put(secretIndex, pos, new SecretWaypoint(secretIndex, waypoint, secretName, pos)); } - DungeonSecrets.getCustomWaypoints(name).values().forEach(this::addCustomWaypoint); + DungeonManager.getCustomWaypoints(name).values().forEach(this::addCustomWaypoint); matchState = MatchState.DOUBLE_CHECKING; } @@ -586,7 +586,7 @@ public class Room { */ private void onSecretFound(SecretWaypoint secretWaypoint, String msg, Object... args) { secretWaypoints.row(secretWaypoint.secretIndex).values().forEach(SecretWaypoint::setFound); - DungeonSecrets.LOGGER.info(msg, args); + DungeonManager.LOGGER.info(msg, args); } protected boolean markSecrets(int secretIndex, boolean found) { -- cgit