aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/DwarvenMinesCategory.java7
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetector.java229
-rw-r--r--src/main/resources/assets/skyblocker/lang/en_us.json9
-rw-r--r--src/test/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetectorTest.java34
6 files changed, 284 insertions, 0 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 3d96cc50..494b16e2 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -20,6 +20,7 @@ import de.hysky.skyblocker.skyblock.dungeon.secrets.SecretsTracker;
import de.hysky.skyblocker.skyblock.dwarven.CrystalsHud;
import de.hysky.skyblocker.skyblock.dwarven.CrystalsLocationsManager;
import de.hysky.skyblocker.skyblock.dwarven.DwarvenHud;
+import de.hysky.skyblocker.skyblock.dwarven.MetalDetector;
import de.hysky.skyblocker.skyblock.end.BeaconHighlighter;
import de.hysky.skyblocker.skyblock.end.EnderNodes;
import de.hysky.skyblocker.skyblock.end.TheEnd;
@@ -123,6 +124,7 @@ public class SkyblockerMod implements ClientModInitializer {
FarmingHud.init();
LowerSensitivity.init();
CrystalsLocationsManager.init();
+ MetalDetector.init();
ChatMessageListener.init();
Shortcuts.init();
ChatRulesHandler.init();
diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
index 6fddd3d0..4a1f7502 100644
--- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
@@ -950,6 +950,9 @@ public class SkyblockerConfig {
public boolean solvePuzzler = true;
@SerialEntry
+ public boolean MetalDetectorHelper = true;
+
+ @SerialEntry
public DwarvenHud dwarvenHud = new DwarvenHud();
@SerialEntry
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/DwarvenMinesCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/DwarvenMinesCategory.java
index 4ae0fc35..67b8632b 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/DwarvenMinesCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/DwarvenMinesCategory.java
@@ -42,6 +42,13 @@ public class DwarvenMinesCategory {
newValue -> config.locations.dwarvenMines.solvePuzzler = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper.@Tooltip"))) .binding(defaults.locations.dwarvenMines.MetalDetectorHelper,
+ () -> config.locations.dwarvenMines.MetalDetectorHelper,
+ newValue -> config.locations.dwarvenMines.MetalDetectorHelper = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
//Dwarven HUD
.group(OptionGroup.createBuilder()
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetector.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetector.java
new file mode 100644
index 00000000..e302b778
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetector.java
@@ -0,0 +1,229 @@
+package de.hysky.skyblocker.skyblock.dwarven;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Constants;
+import de.hysky.skyblocker.utils.Utils;
+import de.hysky.skyblocker.utils.render.RenderHelper;
+import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.entity.decoration.ArmorStandEntity;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Box;
+import net.minecraft.util.math.Vec3d;
+import net.minecraft.util.math.Vec3i;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class MetalDetector {
+
+ private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
+
+ private static final float[] LIGHT_GRAY = { 192 / 255f, 192 / 255f, 192 / 255f };
+
+ private static final Pattern TREASURE_PATTERN = Pattern.compile("(§3§lTREASURE: §b)(\\d+\\.\\d)m");
+
+ private static final Pattern KEEPER_PATTERN = Pattern.compile("Keeper of (\\w+)");
+ private static final HashMap<String, Vec3i> keeperOffsets = new HashMap<>() {{
+ put("Diamond", new Vec3i(33, 0, 3));
+ put("Lapis", new Vec3i(-33, 0, -3));
+ put("Emerald", new Vec3i(-3, 0, 33));
+ put("Gold", new Vec3i(3, 0, -33));
+ }};
+
+ private static final HashSet<Vec3i> knownChestOffsets = new HashSet<>(Arrays.asList(
+ new Vec3i(-38, -22, 26), // -38, -22, 26
+ new Vec3i(38, -22, -26), // 38, -22, -26
+ new Vec3i(-40, -22, 18), // -40, -22, 18
+ new Vec3i(-41, -20, 22), // -41, -20, 22
+ new Vec3i(-5, -21, 16), // -5, -21, 16
+ new Vec3i(40, -22, -30), // 40, -22, -30
+ new Vec3i(-42, -20, -28), // -42, -20, -28
+ new Vec3i(-43, -22, -40), // -43, -22, -40
+ new Vec3i(42, -19, -41), // 42, -19, -41
+ new Vec3i(43, -21, -16), // 43, -21, -16
+ new Vec3i(-1, -22, -20), // -1, -22, -20
+ new Vec3i(6, -21, 28), // 6, -21, 28
+ new Vec3i(7, -21, 11), // 7, -21, 11
+ new Vec3i(7, -21, 22), // 7, -21, 22
+ new Vec3i(-12, -21, -44), // -12, -21, -44
+ new Vec3i(12, -22, 31), // 12, -22, 31
+ new Vec3i(12, -22, -22), // 12, -22, -22
+ new Vec3i(12, -21, 7), // 12, -21, 7
+ new Vec3i(12, -21, -43), // 12, -21, -43
+ new Vec3i(-14, -21, 43), // -14, -21, 43
+ new Vec3i(-14, -21, 22), // -14, -21, 22
+ new Vec3i(-17, -21, 20), // -17, -21, 20
+ new Vec3i(-20, -22, 0), // -20, -22, 0
+ new Vec3i(1, -21, 20), // 1, -21, 20
+ new Vec3i(19, -22, 29), // 19, -22, 29
+ new Vec3i(20, -22, 0), // 20, -22, 0
+ new Vec3i(20, -21, -26), // 20, -21, -26
+ new Vec3i(-23, -22, 40), // -23, -22, 40
+ new Vec3i(22, -21, -14), // 22, -21, -14
+ new Vec3i(-24, -22, 12), // -24, -22, 12
+ new Vec3i(23, -22, 26), // 23, -22, 26
+ new Vec3i(23, -22, -39), // 23, -22, -39
+ new Vec3i(24, -22, 27), // 24, -22, 27
+ new Vec3i(25, -22, 17), // 25, -22, 17
+ new Vec3i(29, -21, -44), // 29, -21, -44
+ new Vec3i(-31, -21, -12), // -31, -21, -12
+ new Vec3i(-31, -21, -40), // -31, -21, -40
+ new Vec3i(30, -21, -25), // 30, -21, -25
+ new Vec3i(-32, -21, -40), // -32, -21, -40
+ new Vec3i(-36, -20, 42), // -36, -20, 42
+ new Vec3i(-37, -21, -14), // -37, -21, -14
+ new Vec3i(-37, -21, -22) // -37, -21, -22
+ ));
+
+ protected static Vec3i minesCenter = null;
+ private static double previousDistance;
+ private static Vec3d previousPlayerPos;
+ protected static boolean newTreasure = true;
+
+ private static boolean startedLooking = false;
+
+ protected static List<Vec3i> possibleBlocks = new ArrayList<>();
+
+ public static void init() {
+ ClientReceiveMessageEvents.GAME.register(MetalDetector::getDistanceMessage);
+ WorldRenderEvents.AFTER_TRANSLUCENT.register(MetalDetector::render);
+ }
+
+ private static void getDistanceMessage(Text text, boolean overlay) {
+ if (!overlay || !SkyblockerConfigManager.get().locations.dwarvenMines.MetalDetectorHelper || !Utils.isInCrystalHollows() || !(Utils.getIslandArea().substring(2).equals("Mines of Divan")) || CLIENT.player == null) {
+ checkChestFound(text);
+ return;
+ }
+ //in the mines of divan
+ Matcher treasureDistanceMature = TREASURE_PATTERN.matcher(text.getString());
+ if (!treasureDistanceMature.matches()) {
+ return;
+ }
+ //find new values
+ double distance = Double.parseDouble(treasureDistanceMature.group(2));
+ Vec3d playerPos = CLIENT.player.getPos();
+ int previousPossibleBlockCount = possibleBlocks.size();
+
+ //send message when starting looking about how to use mod
+ if (!startedLooking) {
+ startedLooking = true;
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper.startTip")),false);
+ }
+
+ //find the center of the mines if possible to speed up search
+ if (minesCenter == null) {
+ findCenterOfMines();
+ }
+
+ //find the possible locations the treasure could be
+ if (distance == previousDistance && playerPos.equals(previousPlayerPos)) {
+ updatePossibleBlocks(distance, playerPos);
+ }
+
+ //if the amount of possible blocks has changed output that to the user
+ if (possibleBlocks.size() != previousPossibleBlockCount) {
+ if (possibleBlocks.size() == 1) {
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper.foundTreasureMessage").formatted(Formatting.GREEN)),false);
+ }
+ else {
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper.possibleTreasureLocationsMessage").append(Text.of(String.valueOf(possibleBlocks.size())))),false);
+ }
+ }
+
+ //update previous positions
+ previousDistance = distance;
+ previousPlayerPos = playerPos;
+ }
+
+ private static void checkChestFound(Text text) {
+ if (!Utils.isInCrystalHollows() || !(Utils.getIslandArea().substring(2).equals("Mines of Divan")) || CLIENT.player == null) {
+ return;
+ }
+ if (text.getString().startsWith("You found")) {
+ newTreasure = true;
+ possibleBlocks = new ArrayList<>();
+ }
+ }
+
+ protected static void updatePossibleBlocks(Double distance, Vec3d playerPos) {
+ if (newTreasure) {
+ possibleBlocks = new ArrayList<>();
+ newTreasure = false;
+ if (minesCenter != null) {//if center of the mines is known use the predefined offsets to filter the locations
+ for (Vec3i knownOffset : knownChestOffsets) {
+ Vec3i checkPos = minesCenter.add(knownOffset).add(0,1,0);
+ if (Math.abs(playerPos.distanceTo(Vec3d.of(checkPos)) - distance) < 0.25) {
+ possibleBlocks.add(checkPos);
+ }
+ }
+ }
+ else {
+ for (int x = -distance.intValue(); x <= distance; x++) {
+ for (int z = -distance.intValue(); z <= distance; z++) {
+ Vec3i checkPos =new Vec3i((int)(playerPos.x) + x, (int)playerPos.y, (int)playerPos.z + z);
+ if (Math.abs(playerPos.distanceTo(Vec3d.of(checkPos)) - distance) < 0.25) {
+ possibleBlocks.add(checkPos);
+ }
+ }
+ }
+ }
+
+ } else {
+ possibleBlocks.removeIf(location -> Math.abs(playerPos.distanceTo(Vec3d.of(location)) - distance) >= 0.25);
+ }
+
+ //if possible blocks is of length 0 something has failed reset and try again
+ if (possibleBlocks.isEmpty()) {
+ newTreasure = true;
+ if (CLIENT.player != null) {
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper.somethingWentWrongMessage").formatted(Formatting.RED)),false);
+ }
+ }
+ }
+
+ private static void findCenterOfMines() {
+ if (CLIENT.player == null || CLIENT.world == null) {
+ return;
+ }
+ Box searchBox = CLIENT.player.getBoundingBox().expand(500d);
+ List<ArmorStandEntity> armorStands = CLIENT.world.getEntitiesByClass(ArmorStandEntity.class, searchBox, ArmorStandEntity::hasCustomName);
+
+ for (ArmorStandEntity armorStand : armorStands) {
+ String name = armorStand.getName().getString();
+ Matcher nameMatcher = KEEPER_PATTERN.matcher(name);
+
+ if (nameMatcher.matches()) {
+ Vec3i offset = keeperOffsets.get(nameMatcher.group(1));
+ minesCenter = armorStand.getBlockPos().add(offset);
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper.foundCenter").formatted(Formatting.GREEN)),false);
+ return;
+ }
+ }
+ }
+
+ private static void render(WorldRenderContext context) {
+ //only render enabled and if there is a few location options and in the mines of divan
+ if (!SkyblockerConfigManager.get().locations.dwarvenMines.MetalDetectorHelper || possibleBlocks.isEmpty() || possibleBlocks.size() > 8 || !(Utils.getIslandArea().substring(2).equals("Mines of Divan"))) {
+ return;
+ }
+ //only one location render just that and guiding line to it
+ if (possibleBlocks.size() == 1) {
+ Vec3i block = possibleBlocks.get(0).add(0, -1, 0); //the block you are taken to is one block above the chest
+ CrystalsWaypoint waypoint = new CrystalsWaypoint(CrystalsWaypoint.Category.MINES_OF_DIVAN, Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper.treasure"), new BlockPos(block.getX(), block.getY(), block.getZ()));
+ waypoint.render(context);
+ RenderHelper.renderLineFromCursor(context, Vec3d.ofCenter(block), LIGHT_GRAY, 1f, 5f);
+ return;
+ }
+
+ for (Vec3i block : possibleBlocks) {
+ CrystalsWaypoint waypoint = new CrystalsWaypoint(CrystalsWaypoint.Category.MINES_OF_DIVAN, Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper.possible"), new BlockPos(block.getX(), block.getY(), block.getZ()));
+ waypoint.render(context);
+ }
+ }
+}
diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json
index 200bbcae..5fa39b63 100644
--- a/src/main/resources/assets/skyblocker/lang/en_us.json
+++ b/src/main/resources/assets/skyblocker/lang/en_us.json
@@ -341,6 +341,15 @@
"text.autoconfig.skyblocker.option.locations.dwarvenMines.enableDrillFuel": "Enable Drill Fuel",
"text.autoconfig.skyblocker.option.locations.dwarvenMines.solveFetchur": "Solve Fetchur",
"text.autoconfig.skyblocker.option.locations.dwarvenMines.solvePuzzler": "Solve Puzzler Puzzle",
+ "text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper": "Metal Detector Helper",
+ "text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper.@Tooltip": "Helper for the metal detector puzzle in the Mines of Divan.",
+ "text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper.startTip": "Stand still in multiple places unit solver has narrowed down locations to one",
+ "text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper.foundCenter": "Found center of mines now working faster",
+ "text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper.foundTreasureMessage": "Found treasure",
+ "text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper.possibleTreasureLocationsMessage": "Possible treasure locations: ",
+ "text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper.somethingWentWrongMessage": "Something went wrong with the metal detector. Trying again",
+ "text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper.treasure": "Treasure",
+ "text.autoconfig.skyblocker.option.locations.dwarvenMines.MetalDetectorHelper.possible": "Possible",
"text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud": "Dwarven HUD",
"text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud.enabledCommissions": "Enable Commissions",
"text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud.enabledPowder": "Enable Powder",
diff --git a/src/test/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetectorTest.java b/src/test/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetectorTest.java
new file mode 100644
index 00000000..22532f96
--- /dev/null
+++ b/src/test/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetectorTest.java
@@ -0,0 +1,34 @@
+package de.hysky.skyblocker.skyblock.dwarven;
+
+import net.minecraft.util.math.Vec3d;
+import net.minecraft.util.math.Vec3i;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+
+public class MetalDetectorTest {
+
+ @Test
+ void testFindPossibleBlocks() {
+ //test starting without knowing middle
+ MetalDetector.updatePossibleBlocks(10.0, new Vec3d(0, 0, 0));
+ Assertions.assertEquals(MetalDetector.possibleBlocks.size(), 40);
+
+ MetalDetector.updatePossibleBlocks(11.2, new Vec3d(5, 0, 0));
+ Assertions.assertEquals(MetalDetector.possibleBlocks.size(), 2);
+
+ MetalDetector.updatePossibleBlocks(10.0, new Vec3d(10, 0, 10));
+ Assertions.assertEquals(MetalDetector.possibleBlocks.get(0), new Vec3i(0, 0, 10));
+
+ //test while knowing the middle location
+ MetalDetector.possibleBlocks = new ArrayList<>();
+ MetalDetector.newTreasure = true;
+ MetalDetector.minesCenter = new Vec3i(0, 0, 0);
+
+ MetalDetector.updatePossibleBlocks(24.9, new Vec3d(10, 1, 10));
+ Assertions.assertEquals(MetalDetector.possibleBlocks.size(), 1);
+ Assertions.assertEquals(MetalDetector.possibleBlocks.get(0), new Vec3i(1, -20, 20));
+
+ }
+}