Skip to content

Commit

Permalink
Add Wither & Blood Door Highlight (#429)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinthegreat1 authored Nov 24, 2023
1 parent ea8f0b1 commit 6eb9559
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 16 deletions.
26 changes: 26 additions & 0 deletions src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,9 @@ public static class Dungeons {
@SerialEntry
public SecretWaypoints secretWaypoints = new SecretWaypoints();

@SerialEntry
public DoorHighlight doorHighlight = new DoorHighlight();

@SerialEntry
public DungeonChestProfit dungeonChestProfit = new DungeonChestProfit();

Expand Down Expand Up @@ -683,6 +686,29 @@ public static class SecretWaypoints {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,28 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig
.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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
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;
Expand Down Expand Up @@ -271,4 +274,26 @@ public static Vector2ic[] getRoomSegments(MapState map, Vector2ic mapPos, int ma
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
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;
Expand Down Expand Up @@ -66,6 +67,8 @@
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;

Expand All @@ -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.
*
Expand Down Expand Up @@ -500,25 +504,60 @@ private static Room newRoom(Room.Type type, Vector2ic... physicalPositions) {
}

/**
* 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?!")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
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;
Expand All @@ -27,6 +29,8 @@
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;
Expand All @@ -43,10 +47,14 @@
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)}.
*/
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -238,7 +252,8 @@ private void removeCustomWaypoint(int secretIndex, BlockPos relativePos) {
/**
* 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>
Expand All @@ -254,14 +269,27 @@ private void removeCustomWaypoint(int secretIndex, BlockPos relativePos) {
*/
@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(() -> {
Expand Down Expand Up @@ -451,14 +479,29 @@ protected BlockPos relativeToActual(BlockPos pos) {
}

/**
* 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);
}
}

/**
Expand Down Expand Up @@ -550,6 +593,10 @@ protected boolean markSecrets(int secretIndex, boolean found) {
}
}

protected void keyFound() {
keyFound = true;
}

public enum Type {
ENTRANCE(MapColor.DARK_GREEN.getRenderColorByte(MapColor.Brightness.HIGH)),
ROOM(MapColor.ORANGE.getRenderColorByte(MapColor.Brightness.LOWEST)),
Expand Down
12 changes: 8 additions & 4 deletions src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,17 @@ public static void renderFilledWithBeaconBeam(WorldRenderContext context, BlockP
}

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);
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/assets/skyblocker/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down

0 comments on commit 6eb9559

Please sign in to comment.