aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAaron <51387595+AzureAaron@users.noreply.github.com>2024-06-13 02:06:51 -0400
committerGitHub <noreply@github.com>2024-06-13 02:06:51 -0400
commit7b288fbfccb93ed8a10242acf6532127c41f6dfb (patch)
tree2b3842d1eb8b2d45dab7df29919a127dc76fbe09 /src
parentabd23322c50f449e1cf5b1ae3e5d8b9ca53489c9 (diff)
parentbd11cc01bfa90695ad7498336a90ee4efb5ec813 (diff)
downloadSkyblocker-7b288fbfccb93ed8a10242acf6532127c41f6dfb.tar.gz
Skyblocker-7b288fbfccb93ed8a10242acf6532127c41f6dfb.tar.bz2
Skyblocker-7b288fbfccb93ed8a10242acf6532127c41f6dfb.zip
Merge pull request #713 from kevinthegreat1/waypoint
Waypoints
Diffstat (limited to 'src')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/accessors/CheckboxWidgetAccessor.java11
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java16
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigScreen.java15
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/waypoint/AbstractWaypointsScreen.java73
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/waypoint/DropdownWidget.java141
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/waypoint/Waypoints.java132
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsListWidget.java318
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsScreen.java76
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsShareScreen.java86
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/PosUtils.java5
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/waypoint/NamedWaypoint.java128
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/waypoint/ProfileAwareWaypoint.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java48
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/waypoint/WaypointCategory.java43
-rw-r--r--src/main/resources/assets/skyblocker/lang/en_us.json22
-rw-r--r--src/main/resources/skyblocker.mixins.json1
-rw-r--r--src/test/java/de/hysky/skyblocker/utils/PosUtilsTest.java7
-rw-r--r--src/test/java/de/hysky/skyblocker/utils/waypoint/WaypointCategoryTest.java39
-rw-r--r--src/test/java/de/hysky/skyblocker/utils/waypoint/WaypointsTest.java123
24 files changed, 1278 insertions, 28 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 8611af33..eff88783 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -47,6 +47,7 @@ import de.hysky.skyblocker.skyblock.waypoint.FairySouls;
import de.hysky.skyblocker.skyblock.waypoint.MythologicalRitual;
import de.hysky.skyblocker.skyblock.waypoint.OrderedWaypoints;
import de.hysky.skyblocker.skyblock.waypoint.Relics;
+import de.hysky.skyblocker.skyblock.waypoint.Waypoints;
import de.hysky.skyblocker.utils.*;
import de.hysky.skyblocker.utils.chat.ChatMessageListener;
import de.hysky.skyblocker.utils.discord.DiscordRPCManager;
@@ -113,6 +114,7 @@ public class SkyblockerMod implements ClientModInitializer {
ItemTooltip.init();
AccessoriesHelper.init();
WikiLookup.init();
+ Waypoints.init();
FairySouls.init();
Relics.init();
MythologicalRitual.init();
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java
index 369a9a90..a2a0f815 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java
@@ -3,6 +3,7 @@ package de.hysky.skyblocker.config.categories;
import de.hysky.skyblocker.config.ConfigUtils;
import de.hysky.skyblocker.config.SkyblockerConfig;
import de.hysky.skyblocker.skyblock.fancybars.StatusBarsConfigScreen;
+import de.hysky.skyblocker.skyblock.waypoint.WaypointsScreen;
import de.hysky.skyblocker.utils.render.title.TitleContainerConfigScreen;
import de.hysky.skyblocker.config.configs.UIAndVisualsConfig;
import de.hysky.skyblocker.utils.waypoint.Waypoint;
@@ -226,12 +227,17 @@ public class UIAndVisualsCategory {
.option(Option.<Waypoint.Type>createBuilder()
.name(Text.translatable("skyblocker.config.uiAndVisuals.waypoints.waypointType"))
.description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.waypoints.waypointType.@Tooltip"),
- Text.translatable("skyblocker.config.uiAndVisuals.waypoints.waypointType")))
+ Text.translatable("skyblocker.config.uiAndVisuals.waypoints.waypointType.generalNote")))
.binding(defaults.uiAndVisuals.waypoints.waypointType,
() -> config.uiAndVisuals.waypoints.waypointType,
newValue -> config.uiAndVisuals.waypoints.waypointType = newValue)
.controller(ConfigUtils::createEnumCyclingListController)
.build())
+ .option(ButtonOption.createBuilder()
+ .name(Text.translatable("skyblocker.waypoints.config"))
+ .text(Text.translatable("text.skyblocker.open"))
+ .action((screen, opt) -> MinecraftClient.getInstance().setScreen(new WaypointsScreen(screen)))
+ .build())
.build())
//Teleport Overlays
diff --git a/src/main/java/de/hysky/skyblocker/mixins/accessors/CheckboxWidgetAccessor.java b/src/main/java/de/hysky/skyblocker/mixins/accessors/CheckboxWidgetAccessor.java
new file mode 100644
index 00000000..5ec4a8e8
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/mixins/accessors/CheckboxWidgetAccessor.java
@@ -0,0 +1,11 @@
+package de.hysky.skyblocker.mixins.accessors;
+
+import net.minecraft.client.gui.widget.CheckboxWidget;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(CheckboxWidget.class)
+public interface CheckboxWidgetAccessor {
+ @Accessor
+ void setChecked(boolean checked);
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java
index e6a8b9d1..c0e54904 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java
@@ -242,7 +242,7 @@ public class Room implements Tickable, Renderable {
protected void removeCustomWaypoint(CommandContext<FabricClientCommandSource> context, BlockPos pos) {
SecretWaypoint waypoint = removeCustomWaypoint(pos);
if (waypoint != null) {
- context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.stringifiedTranslatable("skyblocker.dungeons.secrets.customWaypointRemoved", pos.getX(), pos.getY(), pos.getZ(), name, waypoint.secretIndex, waypoint.category, waypoint.name)));
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.customWaypointRemoved", pos.getX(), pos.getY(), pos.getZ(), name, waypoint.secretIndex, waypoint.category.asString(), waypoint.getName())));
} else {
context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.customWaypointNotFound", pos.getX(), pos.getY(), pos.getZ(), name)));
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java
index 42fe6dbe..3779a66e 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java
@@ -5,10 +5,10 @@ import com.mojang.brigadier.context.CommandContext;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.codecs.RecordCodecBuilder;
-import de.hysky.skyblocker.config.SkyblockerConfig;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.config.configs.DungeonsConfig;
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.rendering.v1.WorldRenderContext;
import net.minecraft.client.MinecraftClient;
@@ -29,7 +29,7 @@ import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.ToDoubleFunction;
-public class SecretWaypoint extends Waypoint {
+public class SecretWaypoint extends NamedWaypoint {
private static final Logger LOGGER = LoggerFactory.getLogger(SecretWaypoint.class);
public static final Codec<SecretWaypoint> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.INT.fieldOf("secretIndex").forGetter(secretWaypoint -> secretWaypoint.secretIndex),
@@ -43,8 +43,6 @@ public class SecretWaypoint extends Waypoint {
static final Supplier<Type> TYPE_SUPPLIER = () -> CONFIG.get().waypointType;
final int secretIndex;
final Category category;
- final Text name;
- private final Vec3d centerPos;
SecretWaypoint(int secretIndex, JsonObject waypoint, String name, BlockPos pos) {
this(secretIndex, Category.get(waypoint), name, pos);
@@ -55,11 +53,9 @@ public class SecretWaypoint extends Waypoint {
}
SecretWaypoint(int secretIndex, Category category, Text name, BlockPos pos) {
- super(pos, TYPE_SUPPLIER, category.colorComponents);
+ super(pos, name, TYPE_SUPPLIER, category.colorComponents);
this.secretIndex = secretIndex;
this.category = category;
- this.name = name;
- this.centerPos = pos.toCenterPos();
}
static ToDoubleFunction<SecretWaypoint> getSquaredDistanceToFunction(Entity entity) {
@@ -96,6 +92,11 @@ public class SecretWaypoint extends Waypoint {
return super.equals(obj) || obj instanceof SecretWaypoint other && secretIndex == other.secretIndex && category == other.category && name.equals(other.name) && pos.equals(other.pos);
}
+ @Override
+ protected boolean shouldRenderName() {
+ return CONFIG.get().showSecretText;
+ }
+
/**
* Renders the secret waypoint, including a waypoint through {@link Waypoint#render(WorldRenderContext)}, the name, and the distance from the player.
*/
@@ -106,7 +107,6 @@ public class SecretWaypoint extends Waypoint {
if (CONFIG.get().showSecretText) {
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);
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java
index 3918037f..a6b5e62d 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java
@@ -1,5 +1,6 @@
package de.hysky.skyblocker.skyblock.shortcut;
+import de.hysky.skyblocker.debug.Debug;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.Element;
@@ -77,6 +78,11 @@ public class ShortcutsConfigListWidget extends ElementListWidget<ShortcutsConfig
}
@Override
+ protected boolean isSelectedEntry(int index) {
+ return Debug.debugEnabled() ? Objects.equals(getSelectedOrNull(), children().get(index)) : super.isSelectedEntry(index);
+ }
+
+ @Override
protected boolean removeEntry(AbstractShortcutEntry entry) {
return super.removeEntry(entry);
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigScreen.java
index 120eb099..66735511 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigScreen.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigScreen.java
@@ -11,7 +11,6 @@ import net.minecraft.screen.ScreenTexts;
import net.minecraft.text.Text;
public class ShortcutsConfigScreen extends Screen {
-
private ShortcutsConfigListWidget shortcutsConfigListWidget;
private ButtonWidget buttonDelete;
private ButtonWidget buttonNew;
@@ -41,14 +40,14 @@ public class ShortcutsConfigScreen extends Screen {
shortcutsConfigListWidget.setDimensions(width, height - 96);
shortcutsConfigListWidget.updatePositions();
} else {
- shortcutsConfigListWidget = new ShortcutsConfigListWidget(client, this, width, height - 96, 32, 25);
+ shortcutsConfigListWidget = new ShortcutsConfigListWidget(client, this, width, height - 96, 32, 24);
initialized = true;
}
addDrawableChild(shortcutsConfigListWidget);
GridWidget gridWidget = new GridWidget();
gridWidget.getMainPositioner().marginX(5).marginY(2);
GridWidget.Adder adder = gridWidget.createAdder(2);
- buttonDelete = ButtonWidget.builder(Text.translatable("selectServer.delete"), button -> {
+ buttonDelete = ButtonWidget.builder(Text.translatable("selectServer.deleteButton"), button -> {
if (client != null && shortcutsConfigListWidget.getSelectedOrNull() instanceof ShortcutsConfigListWidget.ShortcutEntry shortcutEntry) {
scrollAmount = shortcutsConfigListWidget.getScrollAmount();
client.setScreen(new ConfirmScreen(this::deleteEntry, Text.translatable("skyblocker.shortcuts.deleteQuestion"), Text.stringifiedTranslatable("skyblocker.shortcuts.deleteWarning", shortcutEntry), Text.translatable("selectServer.deleteButton"), ScreenTexts.CANCEL));
@@ -57,16 +56,10 @@ public class ShortcutsConfigScreen extends Screen {
adder.add(buttonDelete);
buttonNew = ButtonWidget.builder(Text.translatable("skyblocker.shortcuts.new"), buttonNew -> shortcutsConfigListWidget.addShortcutAfterSelected()).build();
adder.add(buttonNew);
- adder.add(ButtonWidget.builder(ScreenTexts.CANCEL, button -> {
- if (client != null) {
- close();
- }
- }).build());
+ adder.add(ButtonWidget.builder(ScreenTexts.CANCEL, button -> close()).build());
buttonDone = ButtonWidget.builder(ScreenTexts.DONE, button -> {
shortcutsConfigListWidget.saveShortcuts();
- if (client != null) {
- close();
- }
+ close();
}).tooltip(Tooltip.of(Text.translatable("skyblocker.shortcuts.commandSuggestionTooltip"))).build();
adder.add(buttonDone);
gridWidget.refreshPositions();
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/AbstractWaypointsScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/AbstractWaypointsScreen.java
new file mode 100644
index 00000000..da6f52c8
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/AbstractWaypointsScreen.java
@@ -0,0 +1,73 @@
+package de.hysky.skyblocker.skyblock.waypoint;
+
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+import de.hysky.skyblocker.utils.Location;
+import de.hysky.skyblocker.utils.Utils;
+import de.hysky.skyblocker.utils.waypoint.NamedWaypoint;
+import de.hysky.skyblocker.utils.waypoint.WaypointCategory;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.text.Text;
+
+import java.util.Arrays;
+
+public abstract class AbstractWaypointsScreen<T extends Screen> extends Screen {
+ protected final T parent;
+ protected final Multimap<String, WaypointCategory> waypoints;
+ protected String island;
+ protected WaypointsListWidget waypointsListWidget;
+ protected DropdownWidget<Location> islandWidget;
+
+ public AbstractWaypointsScreen(Text title, T parent) {
+ this(title, parent, MultimapBuilder.hashKeys().arrayListValues().build());
+ }
+
+ public AbstractWaypointsScreen(Text title, T parent, Multimap<String, WaypointCategory> waypoints) {
+ this(title, parent, waypoints, Utils.getLocationRaw());
+ }
+
+ public AbstractWaypointsScreen(Text title, T parent, Multimap<String, WaypointCategory> waypoints, String island) {
+ super(title);
+ this.parent = parent;
+ this.waypoints = waypoints;
+ this.island = island;
+ }
+
+ @Override
+ protected void init() {
+ super.init();
+ waypointsListWidget = addDrawableChild(new WaypointsListWidget(client, this, width, height - 96, 32, 24));
+ islandWidget = addDrawableChild(new DropdownWidget<>(client, width - 160, 8, 150, height - 8, Arrays.asList(Location.values()), this::islandChanged, Location.from(island)));
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (islandWidget.mouseClicked(mouseX, mouseY, button)) {
+ return true;
+ }
+ boolean mouseClicked = super.mouseClicked(mouseX, mouseY, button);
+ updateButtons();
+ return mouseClicked;
+ }
+
+ @Override
+ public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) {
+ if (islandWidget.isMouseOver(mouseX, mouseY) && islandWidget.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)) {
+ return true;
+ }
+ return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount);
+ }
+
+ protected void islandChanged(Location location) {
+ island = location.id();
+ waypointsListWidget.setIsland(island);
+ }
+
+ protected abstract boolean isEnabled(NamedWaypoint waypoint);
+
+ protected abstract void enabledChanged(NamedWaypoint waypoint, boolean enabled);
+
+ protected void updateButtons() {
+ waypointsListWidget.updateButtons();
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/DropdownWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/DropdownWidget.java
new file mode 100644
index 00000000..724cb461
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/DropdownWidget.java
@@ -0,0 +1,141 @@
+package de.hysky.skyblocker.skyblock.waypoint;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.gui.Selectable;
+import net.minecraft.client.gui.widget.ElementListWidget;
+import net.minecraft.text.Style;
+import net.minecraft.text.Text;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+public class DropdownWidget<T> extends ElementListWidget<DropdownWidget.Entry<T>> {
+ private static final MinecraftClient client = MinecraftClient.getInstance();
+ public static final int ENTRY_HEIGHT = 15;
+ protected final List<T> entries;
+ protected final Consumer<T> selectCallback;
+ protected T prevSelected;
+ protected T selected;
+ protected boolean open;
+
+ public DropdownWidget(MinecraftClient minecraftClient, int x, int y, int width, int maxHeight, List<T> entries, Consumer<T> selectCallback, T selected) {
+ super(minecraftClient, width, Math.min((entries.size() + 1) * ENTRY_HEIGHT + 8, maxHeight), y, ENTRY_HEIGHT);
+ setX(x);
+ this.entries = entries;
+ this.selectCallback = selectCallback;
+ this.selected = selected;
+ setRenderHeader(true, ENTRY_HEIGHT + 4);
+ for (T entry : entries) {
+ addEntry(new Entry<>(this, entry));
+ }
+ }
+
+ @Override
+ public int getRowLeft() {
+ return getX();
+ }
+
+ @Override
+ public int getRowWidth() {
+ return getWidth();
+ }
+
+ @Override
+ protected boolean clickedHeader(int x, int y) {
+ open = !open;
+ return true;
+ }
+
+ @Override
+ protected void renderHeader(DrawContext context, int x, int y) {
+ context.drawTextWithShadow(client.textRenderer, selected.toString(), x + 4, y + 2, 0xFFFFFFFF);
+ }
+
+ @Override
+ public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
+ context.getMatrices().push();
+ context.getMatrices().translate(0, 0, 100);
+
+ int y = getY() - (int) getScrollAmount();
+ int height = getMaxPosition();
+
+ context.fill(getX(), y, getX() + width, y + headerHeight, 0xFF000000);
+ context.drawHorizontalLine(getX(), getX() + width, y, 0xFFFFFFFF);
+ context.drawHorizontalLine(getX(), getX() + width, y + headerHeight, 0xFFFFFFFF);
+ context.drawVerticalLine(getX(), y, y + headerHeight, 0xFFFFFFFF);
+ context.drawVerticalLine(getX() + width, y, y + headerHeight, 0xFFFFFFFF);
+
+ if (open) {
+ context.fill(getX(), y + headerHeight + 1, getX() + width, y + height, 0xFF000000);
+ context.drawHorizontalLine(getX(), getX() + width, y + height, 0xFFFFFFFF);
+ context.drawVerticalLine(getX(), y + headerHeight, y + height, 0xFFFFFFFF);
+ context.drawVerticalLine(getX() + width, y + headerHeight, y + height, 0xFFFFFFFF);
+
+ super.renderWidget(context, mouseX, mouseY, delta);
+ } else {
+ renderHeader(context, getRowLeft(), y + 4);
+ }
+
+ context.getMatrices().pop();
+ }
+
+ @Override
+ protected void drawMenuListBackground(DrawContext context) {}
+
+ @Override
+ protected void drawHeaderAndFooterSeparators(DrawContext context) {}
+
+ @Override
+ public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) {
+ return open && super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount);
+ }
+
+ protected void select(T entry) {
+ selected = entry;
+ open = false;
+ setScrollAmount(0);
+ if (selected != prevSelected) {
+ selectCallback.accept(entry);
+ prevSelected = selected;
+ }
+ }
+
+ static class Entry<T> extends ElementListWidget.Entry<Entry<T>> {
+ private final DropdownWidget<T> dropdownWidget;
+ private final T entry;
+
+ public Entry(DropdownWidget<T> dropdownWidget, T entry) {
+ this.dropdownWidget = dropdownWidget;
+ this.entry = entry;
+ }
+
+ @Override
+ public List<? extends Selectable> selectableChildren() {
+ return List.of();
+ }
+
+ @Override
+ public List<? extends Element> children() {
+ return List.of();
+ }
+
+ @Override
+ public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
+ context.drawTextWithShadow(client.textRenderer, Text.literal(entry.toString()).fillStyle(Style.EMPTY.withUnderline(hovered)), x + 14, y + 2, 0xFFFFFFFF);
+ if (dropdownWidget.selected == this.entry) {
+ context.drawTextWithShadow(client.textRenderer, "✔", x + 4, y + 2, 0xFFFFFFFF);
+ }
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (button == 0 && dropdownWidget.open) {
+ dropdownWidget.select(entry);
+ return true;
+ }
+ return super.mouseClicked(mouseX, mouseY, button);
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java
index bbc9a655..f8930882 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java
@@ -408,7 +408,7 @@ public class OrderedWaypoints {
}
@Override
- protected float[] getColorComponents() {
+ public float[] getColorComponents() {
if (this.colorComponents.length != 3) {
return switch (this.relativeIndex) {
case PREVIOUS -> RED;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Waypoints.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Waypoints.java
new file mode 100644
index 00000000..18096117
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Waypoints.java
@@ -0,0 +1,132 @@
+package de.hysky.skyblocker.skyblock.waypoint;
+
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+import com.google.common.collect.Multimaps;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSyntaxException;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.JsonOps;
+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 de.hysky.skyblocker.utils.waypoint.WaypointCategory;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
+import net.fabricmc.loader.api.FabricLoader;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.toast.SystemToast;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
+
+public class Waypoints {
+ public static final Logger LOGGER = LoggerFactory.getLogger(Waypoints.class);
+ private static final Codec<List<WaypointCategory>> CODEC = WaypointCategory.CODEC.listOf();
+ private static final Codec<List<WaypointCategory>> SKYTILS_CODEC = WaypointCategory.SKYTILS_CODEC.listOf();
+ protected static final SystemToast.Type WAYPOINTS_TOAST_TYPE = new SystemToast.Type();
+
+ private static final Path waypointsFile = FabricLoader.getInstance().getConfigDir().resolve(SkyblockerMod.NAMESPACE).resolve("waypoints.json");
+ protected static final Multimap<String, WaypointCategory> waypoints = MultimapBuilder.hashKeys().arrayListValues().build();
+
+ public static void init() {
+ loadWaypoints();
+ ClientLifecycleEvents.CLIENT_STOPPING.register(Waypoints::saveWaypoints);
+ WorldRenderEvents.AFTER_TRANSLUCENT.register(Waypoints::render);
+ ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("waypoints").executes(Scheduler.queueOpenScreenCommand(() -> new WaypointsScreen(MinecraftClient.getInstance().currentScreen))))));
+ }
+
+ public static void loadWaypoints() {
+ waypoints.clear();
+ try (BufferedReader reader = Files.newBufferedReader(waypointsFile)) {
+ List<WaypointCategory> waypoints = CODEC.parse(JsonOps.INSTANCE, SkyblockerMod.GSON.fromJson(reader, JsonArray.class)).resultOrPartial(LOGGER::error).orElseThrow();
+ waypoints.forEach(waypointCategory -> Waypoints.waypoints.put(waypointCategory.island(), waypointCategory));
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker Waypoints] Encountered exception while loading waypoints", e);
+ }
+ }
+
+ public static List<WaypointCategory> fromSkytilsBase64(String base64, String defaultIsland) {
+ try {
+ if (base64.startsWith("<Skytils-Waypoint-Data>(V")) {
+ int version = Integer.parseInt(base64.substring(26, base64.indexOf(')')));
+ if (version == 1) {
+ return fromSkytilsJson(new String(Base64.getDecoder().decode(base64.substring(base64.indexOf(':') + 1))), defaultIsland);
+ } else {
+ LOGGER.error("[Skyblocker Waypoints] Unknown Skytils waypoint data version: " + version);
+ }
+ } else return fromSkytilsJson(new String(Base64.getDecoder().decode(base64)), defaultIsland);
+ } catch (NumberFormatException e) {
+ LOGGER.error("[Skyblocker Waypoints] Encountered exception while parsing Skytils waypoint data version", e);
+ } catch (IllegalArgumentException e) {
+ LOGGER.error("[Skyblocker Waypoints] Encountered exception while decoding Skytils waypoint data", e);
+ }
+ return Collections.emptyList();
+ }
+
+ public static List<WaypointCategory> fromSkytilsJson(String waypointCategories, String defaultIsland) {
+ JsonArray waypointCategoriesJson;
+ try {
+ waypointCategoriesJson = SkyblockerMod.GSON.fromJson(waypointCategories, JsonObject.class).getAsJsonArray("categories");
+ } catch (JsonSyntaxException e) {
+ JsonObject waypointCategoryJson = new JsonObject();
+ waypointCategoryJson.addProperty("name", "New Category");
+ waypointCategoryJson.addProperty("island", defaultIsland);
+ waypointCategoryJson.add("waypoints", SkyblockerMod.GSON.fromJson(waypointCategories, JsonArray.class));
+ waypointCategoriesJson = new JsonArray();
+ waypointCategoriesJson.add(waypointCategoryJson);
+ }
+ return SKYTILS_CODEC.parse(JsonOps.INSTANCE, waypointCategoriesJson).resultOrPartial(LOGGER::error).orElseThrow();
+ }
+
+ public static String toSkytilsBase64(List<WaypointCategory> waypointCategories) {
+ return Base64.getEncoder().encodeToString(toSkytilsJson(waypointCategories).getBytes());
+ }
+
+ public static String toSkytilsJson(List<WaypointCategory> waypointCategories) {
+ JsonObject waypointCategoriesJson = new JsonObject();
+ waypointCategoriesJson.add("categories", SKYTILS_CODEC.encodeStart(JsonOps.INSTANCE, waypointCategories).resultOrPartial(LOGGER::error).orElseThrow());
+ return SkyblockerMod.GSON_COMPACT.toJson(waypointCategoriesJson);
+ }
+
+ public static void saveWaypoints(MinecraftClient client) {
+ try (BufferedWriter writer = Files.newBufferedWriter(waypointsFile)) {
+ JsonElement waypointsJson = CODEC.encodeStart(JsonOps.INSTANCE, List.copyOf(waypoints.values())).resultOrPartial(LOGGER::error).orElseThrow();
+ SkyblockerMod.GSON.toJson(waypointsJson, writer);
+ LOGGER.info("[Skyblocker Waypoints] Saved waypoints");
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker Waypoints] Encountered exception while saving waypoints", e);
+ }
+ }
+
+ public static Multimap<String, WaypointCategory> waypointsDeepCopy() {
+ return waypoints.values().stream().map(WaypointCategory::deepCopy).collect(Multimaps.toMultimap(WaypointCategory::island, Function.identity(), () -> MultimapBuilder.hashKeys().arrayListValues().build()));
+ }
+
+ public static void render(WorldRenderContext context) {
+ if (SkyblockerConfigManager.get().uiAndVisuals.waypoints.enableWaypoints) {
+ Collection<WaypointCategory> categories = waypoints.get(Utils.getLocationRaw());
+ for (WaypointCategory category : categories) {
+ if (category != null) {
+ category.render(context);
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsListWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsListWidget.java
new file mode 100644
index 00000000..045c694d
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsListWidget.java