Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Corpse Finder #960

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2b79020
early one. Needs polishing and config
Fluboxer Aug 24, 2024
1687ab7
2nd wave of changes
Fluboxer Aug 25, 2024
f0d6079
a little refactor to parsing. New version is yet to be tested
Fluboxer Aug 26, 2024
87054a5
another little patch to solve some silly issues
Fluboxer Aug 26, 2024
fbb5e81
early one. Needs polishing and config
Fluboxer Aug 24, 2024
41c4eaf
2nd wave of changes
Fluboxer Aug 25, 2024
14f2dac
Fixed bug when some of corpses weren't highlighted. Also rebased
Fluboxer Sep 8, 2024
72cca40
early one. Needs polishing and config
Fluboxer Aug 24, 2024
8158f78
2nd wave of changes
Fluboxer Aug 25, 2024
91254cf
a little refactor to parsing. New version is yet to be tested
Fluboxer Aug 26, 2024
3bd0078
another little patch to solve some silly issues
Fluboxer Aug 26, 2024
0f7a62a
early one. Needs polishing and config
Fluboxer Aug 24, 2024
cc86993
2nd wave of changes
Fluboxer Aug 25, 2024
a754129
dementia
Fluboxer Sep 9, 2024
e0fc71e
moved few code blocks around
Fluboxer Sep 9, 2024
8ee1e77
Remove unused constructor
Emirlol Nov 23, 2024
49aadd7
Simplify and reformat getColor method
Emirlol Nov 23, 2024
4eb014c
Fix typo in log messager
Emirlol Nov 23, 2024
fbcb2ff
Change the way the argument values are obtained and fix formatting
Emirlol Nov 23, 2024
09f036f
Extract coords pattern into a PSF variable
Emirlol Nov 23, 2024
b1652b2
More formatting
Emirlol Nov 23, 2024
80cf8cb
Oops forgot to remove + fix typo
Emirlol Nov 23, 2024
dec9f90
Clear corpses on world change
Emirlol Nov 23, 2024
fe27e89
Rebased for 1.21.3
Emirlol Nov 23, 2024
3101d87
Tweak chat message slightly
Emirlol Nov 24, 2024
61630c3
Inline LOCATION variable as it was hard to read
Emirlol Nov 24, 2024
5bb6127
Bunch of stuff
Emirlol Nov 24, 2024
d131756
Add a debug config for corpse finder and have it toggled off by default
Emirlol Nov 24, 2024
e9544d4
Fix regex and change sent message format
Emirlol Nov 24, 2024
1a1914f
Refactor some if checks
Emirlol Nov 24, 2024
137ba85
Add auto location sharing, off by default
Emirlol Nov 24, 2024
9d0e072
Corrected format and ignore player's own messages
Emirlol Nov 24, 2024
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 @@ -44,6 +44,13 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig
newValue -> config.debug.dumpFormat = newValue)
.controller(opt -> EnumControllerBuilder.create(opt).enumClass(Debug.DumpFormat.class)) // ConfigUtils::createEnumCyclingListController causes a NPE for some reason
.build())
.option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.debug.corpseFinderDebug"))
.binding(defaults.debug.corpseFinderDebug,
() -> config.debug.corpseFinderDebug,
newValue -> config.debug.corpseFinderDebug = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -264,12 +264,36 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig
.collapsed(false)
.option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.mining.glacite.coldOverlay"))
.description(OptionDescription.of(Text.translatable("skyblocker.config.mining.glacite.coldOverlay@Tooltip")))
.description(OptionDescription.of(Text.translatable("skyblocker.config.mining.glacite.coldOverlay.@Tooltip")))
.binding(defaults.mining.glacite.coldOverlay,
() -> config.mining.glacite.coldOverlay,
newValue -> config.mining.glacite.coldOverlay = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
.option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.mining.glacite.enableCorpseFinder"))
.description(OptionDescription.of(Text.translatable("skyblocker.config.mining.glacite.enableCorpseFinder.@Tooltip")))
.binding(defaults.mining.glacite.enableCorpseFinder,
() -> config.mining.glacite.enableCorpseFinder,
newValue -> config.mining.glacite.enableCorpseFinder = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
.option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.mining.glacite.enableParsingChatCorpseFinder"))
.description(OptionDescription.of(Text.translatable("skyblocker.config.mining.glacite.enableParsingChatCorpseFinder.@Tooltip")))
.binding(defaults.mining.glacite.enableParsingChatCorpseFinder,
() -> config.mining.glacite.enableParsingChatCorpseFinder,
newValue -> config.mining.glacite.enableParsingChatCorpseFinder = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
.option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.mining.glacite.autoShareCorpses"))
.description(OptionDescription.of(Text.translatable("skyblocker.config.mining.glacite.autoShareCorpses.@Tooltip")))
.binding(defaults.mining.glacite.autoShareCorpses,
() -> config.mining.glacite.autoShareCorpses,
newValue -> config.mining.glacite.autoShareCorpses = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
.build())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ public class DebugConfig {

@SerialEntry
public boolean webSocketDebug = false;

@SerialEntry
public boolean corpseFinderDebug = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,15 @@ public String toString() {
public static class Glacite {
@SerialEntry
public boolean coldOverlay = true;

@SerialEntry
public boolean enableCorpseFinder = true;

@SerialEntry
public boolean enableParsingChatCorpseFinder = true;

@SerialEntry
public boolean autoShareCorpses = false;
}

public enum DwarvenHudStyle {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import de.hysky.skyblocker.skyblock.crimson.slayer.FirePillarAnnouncer;
import de.hysky.skyblocker.skyblock.dungeon.DungeonScore;
import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
import de.hysky.skyblocker.skyblock.dwarven.CorpseFinder;
import de.hysky.skyblocker.skyblock.dwarven.WishingCompassSolver;
import de.hysky.skyblocker.skyblock.dwarven.CrystalsChestHighlighter;
import de.hysky.skyblocker.skyblock.end.EnderNodes;
Expand Down Expand Up @@ -124,6 +125,7 @@ public abstract class ClientPlayNetworkHandlerMixin {
if (SkyblockerConfigManager.get().slayers.blazeSlayer.firePillarCountdown != SlayersConfig.BlazeSlayer.FirePillar.OFF) FirePillarAnnouncer.checkFirePillar(entity);

EggFinder.checkIfEgg(armorStandEntity);
CorpseFinder.checkIfCorpse(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) {
Expand All @@ -134,5 +136,6 @@ public abstract class ClientPlayNetworkHandlerMixin {
@Inject(method = "onEntityEquipmentUpdate", at = @At(value = "TAIL"))
private void skyblocker$onEntityEquip(EntityEquipmentUpdateS2CPacket packet, CallbackInfo ci, @Local Entity entity) {
EggFinder.checkIfEgg(entity);
CorpseFinder.checkIfCorpse(entity);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.annotations.Init;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.debug.Debug;
import de.hysky.skyblocker.events.SkyblockEvents;
import de.hysky.skyblocker.utils.*;
import de.hysky.skyblocker.utils.command.argumenttypes.EggTypeArgumentType;
Expand Down
288 changes: 288 additions & 0 deletions src/main/java/de/hysky/skyblocker/skyblock/dwarven/CorpseFinder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
package de.hysky.skyblocker.skyblock.dwarven;

import com.google.common.collect.ImmutableBiMap;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.StringArgumentType;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.annotations.Init;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.debug.Debug;
import de.hysky.skyblocker.events.SkyblockEvents;
import de.hysky.skyblocker.utils.*;
import de.hysky.skyblocker.utils.command.argumenttypes.CorpseTypeArgumentType;
import de.hysky.skyblocker.utils.command.argumenttypes.blockpos.ClientBlockPosArgumentType;
import de.hysky.skyblocker.utils.scheduler.MessageScheduler;
import de.hysky.skyblocker.utils.waypoint.Waypoint;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
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;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.decoration.ArmorStandEntity;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.HoverEvent;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Util;
import net.minecraft.util.math.BlockPos;
import org.apache.commons.text.WordUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;

public class CorpseFinder {
private static boolean isLocationCorrect = false;
private static final Pattern CORPSE_FOUND_PATTERN = Pattern.compile("([A-Z]+) CORPSE LOOT!");
private static final Pattern COORDS_PATTERN = Pattern.compile("x: (?<x>-?\\d+), y: (?<y>-?\\d+), z: (?<z>-?\\d+)");
private static final String PREFIX = "[Skyblocker Corpse Finder] ";
private static final Logger LOGGER = LoggerFactory.getLogger(CorpseFinder.class);
private static final Map<String, List<Corpse>> corpsesByType = new HashMap<>();
private static final String LAPIS_HELMET = "LAPIS_ARMOR_HELMET";
private static final String UMBER_HELMET = "ARMOR_OF_YOG_HELMET";
private static final String TUNGSTEN_HELMET = "MINERAL_HELMET";
private static final String VANGUARD_HELMET = "VANGUARD_HELMET";
private static final ImmutableBiMap<String, String> ITEM_IDS = ImmutableBiMap.of(
"LAPIS", LAPIS_HELMET,
"UMBER", UMBER_HELMET,
"TUNGSTEN", TUNGSTEN_HELMET,
"VANGUARD", VANGUARD_HELMET
);

@Init
public static void init() {
ClientPlayConnectionEvents.JOIN.register((ignored, ignored2, ignored3) -> {
isLocationCorrect = false;
corpsesByType.clear();
});
SkyblockEvents.LOCATION_CHANGE.register(CorpseFinder::handleLocationChange);
ClientReceiveMessageEvents.GAME.register(CorpseFinder::onChatMessage);
WorldRenderEvents.AFTER_TRANSLUCENT.register(CorpseFinder::renderWaypoints);
ClientTickEvents.END_CLIENT_TICK.register(client -> {
if (!SkyblockerConfigManager.get().mining.glacite.enableCorpseFinder || client.player == null) return;
if (!isLocationCorrect) return;
for (List<Corpse> corpses : corpsesByType.values()) {
for (Corpse corpse : corpses) {
if (!corpse.seen && client.player.canSee(corpse.entity)) {
setSeen(corpse);
}
}
}
});
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE)
.then(literal("corpseHelper")
.then(literal("shareLocation")
.then(argument("blockPos", ClientBlockPosArgumentType.blockPos())
.then(argument("corpseType", CorpseTypeArgumentType.corpseType())
.executes(context -> {
shareLocation(ClientBlockPosArgumentType.getBlockPos(context, "blockPos"), StringArgumentType.getString(context, "corpseType"));
return Command.SINGLE_SUCCESS;
})
)
)
)
)
));
}

private static boolean seenDebugWarning = false;

private static void handleLocationChange(Location location) {
isLocationCorrect = location == Location.GLACITE_MINESHAFT;
}

public static void checkIfCorpse(Entity entity) {
if (entity instanceof ArmorStandEntity armorStand) checkIfCorpse(armorStand);
}

public static void checkIfCorpse(ArmorStandEntity armorStand) {
if (!isLocationCorrect || !SkyblockerConfigManager.get().mining.glacite.enableCorpseFinder) return;
if (armorStand.hasCustomName() || armorStand.isInvisible() || armorStand.shouldShowBasePlate()) return;
handleArmorStand(armorStand);
}

private static void handleArmorStand(ArmorStandEntity armorStand) {
String itemId = ItemUtils.getItemId(armorStand.getEquippedStack(EquipmentSlot.HEAD));
if (!ITEM_IDS.containsValue(itemId)) return;

LOGGER.debug(PREFIX + "Triggered code for handleArmorStand and matched with ITEM_IDS");
List<Corpse> corpses = corpsesByType.computeIfAbsent(itemId, k -> new ArrayList<>());
if (corpses.stream().noneMatch(c -> c.entity.getBlockPos().equals(armorStand.getBlockPos()))) {
Waypoint corpseWaypoint;
float[] color = getColors(getColor(armorStand));
corpseWaypoint = new Waypoint(armorStand.getBlockPos().up(), Waypoint.Type.OUTLINED_WAYPOINT, color);
if (Debug.debugEnabled() && SkyblockerConfigManager.get().debug.corpseFinderDebug && !seenDebugWarning && (seenDebugWarning = true)) {
MinecraftClient.getInstance().player.sendMessage(
Constants.PREFIX.get().append(
Text.literal("Corpse finder debug mode is active! Please use it only for the sake of debugging corpse detection!")
.formatted(Formatting.GOLD, Formatting.BOLD)
), false);
}
Corpse newCorpse = new Corpse(armorStand, corpseWaypoint, ITEM_IDS.inverse().getOrDefault(itemId, "UNKNOWN"));
corpses.add(newCorpse);
}
}

private static void renderWaypoints(WorldRenderContext context) {
if (!SkyblockerConfigManager.get().mining.glacite.enableCorpseFinder || !isLocationCorrect) return;
for (List<Corpse> corpses : corpsesByType.values()) {
for (Corpse corpse : corpses) {
if (corpse.waypoint.shouldRender() && (corpse.seen || (Debug.debugEnabled() && SkyblockerConfigManager.get().debug.corpseFinderDebug))) {
corpse.waypoint.render(context);
}
}
}
}

private static void onChatMessage(Text text, boolean overlay) {
if (overlay || !isLocationCorrect || !SkyblockerConfigManager.get().mining.glacite.enableCorpseFinder || MinecraftClient.getInstance().player == null) return;
String string = text.getString();
if (string.contains(MinecraftClient.getInstance().getSession().getUsername())) return; // Ignore your own messages
if (SkyblockerConfigManager.get().mining.glacite.enableParsingChatCorpseFinder) parseCords(text); // parsing cords from chat

Matcher matcherCorpse = CORPSE_FOUND_PATTERN.matcher(string);
if (!matcherCorpse.find()) return;

LOGGER.debug(PREFIX + "Triggered code for onChatMessage");
LOGGER.debug(PREFIX + "State of corpsesByType: {}", corpsesByType);
String corpseType = matcherCorpse.group(1).toUpperCase();
String key = ITEM_IDS.inverse().getOrDefault(corpseType, null);

List<Corpse> corpses = corpsesByType.get(key);
if (corpses == null) {
LOGGER.warn(PREFIX + "Couldn't get corpses! corpseType: {}, key: {}", corpseType, key);
return;
}
corpses.stream() // Since squared distance comparison will yield the same result as normal distance comparison, we can use squared distance to avoid square root calculation
.min(Comparator.comparingDouble(corpse -> corpse.entity.squaredDistanceTo(MinecraftClient.getInstance().player)))
.ifPresentOrElse(
corpse -> {
LOGGER.info(PREFIX + "Found corpse, marking as found! {}: {}", corpse.entity.getType(), corpse.entity.getBlockPos().toShortString());
corpse.waypoint.setFound();
},
() -> LOGGER.warn(PREFIX + "Couldn't find the closest corpse despite triggering onChatMessage!")
);
}

@SuppressWarnings("DataFlowIssue")
private static void setSeen(Corpse corpse) {
corpse.seen = true;
if (SkyblockerConfigManager.get().mining.glacite.autoShareCorpses) {
shareLocation(corpse.entity.getBlockPos().up(), corpse.name);
return; // There's no need to send the message twice, so we return here.
}
if (Util.getMeasuringTimeMs() - corpse.messageLastSent < 300) return;

corpse.messageLastSent = Util.getMeasuringTimeMs();

MinecraftClient.getInstance().player.sendMessage(
Constants.PREFIX.get()
.append("Found a ")
.append(Text.literal(WordUtils.capitalizeFully(corpse.name) + " Corpse")
.withColor(corpse.color.getColorValue()))
.append(" at " + corpse.entity.getBlockPos().up().toShortString() + "!")
.styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker corpseHelper shareLocation " + PosUtils.toSpaceSeparatedString(corpse.waypoint.pos) + " " + corpse.name))
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal("Click to share the location in chat!").formatted(Formatting.GREEN)))), false);
}

private static Formatting getColor(ArmorStandEntity entity) {
String itemId = ItemUtils.getItemId(entity.getEquippedStack(EquipmentSlot.HEAD));
if (ITEM_IDS.containsValue(itemId)) {
switch (itemId) {
case LAPIS_HELMET, VANGUARD_HELMET:
return Formatting.BLUE; // dark blue looks bad and those two never exist in same shaft
case UMBER_HELMET:
return Formatting.RED;
case TUNGSTEN_HELMET:
return Formatting.GRAY;
}
}

LOGGER.warn(PREFIX + "Couldn't match a color! Something probably went very wrong!");
return Formatting.YELLOW;
}

private static void shareLocation(BlockPos pos, String corpseType) {
MessageScheduler.INSTANCE.sendMessageAfterCooldown("/pc " + toSkyhanniFormat(pos) + " | (" + WordUtils.capitalizeFully(corpseType) + " Corpse)", true);
}

@SuppressWarnings("DataFlowIssue")
private static float[] getColors(Formatting color) {
return ColorUtils.getFloatComponents(color.getColorValue());
}

// Since read in their format, might as well send in their format too.
// Some other mods seem to send in this same format, so it'll help any other mods that might be listening for this format.
private static String toSkyhanniFormat(BlockPos pos) {
return String.format("x: %d, y: %d, z: %d", pos.getX() + 1, pos.getY(), pos.getZ() + 1);
}

private static void parseCords(Text text) {
String message = text.getString();
Matcher matcher = COORDS_PATTERN.matcher(message);
if (!matcher.find()) return;

int x = Integer.parseInt(matcher.group("x"));
int y = Integer.parseInt(matcher.group("y"));
int z = Integer.parseInt(matcher.group("z"));
LOGGER.debug(PREFIX + "Parsed message! X:{}, Y:{}, Z:{}", x, y, z);
boolean foundCorpse = false;
BlockPos parsedPos = new BlockPos(x - 1, y, z - 1); // skyhanni cords format difference is -1, 0, -1

for (List<Corpse> corpses : corpsesByType.values()) {
for (Corpse corpse : corpses) {
if (corpse.waypoint.pos.equals(parsedPos)) {
corpse.seen = true;
foundCorpse = true;
LOGGER.info(PREFIX + "Setting corpse {} as seen!", corpse.entity);
MinecraftClient.getInstance().player.sendMessage(
Constants.PREFIX.get()
.append("Parsed message from chat, adding corpse at ")
.append(corpse.entity.getBlockPos().toShortString()), false);
break;
}
}
}
if (!foundCorpse) {
LOGGER.warn(PREFIX + "Did NOT find any match for corpses! corpsesByType.values(): {}", corpsesByType.values());
LOGGER.info(PREFIX + "Proceeding to iterate over all corpses!");
for (List<Corpse> corpses : corpsesByType.values()) {
for (Corpse corpse : corpses) {
LOGGER.info(PREFIX + "Corpse: {}, BlockPos: {}", corpse.entity, corpse.entity.getBlockPos());
}
}
}
}

static class Corpse {
private final ArmorStandEntity entity;
/**
* Waypoint position is always 1 above entity position
*/
private final Waypoint waypoint;
private boolean seen;
private long messageLastSent = 0;
private final Formatting color;
/**
* Type of the corpse, fully uppercased.
*/
private final String name;

Corpse(ArmorStandEntity entity, Waypoint waypoint, String name) {
this.entity = entity;
this.waypoint = waypoint;
this.seen = false;
this.color = getColor(entity);
this.name = name;
}
}
}
Loading