diff options
| author | Aaron <51387595+AzureAaron@users.noreply.github.com> | 2024-06-13 02:06:51 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-06-13 02:06:51 -0400 |
| commit | 7b288fbfccb93ed8a10242acf6532127c41f6dfb (patch) | |
| tree | 2b3842d1eb8b2d45dab7df29919a127dc76fbe09 /src | |
| parent | abd23322c50f449e1cf5b1ae3e5d8b9ca53489c9 (diff) | |
| parent | bd11cc01bfa90695ad7498336a90ee4efb5ec813 (diff) | |
| download | Skyblocker-7b288fbfccb93ed8a10242acf6532127c41f6dfb.tar.gz Skyblocker-7b288fbfccb93ed8a10242acf6532127c41f6dfb.tar.bz2 Skyblocker-7b288fbfccb93ed8a10242acf6532127c41f6dfb.zip | |
Merge pull request #713 from kevinthegreat1/waypoint
Waypoints
Diffstat (limited to 'src')
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 |
