aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java1
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java32
-rw-r--r--src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java10
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/PlayerListHudMixin.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHud.java22
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java252
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsWaypoint.java98
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetector.java9
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/MiningLocationLabel.java53
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/WishingCompassSolver.java389
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListMgr.java71
-rw-r--r--src/main/resources/assets/skyblocker/lang/en_us.json23
13 files changed, 761 insertions, 209 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 58b1b48d..9d5f2b0f 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -132,6 +132,7 @@ public class SkyblockerMod implements ClientModInitializer {
FarmingHud.init();
LowerSensitivity.init();
CrystalsLocationsManager.init();
+ WishingCompassSolver.init();
MetalDetector.init();
ChatMessageListener.init();
Shortcuts.init();
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java
index 4e11d869..796a6105 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java
@@ -166,6 +166,14 @@ public class MiningCategory {
newValue -> config.mining.crystalsWaypoints.enabled = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
+ .option(Option.<Float>createBuilder()
+ .name(Text.translatable("skyblocker.config.mining.crystalsWaypoints.textScale"))
+ .description(OptionDescription.of(Text.translatable("skyblocker.config.mining.crystalsWaypoints.textScale.@Tooltip")))
+ .binding(defaults.mining.crystalsWaypoints.textScale,
+ () -> config.mining.crystalsWaypoints.textScale,
+ newValue -> config.mining.crystalsWaypoints.textScale = newValue)
+ .controller(FloatFieldControllerBuilder::create)
+ .build())
.option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.mining.crystalsWaypoints.findInChat"))
.description(OptionDescription.of(Text.translatable("skyblocker.config.mining.crystalsWaypoints.findInChat.@Tooltip")))
@@ -174,6 +182,14 @@ public class MiningCategory {
newValue -> config.mining.crystalsWaypoints.findInChat = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver"))
+ .description(OptionDescription.of(Text.translatable("skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.@Tooltip")))
+ .binding(defaults.mining.crystalsWaypoints.wishingCompassSolver,
+ () -> config.mining.crystalsWaypoints.wishingCompassSolver,
+ newValue -> config.mining.crystalsWaypoints.wishingCompassSolver = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
.build())
@@ -193,14 +209,6 @@ public class MiningCategory {
newValue -> config.mining.commissionWaypoints.mode = newValue)
.controller(ConfigUtils::createEnumCyclingListController)
.build())
- .option(Option.<Boolean>createBuilder()
- .name(Text.translatable("skyblocker.config.mining.commissionWaypoints.useColor"))
- .description(OptionDescription.of(Text.translatable("skyblocker.config.mining.commissionWaypoints.useColor.@Tooltip")))
- .binding(defaults.mining.commissionWaypoints.useColor,
- () -> config.mining.commissionWaypoints.useColor,
- newValue -> config.mining.commissionWaypoints.useColor = newValue)
- .controller(ConfigUtils::createBooleanController)
- .build())
.option(Option.<Float>createBuilder()
.name(Text.translatable("skyblocker.config.mining.commissionWaypoints.textScale"))
.description(OptionDescription.of(Text.translatable("skyblocker.config.mining.commissionWaypoints.textScale.@Tooltip")))
@@ -210,6 +218,14 @@ public class MiningCategory {
.controller(FloatFieldControllerBuilder::create)
.build())
.option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("skyblocker.config.mining.commissionWaypoints.useColor"))
+ .description(OptionDescription.of(Text.translatable("skyblocker.config.mining.commissionWaypoints.useColor.@Tooltip")))
+ .binding(defaults.mining.commissionWaypoints.useColor,
+ () -> config.mining.commissionWaypoints.useColor,
+ newValue -> config.mining.commissionWaypoints.useColor = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.mining.commissionWaypoints.showBaseCamp"))
.description(OptionDescription.of(Text.translatable("skyblocker.config.mining.commissionWaypoints.showBaseCamp.@Tooltip")))
.binding(defaults.mining.commissionWaypoints.showBaseCamp,
diff --git a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java
index d71f57b6..dcf70f24 100644
--- a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java
@@ -94,7 +94,13 @@ public class MiningConfig {
public boolean enabled = true;
@SerialEntry
+ public float textScale = 1;
+
+ @SerialEntry
public boolean findInChat = true;
+
+ @SerialEntry
+ public boolean wishingCompassSolver = true;
}
public static class CommissionWaypoints {
@@ -102,10 +108,10 @@ public class MiningConfig {
public CommissionWaypointMode mode = CommissionWaypointMode.BOTH;
@SerialEntry
- public boolean useColor = true;
+ public float textScale = 1;
@SerialEntry
- public float textScale = 1;
+ public boolean useColor = true;
@SerialEntry
public boolean showBaseCamp = false;
diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java
index 7bbbac81..c0cd8b7b 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java
@@ -9,6 +9,7 @@ import de.hysky.skyblocker.skyblock.chocolatefactory.EggFinder;
import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager;
import de.hysky.skyblocker.skyblock.dungeon.DungeonScore;
import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
+import de.hysky.skyblocker.skyblock.dwarven.WishingCompassSolver;
import de.hysky.skyblocker.skyblock.end.BeaconHighlighter;
import de.hysky.skyblocker.skyblock.end.EnderNodes;
import de.hysky.skyblocker.skyblock.end.TheEnd;
@@ -100,6 +101,7 @@ public abstract class ClientPlayNetworkHandlerMixin {
MythologicalRitual.onParticle(packet);
DojoManager.onParticle(packet);
EnderNodes.onParticle(packet);
+ WishingCompassSolver.onParticle(packet);
}
@ModifyExpressionValue(method = "onEntityStatus", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/EntityStatusS2CPacket;getEntity(Lnet/minecraft/world/World;)Lnet/minecraft/entity/Entity;"))
diff --git a/src/main/java/de/hysky/skyblocker/mixins/PlayerListHudMixin.java b/src/main/java/de/hysky/skyblocker/mixins/PlayerListHudMixin.java
index a96a7727..2c7fde47 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/PlayerListHudMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/PlayerListHudMixin.java
@@ -12,6 +12,7 @@ import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.hud.PlayerListHud;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.text.Text;
+import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
@@ -42,8 +43,6 @@ public class PlayerListHudMixin {
w = (int) (w / scale);
h = (int) (h / scale);
- PlayerListMgr.updateFooter(footer);
-
try {
ScreenMaster.render(context, w,h);
// Screen screen = Screen.getCorrect(w, h, footer);
@@ -54,4 +53,9 @@ public class PlayerListHudMixin {
}
}
+ @Inject(at = @At("HEAD"), method = "setFooter")
+ public void skblocker$updateFooter(@Nullable Text footer, CallbackInfo info) {
+ PlayerListMgr.updateFooter(footer);
+ }
+
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHud.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHud.java
index 7f4dbdbf..30bf6b03 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHud.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHud.java
@@ -23,7 +23,7 @@ public class CrystalsHud {
private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
protected static final Identifier MAP_TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/crystals_map.png");
private static final Identifier MAP_ICON = Identifier.ofVanilla("textures/map/decorations/player.png");
- private static final List<String> SMALL_LOCATIONS = List.of("Fairy Grotto", "King Yolkar", "Corleone", "Odawa", "Key Guardian");
+ private static final List<String> SMALL_LOCATIONS = List.of("Fairy Grotto", "King Yolkar", "Corleone", "Odawa", "Key Guardian", "Unknown");
public static boolean visible = false;
@@ -54,8 +54,8 @@ public class CrystalsHud {
* Renders the map to the players UI. renders the background image ({@link CrystalsHud#MAP_TEXTURE}) of the map then if enabled special locations on the map. then finally the player to the map.
*
* @param context DrawContext to draw map to
- * @param hudX Top left X coordinate of the map
- * @param hudY Top left Y coordinate of the map
+ * @param hudX Top left X coordinate of the map
+ * @param hudY Top left Y coordinate of the map
*/
private static void render(DrawContext context, int hudX, int hudY) {
float scale = SkyblockerConfigManager.get().mining.crystalsHud.mapScaling;
@@ -72,19 +72,19 @@ public class CrystalsHud {
//if enabled add waypoint locations to map
if (SkyblockerConfigManager.get().mining.crystalsHud.showLocations) {
- Map<String,CrystalsWaypoint> ActiveWaypoints = CrystalsLocationsManager.activeWaypoints;
+ Map<String, MiningLocationLabel> ActiveWaypoints = CrystalsLocationsManager.activeWaypoints;
- for (CrystalsWaypoint waypoint : ActiveWaypoints.values()) {
- Color waypointColor = waypoint.category.color;
- Vector2ic renderPos = transformLocation(waypoint.pos.getX(), waypoint.pos.getZ());
+ for (MiningLocationLabel waypoint : ActiveWaypoints.values()) {
+ int waypointColor = waypoint.category().getColor();
+ Vector2ic renderPos = transformLocation(waypoint.centerPos().getX(), waypoint.centerPos().getZ());
int locationSize = SkyblockerConfigManager.get().mining.crystalsHud.locationSize;
- if (SMALL_LOCATIONS.contains(waypoint.name.getString())) {//if small location half the location size
+ if (SMALL_LOCATIONS.contains(waypoint.category().getName())) {//if small location half the location size
locationSize /= 2;
}
//fill square of size locationSize around the coordinates of the location
- context.fill(renderPos.x() - locationSize / 2, renderPos.y() - locationSize / 2, renderPos.x() + locationSize / 2, renderPos.y() + locationSize / 2, waypointColor.getRGB());
+ context.fill(renderPos.x() - locationSize / 2, renderPos.y() - locationSize / 2, renderPos.x() + locationSize / 2, renderPos.y() + locationSize / 2, waypointColor);
}
}
@@ -92,7 +92,6 @@ public class CrystalsHud {
if (CLIENT.player == null || CLIENT.getNetworkHandler() == null) {
return;
}
-
//get player location
double playerX = CLIENT.player.getX();
double playerZ = CLIENT.player.getZ();
@@ -109,8 +108,6 @@ public class CrystalsHud {
//draw marker on map
context.drawTexture(MAP_ICON, 0, 0, 2, 0, 5, 7, 8, 8);
-
- //todo add direction (can not work out how to rotate)
matrices.pop();
}
@@ -146,7 +143,6 @@ public class CrystalsHud {
/**
* Works out if the crystals map should be rendered and sets {@link CrystalsHud#visible} accordingly.
- *
*/
public static void update() {
if (CLIENT.player == null || CLIENT.getNetworkHandler() == null || !SkyblockerConfigManager.get().mining.crystalsHud.enabled) {
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java
index 83167c18..22e494ab 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java
@@ -21,16 +21,15 @@ import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.minecraft.client.MinecraftClient;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.text.ClickEvent;
+import net.minecraft.text.HoverEvent;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Vec3d;
import org.slf4j.Logger;
-import java.awt.*;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -52,12 +51,15 @@ public class CrystalsLocationsManager {
private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
/**
- * A look-up table to convert between location names and waypoint in the {@link CrystalsWaypoint.Category} values.
+ * A look-up table to convert between location names and waypoint in the {@link MiningLocationLabel.CrystalHollowsLocationsCategory} values.
*/
- private static final Map<String, CrystalsWaypoint.Category> WAYPOINT_LOCATIONS = Arrays.stream(CrystalsWaypoint.Category.values()).collect(Collectors.toMap(CrystalsWaypoint.Category::toString, Function.identity()));
- private static final Pattern TEXT_CWORDS_PATTERN = Pattern.compile("([0-9][0-9][0-9]) ([0-9][0-9][0-9]?) ([0-9][0-9][0-9])");
+ private static final Map<String, MiningLocationLabel.CrystalHollowsLocationsCategory> WAYPOINT_LOCATIONS = Arrays.stream(MiningLocationLabel.CrystalHollowsLocationsCategory.values()).collect(Collectors.toMap(MiningLocationLabel.CrystalHollowsLocationsCategory::getName, Function.identity()));
+ //Package-private for testing
+ static final Pattern TEXT_CWORDS_PATTERN = Pattern.compile("\\Dx?(\\d{3})(?=[, ]),? ?y?(\\d{2,3})(?=[, ]),? ?z?(\\d{3})\\D?(?!\\d)");
+ private static final int REMOVE_UNKNOWN_DISTANCE = 50;
- protected static Map<String, CrystalsWaypoint> activeWaypoints = new HashMap<>();
+ protected static Map<String, MiningLocationLabel> activeWaypoints = new HashMap<>();
+ protected static List<String> verifiedWaypoints = new ArrayList<>();
public static void init() {
// Crystal Hollows Waypoints
@@ -72,47 +74,65 @@ public class CrystalsLocationsManager {
}
private static void extractLocationFromMessage(Text message, Boolean overlay) {
- if (!SkyblockerConfigManager.get().mining.crystalsWaypoints.findInChat || !Utils.isInCrystalHollows()) {
+ if (!SkyblockerConfigManager.get().mining.crystalsWaypoints.findInChat || !Utils.isInCrystalHollows() || overlay) {
return;
}
-
+ String text = Formatting.strip(message.getString());
try {
- //get the message text
- String value = message.getString();
- Matcher matcher = TEXT_CWORDS_PATTERN.matcher(value);
- //if there are coordinates in the message try to get them and what they are talking about
- if (matcher.find()) {
- String location = matcher.group();
- int[] coordinates = Arrays.stream(location.split(" ", 3)).mapToInt(Integer::parseInt).toArray();
- BlockPos blockPos = new BlockPos(coordinates[0], coordinates[1], coordinates[2]);
-
- //if position is not in the hollows do not add it
- if (!checkInCrystals(blockPos)) {
- return;
- }
+ //make sure that it is only reading user messages and not from skyblocker
+ if (text.contains(":") && !text.startsWith(Constants.PREFIX.get().getString())) {
+ String userMessage = text.split(":", 2)[1];
+
+ //get the message text
+ Matcher matcher = TEXT_CWORDS_PATTERN.matcher(userMessage);
+ //if there are coordinates in the message try to get them and what they are talking about
+ if (matcher.find()) {
+ BlockPos blockPos = new BlockPos(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)), Integer.parseInt(matcher.group(3)));
+ String location = blockPos.getX() + " " + blockPos.getY() + " " + blockPos.getZ();
+ //if position is not in the hollows do not add it
+ if (!checkInCrystals(blockPos)) {
+ return;
+ }
- //see if there is a name of a location to add to this
- for (String waypointLocation : WAYPOINT_LOCATIONS.keySet()) {
- if (value.toLowerCase().contains(waypointLocation.toLowerCase())) { //todo be more lenient
- //all data found to create waypoint
- addCustomWaypoint(waypointLocation, blockPos);
+ //see if there is a name of a location to add to this
+ for (String waypointLocation : WAYPOINT_LOCATIONS.keySet()) {
+ if (Arrays.stream(waypointLocation.toLowerCase().split(" ")).anyMatch(word -> userMessage.toLowerCase().contains(word))) { //check if contains a word of location
+ //all data found to create waypoint
+ //make sure the waypoint does not already exist in active waypoints, so waypoints can not get randomly moved
+ if (!activeWaypoints.containsKey(waypointLocation)) {
+ addCustomWaypoint(waypointLocation, blockPos);
+ }
+ return;
+ }
+ }
+
+ //if the location is not found ask the user for the location (could have been in a previous chat message)
+ if (CLIENT.player == null || CLIENT.getNetworkHandler() == null) {
return;
}
- }
- //if the location is not found ask the user for the location (could have been in a previous chat message)
- if (CLIENT.player == null || CLIENT.getNetworkHandler() == null) {
- return;
+ CLIENT.player.sendMessage(getLocationMenu(location, false), false);
}
-
- CLIENT.player.sendMessage(getLocationInputText(location), false);
}
+
} catch (Exception e) {
LOGGER.error("[Skyblocker Crystals Locations Manager] Encountered an exception while extracing a location from a chat message!", e);
}
+
+ //move waypoint to be more accurate based on locational chat messages if not already verifed
+ if (CLIENT.player != null && SkyblockerConfigManager.get().mining.crystalsWaypoints.enabled) {
+ for (MiningLocationLabel.CrystalHollowsLocationsCategory waypointLocation : WAYPOINT_LOCATIONS.values()) {
+ String waypointLinkedMessage = waypointLocation.getLinkedMessage();
+ String waypointName = waypointLocation.getName();
+ if (waypointLinkedMessage != null && text.contains(waypointLinkedMessage) && !verifiedWaypoints.contains(waypointName)) {
+ addCustomWaypoint(waypointLocation.getName(), CLIENT.player.getBlockPos());
+ verifiedWaypoints.add(waypointName);
+ }
+ }
+ }
}
- protected static Boolean checkInCrystals(BlockPos pos) {
+ protected static boolean checkInCrystals(BlockPos pos) {
//checks if a location is inside crystal hollows bounds
return pos.getX() >= 202 && pos.getX() <= 823
&& pos.getZ() >= 202 && pos.getZ() <= 823
@@ -122,16 +142,44 @@ public class CrystalsLocationsManager {
private static void registerWaypointLocationCommands(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) {
dispatcher.register(literal(SkyblockerMod.NAMESPACE)
.then(literal("crystalWaypoints")
- .then(argument("pos", ClientBlockPosArgumentType.blockPos())
+ .then(literal("add")
+ .executes(context -> {
+ if (CLIENT.player == null) {
+ return 0;
+ }
+ CLIENT.player.sendMessage(getLocationMenu((int) CLIENT.player.getX() + " " + (int) CLIENT.player.getY() + " " + (int) CLIENT.player.getZ(), true), false);
+ return Command.SINGLE_SUCCESS;
+ })
+ .then(argument("pos", ClientBlockPosArgumentType.blockPos())
+ .then(argument("place", StringArgumentType.greedyString())
+ .suggests((context, builder) -> suggestMatching(WAYPOINT_LOCATIONS.keySet(), builder))
+ .executes(context -> addWaypointFromCommand(context.getSource(), getString(context, "place"), context.getArgument("pos", ClientPosArgument.class)))
+ )
+ ))
+ .then(literal("share")
+ .executes(context -> {
+ if (CLIENT.player == null) {
+ return 0;
+ }
+ CLIENT.player.sendMessage(getPlacesMenu("share"), false);
+ return Command.SINGLE_SUCCESS;
+ })
.then(argument("place", StringArgumentType.greedyString())
.suggests((context, builder) -> suggestMatching(WAYPOINT_LOCATIONS.keySet(), builder))
- .executes(context -> addWaypointFromCommand(context.getSource(), getString(context, "place"), context.getArgument("pos", ClientPosArgument.class)))
+ .executes(context -> shareWaypoint(getString(context, "place")))
)
)
- .then(literal("share")
+ .then(literal("remove")
+ .executes(context -> {
+ if (CLIENT.player == null) {
+ return 0;
+ }
+ CLIENT.player.sendMessage(getPlacesMenu("remove"), false);
+ return Command.SINGLE_SUCCESS;
+ })
.then(argument("place", StringArgumentType.greedyString())
.suggests((context, builder) -> suggestMatching(WAYPOINT_LOCATIONS.keySet(), builder))
- .executes(context -> shareWaypoint(getString(context, "place")))
+ .executes(context -> removeWaypoint(getString(context, "place")))
)
)
)
@@ -139,21 +187,77 @@ public class CrystalsLocationsManager {
}
protected static Text getSetLocationMessage(String location, BlockPos blockPos) {
- MutableText text = Constants.PREFIX.get();
- text.append(Text.literal("Added waypoint for "));
- Color locationColor = WAYPOINT_LOCATIONS.get(location).color;
- text.append(Text.literal(location).withColor(locationColor.getRGB()));
- text.append(Text.literal(" at : " + blockPos.getX() + " " + blockPos.getY() + " " + blockPos.getZ() + "."));
+ int locationColor = WAYPOINT_LOCATIONS.get(location).getColor();
- return text;
+ // Minecraft transforms all arguments (`%s`, `%d`, whatever) to `%$1s` DURING LOADING in `Language#load(InputStream, BiConsumer<String,String>)` for some unknown reason.
+ // And then `TranslatableTextContent#forEachPart` only accepts `%s` for some other unknown reason.
+ // So that's why the arguments are all `%s`. Wtf mojang?????????
+ return Constants.PREFIX.get().append(Text.translatableWithFallback("skyblocker.config.mining.crystalsWaypoints.addedWaypoint", "Added waypoint for '%s' at %s %s %s.", Text.literal(location).withColor(locationColor), blockPos.getX(), blockPos.getY(), blockPos.getZ()));
}
- private static Text getLocationInputText(String location) {
- MutableText text = Constants.PREFIX.get();
+ /**
+ * Creates a formated text with a list of possible places to add a waypoint for
+ *
+ * @param location the location where the waypoint will be created
+ * @param excludeUnknown if the {@link de.hysky.skyblocker.skyblock.dwarven.MiningLocationLabel.CrystalHollowsLocationsCategory#UNKNOWN Unknown} location should be available to add
+ * @return text for a message to send to the player
+ */
+ private static Text getLocationMenu(String location, boolean excludeUnknown) {
+
+ //if the user has all available waypoints active warn them instead of an empty list (excused unknown from check when disabled)
+ if (activeWaypoints.size() == WAYPOINT_LOCATIONS.size() || (excludeUnknown && WAYPOINT_LOCATIONS.size() - activeWaypoints.size() == 1 && !activeWaypoints.containsKey(MiningLocationLabel.CrystalHollowsLocationsCategory.UNKNOWN.getName()))) {
+ return Constants.PREFIX.get().append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.allActive").formatted(Formatting.RED));
+ }
+
+ //add starting message
+ MutableText text = Text.empty();
+ //add possible locations to the message
for (String waypointLocation : WAYPOINT_LOCATIONS.keySet()) {
- Color locationColor = WAYPOINT_LOCATIONS.get(waypointLocation).color;
- text.append(Text.literal("[" + waypointLocation + "]").withColor(locationColor.getRGB()).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker crystalWaypoints " + location + " " + waypointLocation))));
+ //do not show option to add waypoints for existing locations or unknown if its disabled
+ if (activeWaypoints.containsKey(waypointLocation) || (excludeUnknown && Objects.equals(waypointLocation, MiningLocationLabel.CrystalHollowsLocationsCategory.UNKNOWN.getName()))) {
+ continue;
+ }
+ int locationColor = WAYPOINT_LOCATIONS.get(waypointLocation).getColor();
+ text.append(Text.literal("[" + waypointLocation + "]").withColor(locationColor).styled(style -> style
+ .withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker crystalWaypoints add " + location + " " + waypointLocation))
+ .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.translatable("skyblocker.config.mining.crystalsWaypoints.getLocationHover.add").withColor(locationColor))))
+ );
+ }
+
+ return Constants.PREFIX.get().append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.markLocation", location, text));
+ }
+
+ /**
+ * Creates a formated text with a list of found places to remove / share a waypoint for
+ *
+ * @param action the action the command should perform (remove / share)
+ * @return text for a message to send to the player
+ */
+ private static Text getPlacesMenu(String action) {
+ MutableText text = Constants.PREFIX.get();
+
+ //if the user has no active warn them instead of an empty list
+ if (activeWaypoints.isEmpty()) {
+ return text.append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.noActive").formatted(Formatting.RED));
+ }
+
+ //depending on the action load the correct prefix and hover message
+ MutableText hoverMessage;
+ if (action.equals("remove")) {
+ text.append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.getLocationHover.remove").append(Text.literal(": ")));
+ hoverMessage = Text.translatable("skyblocker.config.mining.crystalsWaypoints.getLocationHover.remove");
+ } else {
+ text.append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.getLocationHover.share").append(Text.literal(": ")));
+ hoverMessage = Text.translatable("skyblocker.config.mining.crystalsWaypoints.getLocationHover.share");
+ }
+
+ for (String waypointLocation : activeWaypoints.keySet()) {
+ int locationColor = WAYPOINT_LOCATIONS.get(waypointLocation).getColor();
+ text.append(Text.literal("[" + waypointLocation + "]").withColor(locationColor).styled(style -> style
+ .withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker crystalWaypoints " + action + " " + waypointLocation))
+ .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, hoverMessage.withColor(locationColor))))
+ );
}
return text;
@@ -178,8 +282,8 @@ public class CrystalsLocationsManager {
public static int shareWaypoint(String place) {
if (activeWaypoints.containsKey(place)) {
- BlockPos pos = activeWaypoints.get(place).pos;
- MessageScheduler.INSTANCE.sendMessageAfterCooldown(Constants.PREFIX.get().getString() + " " + place + ": " + pos.getX() + ", " + pos.getY() + ", " + pos.getZ());
+ Vec3d pos = activeWaypoints.get(place).centerPos();
+ MessageScheduler.INSTANCE.sendMessageAfterCooldown(Constants.PREFIX.get().getString() + " " + place + ": " + (int) pos.getX() + ", " + (int) pos.getY() + ", " + (int) pos.getZ());
} else {
//send fail message
if (CLIENT.player == null || CLIENT.getNetworkHandler() == null) {
@@ -191,25 +295,57 @@ public class CrystalsLocationsManager {
return Command.SINGLE_SUCCESS;
}
+ public static int removeWaypoint(String place) {
+ if (CLIENT.player == null || CLIENT.getNetworkHandler() == null) {
+ return 0;
+ }
+ if (activeWaypoints.containsKey(place)) {
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.removeSuccess").formatted(Formatting.GREEN)).append(Text.literal(place).withColor(WAYPOINT_LOCATIONS.get(place).getColor())), false);
+ activeWaypoints.remove(place);
+ verifiedWaypoints.remove(place);
+ } else {
+ //send fail message
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.removeFail").formatted(Formatting.RED)), false);
+ }
- private static void addCustomWaypoint(String waypointName, BlockPos pos) {
- CrystalsWaypoint.Category category = WAYPOINT_LOCATIONS.get(waypointName);
- CrystalsWaypoint waypoint = new CrystalsWaypoint(category, Text.literal(waypointName), pos);
+ return Command.SINGLE_SUCCESS;
+ }
+
+
+ protected static void addCustomWaypoint(String waypointName, BlockPos pos) {
+ removeUnknownNear(pos);
+ MiningLocationLabel.CrystalHollowsLocationsCategory category = WAYPOINT_LOCATIONS.get(waypointName);
+ MiningLocationLabel waypoint = new MiningLocationLabel(category, pos);
activeWaypoints.put(waypointName, waypoint);
}
+ /**
+ * Removes unknown waypoint from active waypoints if it's close to a location
+ *
+ * @param location center location
+ */
+ private static void removeUnknownNear(BlockPos location) {
+ String name = MiningLocationLabel.CrystalHollowsLocationsCategory.UNKNOWN.getName();
+ MiningLocationLabel unknownWaypoint = activeWaypoints.getOrDefault(name, null);
+ if (unknownWaypoint != null) {
+ double distance = unknownWaypoint.centerPos().distanceTo(location.toCenterPos());
+ if (distance < REMOVE_UNKNOWN_DISTANCE) {
+ activeWaypoints.remove(name);
+ }
+ }
+ }
+
public static void render(WorldRenderContext context) {
if (SkyblockerConfigManager.get().mining.crystalsWaypoints.enabled) {
- for (CrystalsWaypoint crystalsWaypoint : activeWaypoints.values()) {
- if (crystalsWaypoint.shouldRender()) {
- crystalsWaypoint.render(context);
- }
+ for (MiningLocationLabel crystalsWaypoint : activeWaypoints.values()) {
+ crystalsWaypoint.render(context);
}
}
}
private static void reset() {
activeWaypoints.clear();
+ verifiedWaypoints.clear();
}
public static void update() {
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsWaypoint.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsWaypoint.java
deleted file mode 100644
index dc40f82c..00000000
--- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsWaypoint.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package de.hysky.skyblocker.skyblock.dwarven;
-
-import de.hysky.skyblocker.config.SkyblockerConfigManager;
-import de.hysky.skyblocker.config.configs.UIAndVisualsConfig;
-import de.hysky.skyblocker.utils.render.RenderHelper;
-import de.hysky.skyblocker.utils.waypoint.Waypoint;
-import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.entity.Entity;
-import net.minecraft.text.Text;
-import net.minecraft.util.DyeColor;
-import net.minecraft.util.Formatting;
-import net.minecraft.util.math.BlockPos;
-import net.minecraft.util.math.Vec3d;
-
-import java.awt.*;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
-import java.util.function.ToDoubleFunction;
-
-public class CrystalsWaypoint extends Waypoint {
- private static final Supplier<UIAndVisualsConfig.Waypoints> CONFIG = () -> SkyblockerConfigManager.get().uiAndVisuals.waypoints;
- private static final Supplier<Type> TYPE_SUPPLIER = () -> CONFIG.get().waypointType;
- final Category category;
- final Text name;
- private final Vec3d centerPos;
-
- CrystalsWaypoint(Category category, Text name, BlockPos pos) {
- super(pos, TYPE_SUPPLIER, category.colorComponents);
- this.category = category;
- this.name = name;
- this.centerPos = pos.toCenterPos();
- }
-
- static ToDoubleFunction<CrystalsWaypoint> getSquaredDistanceToFunction(Entity entity) {
- return crystalsWaypoint -> entity.squaredDistanceTo(crystalsWaypoint.centerPos);
- }
-
- static Predicate<CrystalsWaypoint> getRangePredicate(Entity entity) {
- return crystalsWaypoint -> entity.squaredDistanceTo(crystalsWaypoint.centerPos) <= 36D;
- }
-
- @Override
- public boolean shouldRender() {
- return super.shouldRender();
- }
-
- @Override
- public boolean equals(Object obj) {
- return super.equals(obj) || obj instanceof CrystalsWaypoint other && category == other.category && name.equals(other.name) && pos.equals(other.pos);
- }
-
- /**
- * Renders the secret waypoint, including a waypoint through {@link Waypoint#render(WorldRenderContext)}, the name, and the distance from the player.
- */
- @Override
- public void render(WorldRenderContext context) {
- super.render(context);
-
- Vec3d posUp = centerPos.add(0, 1, 0);
- RenderHelper.renderText(context, name, posUp, true);
- double distance = context.camera().getPos().distanceTo(centerPos);
- RenderHelper.renderText(context, Text.literal(Math.round(distance) + "m").formatted(Formatting.YELLOW), posUp, 1, MinecraftClient.getInstance().textRenderer.fontHeight + 1, true);
-
- }
-
- /**
- * enum for the different waypoints used int the crystals hud each with a {@link Category#name} and associated {@link Category#color}
- */
- enum Category {
- JUNGLE_TEMPLE("Jungle Temple", new Color(DyeColor.PURPLE.getSignColor())),
- MINES_OF_DIVAN("Mines of Divan", Color.GREEN),
- GOBLIN_QUEENS_DEN("Goblin Queen's Den", new Color(DyeColor.ORANGE.getSignColor())),
- LOST_PRECURSOR_CITY("Lost Precursor City", Color.CYAN),
- KHAZAD_DUM("Khazad-dûm", Color.YELLOW),
- FAIRY_GROTTO("Fairy Grotto", Color.PINK),
- DRAGONS_LAIR("Dragon's Lair", Color.BLACK),
- CORLEONE("Corleone", Color.WHITE),
- KING_YOLKAR("King Yolkar", Color.RED),
- ODAWA("Odawa", Color.MAGENTA),
- KEY_GUARDIAN("Key Guardian", Color.LIGHT_GRAY);
-
- public final Color color;
- private final String name;
- private final float[] colorComponents;
-
- Category(String name, Color color) {
- this.name = name;
- this.color = color;
- this.colorComponents = color.getColorComponents(null);
- }
-
- @Override
- public String toString() {
- return name;
- }
- }
-}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetector.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetector.java
index 294b2c3d..ae45ff0b 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetector.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/MetalDetector.java
@@ -4,6 +4,8 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.utils.Constants;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.render.RenderHelper;
+import de.hysky.skyblocker.utils.waypoint.NamedWaypoint;
+import de.hysky.skyblocker.utils.waypoint.Waypoint;
import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
@@ -17,6 +19,7 @@ import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.Vec3i;
+import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -26,7 +29,7 @@ import java.util.regex.Pattern;
public class MetalDetector {
private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
- private static final float[] LIGHT_GRAY = { 192 / 255f, 192 / 255f, 192 / 255f };
+ private static final float[] LIGHT_GRAY = {192 / 255f, 192 / 255f, 192 / 255f};
private static final Pattern TREASURE_PATTERN = Pattern.compile("(§3§lTREASURE: §b)(\\d+\\.?\\d?)m");
private static final Pattern KEEPER_PATTERN = Pattern.compile("Keeper of (\\w+)");
private static final Map<String, Vec3i> keeperOffsets = Map.of(
@@ -242,14 +245,14 @@ public class MetalDetector {
//only one location render just that and guiding line to it
if (possibleBlocks.size() == 1) {
Vec3i block = possibleBlocks.getFirst().add(0, -1, 0); //the block you are taken to is one block above the chest
- CrystalsWaypoint waypoint = new CrystalsWaypoint(CrystalsWaypoint.Category.CORLEONE, Text.translatable("skyblocker.dwarvenMines.metalDetectorHelper.treasure"), new BlockPos(block.getX(), block.getY(), block.getZ()));
+ NamedWaypoint waypoint = new NamedWaypoint(new BlockPos(block.getX(), block.getY(), block.getZ()), Text.translatable("skyblocker.dwarvenMines.metalDetectorHelper.treasure").getString(), Color.yellow.getColorComponents(null));
waypoint.render(context);
RenderHelper.renderLineFromCursor(context, Vec3d.ofCenter(block), LIGHT_GRAY, 1f, 5f);
return;
}
for (Vec3i block : possibleBlocks) {
- CrystalsWaypoint waypoint = new CrystalsWaypoint(CrystalsWaypoint.Category.CORLEONE, Text.translatable("skyblocker.dwarvenMines.metalDetectorHelper.possible"), new BlockPos(block.getX(), block.getY(), block.getZ()));
+ NamedWaypoint waypoint = new NamedWaypoint(new BlockPos(block.getX(), block.getY(), block.getZ()), Text.translatable("skyblocker.dwarvenMines.metalDetectorHelper.possible").getString(), Color.white.getColorComponents(null));
waypoint.render(context);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/MiningLocationLabel.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/MiningLocationLabel.java
index 1f373b55..3817f6c7 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/MiningLocationLabel.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/MiningLocationLabel.java
@@ -1,15 +1,20 @@
package de.hysky.skyblocker.skyblock.dwarven;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.render.RenderHelper;
import de.hysky.skyblocker.utils.render.Renderable;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;
+import net.minecraft.util.DyeColor;
import net.minecraft.util.Formatting;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
+import java.awt.*;
+
+// TODO: Clean up into the waypoint system with a new `DistancedWaypoint` that extends `NamedWaypoint` for this and secret waypoints.
public record MiningLocationLabel(Category category, Vec3d centerPos) implements Renderable {
public MiningLocationLabel(Category category, BlockPos pos) {
this(category, pos.toCenterPos());
@@ -24,13 +29,16 @@ public record MiningLocationLabel(Category category, Vec3d centerPos) implements
/**
* Renders the name and distance to the label scaled so can be seen at a distance
+ *
* @param context render context
*/
@Override
public void render(WorldRenderContext context) {
Vec3d posUp = centerPos.add(0, 1, 0);
double distance = context.camera().getPos().distanceTo(centerPos);
- float scale = (float) (SkyblockerConfigManager.get().mining.commissionWaypoints.textScale * (distance / 10));
+ //set scale config based on if in crystals or not
+ float textScale = Utils.isInCrystalHollows() ? SkyblockerConfigManager.get().mining.crystalsWaypoints.textScale : SkyblockerConfigManager.get().mining.commissionWaypoints.textScale;
+ float scale = (float) (textScale * (distance / 10));
RenderHelper.renderText(context, getName(), posUp, scale, true);
RenderHelper.renderText(context, Text.literal(Math.round(distance) + "m").formatted(Formatting.YELLOW), posUp, scale, MinecraftClient.getInstance().textRenderer.fontHeight + 1, true);
}
@@ -154,4 +162,47 @@ public record MiningLocationLabel(Category category, Vec3d centerPos) implements
return color;
}
}
+
+ /**
+ * enum for the different waypoints used int the crystals hud each with a {@link CrystalHollowsLocationsCategory#name} and associated {@link CrystalHollowsLocationsCategory#color}
+ */
+ enum CrystalHollowsLocationsCategory implements Category {
+ UNKNOWN("Unknown", Color.WHITE, null), //used when a location is known but what's at the location is not known
+ JUNGLE_TEMPLE("Jungle Temple", new Color(DyeColor.PURPLE.getSignColor()), "[NPC] Kalhuiki Door Guardian:"),
+ MINES_OF_DIVAN("Mines of Divan", Color.GREEN, " Jade Crystal"),
+ GOBLIN_QUEENS_DEN("Goblin Queen's Den", new Color(DyeColor.ORANGE.getSignColor()), " Amber Crystal"),
+ LOST_PRECURSOR_CITY("Lost Precursor City", Color.CYAN, " Sapphire Crystal"),
+ KHAZAD_DUM("Khazad-dûm", Color.YELLOW, " Topaz Crystal"),
+ FAIRY_GROTTO("Fairy Grotto", Color.PINK, null),
+ DRAGONS_LAIR("Dragon's Lair", Color.BLACK, null),
+ CORLEONE("Corleone", Color.WHITE, null),
+ KING_YOLKAR("King Yolkar", Color.RED, "[NPC] King Yolkar:"),
+ ODAWA("Odawa", Color.MAGENTA, "[NPC] Odawa:"),
+ KEY_GUARDIAN("Key Guardian", Color.LIGHT_GRAY, null);
+
+ public final Color color;
+ private final String name;
+ private final String linkedMessage;
+
+ CrystalHollowsLocationsCategory(String name, Color color, String linkedMessage) {
+ this.name = name;
+ this.color = color;
+ this.linkedMessage = linkedMessage;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public int getColor() {
+ return this.color.getRGB();
+ }
+
+ public String getLinkedMessage() {
+ return this.linkedMessage;
+ }
+ }
+
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/WishingCompassSolver.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/WishingCompassSolver.java
new file mode 100644
index 00000000..7b14002b
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/WishingCompassSolver.java
@@ -0,0 +1,389 @@
+package de.hysky.skyblocker.skyblock.dwarven;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.utils.Constants;
+import de.hysky.skyblocker.utils.Utils;
+import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
+import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
+import net.fabricmc.fabric.api.event.player.UseBlockCallback;
+import net.fabricmc.fabric.api.event.player.UseItemCallback;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.network.packet.s2c.play.ParticleS2CPacket;
+import net.minecraft.particle.ParticleTypes;
+import net.minecraft.text.Text;
+import net.minecraft.util.ActionResult;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.Hand;
+import net.minecraft.util.TypedActionResult;
+import net.minecraft.util.hit.BlockHitResult;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Box;
+import net.minecraft.util.math.Vec3d;
+import net.minecraft.world.World;
+import org.apache.commons.math3.geometry.euclidean.threed.Line;
+import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+public class WishingCompassSolver {
+ private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
+ private static final Map<Zone, Box> ZONE_BOUNDING_BOXES = Map.of(
+ Zone.CRYSTAL_NUCLEUS, new Box(462, 63, 461, 564, 181, 565),
+ Zone.JUNGLE, new Box(201, 63, 201, 513, 189, 513),
+ Zone.MITHRIL_DEPOSITS, new Box(512, 63, 201, 824, 189, 513),
+ Zone.GOBLIN_HOLDOUT, new Box(201, 63, 512, 513, 189, 824),
+ Zone.PRECURSOR_REMNANTS, new Box(512, 63, 512, 824, 189, 824),
+ Zone.MAGMA_FIELDS, new Box(201, 30, 201, 824, 64, 824)
+ );
+ private static final Vec3d JUNGLE_TEMPLE_DOOR_OFFSET = new Vec3d(-57, 36, -21);
+ /**
+ * The number of particles to use to get direction of a line
+ */
+ private static final long PARTICLES_PER_LINE = 25;
+ /**
+ * The time in milliseconds to wait for the next particle until assumed failed
+ */
+ private static final long PARTICLES_MAX_DELAY = 500;
+ /**
+ * The maximum distance a particle can be from the last to be considered part of the line
+ */
+ private static final double PARTICLES_MAX_DISTANCE = 0.9;
+ /**
+ * the distance squared the player has to be from where they used the first compass to where they use the second
+ */
+ private static final long DISTANCE_BETWEEN_USES = 64;
+
+ private static SolverStates currentState = SolverStates.NOT_STARTED;
+ private static Vec3d startPosOne = Vec3d.ZERO;
+ private static Vec3d startPosTwo = Vec3d.ZERO;
+ private static Vec3d directionOne = Vec3d.ZERO;
+ private static Vec3d directionTwo = Vec3d.ZERO;
+ private static long particleUsedCountOne = 0;
+ private static long particleUsedCountTwo = 0;
+ private static long particleLastUpdate = System.currentTimeMillis();
+ private static Vec3d particleLastPos = Vec3d.ZERO;
+
+
+ public static void init() {
+ UseItemCallback.EVENT.register(WishingCompassSolver::onItemInteract);
+ UseBlockCallback.EVENT.register(WishingCompassSolver::onBlockInteract);
+ ClientReceiveMessageEvents.GAME.register(WishingCompassSolver::failMessageListener);
+ ClientPlayConnectionEvents.JOIN.register((_handler, _sender, _client) -> reset());
+ }
+
+ /**
+ * When a filed message is sent in chat, reset the wishing compass solver to start
+ * @param text message
+ * @param b overlay
+ */
+ private static void failMessageListener(Text text, boolean b) {
+ if (!Utils.isInCrystalHollows()) {
+ return;
+ }
+ if (Formatting.strip(text.getString()).equals("The Wishing Compass can't seem to locate anything!")) {
+ currentState = SolverStates.NOT_STARTED;
+ }
+ }
+
+ private static void reset() {
+ currentState = SolverStates.NOT_STARTED;
+ startPosOne = Vec3d.ZERO;
+ startPosTwo = Vec3d.ZERO;
+ directionOne = Vec3d.ZERO;
+ directionTwo = Vec3d.ZERO;
+ particleUsedCountOne = 0;
+ particleUsedCountTwo = 0;
+ particleLastUpdate = System.currentTimeMillis();
+ particleLastPos = Vec3d.ZERO;
+ }
+
+ private static boolean isKingsScentPresent() {
+ String footer = PlayerListMgr.getFooter();
+ if (footer == null) {
+ return false;
+ }
+ return footer.contains("King's Scent I");
+ }
+
+ private static boolean isKeyInInventory() {
+ return CLIENT.player != null && CLIENT.player.getInventory().main.stream().anyMatch(stack -> stack != null && Objects.equals(stack.getSkyblockId(), "JUNGLE_KEY"));
+ }
+
+ private static Zone getZoneOfLocation(Vec3d location) {
+ return ZONE_BOUNDING_BOXES.entrySet().stream().filter(zone -> zone.getValue().contains(location)).findFirst().map(Map.Entry::getKey).orElse(Zone.CRYSTAL_NUCLEUS); //default to nucleus if somehow not in another zone
+ }
+
+ private static Boolean isZoneComplete(Zone zone) {
+ if (CLIENT.getNetworkHandler() == null || CLIENT.player == null) {
+ return false;
+ }
+
+ //make sure the data is in tab and if not tell the user
+ if (PlayerListMgr.getPlayerStringList().stream().noneMatch(entry -> entry.equals("Crystals:"))) {
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.enableTabMessage")), false);
+ return false;
+ }
+
+ //return if the crystal for a zone is found
+ Stream<String> displayNameStream = PlayerListMgr.getPlayerStringList().stream();
+ return switch (zone) {
+ case JUNGLE -> displayNameStream.noneMatch(entry -> entry.equals("Amethyst: ✖ Not Found"));
+ case MITHRIL_DEPOSITS -> displayNameStream.noneMatch(entry -> entry.equals("Jade: ✖ Not Found"));
+ case GOBLIN_HOLDOUT -> displayNameStream.noneMatch(entry -> entry.equals("Amber: ✖ Not Found"));
+ case PRECURSOR_REMNANTS -> displayNameStream.noneMatch(entry -> entry.equals("Sapphire: ✖ Not Found"));
+ case MAGMA_FIELDS -> displayNameStream.noneMatch(entry -> entry.equals("Topaz: ✖ Not Found"));
+ default -> false;
+ };
+ }
+
+ private static MiningLocationLabel.CrystalHollowsLocationsCategory getTargetLocation(Zone startingZone) {
+ //if the zone is complete return null
+ if (isZoneComplete(startingZone)) {
+ return MiningLocationLabel.CrystalHollowsLocationsCategory.UNKNOWN;
+ }
+ return switch (startingZone) {
+ case JUNGLE -> isKeyInInventory() ? MiningLocationLabel.CrystalHollowsLocationsCategory.JUNGLE_TEMPLE : MiningLocationLabel.CrystalHollowsLocationsCategory.ODAWA;
+ case MITHRIL_DEPOSITS -> MiningLocationLabel.CrystalHollowsLocationsCategory.MINES_OF_DIVAN;
+ case GOBLIN_HOLDOUT -> isKingsScentPresent() ? MiningLocationLabel.CrystalHollowsLocationsCategory.GOBLIN_QUEENS_DEN : MiningLocationLabel.CrystalHollowsLocationsCategory.KING_YOLKAR;
+ case PRECURSOR_REMNANTS -> MiningLocationLabel.CrystalHollowsLocationsCategory.LOST_PRECURSOR_CITY;
+ case MAGMA_FIELDS -> MiningLocationLabel.CrystalHollowsLocationsCategory.KHAZAD_DUM;
+ default -> MiningLocationLabel.CrystalHollowsLocationsCategory.UNKNOWN;
+ };
+ }
+
+ /**
+ * Verifies that a location could be correct and not to far out of zone. This is a problem when areas sometimes do not exist and is not a perfect solution
+ * @param startingZone zone player is searching in
+ * @param pos location where the area should be
+ * @return corrected location
+ */
+ private static Boolean verifyLocation(Zone startingZone, Vec3d pos) {
+ return ZONE_BOUNDING_BOXES.get(startingZone).expand(100, 0, 100).contains(pos);
+ }
+
+ public static void onParticle(ParticleS2CPacket packet) {
+ if (!Utils.isInCrystalHollows() || !ParticleTypes.HAPPY_VILLAGER.equals(packet.getParameters().getType())) {
+ return;
+ }
+ //get location of particle
+ Vec3d particlePos = new Vec3d(packet.getX(), packet.getY(), packet.getZ());
+ //update particle used time
+ particleLastUpdate = System.currentTimeMillis();
+ //ignore particle not in the line
+ if (particlePos.distanceTo(particleLastPos) > PARTICLES_MAX_DISTANCE) {
+ return;
+ }
+ particleLastPos = particlePos;
+
+ switch (currentState) {
+ case PROCESSING_FIRST_USE -> {
+ Vec3d particleDirection = particlePos.subtract(startPosOne).normalize();
+ //move direction to fit with particle
+ directionOne = directionOne.add(particleDirection.multiply((double) 1 / PARTICLES_PER_LINE));
+ particleUsedCountOne += 1;
+ //if used enough particle go to next state
+ if (particleUsedCountOne >= PARTICLES_PER_LINE) {
+ currentState = SolverStates.WAITING_FOR_SECOND;
+ if (CLIENT.player != null) {
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.wishingCompassUsedMessage").formatted(Formatting.GREEN)), false);
+ }
+ }
+ }
+ case PROCESSING_SECOND_USE -> {
+ Vec3d particleDirection = particlePos.subtract(startPosTwo).normalize();
+ //move direction to fit with particle
+ directionTwo = directionTwo.add(particleDirection.multiply((double) 1 / PARTICLES_PER_LINE));
+ particleUsedCountTwo += 1;
+ //if used enough particle go to next state
+ if (particleUsedCountTwo >= PARTICLES_PER_LINE) {
+ processSolution();
+ }
+ }
+ }
+ }
+
+ private static void processSolution() {
+ if (CLIENT.player == null) {
+ reset();
+ return;
+ }
+ Vec3d targetLocation = solve(startPosOne, startPosTwo, directionOne, directionTwo);
+ if (targetLocation == null) {
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.somethingWentWrongMessage").formatted(Formatting.RED)), false);
+ } else {
+ //send message to player with location and name
+ Zone playerZone = getZoneOfLocation(startPosOne);
+ MiningLocationLabel.CrystalHollowsLocationsCategory location = getTargetLocation(playerZone);
+ //set to unknown if the target is to far from the region it's allowed to spawn in
+ if (!verifyLocation(playerZone, targetLocation)) {
+ location = MiningLocationLabel.CrystalHollowsLocationsCategory.UNKNOWN;
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.targetLocationToFar").formatted(Formatting.RED)), false);
+ }
+ //offset the jungle location to its doors
+ if (location == MiningLocationLabel.CrystalHollowsLocationsCategory.JUNGLE_TEMPLE) {
+ targetLocation = targetLocation.add(JUNGLE_TEMPLE_DOOR_OFFSET);
+ }
+
+ CLIENT.player.sendMessage(Constants.PREFIX.get()
+ .append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.foundMessage").formatted(Formatting.GREEN))
+ .append(Text.literal(location.getName()).withColor(location.getColor()))
+ .append(Text.literal(": " + (int) targetLocation.getX() + " " + (int) targetLocation.getY() + " " + (int) targetLocation.getZ())),
+ false);
+
+ //add waypoint
+ CrystalsLocationsManager.addCustomWaypoint(location.getName(), BlockPos.ofFloored(targetLocation));
+ }
+
+ //reset ready for another go
+ reset();
+ }
+
+ /**
+ * using the stating locations and line direction solve for where the location must be
+ */
+ protected static Vec3d solve(Vec3d startPosOne, Vec3d startPosTwo, Vec3d directionOne, Vec3d directionTwo) {
+ //convert format to get lines for the intersection solving
+ Vector3D lineOneStart = new Vector3D(startPosOne.x, startPosOne.y, startPosOne.z);
+ Vector3D lineOneEnd = new Vector3D(directionOne.x, directionOne.y, directionOne.z).add(lineOneStart);
+ Vector3D lineTwoStart = new Vector3D(startPosTwo.x, startPosTwo.y, startPosTwo.z);
+ Vector3D lineTwoEnd = new Vector3D(directionTwo.x, directionTwo.y, directionTwo.z).add(lineTwoStart);
+ Line line = new Line(lineOneStart, lineOneEnd, 1);
+ Line lineTwo = new Line(lineTwoStart, lineTwoEnd, 1);
+ Vector3D intersection = line.intersection(lineTwo);
+
+ //return final target location
+ if (intersection == null || intersection.equals(new Vector3D(0, 0, 0))) {
+ return null;
+ }
+ return new Vec3d(intersection.getX(), intersection.getY(), intersection.getZ());
+ }
+
+ private static ActionResult onBlockInteract(PlayerEntity playerEntity, World world, Hand hand, BlockHitResult blockHitResult) {
+ if (CLIENT.player == null) {
+ return null;
+ }
+ ItemStack stack = CLIENT.player.getStackInHand(hand);
+ //make sure the user is in the crystal hollows and holding the wishing compass
+ if (!Utils.isInCrystalHollows() || !SkyblockerConfigManager.get().mining.crystalsWaypoints.wishingCompassSolver || !Objects.equals(stack.getSkyblockId(), "WISHING_COMPASS")) {
+ return ActionResult.PASS;
+ }
+ if (useCompass()) {
+ return ActionResult.FAIL;
+ }
+
+ return ActionResult.PASS;
+ }
+
+ private static TypedActionResult<ItemStack> onItemInteract(PlayerEntity playerEntity, World world, Hand hand) {
+ if (CLIENT.player == null) {
+ return null;
+ }
+ ItemStack stack = CLIENT.player.getStackInHand(hand);
+ //make sure the user is in the crystal hollows and holding the wishing compass
+ if (!Utils.isInCrystalHollows() || !SkyblockerConfigManager.get().mining.crystalsWaypoints.wishingCompassSolver || !Objects.equals(stack.getSkyblockId(), "WISHING_COMPASS")) {
+ return TypedActionResult.pass(stack);
+ }
+ if (useCompass()) {
+ return TypedActionResult.fail(stack);
+ }
+
+ return TypedActionResult.pass(stack);
+ }
+
+ /**
+ * Computes what to do next when a compass is used.
+ *
+ * @return if the use event should be canceled
+ */
+ private static boolean useCompass() {
+ if (CLIENT.player == null) {
+ return true;
+ }
+ Vec3d playerPos = CLIENT.player.getEyePos();
+ Zone currentZone = getZoneOfLocation(playerPos);
+
+ switch (currentState) {
+ case NOT_STARTED -> {
+ //do not start if the player is in nucleus as this does not work well
+ if (currentZone == Zone.CRYSTAL_NUCLEUS) {
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.useOutsideNucleusMessage")), false);
+ return true;
+ }
+ startNewState(SolverStates.PROCESSING_FIRST_USE);
+ }
+
+ case WAITING_FOR_SECOND -> {
+ //only continue if the player is far enough away from the first position to get a better reading
+ if (startPosOne.squaredDistanceTo(playerPos) < DISTANCE_BETWEEN_USES) {
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.moveFurtherMessage")), false);
+ return true;
+ } else {
+ //make sure the player is in the same zone as they used to first or restart
+ if (currentZone != getZoneOfLocation(startPosOne)) {
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.changingZoneMessage")), false);
+ reset();
+ startNewState(SolverStates.PROCESSING_FIRST_USE);
+ } else {
+ startNewState(SolverStates.PROCESSING_SECOND_USE);
+ }
+ }
+ }
+
+ case PROCESSING_FIRST_USE, PROCESSING_SECOND_USE -> {
+ //if still looking for particles for line tell the user to wait
+ //else tell the user something went wrong and its starting again
+ if (System.currentTimeMillis() - particleLastUpdate < PARTICLES_MAX_DELAY) {
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.waitLongerMessage").formatted(Formatting.RED)), false);
+ return true;
+ } else {
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.couldNotDetectLastUseMessage").formatted(Formatting.RED)), false);
+ reset();
+ startNewState(SolverStates.PROCESSING_FIRST_USE);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static void startNewState(SolverStates newState) {
+ if (CLIENT.player == null) {
+ return;
+ }
+ //get where eye pos independent of if player is crouching
+ Vec3d playerPos = CLIENT.player.getPos().add(0, 1.62, 0);
+
+ if (newState == SolverStates.PROCESSING_FIRST_USE) {
+ currentState = SolverStates.PROCESSING_FIRST_USE;
+ startPosOne = playerPos;
+ particleLastUpdate = System.currentTimeMillis();
+ particleLastPos = playerPos;
+ } else if (newState == SolverStates.PROCESSING_SECOND_USE) {
+ currentState = SolverStates.PROCESSING_SECOND_USE;
+ startPosTwo = playerPos;
+ particleLastUpdate = System.currentTimeMillis();
+ particleLastPos = playerPos;
+ }
+ }
+
+ private enum SolverStates {
+ NOT_STARTED,
+ PROCESSING_FIRST_USE,
+ WAITING_FOR_SECOND,
+ PROCESSING_SECOND_USE,
+ }
+
+ private enum Zone {
+ CRYSTAL_NUCLEUS,
+ JUNGLE,
+ MITHRIL_DEPOSITS,
+ GOBLIN_HOLDOUT,
+ PRECURSOR_REMNANTS,
+ MAGMA_FIELDS,
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListMgr.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListMgr.java
index 472cf700..b08a09d6 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListMgr.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListMgr.java
@@ -1,19 +1,20 @@
package de.hysky.skyblocker.skyblock.tabhud.util;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
import de.hysky.skyblocker.mixins.accessors.PlayerListHudAccessor;
import de.hysky.skyblocker.utils.Utils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* This class may be used to get data from the player list. It doesn't get its
@@ -24,8 +25,15 @@ public class PlayerListMgr {
public static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Regex");
- private static List<PlayerListEntry> playerList;
- private static String footer;
+ /**
+ * The player list in tab.
+ */
+ private static List<PlayerListEntry> playerList = new ArrayList<>(); // Initialize to prevent npe.
+ /**
+ * The player list in tab, but a list of strings instead of {@link PlayerListEntry}s.
+ */
+ private static List<String> playerStringList = new ArrayList<>();
+ private static String footer;
public static void updateList() {
@@ -38,25 +46,40 @@ public class PlayerListMgr {
// check is needed, else game crashes on server leave
if (cpnwh != null) {
playerList = cpnwh.getPlayerList().stream().sorted(PlayerListHudAccessor.getOrdering()).toList();
+ playerStringList = playerList.stream().map(PlayerListEntry::getDisplayName).filter(Objects::nonNull).map(Text::getString).map(String::strip).toList();
}
}
- public static void updateFooter(Text f) {
- if (f == null) {
- footer = null;
- } else {
- footer = f.getString();
- }
- }
+ /**
+ * @return the cached player list
+ */
+ public static List<PlayerListEntry> getPlayerList() {
+ return playerList;
+ }
+
+ /**
+ * @return the cached player list as a list of strings
+ */
+ public static List<String> getPlayerStringList() {
+ return playerStringList;
+ }
+
+ public static void updateFooter(Text f) {
+ if (f == null) {
+ footer = null;
+ } else {
+ footer = f.getString();
+ }
+ }
- public static String getFooter() {
- return footer;
- }
+ public static String getFooter() {
+ return footer;
+ }
/**
* Get the display name at some index of the player list and apply a pattern to
* it
- *
+ *
* @return the matcher if p fully matches, else null
*/
public static Matcher regexAt(int idx, Pattern p) {
@@ -78,7 +101,7 @@ public class PlayerListMgr {
/**
* Get the display name at some index of the player list as string
- *
+ *
* @return the string or null, if the display name is null, empty or whitespace
* only
*/
@@ -105,9 +128,9 @@ public class PlayerListMgr {
/**
* Gets the display name at some index of the player list
- *
+ *
* @return the text or null, if the display name is null
- *
+ *
* @implNote currently designed specifically for crimson isles faction quests
* widget and the rift widgets, might not work correctly without
* modification for other stuff. you've been warned!
@@ -157,7 +180,7 @@ public class PlayerListMgr {
/**
* Get the display name at some index of the player list as Text as seen in the
* game
- *
+ *
* @return the PlayerListEntry at that index
*/
public static PlayerListEntry getRaw(int idx) {
diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json
index 2c19e036..984ef321 100644
--- a/src/main/resources/assets/skyblocker/lang/en_us.json
+++ b/src/main/resources/assets/skyblocker/lang/en_us.json
@@ -476,9 +476,32 @@
"skyblocker.config.mining.crystalsWaypoints": "Crystal Hollows Waypoints",
"skyblocker.config.mining.crystalsWaypoints.enabled": "Enabled Waypoints",
"skyblocker.config.mining.crystalsWaypoints.enabled.@Tooltip": "Show a waypoint (waypoint selected in general/waypoints) at important areas in the crystal hollows, e.g., Jungle Temple and Fairy Grotto. ",
+ "skyblocker.config.mining.crystalsWaypoints.textScale": "Text Scale",
+ "skyblocker.config.mining.crystalsWaypoints.textScale.@Tooltip": "Scale the size of the commission labels.",
"skyblocker.config.mining.crystalsWaypoints.findInChat": "Find Waypoints In Chat",
"skyblocker.config.mining.crystalsWaypoints.findInChat.@Tooltip": "When in crystal hollows read the chat to see if coordinates are sent and extract these to show as waypoint or on the map",
+ "skyblocker.config.mining.crystalsWaypoints.addedWaypoint": "Added waypoint for '%s' at %d %d %d.",
+ "skyblocker.config.mining.crystalsWaypoints.noActive": "You have no active waypoints to share or remove.",
+ "skyblocker.config.mining.crystalsWaypoints.allActive": "You have no more waypoints left to create.",
+ "skyblocker.config.mining.crystalsWaypoints.markLocation": "Mark (%s) as: %s",
+ "skyblocker.config.mining.crystalsWaypoints.getLocationHover.add": "Add Location",
+ "skyblocker.config.mining.crystalsWaypoints.getLocationHover.remove": "Remove Location",
+ "skyblocker.config.mining.crystalsWaypoints.getLocationHover.share": "Share Location",
"skyblocker.config.mining.crystalsWaypoints.shareFail": "Can only share waypoints you have found.",
+ "skyblocker.config.mining.crystalsWaypoints.removeSuccess": "Removed waypoint for ",
+ "skyblocker.config.mining.crystalsWaypoints.removeFail": "Can only remove waypoints you have found.",
+ "skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver": "Wishing Compass Solver",
+ "skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.@Tooltip": "Calculates and adds a waypoint at the location indicated by wishing compasses.",
+ "skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.enableTabMessage": "Enable crystals in the /tab to allow the compass to identify discovered items",
+ "skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.wishingCompassUsedMessage": "Wishing compass used. Move to another location and use a second compass to triangulate the target",
+ "skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.somethingWentWrongMessage": "The lines did not intersect (Something went wrong) please try again.",
+ "skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.targetLocationToFar": "The target location is too far away to identify the structure, possibly due to a missing structure in the lobby.",
+ "skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.foundMessage": "Wishing compass solver found ",
+ "skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.useOutsideNucleusMessage": "Use compass outside of nucleus for better results",
+ "skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.moveFurtherMessage": "Move further away from the first use before using again",
+ "skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.changingZoneMessage": "Changed zone. Restarting...",
+ "skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.waitLongerMessage": "Wait a moment before using another wishing compass",
+ "skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.couldNotDetectLastUseMessage": "Unable to detect the last use. Restarting...",
"skyblocker.config.mining.dwarvenHud": "Dwarven HUD",
"skyblocker.config.mining.dwarvenHud.enabledCommissions": "Enable Commissions",