Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import de.hysky.skyblocker.annotations.Init;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.events.SkyblockEvents;
import de.hysky.skyblocker.skyblock.dwarven.MiningLocationLabel.CrystalHollowsLocationsCategory;
import de.hysky.skyblocker.skyblock.entity.MobGlow;
import de.hysky.skyblocker.utils.Constants;
import de.hysky.skyblocker.utils.Location;
import de.hysky.skyblocker.utils.Utils;
Expand All @@ -26,14 +28,23 @@
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.fabric.api.event.player.AttackEntityCallback;
import net.fabricmc.fabric.api.event.player.UseEntityCallback;
import net.minecraft.client.MinecraftClient;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.HoverEvent;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Formatting;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;

import org.slf4j.Logger;

import java.util.*;
Expand All @@ -46,6 +57,7 @@
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
import static net.minecraft.command.CommandSource.suggestMatching;
import org.jetbrains.annotations.Nullable;

/**
* Manager for Crystal Hollows waypoints that handles {@link #update() location detection},
Expand All @@ -61,6 +73,14 @@ public class CrystalsLocationsManager {
* A look-up table to convert between location names and waypoint in the {@link MiningLocationLabel.CrystalHollowsLocationsCategory} values.
*/
private static final Map<String, MiningLocationLabel.CrystalHollowsLocationsCategory> WAYPOINT_LOCATIONS = Arrays.stream(MiningLocationLabel.CrystalHollowsLocationsCategory.values()).collect(Collectors.toMap(MiningLocationLabel.CrystalHollowsLocationsCategory::getName, Function.identity()));
// Locations to search for in chat messages
private static final EnumSet<MiningLocationLabel.CrystalHollowsLocationsCategory> CHAT_VERIFIABLE_WAYPOINTS =
EnumSet.of(
MiningLocationLabel.CrystalHollowsLocationsCategory.KHAZAD_DUM,
MiningLocationLabel.CrystalHollowsLocationsCategory.GOBLIN_QUEENS_DEN,
MiningLocationLabel.CrystalHollowsLocationsCategory.MINES_OF_DIVAN
);

//Package-private for testing
static final Pattern TEXT_CWORDS_PATTERN = Pattern.compile("\\Dx?(\\d{3})(?=[, ]),? ?y?(\\d{2,3})(?=[, ]),? ?z?(\\d{3})\\D?(?!\\d)");
private static final int REMOVE_UNKNOWN_DISTANCE = 50;
Expand All @@ -81,6 +101,27 @@ public static void init() {

// Nucleus Waypoints
WorldRenderEvents.AFTER_TRANSLUCENT.register(NucleusWaypoints::render);

// Verify waypoints by left- or right-clicking on an NPC such as Odawa, Boss Corleone, or Kalhuiki Door Guardian, etc.
AttackEntityCallback.EVENT.register(CrystalsLocationsManager::onEntryInteract);
UseEntityCallback.EVENT.register(CrystalsLocationsManager::onEntryInteract);
}

// Verify waypoints when interacting with an NPC
private static ActionResult onEntryInteract(PlayerEntity playerEntity, World world, Hand hand, Entity entity, @Nullable EntityHitResult entityHitResult) {
if (!Utils.isInCrystalHollows()) {
return ActionResult.PASS;
}
// Searching for an invisible armor stand behind an entity (it has the entity’s name)
String npcName = MobGlow.getArmorStandName(entity);

CrystalHollowsLocationsCategory waypointLocation = CrystalHollowsLocationsCategory.fromContainsIdentifyingText(npcName);
if (waypointLocation != null && !npcName.isBlank()) {
BlockPos pos = entity.getBlockPos();
placeVerifiedWaypoint(waypointLocation, pos);
}

return ActionResult.PASS;
}

private static boolean extractLocationFromMessage(Text message, Boolean overlay) {
Expand Down Expand Up @@ -123,25 +164,22 @@ private static boolean extractLocationFromMessage(Text message, Boolean overlay)

CLIENT.player.sendMessage(getLocationMenu(location, false), false);
}
} else { // if not a user message, try to find keywords in the chat to place waypoint more accurate, if not already verifed
if (CLIENT.player != null && SkyblockerConfigManager.get().mining.crystalsWaypoints.enabled) {
// Iterate only through waypoints with crystal names
for (MiningLocationLabel.CrystalHollowsLocationsCategory waypointLocation : CHAT_VERIFIABLE_WAYPOINTS) {
String waypointLinkedMessage = waypointLocation.getIdentifyingText();
if (waypointLinkedMessage != null && text.contains(waypointLinkedMessage)) {
placeVerifiedWaypoint(waypointLocation, CLIENT.player.getBlockPos());
}
}
}
}

} catch (Exception e) {
LOGGER.error("[Skyblocker Crystals Locations Manager] Encountered an exception while extracing a location from a chat message!", e);
}

//move waypoint to be more accurate based on locational chat messages if not already verifed
if (CLIENT.player != null && SkyblockerConfigManager.get().mining.crystalsWaypoints.enabled) {
for (MiningLocationLabel.CrystalHollowsLocationsCategory waypointLocation : WAYPOINT_LOCATIONS.values()) {
String waypointLinkedMessage = waypointLocation.getLinkedMessage();
String waypointName = waypointLocation.getName();
if (waypointLinkedMessage != null && text.contains(waypointLinkedMessage) && !verifiedWaypoints.contains(waypointName)) {
addCustomWaypoint(waypointLocation.getName(), CLIENT.player.getBlockPos());
verifiedWaypoints.add(waypointName);
trySendWaypoint2Socket(waypointLocation);
}
}
}

return true;
}

Expand Down Expand Up @@ -404,4 +442,24 @@ private static void trySendWaypoint2Socket(MiningLocationLabel.CrystalHollowsLoc
WsMessageHandler.sendMessage(Service.CRYSTAL_WAYPOINTS, new CrystalsWaypointMessage(category, CLIENT.player.getBlockPos()));
waypointsSent2Socket.add(category);
}

// Check if new coords are distant enough from old coords (return true if too close)
private static boolean areWaypointsOverlapping(BlockPos newPos, MiningLocationLabel oldWaypoint, int searchRadius) {
if (oldWaypoint == null || searchRadius == 0) return false;

return newPos.getSquaredDistance(oldWaypoint.pos) < searchRadius * searchRadius;
}

private static void placeVerifiedWaypoint(CrystalHollowsLocationsCategory waypoint, BlockPos pos) {
String waypointName = waypoint.getName();

if (!verifiedWaypoints.contains(waypointName)) {
// Check that we don't already have the same waypoint nearby (e.g., from Socket), should (theoretically) save some traffic for the server
if (!areWaypointsOverlapping(pos, activeWaypoints.get(waypointName), waypoint.getSearchRadius())) {
trySendWaypoint2Socket(waypoint);
}
addCustomWaypoint(waypointName, pos);
verifiedWaypoints.add(waypointName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import net.minecraft.util.DyeColor;
import net.minecraft.util.StringIdentifiable;
import net.minecraft.util.math.BlockPos;
import org.jetbrains.annotations.Nullable;

import java.awt.*;

Expand Down Expand Up @@ -160,32 +161,41 @@ public int getColor() {
}

/**
* enum for the different waypoints used int the crystals hud each with a {@link CrystalHollowsLocationsCategory#name} and associated {@link CrystalHollowsLocationsCategory#color}
* Enum for the different waypoints used in the crystals hud each with a {@link CrystalHollowsLocationsCategory#name} and associated {@link CrystalHollowsLocationsCategory#color}.
*/
public enum CrystalHollowsLocationsCategory implements Category, StringIdentifiable {
UNKNOWN("Unknown", Color.WHITE, null), //used when a location is known but what's at the location is not known
JUNGLE_TEMPLE("Jungle Temple", new Color(DyeColor.PURPLE.getSignColor()), "[NPC] Kalhuiki Door Guardian:"),
MINES_OF_DIVAN("Mines of Divan", Color.GREEN, " Jade Crystal"),
GOBLIN_QUEENS_DEN("Goblin Queen's Den", new Color(DyeColor.ORANGE.getSignColor()), " Amber Crystal"),
LOST_PRECURSOR_CITY("Lost Precursor City", Color.CYAN, " Sapphire Crystal"),
KHAZAD_DUM("Khazad-dûm", Color.YELLOW, " Topaz Crystal"),
FAIRY_GROTTO("Fairy Grotto", Color.PINK, null),
DRAGONS_LAIR("Dragon's Lair", Color.BLACK, null),
CORLEONE("Corleone", Color.WHITE, null),
KING_YOLKAR("King Yolkar", Color.RED, "[NPC] King Yolkar:"),
ODAWA("Odawa", Color.MAGENTA, "[NPC] Odawa:"),
KEY_GUARDIAN("Key Guardian", Color.LIGHT_GRAY, null);
UNKNOWN("Unknown", Color.WHITE), // Used when a location is known but what's at the location is not known
// These waypoints are verified by interacting with the corresponding NPC (e.g., by clicking on Odawa)
JUNGLE_TEMPLE("Jungle Temple", new Color(DyeColor.PURPLE.getSignColor()), "Kalhuiki Door Guardian", 10),
LOST_PRECURSOR_CITY("Lost Precursor City", Color.CYAN, "Professor Robot", 8),
KING_YOLKAR("King Yolkar", Color.RED, "King Yolkar", 8),
ODAWA("Odawa", Color.MAGENTA, "Odawa", 8),
CORLEONE("Corleone", Color.WHITE, "Boss Corleone", 20),
KEY_GUARDIAN("Key Guardian", Color.LIGHT_GRAY, "Key Guardian", 10),
// Look for chat messages containing the crystal name
KHAZAD_DUM("Khazad-dûm", Color.YELLOW, " Topaz Crystal", 20),
GOBLIN_QUEENS_DEN("Goblin Queen's Den", new Color(DyeColor.ORANGE.getSignColor()), " Amber Crystal", 20),
MINES_OF_DIVAN("Mines of Divan", Color.GREEN, " Jade Crystal", 20),
// These cannot be found automatically yet.
FAIRY_GROTTO("Fairy Grotto", Color.PINK),
DRAGONS_LAIR("Dragon's Lair", Color.BLACK);

public static final Codec<CrystalHollowsLocationsCategory> CODEC = StringIdentifiable.createBasicCodec(CrystalHollowsLocationsCategory::values);

public final Color color;
private final String name;
private final String linkedMessage;
private final @Nullable String identifyingText;
private final int searchRadius;

CrystalHollowsLocationsCategory(String name, Color color, String linkedMessage) {
CrystalHollowsLocationsCategory(String name, Color color) {
this(name, color, null, 0);
}

CrystalHollowsLocationsCategory(String name, Color color, @Nullable String identifyingText, int searchRadius) {
this.name = name;
this.color = color;
this.linkedMessage = linkedMessage;
this.identifyingText = identifyingText;
this.searchRadius = searchRadius;
}

@Override
Expand All @@ -198,14 +208,29 @@ public int getColor() {
return this.color.getRGB();
}

public String getLinkedMessage() {
return this.linkedMessage;
public @Nullable String getIdentifyingText() {
return this.identifyingText;
}

public int getSearchRadius() {
return this.searchRadius;
}

@Override
public String asString() {
return name();
}

public static CrystalHollowsLocationsCategory fromContainsIdentifyingText(String query) {
if (query == null || query.isBlank()) return null;

for (CrystalHollowsLocationsCategory c : values()) {
if (c.identifyingText != null && !c.identifyingText.isBlank() && query.contains(c.identifyingText)) {
return c;
}
}
return null;
}
}

}
Loading