From bc3f389c52aef41f6fb4351e8d0a5a8fed8336ef Mon Sep 17 00:00:00 2001
From: bowser0000 <bowser0000@gmail.com>
Date: Sat, 24 Jul 2021 01:54:57 -0400
Subject: Add crystal hollows waypoints

With support for SBE crystal hollows waypoints
---
 src/main/java/me/Danker/DankersSkyblockMod.java    |   6 +
 .../commands/CrystalHollowWaypointCommand.java     |  50 ++++++
 .../java/me/Danker/commands/DankerGuiCommand.java  |   2 +
 .../java/me/Danker/commands/ToggleCommand.java     |  22 ++-
 .../me/Danker/features/CrystalHollowWaypoints.java | 193 +++++++++++++++++++++
 .../Danker/gui/CrystalHollowWaypointActionGui.java |  87 ++++++++++
 .../me/Danker/gui/CrystalHollowWaypointsGui.java   | 139 +++++++++++++++
 src/main/java/me/Danker/gui/DankerGui.java         |   5 +
 .../java/me/Danker/handlers/ConfigHandler.java     |   2 +
 src/main/java/me/Danker/utils/Utils.java           | 147 ++++++++++++++++
 10 files changed, 649 insertions(+), 4 deletions(-)
 create mode 100644 src/main/java/me/Danker/commands/CrystalHollowWaypointCommand.java
 create mode 100644 src/main/java/me/Danker/features/CrystalHollowWaypoints.java
 create mode 100644 src/main/java/me/Danker/gui/CrystalHollowWaypointActionGui.java
 create mode 100644 src/main/java/me/Danker/gui/CrystalHollowWaypointsGui.java

(limited to 'src/main')

diff --git a/src/main/java/me/Danker/DankersSkyblockMod.java b/src/main/java/me/Danker/DankersSkyblockMod.java
index b85c608..e02f12a 100644
--- a/src/main/java/me/Danker/DankersSkyblockMod.java
+++ b/src/main/java/me/Danker/DankersSkyblockMod.java
@@ -111,6 +111,7 @@ public class DankersSkyblockMod {
         MinecraftForge.EVENT_BUS.register(new ClickInOrderSolver());
         MinecraftForge.EVENT_BUS.register(new ColouredNames());
         MinecraftForge.EVENT_BUS.register(new CreeperSolver());
+        MinecraftForge.EVENT_BUS.register(new CrystalHollowWaypoints());
         MinecraftForge.EVENT_BUS.register(new CustomMusic());
         MinecraftForge.EVENT_BUS.register(new DungeonTimer());
         MinecraftForge.EVENT_BUS.register(new EndOfFarmAlert());
@@ -175,6 +176,7 @@ public class DankersSkyblockMod {
         ClientCommandHandler.instance.registerCommand(new ArmourCommand());
         ClientCommandHandler.instance.registerCommand(new BankCommand());
         ClientCommandHandler.instance.registerCommand(new CustomMusicCommand());
+        ClientCommandHandler.instance.registerCommand(new CrystalHollowWaypointCommand());
         ClientCommandHandler.instance.registerCommand(new DHelpCommand());
         ClientCommandHandler.instance.registerCommand(new DankerGuiCommand());
         ClientCommandHandler.instance.registerCommand(new DisplayCommand());
@@ -357,6 +359,7 @@ public class DankersSkyblockMod {
             if (player != null) {
                 Utils.checkForSkyblock();
                 Utils.checkForDungeons();
+                Utils.checkForCrystalHollows();
             }
 
             tickAmount = 0;
@@ -425,6 +428,9 @@ public class DankersSkyblockMod {
                     case "custommusic":
                         mc.displayGuiScreen(new CustomMusicGui());
                         break;
+                    case "crystalwaypoints":
+                        mc.displayGuiScreen(new CrystalHollowWaypointsGui(1));
+                        break;
                 }
             }
             guiToOpen = null;
diff --git a/src/main/java/me/Danker/commands/CrystalHollowWaypointCommand.java b/src/main/java/me/Danker/commands/CrystalHollowWaypointCommand.java
new file mode 100644
index 0000000..c545447
--- /dev/null
+++ b/src/main/java/me/Danker/commands/CrystalHollowWaypointCommand.java
@@ -0,0 +1,50 @@
+package me.Danker.commands;
+
+import me.Danker.DankersSkyblockMod;
+import me.Danker.features.CrystalHollowWaypoints;
+import net.minecraft.command.CommandBase;
+import net.minecraft.command.CommandException;
+import net.minecraft.command.ICommandSender;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.util.BlockPos;
+import net.minecraft.util.ChatComponentText;
+
+public class CrystalHollowWaypointCommand extends CommandBase {
+
+    @Override
+    public String getCommandName() {
+        return "dsmaddcrystalhollowwaypoints";
+    }
+
+    @Override
+    public String getCommandUsage(ICommandSender arg0) {
+        return "/" + getCommandName() + " <formatted waypoint>";
+    }
+
+    @Override
+    public int getRequiredPermissionLevel() {
+        return 0;
+    }
+
+    @Override
+    public void processCommand(ICommandSender arg0, String[] arg1) throws CommandException {
+        EntityPlayer player = (EntityPlayer) arg0;
+
+        if (arg1.length == 0) return;
+
+        String[] waypoints = String.join(" ", arg1).split("\\\\n");
+
+        for (String waypoint : waypoints) {
+            String[] parts = waypoint.split("@");
+            String[] coords = parts[1].split(",");
+
+            String location = parts[0];
+            BlockPos pos = new BlockPos(Integer.parseInt(coords[0]), Integer.parseInt(coords[1]), Integer.parseInt(coords[2]));
+            CrystalHollowWaypoints.Waypoint newWaypoint = new CrystalHollowWaypoints.Waypoint(location, pos);
+
+            CrystalHollowWaypoints.waypoints.add(newWaypoint);
+            player.addChatMessage(new ChatComponentText(DankersSkyblockMod.MAIN_COLOUR + "Added " + newWaypoint.location + " @ " + newWaypoint.getPos()));
+        }
+    }
+
+}
diff --git a/src/main/java/me/Danker/commands/DankerGuiCommand.java b/src/main/java/me/Danker/commands/DankerGuiCommand.java
index 9561074..dc6845d 100644
--- a/src/main/java/me/Danker/commands/DankerGuiCommand.java
+++ b/src/main/java/me/Danker/commands/DankerGuiCommand.java
@@ -100,6 +100,8 @@ public class DankerGuiCommand extends CommandBase {
 			debug.append("[customcolourednames][").append(ToggleCommand.customColouredNames).append("]\n");
 			debug.append("[endoffarmalert][").append(ToggleCommand.endOfFarmAlert).append("]\n");
 			debug.append("[gemstonelore][").append(ToggleCommand.gemstoneLore).append("]\n");
+			debug.append("[crystalhollowwaypoints][").append(ToggleCommand.crystalHollowWaypoints).append("]\n");
+			debug.append("[crystalautowaypoints][").append(ToggleCommand.crystalAutoWaypoints).append("]\n");
 			debug.append("[dungeonbossmusic][").append(ToggleCommand.dungeonBossMusic).append("]\n");
 			debug.append("[bloodroommusic][").append(ToggleCommand.bloodRoomMusic).append("]\n");
 			debug.append("[dungeonmusic][").append(ToggleCommand.dungeonMusic).append("]\n");
diff --git a/src/main/java/me/Danker/commands/ToggleCommand.java b/src/main/java/me/Danker/commands/ToggleCommand.java
index d52ee76..061a277 100644
--- a/src/main/java/me/Danker/commands/ToggleCommand.java
+++ b/src/main/java/me/Danker/commands/ToggleCommand.java
@@ -41,6 +41,8 @@ public class ToggleCommand extends CommandBase implements ICommand {
 	public static boolean customColouredNames;
 	public static boolean endOfFarmAlert;
 	public static boolean gemstoneLore;
+	public static boolean crystalHollowWaypoints;
+	public static boolean crystalAutoWaypoints;
 	// Chat Messages
 	public static boolean sceptreMessages;
 	public static boolean midasStaffMessages;
@@ -92,7 +94,6 @@ public class ToggleCommand extends CommandBase implements ICommand {
 
 	@Override
 	public String getCommandUsage(ICommandSender arg0) {
-
 		return "/" + getCommandName() + " <gparty/coords/golden/slayercount/rngesusalerts/splitfishing/ghostdisplay/chatmaddox/spiritbearalert/" +
 										  "sceptremessages/petcolors/dungeontimer/golemalerts/expertiselore/" + //ghosttimer
 										  "skill50display/outlinetext/midasstaffmessages/implosionmessages/healmessages/cooldownmessages/" +
@@ -102,11 +103,12 @@ public class ToggleCommand extends CommandBase implements ICommand {
 										  "startswithterminal/selectallterminal/clickinorderterminal/" +
 										  "ultrasequencer/chronomatron/superpairs/hidetooltipsinaddons/pickblock/" +
 										  "melodytooltips/highlightslayers/highlightarachne/highlightskeletonmasters/teammatesinradius/" +
-										  "gianthp/hidepetcandy/customcolorednames/endoffarmalert/gemstonelore/dungeonbossmusic/bloodroommusic/dungeonmusic/list>";
+										  "gianthp/hidepetcandy/customcolorednames/endoffarmalert/gemstonelore/crystalhollowwaypoints/crystalautowaypoints/" +
+										  "dungeonbossmusic/bloodroommusic/dungeonmusic/list>";
 	}
 
 	public static String usage(ICommandSender arg0) {
-		return new ToggleCommand().getCommandUsage(arg0);
+		return "/toggle <too many to list>";
 	}
 
 	@Override
@@ -132,7 +134,7 @@ public class ToggleCommand extends CommandBase implements ICommand {
 														  "hidetooltipsinaddons", "pickblock", "melodytooltips", "highlightslayers",
 														  "highlightskeletonmasters", "dungeonbossmusic", "bloodroommusic", "dungeonmusic",
 														  "teammatesinradius", "gianthp", "hidepetcandy", "customcolorednames", "endoffarmalert",
-														  "gemstonelore", "list");
+														  "gemstonelore", "crystalhollowwaypoints", "crystalautowaypoints", "list");
 		}
 		return null;
 	}
@@ -449,6 +451,16 @@ public class ToggleCommand extends CommandBase implements ICommand {
 				ConfigHandler.writeBooleanConfig("toggles", "GemstoneLore", gemstoneLore);
 				player.addChatMessage(new ChatComponentText(DankersSkyblockMod.MAIN_COLOUR + "Gemstone in lore has been set to " + DankersSkyblockMod.SECONDARY_COLOUR + gemstoneLore + DankersSkyblockMod.MAIN_COLOUR + "."));
 				break;
+			case "crystalhollowwaypoints":
+				crystalHollowWaypoints = !crystalHollowWaypoints;
+				ConfigHandler.writeBooleanConfig("toggles", "CrystalHollowWaypoints", crystalHollowWaypoints);
+				player.addChatMessage(new ChatComponentText(DankersSkyblockMod.MAIN_COLOUR + "Crystal Hollows waypoints has been set to " + DankersSkyblockMod.SECONDARY_COLOUR + crystalHollowWaypoints + DankersSkyblockMod.MAIN_COLOUR + "."));
+				break;
+			case "crystalautowaypoints":
+				crystalAutoWaypoints = !crystalAutoWaypoints;
+				ConfigHandler.writeBooleanConfig("toggles", "CrystalAutoWaypoints", crystalAutoWaypoints);
+				player.addChatMessage(new ChatComponentText(DankersSkyblockMod.MAIN_COLOUR + "Auto Crystal Hollows waypoints has been set to " + DankersSkyblockMod.SECONDARY_COLOUR + crystalAutoWaypoints + DankersSkyblockMod.MAIN_COLOUR + "."));
+				break;
 			case "dungeonbossmusic":
 				dungeonBossMusic = !dungeonBossMusic;
 				CustomMusic.dungeonboss.stop();
@@ -520,6 +532,8 @@ public class ToggleCommand extends CommandBase implements ICommand {
 															DankersSkyblockMod.TYPE_COLOUR + " Custom name colors: " + DankersSkyblockMod.VALUE_COLOUR + customColouredNames + "\n" +
 															DankersSkyblockMod.TYPE_COLOUR + " End of farm alert: " + DankersSkyblockMod.VALUE_COLOUR + endOfFarmAlert + "\n" +
 															DankersSkyblockMod.TYPE_COLOUR + " Gemstone in lore: " + DankersSkyblockMod.VALUE_COLOUR + gemstoneLore + "\n" +
+															DankersSkyblockMod.TYPE_COLOUR + " Crystal Hollows waypoints: " + DankersSkyblockMod.VALUE_COLOUR + crystalHollowWaypoints + "\n" +
+															DankersSkyblockMod.TYPE_COLOUR + " Auto Crystal Hollows waypoints: " + DankersSkyblockMod.VALUE_COLOUR + crystalAutoWaypoints + "\n" +
 															DankersSkyblockMod.TYPE_COLOUR + " Custom dungeon boss music: " + DankersSkyblockMod.VALUE_COLOUR + dungeonBossMusic + "\n" +
 															DankersSkyblockMod.TYPE_COLOUR + " Custom blood room music: " + DankersSkyblockMod.VALUE_COLOUR + bloodRoomMusic + "\n" +
 															DankersSkyblockMod.TYPE_COLOUR + " Custom dungeon music: " + DankersSkyblockMod.VALUE_COLOUR + dungeonMusic
diff --git a/src/main/java/me/Danker/features/CrystalHollowWaypoints.java b/src/main/java/me/Danker/features/CrystalHollowWaypoints.java
new file mode 100644
index 0000000..17578fe
--- /dev/null
+++ b/src/main/java/me/Danker/features/CrystalHollowWaypoints.java
@@ -0,0 +1,193 @@
+package me.Danker.features;
+
+import me.Danker.DankersSkyblockMod;
+import me.Danker.commands.ToggleCommand;
+import me.Danker.handlers.ScoreboardHandler;
+import me.Danker.utils.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.entity.item.EntityArmorStand;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.event.ClickEvent;
+import net.minecraft.util.*;
+import net.minecraft.world.World;
+import net.minecraftforge.client.event.ClientChatReceivedEvent;
+import net.minecraftforge.client.event.RenderWorldLastEvent;
+import net.minecraftforge.event.world.WorldEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CrystalHollowWaypoints {
+
+    public static List<Waypoint> waypoints = new ArrayList<>();
+    
+    static boolean khazad = false;
+    static boolean fairy = false;
+    static boolean temple = false;
+    static boolean guardian = false;
+    static boolean divan = false;
+    static boolean corleone = false;
+    static boolean king = false;
+    static boolean queen = false;
+    static boolean city = false;
+    static boolean nucleus = false;
+    static boolean shop = false;
+
+    @SubscribeEvent
+    public void onTick(TickEvent.ClientTickEvent event) {
+        if (event.phase != TickEvent.Phase.START) return;
+
+        Minecraft mc = Minecraft.getMinecraft();
+        EntityPlayer player = mc.thePlayer;
+        World world = mc.theWorld;
+
+        if (DankersSkyblockMod.tickAmount % 20 == 0) {
+            if (ToggleCommand.crystalAutoWaypoints && Utils.inCrystalHollows && world != null) {
+                boolean found = false;
+                List<String> scoreboard = ScoreboardHandler.getSidebarLines();
+
+                if (!nucleus) {
+                    nucleus = true;
+                    waypoints.add(new Waypoint("Crystal Nucleus", new BlockPos(512, 110, 512)));
+                }
+
+                for (String s : scoreboard) {
+                    String sCleaned = ScoreboardHandler.cleanSB(s);
+                    if (!khazad && sCleaned.contains("Khazad-d")) {
+                        khazad = found = true;
+                        waypoints.add(new Waypoint("Khazad-dûm", player.getPosition()));
+                    } else if (!fairy && sCleaned.contains("Fairy Grotto")) {
+                        fairy = found = true;
+                        waypoints.add(new Waypoint("Fairy Grotto", player.getPosition()));
+                    } else if (!temple && sCleaned.contains("Jungle Temple")) {
+                        temple = found = true;
+                        waypoints.add(new Waypoint("Jungle Temple", player.getPosition()));
+                    } else if (!divan && sCleaned.contains("Mines of Divan")) {
+                        divan = found = true;
+                        waypoints.add(new Waypoint("Mines of Divan", player.getPosition()));
+                    } else if (!queen && sCleaned.contains("Goblin Queen's Den")) {
+                        queen = found = true;
+                        waypoints.add(new Waypoint("Goblin Queen's Den", player.getPosition()));
+                    } else if (!city && sCleaned.contains("Lost Precursor City")) {
+                        city = found = true;
+                        waypoints.add(new Waypoint("Lost Precursor City", player.getPosition()));
+                    }
+
+                    if (found) break;
+                }
+
+                if (!found) {
+                    AxisAlignedBB scan = new AxisAlignedBB(player.getPosition().add(-15, -15, -15), player.getPosition().add(15, 15, 15));
+                    List<EntityArmorStand> entities = world.getEntitiesWithinAABB(EntityArmorStand.class, scan);
+
+                    for (EntityArmorStand entity : entities) {
+                        if (entity.hasCustomName()) {
+                            if (!king && entity.getCustomNameTag().endsWith("King Yolkar")) {
+                                king = found = true;
+                                waypoints.add(new Waypoint("King Yolkar", entity.getPosition()));
+                            } else if (!corleone && entity.getCustomNameTag().endsWith("Boss Corleone")) {
+                                corleone = found = true;
+                                waypoints.add(new Waypoint("Boss Corleone", entity.getPosition()));
+                            } else if (!guardian && entity.getCustomNameTag().contains("Key Guardian")) {
+                                guardian = found = true;
+                                waypoints.add(new Waypoint("Key Guardian", entity.getPosition()));
+                            } else if (!shop && entity.getCustomNameTag().contains("Odawa")) {
+                                shop = found = true;
+                                waypoints.add(new Waypoint("Odawa", entity.getPosition()));
+                            }
+                        }
+                    }
+                }
+
+                if (found && ToggleCommand.crystalHollowWaypoints) {
+                    Waypoint latest = waypoints.get(waypoints.size() - 1);
+                    player.addChatMessage(new ChatComponentText(DankersSkyblockMod.MAIN_COLOUR + "Added " + latest.location + " @ " + latest.getPos()));
+                }
+            }
+        }
+    }
+
+    @SubscribeEvent
+    public void onChat(ClientChatReceivedEvent event) {
+        String message = StringUtils.stripControlCodes(event.message.getUnformattedText());
+        EntityPlayer player = Minecraft.getMinecraft().thePlayer;
+
+        /* examples
+        $SBECHWP:Mines of Divan@-673,117,426
+        $SBECHWP:Khazad-dûm@-292,63,281\nFairy Grotto@-216,110,400\njungle temple@-525,110,395\nJungle Temple@-493,101,425\nMines of Divan@-673,117,426
+        */
+        if (ToggleCommand.crystalHollowWaypoints && Utils.inCrystalHollows) {
+            if (!message.contains(player.getName()) && (message.contains(": $DSMCHWP:") || message.contains(": $SBECHWP:"))) {
+                ChatComponentText add = new ChatComponentText(EnumChatFormatting.GREEN + "" + EnumChatFormatting.BOLD + "  [ADD]\n");
+                add.setChatStyle(add.getChatStyle().setChatClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/dsmaddcrystalhollowwaypoints " + message.substring(message.lastIndexOf(":") + 1))));
+
+                new Thread(() -> {
+                    try {
+                        Thread.sleep(10);
+                    } catch (InterruptedException ex) {
+                        ex.printStackTrace();
+                    }
+                    player.addChatMessage(new ChatComponentText("\n" + DankersSkyblockMod.MAIN_COLOUR + "DSM/SBE Crystal Hollows waypoints found. Click to add.\n").appendSibling(add));
+                }).start();
+            }
+        }
+    }
+
+    @SubscribeEvent
+    public void onWorldRender(RenderWorldLastEvent event) {
+        if (ToggleCommand.crystalHollowWaypoints && Utils.inCrystalHollows) {
+            for (Waypoint waypoint : waypoints) {
+                if (waypoint.toggled) Utils.draw3DWaypointString(waypoint, event.partialTicks);
+            }
+        }
+    }
+
+    @SubscribeEvent
+    public void onWorldChange(WorldEvent.Load event) {
+        waypoints.clear();
+        khazad = false;
+        fairy = false;
+        temple = false;
+        guardian = false;
+        divan = false;
+        corleone = false;
+        king = false;
+        queen = false;
+        city = false;
+        nucleus = false;
+        shop = false;
+    }
+
+    public static class Waypoint {
+
+        public String location;
+        public BlockPos pos;
+        public boolean toggled;
+
+        public Waypoint(String location, BlockPos pos) {
+            this.location = location;
+            this.pos = pos;
+            this.toggled = true;
+        }
+
+        public String getFormattedWaypoint() {
+            return location + "@" + pos.getX() + "," + pos.getY() + "," + pos.getZ();
+        }
+
+        public String getDistance(EntityPlayer player) {
+            return Math.round(player.getDistance(pos.getX(), pos.getY(), pos.getZ())) + "m";
+        }
+
+        public String getPos() {
+            return pos.getX() + ", " + pos.getY() + ", " + pos.getZ();
+        }
+
+        public void toggle() {
+            toggled = !toggled;
+        }
+
+    }
+
+}
diff --git a/src/main/java/me/Danker/gui/CrystalHollowWaypointActionGui.java b/src/main/java/me/Danker/gui/CrystalHollowWaypointActionGui.java
new file mode 100644
index 0000000..d7f598d
--- /dev/null
+++ b/src/main/java/me/Danker/gui/CrystalHollowWaypointActionGui.java
@@ -0,0 +1,87 @@
+package me.Danker.gui;
+
+import me.Danker.DankersSkyblockMod;
+import me.Danker.features.CrystalHollowWaypoints;
+import me.Danker.utils.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.entity.EntityPlayerSP;
+import net.minecraft.client.gui.GuiButton;
+import net.minecraft.client.gui.GuiScreen;
+import net.minecraft.client.gui.ScaledResolution;
+import net.minecraft.util.EnumChatFormatting;
+
+public class CrystalHollowWaypointActionGui extends GuiScreen {
+
+    private int id;
+
+    private GuiButton goBack;
+    private GuiButton toggle;
+    private GuiButton sendNormal;
+    private GuiButton sendDSM;
+    private GuiButton sendSBE;
+    private GuiButton delete;
+
+    public CrystalHollowWaypointActionGui(int id) {
+        this.id = id;
+    }
+
+    @Override
+    public boolean doesGuiPauseGame() {
+        return false;
+    }
+
+    @Override
+    public void initGui() {
+        super.initGui();
+
+        ScaledResolution sr = new ScaledResolution(Minecraft.getMinecraft());
+        int height = sr.getScaledHeight();
+        int width = sr.getScaledWidth();
+
+        CrystalHollowWaypoints.Waypoint waypoint = CrystalHollowWaypoints.waypoints.get(id);
+
+        goBack = new GuiButton(0, 2, height - 30, 100, 20, "Go Back");
+        toggle = new GuiButton(0, width / 2 - 100, (int) (height * 0.1), "Set Visibility: " + Utils.getColouredBoolean(waypoint.toggled));
+        sendNormal = new GuiButton(0, width / 2 - 100, (int) (height * 0.2), "Send Location And Coordinates");
+        sendDSM = new GuiButton(0, width / 2 - 100, (int) (height * 0.3), "Send DSM Formatted Waypoint");
+        sendSBE = new GuiButton(0, width / 2 - 100, (int) (height * 0.4), "Send SBE Formatted Waypoint");
+        delete = new GuiButton(0, width / 2 - 100, (int) (height * 0.8), EnumChatFormatting.RED + "Delete Waypoint");
+
+        this.buttonList.add(toggle);
+        this.buttonList.add(sendNormal);
+        this.buttonList.add(sendDSM);
+        this.buttonList.add(sendSBE);
+        this.buttonList.add(delete);
+        this.buttonList.add(goBack);
+    }
+
+    @Override
+    public void drawScreen(int mouseX, int mouseY, float partialTicks) {
+        this.drawDefaultBackground();
+        super.drawScreen(mouseX, mouseY, partialTicks);
+    }
+
+    @Override
+    public void actionPerformed(GuiButton button) {
+        CrystalHollowWaypoints.Waypoint waypoint = CrystalHollowWaypoints.waypoints.get(id);
+        EntityPlayerSP player = Minecraft.getMinecraft().thePlayer;
+        if (button == goBack) {
+            DankersSkyblockMod.guiToOpen = "crystalwaypoints";
+        } else if (button == toggle) {
+            waypoint.toggle();
+            toggle.displayString = "Set Visibility: " + Utils.getColouredBoolean(waypoint.toggled);
+        } else if (button == sendNormal) {
+            player.sendChatMessage(waypoint.location + " @ " + waypoint.getPos());
+        } else if (button == sendDSM) {
+            player.sendChatMessage("$DSMCHWP:" + waypoint.getFormattedWaypoint());
+        } else if (button == sendSBE) {
+            player.sendChatMessage("$SBECHWP:" + waypoint.getFormattedWaypoint());
+        } else if (button == delete) {
+            CrystalHollowWaypoints.waypoints.remove(id);
+            mc.displayGuiScreen(new CrystalHollowWaypointsGui(1));
+            return;
+        }
+        CrystalHollowWaypoints.waypoints.set(id, waypoint);
+    }
+
+}
diff --git a/src/main/java/me/Danker/gui/CrystalHollowWaypointsGui.java b/src/main/java/me/Danker/gui/CrystalHollowWaypointsGui.java
new file mode 100644
index 0000000..392f16b
--- /dev/null
+++ b/src/main/java/me/Danker/gui/CrystalHollowWaypointsGui.java
@@ -0,0 +1,139 @@
+package me.Danker.gui;
+
+import me.Danker.DankersSkyblockMod;
+import me.Danker.commands.ToggleCommand;
+import me.Danker.features.CrystalHollowWaypoints;
+import me.Danker.gui.buttons.FeatureButton;
+import me.Danker.handlers.ConfigHandler;
+import me.Danker.handlers.TextRenderer;
+import me.Danker.utils.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.entity.EntityPlayerSP;
+import net.minecraft.client.gui.GuiButton;
+import net.minecraft.client.gui.GuiScreen;
+import net.minecraft.client.gui.ScaledResolution;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CrystalHollowWaypointsGui extends GuiScreen {
+
+    private int page;
+    private List<GuiButton> allButtons = new ArrayList<>();
+
+    private GuiButton goBack;
+    private GuiButton backPage;
+    private GuiButton nextPage;
+    private GuiButton sendDSM;
+    private GuiButton sendSBE;
+    private FeatureButton crystalHollowWaypoints;
+    private FeatureButton crystalAutoWaypoints;
+
+    public CrystalHollowWaypointsGui(int page) {
+        this.page = page;
+    }
+
+    @Override
+    public boolean doesGuiPauseGame() {
+        return false;
+    }
+
+    @Override
+    public void initGui() {
+        super.initGui();
+
+        ScaledResolution sr = new ScaledResolution(Minecraft.getMinecraft());
+        int height = sr.getScaledHeight();
+        int width = sr.getScaledWidth();
+
+        goBack = new GuiButton(0, 2, height - 30, 100, 20, "Go Back");
+        backPage = new GuiButton(0, width / 2 - 100, (int) (height * 0.8), 80, 20, "< Back");
+        nextPage = new GuiButton(0, width / 2 + 20, (int) (height * 0.8), 80, 20, "Next >");
+        sendDSM = new GuiButton(0, 2, 10, 175, 20, "Send DSM Formatted Waypoints");
+        sendSBE = new GuiButton(0, 2, 40, 175, 20, "Send SBE Formatted Waypoints");
+        crystalHollowWaypoints = new FeatureButton("Crystal Hollows Waypoints: " + Utils.getColouredBoolean(ToggleCommand.crystalHollowWaypoints), "Shows waypoints to various places in the Crystal Hollows.");
+        crystalAutoWaypoints = new FeatureButton("Auto Waypoints: " + Utils.getColouredBoolean(ToggleCommand.crystalAutoWaypoints), "Automatically creates waypoints when you visit a special place in the Crystal Hollows.");
+
+        allButtons.clear();
+        allButtons.add(crystalHollowWaypoints);
+        allButtons.add(crystalAutoWaypoints);
+        for (int i = 0; i < CrystalHollowWaypoints.waypoints.size(); i++) {
+            CrystalHollowWaypoints.Waypoint waypoint = CrystalHollowWaypoints.waypoints.get(i);
+            GuiButton button = new GuiButton(i, 0, 0, waypoint.location + " >");
+            allButtons.add(button);
+        }
+
+        reInit();
+    }
+
+    public void reInit() {
+        this.buttonList.clear();
+
+        for (int i = (page - 1) * 7, iteration = 0; iteration < 7 && i < allButtons.size(); i++, iteration++) {
+            GuiButton button = allButtons.get(i);
+            button.xPosition = width / 2 - 100;
+            button.yPosition = (int) (height * (0.1 * (iteration + 1)));
+            this.buttonList.add(button);
+        }
+
+        if (page > 1) this.buttonList.add(backPage);
+        if (page < Math.ceil(allButtons.size() / 7D)) this.buttonList.add(nextPage);
+
+        this.buttonList.add(goBack);
+        this.buttonList.add(sendDSM);
+        this.buttonList.add(sendSBE);
+    }
+
+    @Override
+    public void drawScreen(int mouseX, int mouseY, float partialTicks) {
+        this.drawDefaultBackground();
+        super.drawScreen(mouseX, mouseY, partialTicks);
+
+        String pageText = "Page: " + page + "/" + (int) Math.ceil(allButtons.size() / 7D);
+        int pageWidth = mc.fontRendererObj.getStringWidth(pageText);
+        new TextRenderer(mc, pageText, width / 2 - pageWidth / 2, 10, 1D);
+    }
+
+    @Override
+    public void actionPerformed(GuiButton button) {
+        EntityPlayerSP player = Minecraft.getMinecraft().thePlayer;
+        if (button == goBack) {
+            DankersSkyblockMod.guiToOpen = "dankergui1";
+        } else if (button == nextPage) {
+            mc.displayGuiScreen(new CrystalHollowWaypointsGui(page + 1));
+        } else if (button == backPage) {
+            mc.displayGuiScreen(new CrystalHollowWaypointsGui(page - 1));
+        } else if (button == sendDSM) {
+            if (CrystalHollowWaypoints.waypoints.size() > 0) {
+                StringBuilder message = new StringBuilder();
+                for (CrystalHollowWaypoints.Waypoint waypoint : CrystalHollowWaypoints.waypoints) {
+                    if (message.length() > 0) message.append("\\n");
+                    message.append(waypoint.getFormattedWaypoint());
+                }
+                message.insert(0, "$DSMCHWP:");
+                player.sendChatMessage(message.toString());
+            }
+        } else if (button == sendSBE) {
+            if (CrystalHollowWaypoints.waypoints.size() > 0) {
+                StringBuilder message = new StringBuilder();
+                for (CrystalHollowWaypoints.Waypoint waypoint : CrystalHollowWaypoints.waypoints) {
+                    if (message.length() > 0) message.append("\\n");
+                    message.append(waypoint.getFormattedWaypoint());
+                }
+                message.insert(0, "$SBECHWP:");
+                player.sendChatMessage(message.toString());
+            }
+        } else if (button == crystalHollowWaypoints) {
+            ToggleCommand.crystalHollowWaypoints = !ToggleCommand.crystalHollowWaypoints;
+            ConfigHandler.writeBooleanConfig("toggles", "CrystalHollowWaypoints", ToggleCommand.crystalHollowWaypoints);
+            crystalHollowWaypoints.displayString = "Crystal Hollows Waypoints: " + Utils.getColouredBoolean(ToggleCommand.crystalHollowWaypoints);
+        } else if (button == crystalAutoWaypoints) {
+            ToggleCommand.crystalAutoWaypoints = !ToggleCommand.crystalAutoWaypoints;
+            ConfigHandler.writeBooleanConfig("toggles", "CrystalAutoWaypoints", ToggleCommand.crystalAutoWaypoints);
+            crystalAutoWaypoints.displayString = "Auto Waypoints: " + Utils.getColouredBoolean(ToggleCommand.crystalAutoWaypoints);
+        } else {
+            mc.displayGuiScreen(new CrystalHollowWaypointActionGui(button.id));
+        }
+    }
+
+}
diff --git a/src/main/java/me/Danker/gui/DankerGui.java b/src/main/java/me/Danker/gui/DankerGui.java
index d7e2c16..bed219a 100644
--- a/src/main/java/me/Danker/gui/DankerGui.java
+++ b/src/main/java/me/Danker/gui/DankerGui.java
@@ -41,6 +41,7 @@ public class DankerGui extends GuiScreen {
 	private GuiButton experimentationTableSolvers;
 	private GuiButton skillTracker;
 	private GuiButton customMusic;
+	private GuiButton crystalHollowWaypoints;
 	// Toggles
 	private GuiButton gparty;
 	private GuiButton coords;
@@ -118,6 +119,7 @@ public class DankerGui extends GuiScreen {
 		experimentationTableSolvers = new GuiButton(0, 0, 0, "Toggle Experimentation Table Solvers");
 		skillTracker = new GuiButton(0, 0, 0, "Toggle Skill XP/Hour Tracking");
 		customMusic = new GuiButton(0, 0, 0, "Custom Music");
+		crystalHollowWaypoints = new GuiButton(0, 0, 0, "Crystal Hollows Waypoints");
 		outlineText = new FeatureButton("Outline Displayed Text: " + Utils.getColouredBoolean(ToggleCommand.outlineTextToggled), "Adds bold outline to on-screen text.");
 		pickBlock = new FeatureButton("Auto-Swap to Pick Block: " + Utils.getColouredBoolean(ToggleCommand.swapToPickBlockToggled), "Automatically changes left clicks to middle clicks.\nHelpful when lagging.");
 		coords = new FeatureButton("Coordinate/Angle Display: " + Utils.getColouredBoolean(ToggleCommand.coordsToggled), "Displays coordinates and angle.");
@@ -166,6 +168,7 @@ public class DankerGui extends GuiScreen {
 		allButtons.add(experimentationTableSolvers);
 		allButtons.add(skillTracker);
 		allButtons.add(customMusic);
+		allButtons.add(crystalHollowWaypoints);
 		allButtons.add(outlineText);
 		allButtons.add(pickBlock);
 		allButtons.add(coords);
@@ -297,6 +300,8 @@ public class DankerGui extends GuiScreen {
 			DankersSkyblockMod.guiToOpen = "skilltracker";
 		} else if (button == customMusic) {
 			DankersSkyblockMod.guiToOpen = "custommusic";
+		} else if (button == crystalHollowWaypoints) {
+			DankersSkyblockMod.guiToOpen = "crystalwaypoints";
 		} else if (button == outlineText) {
 			ToggleCommand.outlineTextToggled = !ToggleCommand.outlineTextToggled;
 			ConfigHandler.writeBooleanConfig("toggles", "OutlineText", ToggleCommand.outlineTextToggled);
diff --git a/src/main/java/me/Danker/handlers/ConfigHandler.java b/src/main/java/me/Danker/handlers/ConfigHandler.java
index 1d6311e..d5ff5bd 100644
--- a/src/main/java/me/Danker/handlers/ConfigHandler.java
+++ b/src/main/java/me/Danker/handlers/ConfigHandler.java
@@ -217,6 +217,8 @@ public class ConfigHandler {
 		ToggleCommand.customColouredNames = initBoolean("toggles", "CustomColouredNames", true);
 		ToggleCommand.endOfFarmAlert = initBoolean("toggles", "EndOfFarmAlert", false);
 		ToggleCommand.gemstoneLore = initBoolean("toggles", "GemstoneLore", false);
+		ToggleCommand.crystalHollowWaypoints = initBoolean("toggles", "CrystalHollowWaypoints", false);
+		ToggleCommand.crystalAutoWaypoints = initBoolean("toggles", "CrystalAutoWaypoints", true);
 		// Chat Messages
 		ToggleCommand.sceptreMessages = initBoolean("toggles", "SceptreMessages", true);
 		ToggleCommand.midasStaffMessages = initBoolean("toggles", "MidasStaffMessages", true);
diff --git a/src/main/java/me/Danker/utils/Utils.java b/src/main/java/me/Danker/utils/Utils.java
index e1a99ea..dd6eed5 100644
--- a/src/main/java/me/Danker/utils/Utils.java
+++ b/src/main/java/me/Danker/utils/Utils.java
@@ -2,6 +2,7 @@ package me.Danker.utils;
 
 import me.Danker.DankersSkyblockMod;
 import me.Danker.features.ColouredNames;
+import me.Danker.features.CrystalHollowWaypoints;
 import me.Danker.features.GoldenEnchants;
 import me.Danker.handlers.APIHandler;
 import me.Danker.handlers.ConfigHandler;
@@ -9,6 +10,7 @@ import me.Danker.handlers.ScoreboardHandler;
 import me.Danker.handlers.TextRenderer;
 import net.minecraft.block.Block;
 import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
 import net.minecraft.client.gui.Gui;
 import net.minecraft.client.gui.ScaledResolution;
 import net.minecraft.client.network.NetworkPlayerInfo;
@@ -38,6 +40,7 @@ public class Utils {
 	
 	public static boolean inSkyblock = false;
 	public static boolean inDungeons = false;
+	public static boolean inCrystalHollows = false;
 	public static int[] skillXPPerLevel = {0, 50, 125, 200, 300, 500, 750, 1000, 1500, 2000, 3500, 5000, 7500, 10000, 15000, 20000, 30000, 50000,
 										   75000, 100000, 200000, 300000, 400000, 500000, 600000, 700000, 800000, 900000, 1000000, 1100000,
 										   1200000, 1300000, 1400000, 1500000, 1600000, 1700000, 1800000, 1900000, 2000000, 2100000, 2200000,
@@ -167,6 +170,20 @@ public class Utils {
     	inDungeons = false;
 	}
 
+	public static void checkForCrystalHollows() {
+		if (inSkyblock) {
+			Collection<NetworkPlayerInfo> players = Minecraft.getMinecraft().getNetHandler().getPlayerInfoMap();
+			for (NetworkPlayerInfo player : players) {
+				if (player == null || player.getDisplayName() == null) continue;
+				if (player.getDisplayName().getUnformattedText().contains("Crystal Hollows")) {
+					inCrystalHollows = true;
+					return;
+				}
+			}
+		}
+		inCrystalHollows = false;
+	}
+
 	public static boolean isInScoreboard(String text) {
 		List<String> scoreboard = ScoreboardHandler.getSidebarLines();
 		for (String s : scoreboard) {
@@ -397,6 +414,136 @@ public class Utils {
 		GlStateManager.popMatrix();
 	}
 
+	// I couldnt get waypoint strings to work so in the end I just copied from NEU
+	// If anyone sees this please help
+	/*public static void draw3DWaypointString(CrystalHollowWaypoints.Waypoint waypoint, float partialTicks) {
+		Minecraft mc = Minecraft.getMinecraft();
+		EntityPlayer player = mc.thePlayer;
+		BlockPos pos = waypoint.pos;
+		double x = (pos.getX() - player.lastTickPosX) + ((pos.getX() - player.posX) - (pos.getX() - player.lastTickPosX)) * partialTicks;
+		double y = (pos.getY() - player.lastTickPosY) + ((pos.getY() - player.posY) - (pos.getY() - player.lastTickPosY)) * partialTicks;
+		double z = (pos.getZ() - player.lastTickPosZ) + ((pos.getZ() - player.posZ) - (pos.getZ() - player.lastTickPosZ)) * partialTicks;
+
+		double distance = player.getDistance(x, y, z);
+		if (distance > 12) {
+			x *= 12 / distance;
+			y *= 12 / distance;
+			z *= 12 / distance;
+		}
+
+		RenderManager renderManager = mc.getRenderManager();
+		Entity viewer = Minecraft.getMinecraft().getRenderViewEntity();
+
+		float f = 1.6F;
+		float f1 = 0.016666668F * f;
+		int width = mc.fontRendererObj.getStringWidth(waypoint.location) / 2;
+		int width2 = mc.fontRendererObj.getStringWidth(waypoint.getDistance(player)) / 2;
+		GlStateManager.pushMatrix();
+		GlStateManager.translate(x, y, z);
+		GL11.glNormal3f(0f, 1f, 0f);
+		GlStateManager.rotate(-renderManager.playerViewY, 0f, 1f, 0f);
+		GlStateManager.rotate(renderManager.playerViewX, 1f, 0f, 0f);
+		GlStateManager.scale(-f1, -f1, -f1);
+		GlStateManager.enableBlend();
+		GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0);
+		GL11.glDisable(GL11.GL_DEPTH_TEST);
+		GlStateManager.depthMask(false);
+
+		mc.fontRendererObj.drawString(waypoint.location, -width, 0, 0x55FFFF);
+
+		GlStateManager.rotate(-renderManager.playerViewY, 0.0F, 1.0F, 0.0F);
+		GlStateManager.rotate(renderManager.playerViewX, 1.0F, 0.0F, 0.0F);
+		GlStateManager.translate(0, -1, 0);
+		GlStateManager.rotate(-renderManager.playerViewX, 1.0F, 0.0F, 0.0F);
+		GlStateManager.rotate(renderManager.playerViewY, 0.0F, 1.0F, 0.0F);
+
+		mc.fontRendererObj.drawString(waypoint.getDistance(player), -width2, 0, 0xFFFF55);
+
+		GL11.glEnable(GL11.GL_DEPTH_TEST);
+		GlStateManager.depthMask(true);
+		GlStateManager.disableBlend();
+		GlStateManager.popMatrix();
+	}*/
+
+	// https://github.com/Moulberry/NotEnoughUpdates/blob/master/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DwarvenMinesWaypoints.java#L261
+	public static void draw3DWaypointString(CrystalHollowWaypoints.Waypoint waypoint, float partialTicks) {
+		GlStateManager.alphaFunc(516, 0.1F);
+
+		GlStateManager.pushMatrix();
+
+		Entity viewer = Minecraft.getMinecraft().getRenderViewEntity();
+		double viewerX = viewer.lastTickPosX + (viewer.posX - viewer.lastTickPosX) * partialTicks;
+		double viewerY = viewer.lastTickPosY + (viewer.posY - viewer.lastTickPosY) * partialTicks;
+		double viewerZ = viewer.lastTickPosZ + (viewer.posZ - viewer.lastTickPosZ) * partialTicks;
+
+		double x = waypoint.pos.getX()-viewerX+0.5f;
+		double y = waypoint.pos.getY()-viewerY-viewer.getEyeHeight();
+		double z = waypoint.pos.getZ()-viewerZ+0.5f;
+
+		double distSq = x*x + y*y + z*z;
+		double dist = Math.sqrt(distSq);
+		if(distSq > 144) {
+			x *= 12/dist;
+			y *= 12/dist;
+			z *= 12/dist;
+		}
+		GlStateManager.translate(x, y, z);
+		GlStateManager.translate(0, viewer.getEyeHeight(), 0);
+
+		renderNametag(EnumChatFormatting.AQUA + waypoint.location);
+
+		GlStateManager.rotate(-Minecraft.getMinecraft().getRenderManager().playerViewY, 0.0F, 1.0F, 0.0F);
+		GlStateManager.rotate(Minecraft.getMinecraft().getRenderManager().playerViewX, 1.0F, 0.0F, 0.0F);
+		GlStateManager.translate(0, -0.25f, 0);
+		GlStateManager.rotate(-Minecraft.getMinecraft().getRenderManager().playerViewX, 1.0F, 0.0F, 0.0F);
+		GlStateManager.rotate(Minecraft.getMinecraft().getRenderManager().playerViewY, 0.0F, 1.0F, 0.0F);
+
+		renderNametag(EnumChatFormatting.YELLOW + waypoint.getDistance(Minecraft.getMinecraft().thePlayer));
+
+		GlStateManager.popMatrix();
+
+		GlStateManager.disableLighting();
+	}
+
+	// https://github.com/Moulberry/NotEnoughUpdates/blob/master/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DwarvenMinesWaypoints.java#L300
+	public static void renderNametag(String str) {
+		FontRenderer fontrenderer = Minecraft.getMinecraft().fontRendererObj;
+		float f = 1.6F;
+		float f1 = 0.016666668F * f;
+		GlStateManager.pushMatrix();
+		GL11.glNormal3f(0.0F, 1.0F, 0.0F);
+		GlStateManager.rotate(-Minecraft.getMinecraft().getRenderManager().playerViewY, 0.0F, 1.0F, 0.0F);
+		GlStateManager.rotate(Minecraft.getMinecraft().getRenderManager().playerViewX, 1.0F, 0.0F, 0.0F);
+		GlStateManager.scale(-f1, -f1, f1);
+		GlStateManager.disableLighting();
+		GlStateManager.depthMask(false);
+		GlStateManager.disableDepth();
+		GlStateManager.enableBlend();
+		GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0);
+		Tessellator tessellator = Tessellator.getInstance();
+		WorldRenderer worldrenderer = tessellator.getWorldRenderer();
+		int i = 0;
+
+		int j = fontrenderer.getStringWidth(str) / 2;
+		GlStateManager.disableTexture2D();
+		worldrenderer.begin(7, DefaultVertexFormats.POSITION_COLOR);
+		worldrenderer.pos(-j - 1, -1 + i, 0.0D).color(0.0F, 0.0F, 0.0F, 0.25F).endVertex();
+		worldrenderer.pos(-j - 1, 8 + i, 0.0D).color(0.0F, 0.0F, 0.0F, 0.25F).endVertex();
+		worldrenderer.pos(j + 1, 8 + i, 0.0D).color(0.0F, 0.0F, 0.0F, 0.25F).endVertex();
+		worldrenderer.pos(j + 1, -1 + i, 0.0D).color(0.0F, 0.0F, 0.0F, 0.25F).endVertex();
+		tessellator.draw();
+		GlStateManager.enableTexture2D();
+		fontrenderer.drawString(str, -fontrenderer.getStringWidth(str) / 2, i, 553648127);
+		GlStateManager.depthMask(true);
+
+		fontrenderer.drawString(str, -fontrenderer.getStringWidth(str) / 2, i, -1);
+
+		GlStateManager.enableDepth();
+		GlStateManager.enableBlend();
+		GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+		GlStateManager.popMatrix();
+	}
+
 	public static void draw3DBox(AxisAlignedBB aabb, int colourInt, float partialTicks) {
     	Entity render = Minecraft.getMinecraft().getRenderViewEntity();
 		Color colour = new Color(colourInt);
-- 
cgit