Skip to content

Commit

Permalink
Merge pull request #653 from olim88/metal-detector-helper
Browse files Browse the repository at this point in the history
Metal detector helper
  • Loading branch information
kevinthegreat1 authored Apr 19, 2024
2 parents 7fa4ac6 + 8178c28 commit 22a7fe7
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 2 deletions.
2 changes: 2 additions & 0 deletions src/main/java/de/hysky/skyblocker/SkyblockerMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;
Expand Down Expand Up @@ -123,6 +124,7 @@ public void onInitializeClient() {
FarmingHud.init();
LowerSensitivity.init();
CrystalsLocationsManager.init();
MetalDetector.init();
ChatMessageListener.init();
Shortcuts.init();
ChatRulesHandler.init();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,9 @@ public static class DwarvenMines {
@SerialEntry
public boolean solvePuzzler = true;

@SerialEntry
public boolean metalDetectorHelper = true;

@SerialEntry
public DwarvenHud dwarvenHud = new DwarvenHud();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig
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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public AuctionTypeWidget(int x, int y, SlotClickHandler slotClick) {
super(x, y, 17, 17, Text.literal("Auction Type Widget"), slotClick, Option.ALL);
}

public enum Option implements OptionInfo {
public enum Option implements SliderWidget.OptionInfo {
ALL("all.png"),
BIN("bin.png"),
AUC("auctions.png");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public SortWidget(int x, int y, SlotClickHandler clickSlot) {
super(x, y, 36, 9, Text.literal("Sort Widget"), clickSlot, Option.HIGH);
}

public enum Option implements OptionInfo {
public enum Option implements SliderWidget.OptionInfo {
HIGH("high.png"),
LOW("low.png"),
SOON("soon.png"),
Expand Down
249 changes: 249 additions & 0 deletions src/main/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
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.Util;
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.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
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 = Util.make(new HashMap<>(), map -> {
map.put("Diamond", new Vec3i(33, 0, 3));
map.put("Lapis", new Vec3i(-33, 0, -3));
map.put("Emerald", new Vec3i(-3, 0, 33));
map.put("Gold", new Vec3i(3, 0, -33));
});
private static final HashSet<Vec3i> knownChestOffsets = Util.make(new HashSet<>(), set -> {
set.add(new Vec3i(-38, -22, 26)); // -38, -22, 26
set.add(new Vec3i(38, -22, -26)); // 38, -22, -26
set.add(new Vec3i(-40, -22, 18)); // -40, -22, 18
set.add(new Vec3i(-41, -20, 22)); // -41, -20, 22
set.add(new Vec3i(-5, -21, 16)); // -5, -21, 16
set.add(new Vec3i(40, -22, -30)); // 40, -22, -30
set.add(new Vec3i(-42, -20, -28)); // -42, -20, -28
set.add(new Vec3i(-43, -22, -40)); // -43, -22, -40
set.add(new Vec3i(42, -19, -41)); // 42, -19, -41
set.add(new Vec3i(43, -21, -16)); // 43, -21, -16
set.add(new Vec3i(-1, -22, -20)); // -1, -22, -20
set.add(new Vec3i(6, -21, 28)); // 6, -21, 28
set.add(new Vec3i(7, -21, 11)); // 7, -21, 11
set.add(new Vec3i(7, -21, 22)); // 7, -21, 22
set.add(new Vec3i(-12, -21, -44)); // -12, -21, -44
set.add(new Vec3i(12, -22, 31)); // 12, -22, 31
set.add(new Vec3i(12, -22, -22)); // 12, -22, -22
set.add(new Vec3i(12, -21, 7)); // 12, -21, 7
set.add(new Vec3i(12, -21, -43)); // 12, -21, -43
set.add(new Vec3i(-14, -21, 43)); // -14, -21, 43
set.add(new Vec3i(-14, -21, 22)); // -14, -21, 22
set.add(new Vec3i(-17, -21, 20)); // -17, -21, 20
set.add(new Vec3i(-20, -22, 0)); // -20, -22, 0
set.add(new Vec3i(1, -21, 20)); // 1, -21, 20
set.add(new Vec3i(19, -22, 29)); // 19, -22, 29
set.add(new Vec3i(20, -22, 0)); // 20, -22, 0
set.add(new Vec3i(20, -21, -26)); // 20, -21, -26
set.add(new Vec3i(-23, -22, 40)); // -23, -22, 40
set.add(new Vec3i(22, -21, -14)); // 22, -21, -14
set.add(new Vec3i(-24, -22, 12)); // -24, -22, 12
set.add(new Vec3i(23, -22, 26)); // 23, -22, 26
set.add(new Vec3i(23, -22, -39)); // 23, -22, -39
set.add(new Vec3i(24, -22, 27)); // 24, -22, 27
set.add(new Vec3i(25, -22, 17)); // 25, -22, 17
set.add(new Vec3i(29, -21, -44)); // 29, -21, -44
set.add(new Vec3i(-31, -21, -12)); // -31, -21, -12
set.add(new Vec3i(-31, -21, -40)); // -31, -21, -40
set.add(new Vec3i(30, -21, -25)); // 30, -21, -25
set.add(new Vec3i(-32, -21, -40)); // -32, -21, -40
set.add(new Vec3i(-36, -20, 42)); // -36, -20, 42
set.add(new Vec3i(-37, -21, -14)); // -37, -21, -14
set.add(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);
}

/**
* Processes the message with the distance to the treasure, updates the helper, and works out possible locations using that message.
*
* @param text the message sent to the player
* @param overlay if the message is an overlay message
*/
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("skyblocker.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("skyblocker.dwarvenMines.metalDetectorHelper.foundTreasureMessage").formatted(Formatting.GREEN)), false);
} else {
CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.dwarvenMines.metalDetectorHelper.possibleTreasureLocationsMessage").append(Text.of(String.valueOf(possibleBlocks.size())))), false);
}
}

//update previous positions
previousDistance = distance;
previousPlayerPos = playerPos;
}

/**
* Processes the found treasure message and resets the helper
*
* @param text the message sent to the player
*/
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<>();
}
}

/**
* Works out the possible locations the treasure could be using the distance the treasure is from the player and
* narrows down possible locations until there is one left.
*
* @param distance the distance the treasure is from the player squared
* @param playerPos the position of the player
*/
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 = (int) -distance; x <= distance; x++) {
for (int z = (int) -distance; 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("skyblocker.dwarvenMines.metalDetectorHelper.somethingWentWrongMessage").formatted(Formatting.RED)), false);
}
}
}

/**
* Uses the labels for the keepers names to find the central point of the mines of divan so the known offsets can be used.
*/
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("skyblocker.dwarvenMines.metalDetectorHelper.foundCenter").formatted(Formatting.GREEN)), false);
return;
}
}
}

/**
* Renders waypoints for the location of treasure or possible treasure.
* @param context world render context
*/
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 || !Utils.isInCrystalHollows() || 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.CORLEONE, Text.translatable("skyblocker.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.CORLEONE, Text.translatable("skyblocker.dwarvenMines.metalDetectorHelper.possible"), new BlockPos(block.getX(), block.getY(), block.getZ()));
waypoint.render(context);
}
}
}
10 changes: 10 additions & 0 deletions src/main/resources/assets/skyblocker/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,8 @@
"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.dwarvenHud": "Dwarven HUD",
"text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud.enabledCommissions": "Enable Commissions",
"text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud.enabledPowder": "Enable Powder",
Expand Down Expand Up @@ -521,6 +523,14 @@
"skyblocker.exotic.glitched": "GLITCHED",
"skyblocker.exotic.exotic": "EXOTIC",

"skyblocker.dwarvenMines.metalDetectorHelper.startTip": "Stand still in multiple places until the solver has narrowed down possible locations to one",
"skyblocker.dwarvenMines.metalDetectorHelper.foundCenter": "Found the center of mines, now working faster",
"skyblocker.dwarvenMines.metalDetectorHelper.foundTreasureMessage": "Found treasure",
"skyblocker.dwarvenMines.metalDetectorHelper.possibleTreasureLocationsMessage": "Possible treasure locations: ",
"skyblocker.dwarvenMines.metalDetectorHelper.somethingWentWrongMessage": "Something went wrong with the metal detector. Trying again",
"skyblocker.dwarvenMines.metalDetectorHelper.treasure": "Treasure",
"skyblocker.dwarvenMines.metalDetectorHelper.possible": "Possible",

"skyblocker.end.hud.zealotsSinceLastEye": "Since last eye: %d",
"skyblocker.end.hud.zealotsTotalKills": "Total kills: %d",
"skyblocker.end.hud.avgKillsPerEye": "Avg kills per eye: %d",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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));
}
}

0 comments on commit 22a7fe7

Please sign in to comment.