aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
authorCraftyOldMiner <85420839+CraftyOldMiner@users.noreply.github.com>2022-03-24 14:55:20 -0500
committerGitHub <noreply@github.com>2022-03-24 19:55:20 +0000
commite202e4adf979073921455083f5e737bc4fcf5f14 (patch)
tree5e1653b8696168294f2b3456d532e88d4a73fc73 /src/main/java
parent7d923e6675dc681261e3cbd5fb0c81263209dbc6 (diff)
downloadNotEnoughUpdates-e202e4adf979073921455083f5e737bc4fcf5f14.tar.gz
NotEnoughUpdates-e202e4adf979073921455083f5e737bc4fcf5f14.tar.bz2
NotEnoughUpdates-e202e4adf979073921455083f5e737bc4fcf5f14.zip
Refactor Hollows solvers, add tests, add Vec3Comparable, fix bugs (#95)
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DiagCommand.java27
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/core/util/Vec3Comparable.java129
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalMetalDetectorSolver.java450
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalWishingCompassSolver.java942
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Mining.java54
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/NEUDebugLogger.java10
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java14
7 files changed, 1290 insertions, 336 deletions
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DiagCommand.java b/src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DiagCommand.java
index 88264538..dab99698 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DiagCommand.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DiagCommand.java
@@ -17,8 +17,10 @@ public class DiagCommand extends ClientCommandBase {
private static final String USAGE_TEXT = EnumChatFormatting.WHITE +
"Usage: /neudiag <metal | wishing | debug>\n\n" +
- "/neudiag metal Metal Detector Solver diagnostics\n" +
- "/neudiag wishing Wishing Compass Solver diagnostics\n" +
+ "/neudiag metal Metal Detector Solver diagnostics\n" +
+ " <no sub-command> Show current solution diags\n" +
+ " center=<off | on> Disable / enable using center\n" +
+ "/neudiag wishing Wishing Compass Solver diagnostics\n" +
"/neudiag debug\n" +
" <no sub-command> Show current flags\n" +
" <enable | disable> <flag> Enable/disable flag\n";
@@ -37,7 +39,26 @@ public class DiagCommand extends ClientCommandBase {
String command = args[0].toLowerCase();
switch (command) {
case "metal":
- CrystalMetalDetectorSolver.logDiagnosticData(true);
+ if (args.length == 1) {
+ CrystalMetalDetectorSolver.logDiagnosticData(true);
+ return;
+ }
+
+ String subCommand = args[1].toLowerCase();
+ if (subCommand.equals("center=off")) {
+ CrystalMetalDetectorSolver.setDebugDoNotUseCenter(true);
+ sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "Center coordinates-based solutions disabled"));
+ }
+ else if (subCommand.equals("center=on")) {
+ CrystalMetalDetectorSolver.setDebugDoNotUseCenter(false);
+ sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "Center coordinates-based solutions enabled"));
+ } else {
+ showUsage(sender);
+ return;
+ }
+
break;
case "wishing":
CrystalWishingCompassSolver.getInstance().logDiagnosticData(true);
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/core/util/Vec3Comparable.java b/src/main/java/io/github/moulberry/notenoughupdates/core/util/Vec3Comparable.java
new file mode 100644
index 00000000..cd144c21
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/core/util/Vec3Comparable.java
@@ -0,0 +1,129 @@
+package io.github.moulberry.notenoughupdates.core.util;
+
+import net.minecraft.util.BlockPos;
+import net.minecraft.util.Vec3;
+import net.minecraft.util.Vec3i;
+
+public class Vec3Comparable extends Vec3 implements Comparable<Vec3Comparable> {
+ public static final Vec3Comparable NULL_VECTOR = new Vec3Comparable(0.0, 0.0, 0.0);
+
+ public Vec3Comparable(double x, double y, double z) {
+ super(x, y, z);
+ }
+
+ public Vec3Comparable(Vec3i sourceVec) {
+ super(sourceVec);
+ }
+
+ public Vec3Comparable(Vec3 source) {
+ super(source.xCoord, source.yCoord, source.zCoord);
+ }
+
+ public Vec3Comparable(BlockPos source) {
+
+ super(source.getX(), source.getY(), source.getZ());
+ }
+
+ public Vec3Comparable(Vec3Comparable source) {
+ super(source.xCoord, source.yCoord, source.zCoord);
+ }
+
+ @Override
+ public Vec3Comparable subtractReverse(Vec3 vec) {
+ return new Vec3Comparable(super.subtractReverse(vec));
+ }
+
+ @Override
+ public Vec3Comparable normalize() {
+ return new Vec3Comparable(super.normalize());
+ }
+
+ @Override
+ public Vec3Comparable crossProduct(Vec3 vec) {
+ return new Vec3Comparable(super.crossProduct(vec));
+ }
+
+ @Override
+ public Vec3Comparable subtract(Vec3 vec) {
+ return new Vec3Comparable(super.subtract(vec));
+ }
+
+ @Override
+ public Vec3Comparable subtract(double x, double y, double z) {
+ return new Vec3Comparable(super.subtract(x, y, z));
+ }
+
+ @Override
+ public Vec3Comparable add(Vec3 other) {
+ return new Vec3Comparable(super.add(other));
+ }
+
+ @Override
+ public Vec3Comparable addVector(double x, double y, double z) {
+ return new Vec3Comparable(super.addVector(x, y, z));
+ }
+
+ @Override
+ public Vec3Comparable getIntermediateWithXValue(Vec3 vec, double x) {
+ return new Vec3Comparable(super.getIntermediateWithXValue(vec, x));
+ }
+
+ @Override
+ public Vec3Comparable getIntermediateWithYValue(Vec3 vec, double y) {
+ return new Vec3Comparable(super.getIntermediateWithYValue(vec, y));
+ }
+
+ @Override
+ public Vec3Comparable getIntermediateWithZValue(Vec3 vec, double z) {
+ return new Vec3Comparable(super.getIntermediateWithZValue(vec, z));
+ }
+
+ @Override
+ public Vec3Comparable rotatePitch(float pitch) {
+ return new Vec3Comparable(super.rotatePitch(pitch));
+ }
+
+ @Override
+ public Vec3Comparable rotateYaw(float yaw) {
+ return new Vec3Comparable(super.rotateYaw(yaw));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (!(other instanceof Vec3Comparable)) {
+ return false;
+ } else {
+ Vec3Comparable vec3c = (Vec3Comparable) other;
+ return this.xCoord == vec3c.xCoord && this.yCoord == vec3c.yCoord && this.zCoord == vec3c.zCoord;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ long bits = 1L;
+ bits = 31L * bits + doubleToLongBits(xCoord);
+ bits = 31L * bits + doubleToLongBits(yCoord);
+ bits = 31L * bits + doubleToLongBits(zCoord);
+ return (int) (bits ^ (bits >> 32));
+ }
+
+ public int compareTo(Vec3Comparable other) {
+ return this.yCoord == other.yCoord ?
+ (this.zCoord == other.zCoord ?
+ (int)(this.xCoord - other.xCoord)
+ : (int)(this.zCoord - other.zCoord))
+ : (int)(this.yCoord - other.yCoord);
+ }
+
+ public boolean signumEquals(Vec3 other) {
+ return Math.signum(xCoord) == Math.signum(other.xCoord) &&
+ Math.signum(yCoord) == Math.signum(other.yCoord) &&
+ Math.signum(zCoord) == Math.signum(other.zCoord);
+ }
+
+ private static long doubleToLongBits(double d) {
+ return d == 0.0 ? 0L : Double.doubleToLongBits(d);
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalMetalDetectorSolver.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalMetalDetectorSolver.java
index 27c334ad..f43ebda2 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalMetalDetectorSolver.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalMetalDetectorSolver.java
@@ -1,6 +1,7 @@
package io.github.moulberry.notenoughupdates.miscfeatures;
import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import io.github.moulberry.notenoughupdates.core.util.Vec3Comparable;
import io.github.moulberry.notenoughupdates.core.util.render.RenderUtils;
import io.github.moulberry.notenoughupdates.options.customtypes.NEUDebugFlag;
import io.github.moulberry.notenoughupdates.util.NEUDebugLogger;
@@ -11,7 +12,6 @@ import net.minecraft.util.BlockPos;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.IChatComponent;
-import net.minecraft.util.Vec3;
import net.minecraft.util.Vec3i;
import java.util.Arrays;
@@ -22,25 +22,35 @@ import java.util.List;
import java.util.stream.Collectors;
public class CrystalMetalDetectorSolver {
+ enum SolutionState {
+ NOT_STARTED,
+ MULTIPLE,
+ MULTIPLE_KNOWN,
+ FOUND,
+ FOUND_KNOWN,
+ FAILED,
+ INVALID,
+ }
+
private static final Minecraft mc = Minecraft.getMinecraft();
- private static BlockPos prevPlayerPos;
- private static double prevDistToTreasure = 0;
+ private static Vec3Comparable prevPlayerPos;
+ private static double prevDistToTreasure;
private static HashSet<BlockPos> possibleBlocks = new HashSet<>();
- private static final HashMap<BlockPos, Double> evaluatedPlayerPositions = new HashMap<>();
- private static BlockPos blockPosIfLastSolutionInvalid;
- private static Boolean chestRecentlyFound = false;
- private static long chestLastFoundMillis = 0;
+ private static final HashMap<Vec3Comparable, Double> evaluatedPlayerPositions = new HashMap<>();
+ private static boolean chestRecentlyFound;
+ private static long chestLastFoundMillis;
private static final HashSet<BlockPos> openedChestPositions = new HashSet<>();
// Keeper and Mines of Divan center location info
private static Vec3i minesCenter;
- private static boolean visitKeeperMessagePrinted = false;
- private static String KEEPER_OF_STRING = "Keeper of ";
- private static String DIAMOND_STRING = "diamond";
- private static String LAPIS_STRING = "lapis";
- private static String EMERALD_STRING = "emerald";
- private static String GOLD_STRING = "gold";
+ private static boolean debugDoNotUseCenter = false;
+ private static boolean visitKeeperMessagePrinted;
+ private static final String KEEPER_OF_STRING = "Keeper of ";
+ private static final String DIAMOND_STRING = "diamond";
+ private static final String LAPIS_STRING = "lapis";
+ private static final String EMERALD_STRING = "emerald";
+ private static final String GOLD_STRING = "gold";
private static final HashMap<String, Vec3i> keeperOffsets = new HashMap<String, Vec3i>() {{
put(DIAMOND_STRING, new Vec3i(33,0,3));
put(LAPIS_STRING, new Vec3i(-33,0,-3));
@@ -94,6 +104,14 @@ public class CrystalMetalDetectorSolver {
-9896946827286L // x=-37, y=-21, z=-22
));
+ static Predicate<BlockPos> treasureAllowedPredicate = CrystalMetalDetectorSolver::treasureAllowed;
+ static SolutionState currentState = SolutionState.NOT_STARTED;
+ static SolutionState previousState = SolutionState.NOT_STARTED;
+
+ public interface Predicate<BlockPos> {
+ boolean check(BlockPos blockPos);
+ }
+
public static void process(IChatComponent message) {
if (SBInfo.getInstance().getLocation() == null ||
!NotEnoughUpdates.INSTANCE.config.mining.metalDetectorEnabled ||
@@ -102,7 +120,7 @@ public class CrystalMetalDetectorSolver {
return;
}
- locateMinesCenterIfNeeded();
+ boolean centerNewlyDiscovered = locateMinesCenterIfNeeded();
double distToTreasure = Double.parseDouble(message
.getUnformattedText()
@@ -122,20 +140,64 @@ public class CrystalMetalDetectorSolver {
chestRecentlyFound = false;
}
- if (prevDistToTreasure == distToTreasure &&
- prevPlayerPos.equals(mc.thePlayer.getPosition()) &&
- !evaluatedPlayerPositions.keySet().contains(mc.thePlayer.getPosition())) {
+ SolutionState originalState = currentState;
+ int originalCount = possibleBlocks.size();
+ Vec3Comparable adjustedPlayerPos = getPlayerPosAdjustedForEyeHeight();
+ findPossibleSolutions(distToTreasure, adjustedPlayerPos, centerNewlyDiscovered);
+ if (currentState != originalState || originalCount != possibleBlocks.size()) {
+ switch (currentState) {
+ case FOUND_KNOWN:
+ NEUDebugLogger.log(NEUDebugFlag.METAL, "Known location identified.");
+ // falls through
+ case FOUND:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "[NEU] Found solution."));
+ if (NotEnoughUpdates.INSTANCE.config.hidden.debugFlags.contains(NEUDebugFlag.METAL) &&
+ (previousState == SolutionState.INVALID || previousState == SolutionState.FAILED)) {
+ NEUDebugLogger.log(NEUDebugFlag.METAL,
+ EnumChatFormatting.AQUA + "Solution coordinates: " +
+ EnumChatFormatting.WHITE + possibleBlocks.iterator().next().toString());
+ }
+ break;
+ case INVALID:
+ mc.thePlayer.addChatMessage(new ChatComponentText(
+ EnumChatFormatting.RED + "[NEU] Previous solution is invalid."));
+ logDiagnosticData(false);
+ resetSolution(false);
+ break;
+ case FAILED:
+ mc.thePlayer.addChatMessage(new ChatComponentText(
+ EnumChatFormatting.RED + "[NEU] Failed to find a solution."));
+ logDiagnosticData(false);
+ resetSolution(false);
+ break;
+ case MULTIPLE_KNOWN:
+ NEUDebugLogger.log(NEUDebugFlag.METAL, "Multiple known locations identified:");
+ // falls through
+ case MULTIPLE:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "[NEU] Need another position to find solution. Possible blocks: " + possibleBlocks.size()));
+ break;
+ default:
+ throw new IllegalStateException("Metal detector is in invalid state");
+ }
+ }
+ }
+
+ static void findPossibleSolutions(double distToTreasure, Vec3Comparable playerPos, boolean centerNewlyDiscovered) {
+ if (prevDistToTreasure == distToTreasure && prevPlayerPos.equals(playerPos) &&
+ !evaluatedPlayerPositions.containsKey(playerPos)) {
+ evaluatedPlayerPositions.put(playerPos, distToTreasure);
if (possibleBlocks.size() == 0) {
- evaluatedPlayerPositions.put(mc.thePlayer.getPosition(), distToTreasure);
for (int zOffset = (int) Math.floor(-distToTreasure); zOffset <= Math.ceil(distToTreasure); zOffset++) {
for (int y = 65; y <= 75; y++) {
double calculatedDist = 0;
int xOffset = 0;
while (calculatedDist < distToTreasure) {
- BlockPos pos = new BlockPos(Math.floor(mc.thePlayer.posX) + xOffset,
- y, Math.floor(mc.thePlayer.posZ) + zOffset);
- calculatedDist = getPlayerPos().distanceTo(new Vec3(pos).addVector(0D, 1D, 0D));
- if (round(calculatedDist, 1) == distToTreasure && treasureAllowed(pos)) {
+ BlockPos pos = new BlockPos(Math.floor(playerPos.xCoord) + xOffset,
+ y, Math.floor(playerPos.zCoord) + zOffset
+ );
+ calculatedDist = playerPos.distanceTo(new Vec3Comparable(pos).addVector(0D, 1D, 0D));
+ if (round(calculatedDist, 1) == distToTreasure && treasureAllowedPredicate.check(pos)) {
possibleBlocks.add(pos);
}
xOffset++;
@@ -143,10 +205,11 @@ public class CrystalMetalDetectorSolver {
xOffset = 0;
calculatedDist = 0;
while (calculatedDist < distToTreasure) {
- BlockPos pos = new BlockPos(Math.floor(mc.thePlayer.posX) - xOffset,
- y, Math.floor(mc.thePlayer.posZ) + zOffset);
- calculatedDist = getPlayerPos().distanceTo(new Vec3(pos).addVector(0D, 1D, 0D));
- if (round(calculatedDist, 1) == distToTreasure && treasureAllowed(pos)) {
+ BlockPos pos = new BlockPos(Math.floor(playerPos.xCoord) - xOffset,
+ y, Math.floor(playerPos.zCoord) + zOffset
+ );
+ calculatedDist = playerPos.distanceTo(new Vec3Comparable(pos).addVector(0D, 1D, 0D));
+ if (round(calculatedDist, 1) == distToTreasure && treasureAllowedPredicate.check(pos)) {
possibleBlocks.add(pos);
}
xOffset++;
@@ -154,48 +217,33 @@ public class CrystalMetalDetectorSolver {
}
}
- checkAndDisplaySolutionState();
+ updateSolutionState();
} else if (possibleBlocks.size() != 1) {
- evaluatedPlayerPositions.put(mc.thePlayer.getPosition().getImmutable(), distToTreasure);
HashSet<BlockPos> temp = new HashSet<>();
for (BlockPos pos : possibleBlocks) {
- if (round(getPlayerPos().distanceTo(new Vec3(pos).addVector(0D, 1D, 0D)), 1) == distToTreasure) {
+ if (round(playerPos.distanceTo(new Vec3Comparable(pos).addVector(0D, 1D, 0D)), 1) == distToTreasure) {
temp.add(pos);
}
}
possibleBlocks = temp;
- checkAndDisplaySolutionState();
+ updateSolutionState();
} else {
BlockPos pos = possibleBlocks.iterator().next();
- if (Math.abs(distToTreasure - (getPlayerPos().distanceTo(new Vec3(pos)))) > 5) {
- mc.thePlayer.addChatMessage(new ChatComponentText(
- EnumChatFormatting.RED + "[NEU] Previous solution is invalid."));
- blockPosIfLastSolutionInvalid = pos.getImmutable();
- logDiagnosticData(false);
- resetSolution(false);
+ if (Math.abs(distToTreasure - (playerPos.distanceTo(new Vec3Comparable(pos)))) > 5) {
+ currentState = SolutionState.INVALID;
}
}
+ } else if (centerNewlyDiscovered && possibleBlocks.size() > 1) {
+ updateSolutionState();
}
- prevPlayerPos = mc.thePlayer.getPosition().getImmutable();
+ prevPlayerPos = playerPos;
prevDistToTreasure = distToTreasure;
}
- private static void checkForSingleKnownLocationMatch() {
- if (minesCenter == BlockPos.NULL_VECTOR || possibleBlocks.size() < 2) {
- return;
- }
-
- HashSet<BlockPos> temp = possibleBlocks.stream()
- .filter(block -> knownChestOffsets.contains(block.subtract(minesCenter).toLong()))
- .collect(Collectors.toCollection(HashSet::new));
- if (temp.size() == 1) {
- possibleBlocks = temp;
- NEUDebugLogger.log(NEUDebugFlag.METAL, "Known location identified.");
- } else if (temp.size() > 1) {
- NEUDebugLogger.log(NEUDebugFlag.METAL, temp.size() + " known locations identified:");
- }
+ public static void setDebugDoNotUseCenter(boolean val) {
+ debugDoNotUseCenter = val;
}
private static String getFriendlyBlockPositions(Collection<BlockPos> positions) {
@@ -206,10 +254,13 @@ public class CrystalMetalDetectorSolver {
StringBuilder sb = new StringBuilder();
sb.append("\n");
for (BlockPos blockPos : positions) {
- sb.append("Absolute: " + blockPos.toString());
+ sb.append("Absolute: ");
+ sb.append(blockPos.toString());
if (minesCenter != Vec3i.NULL_VECTOR) {
BlockPos relativeOffset = blockPos.subtract(minesCenter);
- sb.append(", Relative: " + relativeOffset.toString() + " (" + relativeOffset.toLong() + ")");
+ sb.append(", Relative: ");
+ sb.append(relativeOffset.toString() );
+ sb.append(" (" + relativeOffset.toLong() + ")");
}
sb.append("\n");
}
@@ -217,22 +268,23 @@ public class CrystalMetalDetectorSolver {
return sb.toString();
}
- private static String getFriendlyEvaluatedPositions(HashMap<BlockPos, Double> positions) {
- if (!NEUDebugLogger.isFlagEnabled(NEUDebugFlag.METAL) || positions.size() == 0) {
+ private static String getFriendlyEvaluatedPositions() {
+ if (!NEUDebugLogger.isFlagEnabled(NEUDebugFlag.METAL) || evaluatedPlayerPositions.size() == 0) {
return "";
}
StringBuilder sb = new StringBuilder();
sb.append("\n");
- for (BlockPos blockPos : positions.keySet()) {
- sb.append("Absolute: " + blockPos.toString());
+ for (Vec3Comparable vec : evaluatedPlayerPositions.keySet()) {
+ sb.append("Absolute: " + vec.toString());
if (minesCenter != Vec3i.NULL_VECTOR) {
- BlockPos relativeOffset = blockPos.subtract(minesCenter);
+ BlockPos positionBlockPos = new BlockPos(vec);
+ BlockPos relativeOffset = positionBlockPos.subtract(minesCenter);
sb.append(", Relative: " + relativeOffset.toString() + " (" + relativeOffset.toLong() + ")");
}
sb.append(" Distance: ");
- sb.append(positions.get(blockPos));
+ sb.append(evaluatedPlayerPositions.get(vec));
sb.append("\n");
}
@@ -240,101 +292,8 @@ public class CrystalMetalDetectorSolver {
return sb.toString();
}
- public static void logDiagnosticData(boolean outputAlways) {
- if (SBInfo.getInstance().getLocation() == null) {
- mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
- "[NEU] This command is not available outside SkyBlock"));
- return;
- }
-
- if (!NotEnoughUpdates.INSTANCE.config.mining.metalDetectorEnabled)
- {
- mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
- "[NEU] Metal Detector Solver is not enabled."));
- return;
- }
-
- if (!outputAlways && !NotEnoughUpdates.INSTANCE.config.hidden.debugFlags.contains(NEUDebugFlag.METAL)) {
- return;
- }
-
- boolean originalDebugFlag = !NotEnoughUpdates.INSTANCE.config.hidden.debugFlags.add(NEUDebugFlag.METAL);
-
- StringBuilder diagsMessage = new StringBuilder();
-
- diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("Mines Center: ");
- diagsMessage.append(EnumChatFormatting.WHITE);
- diagsMessage.append((minesCenter == Vec3i.NULL_VECTOR) ? "<NOT DISCOVERED>" : minesCenter.toString());
- diagsMessage.append("\n");
-
- diagsMessage.append("\n");
- diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("Previous Player Position: ");
- diagsMessage.append(EnumChatFormatting.WHITE);
- diagsMessage.append((prevPlayerPos == null) ? "<NONE>" : prevPlayerPos.toString());
- diagsMessage.append("\n");
-
- diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("Previous Distance To Treasure: ");
- diagsMessage.append(EnumChatFormatting.WHITE);
- diagsMessage.append((prevDistToTreasure == 0) ? "<NONE>" : prevDistToTreasure);
- diagsMessage.append("\n");
-
- diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("Last Solution Invalid Position: ");
- diagsMessage.append(EnumChatFormatting.WHITE);
- diagsMessage.append((blockPosIfLastSolutionInvalid == null) ? "<NONE>" : blockPosIfLastSolutionInvalid.toString());
- diagsMessage.append("\n");
-
- diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("Current Possible Blocks: ");
- diagsMessage.append(EnumChatFormatting.WHITE);
- diagsMessage.append(possibleBlocks.size());
- diagsMessage.append(getFriendlyBlockPositions(possibleBlocks));
- diagsMessage.append("\n");
-
- diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("Evaluated player positions: ");
- diagsMessage.append(EnumChatFormatting.WHITE);
- diagsMessage.append(evaluatedPlayerPositions.size());
- diagsMessage.append(getFriendlyEvaluatedPositions(evaluatedPlayerPositions));
- diagsMessage.append("\n");
-
- diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("Chest locations not on known list:\n");
- diagsMessage.append(EnumChatFormatting.WHITE);
- if (minesCenter != Vec3i.NULL_VECTOR) {
- HashSet<BlockPos> locationsNotOnKnownList = openedChestPositions
- .stream()
- .filter(block -> !knownChestOffsets.contains(block.subtract(minesCenter).toLong()))
- .map(block -> block.subtract(minesCenter))
- .collect(Collectors.toCollection(HashSet::new));
- if (locationsNotOnKnownList.size() > 0) {
- for (BlockPos blockPos : locationsNotOnKnownList) {
- diagsMessage.append(String.format(
- "%dL,\t\t// x=%d, y=%d, z=%d",
- blockPos.toLong(),
- blockPos.getX(),
- blockPos.getY(),
- blockPos.getZ()
- ));
- }
- }
- } else {
- diagsMessage.append("<REQUIRES MINES CENTER>");
- }
-
- NEUDebugLogger.log(NEUDebugFlag.METAL, diagsMessage.toString());
-
- if (!originalDebugFlag) {
- NotEnoughUpdates.INSTANCE.config.hidden.debugFlags.remove(NEUDebugFlag.METAL);
- }
- }
-
public static void resetSolution(Boolean chestFound) {
if (chestFound) {
- blockPosIfLastSolutionInvalid = null;
prevPlayerPos = null;
prevDistToTreasure = 0;
if (possibleBlocks.size() == 1) {
@@ -345,15 +304,18 @@ public class CrystalMetalDetectorSolver {
chestRecentlyFound = chestFound;
possibleBlocks.clear();
evaluatedPlayerPositions.clear();
+ previousState = currentState;
+ currentState = SolutionState.NOT_STARTED;
}
public static void initWorld() {
minesCenter = Vec3i.NULL_VECTOR;
visitKeeperMessagePrinted = false;
- blockPosIfLastSolutionInvalid = null;
openedChestPositions.clear();
+ chestLastFoundMillis = 0;
prevDistToTreasure = 0;
prevPlayerPos = null;
+ currentState = SolutionState.NOT_STARTED;
resetSolution(false);
}
@@ -377,15 +339,14 @@ public class CrystalMetalDetectorSolver {
}
}
- private static void locateMinesCenterIfNeeded() {
+ private static boolean locateMinesCenterIfNeeded() {
if (minesCenter != Vec3i.NULL_VECTOR) {
- return;
+ return false;
}
List<EntityArmorStand> keeperEntities = mc.theWorld.getEntities(EntityArmorStand.class, (entity) -> {
if (!entity.hasCustomName()) return false;
- if (entity.getCustomNameTag().contains(KEEPER_OF_STRING)) return true;
- return false;
+ return entity.getCustomNameTag().contains(KEEPER_OF_STRING);
});
if (keeperEntities.size() == 0) {
@@ -394,7 +355,7 @@ public class CrystalMetalDetectorSolver {
"[NEU] Approach a Keeper while holding the metal detector to enable faster treasure hunting."));
visitKeeperMessagePrinted = true;
}
- return;
+ return false;
}
EntityArmorStand keeperEntity = keeperEntities.get(0);
@@ -407,6 +368,11 @@ public class CrystalMetalDetectorSolver {
EnumChatFormatting.WHITE + minesCenter.toString());
mc.thePlayer.addChatMessage(new ChatComponentText(
EnumChatFormatting.YELLOW + "[NEU] Faster treasure hunting is now enabled based on Keeper location."));
+ return true;
+ }
+
+ public static void setMinesCenter(BlockPos center) {
+ minesCenter = center;
}
private static double round(double value, int precision) {
@@ -414,42 +380,174 @@ public class CrystalMetalDetectorSolver {
return (double) Math.round(value * scale) / scale;
}
- private static void checkAndDisplaySolutionState() {
+ private static void updateSolutionState() {
+ previousState = currentState;
+
if (possibleBlocks.size() == 0) {
- mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "[NEU] Failed to find a solution."));
- logDiagnosticData(false);
- resetSolution(false);
+ currentState = SolutionState.FAILED;
return;
}
- checkForSingleKnownLocationMatch();
- if (possibleBlocks.size() > 1) {
- mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
- "[NEU] Need another position to find solution. Possible blocks: " + possibleBlocks.size()));
- } else {
- mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "[NEU] Found solution."));
+ if (possibleBlocks.size() == 1) {
+ currentState = SolutionState.FOUND;
+ return;
}
+
+ // Narrow solutions using known locations if the mines center is known
+ if (minesCenter.equals(BlockPos.NULL_VECTOR) || debugDoNotUseCenter) {
+ currentState = SolutionState.MULTIPLE;
+ return;
+ }
+
+ HashSet<BlockPos> temp =
+ possibleBlocks.stream()
+ .filter(block -> knownChestOffsets.contains(block.subtract(minesCenter).toLong()))
+ .collect(Collectors.toCollection(HashSet::new));
+ if (temp.size() == 0) {
+ currentState = SolutionState.MULTIPLE;
+ return;
+ }
+
+ if (temp.size() == 1) {
+ possibleBlocks = temp;
+ currentState = SolutionState.FOUND_KNOWN;
+ return;
+
+ }
+
+ currentState = SolutionState.MULTIPLE_KNOWN;
+ }
+
+ public static BlockPos getSolution() {
+ if (CrystalMetalDetectorSolver.possibleBlocks.size() != 1) {
+ return BlockPos.ORIGIN;
+ }
+
+ return CrystalMetalDetectorSolver.possibleBlocks.stream().iterator().next();
}
- private static Vec3 getPlayerPos() {
- return new Vec3(
+ private static Vec3Comparable getPlayerPosAdjustedForEyeHeight() {
+ return new Vec3Comparable(
mc.thePlayer.posX,
mc.thePlayer.posY + (mc.thePlayer.getEyeHeight() - mc.thePlayer.getDefaultEyeHeight()),
mc.thePlayer.posZ
);
}
- private static boolean treasureAllowed(BlockPos pos) {
- boolean airAbove = mc.theWorld.
- getBlockState(pos.add(0, 1, 0)).getBlock().getRegistryName().equals("minecraft:air");
- boolean allowedBlockType = mc.theWorld.getBlockState(pos).getBlock().getRegistryName().equals("minecraft:gold_block") ||
+ static boolean isKnownOffset(BlockPos pos) {
+ return knownChestOffsets.contains(pos.subtract(minesCenter).toLong());
+ }
+
+ static boolean isAllowedBlockType(BlockPos pos) {
+ return mc.theWorld.getBlockState(pos).getBlock().getRegistryName().equals("minecraft:gold_block") ||
mc.theWorld.getBlockState(pos).getBlock().getRegistryName().equals("minecraft:prismarine") ||
mc.theWorld.getBlockState(pos).getBlock().getRegistryName().equals("minecraft:chest") ||
mc.theWorld.getBlockState(pos).getBlock().getRegistryName().equals("minecraft:stained_glass") ||
mc.theWorld.getBlockState(pos).getBlock().getRegistryName().equals("minecraft:stained_glass_pane") ||
mc.theWorld.getBlockState(pos).getBlock().getRegistryName().equals("minecraft:wool") ||
mc.theWorld.getBlockState(pos).getBlock().getRegistryName().equals("minecraft:stained_hardened_clay");
- boolean knownOffset = knownChestOffsets.contains(pos.subtract(minesCenter).toLong());
- return airAbove & (knownOffset | allowedBlockType);
+ }
+
+ static boolean isAirAbove(BlockPos pos) {
+ return mc.theWorld.
+ getBlockState(pos.add(0, 1, 0)).getBlock().getRegistryName().equals("minecraft:air");
+ }
+
+ private static boolean treasureAllowed(BlockPos pos) {
+ boolean airAbove = isAirAbove(pos);
+ boolean allowedBlockType = isAllowedBlockType(pos);
+ return isKnownOffset(pos) || (airAbove && allowedBlockType);
+ }
+
+ static private String getDiagnosticMessage() {
+ StringBuilder diagsMessage = new StringBuilder();
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Mines Center: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append((minesCenter.equals(Vec3i.NULL_VECTOR)) ? "<NOT DISCOVERED>" : minesCenter.toString());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Current Solution State: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(currentState.name());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Previous Solution State: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(previousState.name());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Previous Player Position: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append((prevPlayerPos == null) ? "<NONE>" : prevPlayerPos.toString());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Previous Distance To Treasure: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append((prevDistToTreasure == 0) ? "<NONE>" : prevDistToTreasure);
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Current Possible Blocks: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(possibleBlocks.size());
+ diagsMessage.append(getFriendlyBlockPositions(possibleBlocks));
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Evaluated player positions: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(evaluatedPlayerPositions.size());
+ diagsMessage.append(getFriendlyEvaluatedPositions());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Chest locations not on known list:\n");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ if (minesCenter != Vec3i.NULL_VECTOR) {
+ HashSet<BlockPos> locationsNotOnKnownList = openedChestPositions
+ .stream()
+ .filter(block -> !knownChestOffsets.contains(block.subtract(minesCenter).toLong()))
+ .map(block -> block.subtract(minesCenter))
+ .collect(Collectors.toCollection(HashSet::new));
+ if (locationsNotOnKnownList.size() > 0) {
+ for (BlockPos blockPos : locationsNotOnKnownList) {
+ diagsMessage.append(String.format(
+ "%dL,\t\t// x=%d, y=%d, z=%d",
+ blockPos.toLong(),
+ blockPos.getX(),
+ blockPos.getY(),
+ blockPos.getZ()
+ ));
+ }
+ }
+ } else {
+ diagsMessage.append("<REQUIRES MINES CENTER>");
+ }
+
+ return diagsMessage.toString();
+ }
+
+ public static void logDiagnosticData(boolean outputAlways) {
+ if (!SBInfo.getInstance().checkForSkyblockLocation()) {
+ return;
+ }
+
+ if (!NotEnoughUpdates.INSTANCE.config.mining.metalDetectorEnabled)
+ {
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Metal Detector Solver is not enabled."));
+ return;
+ }
+
+ boolean metalDebugFlagSet = NotEnoughUpdates.INSTANCE.config.hidden.debugFlags.contains(NEUDebugFlag.METAL);
+ if (outputAlways || metalDebugFlagSet) {
+ NEUDebugLogger.logAlways(getDiagnosticMessage());
+ }
}
}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalWishingCompassSolver.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalWishingCompassSolver.java
index 25f39c3a..9a950e7f 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalWishingCompassSolver.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CrystalWishingCompassSolver.java
@@ -2,27 +2,67 @@ package io.github.moulberry.notenoughupdates.miscfeatures;
import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
import io.github.moulberry.notenoughupdates.core.util.Line;
+import io.github.moulberry.notenoughupdates.core.util.Vec3Comparable;
+import io.github.moulberry.notenoughupdates.options.NEUConfig;
import io.github.moulberry.notenoughupdates.options.customtypes.NEUDebugFlag;
import io.github.moulberry.notenoughupdates.util.NEUDebugLogger;
import io.github.moulberry.notenoughupdates.util.SBInfo;
+import io.github.moulberry.notenoughupdates.util.Utils;
import net.minecraft.client.Minecraft;
import net.minecraft.event.ClickEvent;
-import net.minecraft.event.HoverEvent;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.BlockPos;
import net.minecraft.util.ChatComponentText;
-import net.minecraft.util.ChatStyle;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.EnumParticleTypes;
-import net.minecraft.util.Vec3;
+import net.minecraft.util.Vec3i;
+import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.function.BooleanSupplier;
+import java.util.function.LongSupplier;
+
public class CrystalWishingCompassSolver {
+ enum SolverState {
+ NOT_STARTED,
+ PROCESSING_FIRST_USE,
+ NEED_SECOND_COMPASS,
+ PROCESSING_SECOND_USE,
+ SOLVED,
+ FAILED_EXCEPTION,
+ FAILED_TIMEOUT_NO_REPEATING,
+ FAILED_TIMEOUT_NO_PARTICLES,
+ FAILED_INTERSECTION_CALCULATION,
+ FAILED_INVALID_SOLUTION,
+ }
+
+ enum CompassTarget {
+ GOBLIN_QUEEN,
+ GOBLIN_KING,
+ BAL,
+ JUNGLE_TEMPLE,
+ ODAWA,
+ PRECURSOR_CITY,
+ MINES_OF_DIVAN,
+ CRYSTAL_NUCLEUS,
+ }
+
+ enum Crystal {
+ AMBER,
+ AMETHYST,
+ JADE,
+ SAPPHIRE,
+ TOPAZ,
+ }
+
private static final CrystalWishingCompassSolver INSTANCE = new CrystalWishingCompassSolver();
public static CrystalWishingCompassSolver getInstance() {
return INSTANCE;
@@ -31,40 +71,72 @@ public class CrystalWishingCompassSolver {
private static final Minecraft mc = Minecraft.getMinecraft();
private static boolean isSkytilsPresent = false;
- // Crystal Nucleus unbreakable blocks, area coordinates reported by Hypixel server are slightly different
- private static final AxisAlignedBB NUCLEUS_BB = new AxisAlignedBB(463, 63, 460, 563, 181, 564);
- private static final double MAX_COMPASS_PARTICLE_SPREAD = 16;
-
- private static BlockPos prevPlayerPos;
- private long compassUsedMillis = 0;
- private Vec3 firstParticle = null;
- private Vec3 lastParticle = null;
- private double lastParticleDistanceFromFirst = 0;
- private Line firstCompassLine = null;
- private Line secondCompassLine = null;
- private Vec3 solution = null;
- private Line solutionIntersectionLine = null;
-
- private void resetForNewCompass() {
- compassUsedMillis = 0;
- firstParticle = null;
- lastParticle = null;
- lastParticleDistanceFromFirst = 0;
+ // NOTE: There is a small set of breakable blocks above the nucleus at Y > 181. While this zone is reported
+ // as the Crystal Nucleus by Hypixel, for wishing compass purposes it is in the appropriate quadrant.
+ private static final AxisAlignedBB NUCLEUS_BB = new AxisAlignedBB(462, 63, 461, 564, 181, 565);
+ private static final AxisAlignedBB HOLLOWS_BB = new AxisAlignedBB(201, 30, 201, 824, 189, 824);
+ private static final AxisAlignedBB PRECURSOR_REMNANTS_BB = new AxisAlignedBB(513, 64, 513, 824, 189, 824);
+ private static final AxisAlignedBB MITHRIL_DEPOSITS_BB = new AxisAlignedBB(513, 64, 201, 824, 189, 512);
+ private static final AxisAlignedBB GOBLIN_HOLDOUT_BB = new AxisAlignedBB(201, 64, 513, 512, 189, 824);
+ private static final AxisAlignedBB JUNGLE_BB = new AxisAlignedBB(201, 64, 201, 512, 189, 512);
+ private static final AxisAlignedBB MAGMA_FIELDS_BB = new AxisAlignedBB(201, 30, 201, 824, 63, 824);
+ private static final double MAX_DISTANCE_BETWEEN_PARTICLES = 0.6;
+ private static final double MAX_DISTANCE_FROM_USE_TO_FIRST_PARTICLE = 9.0;
+
+ // 64.0 is an arbitrary value but seems to work well
+ private static final double MINIMUM_DISTANCE_SQ_BETWEEN_COMPASSES = 64.0;
+
+ // All particles typically arrive in < 3500, so 5000 should be enough buffer
+ public static final long ALL_PARTICLES_MAX_MILLIS = 5000L;
+
+ public LongSupplier currentTimeMillis = System::currentTimeMillis;
+ public BooleanSupplier kingsScentPresent = this::isKingsScentPresent;
+ public BooleanSupplier keyInInventory = this::isKeyInInventory;
+ public interface CrystalEnumSetSupplier {
+ EnumSet<Crystal> getAsCrystalEnumSet();
+ }
+ public CrystalEnumSetSupplier foundCrystals = this::getFoundCrystals;
+
+ private SolverState solverState;
+ private Compass firstCompass;
+ private Compass secondCompass;
+ private Line solutionIntersectionLine;
+ private EnumSet<CompassTarget> possibleTargets;
+ private Vec3Comparable solution;
+ private Vec3Comparable originalSolution;
+ private EnumSet<CompassTarget> solutionPossibleTargets;
+
+ public SolverState getSolverState() {
+ return solverState;
+ }
+
+ public Vec3i getSolutionCoords() {
+ return new Vec3i(solution.xCoord, solution.yCoord, solution.zCoord);
+ }
+
+ public EnumSet<CompassTarget> getPossibleTargets() {
+ return possibleTargets;
}
private void resetForNewTarget() {
NEUDebugLogger.log(NEUDebugFlag.WISHING,"Resetting for new target");
- resetForNewCompass();
- firstCompassLine = null;
- secondCompassLine = null;
+ solverState = SolverState.NOT_STARTED;
+ firstCompass = null;
+ secondCompass = null;
solutionIntersectionLine = null;
- prevPlayerPos = null;
+ possibleTargets = null;
solution = null;
+ originalSolution = null;
+ solutionPossibleTargets = null;
+ }
+
+ public void initWorld() {
+ resetForNewTarget();
}
@SubscribeEvent
public void onWorldLoad(WorldEvent.Unload event) {
- resetForNewTarget();
+ initWorld();
isSkytilsPresent = Loader.isModLoaded("skytils");
}
@@ -90,28 +162,101 @@ public class CrystalWishingCompassSolver {
return;
}
- try {
- if (isSolved()) {
- resetForNewTarget();
- }
+ BlockPos playerPos = mc.thePlayer.getPosition().getImmutable();
- // 64.0 is an arbitrary value but seems to work well
- if (prevPlayerPos != null && prevPlayerPos.distanceSq(mc.thePlayer.getPosition()) < 64.0) {
- mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
- "[NEU] Move a little further before using the wishing compass again."));
- event.setCanceled(true);
- return;
+ try {
+ HandleCompassResult result = handleCompassUse(playerPos);
+ switch (result) {
+ case SUCCESS:
+ return;
+ case STILL_PROCESSING_PRIOR_USE:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "[NEU] Wait a little longer before using the wishing compass again."));
+ event.setCanceled(true);
+ break;
+ case LOCATION_TOO_CLOSE:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "[NEU] Move a little further before using the wishing compass again."));
+ event.setCanceled(true);
+ break;
+ case POSSIBLE_TARGETS_CHANGED:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "[NEU] Possible wishing compass targets have changed. Solver has been reset."));
+ event.setCanceled(true);
+ break;
+ case NO_PARTICLES_FOR_PREVIOUS_COMPASS:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "[NEU] No particles detected for prior compass use. Need another position to solve."));
+ break;
+ case PLAYER_IN_NUCLEUS:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "[NEU] Wishing compass must be used outside the nucleus for accurate results."));
+ event.setCanceled(true);
+ break;
+ default:
+ throw new IllegalStateException("Unexpected wishing compass solver state: \n" + getDiagnosticMessage());
}
-
- prevPlayerPos = mc.thePlayer.getPosition().getImmutable();
- compassUsedMillis = System.currentTimeMillis();
} catch (Exception e) {
mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
"[NEU] Error processing wishing compass action - see log for details"));
e.printStackTrace();
+ event.setCanceled(true);
+ solverState = SolverState.FAILED_EXCEPTION;
}
}
+ public HandleCompassResult handleCompassUse(BlockPos playerPos) {
+ long lastCompassUsedMillis = 0;
+ switch (solverState) {
+ case PROCESSING_SECOND_USE:
+ if (secondCompass != null) {
+ lastCompassUsedMillis = secondCompass.whenUsedMillis;
+ }
+ case PROCESSING_FIRST_USE:
+ if (lastCompassUsedMillis == 0 && firstCompass != null) {
+ lastCompassUsedMillis = firstCompass.whenUsedMillis;
+ }
+ if (lastCompassUsedMillis != 0 &&
+ (currentTimeMillis.getAsLong() > lastCompassUsedMillis + ALL_PARTICLES_MAX_MILLIS)) {
+ return HandleCompassResult.NO_PARTICLES_FOR_PREVIOUS_COMPASS;
+ }
+
+ return HandleCompassResult.STILL_PROCESSING_PRIOR_USE;
+ case SOLVED:
+ case FAILED_EXCEPTION:
+ case FAILED_TIMEOUT_NO_REPEATING:
+ case FAILED_TIMEOUT_NO_PARTICLES:
+ case FAILED_INTERSECTION_CALCULATION:
+ case FAILED_INVALID_SOLUTION:
+ resetForNewTarget();
+ // falls through, NOT_STARTED is the state when resetForNewTarget returns
+ case NOT_STARTED:
+ if (NUCLEUS_BB.isVecInside(new Vec3Comparable(playerPos.getX(), playerPos.getY(), playerPos.getZ()))) {
+ return HandleCompassResult.PLAYER_IN_NUCLEUS;
+ }
+
+ firstCompass = new Compass(playerPos, currentTimeMillis.getAsLong());
+ solverState = SolverState.PROCESSING_FIRST_USE;
+ possibleTargets = calculatePossibleTargets(playerPos);
+ return HandleCompassResult.SUCCESS;
+ case NEED_SECOND_COMPASS:
+ if (firstCompass.whereUsed.distanceSq(playerPos) < MINIMUM_DISTANCE_SQ_BETWEEN_COMPASSES) {
+ return HandleCompassResult.LOCATION_TOO_CLOSE;
+ }
+
+ if (!possibleTargets.equals(calculatePossibleTargets(playerPos))) {
+ resetForNewTarget();
+ return HandleCompassResult.POSSIBLE_TARGETS_CHANGED;
+ }
+
+ secondCompass = new Compass(playerPos, currentTimeMillis.getAsLong());
+ solverState = SolverState.PROCESSING_SECOND_USE;
+ return HandleCompassResult.SUCCESS;
+ }
+
+ throw new IllegalStateException("Unexpected compass state" );
+ }
+
/*
* Processes particles if the wishing compass was used within the last 5 seconds.
*
@@ -142,188 +287,701 @@ public class CrystalWishingCompassSolver {
) {
if (!NotEnoughUpdates.INSTANCE.config.mining.wishingCompassSolver ||
particleType != EnumParticleTypes.VILLAGER_HAPPY ||
- !SBInfo.getInstance().getLocation().equals("crystal_hollows") ||
- isSolved() ||
- System.currentTimeMillis() - compassUsedMillis > 5000) {
+ !SBInfo.getInstance().getLocation().equals("crystal_hollows")) {
return;
}
try {
- Vec3 particleVec = new Vec3(x, y, z);
- if (firstParticle == null) {
- firstParticle = particleVec;
- return;
+ SolverState originalSolverState = solverState;
+ solveUsingParticle(x, y, z, currentTimeMillis.getAsLong());
+ if (solverState != originalSolverState) {
+ switch (solverState) {
+ case SOLVED:
+ showSolution();
+ break;
+ case FAILED_EXCEPTION:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Unable to determine wishing compass target."));
+ logDiagnosticData(false);
+ break;
+ case FAILED_TIMEOUT_NO_REPEATING:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Timed out waiting for repeat set of compass particles."));
+ logDiagnosticData(false);
+ break;
+ case FAILED_TIMEOUT_NO_PARTICLES:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Timed out waiting for compass particles."));
+ logDiagnosticData(false);
+ break;
+ case FAILED_INTERSECTION_CALCULATION:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Unable to determine intersection of wishing compasses."));
+ logDiagnosticData(false);
+ break;
+ case FAILED_INVALID_SOLUTION:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Failed to find solution."));
+ logDiagnosticData(false);
+ break;
+ case NEED_SECOND_COMPASS:
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
+ "[NEU] Need another position to determine wishing compass target."));
+ break;
+ }
}
+ } catch (Exception e) {
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Exception while calculating wishing compass solution - see log for details"));
+ e.printStackTrace();
+ }
+ }
- double distanceFromFirst = particleVec.distanceTo(firstParticle);
- if (distanceFromFirst > MAX_COMPASS_PARTICLE_SPREAD) {
+ /**
+ *
+ * @param x Particle x coordinate
+ * @param y Particle y coordinate
+ * @param z Particle z coordinate
+ */
+ public void solveUsingParticle(double x, double y, double z, long currentTimeMillis) {
+ Compass currentCompass;
+ switch (solverState) {
+ case PROCESSING_FIRST_USE:
+ currentCompass = firstCompass;
+ break;
+ case PROCESSING_SECOND_USE:
+ currentCompass = secondCompass;
+ break;
+ default:
return;
- }
+ }
- if (distanceFromFirst >= lastParticleDistanceFromFirst) {
- lastParticleDistanceFromFirst = distanceFromFirst;
- lastParticle = particleVec;
+ currentCompass.processParticle(x, y, z, currentTimeMillis);
+ switch (currentCompass.compassState) {
+ case FAILED_TIMEOUT_NO_PARTICLES:
+ solverState = SolverState.FAILED_TIMEOUT_NO_PARTICLES;
+ return;
+ case FAILED_TIMEOUT_NO_REPEATING:
+ solverState = SolverState.FAILED_TIMEOUT_NO_REPEATING;
+ return;
+ case WAITING_FOR_FIRST_PARTICLE:
+ case COMPUTING_LAST_PARTICLE:
return;
+ case COMPLETED:
+ if (solverState == SolverState.NEED_SECOND_COMPASS) {
+ return;
+ }
+ if (solverState == SolverState.PROCESSING_FIRST_USE) {
+ solverState = SolverState.NEED_SECOND_COMPASS;
+ return;
+ }
+ break;
+ }
+
+ // First and Second compasses have completed
+ solutionIntersectionLine = firstCompass.line.getIntersectionLineSegment(secondCompass.line);
+
+ if (solutionIntersectionLine == null) {
+ solverState = SolverState.FAILED_INTERSECTION_CALCULATION;
+ return;
+ }
+
+ solution = new Vec3Comparable(solutionIntersectionLine.getMidpoint());
+
+ Vec3Comparable firstDirection = firstCompass.getDirection();
+ Vec3Comparable firstSolutionDirection = firstCompass.getDirectionTo(solution);
+ Vec3Comparable secondDirection = secondCompass.getDirection();
+ Vec3Comparable secondSolutionDirection = secondCompass.getDirectionTo(solution);
+ if (!firstDirection.signumEquals(firstSolutionDirection) ||
+ !secondDirection.signumEquals(secondSolutionDirection) ||
+ !HOLLOWS_BB.isVecInside(solution)) {
+ solverState = SolverState.FAILED_INVALID_SOLUTION;
+ return;
+ }
+
+ solutionPossibleTargets = getSolutionTargets(possibleTargets, solution);
+
+ // Adjust the Jungle Temple solution coordinates
+ if (solutionPossibleTargets.size() == 1 &&
+ solutionPossibleTargets.contains(CompassTarget.JUNGLE_TEMPLE)) {
+ originalSolution = solution;
+ solution = solution.addVector(-57, 36, -21);
+ }
+
+ solverState = SolverState.SOLVED;
+ }
+
+ private boolean isKeyInInventory()
+ {
+ for (ItemStack item : mc.thePlayer.inventory.mainInventory){
+ if (item != null && item.getDisplayName().contains("Jungle Key")) {
+ return true;
}
+ }
+ return false;
+ }
- // We get here when the second repetition of particles begins.
- // Since the second repetition overlaps with the last few particles
- // of the first repetition, the last particle we capture isn't truly the last.
- // But that's OK since subsequent particles will be on the same line.
- Line line = new Line(firstParticle, lastParticle);
- if (firstCompassLine == null) {
- firstCompassLine = line;
- resetForNewCompass();
- mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
- "[NEU] Need another position to determine wishing compass target."));
- return;
+ private boolean isKingsScentPresent()
+ {
+ return SBInfo.getInstance().footer.getUnformattedText().contains("King's Scent I");
+ }
+
+ private EnumSet<Crystal> getFoundCrystals() {
+ EnumSet<Crystal> foundCrystals = EnumSet.noneOf(Crystal.class);
+ NEUConfig.HiddenProfileSpecific perProfileConfig = NotEnoughUpdates.INSTANCE.config.getProfileSpecific();
+ if (perProfileConfig == null) return foundCrystals;
+ HashMap<String, Integer> crystals = perProfileConfig.crystals;
+ for (String crystalName : crystals.keySet()) {
+ Integer crystalState = crystals.get(crystalName);
+ if (crystalState != null && crystalState > 0) {
+ foundCrystals.add(Crystal.valueOf(crystalName.toUpperCase()));
}
+ }
- secondCompassLine = line;
- solutionIntersectionLine = firstCompassLine.getIntersectionLineSegment(secondCompassLine);
- if (solutionIntersectionLine == null) {
- mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
- "[NEU] Unable to determine wishing compass target."));
- logDiagnosticData(false);
- return;
+ return foundCrystals;
+ }
+
+ // Returns candidates based on seen Y coordinates and quadrants that
+ // are not adjacent to the solution's quadrant. If the solution is
+ // the nucleus then a copy of the original possible targets is
+ // returned.
+ //
+ // NOTE: Adjacent quadrant filtering could be improved based on
+ // structure sizes in the future to only allow a certain
+ // distance into the adjacent quadrant.
+ //
+ // |----------|------------|
+ // | Jungle | Mithril |
+ // | | Deposits |
+ // |----------|----------- |
+ // | Goblin | Precursor |
+ // | Holdout | Deposits |
+ // |----------|------------|
+ static public EnumSet<CompassTarget> getSolutionTargets(
+ EnumSet<CompassTarget> possibleTargets,
+ Vec3Comparable solution) {
+ EnumSet<CompassTarget> solutionPossibleTargets;
+ solutionPossibleTargets = possibleTargets.clone();
+
+ if (NUCLEUS_BB.isVecInside(solution)) {
+ return solutionPossibleTargets;
+ }
+
+ solutionPossibleTargets.remove(CompassTarget.CRYSTAL_NUCLEUS);
+
+ // Eliminate non-adjacent zones first
+ if (MITHRIL_DEPOSITS_BB.isVecInside(solution)) {
+ solutionPossibleTargets.remove(CompassTarget.GOBLIN_KING);
+ solutionPossibleTargets.remove(CompassTarget.GOBLIN_QUEEN);
+ } else if (PRECURSOR_REMNANTS_BB.isVecInside(solution)) {
+ solutionPossibleTargets.remove(CompassTarget.ODAWA);
+ solutionPossibleTargets.remove(CompassTarget.JUNGLE_TEMPLE);
+ } else if (GOBLIN_HOLDOUT_BB.isVecInside(solution)) {
+ solutionPossibleTargets.remove(CompassTarget.MINES_OF_DIVAN);
+ } else if (JUNGLE_BB.isVecInside(solution)) {
+ solutionPossibleTargets.remove(CompassTarget.PRECURSOR_CITY);
+ }
+
+ // If there's only 1 possible target then don't remove based
+ // on Y coordinates since assumptions about Y coordinates could
+ // be wrong.
+ if (solutionPossibleTargets.size() > 1) {
+ // Y coordinates are 43-70 from 11 samples
+ if (solutionPossibleTargets.contains(CompassTarget.BAL) &&
+ solution.yCoord > 72) {
+ solutionPossibleTargets.remove(CompassTarget.BAL);
}
- solution = solutionIntersectionLine.getMidpoint();
+ // Y coordinates are 93-157 from 10 samples, may be able to filter
+ // more based on the offset of the King within the structure
+ if (solutionPossibleTargets.contains(CompassTarget.GOBLIN_KING) &&
+ solution.yCoord < 64) {
+ solutionPossibleTargets.remove(CompassTarget.GOBLIN_KING);
+ }
- if (solution.distanceTo(firstParticle) < 8) {
- mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW +
- "[NEU] WARNING: Solution is likely incorrect."));
- logDiagnosticData(false);
- return;
+ // Y coordinates are 129-139 from 10 samples
+ if (solutionPossibleTargets.contains(CompassTarget.GOBLIN_QUEEN) &&
+ (solution.yCoord < 127 || solution.yCoord > 141)) {
+ solutionPossibleTargets.remove(CompassTarget.GOBLIN_QUEEN);
}
- showSolution();
- } catch (Exception e) {
- mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
- "[NEU] Exception while calculating wishing compass solution - see log for details"));
- e.printStackTrace();
+ // Y coordinates are 72-80 from 10 samples
+ if (solutionPossibleTargets.contains(CompassTarget.JUNGLE_TEMPLE) &&
+ (solution.yCoord < 70 || solution.yCoord > 82)) {
+ solutionPossibleTargets.remove(CompassTarget.JUNGLE_TEMPLE);
+ }
+
+ // Y coordinates are 110-128 from 3 samples, not enough data to use
+ if (solutionPossibleTargets.contains(CompassTarget.ODAWA) &&
+ solution.yCoord < 64) {
+ solutionPossibleTargets.remove(CompassTarget.ODAWA);
+ }
+
+ // Y coordinates are 122-129 from 8 samples
+ if (solutionPossibleTargets.contains(CompassTarget.PRECURSOR_CITY) &&
+ (solution.yCoord < 119 || solution.yCoord > 132)) {
+ solutionPossibleTargets.remove(CompassTarget.PRECURSOR_CITY);
+ }
+
+ // Y coordinates are 98-102 from 15 samples
+ if (solutionPossibleTargets.contains(CompassTarget.MINES_OF_DIVAN) &&
+ (solution.yCoord < 96 || solution.yCoord > 104)) {
+ solutionPossibleTargets.remove(CompassTarget.MINES_OF_DIVAN);
+ }
}
+
+ return solutionPossibleTargets;
}
- private boolean isSolved() {
- return solution != null;
+ private EnumSet<CompassTarget> calculatePossibleTargets(BlockPos playerPos) {
+ boolean targetsBasedOnZoneWithoutCrystal = false;
+ EnumSet<CompassTarget> candidateTargets = EnumSet.allOf(CompassTarget.class);
+ EnumSet<Crystal> foundCrystals = this.foundCrystals.getAsCrystalEnumSet();
+ Vec3Comparable playerPosVec = new Vec3Comparable(playerPos);
+
+ // If the current zone's crystal hasn't been found then remove all non-nucleus candidates other
+ // than the ones in the current zone. The one exception is that the king is kept when in the jungle
+ // since the compass can point to the king if odawa is missing (which often happens).
+ // The nucleus is kept since it can be returned if the structure for the current zone is missing.
+ if (GOBLIN_HOLDOUT_BB.isVecInside(playerPosVec) && !foundCrystals.contains(Crystal.AMBER)) {
+ candidateTargets.clear();
+ candidateTargets.add(CompassTarget.CRYSTAL_NUCLEUS);
+ candidateTargets.add(CompassTarget.GOBLIN_KING);
+ candidateTargets.add(CompassTarget.GOBLIN_QUEEN);
+ targetsBasedOnZoneWithoutCrystal = true;
+ }
+
+ if (JUNGLE_BB.isVecInside(playerPosVec) && !foundCrystals.contains(Crystal.AMETHYST)) {
+ candidateTargets.clear();
+ candidateTargets.add(CompassTarget.CRYSTAL_NUCLEUS);
+ candidateTargets.add(CompassTarget.ODAWA);
+ candidateTargets.add(CompassTarget.JUNGLE_TEMPLE);
+ if (!keyInInventory.getAsBoolean() && !kingsScentPresent.getAsBoolean()) {
+ // If Odawa is missing then the king may be returned
+ candidateTargets.add(CompassTarget.GOBLIN_KING);
+ }
+ targetsBasedOnZoneWithoutCrystal = true;
+ }
+
+ if (MITHRIL_DEPOSITS_BB.isVecInside(playerPosVec) && !foundCrystals.contains(Crystal.JADE)) {
+ candidateTargets.clear();
+ candidateTargets.add(CompassTarget.CRYSTAL_NUCLEUS);
+ candidateTargets.add(CompassTarget.MINES_OF_DIVAN);
+ targetsBasedOnZoneWithoutCrystal = true;
+ }
+
+ if (PRECURSOR_REMNANTS_BB.isVecInside(playerPosVec) && !foundCrystals.contains(Crystal.SAPPHIRE)) {
+ candidateTargets.clear();
+ candidateTargets.add(CompassTarget.CRYSTAL_NUCLEUS);
+ candidateTargets.add(CompassTarget.PRECURSOR_CITY);
+ targetsBasedOnZoneWithoutCrystal = true;
+ }
+
+ if (MAGMA_FIELDS_BB.isVecInside(playerPosVec) && !foundCrystals.contains(Crystal.TOPAZ)) {
+ candidateTargets.clear();
+ candidateTargets.add(CompassTarget.CRYSTAL_NUCLEUS);
+ candidateTargets.add(CompassTarget.BAL);
+ targetsBasedOnZoneWithoutCrystal = true;
+ }
+
+ if (!targetsBasedOnZoneWithoutCrystal) {
+ // Filter out crystal-based targets outside the current zone
+ if (foundCrystals.contains(Crystal.AMBER)) {
+ candidateTargets.remove(CompassTarget.GOBLIN_KING);
+ candidateTargets.remove(CompassTarget.GOBLIN_QUEEN);
+ }
+
+ if (foundCrystals.contains(Crystal.AMETHYST)) {
+ candidateTargets.remove(CompassTarget.ODAWA);
+ candidateTargets.remove(CompassTarget.JUNGLE_TEMPLE);
+ }
+
+ if (foundCrystals.contains(Crystal.JADE)) {
+ candidateTargets.remove(CompassTarget.MINES_OF_DIVAN);
+ }
+
+ if (foundCrystals.contains(Crystal.TOPAZ)) {
+ candidateTargets.remove(CompassTarget.BAL);
+ }
+
+ if (foundCrystals.contains(Crystal.SAPPHIRE)) {
+ candidateTargets.remove(CompassTarget.PRECURSOR_CITY);
+ }
+ }
+
+ candidateTargets.remove(kingsScentPresent.getAsBoolean() ? CompassTarget.GOBLIN_KING : CompassTarget.GOBLIN_QUEEN);
+ candidateTargets.remove(keyInInventory.getAsBoolean() ? CompassTarget.ODAWA : CompassTarget.JUNGLE_TEMPLE);
+
+ return candidateTargets;
}
- private void showSolution() {
- if (solution == null) return;
- String description = "[NEU] Wishing compass target: ";
- String coordsText = String.format("%.0f %.0f %.0f",
- solution.xCoord,
- solution.yCoord,
- solution.zCoord);
+ private String getFriendlyNameForCompassTarget(CompassTarget compassTarget) {
+ switch (compassTarget) {
+ case BAL: return EnumChatFormatting.RED + "Bal";
+ case ODAWA: return EnumChatFormatting.GREEN + "Odawa";
+ case JUNGLE_TEMPLE: return EnumChatFormatting.AQUA + "the " +
+ EnumChatFormatting.GREEN + "Jungle Temple";
+ case GOBLIN_KING: return EnumChatFormatting.GOLD + "King Yolkar";
+ case GOBLIN_QUEEN: return EnumChatFormatting.AQUA + "the " +
+ EnumChatFormatting.YELLOW + "Goblin Queen";
+ case PRECURSOR_CITY: return EnumChatFormatting.AQUA + "the " +
+ EnumChatFormatting.WHITE + "Precursor City";
+ case MINES_OF_DIVAN: return EnumChatFormatting.AQUA + "the " +
+ EnumChatFormatting.BLUE + "Mines of Divan";
+ default: return EnumChatFormatting.WHITE + "an undetermined location";
+ }
+ }
- if (NUCLEUS_BB.isVecInside(solution)) {
- description += "Crystal Nucleus (" + coordsText + ")";
- } else {
- description += coordsText;
+ private String getNameForCompassTarget(CompassTarget compassTarget) {
+ boolean useSkytilsNames = (NotEnoughUpdates.INSTANCE.config.mining.wishingCompassWaypointNameType == 1);
+ switch (compassTarget) {
+ case BAL: return useSkytilsNames ? "internal_bal" : "Bal";
+ case ODAWA: return "Odawa";
+ case JUNGLE_TEMPLE: return useSkytilsNames ? "internal_temple" : "Temple";
+ case GOBLIN_KING: return useSkytilsNames ? "internal_king" : "King";
+ case GOBLIN_QUEEN: return useSkytilsNames ? "internal_den" : "Queen";
+ case PRECURSOR_CITY: return useSkytilsNames ? "internal_city" : "City";
+ case MINES_OF_DIVAN: return useSkytilsNames ? "internal_mines" : "Mines";
+ default: return "WishingTarget";
}
+ }
- ChatComponentText message = new ChatComponentText(EnumChatFormatting.YELLOW + description);
+ private String getSolutionCoordsText() {
+ return solution == null ? "" :
+ String.format("%.0f %.0f %.0f", solution.xCoord, solution.yCoord, solution.zCoord);
+ }
- if (isSkytilsPresent) {
- ChatStyle clickEvent = new ChatStyle().setChatClickEvent(
- new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/sthw add WishingTarget " + coordsText));
- clickEvent.setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(
- EnumChatFormatting.YELLOW +
- "Add Skytils hollows waypoint")));
- message.setChatStyle(clickEvent);
+ private String getWishingCompassDestinationsMessage() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(EnumChatFormatting.YELLOW);
+ sb.append("[NEU] ");
+ sb.append(EnumChatFormatting.AQUA);
+ sb.append("Wishing compass points to ");
+ int index = 1;
+ for (CompassTarget target : solutionPossibleTargets) {
+ if (index > 1) {
+ sb.append(EnumChatFormatting.AQUA);
+ if (index == solutionPossibleTargets.size()) {
+ sb.append(" or ");
+ } else {
+ sb.append(", ");
+ }
+ }
+ sb.append(getFriendlyNameForCompassTarget(target));
+ index++;
}
- Minecraft.getMinecraft().thePlayer.addChatMessage(message);
+ sb.append(EnumChatFormatting.AQUA);
+ sb.append(" (");
+ sb.append(getSolutionCoordsText());
+ sb.append(")");
+ return sb.toString();
}
- public void logDiagnosticData(boolean outputAlways) {
- if (SBInfo.getInstance().getLocation() == null) {
- mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
- "[NEU] This command is not available outside SkyBlock"));
+ private void showSolution() {
+ if (solution == null) return;
+
+ if (NUCLEUS_BB.isVecInside(solution)) {
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "[NEU] " +
+ EnumChatFormatting.AQUA + "Wishing compass target is the Crystal Nucleus"));
return;
}
- if (!NotEnoughUpdates.INSTANCE.config.mining.wishingCompassSolver)
- {
- mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
- "[NEU] Wishing Compass Solver is not enabled."));
+ String destinationMessage = getWishingCompassDestinationsMessage();
+
+ if (!isSkytilsPresent) {
+ mc.thePlayer.addChatMessage(new ChatComponentText(destinationMessage));
return;
}
- if (!outputAlways && !NotEnoughUpdates.INSTANCE.config.hidden.debugFlags.contains(NEUDebugFlag.WISHING)) {
- return;
+ String targetNameForSkytils = solutionPossibleTargets.size() == 1 ?
+ getNameForCompassTarget(solutionPossibleTargets.iterator().next()) :
+ "WishingTarget";
+ String skytilsCommand = String.format("/sthw add %s %s", targetNameForSkytils, getSolutionCoordsText());
+ if (NotEnoughUpdates.INSTANCE.config.mining.wishingCompassAutocreateKnownWaypoints &&
+ solutionPossibleTargets.size() == 1) {
+ mc.thePlayer.addChatMessage(new ChatComponentText(destinationMessage));
+ int commandResult = ClientCommandHandler.instance.executeCommand(mc.thePlayer, skytilsCommand);
+ if (commandResult == 1)
+ {
+ return;
+ }
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "[NEU] Failed to automatically run /sthw"));
}
- boolean originalDebugFlag = !NotEnoughUpdates.INSTANCE.config.hidden.debugFlags.add(NEUDebugFlag.WISHING);
+ destinationMessage += EnumChatFormatting.YELLOW + " [Add Skytils Waypoint]";
+ ChatComponentText chatMessage = new ChatComponentText(destinationMessage);
+ chatMessage.setChatStyle(Utils.createClickStyle(ClickEvent.Action.RUN_COMMAND,
+ skytilsCommand,
+ EnumChatFormatting.YELLOW + "Set waypoint for wishing target\n"));
+ mc.thePlayer.addChatMessage(chatMessage);
+ }
+ private String getDiagnosticMessage() {
StringBuilder diagsMessage = new StringBuilder();
- diagsMessage.append("\n");
diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("Skytils Present: ");
+ diagsMessage.append("Solver State: ");
diagsMessage.append(EnumChatFormatting.WHITE);
- diagsMessage.append(isSkytilsPresent);
+ diagsMessage.append(solverState.name());
diagsMessage.append("\n");
- diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("Compass Used Millis: ");
- diagsMessage.append(EnumChatFormatting.WHITE);
- diagsMessage.append(compassUsedMillis);
- diagsMessage.append("\n");
+ if (firstCompass == null) {
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("First Compass: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append("<NONE>");
+ diagsMessage.append("\n");
+ } else {
+ firstCompass.appendCompassDiagnostics(diagsMessage, "First Compass");
+ }
+
+ if (secondCompass == null) {
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Second Compass: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append("<NONE>");
+ diagsMessage.append("\n");
+ } else {
+ secondCompass.appendCompassDiagnostics(diagsMessage, "Second Compass");
+ }
diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("Compass Used Position: ");
+ diagsMessage.append("Intersection Line: ");
diagsMessage.append(EnumChatFormatting.WHITE);
- diagsMessage.append((prevPlayerPos == null) ? "<NONE>" : prevPlayerPos.toString());
+ diagsMessage.append((solutionIntersectionLine == null) ? "<NONE>" : solutionIntersectionLine);
diagsMessage.append("\n");
diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("First Particle: ");
+ diagsMessage.append("Jungle Key in Inventory: ");
diagsMessage.append(EnumChatFormatting.WHITE);
- diagsMessage.append((firstParticle == null) ? "<NONE>" : firstParticle.toString());
+ diagsMessage.append(isKeyInInventory());
diagsMessage.append("\n");
diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("Last Particle: ");
+ diagsMessage.append("King's Scent Present: ");
diagsMessage.append(EnumChatFormatting.WHITE);
- diagsMessage.append((lastParticle == null) ? "<NONE>" : lastParticle.toString());
+ diagsMessage.append(isKingsScentPresent());
diagsMessage.append("\n");
diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("Last Particle Distance From First: ");
+ diagsMessage.append("First Compass Targets: ");
diagsMessage.append(EnumChatFormatting.WHITE);
- diagsMessage.append(lastParticleDistanceFromFirst);
+ diagsMessage.append(possibleTargets == null ? "<NONE>" : possibleTargets.toString());
diagsMessage.append("\n");
diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("First Compass Line: ");
+ diagsMessage.append("Current Calculated Targets: ");
diagsMessage.append(EnumChatFormatting.WHITE);
- diagsMessage.append((firstCompassLine == null) ? "<NONE>" : firstCompassLine.toString());
+ diagsMessage.append(calculatePossibleTargets(mc.thePlayer.getPosition()));
diagsMessage.append("\n");
diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("Second Compass Line: ");
+ diagsMessage.append("Found Crystals: ");
diagsMessage.append(EnumChatFormatting.WHITE);
- diagsMessage.append((secondCompassLine == null) ? "<NONE>" : secondCompassLine.toString());
+ diagsMessage.append(getFoundCrystals());
diagsMessage.append("\n");
+ if (originalSolution != null) {
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Original Solution: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(originalSolution);
+ diagsMessage.append("\n");
+ }
+
diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("Intersection Line: ");
+ diagsMessage.append("Solution: ");
diagsMessage.append(EnumChatFormatting.WHITE);
- diagsMessage.append((secondCompassLine == null) ? "<NONE>" : solutionIntersectionLine);
+ diagsMessage.append((solution == null) ? "<NONE>" : solution.toString());
diagsMessage.append("\n");
diagsMessage.append(EnumChatFormatting.AQUA);
- diagsMessage.append("Solution: ");
+ diagsMessage.append("Solution Targets: ");
diagsMessage.append(EnumChatFormatting.WHITE);
- diagsMessage.append((solution == null) ? "<NONE>" : solution.toString());
+ diagsMessage.append((solutionPossibleTargets == null) ? "<NONE>" : solutionPossibleTargets.toString());
diagsMessage.append("\n");
- NEUDebugLogger.log(NEUDebugFlag.WISHING, diagsMessage.toString());
+ return diagsMessage.toString();
+ }
+
+ public void logDiagnosticData(boolean outputAlways) {
+ if (!SBInfo.getInstance().checkForSkyblockLocation()) {
+ return;
+ }
- if (!originalDebugFlag) {
- NotEnoughUpdates.INSTANCE.config.hidden.debugFlags.remove(NEUDebugFlag.WISHING);
+ if (!NotEnoughUpdates.INSTANCE.config.mining.wishingCompassSolver)
+ {
+ mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] Wishing Compass Solver is not enabled."));
+ return;
+ }
+
+ boolean wishingDebugFlagSet = NotEnoughUpdates.INSTANCE.config.hidden.debugFlags.contains(NEUDebugFlag.WISHING);
+ if (outputAlways || wishingDebugFlagSet) {
+ NEUDebugLogger.logAlways(getDiagnosticMessage());
+ }
+ }
+
+ enum CompassState {
+ WAITING_FOR_FIRST_PARTICLE,
+ COMPUTING_LAST_PARTICLE,
+ COMPLETED,
+ FAILED_TIMEOUT_NO_REPEATING,
+ FAILED_TIMEOUT_NO_PARTICLES,
+ }
+
+ enum HandleCompassResult {
+ SUCCESS,
+ LOCATION_TOO_CLOSE,
+ STILL_PROCESSING_PRIOR_USE,
+ POSSIBLE_TARGETS_CHANGED,
+ NO_PARTICLES_FOR_PREVIOUS_COMPASS,
+ PLAYER_IN_NUCLEUS
+ }
+
+ static class Compass {
+ public CompassState compassState;
+ public Line line = null;
+
+ private final BlockPos whereUsed;
+ private final long whenUsedMillis;
+ private Vec3Comparable firstParticle = null;
+ private Vec3Comparable previousParticle = null;
+ private Vec3Comparable lastParticle = null;
+ private final ArrayList<ProcessedParticle> processedParticles;
+
+ Compass(BlockPos whereUsed, long whenUsedMillis) {
+ this.whereUsed = whereUsed;
+ this.whenUsedMillis = whenUsedMillis;
+ compassState = CompassState.WAITING_FOR_FIRST_PARTICLE;
+ processedParticles = new ArrayList<>();
+ }
+
+ public Vec3Comparable getDirection() {
+ if (firstParticle == null || lastParticle == null) {
+ return null;
+ }
+
+ return new Vec3Comparable(firstParticle.subtractReverse(lastParticle).normalize());
+ }
+
+ public Vec3Comparable getDirectionTo(Vec3Comparable target) {
+ if (firstParticle == null || target == null) {
+ return null;
+ }
+
+ return new Vec3Comparable(firstParticle.subtractReverse(target).normalize());
+ }
+
+ public double particleSpread() {
+ if (firstParticle == null || lastParticle == null) {
+ return 0.0;
+ }
+ return firstParticle.distanceTo(lastParticle);
+ }
+
+ public void processParticle(double x, double y, double z, long particleTimeMillis) {
+ if (compassState == CompassState.FAILED_TIMEOUT_NO_REPEATING ||
+ compassState == CompassState.FAILED_TIMEOUT_NO_PARTICLES ||
+ compassState == CompassState.COMPLETED) {
+ throw new UnsupportedOperationException("processParticle should not be called in a failed or completed state");
+ }
+
+ if (particleTimeMillis - this.whenUsedMillis > ALL_PARTICLES_MAX_MILLIS) {
+ // Assume we have failed if we're still trying to process particles
+ compassState = CompassState.FAILED_TIMEOUT_NO_REPEATING;
+ return;
+ }
+
+ Vec3Comparable currentParticle = new Vec3Comparable(x, y, z);
+ if (compassState == CompassState.WAITING_FOR_FIRST_PARTICLE) {
+ if (currentParticle.distanceTo(new Vec3Comparable(whereUsed)) < MAX_DISTANCE_FROM_USE_TO_FIRST_PARTICLE) {
+ processedParticles.add(new ProcessedParticle(currentParticle, particleTimeMillis));
+ firstParticle = currentParticle;
+ previousParticle = currentParticle;
+ compassState = CompassState.COMPUTING_LAST_PARTICLE;
+ }
+ return;
+ }
+
+ // State is COMPUTING_LAST_PARTICLE, keep updating the previousParticle until
+ // the first particle in the second sequence is seen.
+ if (currentParticle.distanceTo(previousParticle) <= MAX_DISTANCE_BETWEEN_PARTICLES) {
+ processedParticles.add(new ProcessedParticle(currentParticle, particleTimeMillis));
+ previousParticle = currentParticle;
+ return;
+ }
+
+ if (currentParticle.distanceTo(firstParticle) > MAX_DISTANCE_BETWEEN_PARTICLES) {
+ return;
+ }
+
+ // It's a repeating particle
+ processedParticles.add(new ProcessedParticle(currentParticle, particleTimeMillis));
+ lastParticle = previousParticle;
+ line = new Line(firstParticle, lastParticle);
+ compassState = CompassState.COMPLETED;
+ }
+
+ public void appendCompassDiagnostics(StringBuilder diagsMessage, String compassName) {
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append("Compass State: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(compassState.name());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append(compassName);
+ diagsMessage.append(" Used Millis: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(whenUsedMillis);
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append(compassName);
+ diagsMessage.append(" Used Position: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append((whereUsed == null) ? "<NONE>" : whereUsed.toString());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append(compassName);
+ diagsMessage.append(" All Seen Particles: \n");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ for (ProcessedParticle particle : processedParticles) {
+ diagsMessage.append(particle.toString());
+ diagsMessage.append("\n");
+ }
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append(compassName);
+ diagsMessage.append(" Particle Spread: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append(particleSpread());
+ diagsMessage.append("\n");
+
+ diagsMessage.append(EnumChatFormatting.AQUA);
+ diagsMessage.append(compassName);
+ diagsMessage.append(" Compass Line: ");
+ diagsMessage.append(EnumChatFormatting.WHITE);
+ diagsMessage.append((line == null) ? "<NONE>" : line.toString());
+ diagsMessage.append("\n");
+ }
+
+ static class ProcessedParticle {
+ Vec3Comparable coords;
+ long particleTimeMillis;
+
+ ProcessedParticle(Vec3Comparable coords, long particleTimeMillis) {
+ this.coords = coords;
+ this.particleTimeMillis = particleTimeMillis;
+ }
+
+ @Override
+ public String toString() {
+ return coords.toString() + " " + particleTimeMillis;
+ }
}
}
}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Mining.java b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Mining.java
index 6cfb8c04..f793baf0 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Mining.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Mining.java
@@ -641,6 +641,42 @@ public class Mining {
@ConfigAccordionId(id = 6)
public int crystalHollowNoneColor = 12;
+ @ConfigOption(
+ name = "Wishing Compass Solver",
+ desc = ""
+ )
+ @ConfigEditorAccordion(id = 7)
+ public boolean wishingCompassSolverAccordion = false;
+
+ @Expose
+ @ConfigOption(
+ name = "Enable Solver",
+ desc = "Show wishing compass target coordinates based on two samples"
+ )
+ @ConfigAccordionId(id = 7)
+ @ConfigEditorBoolean
+ public boolean wishingCompassSolver = true;
+
+ @Expose
+ @ConfigOption(
+ name = "Skytils Waypoints",
+ desc = "Automatically create Skytils waypoints for well-known targets"
+ )
+ @ConfigAccordionId(id = 7)
+ @ConfigEditorBoolean
+ public boolean wishingCompassAutocreateKnownWaypoints = false;
+
+ @Expose
+ @ConfigOption(
+ name = "Waypoint Type",
+ desc = "Skytils Waypoint name type. Skytils Built-in will be overwritten by Skytils when the waypoint is nearby."
+ )
+ @ConfigAccordionId(id = 7)
+ @ConfigEditorDropdown(
+ values = {"NEU", "Skytils"}
+ )
+ public int wishingCompassWaypointNameType = 0;
+
@Expose
@ConfigOption(
name = "Puzzler Solver",
@@ -665,19 +701,11 @@ public class Mining {
@ConfigEditorBoolean
public boolean titaniumAlertMustBeVisible = false;
- @Expose
- @ConfigOption(
- name = "Wishing Compass Solver",
- desc = "Show wishing compass target coordinates based on two samples"
- )
- @ConfigEditorBoolean
- public boolean wishingCompassSolver = true;
-
@ConfigOption(
name = "Custom Textures",
desc = ""
)
- @ConfigEditorAccordion(id = 7)
+ @ConfigEditorAccordion(id = 8)
public boolean texturesAccordion = false;
@Expose
@@ -685,7 +713,7 @@ public class Mining {
name = "Dwarven Mines Textures",
desc = "Allows texture packs to retexture blocks in the Dwarven Mines. If you don't have a texture pack that does this, you should leave this off"
)
- @ConfigAccordionId(id = 7)
+ @ConfigAccordionId(id = 8)
@ConfigEditorBoolean
public boolean dwarvenTextures = false;
@Expose
@@ -693,7 +721,7 @@ public class Mining {
name = "Crystal Hollows Textures",
desc = "Allows texture packs to retexture blocks in the Crystal Hollows. If you don't have a texture pack that does this, you should leave this off"
)
- @ConfigAccordionId(id = 7)
+ @ConfigAccordionId(id = 8)
@ConfigEditorBoolean
public boolean crystalHollowTextures = false;
@@ -702,7 +730,7 @@ public class Mining {
name = "Replace Gemstone sounds",
desc = "Replace the break sounds of crystals in the Crystal Hollows. Requires a texture pack with this feature"
)
- @ConfigAccordionId(id = 7)
+ @ConfigAccordionId(id = 8)
@ConfigEditorBoolean
public boolean gemstoneSounds = false;
@@ -711,7 +739,7 @@ public class Mining {
name = "Replace Mithril sounds",
desc = "Replace the break sounds of mithril and titanium in the Dwarven mines. Requires a texture pack with this feature"
)
- @ConfigAccordionId(id = 7)
+ @ConfigAccordionId(id = 8)
@ConfigEditorBoolean
public boolean mithrilSounds = false;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/NEUDebugLogger.java b/src/main/java/io/github/moulberry/notenoughupdates/util/NEUDebugLogger.java
index d662a872..827a3e97 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/util/NEUDebugLogger.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/NEUDebugLogger.java
@@ -13,6 +13,8 @@ import java.util.function.Consumer;
public class NEUDebugLogger {
private static final Minecraft mc = Minecraft.getMinecraft();
public static Consumer<String> logMethod = NEUDebugLogger::chatLogger;
+ // Used to prevent accessing NEUConfig in unit tests
+ public static boolean allFlagsEnabled = false;
private static void chatLogger(String message) {
mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "[NEU DEBUG] " + message));
@@ -23,7 +25,13 @@ public class NEUDebugLogger {
}
public static void log(NEUDebugFlag flag, String message) {
- if (logMethod != null && isFlagEnabled(flag)) {
+ if (logMethod != null && (allFlagsEnabled || isFlagEnabled(flag))) {
+ logAlways(message);
+ }
+ }
+
+ public static void logAlways(String message) {
+ if (logMethod != null) {
logMethod.accept(message);
}
}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java b/src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java
index 5b7bd055..a557eb12 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java
@@ -18,6 +18,8 @@ import net.minecraft.scoreboard.Score;
import net.minecraft.scoreboard.ScoreObjective;
import net.minecraft.scoreboard.ScorePlayerTeam;
import net.minecraft.scoreboard.Scoreboard;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.IChatComponent;
import net.minecraftforge.client.event.ClientChatReceivedEvent;
import net.minecraftforge.client.event.GuiOpenEvent;
@@ -57,7 +59,7 @@ public class SBInfo {
public String slayer = "";
public boolean stranded = false;
- public String mode = "";
+ public String mode = null;
public Date currentTimeDate = null;
@@ -120,6 +122,16 @@ public class SBInfo {
}
}
+ public boolean checkForSkyblockLocation() {
+ if (!NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard() || getLocation() == null) {
+ Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED +
+ "[NEU] This command is not available outside SkyBlock"));
+ return false;
+ }
+
+ return true;
+ }
+
private static final Pattern PROFILE_PATTERN =
Pattern.compile("(?<type>(♲ Ironman)|(☀ Stranded)|()) *Profile: (?<name>[^ ]+)");