diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientPacketListenerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientPacketListenerMixin.java index 1797ff7ede3..34ac7dbf03f 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ClientPacketListenerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientPacketListenerMixin.java @@ -12,6 +12,7 @@ import de.hysky.skyblocker.events.PlaySoundEvents; import de.hysky.skyblocker.skyblock.CompactDamage; import de.hysky.skyblocker.skyblock.HealthBars; +import de.hysky.skyblocker.skyblock.dungeon.terminal.GoldorWaypointsManager; import de.hysky.skyblocker.skyblock.teleport.PredictiveSmoothAOTE; import de.hysky.skyblocker.skyblock.dungeon.DungeonMapTexture; import de.hysky.skyblocker.skyblock.dungeon.DungeonScore; @@ -89,6 +90,7 @@ protected ClientPacketListenerMixin(Minecraft client, Connection connection, Com FishingHelper.checkIfFishWasCaught(armorStandEntity); TreeBreakProgressHud.onEntityUpdate(armorStandEntity); LassoHud.onEntityUpdate(armorStandEntity); + GoldorWaypointsManager.checkForTerminalNameTags(armorStandEntity); try { //Prevent packet handling fails if something goes wrong so that entity trackers still update, just without compact damage numbers CompactDamage.compactDamage(armorStandEntity); } catch (Exception e) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/GoldorWaypointsManager.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/terminal/GoldorWaypointsManager.java similarity index 75% rename from src/main/java/de/hysky/skyblocker/skyblock/dungeon/GoldorWaypointsManager.java rename to src/main/java/de/hysky/skyblocker/skyblock/dungeon/terminal/GoldorWaypointsManager.java index 0855fae1c81..838947fb85b 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/GoldorWaypointsManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/terminal/GoldorWaypointsManager.java @@ -1,4 +1,4 @@ -package de.hysky.skyblocker.skyblock.dungeon; +package de.hysky.skyblocker.skyblock.dungeon.terminal; import com.google.gson.JsonArray; import com.google.gson.JsonParser; @@ -25,6 +25,7 @@ import net.minecraft.resources.Identifier; import net.minecraft.util.StringRepresentable; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.phys.Vec3; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,6 +48,7 @@ public class GoldorWaypointsManager { private static final ObjectArrayList TERMINALS = new ObjectArrayList<>(); private static final ObjectArrayList DEVICES = new ObjectArrayList<>(); private static final ObjectArrayList LEVERS = new ObjectArrayList<>(); + private static final ObjectArrayList ALL_WAYPOINTS = new ObjectArrayList<>(); private static final ObjectArrayList ACTIVE_PHASE_WAYPOINTS = new ObjectArrayList<>(); @@ -55,6 +57,8 @@ public class GoldorWaypointsManager { private static final Pattern DEVICE_ACTIVATED = Pattern.compile("^(?\\w+) completed a device! \\(\\d/\\d\\)$"); private static final Pattern LEVER_ACTIVATED = Pattern.compile("^(?\\w+) activated a lever! \\(\\d/\\d\\)$"); private static final Pattern PHASE_COMPLETE = Pattern.compile("^(?\\w+) (?:activated a (?:terminal|lever)|completed a device)! (?:\\(7/7\\)|\\(8/8\\))$"); + private static final Pattern NAMETAG_INCOMPLETE = Pattern.compile("Inactive|Not Activated|Inactive Terminal"); + private static final Pattern NAMETAG_COMPLETE = Pattern.compile("Active|Activated|Terminal Active"); private static final String GATE_DESTROYED = "The gate has been destroyed!"; private static final String CORE_ENTRANCE = "The Core entrance is opening!"; private static final Codec> CODEC = GoldorWaypoint.CODEC.listOf(); @@ -79,7 +83,12 @@ public static void init() { private static void load(Minecraft client) { CompletableFuture terminals = loadWaypoints(client, SkyblockerMod.id("dungeons/goldorwaypoints.json")); - terminals.whenComplete((_result, _throwable) -> loaded = true); + terminals.whenComplete((_result, _throwable) -> { + loaded = true; + ALL_WAYPOINTS.addAll(TERMINALS); + ALL_WAYPOINTS.addAll(DEVICES); + ALL_WAYPOINTS.addAll(LEVERS); + }); } private static CompletableFuture loadWaypoints(Minecraft client, Identifier file) { @@ -113,20 +122,29 @@ private static boolean shouldProcess() { } /** - * Given a list of waypoints to operate on and a player name, hides the visible waypoint that is closest to the player + * Util for getting a player's position + */ + private static Optional getPlayerPos(String playerName) { + Minecraft client = Minecraft.getInstance(); + if (client.level == null) return Optional.empty(); + + return client.level.players().stream().filter(player -> player.getGameProfile().name().equals(playerName)).findAny().map(Entity::position); + } + + /** + * Given a list of waypoints to operate on and a position, enables/disables the waypoint that is closest to said pos. * * @param waypoints The list of waypoints to operate on - * @param playerName The name of the player to check against + * @param position The position to query from + * @param completed If the terminal should be marked as completed or missing (for corrections) */ - private static void removeNearestWaypoint(List waypoints, String playerName) { + private static void markClosestWaypoint(List waypoints, Vec3 position, boolean completed) { Minecraft client = Minecraft.getInstance(); if (client.level == null) return; - // Get the position of the player with the given name - Optional posOptional = client.level.players().stream().filter(player -> player.getGameProfile().name().equals(playerName)).findAny().map(Entity::position); + Optional closest = waypoints.stream().min(Comparator.comparingDouble(waypoint -> waypoint.centerPos.distanceToSqr(position))); - // Find the nearest waypoint to the player and hide it - posOptional.flatMap(pos -> waypoints.stream().filter(GoldorWaypoint::shouldRender).min(Comparator.comparingDouble(waypoint -> waypoint.centerPos.distanceToSqr(pos)))).ifPresent(Waypoint::setFound); + closest.ifPresent(completed ? Waypoint::setFound : Waypoint::setMissing); TerminalHud.INSTANCE.update(); } @@ -178,11 +196,14 @@ private static boolean onChatMessage(Component text, boolean overlay) { String playerName; if ((playerName = getPlayerName(TERMINAL_ACTIVATED.matcher(message))) != null) { - removeNearestWaypoint(TERMINALS, playerName); + Optional playerPos = getPlayerPos(playerName); + playerPos.ifPresent(pos -> markClosestWaypoint(TERMINALS, pos, true)); } else if ((playerName = getPlayerName(DEVICE_ACTIVATED.matcher(message))) != null) { - removeNearestWaypoint(DEVICES, playerName); + Optional playerPos = getPlayerPos(playerName); + playerPos.ifPresent(pos -> markClosestWaypoint(DEVICES, pos, true)); } else if ((playerName = getPlayerName(LEVER_ACTIVATED.matcher(message))) != null) { - removeNearestWaypoint(LEVERS, playerName); + Optional playerPos = getPlayerPos(playerName); + playerPos.ifPresent(pos -> markClosestWaypoint(LEVERS, pos, true)); } else if (message.equals(CORE_ENTRANCE)) { active = false; ACTIVE_PHASE_WAYPOINTS.clear(); @@ -238,6 +259,45 @@ public static List getPhaseWaypoints() { return ACTIVE_PHASE_WAYPOINTS; } + + /** + * Corrects the waypoint states. + * Each terminal/device has an associated display (Armor Stand), which shows when the player is close-ish + * It is best to use player position-based detection, but correct eventual mistakes with this. + * (player bounced in lava, landing closer to another term upon completion, or player is out of render distance doing something like i4) + *

+ * As of this being added, here are the names matched : + * # Devices (uses two displays, but only one of them is relevant + * Inactive + * Active + * # Levers + * Not Activated + * Activated + * # Terminals + * Inactive Terminal + * Terminal Active + */ + public static void checkForTerminalNameTags(ArmorStand armorStandEntity) { + if (!(DungeonManager.isInBoss() && DungeonManager.getBoss().isFloor(7))) { + return; + } + + Component nameComponent = armorStandEntity.getCustomName(); + if (nameComponent != null) { + String name = nameComponent.getString(); + + if (NAMETAG_INCOMPLETE.matcher(name).matches()) { + Vec3 pos = armorStandEntity.getEyePosition(); + + markClosestWaypoint(ALL_WAYPOINTS, pos, false); + } else if (NAMETAG_COMPLETE.matcher(name).matches()) { + Vec3 pos = armorStandEntity.getEyePosition(); + + markClosestWaypoint(ALL_WAYPOINTS, pos, true); + } + } + } + public static class GoldorWaypoint extends NamedWaypoint { public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( WaypointTargetKind.CODEC.fieldOf("kind").forGetter(w -> w.kind), diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/TerminalHud.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/terminal/TerminalHud.java similarity index 99% rename from src/main/java/de/hysky/skyblocker/skyblock/dungeon/TerminalHud.java rename to src/main/java/de/hysky/skyblocker/skyblock/dungeon/terminal/TerminalHud.java index 698d8abef88..b885a3dc6ff 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/TerminalHud.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/terminal/TerminalHud.java @@ -1,4 +1,4 @@ -package de.hysky.skyblocker.skyblock.dungeon; +package de.hysky.skyblocker.skyblock.dungeon.terminal; import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.config.SkyblockerConfigManager;