aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java26
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java22
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java25
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java47
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java63
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java12
-rw-r--r--src/main/resources/assets/skyblocker/lang/en_us.json6
7 files changed, 185 insertions, 16 deletions
diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
index 3c8e6739..633ea670 100644
--- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
@@ -588,6 +588,9 @@ public class SkyblockerConfig {
public SecretWaypoints secretWaypoints = new SecretWaypoints();
@SerialEntry
+ public DoorHighlight doorHighlight = new DoorHighlight();
+
+ @SerialEntry
public DungeonChestProfit dungeonChestProfit = new DungeonChestProfit();
@SerialEntry
@@ -683,6 +686,29 @@ public class SkyblockerConfig {
public boolean enableDefaultWaypoints = true;
}
+ public static class DoorHighlight {
+ @SerialEntry
+ public boolean enableDoorHighlight = true;
+
+ @SerialEntry
+ public Type doorHighlightType = Type.OUTLINED_HIGHLIGHT;
+
+ public enum Type {
+ HIGHLIGHT,
+ OUTLINED_HIGHLIGHT,
+ OUTLINE;
+
+ @Override
+ public String toString() {
+ return switch (this) {
+ case HIGHLIGHT -> "Highlight";
+ case OUTLINED_HIGHLIGHT -> "Outlined Highlight";
+ case OUTLINE -> "Outline";
+ };
+ }
+ }
+ }
+
public static class DungeonChestProfit {
@SerialEntry
public boolean enableProfitCalculator = true;
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 37f268b0..2f738ff2 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
@@ -147,6 +147,28 @@ public class DungeonsCategory {
.build())
.build())
+ //Dungeon Door Highlight
+ .group(OptionGroup.createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.doorHighlight"))
+ .collapsed(true)
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.doorHighlight.enableDoorHighlight"))
+ .binding(defaults.locations.dungeons.doorHighlight.enableDoorHighlight,
+ () -> config.locations.dungeons.doorHighlight.enableDoorHighlight,
+ newValue -> config.locations.dungeons.doorHighlight.enableDoorHighlight = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<SkyblockerConfig.DoorHighlight.Type>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.doorHighlight.doorHighlightType"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.doorHighlight.doorHighlightType.@Tooltip"), Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.doorHighlight.doorHighlightType.secretWaypointsNote")))
+ .binding(defaults.locations.dungeons.doorHighlight.doorHighlightType,
+ () -> config.locations.dungeons.doorHighlight.doorHighlightType,
+ newValue -> config.locations.dungeons.doorHighlight.doorHighlightType = newValue)
+ .controller(ConfigUtils::createEnumCyclingListController)
+ .build())
+ .build())
+
+ //Dungeon Chest Profit
.group(OptionGroup.createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit"))
.collapsed(true)
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java
index 73d4a452..01f2c9fc 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java
@@ -3,12 +3,15 @@ package de.hysky.skyblocker.skyblock.dungeon.secrets;
import com.google.gson.JsonObject;
import it.unimi.dsi.fastutil.ints.IntSortedSet;
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.Blocks;
import net.minecraft.block.MapColor;
import net.minecraft.item.map.MapIcon;
import net.minecraft.item.map.MapState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.Vec3i;
+import net.minecraft.world.World;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.RoundingMode;
@@ -271,4 +274,26 @@ public class DungeonMapUtils {
DungeonSecrets.LOGGER.debug("[Skyblocker] Found dungeon room segments: {}", Arrays.toString(segments.toArray()));
return segments.toArray(Vector2ic[]::new);
}
+
+ public static BlockPos getWitherBloodDoorPos(World world, Collection<Vector2ic> physicalPositions) {
+ BlockPos.Mutable doorPos = new BlockPos.Mutable();
+ for (Vector2ic pos : physicalPositions) {
+ if (hasWitherOrBloodDoor(world, pos, doorPos)) {
+ return doorPos;
+ }
+ }
+ return null;
+ }
+
+ private static boolean hasWitherOrBloodDoor(World world, Vector2ic pos, BlockPos.Mutable doorPos) {
+ return isWitherOrBloodDoor(world, doorPos.set(pos.x() - 2, 69, pos.y() + 14)) ||
+ isWitherOrBloodDoor(world, doorPos.set(pos.x() + 14, 69, pos.y() - 2)) ||
+ isWitherOrBloodDoor(world, doorPos.set(pos.x() + 14, 69, pos.y() + 30)) ||
+ isWitherOrBloodDoor(world, doorPos.set(pos.x() + 30, 69, pos.y() + 14));
+ }
+
+ private static boolean isWitherOrBloodDoor(World world, BlockPos pos) {
+ BlockState state = world.getBlockState(pos);
+ return state.isOf(Blocks.COAL_BLOCK) || state.isOf(Blocks.RED_TERRACOTTA);
+ }
}
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 ee517eb8..7f401fdb 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
@@ -32,6 +32,7 @@ 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.Entity;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.mob.AmbientEntity;
@@ -66,6 +67,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.zip.InflaterInputStream;
@@ -76,6 +79,7 @@ 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");
+ private static final Pattern KEY_FOUND = Pattern.compile("^(?:\\[.+] )?(?<name>\\w+) has obtained (?<type>Wither|Blood) Key!$");
/**
* Maps the block identifier string to a custom numeric block id used in dungeon rooms data.
*
@@ -500,25 +504,60 @@ public class DungeonSecrets {
}
/**
- * Renders the secret waypoints in {@link #currentRoom} if {@link #isCurrentRoomMatched()}.
+ * Renders the secret waypoints in {@link #currentRoom} if {@link #shouldProcess()} and {@link #currentRoom} is not null.
*/
private static void render(WorldRenderContext context) {
- if (isCurrentRoomMatched()) {
+ if (shouldProcess() && currentRoom != null) {
currentRoom.render(context);
}
}
/**
- * Calls {@link Room#onChatMessage(String)} on {@link #currentRoom} if the message is an overlay message and {@link #isCurrentRoomMatched()}.
- * Used to detect when all secrets in a room are found.
+ * Calls {@link Room#onChatMessage(String)} on {@link #currentRoom} if the message is an overlay message and {@link #isCurrentRoomMatched()} and processes key obtained messages.
+ * <p>Used to detect when all secrets in a room are found and detect when a wither or blood door is unlocked.
+ * To process key obtained messages, this method checks if door highlight is enabled and if the message matches a key obtained message.
+ * Then, it calls {@link Room#keyFound()} on {@link #currentRoom} if the client's player is the one who obtained the key.
+ * Otherwise, it calls {@link Room#keyFound()} on the room the player who obtained the key is in.
*/
private static void onChatMessage(Text text, boolean overlay) {
+ if (!shouldProcess()) {
+ return;
+ }
+
String message = text.getString();
if (overlay && isCurrentRoomMatched()) {
currentRoom.onChatMessage(message);
}
+ // Process key found messages for door highlight
+ if (SkyblockerConfigManager.get().locations.dungeons.doorHighlight.enableDoorHighlight) {
+ Matcher matcher = KEY_FOUND.matcher(message);
+ if (matcher.matches()) {
+ String name = matcher.group("name");
+ MinecraftClient client = MinecraftClient.getInstance();
+ if (client.player != null && client.player.getGameProfile().getName().equals(name)) {
+ if (currentRoom != null) {
+ currentRoom.keyFound();
+ } else {
+ LOGGER.warn("[Skyblocker Dungeon Door] The current room at the current player {} does not exist", name);
+ }
+ } else if (client.world != null) {
+ Optional<Vec3d> posOptional = client.world.getPlayers().stream().filter(player -> player.getGameProfile().getName().equals(name)).findAny().map(Entity::getPos);
+ if (posOptional.isPresent()) {
+ Room room = getRoomAtPhysical(posOptional.get());
+ if (room != null) {
+ room.keyFound();
+ } else {
+ LOGGER.warn("[Skyblocker Dungeon Door] Failed to find room at player {} with position {}", name, posOptional.get());
+ }
+ } else {
+ LOGGER.warn("[Skyblocker Dungeon Door] Failed to find player {}", name);
+ }
+ }
+ }
+ }
+
if (message.equals("[BOSS] Bonzo: Gratz for making it this far, but I'm basically unbeatable.") || message.equals("[BOSS] Scarf: This is where the journey ends for you, Adventurers.")
|| message.equals("[BOSS] The Professor: I was burdened with terrible news recently...") || message.equals("[BOSS] Thorn: Welcome Adventurers! I am Thorn, the Spirit! And host of the Vegan Trials!")
|| message.equals("[BOSS] Livid: Welcome, you've arrived right on time. I am Livid, the Master of Shadows.") || message.equals("[BOSS] Sadan: So you made it all the way here... Now you wish to defy me? Sadan?!")
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 9b95f146..7797513f 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
@@ -6,7 +6,9 @@ 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.config.SkyblockerConfigManager;
import de.hysky.skyblocker.utils.Constants;
+import de.hysky.skyblocker.utils.render.RenderHelper;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
import it.unimi.dsi.fastutil.ints.IntSortedSet;
@@ -27,6 +29,8 @@ 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.util.math.Box;
+import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import org.apache.commons.lang3.tuple.MutableTriple;
import org.apache.commons.lang3.tuple.Triple;
@@ -43,10 +47,14 @@ import java.util.regex.Pattern;
public class Room {
private static final Pattern SECRET_INDEX = Pattern.compile("^(\\d+)");
private static final Pattern SECRETS = Pattern.compile("ยง7(\\d{1,2})/(\\d{1,2}) Secrets");
+ private static final Vec3d DOOR_SIZE = new Vec3d(3, 4, 3);
+ private static final float[] RED_COLOR_COMPONENTS = {1, 0, 0};
+ private static final float[] GREEN_COLOR_COMPONENTS = {0, 1, 0};
@NotNull
private final Type type;
@NotNull
private final Set<Vector2ic> segments;
+
/**
* The shape of the room. See {@link #getShape(IntSortedSet, IntSortedSet)}.
*/
@@ -82,6 +90,12 @@ public class Room {
private Direction direction;
private Vector2ic physicalCornerPos;
+ @Nullable
+ private BlockPos doorPos;
+ @Nullable
+ private Box doorBox;
+ private boolean keyFound;
+
public Room(@NotNull Type type, @NotNull Vector2ic... physicalPositions) {
this.type = type;
segments = Set.of(physicalPositions);
@@ -238,7 +252,8 @@ public class Room {
/**
* Updates the room.
* <p></p>
- * This method returns immediately if any of the following conditions are met:
+ * First, this method tries to find a wither door and blood door.
+ * Then, this method returns immediately if any of the following conditions are met:
* <ul>
* <li> The room does not need to be scanned and matched. (When the room is not of type {@link Type.ROOM}, {@link Type.PUZZLE}, or {@link Type.TRAP}. See {@link Type#needsScanning()}) </li>
* <li> The room has been matched or failed to match and is on cooldown. See {@link #matchState}. </li>
@@ -254,14 +269,27 @@ public class Room {
*/
@SuppressWarnings("JavadocReference")
protected void update() {
+ MinecraftClient client = MinecraftClient.getInstance();
+ ClientWorld world = client.world;
+ if (world == null) {
+ return;
+ }
+
+ // Wither and blood door
+ if (SkyblockerConfigManager.get().locations.dungeons.doorHighlight.enableDoorHighlight && doorPos == null) {
+ doorPos = DungeonMapUtils.getWitherBloodDoorPos(world, segments);
+ if (doorPos != null) {
+ doorBox = new Box(doorPos.getX(), doorPos.getY(), doorPos.getZ(), doorPos.getX() + DOOR_SIZE.getX(), doorPos.getY() + DOOR_SIZE.getY(), doorPos.getZ() + DOOR_SIZE.getZ());
+ }
+ }
+
+ // Room scanning and matching
// Logical AND has higher precedence than logical OR
if (!type.needsScanning() || matchState != MatchState.MATCHING && matchState != MatchState.DOUBLE_CHECKING || !DungeonSecrets.isRoomsLoaded() || findRoom != null && !findRoom.isDone()) {
return;
}
- MinecraftClient client = MinecraftClient.getInstance();
ClientPlayerEntity player = client.player;
- ClientWorld world = client.world;
- if (player == null || world == null) {
+ if (player == null) {
return;
}
findRoom = CompletableFuture.runAsync(() -> {
@@ -451,14 +479,29 @@ public class Room {
}
/**
- * Calls {@link SecretWaypoint#render(WorldRenderContext)} on {@link #secretWaypoints all secret waypoints}.
+ * Calls {@link SecretWaypoint#render(WorldRenderContext)} on {@link #secretWaypoints all secret waypoints} and renders a highlight around the wither or blood door, if it exists.
*/
protected void render(WorldRenderContext context) {
- for (SecretWaypoint secretWaypoint : secretWaypoints.values()) {
- if (secretWaypoint.shouldRender()) {
- secretWaypoint.render(context);
+ if (isMatched()) {
+ for (SecretWaypoint secretWaypoint : secretWaypoints.values()) {
+ if (secretWaypoint.shouldRender()) {
+ secretWaypoint.render(context);
+ }
}
}
+
+ if (!SkyblockerConfigManager.get().locations.dungeons.doorHighlight.enableDoorHighlight || doorPos == null) {
+ return;
+ }
+ float[] colorComponents = keyFound ? GREEN_COLOR_COMPONENTS : RED_COLOR_COMPONENTS;
+ switch (SkyblockerConfigManager.get().locations.dungeons.doorHighlight.doorHighlightType) {
+ case HIGHLIGHT -> RenderHelper.renderFilled(context, doorPos, DOOR_SIZE, colorComponents, 0.5f, true);
+ case OUTLINED_HIGHLIGHT -> {
+ RenderHelper.renderFilled(context, doorPos, DOOR_SIZE, colorComponents, 0.5f, true);
+ RenderHelper.renderOutline(context, doorBox, colorComponents, 5, true);
+ }
+ case OUTLINE -> RenderHelper.renderOutline(context, doorBox, colorComponents, 5, true);
+ }
}
/**
@@ -550,6 +593,10 @@ public class Room {
}
}
+ protected void keyFound() {
+ keyFound = true;
+ }
+
public enum Type {
ENTRANCE(MapColor.DARK_GREEN.getRenderColorByte(MapColor.Brightness.HIGH)),
ROOM(MapColor.ORANGE.getRenderColorByte(MapColor.Brightness.LOWEST)),
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
index 43d595a5..9ffd3a43 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
@@ -52,13 +52,17 @@ public class RenderHelper {
}
public static void renderFilled(WorldRenderContext context, BlockPos pos, float[] colorComponents, float alpha, boolean throughWalls) {
+ renderFilled(context, Vec3d.of(pos), ONE, colorComponents, alpha, throughWalls);
+ }
+
+ public static void renderFilled(WorldRenderContext context, BlockPos pos, Vec3d dimensions, float[] colorComponents, float alpha, boolean throughWalls) {
if (throughWalls) {
- if (FrustumUtils.isVisible(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1)) {
- renderFilled(context, Vec3d.of(pos), ONE, colorComponents, alpha, true);
+ if (FrustumUtils.isVisible(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + dimensions.x, pos.getY() + dimensions.y, pos.getZ() + dimensions.z)) {
+ renderFilled(context, Vec3d.of(pos), dimensions, colorComponents, alpha, true);
}
} else {
- if (OcclusionCulling.isVisible(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1)) {
- renderFilled(context, Vec3d.of(pos), ONE, colorComponents, alpha, false);
+ if (OcclusionCulling.isVisible(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + dimensions.x, pos.getY() + dimensions.y, pos.getZ() + dimensions.z)) {
+ renderFilled(context, Vec3d.of(pos), dimensions, colorComponents, alpha, false);
}
}
}
diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json
index 4679fa11..1a527326 100644
--- a/src/main/resources/assets/skyblocker/lang/en_us.json
+++ b/src/main/resources/assets/skyblocker/lang/en_us.json
@@ -183,6 +183,12 @@
"text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enablePearlWaypoints.@Tooltip": "With these waypoints, you throw a pearl towards the block and at the same time AOTV up.",
"text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enableDefaultWaypoints" : "Enable Default Waypoints",
"text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enableDefaultWaypoints.@Tooltip" : "This includes all waypoints that do not belong to a category.",
+ "text.autoconfig.skyblocker.option.locations.dungeons.doorHighlight": "Door Highlight",
+ "text.autoconfig.skyblocker.option.locations.dungeons.doorHighlight.enableDoorHighlight": "Enable Door Highlight",
+ "text.autoconfig.skyblocker.option.locations.dungeons.doorHighlight.enableDoorHighlight.@Tooltip": "Highlights wither and blood doors red if locked and green if unlocked.",
+ "text.autoconfig.skyblocker.option.locations.dungeons.doorHighlight.doorHighlightType": "Door Highlight Type",
+ "text.autoconfig.skyblocker.option.locations.dungeons.doorHighlight.doorHighlightType.@Tooltip": "Highlight: Only displays a highlight.\n\nOutlined Highlight: Displays both a highlight and an outline.\n\nOutline: Only displays an outline.",
+ "text.autoconfig.skyblocker.option.locations.dungeons.doorHighlight.doorHighlightType.secretWaypointsNote": "\n\n\nNote: Dungeon Secret Waypoints must be enabled for this to work.",
"text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit": "Dungeon Chest Profit Calculator",
"text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.enableProfitCalculator": "Enable Profit Calculator",
"text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.enableProfitCalculator.@Tooltip": "Displays the profit of a dungeon chest in the chest screen's title.\nGreen if there's profit.\nRed if there isn't profit.\nGray if you don't gain or lose anything.\nBlue if calculations were based on incomplete data.",