diff options
Diffstat (limited to 'src/main/java/de')
8 files changed, 454 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"; + }; + } + } +} |