diff --git a/src/main/java/de/hysky/skyblocker/config/categories/DebugCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/DebugCategory.java index 9ff08e2f65..a09040a0d0 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/DebugCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/DebugCategory.java @@ -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.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(); } } diff --git a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java index d12f523a72..890d86aa10 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java @@ -250,12 +250,36 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig .collapsed(false) .option(Option.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.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.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.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(); } diff --git a/src/main/java/de/hysky/skyblocker/config/configs/DebugConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/DebugConfig.java index 8002363117..df0a69c646 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/DebugConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/DebugConfig.java @@ -15,4 +15,7 @@ public class DebugConfig { @SerialEntry public boolean webSocketDebug = false; + + @SerialEntry + public boolean corpseFinderDebug = false; } diff --git a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java index 201c5c4961..f73f38a9b9 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java @@ -164,6 +164,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 { diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java index 79f45a3205..cf4d2060f0 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java @@ -11,6 +11,7 @@ import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager; 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.CrystalsChestHighlighter; import de.hysky.skyblocker.skyblock.dwarven.WishingCompassSolver; import de.hysky.skyblocker.skyblock.end.EnderNodes; @@ -57,6 +58,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) { @@ -94,6 +96,7 @@ 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); } @Inject(method = "onPlayerListHeader", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/PlayerListHud;setFooter(Lnet/minecraft/text/Text;)V")) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CorpseFinder.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CorpseFinder.java new file mode 100644 index 0000000000..f3dd9aab44 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CorpseFinder.java @@ -0,0 +1,318 @@ +package de.hysky.skyblocker.skyblock.dwarven; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.serialization.Codec; +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.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.command.argument.EnumArgumentType; +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.StringIdentifiable; +import net.minecraft.util.Util; +import net.minecraft.util.math.BlockPos; +import org.apache.commons.lang3.EnumUtils; +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: (?-?\\d+), y: (?\\d+), z: (?-?\\d+)"); + private static final String PREFIX = "[Skyblocker Corpse Finder] "; + private static final Logger LOGGER = LoggerFactory.getLogger(CorpseFinder.class); + private static final Map> corpsesByType = new EnumMap<>(CorpseType.class); + 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"; + + @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 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", CorpseType.CorpseTypeArgumentType.corpseType()) + .executes(context -> { + shareLocation(ClientBlockPosArgumentType.getBlockPos(context, "blockPos"), CorpseType.CorpseTypeArgumentType.getCorpseType(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 helmetItemId = ItemUtils.getItemId(armorStand.getEquippedStack(EquipmentSlot.HEAD)); + CorpseType corpseType = CorpseType.fromHelmetItemId(helmetItemId); + if (corpseType == CorpseType.UNKNOWN) return; + + LOGGER.debug(PREFIX + "Triggered code for handleArmorStand and matched with ITEM_IDS"); + List corpses = corpsesByType.computeIfAbsent(corpseType, k -> new ArrayList<>()); + if (corpses.stream().noneMatch(c -> c.entity.getBlockPos().equals(armorStand.getBlockPos()))) { + Waypoint corpseWaypoint; + float[] color = getColors(corpseType.color); + 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, corpseType); + corpses.add(newCorpse); + } + } + + private static void renderWaypoints(WorldRenderContext context) { + if (!SkyblockerConfigManager.get().mining.glacite.enableCorpseFinder || !isLocationCorrect) return; + for (List 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 corpseTypeString = matcherCorpse.group(1).toUpperCase(Locale.ENGLISH); + CorpseType corpseType = EnumUtils.getEnum(CorpseType.class, corpseTypeString, CorpseType.UNKNOWN); + + List corpses = corpsesByType.get(corpseType); + if (corpses == null) { + LOGGER.warn(PREFIX + "Couldn't get corpses! corpse type string: {}, parsed corpse type: {}", corpseTypeString, corpseType); + 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.corpseType); + 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.corpseType.asString()) + " Corpse") + .withColor(corpse.corpseType.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.corpseType)) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal("Click to share the location in chat!").formatted(Formatting.GREEN)))), false); + } + + private static void shareLocation(BlockPos pos, CorpseType corpseType) { + MessageScheduler.INSTANCE.sendMessageAfterCooldown("/pc " + toSkyhanniFormat(pos) + " | (" + WordUtils.capitalizeFully(corpseType.asString()) + " 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, y, z; + try { + x = Integer.parseInt(matcher.group("x")); + y = Integer.parseInt(matcher.group("y")); + z = Integer.parseInt(matcher.group("z")); + } catch (NumberFormatException e) { + LOGGER.warn(PREFIX + "Failed to parse message: `{}`, reason: {}", message, e.getMessage()); + return; + } + + 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 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 corpses : corpsesByType.values()) { + for (Corpse corpse : corpses) { + LOGGER.info(PREFIX + "Corpse: {}, BlockPos: {}", corpse.entity, corpse.entity.getBlockPos()); + } + } + } + } + + enum CorpseType implements StringIdentifiable { + LAPIS(LAPIS_HELMET, Formatting.BLUE), // dark blue looks bad and these two never exist in same shaft + UMBER(UMBER_HELMET, Formatting.RED), + TUNGSTEN(TUNGSTEN_HELMET, Formatting.GRAY), + VANGUARD(VANGUARD_HELMET, Formatting.BLUE), + UNKNOWN("UNKNOWN", Formatting.YELLOW); + private static final Codec CODEC = StringIdentifiable.createCodec(CorpseType::values); + private final String helmetItemId; + private final Formatting color; + + CorpseType(String helmetItemId, Formatting color) { + this.helmetItemId = helmetItemId; + this.color = color; + } + + static CorpseType fromHelmetItemId(String helmetItemId) { + for (CorpseType value : values()) { + if (value.helmetItemId.equals(helmetItemId)) { + return value; + } + } + return UNKNOWN; + } + + @Override + public String asString() { + return name().toLowerCase(); + } + + static class CorpseTypeArgumentType extends EnumArgumentType { + protected CorpseTypeArgumentType() { + super(CODEC, CorpseType::values); + } + + static CorpseTypeArgumentType corpseType() { + return new CorpseTypeArgumentType(); + } + + static CorpseType getCorpseType(CommandContext context, String name) { + return context.getArgument(name, CorpseType.class); + } + } + } + + static class Corpse { + private final ArmorStandEntity entity; + /** + * Waypoint position is always 1 above entity position + */ + private final Waypoint waypoint; + /** + * Type of the corpse, fully uppercased. + */ + private final CorpseType corpseType; + // TODO: migrate to seen waypoint #1108 + private boolean seen; + private long messageLastSent = 0; + + Corpse(ArmorStandEntity entity, Waypoint waypoint, CorpseType corpseType) { + this.entity = entity; + this.waypoint = waypoint; + this.seen = false; + this.corpseType = corpseType; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java b/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java index 499e605dd9..cd07ac58f0 100644 --- a/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java +++ b/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java @@ -44,14 +44,18 @@ public Waypoint(BlockPos pos, Supplier typeSupplier, float[] colorComponen this(pos, typeSupplier, colorComponents, DEFAULT_HIGHLIGHT_ALPHA, DEFAULT_LINE_WIDTH); } - public Waypoint(BlockPos pos, Type type, float[] colorComponents, float alpha) { - this(pos, () -> type, colorComponents, alpha, DEFAULT_LINE_WIDTH); + public Waypoint(BlockPos pos, Type type, float[] colorComponents, float alpha) { // @formatter:off + this(pos, () -> type, colorComponents, alpha, DEFAULT_LINE_WIDTH); // @formatter:on } public Waypoint(BlockPos pos, Supplier typeSupplier, float[] colorComponents, float alpha, float lineWidth) { this(pos, typeSupplier, colorComponents, alpha, lineWidth, true); } + public Waypoint(BlockPos pos, Type type, float[] colorComponents, boolean throughWalls) { // @formatter:off + this(pos, () -> type, colorComponents, throughWalls); // @formatter:on + } + public Waypoint(BlockPos pos, Supplier typeSupplier, float[] colorComponents, boolean throughWalls) { this(pos, typeSupplier, colorComponents, DEFAULT_HIGHLIGHT_ALPHA, DEFAULT_LINE_WIDTH, throughWalls); } diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index aff2313e88..524b2f4f7c 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -69,6 +69,7 @@ "skyblocker.config.debug.dumpRange.@Tooltip": "The range in blocks around the player to dump entities within.", "skyblocker.config.debug.showInvisibleArmorStands": "Show Invisible Armor Stands", "skyblocker.config.debug.debugWebSockets": "Enable Websocket Debug Messages", + "skyblocker.config.debug.corpseFinderDebug": "Debug Corpse Finder", "skyblocker.config.dungeons": "Dungeons", @@ -639,7 +640,13 @@ "skyblocker.config.mining.glacite": "Glacite Tunnels", "skyblocker.config.mining.glacite.coldOverlay": "Cold Overlay", - "skyblocker.config.mining.glacite.coldOverlay@Tooltip": "Shows a frosty overlay in the Glacite mines that gets stronger as you get colder.", + "skyblocker.config.mining.glacite.coldOverlay.@Tooltip": "Shows a frosty overlay in the Glacite mines that gets stronger as you get colder.", + "skyblocker.config.mining.glacite.enableCorpseFinder": "Corpse Finder", + "skyblocker.config.mining.glacite.enableCorpseFinder.@Tooltip": "Creates waypoints for corpses. Allows for sharing them.", + "skyblocker.config.mining.glacite.enableParsingChatCorpseFinder": "Parse Corpses from chat", + "skyblocker.config.mining.glacite.enableParsingChatCorpseFinder.@Tooltip": "If enabled, will listen to chat and process corpses shared by other players. Should be compatible with other mods.", + "skyblocker.config.mining.glacite.autoShareCorpses": "Automatically Share Found Corpses In Chat", + "skyblocker.config.mining.glacite.autoShareCorpses.@Tooltip": "Sends a message in party chat with the location of the found corpse when you find one.", "skyblocker.config.misc": "Misc", diff --git a/src/main/resources/assets/skyblocker/lang/ru_ru.json b/src/main/resources/assets/skyblocker/lang/ru_ru.json index 034344700c..2be0442b82 100644 --- a/src/main/resources/assets/skyblocker/lang/ru_ru.json +++ b/src/main/resources/assets/skyblocker/lang/ru_ru.json @@ -876,7 +876,7 @@ "skyblocker.profileviewer.inventory.pets": "Питомцы", "skyblocker.profileviewer.inventory.backpack": "Рюкзак", "skyblocker.profileviewer.inventory.accessoryBag": "Сумка для аксессуаров", - "skyblocker.config.mining.glacite.coldOverlay@Tooltip": "Показывает морозное наложение в шахтах Гласита, которое становится сильнее по мере того, как становится холоднее.", + "skyblocker.config.mining.glacite.coldOverlay.@Tooltip": "Показывает морозное наложение в шахтах Гласита, которое становится сильнее по мере того, как становится холоднее.", "skyblocker.crimson.dojo.controlHelper": "Включить помощник Control", "skyblocker.config.mining.crystalsWaypoints.getLocationHover.add": "Добавить локацию", "skyblocker.config.mining.crystalsWaypoints.getLocationHover.remove": "Удалить локацию", diff --git a/src/main/resources/assets/skyblocker/lang/zh_cn.json b/src/main/resources/assets/skyblocker/lang/zh_cn.json index 2283103817..f08a7e0fb4 100644 --- a/src/main/resources/assets/skyblocker/lang/zh_cn.json +++ b/src/main/resources/assets/skyblocker/lang/zh_cn.json @@ -650,7 +650,7 @@ "skyblocker.config.helpers.chocolateFactory.enableTimeTowerReminder.@Tooltip": "当时间塔停止运作时,在聊天栏发送一条提示信息。", "skyblocker.config.mining.glacite.coldOverlay": "寒冷效果动画修改", "skyblocker.config.mining.glacite": "极寒隧道", - "skyblocker.config.mining.glacite.coldOverlay@Tooltip": "显示极寒隧道中的霜冻效果,随着温度的降低,霜冻效果会变得更强。", + "skyblocker.config.mining.glacite.coldOverlay.@Tooltip": "显示极寒隧道中的霜冻效果,随着温度的降低,霜冻效果会变得更强。", "skyblocker.config.misc": "杂项", "skyblocker.config.general.searchOverlay.maxPet.@Tooltip": "只显示最高等级的宠物", "skyblocker.config.general.searchOverlay.starsTooltip": "地牢物品的星星数量", diff --git a/src/main/resources/assets/skyblocker/lang/zh_tw.json b/src/main/resources/assets/skyblocker/lang/zh_tw.json index cf0a8a9f65..dd8a38b324 100644 --- a/src/main/resources/assets/skyblocker/lang/zh_tw.json +++ b/src/main/resources/assets/skyblocker/lang/zh_tw.json @@ -650,7 +650,7 @@ "skyblocker.config.helpers.chocolateFactory.waypointType": "彩蛋路徑點類型", "skyblocker.config.mining.glacite": "極寒隧道", "skyblocker.config.mining.glacite.coldOverlay": "寒冷效果動畫修改", - "skyblocker.config.mining.glacite.coldOverlay@Tooltip": "顯示極寒隧道中的霜凍效果,隨著溫度的降低,霜凍效果會變得更強。", + "skyblocker.config.mining.glacite.coldOverlay.@Tooltip": "顯示極寒隧道中的霜凍效果,隨著溫度的降低,霜凍效果會變得更強。", "skyblocker.config.misc": "雜項", "skyblocker.config.general.searchOverlay.starsTooltip": "地下城物品的星星數量", "skyblocker.config.general.searchOverlay.maxPet": "最高寵物等級",