diff options
8 files changed, 230 insertions, 32 deletions
diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java index 9d5e95f5..001c7506 100644 --- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java @@ -613,6 +613,12 @@ public class SkyblockerConfig { @SerialEntry public boolean enableStonkWaypoints = true; + + @SerialEntry + public boolean enableAotvWaypoints = true; + + @SerialEntry + public boolean enablePearlWaypoints = true; @SerialEntry public boolean enableDefaultWaypoints = true; diff --git a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java index c3f54d4d..2cdde89d 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java @@ -123,6 +123,21 @@ public class DungeonsCategory { .controller(ConfigUtils::createBooleanController) .build()) .option(Option.<Boolean>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enableAotvWaypoints")) + .binding(defaults.locations.dungeons.secretWaypoints.enableAotvWaypoints, + () -> config.locations.dungeons.secretWaypoints.enableAotvWaypoints, + newValue -> config.locations.dungeons.secretWaypoints.enableAotvWaypoints = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enablePearlWaypoints")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enablePearlWaypoints.@Tooltip"))) + .binding(defaults.locations.dungeons.secretWaypoints.enablePearlWaypoints, + () -> config.locations.dungeons.secretWaypoints.enablePearlWaypoints, + newValue -> config.locations.dungeons.secretWaypoints.enablePearlWaypoints = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enableDefaultWaypoints")) .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enableDefaultWaypoints.@Tooltip"))) .binding(defaults.locations.dungeons.secretWaypoints.enableDefaultWaypoints, 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 259cc3f3..73d4a452 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 @@ -7,7 +7,6 @@ import net.minecraft.block.MapColor; import net.minecraft.item.map.MapIcon; import net.minecraft.item.map.MapState; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3i; import org.jetbrains.annotations.NotNull; @@ -173,7 +172,7 @@ public class DungeonMapUtils { @NotNull public static Vector2ic getPhysicalRoomPos(double x, double z) { Vector2i physicalPos = new Vector2i(x + 8.5, z + 8.5, RoundingMode.TRUNCATE); - return physicalPos.sub(MathHelper.floorMod(physicalPos.x(), 32), MathHelper.floorMod(physicalPos.y(), 32)).sub(8, 8); + return physicalPos.sub(Math.floorMod(physicalPos.x(), 32), Math.floorMod(physicalPos.y(), 32)).sub(8, 8); } public static Vector2ic[] getPhysicalPosFromMap(Vector2ic mapEntrancePos, int mapRoomSize, Vector2ic physicalEntrancePos, Vector2ic... mapPositions) { 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 index cb9615fb..08b84852 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java @@ -7,14 +7,14 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; -import it.unimi.dsi.fastutil.objects.Object2ByteMap; -import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectIntPair; import 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.message.v1.ClientReceiveMessageEvents; @@ -37,7 +37,10 @@ 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; @@ -149,7 +152,9 @@ public class DungeonSecrets { 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("markAsMissing").then(markSecretsCommand(false))) + .then(literal("getRelativePos").executes(context -> getRelativePos(context.getSource()))) + .then(literal("getRelativeTargetPos").executes(context -> getRelativeTargetPos(context.getSource()))))))); ClientPlayConnectionEvents.JOIN.register(((handler, sender, client) -> reset())); } @@ -204,8 +209,9 @@ public class DungeonSecrets { /** * 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 + * @param map the map to load into */ private static void loadJson(BufferedReader reader, Map<String, JsonElement> map) { SkyblockerMod.GSON.fromJson(reader, JsonObject.class).asMap().forEach((room, jsonElement) -> map.put(room.toLowerCase().replaceAll(" ", "-"), jsonElement)); @@ -223,6 +229,30 @@ public class DungeonSecrets { }); } + private static int getRelativePos(FabricClientCommandSource source) { + return getRelativePos(source, source.getPlayer().getBlockPos()); + } + + private static int getRelativeTargetPos(FabricClientCommandSource source) { + if (MinecraftClient.getInstance().crosshairTarget instanceof BlockHitResult blockHitResult && blockHitResult.getType() == HitResult.Type.BLOCK) { + return getRelativePos(source, blockHitResult.getBlockPos()); + } else { + source.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; + } + /** * Updates the dungeon. The general idea is similar to the Dungeon Rooms Mod. * <p></p> @@ -340,9 +370,16 @@ public class DungeonSecrets { * Used to detect when all secrets in a room are found. */ private static void onChatMessage(Text text, boolean overlay) { + String message = text.getString(); + if (overlay && isCurrentRoomMatched()) { - currentRoom.onChatMessage(text.getString()); + currentRoom.onChatMessage(message); } + + 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(); } /** @@ -411,6 +448,19 @@ public class DungeonSecrets { } /** + * 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)} @@ -440,7 +490,7 @@ public class DungeonSecrets { } /** - * Resets fields when leaving a dungeon. + * Resets fields when leaving a dungeon or entering boss. */ private static void reset() { mapEntrancePos = 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 dd7dc91e..0d7a444f 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 @@ -5,10 +5,10 @@ import com.google.common.collect.ImmutableTable; import com.google.common.collect.Table; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import de.hysky.skyblocker.utils.scheduler.Scheduler; import it.unimi.dsi.fastutil.ints.IntRBTreeSet; import it.unimi.dsi.fastutil.ints.IntSortedSet; import it.unimi.dsi.fastutil.ints.IntSortedSets; -import de.hysky.skyblocker.utils.scheduler.Scheduler; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; import net.fabricmc.fabric.api.util.TriState; import net.minecraft.block.BlockState; @@ -23,7 +23,6 @@ import net.minecraft.entity.mob.AmbientEntity; import net.minecraft.registry.Registries; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; import org.apache.commons.lang3.tuple.MutableTriple; import org.apache.commons.lang3.tuple.Triple; @@ -72,6 +71,9 @@ public class Room { */ private TriState matched = TriState.DEFAULT; private Table<Integer, BlockPos, SecretWaypoint> secretWaypoints; + private String name; + private Direction direction; + private Vector2ic physicalCornerPos; public Room(@NotNull Type type, @NotNull Vector2ic... physicalPositions) { this.type = type; @@ -92,6 +94,13 @@ public class Room { return matched == TriState.TRUE; } + /** + * Not null if {@link #isMatched()}. + */ + public String getName() { + return name; + } + @Override public String toString() { return "Room{type=" + type + ", shape=" + shape + ", matched=" + matched + ", segments=" + Arrays.toString(segments.toArray()) + "}"; @@ -186,8 +195,8 @@ public class Room { if (pos.getY() < 66 || pos.getY() > 73) { return true; } - int x = MathHelper.floorMod(pos.getX() - 8, 32); - int z = MathHelper.floorMod(pos.getZ() - 8, 32); + int x = Math.floorMod(pos.getX() - 8, 32); + int z = Math.floorMod(pos.getZ() - 8, 32); return (x < 13 || x > 17 || z > 2 && z < 28) && (z < 13 || z > 17 || x > 2 && x < 28); } @@ -217,7 +226,7 @@ public class Room { * </ul> * <li> If there are exactly one room matching: </li> * <ul> - * <li> Call {@link #roomMatched(String, Direction, Vector2ic)}. </li> + * <li> Call {@link #roomMatched()}. </li> * <li> Discard the no longer needed fields to save memory. </li> * <li> Return {@code true} </li> * </ul> @@ -256,7 +265,10 @@ public class Room { // If one room matches, load the secrets for that room and discard the no longer needed fields. for (Triple<Direction, Vector2ic, List<String>> directionRooms : possibleRooms) { if (directionRooms.getRight().size() == 1) { - roomMatched(directionRooms.getRight().get(0), directionRooms.getLeft(), directionRooms.getMiddle()); + name = directionRooms.getRight().get(0); + direction = directionRooms.getLeft(); + physicalCornerPos = directionRooms.getMiddle(); + roomMatched(); discard(); return true; } @@ -286,7 +298,7 @@ public class Room { * @param directionRooms the direction, position, and name of the room */ @SuppressWarnings("JavadocReference") - private void roomMatched(String name, Direction direction, Vector2ic physicalCornerPos) { + private void roomMatched() { Table<Integer, BlockPos, SecretWaypoint> secretWaypointsMutable = HashBasedTable.create(); for (JsonElement waypointElement : DungeonSecrets.getRoomWaypoints(name)) { JsonObject waypoint = waypointElement.getAsJsonObject(); @@ -297,6 +309,7 @@ public class Room { } secretWaypoints = ImmutableTable.copyOf(secretWaypointsMutable); matched = TriState.TRUE; + DungeonSecrets.LOGGER.info("[Skyblocker] Room {} matched after checking {} block(s)", name, checkedBlocks.size()); } @@ -323,6 +336,20 @@ public class Room { } /** + * Fails if !{@link #isMatched()} + */ + protected BlockPos actualToRelative(BlockPos pos) { + return DungeonMapUtils.actualToRelative(direction, physicalCornerPos, pos); + } + + /** + * Fails if !{@link #isMatched()} + */ + protected BlockPos relativeToActual(BlockPos pos) { + return DungeonMapUtils.relativeToActual(direction, physicalCornerPos, pos); + } + + /** * Calls {@link SecretWaypoint#render(WorldRenderContext)} on {@link #secretWaypoints all secret waypoints}. */ protected void render(WorldRenderContext context) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java index 0d9bdff1..a520c73f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java @@ -112,6 +112,8 @@ public class SecretWaypoint { LEVER(secretWaypoints -> secretWaypoints.enableLeverWaypoints, 250, 217, 2), FAIRYSOUL(secretWaypoints -> secretWaypoints.enableFairySoulWaypoints, 255, 85, 255), STONK(secretWaypoints -> secretWaypoints.enableStonkWaypoints, 146, 52, 235), + AOTV(secretWaypoints -> secretWaypoints.enableAotvWaypoints, 252, 98, 3), + PEARL(secretWaypoints -> secretWaypoints.enablePearlWaypoints, 57, 117, 125), DEFAULT(secretWaypoints -> secretWaypoints.enableDefaultWaypoints, 190, 255, 252); private final Predicate<SkyblockerConfig.SecretWaypoints> enabledPredicate; private final float[] colorComponents; @@ -135,6 +137,8 @@ public class SecretWaypoint { case "lever" -> Category.LEVER; case "fairysoul" -> Category.FAIRYSOUL; case "stonk" -> Category.STONK; + case "aotv" -> Category.AOTV; + case "pearl" -> Category.PEARL; default -> Category.DEFAULT; }; } diff --git a/src/main/resources/assets/skyblocker/dungeons/secretlocations.json b/src/main/resources/assets/skyblocker/dungeons/secretlocations.json index d5e38345..a0a97c67 100644 --- a/src/main/resources/assets/skyblocker/dungeons/secretlocations.json +++ b/src/main/resources/assets/skyblocker/dungeons/secretlocations.json @@ -263,6 +263,13 @@ "z":21 }, { + "secretName":"1 - Pearl", + "category":"pearl", + "x":25, + "y":75, + "z":27 + }, + { "secretName":"1 - Item", "category":"item", "x":27, @@ -835,6 +842,13 @@ "z":21 }, { + "secretName":"2 - AOTV", + "category":"aotv", + "x":25, + "y":76, + "z":22 + }, + { "secretName":"2 - Stonk", "category":"stonk", "x":23, @@ -1713,13 +1727,6 @@ ], "Redstone-Key-3":[ { - "secretName":"1 - Redstone Skull (right click)", - "category":"lever", - "x":10, - "y":70, - "z":26 - }, - { "secretName":"1 - Superboom", "category":"superboom", "x":20, @@ -1727,13 +1734,6 @@ "z":7 }, { - "secretName":"1 - Redstone Skull (right click)", - "category":"lever", - "x":19, - "y":66, - "z":7 - }, - { "secretName":"1 - Lever behind Crypt", "category":"lever", "x":5, @@ -1748,6 +1748,20 @@ "z":6 }, { + "secretName":"2 - Redstone Skull (right click)", + "category":"lever", + "x":10, + "y":70, + "z":26 + }, + { + "secretName":"2 - Redstone Skull (right click)", + "category":"lever", + "x":19, + "y":66, + "z":7 + }, + { "secretName":"2 - Place Skull", "category":"lever", "x":27, @@ -2945,9 +2959,9 @@ { "secretName":"6 - Stonk", "category":"stonk", - "x":17, + "x":18, "y":70, - "z":15 + "z":14 }, { "secretName":"6 - Pressure Plate 2", @@ -3059,6 +3073,13 @@ "z":5 }, { + "secretName":"2 - AOTV", + "category":"aotv", + "x":77, + "y":82, + "z":16 + }, + { "secretName":"2 - Wither Essence", "category":"wither", "x":79, @@ -3110,6 +3131,13 @@ "z":9 }, { + "secretName":"3 - AOTV", + "category":"aotv", + "x":7, + "y":80, + "z":15 + }, + { "secretName":"3 - Superboom", "category":"superboom", "x":6, @@ -3226,6 +3254,13 @@ "z":24 }, { + "secretName":"1/2 - AOTV", + "category":"aotv", + "x":84, + "y":91, + "z":26 + }, + { "secretName":"1 - Bat", "category":"bat", "x":81, @@ -3800,6 +3835,13 @@ "z":51 }, { + "secretName":"5 - AOTV", + "category":"aotv", + "x":16, + "y":81, + "z":53 + }, + { "secretName":"5 - Chest", "category":"chest", "x":31, @@ -4072,6 +4114,13 @@ "z":30 }, { + "secretName":"5 - AOTV", + "category":"aotv", + "x":52, + "y":78, + "z":9 + }, + { "secretName":"5 - Crypt", "category":"superboom", "x":52, @@ -4846,6 +4895,13 @@ "z":52 }, { + "secretName":"2 - AOTV", + "category":"aotv", + "x":45, + "y":78, + "z":47 + }, + { "secretName":"2 - Chest", "category":"chest", "x":46, @@ -4881,6 +4937,13 @@ "z":41 }, { + "secretName":"5 - Pearl", + "category":"pearl", + "x":15, + "y":84, + "z":56 + }, + { "secretName":"5 - Stonk", "category":"stonk", "x":18, @@ -5081,6 +5144,13 @@ "z":61 }, { + "secretName": "7 - AOTV", + "category":"aotv", + "x":39, + "y":84, + "z":58 + }, + { "secretName":"7 - Chest", "category":"chest", "x":41, @@ -5332,6 +5402,13 @@ "z":43 }, { + "secretName":"3/4 - AOTV", + "category":"aotv", + "x":49, + "y":83, + "z":36 + }, + { "secretName":"3 - Chest", "category":"chest", "x":49, @@ -5553,6 +5630,13 @@ "z":15 }, { + "secretName":"5 - Pearl", + "category":"pearl", + "x":8, + "y":59, + "z":14 + }, + { "secretName":"5 - Entrance", "category":"entrance", "x":25, @@ -5835,6 +5919,13 @@ } ], "Double-Stair-3":[ + { + "secretName":"1 - Pearl", + "category":"pearl", + "x":24, + "y":72, + "z":11 + }, { "secretName":"1 - Wither Essence", "category":"wither", diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index aaa21814..a37e5d37 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -162,6 +162,9 @@ "text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enableLeverWaypoints" : "Enable Lever Waypoints", "text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enableFairySoulWaypoints" : "Enable Fairy Soul Waypoints", "text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enableStonkWaypoints" : "Enable Stonk Waypoints", + "text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enableAotvWaypoints" : "Enable AOTV Waypoints", + "text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enablePearlWaypoints": "Enable Pearl Waypoints", + "text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enablePearlWaypoints.@Tooltip": "With these waypoints, you throw a pearl towards the block and at the same time AOTV up.", "text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enableDefaultWaypoints" : "Enable Default Waypoints", "text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enableDefaultWaypoints.@Tooltip" : "This includes all waypoints that do not belong to a category.", "text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit": "Dungeon Chest Profit Calculator", @@ -266,6 +269,9 @@ "skyblocker.dungeons.secrets.markSecretMissing": "§rMarked secret #%d as missing.", "skyblocker.dungeons.secrets.markSecretFoundUnable": "§cUnable to mark secret #%d as found.", "skyblocker.dungeons.secrets.markSecretMissingUnable": "§cUnable to mark secret #%d as missing.", + "skyblocker.dungeons.secrets.posMessage": "§rRoom: %s, X: %d, Y: %d, Z: %d", + "skyblocker.dungeons.secrets.noTarget": "§cNo target block found! (Are you pointing at a block in range?)", + "skyblocker.dungeons.secrets.notMatched": "§cThe current room is not matched! (Are you in a dungeon room?)", "skyblocker.fishing.reelNow": "Reel in now!", "skyblocker.rift.healNow": "Heal now!", |