aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java22
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/diana/MythologicalRitual.java23
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java183
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java86
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java156
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/BackpackPreview.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java99
-rw-r--r--src/main/resources/assets/skyblocker/lang/en_us.json3
9 files changed, 457 insertions, 123 deletions
diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
index 51f3f098..8905644f 100644
--- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
@@ -2,6 +2,7 @@ package de.hysky.skyblocker.config;
import de.hysky.skyblocker.skyblock.item.CustomArmorTrims;
import de.hysky.skyblocker.utils.chat.ChatFilterResult;
+import de.hysky.skyblocker.utils.waypoint.Waypoint;
import dev.isxander.yacl3.config.v2.api.SerialEntry;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
@@ -582,7 +583,7 @@ public class SkyblockerConfig {
public boolean noInitSecretWaypoints = false;
@SerialEntry
- public WaypointType waypointType = WaypointType.WAYPOINT;
+ public Waypoint.Type waypointType = Waypoint.Type.WAYPOINT;
@SerialEntry
public boolean showSecretText = true;
@@ -623,25 +624,6 @@ public class SkyblockerConfig {
@SerialEntry
public boolean enableDefaultWaypoints = true;
}
-
- public enum WaypointType {
- WAYPOINT,
- OUTLINED_WAYPOINT,
- HIGHLIGHT,
- OUTLINED_HIGHLIGHT,
- OUTLINE;
-
- @Override
- public String toString() {
- return switch (this) {
- case WAYPOINT -> "Waypoint";
- case OUTLINED_WAYPOINT -> "Outlined Waypoint";
- case HIGHLIGHT -> "Highlight";
- case OUTLINED_HIGHLIGHT -> "Outlined Highlight";
- case OUTLINE -> "Outline";
- };
- }
- }
public static class DungeonChestProfit {
@SerialEntry
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
index 2cdde89d..fdb13892 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
@@ -2,7 +2,7 @@ package de.hysky.skyblocker.config.categories;
import de.hysky.skyblocker.config.ConfigUtils;
import de.hysky.skyblocker.config.SkyblockerConfig;
-import de.hysky.skyblocker.config.SkyblockerConfig.WaypointType;
+import de.hysky.skyblocker.utils.waypoint.Waypoint.Type;
import dev.isxander.yacl3.api.ButtonOption;
import dev.isxander.yacl3.api.ConfigCategory;
import dev.isxander.yacl3.api.Option;
@@ -44,7 +44,7 @@ public class DungeonsCategory {
.controller(ConfigUtils::createBooleanController)
.flag(OptionFlag.GAME_RESTART)
.build())
- .option(Option.<WaypointType>createBuilder()
+ .option(Option.<Type>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.waypointType"))
.description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.waypointType.@Tooltip")))
.binding(defaults.locations.dungeons.secretWaypoints.waypointType,
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/diana/MythologicalRitual.java b/src/main/java/de/hysky/skyblocker/skyblock/diana/MythologicalRitual.java
index c407e911..e2962702 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/diana/MythologicalRitual.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/diana/MythologicalRitual.java
@@ -6,6 +6,7 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.render.RenderHelper;
+import de.hysky.skyblocker.utils.waypoint.Waypoint;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
@@ -49,7 +50,7 @@ public class MythologicalRitual {
private static final Map<BlockPos, GriffinBurrow> griffinBurrows = new HashMap<>();
@Nullable
private static BlockPos lastDugBurrowPos;
- private static GriffinBurrow previousBurrow = new GriffinBurrow();
+ private static GriffinBurrow previousBurrow = new GriffinBurrow(BlockPos.ORIGIN);
public static void init() {
WorldRenderEvents.AFTER_TRANSLUCENT.register(MythologicalRitual::render);
@@ -82,7 +83,7 @@ public class MythologicalRitual {
if (MinecraftClient.getInstance().world == null || !MinecraftClient.getInstance().world.getBlockState(pos).isOf(Blocks.GRASS_BLOCK)) {
return;
}
- GriffinBurrow burrow = griffinBurrows.computeIfAbsent(pos, pos1 -> new GriffinBurrow());
+ GriffinBurrow burrow = griffinBurrows.computeIfAbsent(pos, GriffinBurrow::new);
if (ParticleTypes.CRIT.equals(packet.getParameters().getType())) burrow.critParticle++;
if (ParticleTypes.ENCHANT.equals(packet.getParameters().getType())) burrow.enchantParticle++;
if (burrow.critParticle >= 5 && burrow.enchantParticle >= 5 && burrow.confirmed == TriState.FALSE) {
@@ -133,10 +134,9 @@ public class MythologicalRitual {
public static void render(WorldRenderContext context) {
if (isActive()) {
- for (Map.Entry<BlockPos, GriffinBurrow> burrowEntry : griffinBurrows.entrySet()) {
- GriffinBurrow burrow = burrowEntry.getValue();
- if (burrow.confirmed == TriState.TRUE) {
- RenderHelper.renderFilledThroughWallsWithBeaconBeam(context, burrowEntry.getKey(), ORANGE_COLOR_COMPONENTS, 0.25F);
+ for (GriffinBurrow burrow : griffinBurrows.values()) {
+ if (burrow.shouldRender()) {
+ burrow.render(context);
}
if (burrow.confirmed != TriState.FALSE) {
if (burrow.nextBurrowPlane != null) {
@@ -186,7 +186,7 @@ public class MythologicalRitual {
return SkyblockerConfigManager.get().general.mythologicalRitual.enableMythologicalRitualHelper && Utils.getLocationRaw().equals("hub");
}
- private static class GriffinBurrow {
+ private static class GriffinBurrow extends Waypoint {
private int critParticle;
private int enchantParticle;
private TriState confirmed = TriState.FALSE;
@@ -196,9 +196,18 @@ public class MythologicalRitual {
private Vec3d[] echoBurrowDirection;
private Vec3d[] echoBurrowPlane;
+ private GriffinBurrow(BlockPos pos) {
+ super(pos, Type.WAYPOINT, ORANGE_COLOR_COMPONENTS, 0.25F);
+ }
+
private void init() {
confirmed = TriState.TRUE;
regression.clear();
}
+
+ @Override
+ public boolean shouldRender() {
+ return super.shouldRender() && confirmed == TriState.TRUE;
+ }
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java
index 08b84852..eda08cf6 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java
@@ -1,5 +1,7 @@
package de.hysky.skyblocker.skyblock.dungeon.secrets;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@@ -7,6 +9,8 @@ import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.serialization.JsonOps;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.utils.Constants;
@@ -17,6 +21,7 @@ import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
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.message.v1.ClientReceiveMessageEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
@@ -24,6 +29,9 @@ import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.command.argument.BlockPosArgumentType;
+import net.minecraft.command.argument.PosArgument;
+import net.minecraft.command.argument.TextArgumentType;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.mob.AmbientEntity;
@@ -33,6 +41,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.item.map.MapState;
import net.minecraft.resource.Resource;
+import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Identifier;
@@ -50,10 +59,14 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
+import java.io.BufferedWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
+import java.util.stream.Stream;
import java.util.zip.InflaterInputStream;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
@@ -62,6 +75,7 @@ import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.lit
public class DungeonSecrets {
protected static final Logger LOGGER = LoggerFactory.getLogger(DungeonSecrets.class);
private static final String DUNGEONS_PATH = "dungeons";
+ private static final Path CUSTOM_WAYPOINTS_DIR = SkyblockerMod.CONFIG_DIR.resolve("custom_secret_waypoints.json");
/**
* Maps the block identifier string to a custom numeric block id used in dungeon rooms data.
*
@@ -101,6 +115,10 @@ public class DungeonSecrets {
private static final Map<Vector2ic, Room> rooms = new HashMap<>();
private static final Map<String, JsonElement> roomsJson = new HashMap<>();
private static final Map<String, JsonElement> waypointsJson = new HashMap<>();
+ /**
+ * The map of dungeon room names to custom waypoints relative to the room.
+ */
+ private static final Table<String, BlockPos, SecretWaypoint> customWaypoints = HashBasedTable.create();
@Nullable
private static CompletableFuture<Void> roomsLoaded;
/**
@@ -123,6 +141,10 @@ public class DungeonSecrets {
return roomsLoaded != null && roomsLoaded.isDone();
}
+ public static Stream<Room> getRoomsStream() {
+ return rooms.values().stream();
+ }
+
@SuppressWarnings("unused")
public static JsonObject getRoomMetadata(String room) {
return roomsJson.get(room).getAsJsonObject();
@@ -133,6 +155,38 @@ public class DungeonSecrets {
}
/**
+ * @see #customWaypoints
+ */
+ public static Map<BlockPos, SecretWaypoint> getCustomWaypoints(String room) {
+ return customWaypoints.row(room);
+ }
+
+ /**
+ * @see #customWaypoints
+ */
+ @SuppressWarnings("UnusedReturnValue")
+ public static SecretWaypoint addCustomWaypoint(String room, SecretWaypoint waypoint) {
+ return customWaypoints.put(room, waypoint.pos, waypoint);
+ }
+
+ /**
+ * @see #customWaypoints
+ */
+ public static void addCustomWaypoints(String room, Collection<SecretWaypoint> waypoints) {
+ for (SecretWaypoint waypoint : waypoints) {
+ addCustomWaypoint(room, waypoint);
+ }
+ }
+
+ /**
+ * @see #customWaypoints
+ */
+ @Nullable
+ public static SecretWaypoint removeCustomWaypoint(String room, BlockPos pos) {
+ return customWaypoints.remove(room, pos);
+ }
+
+ /**
* Loads the dungeon secrets asynchronously from {@code /assets/skyblocker/dungeons}.
* Use {@link #isRoomsLoaded()} to check for completion of loading.
*/
@@ -142,9 +196,10 @@ public class DungeonSecrets {
}
// Execute with MinecraftClient as executor since we need to wait for MinecraftClient#resourceManager to be set
CompletableFuture.runAsync(DungeonSecrets::load, MinecraftClient.getInstance()).exceptionally(e -> {
- LOGGER.error("[Skyblocker] Failed to load dungeon secrets", e);
+ LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load dungeon secrets", e);
return null;
});
+ ClientLifecycleEvents.CLIENT_STOPPING.register(DungeonSecrets::saveCustomWaypoints);
Scheduler.INSTANCE.scheduleCyclic(DungeonSecrets::update, 10);
WorldRenderEvents.AFTER_TRANSLUCENT.register(DungeonSecrets::render);
ClientReceiveMessageEvents.GAME.register(DungeonSecrets::onChatMessage);
@@ -153,8 +208,13 @@ public class DungeonSecrets {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("dungeons").then(literal("secrets")
.then(literal("markAsFound").then(markSecretsCommand(true)))
.then(literal("markAsMissing").then(markSecretsCommand(false)))
- .then(literal("getRelativePos").executes(context -> getRelativePos(context.getSource())))
- .then(literal("getRelativeTargetPos").executes(context -> getRelativeTargetPos(context.getSource())))))));
+ .then(literal("getRelativePos").executes(DungeonSecrets::getRelativePos))
+ .then(literal("getRelativeTargetPos").executes(DungeonSecrets::getRelativeTargetPos))
+ .then(literal("addWaypoint").then(addCustomWaypointCommand(false)))
+ .then(literal("addWaypointRelatively").then(addCustomWaypointCommand(true)))
+ .then(literal("removeWaypoint").then(removeCustomWaypointCommand(false)))
+ .then(literal("removeWaypointRelatively").then(removeCustomWaypointCommand(true)))
+ ))));
ClientPlayConnectionEvents.JOIN.register(((handler, sender, client) -> reset()));
}
@@ -164,7 +224,7 @@ public class DungeonSecrets {
for (Map.Entry<Identifier, Resource> resourceEntry : MinecraftClient.getInstance().getResourceManager().findResources(DUNGEONS_PATH, id -> id.getPath().endsWith(".skeleton")).entrySet()) {
String[] path = resourceEntry.getKey().getPath().split("/");
if (path.length != 4) {
- LOGGER.error("[Skyblocker] Failed to load dungeon secrets, invalid resource identifier {}", resourceEntry.getKey());
+ LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load dungeon secrets, invalid resource identifier {}", resourceEntry.getKey());
break;
}
String dungeon = path[1];
@@ -177,9 +237,9 @@ public class DungeonSecrets {
synchronized (roomsMap) {
roomsMap.put(room, rooms);
}
- LOGGER.debug("[Skyblocker] Loaded dungeon secrets dungeon {} room shape {} room {}", dungeon, roomShape, room);
+ LOGGER.debug("[Skyblocker Dungeon Secrets] Loaded dungeon secrets dungeon {} room shape {} room {}", dungeon, roomShape, room);
}).exceptionally(e -> {
- LOGGER.error("[Skyblocker] Failed to load dungeon secrets dungeon {} room shape {} room {}", dungeon, roomShape, room, e);
+ LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load dungeon secrets dungeon {} room shape {} room {}", dungeon, roomShape, room, e);
return null;
}));
}
@@ -187,16 +247,39 @@ public class DungeonSecrets {
try (BufferedReader roomsReader = MinecraftClient.getInstance().getResourceManager().openAsReader(new Identifier(SkyblockerMod.NAMESPACE, "dungeons/dungeonrooms.json")); BufferedReader waypointsReader = MinecraftClient.getInstance().getResourceManager().openAsReader(new Identifier(SkyblockerMod.NAMESPACE, "dungeons/secretlocations.json"))) {
loadJson(roomsReader, roomsJson);
loadJson(waypointsReader, waypointsJson);
- LOGGER.debug("[Skyblocker] Loaded dungeon secrets json");
+ LOGGER.debug("[Skyblocker Dungeon Secrets] Loaded dungeon secret waypoints json");
} catch (Exception e) {
- LOGGER.error("[Skyblocker] Failed to load dungeon secrets json", e);
+ LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load dungeon secret waypoints json", e);
}
}));
- roomsLoaded = CompletableFuture.allOf(dungeonFutures.toArray(CompletableFuture[]::new)).thenRun(() -> LOGGER.info("[Skyblocker] Loaded dungeon secrets for {} dungeon(s), {} room shapes, and {} rooms total in {} ms", ROOMS_DATA.size(), ROOMS_DATA.values().stream().mapToInt(Map::size).sum(), ROOMS_DATA.values().stream().map(Map::values).flatMap(Collection::stream).mapToInt(Map::size).sum(), System.currentTimeMillis() - startTime)).exceptionally(e -> {
- LOGGER.error("[Skyblocker] Failed to load dungeon secrets", e);
+ dungeonFutures.add(CompletableFuture.runAsync(() -> {
+ try (BufferedReader customWaypointsReader = Files.newBufferedReader(CUSTOM_WAYPOINTS_DIR)) {
+ SkyblockerMod.GSON.fromJson(customWaypointsReader, JsonObject.class).asMap().forEach((room, waypointsJson) ->
+ addCustomWaypoints(room, SecretWaypoint.LIST_CODEC.parse(JsonOps.INSTANCE, waypointsJson).resultOrPartial(LOGGER::error).orElseThrow())
+ );
+ LOGGER.debug("[Skyblocker Dungeon Secrets] Loaded custom dungeon secret waypoints");
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load custom dungeon secret waypoints", e);
+ }
+ }));
+ roomsLoaded = CompletableFuture.allOf(dungeonFutures.toArray(CompletableFuture[]::new)).thenRun(() -> LOGGER.info("[Skyblocker Dungeon Secrets] Loaded dungeon secrets for {} dungeon(s), {} room shapes, {} rooms, and {} custom secret waypoints total in {} ms", ROOMS_DATA.size(), ROOMS_DATA.values().stream().mapToInt(Map::size).sum(), ROOMS_DATA.values().stream().map(Map::values).flatMap(Collection::stream).mapToInt(Map::size).sum(), customWaypoints.size(), System.currentTimeMillis() - startTime)).exceptionally(e -> {
+ LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load dungeon secrets", e);
return null;
});
- LOGGER.info("[Skyblocker] Started loading dungeon secrets in (blocked main thread for) {} ms", System.currentTimeMillis() - startTime);
+ LOGGER.info("[Skyblocker Dungeon Secrets] Started loading dungeon secrets in (blocked main thread for) {} ms", System.currentTimeMillis() - startTime);
+ }
+
+ private static void saveCustomWaypoints(MinecraftClient client) {
+ try (BufferedWriter writer = Files.newBufferedWriter(CUSTOM_WAYPOINTS_DIR)) {
+ JsonObject customWaypointsJson = new JsonObject();
+ customWaypoints.rowMap().forEach((room, waypoints) ->
+ customWaypointsJson.add(room, SecretWaypoint.LIST_CODEC.encodeStart(JsonOps.INSTANCE, new ArrayList<>(waypoints.values())).resultOrPartial(LOGGER::error).orElseThrow())
+ );
+ SkyblockerMod.GSON.toJson(customWaypointsJson, writer);
+ LOGGER.info("[Skyblocker Dungeon Secrets] Saved custom dungeon secret waypoints");
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker Dungeon Secrets] Failed to save custom dungeon secret waypoints", e);
+ }
}
private static int[] readRoom(Resource resource) throws RuntimeException {
@@ -218,8 +301,8 @@ public class DungeonSecrets {
}
private static ArgumentBuilder<FabricClientCommandSource, RequiredArgumentBuilder<FabricClientCommandSource, Integer>> markSecretsCommand(boolean found) {
- return argument("secret", IntegerArgumentType.integer()).executes(context -> {
- int secretIndex = IntegerArgumentType.getInteger(context, "secret");
+ return argument("secretIndex", IntegerArgumentType.integer()).executes(context -> {
+ int secretIndex = IntegerArgumentType.getInteger(context, "secretIndex");
if (markSecrets(secretIndex, found)) {
context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable(found ? "skyblocker.dungeons.secrets.markSecretFound" : "skyblocker.dungeons.secrets.markSecretMissing", secretIndex)));
} else {
@@ -229,15 +312,15 @@ public class DungeonSecrets {
});
}
- private static int getRelativePos(FabricClientCommandSource source) {
- return getRelativePos(source, source.getPlayer().getBlockPos());
+ private static int getRelativePos(CommandContext<FabricClientCommandSource> context) {
+ return getRelativePos(context.getSource(), context.getSource().getPlayer().getBlockPos());
}
- private static int getRelativeTargetPos(FabricClientCommandSource source) {
+ private static int getRelativeTargetPos(CommandContext<FabricClientCommandSource> context) {
if (MinecraftClient.getInstance().crosshairTarget instanceof BlockHitResult blockHitResult && blockHitResult.getType() == HitResult.Type.BLOCK) {
- return getRelativePos(source, blockHitResult.getBlockPos());
+ return getRelativePos(context.getSource(), blockHitResult.getBlockPos());
} else {
- source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.noTarget")));
+ context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.noTarget")));
}
return Command.SINGLE_SUCCESS;
}
@@ -253,6 +336,66 @@ public class DungeonSecrets {
return Command.SINGLE_SUCCESS;
}
+ private static ArgumentBuilder<FabricClientCommandSource, RequiredArgumentBuilder<FabricClientCommandSource, PosArgument>> addCustomWaypointCommand(boolean relative) {
+ return argument("pos", BlockPosArgumentType.blockPos())
+ .then(argument("secretIndex", IntegerArgumentType.integer())
+ .then(argument("category", SecretWaypoint.Category.CategoryArgumentType.category())
+ .then(argument("name", TextArgumentType.text()).executes(context -> {
+ // TODO Less hacky way with custom ClientBlockPosArgumentType
+ BlockPos pos = context.getArgument("pos", PosArgument.class).toAbsoluteBlockPos(new ServerCommandSource(null, context.getSource().getPosition(), context.getSource().getRotation(), null, 0, null, null, null, null));
+ return relative ? addCustomWaypointRelative(context, pos) : addCustomWaypoint(context, pos);
+ }))
+ )
+ );
+ }
+
+ private static int addCustomWaypoint(CommandContext<FabricClientCommandSource> context, BlockPos pos) {
+ Room room = getRoomAtPhysical(pos);
+ if (isRoomMatched(room)) {
+ room.addCustomWaypoint(context, room.actualToRelative(pos));
+ } else {
+ context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched")));
+ }
+ return Command.SINGLE_SUCCESS;
+ }
+
+ private static int addCustomWaypointRelative(CommandContext<FabricClientCommandSource> context, BlockPos pos) {
+ if (isCurrentRoomMatched()) {
+ currentRoom.addCustomWaypoint(context, pos);
+ } else {
+ context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched")));
+ }
+ return Command.SINGLE_SUCCESS;
+ }
+
+ private static ArgumentBuilder<FabricClientCommandSource, RequiredArgumentBuilder<FabricClientCommandSource, PosArgument>> removeCustomWaypointCommand(boolean relative) {
+ return argument("pos", BlockPosArgumentType.blockPos())
+ .executes(context -> {
+ // TODO Less hacky way with custom ClientBlockPosArgumentType
+ BlockPos pos = context.getArgument("pos", PosArgument.class).toAbsoluteBlockPos(new ServerCommandSource(null, context.getSource().getPosition(), context.getSource().getRotation(), null, 0, null, null, null, null));
+ return relative ? removeCustomWaypointRelative(context, pos) : removeCustomWaypoint(context, pos);
+ });
+ }
+
+ private static int removeCustomWaypoint(CommandContext<FabricClientCommandSource> context, BlockPos pos) {
+ Room room = getRoomAtPhysical(pos);
+ if (isRoomMatched(room)) {
+ room.removeCustomWaypoint(context, room.actualToRelative(pos));
+ } else {
+ context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched")));
+ }
+ return Command.SINGLE_SUCCESS;
+ }
+
+ private static int removeCustomWaypointRelative(CommandContext<FabricClientCommandSource> context, BlockPos pos) {
+ if (isCurrentRoomMatched()) {
+ currentRoom.removeCustomWaypoint(context, pos);
+ } else {
+ context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched")));
+ }
+ return Command.SINGLE_SUCCESS;
+ }
+
/**
* Updates the dungeon. The general idea is similar to the Dungeon Rooms Mod.
* <p></p>
@@ -313,7 +456,7 @@ public class DungeonSecrets {
}
mapEntrancePos = mapEntrancePosAndSize.left();
mapRoomSize = mapEntrancePosAndSize.rightInt();
- LOGGER.info("[Skyblocker] Started dungeon with map room size {}, map entrance pos {}, player pos {}, and physical entrance pos {}", mapRoomSize, mapEntrancePos, client.player.getPos(), physicalEntrancePos);
+ LOGGER.info("[Skyblocker Dungeon Secrets] Started dungeon with map room size {}, map entrance pos {}, player pos {}, and physical entrance pos {}", mapRoomSize, mapEntrancePos, client.player.getPos(), physicalEntrancePos);
}
Vector2ic physicalPos = DungeonMapUtils.getPhysicalRoomPos(client.player.getPos());
@@ -351,7 +494,7 @@ public class DungeonSecrets {
}
return newRoom;
} catch (IllegalArgumentException e) {
- LOGGER.error("[Skyblocker] Failed to create room", e);
+ LOGGER.error("[Skyblocker Dungeon Secrets] Failed to create room", e);
}
return null;
}
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 0d7a444f..ecfcf496 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
@@ -1,14 +1,17 @@
package de.hysky.skyblocker.skyblock.dungeon.secrets;
import com.google.common.collect.HashBasedTable;
-import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Table;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
+import com.mojang.brigadier.arguments.IntegerArgumentType;
+import com.mojang.brigadier.context.CommandContext;
+import de.hysky.skyblocker.utils.Constants;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
import it.unimi.dsi.fastutil.ints.IntSortedSet;
import it.unimi.dsi.fastutil.ints.IntSortedSets;
+import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
import net.fabricmc.fabric.api.util.TriState;
import net.minecraft.block.BlockState;
@@ -21,12 +24,14 @@ import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.mob.AmbientEntity;
import net.minecraft.registry.Registries;
+import net.minecraft.text.Text;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.apache.commons.lang3.tuple.MutableTriple;
import org.apache.commons.lang3.tuple.Triple;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.joml.Vector2i;
import org.joml.Vector2ic;
@@ -154,6 +159,79 @@ public class Room {
}
/**
+ * @see #addCustomWaypoint(int, SecretWaypoint.Category, Text, BlockPos)
+ */
+ protected void addCustomWaypoint(CommandContext<FabricClientCommandSource> context, BlockPos pos) {
+ int secretIndex = IntegerArgumentType.getInteger(context, "secretIndex");
+ SecretWaypoint.Category category = SecretWaypoint.Category.CategoryArgumentType.getCategory(context, "category");
+ Text waypointName = context.getArgument("name", Text.class);
+ addCustomWaypoint(secretIndex, category, waypointName, pos);
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.customWaypointAdded", pos.getX(), pos.getY(), pos.getZ(), name, secretIndex, category, waypointName)));
+ }
+
+ /**
+ * Adds a custom waypoint relative to this room to {@link DungeonSecrets#customWaypoints} and all existing instances of this room.
+ *
+ * @param secretIndex the index of the secret waypoint
+ * @param category the category of the secret waypoint
+ * @param waypointName the name of the secret waypoint
+ * @param pos the position of the secret waypoint relative to this room
+ */
+ @SuppressWarnings("JavadocReference")
+ private void addCustomWaypoint(int secretIndex, SecretWaypoint.Category category, Text waypointName, BlockPos pos) {
+ SecretWaypoint waypoint = new SecretWaypoint(secretIndex, category, waypointName, pos);
+ DungeonSecrets.addCustomWaypoint(name, waypoint);
+ DungeonSecrets.getRoomsStream().filter(r -> name.equals(r.getName())).forEach(r -> r.addCustomWaypoint(waypoint));
+ }
+
+ /**
+ * Adds a custom waypoint relative to this room to this instance of the room.
+ *
+ * @param relativeWaypoint the secret waypoint relative to this room to add
+ */
+ private void addCustomWaypoint(SecretWaypoint relativeWaypoint) {
+ SecretWaypoint actualWaypoint = relativeWaypoint.relativeToActual(this);
+ secretWaypoints.put(actualWaypoint.secretIndex, actualWaypoint.pos, actualWaypoint);
+ }
+
+ /**
+ * @see #removeCustomWaypoint(BlockPos)
+ */
+ protected void removeCustomWaypoint(CommandContext<FabricClientCommandSource> context, BlockPos pos) {
+ SecretWaypoint waypoint = removeCustomWaypoint(pos);
+ if (waypoint != null) {
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.customWaypointRemoved", pos.getX(), pos.getY(), pos.getZ(), name, waypoint.secretIndex, waypoint.category, waypoint.name)));
+ } else {
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.customWaypointNotFound", pos.getX(), pos.getY(), pos.getZ(), name)));
+ }
+ }
+
+ /**
+ * Removes a custom waypoint relative to this room from {@link DungeonSecrets#customWaypoints} and all existing instances of this room.
+ * @param pos the position of the secret waypoint relative to this room
+ * @return the removed secret waypoint or {@code null} if there was no secret waypoint at the given position
+ */
+ @SuppressWarnings("JavadocReference")
+ @Nullable
+ private SecretWaypoint removeCustomWaypoint(BlockPos pos) {
+ SecretWaypoint waypoint = DungeonSecrets.removeCustomWaypoint(name, pos);
+ if (waypoint != null) {
+ DungeonSecrets.getRoomsStream().filter(r -> name.equals(r.getName())).forEach(r -> r.removeCustomWaypoint(waypoint.secretIndex, pos));
+ }
+ return waypoint;
+ }
+
+ /**
+ * Removes a custom waypoint relative to this room from this instance of the room.
+ * @param secretIndex the index of the secret waypoint
+ * @param relativePos the position of the secret waypoint relative to this room
+ */
+ private void removeCustomWaypoint(int secretIndex, BlockPos relativePos) {
+ BlockPos actualPos = relativeToActual(relativePos);
+ secretWaypoints.remove(secretIndex, actualPos);
+ }
+
+ /**
* Updates the room.
* <p></p>
* This method returns immediately if any of the following conditions are met:
@@ -299,15 +377,15 @@ public class Room {
*/
@SuppressWarnings("JavadocReference")
private void roomMatched() {
- Table<Integer, BlockPos, SecretWaypoint> secretWaypointsMutable = HashBasedTable.create();
+ secretWaypoints = HashBasedTable.create();
for (JsonElement waypointElement : DungeonSecrets.getRoomWaypoints(name)) {
JsonObject waypoint = waypointElement.getAsJsonObject();
String secretName = waypoint.get("secretName").getAsString();
int secretIndex = Integer.parseInt(secretName.substring(0, Character.isDigit(secretName.charAt(1)) ? 2 : 1));
BlockPos pos = DungeonMapUtils.relativeToActual(direction, physicalCornerPos, waypoint);
- secretWaypointsMutable.put(secretIndex, pos, new SecretWaypoint(secretIndex, waypoint, secretName, pos));
+ secretWaypoints.put(secretIndex, pos, new SecretWaypoint(secretIndex, waypoint, secretName, pos));
}
- secretWaypoints = ImmutableTable.copyOf(secretWaypointsMutable);
+ DungeonSecrets.getCustomWaypoints(name).values().forEach(this::addCustomWaypoint);
matched = TriState.TRUE;
DungeonSecrets.LOGGER.info("[Skyblocker] Room {} matched after checking {} block(s)", name, checkedBlocks.size());
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 a520c73f..0c2d1b34 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
@@ -1,40 +1,61 @@
package de.hysky.skyblocker.skyblock.dungeon.secrets;
import com.google.gson.JsonObject;
+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.utils.render.RenderHelper;
+import de.hysky.skyblocker.utils.waypoint.Waypoint;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
import net.minecraft.client.MinecraftClient;
+import net.minecraft.command.argument.EnumArgumentType;
import net.minecraft.entity.Entity;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
+import net.minecraft.util.StringIdentifiable;
+import net.minecraft.util.dynamic.Codecs;
import net.minecraft.util.math.BlockPos;
-import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
+import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.function.Predicate;
+import java.util.function.Supplier;
import java.util.function.ToDoubleFunction;
-public class SecretWaypoint {
- private static final float HIGHLIGHT_ALPHA = 0.5f;
- private static final float LINE_WIDTH = 5f;
+public class SecretWaypoint extends Waypoint {
+ public static final Codec<SecretWaypoint> CODEC = RecordCodecBuilder.create(instance -> instance.group(
+ Codec.INT.fieldOf("secretIndex").forGetter(secretWaypoint -> secretWaypoint.secretIndex),
+ Category.CODEC.fieldOf("category").forGetter(secretWaypoint -> secretWaypoint.category),
+ Codecs.TEXT.fieldOf("name").forGetter(secretWaypoint -> secretWaypoint.name),
+ BlockPos.CODEC.fieldOf("pos").forGetter(secretWaypoint -> secretWaypoint.pos)
+ ).apply(instance, SecretWaypoint::new));
+ public static final Codec<List<SecretWaypoint>> LIST_CODEC = CODEC.listOf();
static final List<String> SECRET_ITEMS = List.of("Decoy", "Defuse Kit", "Dungeon Chest Key", "Healing VIII", "Inflatable Jerry", "Spirit Leap", "Training Weights", "Trap", "Treasure Talisman");
+ private static final SkyblockerConfig.SecretWaypoints CONFIG = SkyblockerConfigManager.get().locations.dungeons.secretWaypoints;
+ private static final Supplier<Type> TYPE_SUPPLIER = () -> CONFIG.waypointType;
final int secretIndex;
final Category category;
- private final Text name;
- private final BlockPos pos;
+ final Text name;
private final Vec3d centerPos;
- private boolean missing;
SecretWaypoint(int secretIndex, JsonObject waypoint, String name, BlockPos pos) {
+ this(secretIndex, Category.get(waypoint), name, pos);
+ }
+
+ SecretWaypoint(int secretIndex, Category category, String name, BlockPos pos) {
+ this(secretIndex, category, Text.of(name), pos);
+ }
+
+ SecretWaypoint(int secretIndex, Category category, Text name, BlockPos pos) {
+ super(pos, TYPE_SUPPLIER, category.colorComponents);
this.secretIndex = secretIndex;
- this.category = Category.get(waypoint);
- this.name = Text.of(name);
- this.pos = pos;
+ this.category = category;
+ this.name = name;
this.centerPos = pos.toCenterPos();
- this.missing = true;
}
static ToDoubleFunction<SecretWaypoint> getSquaredDistanceToFunction(Entity entity) {
@@ -45,8 +66,9 @@ public class SecretWaypoint {
return secretWaypoint -> entity.squaredDistanceTo(secretWaypoint.centerPos) <= 36D;
}
- boolean shouldRender() {
- return category.isEnabled() && missing;
+ @Override
+ public boolean shouldRender() {
+ return super.shouldRender() && category.isEnabled();
}
boolean needsInteraction() {
@@ -65,36 +87,15 @@ public class SecretWaypoint {
return category.isBat();
}
- void setFound() {
- this.missing = false;
- }
-
- void setMissing() {
- this.missing = true;
- }
-
/**
* Renders the secret waypoint, including a filled cube, a beacon beam, the name, and the distance from the player.
*/
- void render(WorldRenderContext context) {
- SkyblockerConfig.SecretWaypoints config = SkyblockerConfigManager.get().locations.dungeons.secretWaypoints;
-
- switch (config.waypointType) {
- case WAYPOINT -> RenderHelper.renderFilledThroughWallsWithBeaconBeam(context, pos, category.colorComponents, HIGHLIGHT_ALPHA);
- case OUTLINED_WAYPOINT -> {
- RenderHelper.renderFilledThroughWallsWithBeaconBeam(context, pos, category.colorComponents, HIGHLIGHT_ALPHA);
- RenderHelper.renderOutline(context, new Box(pos), category.colorComponents, LINE_WIDTH, true);
- }
- case HIGHLIGHT -> RenderHelper.renderFilledThroughWalls(context, pos, category.colorComponents, HIGHLIGHT_ALPHA);
- case OUTLINED_HIGHLIGHT -> {
- RenderHelper.renderFilledThroughWalls(context, pos, category.colorComponents, HIGHLIGHT_ALPHA);
- RenderHelper.renderOutline(context, new Box(pos), category.colorComponents, LINE_WIDTH, true);
- }
- //TODO In the future, shrink the box for wither essence and items so its more realistic
- case OUTLINE -> RenderHelper.renderOutline(context, new Box(pos), category.colorComponents, LINE_WIDTH, true);
- }
+ @Override
+ public void render(WorldRenderContext context) {
+ //TODO In the future, shrink the box for wither essence and items so its more realistic
+ super.render(context);
- if (config.showSecretText) {
+ if (CONFIG.showSecretText) {
Vec3d posUp = centerPos.add(0, 1, 0);
RenderHelper.renderText(context, name, posUp, true);
double distance = context.camera().getPos().distanceTo(centerPos);
@@ -102,23 +103,31 @@ public class SecretWaypoint {
}
}
- enum Category {
- ENTRANCE(secretWaypoints -> secretWaypoints.enableEntranceWaypoints, 0, 255, 0),
- SUPERBOOM(secretWaypoints -> secretWaypoints.enableSuperboomWaypoints, 255, 0, 0),
- CHEST(secretWaypoints -> secretWaypoints.enableChestWaypoints, 2, 213, 250),
- ITEM(secretWaypoints -> secretWaypoints.enableItemWaypoints, 2, 64, 250),
- BAT(secretWaypoints -> secretWaypoints.enableBatWaypoints, 142, 66, 0),
- WITHER(secretWaypoints -> secretWaypoints.enableWitherWaypoints, 30, 30, 30),
- LEVER(secretWaypoints -> secretWaypoints.enableLeverWaypoints, 250, 217, 2),
- FAIRYSOUL(secretWaypoints -> secretWaypoints.enableFairySoulWaypoints, 255, 85, 255),
- STONK(secretWaypoints -> secretWaypoints.enableStonkWaypoints, 146, 52, 235),
- AOTV(secretWaypoints -> secretWaypoints.enableAotvWaypoints, 252, 98, 3),
- PEARL(secretWaypoints -> secretWaypoints.enablePearlWaypoints, 57, 117, 125),
- DEFAULT(secretWaypoints -> secretWaypoints.enableDefaultWaypoints, 190, 255, 252);
+ @NotNull
+ SecretWaypoint relativeToActual(Room room) {
+ return new SecretWaypoint(secretIndex, category, name, room.relativeToActual(pos));
+ }
+
+ enum Category implements StringIdentifiable {
+ ENTRANCE("entrance", secretWaypoints -> secretWaypoints.enableEntranceWaypoints, 0, 255, 0),
+ SUPERBOOM("superboom", secretWaypoints -> secretWaypoints.enableSuperboomWaypoints, 255, 0, 0),
+ CHEST("chest", secretWaypoints -> secretWaypoints.enableChestWaypoints, 2, 213, 250),
+ ITEM("item", secretWaypoints -> secretWaypoints.enableItemWaypoints, 2, 64, 250),
+ BAT("bat", secretWaypoints -> secretWaypoints.enableBatWaypoints, 142, 66, 0),
+ WITHER("wither", secretWaypoints -> secretWaypoints.enableWitherWaypoints, 30, 30, 30),
+ LEVER("lever", secretWaypoints -> secretWaypoints.enableLeverWaypoints, 250, 217, 2),
+ FAIRYSOUL("fairysoul", secretWaypoints -> secretWaypoints.enableFairySoulWaypoints, 255, 85, 255),
+ STONK("stonk", secretWaypoints -> secretWaypoints.enableStonkWaypoints, 146, 52, 235),
+ AOTV("aotv", secretWaypoints -> secretWaypoints.enableAotvWaypoints, 252, 98, 3),
+ PEARL("pearl", secretWaypoints -> secretWaypoints.enablePearlWaypoints, 57, 117, 125),
+ DEFAULT("default", secretWaypoints -> secretWaypoints.enableDefaultWaypoints, 190, 255, 252);
+ private static final Codec<Category> CODEC = StringIdentifiable.createCodec(Category::values);
+ private final String name;
private final Predicate<SkyblockerConfig.SecretWaypoints> enabledPredicate;
private final float[] colorComponents;
- Category(Predicate<SkyblockerConfig.SecretWaypoints> enabledPredicate, int... intColorComponents) {
+ Category(String name, Predicate<SkyblockerConfig.SecretWaypoints> enabledPredicate, int... intColorComponents) {
+ this.name = name;
this.enabledPredicate = enabledPredicate;
colorComponents = new float[intColorComponents.length];
for (int i = 0; i < intColorComponents.length; i++) {
@@ -126,21 +135,8 @@ public class SecretWaypoint {
}
}
- private static Category get(JsonObject categoryJson) {
- return switch (categoryJson.get("category").getAsString()) {
- case "entrance" -> Category.ENTRANCE;
- case "superboom" -> Category.SUPERBOOM;
- case "chest" -> Category.CHEST;
- case "item" -> Category.ITEM;
- case "bat" -> Category.BAT;
- case "wither" -> Category.WITHER;
- case "lever" -> Category.LEVER;
- case "fairysoul" -> Category.FAIRYSOUL;
- case "stonk" -> Category.STONK;
- case "aotv" -> Category.AOTV;
- case "pearl" -> Category.PEARL;
- default -> Category.DEFAULT;
- };
+ private static Category get(JsonObject waypointJson) {
+ return CODEC.parse(JsonOps.INSTANCE, waypointJson.get("category")).resultOrPartial(DungeonSecrets.LOGGER::error).orElseThrow();
}
boolean needsInteraction() {
@@ -162,5 +158,29 @@ public class SecretWaypoint {
boolean isEnabled() {
return enabledPredicate.test(SkyblockerConfigManager.get().locations.dungeons.secretWaypoints);
}
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ @Override
+ public String asString() {
+ return name;
+ }
+
+ static class CategoryArgumentType extends EnumArgumentType<Category> {
+ public CategoryArgumentType() {
+ super(Category.CODEC, Category::values);
+ }
+
+ public static CategoryArgumentType category() {
+ return new CategoryArgumentType();
+ }
+
+ public static <S> Category getCategory(CommandContext<S> context, String name) {
+ return context.getArgument(name, Category.class);
+ }
+ }
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/BackpackPreview.java b/src/main/java/de/hysky/skyblocker/skyblock/item/BackpackPreview.java
index d621d388..8782440c 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/BackpackPreview.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/BackpackPreview.java
@@ -1,10 +1,10 @@
package de.hysky.skyblocker.skyblock.item;
import com.mojang.blaze3d.systems.RenderSystem;
+import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.utils.Utils;
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
-import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
@@ -60,7 +60,7 @@ public class BackpackPreview {
// update save dir based on sb profile id
String id = MinecraftClient.getInstance().getSession().getUuidOrNull().toString().replaceAll("-", "") + "/" + Utils.getProfileId();
if (!id.equals(loaded)) {
- saveDir = FabricLoader.getInstance().getConfigDir().resolve("skyblocker/backpack-preview/" + id);
+ saveDir = SkyblockerMod.CONFIG_DIR.resolve("backpack-preview/" + id);
//noinspection ResultOfMethodCallIgnored
saveDir.toFile().mkdirs();
// load storage again because profile id changed
diff --git a/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java b/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java
new file mode 100644
index 00000000..e7858f05
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java
@@ -0,0 +1,99 @@
+package de.hysky.skyblocker.utils.waypoint;
+
+import de.hysky.skyblocker.utils.render.RenderHelper;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Box;
+
+import java.util.function.Supplier;
+
+public class Waypoint {
+ protected static final float DEFAULT_HIGHLIGHT_ALPHA = 0.5f;
+ protected static final float DEFAULT_LINE_WIDTH = 5f;
+ public final BlockPos pos;
+ private final Box box;
+ private final Supplier<Type> typeSupplier;
+ private final float[] colorComponents;
+ private final float alpha;
+ private final float lineWidth;
+ private final boolean throughWalls;
+ private boolean shouldRender;
+
+ protected Waypoint(BlockPos pos, Supplier<Type> typeSupplier, float[] colorComponents) {
+ this(pos, typeSupplier, colorComponents, DEFAULT_HIGHLIGHT_ALPHA);
+ }
+
+ protected Waypoint(BlockPos pos, Type type, float[] colorComponents, float alpha) {
+ this(pos, () -> type, colorComponents, alpha);
+ }
+
+ protected Waypoint(BlockPos pos, Supplier<Type> typeSupplier, float[] colorComponents, float alpha) {
+ this(pos, typeSupplier, colorComponents, alpha, DEFAULT_LINE_WIDTH);
+ }
+
+ protected Waypoint(BlockPos pos, Supplier<Type> typeSupplier, float[] colorComponents, float alpha, float lineWidth) {
+ this(pos, typeSupplier, colorComponents, alpha, lineWidth, true);
+ }
+
+ protected Waypoint(BlockPos pos, Supplier<Type> typeSupplier, float[] colorComponents, float alpha, float lineWidth, boolean throughWalls) {
+ this(pos, typeSupplier, colorComponents, alpha, lineWidth, throughWalls, true);
+ }
+
+ protected Waypoint(BlockPos pos, Supplier<Type> typeSupplier, float[] colorComponents, float alpha, float lineWidth, boolean throughWalls, boolean shouldRender) {
+ this.pos = pos;
+ this.box = new Box(pos);
+ this.typeSupplier = typeSupplier;
+ this.colorComponents = colorComponents;
+ this.alpha = alpha;
+ this.lineWidth = lineWidth;
+ this.throughWalls = throughWalls;
+ this.shouldRender = shouldRender;
+ }
+
+ public boolean shouldRender() {
+ return shouldRender;
+ }
+
+ public void setFound() {
+ this.shouldRender = false;
+ }
+
+ public void setMissing() {
+ this.shouldRender = true;
+ }
+
+ public void render(WorldRenderContext context) {
+ switch (typeSupplier.get()) {
+ case WAYPOINT -> RenderHelper.renderFilledThroughWallsWithBeaconBeam(context, pos, colorComponents, alpha);
+ case OUTLINED_WAYPOINT -> {
+ RenderHelper.renderFilledThroughWallsWithBeaconBeam(context, pos, colorComponents, alpha);
+ RenderHelper.renderOutline(context, box, colorComponents, lineWidth, throughWalls);
+ }
+ case HIGHLIGHT -> RenderHelper.renderFilledThroughWalls(context, pos, colorComponents, alpha);
+ case OUTLINED_HIGHLIGHT -> {
+ RenderHelper.renderFilledThroughWalls(context, pos, colorComponents, alpha);
+ RenderHelper.renderOutline(context, box, colorComponents, lineWidth, throughWalls);
+ }
+ case OUTLINE -> RenderHelper.renderOutline(context, box, colorComponents, lineWidth, throughWalls);
+ }
+ }
+
+ public enum Type {
+ WAYPOINT,
+ OUTLINED_WAYPOINT,
+ HIGHLIGHT,
+ OUTLINED_HIGHLIGHT,
+ OUTLINE;
+
+ @Override
+ public String toString() {
+ return switch (this) {
+ case WAYPOINT -> "Waypoint";
+ case OUTLINED_WAYPOINT -> "Outlined Waypoint";
+ case HIGHLIGHT -> "Highlight";
+ case OUTLINED_HIGHLIGHT -> "Outlined Highlight";
+ case OUTLINE -> "Outline";
+ };
+ }
+ }
+}
diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json
index 1d000825..8665ce45 100644
--- a/src/main/resources/assets/skyblocker/lang/en_us.json
+++ b/src/main/resources/assets/skyblocker/lang/en_us.json
@@ -275,6 +275,9 @@
"skyblocker.dungeons.secrets.posMessage": "§rRoom: %s, X: %d, Y: %d, Z: %d",
"skyblocker.dungeons.secrets.noTarget": "§cNo target block found! (Are you pointing at a block in range?)",
"skyblocker.dungeons.secrets.notMatched": "§cThe current room is not matched! (Are you in a dungeon room?)",
+ "skyblocker.dungeons.secrets.customWaypointAdded": "§rAdded a custom waypoint at X: %d, Y: %d, Z: %d for room %s secret #%d of category %s with name '%s'.",
+ "skyblocker.dungeons.secrets.customWaypointRemoved": "§rRemoved custom waypoint at X: %d, Y: %d, Z: %d for room %s secret #%d of category %s with name '%s'.",
+ "skyblocker.dungeons.secrets.customWaypointNotFound": "§cNo custom waypoint found at X: %d, Y: %d, Z: %d for room %s.",
"skyblocker.fishing.reelNow": "Reel in now!",
"skyblocker.rift.healNow": "Heal now!",