diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index 4b56257407..75918fa972 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java @@ -40,6 +40,7 @@ import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; 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.utils.ApiUtils; import de.hysky.skyblocker.utils.NEURepoManager; @@ -109,6 +110,7 @@ public void onInitializeClient() { FairySouls.init(); Relics.init(); MythologicalRitual.init(); + OrderedWaypoints.init(); BackpackPreview.init(); QuickNav.init(); ItemCooldowns.init(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java index 509f79b731..639e98edee 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java @@ -73,7 +73,7 @@ private static int customizeDyeColor(FabricClientCommandSource source, String he return Command.SINGLE_SUCCESS; } - private static boolean isHexadecimalColor(String s) { + public static boolean isHexadecimalColor(String s) { return s.replace("#", "").chars().allMatch(c -> "0123456789ABCDEFabcdef".indexOf(c) >= 0) && s.replace("#", "").length() == 6; } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java new file mode 100644 index 0000000000..93c6b3f43c --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java @@ -0,0 +1,444 @@ +package de.hysky.skyblocker.skyblock.waypoint; + +import static com.mojang.brigadier.arguments.StringArgumentType.getString; +import static com.mojang.brigadier.arguments.StringArgumentType.word; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Semaphore; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import org.slf4j.Logger; + +import com.google.common.primitives.Floats; +import com.google.gson.Gson; +import com.google.gson.JsonParser; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.logging.LogUtils; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.skyblock.item.CustomArmorDyeColors; +import de.hysky.skyblocker.utils.Constants; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.render.RenderHelper; +import de.hysky.skyblocker.utils.waypoint.Waypoint; +import it.unimi.dsi.fastutil.floats.FloatArrayList; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +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.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.command.CommandSource; +import net.minecraft.command.argument.BlockPosArgumentType; +import net.minecraft.command.argument.PosArgument; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.Text; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; + +public class OrderedWaypoints { + private static final Logger LOGGER = LogUtils.getLogger(); + private static final Codec> SERIALIZATION_CODEC = Codec.unboundedMap(Codec.STRING, OrderedWaypointGroup.CODEC).xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new); + private static final String PREFIX = "[Skyblocker::OrderedWaypoints::v1]"; + private static final Path PATH = SkyblockerMod.CONFIG_DIR.resolve("ordered_waypoints.json"); + private static final Map WAYPOINTS = new Object2ObjectOpenHashMap<>(); + private static final Semaphore SEMAPHORE = new Semaphore(1); + private static final Object2IntOpenHashMap INDEX_STORE = new Object2IntOpenHashMap<>(); + private static final int RADIUS = 2; + private static final float[] LIGHT_GRAY = { 192 / 255f, 192 / 255f, 192 / 255f }; + + private static CompletableFuture loaded; + + public static void init() { + ClientLifecycleEvents.CLIENT_STARTED.register(_client -> load()); + ClientLifecycleEvents.CLIENT_STOPPING.register(_client -> save()); + ClientCommandRegistrationCallback.EVENT.register(OrderedWaypoints::registerCommands); + WorldRenderEvents.AFTER_TRANSLUCENT.register(OrderedWaypoints::render); + } + + private static void registerCommands(CommandDispatcher dispatcher, CommandRegistryAccess registryAccess) { + dispatcher.register(literal(SkyblockerMod.NAMESPACE) + .then(literal("waypoints") + .then(literal("ordered") + .then(literal("add") + .then(argument("groupName", word()) + .suggests((source, builder) -> CommandSource.suggestMatching(WAYPOINTS.keySet(), builder)) + .then(argument("pos", BlockPosArgumentType.blockPos()) + .executes(context -> addWaypoint(context.getSource(), getString(context, "groupName"), context.getArgument("pos", PosArgument.class), Integer.MIN_VALUE, null)) + .then(argument("hex", word()) + .executes(context -> addWaypoint(context.getSource(), getString(context, "groupName"), context.getArgument("pos", PosArgument.class), Integer.MIN_VALUE, getString(context, "hex"))))))) + .then(literal("addAt") + .then(argument("groupName", word()) + .suggests((source, builder) -> CommandSource.suggestMatching(WAYPOINTS.keySet(), builder)) + .then(argument("index", IntegerArgumentType.integer(0)) + .then(argument("pos", BlockPosArgumentType.blockPos()) + .executes(context -> addWaypoint(context.getSource(), getString(context, "groupName"), context.getArgument("pos", PosArgument.class), IntegerArgumentType.getInteger(context, "index"), null)) + .then(argument("hex", word()) + .executes(context -> addWaypoint(context.getSource(), getString(context, "groupName"), context.getArgument("pos", PosArgument.class), IntegerArgumentType.getInteger(context, "index"), getString(context, "hex")))))))) + .then(literal("remove") + .then(argument("groupName", word()) + .suggests((source, builder) -> CommandSource.suggestMatching(WAYPOINTS.keySet(), builder)) + .executes(context -> removeWaypointGroup(context.getSource(), getString(context, "groupName"))) + .then(argument("pos", BlockPosArgumentType.blockPos()) + .executes(context -> removeWaypoint(context.getSource(), getString(context, "groupName"), context.getArgument("pos", PosArgument.class), Integer.MIN_VALUE))))) + .then(literal("removeAt") + .then(argument("groupName", word()) + .suggests((source, builder) -> CommandSource.suggestMatching(WAYPOINTS.keySet(), builder)) + .then(argument("index", IntegerArgumentType.integer(0)) + .executes(context -> removeWaypoint(context.getSource(), getString(context, "groupName"), null, IntegerArgumentType.getInteger(context, "index")))))) + .then(literal("toggle") + .then(argument("groupName", word()) + .suggests((source, builder) -> CommandSource.suggestMatching(WAYPOINTS.keySet(), builder)) + .executes(context -> toggleGroup(context.getSource(), getString(context, "groupName"))))) + .then(literal("import") + .then(literal("coleWeight") + .then(argument("groupName", word()) + .executes(context -> fromColeWeightFormat(context.getSource(), getString(context, "groupName"))))) + .then(literal("skyblocker") + .executes(context -> fromSkyblockerFormat(context.getSource())))) + .then(literal("export") + .executes(context -> export(context.getSource())))))); + } + + private static int addWaypoint(FabricClientCommandSource source, String groupName, PosArgument posArgument, int index, String hex) { + BlockPos pos = posArgument.toAbsoluteBlockPos(new ServerCommandSource(null, source.getPosition(), source.getRotation(), null, 0, null, null, null, null)); + + SEMAPHORE.acquireUninterruptibly(); + + if (hex != null && !CustomArmorDyeColors.isHexadecimalColor(hex)) { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.add.invalidHexColor"))); + SEMAPHORE.release(); + + return Command.SINGLE_SUCCESS; + } + + int rgb = hex != null ? Integer.decode("0x" + hex.replace("#", "")) : Integer.MIN_VALUE; + float[] colorComponents = rgb != Integer.MIN_VALUE ? new float[] { ((rgb >> 16) & 0xFF) / 255f, ((rgb >> 8) & 0xFF) / 255f, (rgb & 0xFF) / 255f } : new float[0]; + + OrderedWaypointGroup group = WAYPOINTS.computeIfAbsent(groupName, name -> new OrderedWaypointGroup(name, true, new ObjectArrayList<>())); + OrderedWaypoint waypoint = new OrderedWaypoint(pos, colorComponents); + + if (index != Integer.MIN_VALUE) { + int indexToAddAt = MathHelper.clamp(index, 0, group.waypoints().size()); + + group.waypoints().add(indexToAddAt, waypoint); + INDEX_STORE.removeInt(group.name()); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.addAt.success", group.name(), indexToAddAt))); + } else { + group.waypoints().add(waypoint); + INDEX_STORE.removeInt(group.name()); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.add.success", group.name(), pos.toShortString()))); + } + + SEMAPHORE.release(); + + return Command.SINGLE_SUCCESS; + } + + private static int removeWaypointGroup(FabricClientCommandSource source, String groupName) { + if (WAYPOINTS.containsKey(groupName)) { + SEMAPHORE.acquireUninterruptibly(); + WAYPOINTS.remove(groupName); + INDEX_STORE.removeInt(groupName); + SEMAPHORE.release(); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.removeGroup.success", groupName))); + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.groupNonExistent", groupName))); + } + + return Command.SINGLE_SUCCESS; + } + + private static int removeWaypoint(FabricClientCommandSource source, String groupName, PosArgument posArgument, int index) { + if (WAYPOINTS.containsKey(groupName)) { + SEMAPHORE.acquireUninterruptibly(); + OrderedWaypointGroup group = WAYPOINTS.get(groupName); + + if (posArgument != null) { + BlockPos pos = posArgument.toAbsoluteBlockPos(new ServerCommandSource(null, source.getPosition(), source.getRotation(), null, 0, null, null, null, null)); + + group.waypoints().removeIf(waypoint -> waypoint.getPos().equals(pos)); + INDEX_STORE.removeInt(group.name()); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.remove.success", pos.toShortString(), group.name()))); + } + + if (index != Integer.MIN_VALUE) { + int indexToRemove = MathHelper.clamp(index, 0, group.waypoints().size() - 1); + + group.waypoints().remove(indexToRemove); + INDEX_STORE.removeInt(group.name()); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.removeAt.success", indexToRemove, group.name()))); + } + + SEMAPHORE.release(); + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.groupNonExistent", groupName))); + } + + return Command.SINGLE_SUCCESS; + } + + private static int toggleGroup(FabricClientCommandSource source, String groupName) { + if (WAYPOINTS.containsKey(groupName)) { + SEMAPHORE.acquireUninterruptibly(); + WAYPOINTS.put(groupName, WAYPOINTS.get(groupName).toggle()); + SEMAPHORE.release(); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.toggle.success", groupName))); + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.groupNonExistent", groupName))); + } + + return Command.SINGLE_SUCCESS; + } + + private static void render(WorldRenderContext wrc) { + if ((Utils.isInCrystalHollows() || Utils.isInDwarvenMines()) && loaded.isDone() && SEMAPHORE.tryAcquire()) { + for (OrderedWaypointGroup group : WAYPOINTS.values()) { + if (group.enabled()) { + List waypoints = group.waypoints(); + ClientPlayerEntity player = MinecraftClient.getInstance().player; + int centreIndex = INDEX_STORE.computeIfAbsent(group.name(), name -> 0); + + for (int i = 0; i < waypoints.size(); i++) { + OrderedWaypoint waypoint = waypoints.get(i); + + if (waypoint.getPos().isWithinDistance(player.getPos(), RADIUS)) { + centreIndex = i; + INDEX_STORE.put(group.name(), i); + + break; + } + } + + int previousIndex = (centreIndex - 1 + waypoints.size()) % waypoints.size(); + int currentIndex = (centreIndex + waypoints.size()) % waypoints.size(); + int nextIndex = (centreIndex + 1) % waypoints.size(); + + OrderedWaypoint previous = waypoints.get(previousIndex); + OrderedWaypoint current = waypoints.get(currentIndex); + OrderedWaypoint next = waypoints.get(nextIndex); + + previous.render(wrc, RelativeIndex.PREVIOUS, previousIndex); + current.render(wrc, RelativeIndex.CURRENT, currentIndex); + next.render(wrc, RelativeIndex.NEXT, nextIndex); + + RenderHelper.renderLineFromCursor(wrc, Vec3d.ofCenter(next.getPos().up()), LIGHT_GRAY, 1f, 5f); + } + } + + SEMAPHORE.release(); + } + } + + private static void load() { + loaded = CompletableFuture.runAsync(() -> { + try (BufferedReader reader = Files.newBufferedReader(PATH)) { + WAYPOINTS.putAll(SERIALIZATION_CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).result().orElseThrow()); + } catch (NoSuchFileException ignored) { + } catch (Exception e) { + LOGGER.error("[Skyblocker Ordered Waypoints] Failed to load the waypoints! :(", e); + } + }); + } + + private static void save() { + try (BufferedWriter writer = Files.newBufferedWriter(PATH)) { + SkyblockerMod.GSON.toJson(SERIALIZATION_CODEC.encodeStart(JsonOps.INSTANCE, WAYPOINTS).result().orElseThrow(), writer); + } catch (Exception e) { + LOGGER.error("[Skyblocker Ordered Waypoints] Failed to save the waypoints! :(", e); + } + } + + private static int export(FabricClientCommandSource source) { + try { + String json = new Gson().toJson(SERIALIZATION_CODEC.encodeStart(JsonOps.INSTANCE, WAYPOINTS).result().orElseThrow()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(out); + + gzip.write(json.getBytes()); + gzip.close(); + + String encoded = new String(Base64.getEncoder().encode(out.toByteArray())); + String exportCode = PREFIX + encoded; + + MinecraftClient.getInstance().keyboard.setClipboard(exportCode); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.export.success"))); + } catch (Exception e) { + LOGGER.error("[Skyblocker Ordered Waypoints] Failed to export waypoints!", e); + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.export.fail"))); + } + + return Command.SINGLE_SUCCESS; + } + + //TODO in future handle for when the group names clash? + private static int fromSkyblockerFormat(FabricClientCommandSource source) { + try { + String importCode = MinecraftClient.getInstance().keyboard.getClipboard(); + + if (importCode.startsWith(PREFIX)) { + String encoded = importCode.replace(PREFIX, ""); + byte[] decoded = Base64.getDecoder().decode(encoded); + + String json = new String(new GZIPInputStream(new ByteArrayInputStream(decoded)).readAllBytes()); + Map importedWaypoints = SERIALIZATION_CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString(json)).result().orElseThrow(); + + SEMAPHORE.acquireUninterruptibly(); + WAYPOINTS.putAll(importedWaypoints); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.import.skyblocker.success"))); + SEMAPHORE.release(); + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.import.skyblocker.unknownFormatHeader"))); + } + } catch (Exception e) { + LOGGER.error("[Skyblocker Ordered Waypoints] Failed to import waypoints!", e); + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.import.skyblocker.fail"))); + } + + return Command.SINGLE_SUCCESS; + } + + private static int fromColeWeightFormat(FabricClientCommandSource source, String groupName) { + try { + if (WAYPOINTS.containsKey(groupName)) { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.import.coleWeight.groupAlreadyExists", groupName))); + + return Command.SINGLE_SUCCESS; + } + + String json = MinecraftClient.getInstance().keyboard.getClipboard(); + List coleWeightWaypoints = ColeWeightWaypoint.LIST_CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString(json)).result().orElseThrow(); + ObjectArrayList convertedWaypoints = new ObjectArrayList<>(); + + for (ColeWeightWaypoint waypoint : coleWeightWaypoints) { + if (waypoint.x().isPresent() && waypoint.y().isPresent() && waypoint.z().isPresent()) { + //I think Cole Weight ignores the colors and overrides them so we will comment this out + //float[] colorComponents = (waypoint.r().isPresent() && waypoint.g().isPresent() && waypoint.b().isPresent()) ? new float[] { waypoint.r().get() / 255f, waypoint.g().get() / 255f, waypoint.b().get() / 255f } : new float[0]; + + convertedWaypoints.add(new OrderedWaypoint(new BlockPos(waypoint.x().get(), waypoint.y().get(), waypoint.z().get()), new float[0])); + } + } + + SEMAPHORE.acquireUninterruptibly(); + WAYPOINTS.put(groupName, new OrderedWaypointGroup(groupName, true, convertedWaypoints)); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.import.coleWeight.success"))); + SEMAPHORE.release(); + } catch (Exception e) { + LOGGER.error("[Skyblocker Ordered Waypoints] Failed to import waypoints from the Cole Weight format!", e); + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.waypoints.ordered.import.coleWeight.fail"))); + } + + return Command.SINGLE_SUCCESS; + } + + private record OrderedWaypointGroup(String name, boolean enabled, ObjectArrayList waypoints) { + static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.fieldOf("name").forGetter(OrderedWaypointGroup::name), + Codec.BOOL.fieldOf("enabled").forGetter(OrderedWaypointGroup::enabled), + OrderedWaypoint.LIST_CODEC.fieldOf("waypoints").xmap(ObjectArrayList::new, ObjectArrayList::new).forGetter(OrderedWaypointGroup::waypoints)) + .apply(instance, OrderedWaypointGroup::new)); + + OrderedWaypointGroup toggle() { + return new OrderedWaypointGroup(name, !enabled, waypoints); + } + } + + private static class OrderedWaypoint extends Waypoint { + static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + BlockPos.CODEC.fieldOf("pos").forGetter(OrderedWaypoint::getPos), + Codec.floatRange(0, 1).listOf().xmap(Floats::toArray, FloatArrayList::new).optionalFieldOf("colorComponents", new float[0]).forGetter(inst -> inst.colorComponents.length == 3 ? inst.colorComponents : new float[0])) + .apply(instance, OrderedWaypoint::new)); + static final Codec> LIST_CODEC = CODEC.listOf(); + static final float[] RED = { 1f, 0f, 0f }; + static final float[] WHITE = { 1f, 1f, 1f }; + static final float[] GREEN = { 0f, 1f, 0f }; + + private RelativeIndex relativeIndex; + private int waypointIndex; + + OrderedWaypoint(BlockPos pos, float[] colorComponents) { + super(pos, Type.WAYPOINT, colorComponents); + } + + private BlockPos getPos() { + return this.pos; + } + + @Override + protected float[] getColorComponents() { + if (this.colorComponents.length != 3) { + return switch (this.relativeIndex) { + case PREVIOUS -> RED; + case CURRENT -> WHITE; + case NEXT -> GREEN; + }; + } + + return this.colorComponents; + } + + private void render(WorldRenderContext context, RelativeIndex relativeIndex, int waypointIndex) { + this.relativeIndex = relativeIndex; + this.waypointIndex = waypointIndex; + + render(context); + } + + @Override + public void render(WorldRenderContext context) { + super.render(context); + RenderHelper.renderText(context, Text.of(String.valueOf(waypointIndex)), Vec3d.ofCenter(pos.up(2)), true); + } + } + + private record ColeWeightWaypoint(Optional x, Optional y, Optional z, Optional r, Optional g, Optional b, Optional options) { + static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.INT.optionalFieldOf("x").forGetter(ColeWeightWaypoint::x), + Codec.INT.optionalFieldOf("y").forGetter(ColeWeightWaypoint::y), + Codec.INT.optionalFieldOf("z").forGetter(ColeWeightWaypoint::z), + Codec.INT.optionalFieldOf("r").forGetter(ColeWeightWaypoint::r), + Codec.INT.optionalFieldOf("g").forGetter(ColeWeightWaypoint::g), + Codec.INT.optionalFieldOf("b").forGetter(ColeWeightWaypoint::b), + Options.CODEC.optionalFieldOf("options").forGetter(ColeWeightWaypoint::options)) + .apply(instance, ColeWeightWaypoint::new)); + static final Codec> LIST_CODEC = CODEC.listOf(); + + //Even though we don't import the name this is still here incase that eventually changes + record Options(Optional name) { + static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.optionalFieldOf("name").forGetter(Options::name)) + .apply(instance, Options::new)); + } + } + + private enum RelativeIndex { + PREVIOUS, + CURRENT, + NEXT + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java index 05514d025b..e39b5364bf 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java @@ -187,6 +187,56 @@ public static void renderLinesFromPoints(WorldRenderContext context, Vec3d[] poi RenderSystem.depthFunc(GL11.GL_LEQUAL); } + public static void renderLineFromCursor(WorldRenderContext context, Vec3d point, float[] colorComponents, float alpha, float lineWidth) { + Vec3d camera = context.camera().getPos(); + MatrixStack matrices = context.matrixStack(); + + matrices.push(); + matrices.translate(-camera.x, -camera.y, -camera.z); + + Tessellator tessellator = RenderSystem.renderThreadTesselator(); + BufferBuilder buffer = tessellator.getBuffer(); + Matrix4f positionMatrix = matrices.peek().getPositionMatrix(); + + GL11.glEnable(GL11.GL_LINE_SMOOTH); + GL11.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_NICEST); + + RenderSystem.setShader(GameRenderer::getRenderTypeLinesProgram); + RenderSystem.setShaderColor(1f, 1f, 1f, 1f); + RenderSystem.lineWidth(lineWidth); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.disableCull(); + RenderSystem.enableDepthTest(); + RenderSystem.depthFunc(GL11.GL_ALWAYS); + + Vec3d offset = Vec3d.fromPolar(context.camera().getPitch(), context.camera().getYaw()); + Vec3d cameraPoint = camera.add(offset); + + buffer.begin(DrawMode.LINES, VertexFormats.LINES); + Vector3f normal = new Vector3f((float) offset.x, (float) offset.y, (float) offset.z); + buffer + .vertex(positionMatrix, (float) cameraPoint.x , (float) cameraPoint.y, (float) cameraPoint.z) + .color(colorComponents[0], colorComponents[1], colorComponents[2], alpha) + .normal(normal.x, normal.y, normal.z) + .next(); + + buffer + .vertex(positionMatrix, (float) point.getX(), (float) point.getY(), (float) point.getZ()) + .color(colorComponents[0], colorComponents[1], colorComponents[2], alpha) + .normal(normal.x, normal.y, normal.z) + .next(); + + + tessellator.draw(); + + matrices.pop(); + GL11.glDisable(GL11.GL_LINE_SMOOTH); + RenderSystem.lineWidth(1f); + RenderSystem.enableCull(); + RenderSystem.depthFunc(GL11.GL_LEQUAL); + } + public static void renderQuad(WorldRenderContext context, Vec3d[] points, float[] colorComponents, float alpha, boolean throughWalls) { Vec3d camera = context.camera().getPos(); MatrixStack matrices = context.matrixStack(); diff --git a/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java b/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java index 7f3d4eda7d..622e165846 100644 --- a/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java +++ b/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java @@ -14,7 +14,7 @@ public class Waypoint implements Renderable { public final BlockPos pos; final Box box; final Supplier typeSupplier; - final float[] colorComponents; + protected final float[] colorComponents; final float alpha; final float lineWidth; final boolean throughWalls; diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 4587a647a2..84409b9fe7 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -604,6 +604,23 @@ "skyblocker.crimson.kuudra.noArrowPoison": "No Arrow Poison!", "skyblocker.crimson.kuudra.lowArrowPoison": "Low on Arrow Poison!", + + "skyblocker.waypoints.ordered.groupNonExistent": "§cThe waypoint group %s doesn't exist.", + "skyblocker.waypoints.ordered.add.invalidHexColor": "§cInvalid HEX color code!", + "skyblocker.waypoints.ordered.addAt.success": "Added a waypoint in group %s at index %d.", + "skyblocker.waypoints.ordered.add.success": "Added a waypoint in group %s at %s.", + "skyblocker.waypoints.ordered.removeGroup.success": "Successfully removed the waypoint group %s.", + "skyblocker.waypoints.ordered.remove.success": "Successfully removed the waypoint at %s from group %s.", + "skyblocker.waypoints.ordered.removeAt.success": "Successfully removed the waypoint at index %d from group %s.", + "skyblocker.waypoints.ordered.toggle.success": "Toggled the waypoint group %s.", + "skyblocker.waypoints.ordered.export.success": "Successfully copied your waypoints to your clipboard!", + "skyblocker.waypoints.ordered.export.fail": "§cFailed to export your waypoints, check the latest.log for more information.", + "skyblocker.waypoints.ordered.import.skyblocker.success": "Successfully imported waypoints from the Skyblocker Ordered Waypoints format!", + "skyblocker.waypoints.ordered.import.skyblocker.unknownFormatHeader": "§cThis import code doesn't look like its in the Skyblocker Ordered Waypoints format, double check your clipboard to see if everything is correct.", + "skyblocker.waypoints.ordered.import.skyblocker.fail": "§cFailed to import waypoints from the Skyblocker Ordered Waypoints format. Make sure to have the waypoint data copied to your clipboard!", + "skyblocker.waypoints.ordered.import.coleWeight.groupAlreadyExists": "§cThere is already an ordered waypoints group under the name \"%s\", please choose another name to import your waypoints under.", + "skyblocker.waypoints.ordered.import.coleWeight.success": "Successfully imported waypoints from the Cole Weight format.", + "skyblocker.waypoints.ordered.import.coleWeight.fail": "§cFailed to import waypoints from the Cole Weight format. Make sure to have the waypoint data copied to your clipboard!", "emi.category.skyblocker.skyblock": "Skyblock" }