aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAaron <51387595+AzureAaron@users.noreply.github.com>2024-02-04 12:49:53 -0500
committerGitHub <noreply@github.com>2024-02-04 12:49:53 -0500
commitfe1bc0589caef8ff9f2d616a3742e63e2668f00e (patch)
tree06e8e1b2ee3e3a9381dc042650f4fe6ac86582c5
parentce90376c4a8a934cc4d2f8bc4dd0e5e1c71b3748 (diff)
parent71467890a61eb4e05793b1856f6303787216f2ec (diff)
downloadSkyblocker-fe1bc0589caef8ff9f2d616a3742e63e2668f00e.tar.gz
Skyblocker-fe1bc0589caef8ff9f2d616a3742e63e2668f00e.tar.bz2
Skyblocker-fe1bc0589caef8ff9f2d616a3742e63e2668f00e.zip
Merge pull request #523 from olim88/crystal-hollows-fetures
Crystal hollows fetures
-rw-r--r--MRREADME.md9
-rw-r--r--README.md9
-rwxr-xr-x[-rw-r--r--]gradlew.bat0
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java45
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/DwarvenMinesCategory.java82
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHud.java164
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHudConfigScreen.java69
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java195
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsWaypoint.java98
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java181
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHudConfigScreen.java36
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/hud/HudPowderWidget.java40
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Utils.java11
-rw-r--r--src/main/resources/assets/skyblocker/lang/en_us.json16
-rw-r--r--src/main/resources/assets/skyblocker/textures/gui/crystals_map.pngbin0 -> 38864 bytes
-rw-r--r--src/test/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHudTest.java19
-rw-r--r--src/test/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationManagerTest.java32
18 files changed, 943 insertions, 69 deletions
diff --git a/MRREADME.md b/MRREADME.md
index 8522d003..88c75b5e 100644
--- a/MRREADME.md
+++ b/MRREADME.md
@@ -62,8 +62,15 @@ Hypixel Skyblock Mod for Minecraft 1.20.x
- **Dwarven Mines Solver:**
- *Fetchur*
- *Puzzler*
-- **Commission HUD**
+- **Commission/Powder HUD**
- *Provides information on Dwarven Mines quests*
+ - *Provides information on powder amounts*
+- **Crystal Hollows Waypoints**
+ - *show waypoints for special location*
+ - *find locations in chat messages*
+- **Crystal Hollows Map HUD**
+ - *Shows players location in crystal hollows*
+ - *Shows important waypoints in crystal hollows*
</details>
<details>
diff --git a/README.md b/README.md
index e6fa68d0..bee317ba 100644
--- a/README.md
+++ b/README.md
@@ -63,8 +63,15 @@ Installation guide is [here](https://github.com/SkyblockerMod/Skyblocker/wiki/in
- **Dwarven Mines Solver:**
- *Fetchur*
- *Puzzler*
-- **Commission HUD**
+- **Commission/Powder HUD**
- *Provides information on Dwarven Mines quests*
+ - *Provides information on powder amounts*
+- **Crystal Hollows Waypoints**
+ - *show waypoints for special location*
+ - *find locations in chat messages*
+- **Crystal Hollows Map HUD**
+ - *Shows players location in crystal hollows*
+ - *Shows important waypoints in crystal hollows*
### Rift Features:
- **Mirrorverse Waypoints**
diff --git a/gradlew.bat b/gradlew.bat
index 6689b85b..6689b85b 100644..100755
--- a/gradlew.bat
+++ b/gradlew.bat
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 3fca09ce..c11e4c86 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -13,6 +13,8 @@ import de.hysky.skyblocker.skyblock.dungeon.puzzle.TicTacToe;
import de.hysky.skyblocker.skyblock.dungeon.puzzle.waterboard.Waterboard;
import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
import de.hysky.skyblocker.skyblock.dungeon.secrets.SecretsTracker;
+import de.hysky.skyblocker.skyblock.dwarven.CrystalsHud;
+import de.hysky.skyblocker.skyblock.dwarven.CrystalsLocationsManager;
import de.hysky.skyblocker.skyblock.dwarven.DwarvenHud;
import de.hysky.skyblocker.skyblock.end.BeaconHighlighter;
import de.hysky.skyblocker.skyblock.item.*;
@@ -99,6 +101,8 @@ public class SkyblockerMod implements ClientModInitializer {
QuickNav.init();
ItemCooldowns.init();
DwarvenHud.init();
+ CrystalsHud.init();
+ CrystalsLocationsManager.init();
ChatMessageListener.init();
Shortcuts.init();
DiscordRPCManager.init();
@@ -142,6 +146,8 @@ public class SkyblockerMod implements ClientModInitializer {
Scheduler.INSTANCE.scheduleCyclic(LividColor::update, 10);
Scheduler.INSTANCE.scheduleCyclic(BackpackPreview::tick, 50);
Scheduler.INSTANCE.scheduleCyclic(DwarvenHud::update, 40);
+ Scheduler.INSTANCE.scheduleCyclic(CrystalsHud::update, 40);
+ Scheduler.INSTANCE.scheduleCyclic(CrystalsLocationsManager::update, 40);
Scheduler.INSTANCE.scheduleCyclic(PlayerListMgr::updateList, 20);
}
diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
index a7569adb..4acb8064 100644
--- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
@@ -873,11 +873,20 @@ public class SkyblockerConfig {
@SerialEntry
public DwarvenHud dwarvenHud = new DwarvenHud();
+
+ @SerialEntry
+ public CrystalsHud crystalsHud = new CrystalsHud();
+
+ @SerialEntry
+ public CrystalsWaypoints crystalsWaypoints = new CrystalsWaypoints();
}
public static class DwarvenHud {
@SerialEntry
- public boolean enabled = true;
+ public boolean enabledCommissions = true;
+
+ @SerialEntry
+ public boolean enabledPowder = true;
@SerialEntry
public DwarvenHudStyle style = DwarvenHudStyle.SIMPLE;
@@ -890,6 +899,40 @@ public class SkyblockerConfig {
@SerialEntry
public int y = 10;
+
+ @SerialEntry
+ public int powderX = 10;
+
+ @SerialEntry
+ public int powderY = 70;
+ }
+
+ public static class CrystalsHud {
+ @SerialEntry
+ public boolean enabled = true;
+
+ @SerialEntry
+ public boolean showLocations = true;
+
+ @SerialEntry
+ public int locationSize = 8;
+
+ @SerialEntry
+ public int x = 10;
+
+ @SerialEntry
+ public int y = 130;
+
+ @SerialEntry
+ public float mapScaling = 1f;
+ }
+
+ public static class CrystalsWaypoints {
+ @SerialEntry
+ public boolean enabled = true;
+
+ @SerialEntry
+ public boolean findInChat = true;
}
public enum DwarvenHudStyle {
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/DwarvenMinesCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/DwarvenMinesCategory.java
index 80d6485b..97b48bc4 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/DwarvenMinesCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/DwarvenMinesCategory.java
@@ -2,12 +2,15 @@ package de.hysky.skyblocker.config.categories;
import de.hysky.skyblocker.config.ConfigUtils;
import de.hysky.skyblocker.config.SkyblockerConfig;
+import de.hysky.skyblocker.skyblock.dwarven.CrystalsHudConfigScreen;
import dev.isxander.yacl3.api.ButtonOption;
import dev.isxander.yacl3.api.ConfigCategory;
import dev.isxander.yacl3.api.Option;
import dev.isxander.yacl3.api.OptionDescription;
import dev.isxander.yacl3.api.OptionGroup;
import de.hysky.skyblocker.skyblock.dwarven.DwarvenHudConfigScreen;
+import dev.isxander.yacl3.api.controller.FloatFieldControllerBuilder;
+import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;
@@ -45,15 +48,22 @@ public class DwarvenMinesCategory {
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud"))
.collapsed(false)
.option(Option.<Boolean>createBuilder()
- .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud.enabled"))
- .binding(defaults.locations.dwarvenMines.dwarvenHud.enabled,
- () -> config.locations.dwarvenMines.dwarvenHud.enabled,
- newValue -> config.locations.dwarvenMines.dwarvenHud.enabled = newValue)
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud.enabledCommissions"))
+ .binding(defaults.locations.dwarvenMines.dwarvenHud.enabledCommissions,
+ () -> config.locations.dwarvenMines.dwarvenHud.enabledCommissions,
+ newValue -> config.locations.dwarvenMines.dwarvenHud.enabledCommissions = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud.enabledPowder"))
+ .binding(defaults.locations.dwarvenMines.dwarvenHud.enabledPowder,
+ () -> config.locations.dwarvenMines.dwarvenHud.enabledPowder,
+ newValue -> config.locations.dwarvenMines.dwarvenHud.enabledPowder = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
.option(Option.<SkyblockerConfig.DwarvenHudStyle>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud.style"))
- .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud.style.@Tooltip[0]"),
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud.style.@Tooltip[0]"),
Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud.style.@Tooltip[1]"),
Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud.style.@Tooltip[2]")))
.binding(defaults.locations.dwarvenMines.dwarvenHud.style,
@@ -74,6 +84,68 @@ public class DwarvenMinesCategory {
.controller(ConfigUtils::createBooleanController)
.build())
.build())
+ //crystal HUD
+ .group(OptionGroup.createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.crystalsHud"))
+ .collapsed(false)
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.crystalsHud.enabled"))
+ .binding(defaults.locations.dwarvenMines.crystalsHud.enabled,
+ () -> config.locations.dwarvenMines.crystalsHud.enabled,
+ newValue -> config.locations.dwarvenMines.crystalsHud.enabled = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(ButtonOption.createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.crystalsHud.screen"))
+ .text(Text.translatable("text.skyblocker.open"))
+ .action((screen, opt) -> MinecraftClient.getInstance().setScreen(new CrystalsHudConfigScreen(screen)))
+ .build())
+ .option(Option.<Float>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.crystalsHud.mapScaling"))
+ .binding(defaults.locations.dwarvenMines.crystalsHud.mapScaling,
+ () -> config.locations.dwarvenMines.crystalsHud.mapScaling,
+ newValue -> config.locations.dwarvenMines.crystalsHud.mapScaling = newValue)
+ .controller(FloatFieldControllerBuilder::create)
+ .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.crystalsHud.showLocations"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.crystalsHud.showLocations.@Tooltip")))
+ .binding(defaults.locations.dwarvenMines.crystalsHud.showLocations,
+ () -> config.locations.dwarvenMines.crystalsHud.showLocations,
+ newValue -> config.locations.dwarvenMines.crystalsHud.showLocations = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Integer>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.crystalsHud.showLocations.locationSize"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.crystalsHud.showLocations.locationSize.@Tooltip")))
+ .binding(defaults.locations.dwarvenMines.crystalsHud.locationSize,
+ () -> config.locations.dwarvenMines.crystalsHud.locationSize,
+ newValue -> config.locations.dwarvenMines.crystalsHud.locationSize = newValue)
+ .controller(opt -> IntegerSliderControllerBuilder.create(opt).range(4, 12).step(2))
+ .build())
+ .build())
+ //crystals waypoints
+ .group(OptionGroup.createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.crystalsWaypoints"))
+ .collapsed(false)
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.crystalsWaypoints.enabled"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.crystalsWaypoints.enabled.@Tooltip")))
+ .binding(defaults.locations.dwarvenMines.crystalsWaypoints.enabled,
+ () -> config.locations.dwarvenMines.crystalsWaypoints.enabled,
+ newValue -> config.locations.dwarvenMines.crystalsWaypoints.enabled = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.crystalsWaypoints.findInChat"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.crystalsWaypoints.findInChat.@Tooltip")))
+ .binding(defaults.locations.dwarvenMines.crystalsWaypoints.findInChat,
+ () -> config.locations.dwarvenMines.crystalsWaypoints.findInChat,
+ newValue -> config.locations.dwarvenMines.crystalsWaypoints.findInChat = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+
+ .build())
.build();
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHud.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHud.java
new file mode 100644
index 00000000..7b15c61e
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHud.java
@@ -0,0 +1,164 @@
+package de.hysky.skyblocker.skyblock.dwarven;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Utils;
+import de.hysky.skyblocker.utils.scheduler.Scheduler;
+import it.unimi.dsi.fastutil.ints.IntIntPair;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.math.MathHelper;
+import net.minecraft.util.math.RotationAxis;
+
+import java.awt.*;
+import java.util.Arrays;
+import java.util.Map;
+
+import org.joml.Vector2i;
+import org.joml.Vector2ic;
+
+public class CrystalsHud {
+ private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
+ protected static final Identifier MAP_TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/crystals_map.png");
+ private static final Identifier MAP_ICON = new Identifier("textures/map/map_icons.png");
+ private static final String[] SMALL_LOCATIONS = { "Fairy Grotto", "King Yolkar", "Corleone", "Odawa", "Key Guardian" };
+
+ public static boolean visible = false;
+
+ public static void init() {
+ ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("skyblocker")
+ .then(ClientCommandManager.literal("hud")
+ .then(ClientCommandManager.literal("crystals")
+ .executes(Scheduler.queueOpenScreenCommand(CrystalsHudConfigScreen::new))))));
+
+ HudRenderCallback.EVENT.register((context, tickDelta) -> {
+ if (!SkyblockerConfigManager.get().locations.dwarvenMines.crystalsHud.enabled
+ || CLIENT.player == null
+ || !visible) {
+ return;
+ }
+ render(context, tickDelta, SkyblockerConfigManager.get().locations.dwarvenMines.crystalsHud.x,
+ SkyblockerConfigManager.get().locations.dwarvenMines.crystalsHud.y);
+ });
+ }
+
+ protected static IntIntPair getDimensionsForConfig() {
+ int size = (int) (62 * SkyblockerConfigManager.get().locations.dwarvenMines.crystalsHud.mapScaling);
+ return IntIntPair.of(size, size);
+ }
+
+
+ /**
+ * 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 tickDelta For interpolating the player's yaw for map marker
+ * @param hudX Top left X coordinate of the map
+ * @param hudY Top left Y coordinate of the map
+ */
+ private static void render(DrawContext context, float tickDelta, int hudX, int hudY) {
+ float scale = SkyblockerConfigManager.get().locations.dwarvenMines.crystalsHud.mapScaling;
+
+ //make sure the map renders infront of some stuff - improve this in the future with better layering (1.20.5?)
+ //and set position and scale
+ MatrixStack matrices = context.getMatrices();
+ matrices.push();
+ matrices.translate(hudX, hudY, 200f);
+ matrices.scale(scale, scale, 0f);
+
+ //draw map texture
+ context.drawTexture(MAP_TEXTURE, 0, 0, 0, 0, 62, 62, 62, 62);
+
+ //if enabled add waypoint locations to map
+ if (SkyblockerConfigManager.get().locations.dwarvenMines.crystalsHud.showLocations) {
+ Map<String,CrystalsWaypoint> ActiveWaypoints = CrystalsLocationsManager.activeWaypoints;
+
+ for (CrystalsWaypoint waypoint : ActiveWaypoints.values()) {
+ Color waypointColor = waypoint.category.color;
+ Vector2ic renderPos = transformLocation(waypoint.pos.getX(), waypoint.pos.getZ());
+ int locationSize = SkyblockerConfigManager.get().locations.dwarvenMines.crystalsHud.locationSize;
+
+ if (Arrays.asList(SMALL_LOCATIONS).contains(waypoint.name.getString())) {//if small location half the location size
+ locationSize = 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());
+ }
+ }
+
+ //draw player on map
+ if (CLIENT.player == null || CLIENT.getNetworkHandler() == null) {
+ return;
+ }
+
+ //get player location
+ double playerX = CLIENT.player.getX();
+ double playerZ = CLIENT.player.getZ();
+ float playerRotation = CLIENT.player.getYaw(); //TODO make the transitions more rough?
+ Vector2ic renderPos = transformLocation(playerX, playerZ);
+
+ int renderX = renderPos.x() - 2;
+ int renderY = renderPos.y() - 3;
+
+ //position, scale and rotate the player marker
+ matrices.translate(renderX, renderY, 0f);
+ matrices.scale(0.75f, 0.75f, 0f);
+ matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(yaw2Cardinal(playerRotation)), 2.5f, 3.5f, 0);
+
+ //draw marker on map
+ context.drawTexture(MAP_ICON, 0, 0, 2, 0, 5, 7, 128, 128);
+
+ //todo add direction (can not work out how to rotate)
+ matrices.pop();
+ }
+
+ /**
+ * Converts an X and Z coordinate in the crystal hollow to a X and Y coordinate on the map.
+ *
+ * @param x the world X coordinate
+ * @param z the world Z coordinate
+ * @return a vector representing the x and y values
+ */
+ protected static Vector2ic transformLocation(double x, double z) {
+ //converts an x and z to a location on the map
+ int transformedX = (int) ((x - 202) / 621 * 62);
+ int transformedY = (int) ((z - 202) / 621 * 62);
+ transformedX = MathHelper.clamp(transformedX, 0, 62);
+ transformedY = MathHelper.clamp(transformedY, 0, 62);
+
+ return new Vector2i(transformedX, transformedY);
+ }
+
+ /**
+ * Converts yaw to the cardinal directions that a player marker can be rotated towards on a map.
+ * The rotations of a marker follow this order: N, NNE, NE, ENE, E, ESE, SE, SSE, S, SSW, SW, WSW, W, WNW, NW, NNW.
+ * <br><br>
+ * Based off code from {@link net.minecraft.client.render.MapRenderer}
+ */
+ private static float yaw2Cardinal(float yaw) {
+ yaw += + 180; //flip direction
+ byte clipped = (byte) ((yaw += yaw < 0.0 ? -8.0 : 8.0) * 16.0 / 360.0);
+
+ return (clipped * 360f) / 16f;
+ }
+
+ /**
+ * 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().locations.dwarvenMines.crystalsHud.enabled) {
+ visible = false;
+ return;
+ }
+
+ //get if the player is in the crystals
+ visible = Utils.isInCrystalHollows();
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHudConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHudConfigScreen.java
new file mode 100644
index 00000000..b4e423e9
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHudConfigScreen.java
@@ -0,0 +1,69 @@
+package de.hysky.skyblocker.skyblock.dwarven;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.render.RenderHelper;
+import it.unimi.dsi.fastutil.ints.IntIntPair;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.text.Text;
+
+import java.awt.*;
+
+public class CrystalsHudConfigScreen extends Screen {
+
+ private int hudX = SkyblockerConfigManager.get().locations.dwarvenMines.crystalsHud.x;
+ private int hudY = SkyblockerConfigManager.get().locations.dwarvenMines.crystalsHud.y;
+ private final Screen parent;
+
+ protected CrystalsHudConfigScreen() {
+ this(null);
+ }
+
+ public CrystalsHudConfigScreen(Screen parent) {
+ super(Text.of("Crystals HUD Config"));
+ this.parent = parent;
+ }
+
+ @Override
+ public void render(DrawContext context, int mouseX, int mouseY, float delta) {
+ super.render(context, mouseX, mouseY, delta);
+ renderBackground(context, mouseX, mouseY, delta);
+ renderHUDMap(context, hudX, hudY);
+ context.drawCenteredTextWithShadow(textRenderer, "Right Click To Reset Position", width / 2, height / 2, Color.GRAY.getRGB());
+ }
+
+ @Override
+ public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
+ IntIntPair dims = CrystalsHud.getDimensionsForConfig();
+ if (RenderHelper.pointIsInArea(mouseX, mouseY, hudX, hudY, hudX + dims.leftInt(), hudY + dims.rightInt()) && button == 0) {
+ hudX = (int) Math.max(Math.min(mouseX - (double) dims.leftInt() / 2, this.width - dims.leftInt()), 0);
+ hudY = (int) Math.max(Math.min(mouseY - (double) dims.rightInt() / 2, this.height - dims.rightInt()), 0);
+ }
+ return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (button == 1) {
+ IntIntPair dims = CrystalsHud.getDimensionsForConfig();
+ hudX = this.width / 2 - dims.leftInt();
+ hudY = this.height / 2 - dims.rightInt();
+ }
+ return super.mouseClicked(mouseX, mouseY, button);
+ }
+
+ private void renderHUDMap(DrawContext context, int x, int y) {
+ float scaling = SkyblockerConfigManager.get().locations.dwarvenMines.crystalsHud.mapScaling;
+ int size = (int) (62 * scaling);
+ context.drawTexture(CrystalsHud.MAP_TEXTURE, x, y, 0, 0, size, size, size, size);
+ }
+
+ @Override
+ public void close() {
+ SkyblockerConfigManager.get().locations.dwarvenMines.crystalsHud.x = hudX;
+ SkyblockerConfigManager.get().locations.dwarvenMines.crystalsHud.y = hudY;
+ SkyblockerConfigManager.save();
+
+ client.setScreen(parent);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java
new file mode 100644
index 00000000..0a4e4518
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java
@@ -0,0 +1,195 @@
+package de.hysky.skyblocker.skyblock.dwarven;
+
+import com.mojang.brigadier.Command;
+import com.mojang.brigadier.CommandDispatcher;
+import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.logging.LogUtils;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Constants;
+import de.hysky.skyblocker.utils.Utils;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
+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;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.command.CommandRegistryAccess;
+import net.minecraft.command.argument.BlockPosArgumentType;
+import net.minecraft.command.argument.PosArgument;
+import net.minecraft.server.command.ServerCommandSource;
+import net.minecraft.text.ClickEvent;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.math.BlockPos;
+
+import java.awt.*;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+
+import static com.mojang.brigadier.arguments.StringArgumentType.getString;
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
+
+public class CrystalsLocationsManager {
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
+
+ /**
+ * A look-up table to convert between location names and waypoint in the {@link CrystalsWaypoint.Category} values.
+ */
+ protected 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])");
+
+ protected static Map<String, CrystalsWaypoint> activeWaypoints = new HashMap<>();
+
+ public static void init() {
+ WorldRenderEvents.AFTER_TRANSLUCENT.register(CrystalsLocationsManager::render);
+ ClientReceiveMessageEvents.GAME.register(CrystalsLocationsManager::extractLocationFromMessage);
+ ClientCommandRegistrationCallback.EVENT.register(CrystalsLocationsManager::registerWaypointLocationCommands);
+ ClientPlayConnectionEvents.JOIN.register((_handler, _sender, _client) -> reset());
+ }
+
+ private static void extractLocationFromMessage(Text message, Boolean overlay) {
+ if (!SkyblockerConfigManager.get().locations.dwarvenMines.crystalsWaypoints.findInChat || !Utils.isInCrystalHollows()) {
+ return;
+ }
+
+ 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;
+ }
+
+ //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(Text.of(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;
+ }
+
+ 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);
+ }
+ }
+ 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
+ && pos.getY() >= 31 && pos.getY() <= 188;
+ }
+
+ private static void registerWaypointLocationCommands(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) {
+ dispatcher.register(literal(SkyblockerMod.NAMESPACE)
+ .then(literal("crystalWaypoints")
+ .then(argument("pos", BlockPosArgumentType.blockPos())
+ .then(argument("place", StringArgumentType.greedyString())
+ .executes(context -> addWaypointFromCommand(context.getSource(), getString(context, "place"), context.getArgument("pos", PosArgument.class)))
+ )
+ )
+ )
+ );
+ }
+
+ 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() + "."));
+
+ return text;
+ }
+
+ private static Text getLocationInputText(String location) {
+ MutableText text = Constants.PREFIX.get();
+
+ 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))));
+ }
+
+ return text;
+ }
+
+ public static int addWaypointFromCommand(FabricClientCommandSource source, String place, PosArgument location) {
+ // TODO Less hacky way with custom ClientBlockPosArgumentType
+ BlockPos blockPos = location.toAbsoluteBlockPos(new ServerCommandSource(null, source.getPosition(), source.getRotation(), null, 0, null, null, null, null));
+
+ if (WAYPOINT_LOCATIONS.containsKey(place)) {
+ addCustomWaypoint(Text.of(place), blockPos);
+
+ //tell the client it has done this
+ if (CLIENT.player == null || CLIENT.getNetworkHandler() == null) {
+ return 0;
+ }
+
+ CLIENT.player.sendMessage(getSetLocationMessage(place, blockPos), false);
+ }
+
+ return Command.SINGLE_SUCCESS;
+ }
+
+
+ private static void addCustomWaypoint( Text waypointName, BlockPos pos) {
+ CrystalsWaypoint.Category category = WAYPOINT_LOCATIONS.get(waypointName.getString());
+ CrystalsWaypoint waypoint = new CrystalsWaypoint(category, waypointName, pos);
+ activeWaypoints.put(waypointName.getString(), waypoint);
+ }
+
+ public static void render(WorldRenderContext context) {
+ if (SkyblockerConfigManager.get().locations.dwarvenMines.crystalsWaypoints.enabled) {
+ for (CrystalsWaypoint crystalsWaypoint : activeWaypoints.values()) {
+ if (crystalsWaypoint.shouldRender()) {
+ crystalsWaypoint.render(context);
+ }
+ }
+ }
+ }
+
+ private static void reset() {
+ activeWaypoints.clear();
+ }
+
+ public static void update() {
+ if (CLIENT.player == null || CLIENT.getNetworkHandler() == null || !SkyblockerConfigManager.get().locations.dwarvenMines.crystalsWaypoints.enabled || !Utils.isInCrystalHollows()) {
+ return;
+ }
+
+ //get if the player is in the crystals
+ String location = Utils.getIslandArea().replace("⏣ ", "");
+ //if new location and n