diff options
| author | CraftyOldMiner <85420839+CraftyOldMiner@users.noreply.github.com> | 2022-03-27 23:07:57 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-03-28 00:07:57 -0400 |
| commit | f237ae4cecdfa5c00ee83bd0176a591a2b174913 (patch) | |
| tree | 5b3e6d01296609b2a9f1701924e973b3bd19edd4 /src/main/java/io/github/moulberry/notenoughupdates/miscfeatures | |
| parent | 4afcd516ffc58d55da24c5f4db14db7922f61121 (diff) | |
| download | notenoughupdates-f237ae4cecdfa5c00ee83bd0176a591a2b174913.tar.gz notenoughupdates-f237ae4cecdfa5c00ee83bd0176a591a2b174913.tar.bz2 notenoughupdates-f237ae4cecdfa5c00ee83bd0176a591a2b174913.zip | |
Restore metal detector and wishing compass changes that were nuked by #94 (#104)
Diffstat (limited to 'src/main/java/io/github/moulberry/notenoughupdates/miscfeatures')
2 files changed, 1074 insertions, 318 deletions
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_PARTI |
