diff options
author | Kevin <92656833+kevinthegreat1@users.noreply.github.com> | 2024-01-21 13:05:51 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-21 13:05:51 -0500 |
commit | a092f33ab7e52006cbca75adb6974ccf67f7e890 (patch) | |
tree | 07412faeffd41b7d2aea72c8d75269db373ce194 | |
parent | 08444dc32b39b8cdf8bf6a1e47248fc4d871d2d7 (diff) | |
parent | cbf7f80faf78764f7787122be194e8d26e8b1f9e (diff) | |
download | Skyblocker-a092f33ab7e52006cbca75adb6974ccf67f7e890.tar.gz Skyblocker-a092f33ab7e52006cbca75adb6974ccf67f7e890.tar.bz2 Skyblocker-a092f33ab7e52006cbca75adb6974ccf67f7e890.zip |
Merge pull request #500 from Emirlol/clientside-dungeon-score
Add dungeon score calculation on client-side
14 files changed, 664 insertions, 99 deletions
diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java index aa6e5d24..9477fbed 100644 --- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java @@ -606,6 +606,9 @@ public class SkyblockerConfig { public DungeonChestProfit dungeonChestProfit = new DungeonChestProfit(); @SerialEntry + public MimicMessage mimicMessage = new MimicMessage(); + + @SerialEntry public boolean croesusHelper = true; @SerialEntry @@ -757,6 +760,18 @@ public class SkyblockerConfig { @SerialEntry public String dungeonScore300Message = "300 Score Reached!"; + + @SerialEntry + public boolean enableScoreHUD = true; + + @SerialEntry + public int scoreX = 29; + + @SerialEntry + public int scoreY = 134; + + @SerialEntry + public float scoreScaling = 1f; } public static class DungeonChestProfit { @@ -785,6 +800,14 @@ public class SkyblockerConfig { public Formatting incompleteColor = Formatting.BLUE; } + public static class MimicMessage { + @SerialEntry + public boolean sendMimicMessage = true; + + @SerialEntry + public String mimicMessage = "Mimic dead!"; + } + public static class LividColor { @SerialEntry public boolean enableLividColorGlow = true; @@ -970,6 +993,12 @@ public class SkyblockerConfig { public ChatFilterResult hideToggleSkyMall = ChatFilterResult.PASS; @SerialEntry + public ChatFilterResult hideMimicKill = ChatFilterResult.PASS; + + @SerialEntry + public ChatFilterResult hideDeath = ChatFilterResult.PASS; + + @SerialEntry public boolean hideMana = false; } diff --git a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java index 583bc166..3b685f9a 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java @@ -2,17 +2,12 @@ package de.hysky.skyblocker.config.categories; import de.hysky.skyblocker.config.ConfigUtils; import de.hysky.skyblocker.config.SkyblockerConfig; +import de.hysky.skyblocker.skyblock.dungeon.DungeonMapConfigScreen; import de.hysky.skyblocker.utils.waypoint.Waypoint.Type; -import dev.isxander.yacl3.api.ButtonOption; -import dev.isxander.yacl3.api.ConfigCategory; -import dev.isxander.yacl3.api.Option; -import dev.isxander.yacl3.api.OptionDescription; -import dev.isxander.yacl3.api.OptionFlag; -import dev.isxander.yacl3.api.OptionGroup; +import dev.isxander.yacl3.api.*; import dev.isxander.yacl3.api.controller.FloatFieldControllerBuilder; import dev.isxander.yacl3.api.controller.IntegerFieldControllerBuilder; import dev.isxander.yacl3.api.controller.StringControllerBuilder; -import de.hysky.skyblocker.skyblock.dungeon.DungeonMapConfigScreen; import net.minecraft.client.MinecraftClient; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -235,6 +230,25 @@ public class DungeonsCategory { newValue -> config.locations.dungeons.dungeonScore.dungeonScore300Message = newValue) .controller(StringControllerBuilder::create) .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableScoreHUD")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableScoreHUD.@Tooltip"), Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableScoreHUD.deathMessagesNote"))) + .binding(defaults.locations.dungeons.dungeonScore.enableScoreHUD, + () -> config.locations.dungeons.dungeonScore.enableScoreHUD, + newValue -> config.locations.dungeons.dungeonScore.enableScoreHUD = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Float>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.scoreScaling")) + .binding(defaults.locations.dungeons.dungeonScore.scoreScaling, + () -> config.locations.dungeons.dungeonScore.scoreScaling, + newValue -> { + config.locations.dungeons.dungeonScore.scoreX = config.locations.dungeons.dungeonScore.scoreX + (int) ((config.locations.dungeons.dungeonScore.scoreScaling - newValue) * 38.0); + config.locations.dungeons.dungeonScore.scoreY = config.locations.dungeons.dungeonScore.scoreY + (int) ((config.locations.dungeons.dungeonScore.scoreScaling - newValue) * MinecraftClient.getInstance().textRenderer.fontHeight / 2.0); + config.locations.dungeons.dungeonScore.scoreScaling = newValue; + }) + .controller(FloatFieldControllerBuilder::create) + .build()) .build()) //Dungeon Chest Profit @@ -419,7 +433,29 @@ public class DungeonsCategory { .controller(ConfigUtils::createBooleanController) .build()) - // Livid Color + //Mimic Message + .group(OptionGroup.createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.mimicMessage")) + .collapsed(true) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.mimicMessage.sendMimicMessage")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.mimicMessage.sendMimicMessage.@Tooltip"))) + .binding(defaults.locations.dungeons.mimicMessage.sendMimicMessage, + () -> config.locations.dungeons.mimicMessage.sendMimicMessage, + newValue -> config.locations.dungeons.mimicMessage.sendMimicMessage = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<String>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.mimicMessage.mimicMessage")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.mimicMessage.mimicMessage.@Tooltip"))) + .binding(defaults.locations.dungeons.mimicMessage.mimicMessage, + () -> config.locations.dungeons.mimicMessage.mimicMessage, + newValue -> config.locations.dungeons.mimicMessage.mimicMessage = newValue) + .controller(StringControllerBuilder::create) + .build()) + .build()) + + //Livid Color .group(OptionGroup.createBuilder() .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.lividColor")) .collapsed(true) diff --git a/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java index 37f24d8c..ce349049 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java @@ -102,6 +102,22 @@ public class MessageFilterCategory { newValue -> config.messages.hideMana = newValue) .controller(ConfigUtils::createBooleanController) .build()) + .option(Option.<ChatFilterResult>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.messages.hideMimicKill")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.messages.hideMimicKill.@Tooltip"))) + .binding(defaults.messages.hideMimicKill, + () -> config.messages.hideMimicKill, + newValue -> config.messages.hideMimicKill = newValue) + .controller(ConfigUtils::createEnumCyclingListController) + .build()) + .option(Option.<ChatFilterResult>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.messages.hideDeath")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.messages.hideDeath.@Tooltip"))) + .binding(defaults.messages.hideDeath, + () -> config.messages.hideDeath, + newValue -> config.messages.hideDeath = newValue) + .controller(ConfigUtils::createEnumCyclingListController) + .build()) .build(); } } diff --git a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java index 4015dfa5..a8537088 100644 --- a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java @@ -1,19 +1,23 @@ package de.hysky.skyblocker.mixin; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.WrapWithCondition; import com.llamalad7.mixinextras.sugar.Local; import de.hysky.skyblocker.skyblock.FishingHelper; +import de.hysky.skyblocker.skyblock.dungeon.DungeonScore; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; import de.hysky.skyblocker.skyblock.waypoint.MythologicalRitual; import de.hysky.skyblocker.utils.Utils; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityStatuses; import net.minecraft.entity.ItemEntity; import net.minecraft.entity.LivingEntity; +import net.minecraft.network.packet.s2c.play.EntityStatusS2CPacket; import net.minecraft.network.packet.s2c.play.ParticleS2CPacket; import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket; import net.minecraft.util.Identifier; - import org.slf4j.Logger; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -49,12 +53,12 @@ public abstract class ClientPlayNetworkHandlerMixin { private boolean skyblocker$cancelTeamWarning(Logger instance, String format, Object... arg) { return !Utils.isOnHypixel(); } - + @WrapWithCondition(method = { "onScoreboardScoreUpdate", "onScoreboardScoreReset" }, at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V", remap = false)) private boolean skyblocker$cancelUnknownScoreboardObjectiveWarnings(Logger instance, String message, Object objectiveName) { return !Utils.isOnHypixel(); } - + @WrapWithCondition(method = "warnOnUnknownPayload", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V", remap = false)) private boolean skyblocker$dropBadlionPacketWarnings(Logger instance, String message, Object identifier) { return !(Utils.isOnHypixel() && ((Identifier) identifier).getNamespace().equals("badlion")); @@ -64,4 +68,10 @@ public abstract class ClientPlayNetworkHandlerMixin { private void skyblocker$onParticle(ParticleS2CPacket packet, CallbackInfo ci) { MythologicalRitual.onParticle(packet); } + + @ModifyExpressionValue(method = "onEntityStatus", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/EntityStatusS2CPacket;getEntity(Lnet/minecraft/world/World;)Lnet/minecraft/entity/Entity;")) + private Entity skyblocker$onEntityDeath(Entity entity, @Local(argsOnly = true) EntityStatusS2CPacket packet) { + if (packet.getStatus() == EntityStatuses.PLAY_DEATH_SOUND_OR_ADD_PROJECTILE_HIT_PARTICLES) DungeonScore.handleEntityDeath(entity); + return entity; + } } diff --git a/src/main/java/de/hysky/skyblocker/mixin/InGameHudMixin.java b/src/main/java/de/hysky/skyblocker/mixin/InGameHudMixin.java index 0ee7b528..df7cbdea 100644 --- a/src/main/java/de/hysky/skyblocker/mixin/InGameHudMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixin/InGameHudMixin.java @@ -5,9 +5,11 @@ import com.llamalad7.mixinextras.sugar.Local; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.FancyStatusBars; +import de.hysky.skyblocker.skyblock.dungeon.DungeonMap; +import de.hysky.skyblocker.skyblock.dungeon.DungeonScore; +import de.hysky.skyblocker.skyblock.dungeon.DungeonScoreHUD; import de.hysky.skyblocker.skyblock.item.HotbarSlotLock; import de.hysky.skyblocker.skyblock.item.ItemCooldowns; -import de.hysky.skyblocker.skyblock.dungeon.DungeonMap; import de.hysky.skyblocker.skyblock.item.ItemRarityBackgrounds; import de.hysky.skyblocker.utils.Utils; import net.fabricmc.api.EnvType; @@ -64,8 +66,10 @@ public abstract class InGameHudMixin { if (statusBars.render(context, scaledWidth, scaledHeight)) ci.cancel(); - if (Utils.isInDungeons() && SkyblockerConfigManager.get().locations.dungeons.enableMap) - DungeonMap.render(context.getMatrices()); + if (Utils.isInDungeons() && DungeonScore.isDungeonStarted()) { + if (SkyblockerConfigManager.get().locations.dungeons.enableMap) DungeonMap.render(context.getMatrices()); + if (SkyblockerConfigManager.get().locations.dungeons.dungeonScore.enableScoreHUD) DungeonScoreHUD.render(context); + } } @Inject(method = "renderMountHealth", at = @At("HEAD"), cancellable = true) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMap.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMap.java index e1af85ea..293d301f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMap.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMap.java @@ -5,7 +5,6 @@ import de.hysky.skyblocker.utils.scheduler.Scheduler; import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; import net.minecraft.client.render.MapRenderer; import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.util.math.MatrixStack; @@ -13,12 +12,9 @@ import net.minecraft.item.FilledMapItem; import net.minecraft.item.ItemStack; import net.minecraft.item.map.MapState; import net.minecraft.nbt.NbtCompound; -import net.minecraft.util.Identifier; import org.apache.commons.lang3.StringUtils; public class DungeonMap { - private static final Identifier MAP_BACKGROUND = new Identifier("textures/map/map_background.png"); - public static void render(MatrixStack matrices) { MinecraftClient client = MinecraftClient.getInstance(); if (client.player == null || client.world == null) return; @@ -46,13 +42,7 @@ public class DungeonMap { } } - public static void renderHUDMap(DrawContext context, int x, int y) { - float scaling = SkyblockerConfigManager.get().locations.dungeons.mapScaling; - int size = (int) (128 * scaling); - context.drawTexture(MAP_BACKGROUND, x, y, 0, 0, size, size, size, size); - } - - public static void init() { + public static void init() { //Todo: consider renaming the command to a more general name since it'll also have dungeon score and maybe other stuff in the future ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("skyblocker") .then(ClientCommandManager.literal("hud") .then(ClientCommandManager.literal("dungeonmap") diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java index 02b08254..0f42c495 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java @@ -5,13 +5,17 @@ import de.hysky.skyblocker.utils.render.RenderHelper; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.text.Text; +import net.minecraft.util.Identifier; import java.awt.*; public class DungeonMapConfigScreen extends Screen { - private int hudX = SkyblockerConfigManager.get().locations.dungeons.mapX; - private int hudY = SkyblockerConfigManager.get().locations.dungeons.mapY; + private int mapX = SkyblockerConfigManager.get().locations.dungeons.mapX; + private int mapY = SkyblockerConfigManager.get().locations.dungeons.mapY; + private int scoreX = SkyblockerConfigManager.get().locations.dungeons.dungeonScore.scoreX; + private int scoreY = SkyblockerConfigManager.get().locations.dungeons.dungeonScore.scoreY; + private static final Identifier MAP_BACKGROUND = new Identifier("textures/map/map_background.png"); private final Screen parent; protected DungeonMapConfigScreen() { @@ -27,17 +31,23 @@ public class DungeonMapConfigScreen extends Screen { public void render(DrawContext context, int mouseX, int mouseY, float delta) { super.render(context, mouseX, mouseY, delta); renderBackground(context, mouseX, mouseY, delta); - DungeonMap.renderHUDMap(context, hudX, hudY); + renderHUDMap(context, mapX, mapY); + renderHUDScore(context, scoreX, scoreY); context.drawCenteredTextWithShadow(textRenderer, "Right Click To Reset Position", width >> 1, height >> 1, Color.GRAY.getRGB()); } @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - float scaling = SkyblockerConfigManager.get().locations.dungeons.mapScaling; - int size = (int) (128 * scaling); - if (RenderHelper.pointIsInArea(mouseX, mouseY, hudX, hudY, hudX + size, hudY + size) && button == 0) { - hudX = (int) Math.max(Math.min(mouseX - (size >> 1), this.width - size), 0); - hudY = (int) Math.max(Math.min(mouseY - (size >> 1), this.height - size), 0); + int mapSize = (int) (128 * SkyblockerConfigManager.get().locations.dungeons.mapScaling); + float scoreScaling = SkyblockerConfigManager.get().locations.dungeons.dungeonScore.scoreScaling; + int scoreWidth = (int) (textRenderer.getWidth(DungeonScoreHUD.getFormattedScoreText()) * scoreScaling); + int scoreHeight = (int) (textRenderer.fontHeight * scoreScaling); + if (RenderHelper.pointIsInArea(mouseX, mouseY, mapX, mapY, mapX + mapSize, mapY + mapSize) && button == 0) { + mapX = (int) Math.max(Math.min(mouseX - (mapSize >> 1), this.width - mapSize), 0); + mapY = (int) Math.max(Math.min(mouseY - (mapSize >> 1), this.height - mapSize), 0); + } else if (RenderHelper.pointIsInArea(mouseX, mouseY, scoreX, scoreY, scoreX + scoreWidth, scoreY + scoreHeight) && button == 0) { + scoreX = (int) Math.max(Math.min(mouseX - (scoreWidth >> 1), this.width - scoreWidth), 0); + scoreY = (int) Math.max(Math.min(mouseY - (scoreHeight >> 1), this.height - scoreHeight), 0); } return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); } @@ -45,8 +55,10 @@ public class DungeonMapConfigScreen extends Screen { @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (button == 1) { - hudX = 2; - hudY = 2; + mapX = 2; + mapY = 2; + scoreX = Math.max((int) ((mapX + (64 * SkyblockerConfigManager.get().locations.dungeons.mapScaling)) - textRenderer.getWidth(DungeonScoreHUD.getFormattedScoreText()) * SkyblockerConfigManager.get().locations.dungeons.dungeonScore.scoreScaling / 2), 0); + scoreY = (int) (mapY + (128 * SkyblockerConfigManager.get().locations.dungeons.mapScaling) + 4); } return super.mouseClicked(mouseX, mouseY, button); @@ -54,10 +66,22 @@ public class DungeonMapConfigScreen extends Screen { @Override public void close() { - SkyblockerConfigManager.get().locations.dungeons.mapX = hudX; - SkyblockerConfigManager.get().locations.dungeons.mapY = hudY; + SkyblockerConfigManager.get().locations.dungeons.mapX = mapX; + SkyblockerConfigManager.get().locations.dungeons.mapY = mapY; + SkyblockerConfigManager.get().locations.dungeons.dungeonScore.scoreX = scoreX; + SkyblockerConfigManager.get().locations.dungeons.dungeonScore.scoreY = scoreY; SkyblockerConfigManager.save(); this.client.setScreen(parent); } + + public void renderHUDMap(DrawContext context, int x, int y) { + float scaling = SkyblockerConfigManager.get().locations.dungeons.mapScaling; + int size = (int) (128 * scaling); + context.drawTexture(MAP_BACKGROUND, x, y, 0, 0, size, size, size, size); + } + + public void renderHUDScore(DrawContext context, int x, int y) { + DungeonScoreHUD.render(context, x, y); + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java index d67d6988..10605d8b 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java @@ -1,76 +1,389 @@ package de.hysky.skyblocker.skyblock.dungeon; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.events.SkyblockEvents; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; +import de.hysky.skyblocker.utils.ApiUtils; import de.hysky.skyblocker.utils.Constants; +import de.hysky.skyblocker.utils.Http; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.scheduler.MessageScheduler; import de.hysky.skyblocker.utils.scheduler.Scheduler; +import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.Entity; +import net.minecraft.entity.mob.ZombieEntity; +import net.minecraft.item.ItemStack; import net.minecraft.sound.SoundEvents; +import net.minecraft.util.collection.DefaultedList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.StreamSupport; public class DungeonScore { - private static final SkyblockerConfig.DungeonScore CONFIG = SkyblockerConfigManager.get().locations.dungeons.dungeonScore; - private static final Pattern DUNGEON_CLEARED_PATTERN = Pattern.compile("Cleared: (?<cleared>\\d+)% \\((?<score>\\d+)\\)"); - private static boolean sent270; - private static boolean sent300; - - public static void init() { - Scheduler.INSTANCE.scheduleCyclic(DungeonScore::tick, 20); - ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> reset()); - } - - public static void tick() { - MinecraftClient client = MinecraftClient.getInstance(); - if (!Utils.isInDungeons() || client.player == null) { - reset(); - return; - } - - for (String sidebarLine : Utils.STRING_SCOREBOARD) { - Matcher dungeonClearedMatcher = DUNGEON_CLEARED_PATTERN.matcher(sidebarLine); - if (!dungeonClearedMatcher.matches()) { - continue; - } - int score = Integer.parseInt(dungeonClearedMatcher.group("score")); - if (!DungeonManager.isInBoss()) score += 28; - if (!sent270 && score >= 270 && score < 300) { - if (CONFIG.enableDungeonScore270Message) { - MessageScheduler.INSTANCE.sendMessageAfterCooldown(Constants.PREFIX.get().getString() + CONFIG.dungeonScore270Message.replaceAll("\\[score]", "270")); - } - if (CONFIG.enableDungeonScore270Title) { - client.inGameHud.setDefaultTitleFade(); - client.inGameHud.setTitle(Constants.PREFIX.get().append(CONFIG.dungeonScore270Message.replaceAll("\\[score]", "270"))); - } - if (CONFIG.enableDungeonScore270Sound) { - client.player.playSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 100f, 0.1f); - } - sent270 = true; - } - if (!sent300 && score >= 300) { - if (CONFIG.enableDungeonScore300Message) { - MessageScheduler.INSTANCE.sendMessageAfterCooldown(Constants.PREFIX.get().getString() + CONFIG.dungeonScore300Message.replaceAll("\\[score]", "300")); - } - if (CONFIG.enableDungeonScore300Title) { - client.inGameHud.setDefaultTitleFade(); - client.inGameHud.setTitle(Constants.PREFIX.get().append(CONFIG.dungeonScore300Message.replaceAll("\\[score]", "300"))); - } - if (CONFIG.enableDungeonScore300Sound) { - client.player.playSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 100f, 0.1f); - } - sent300 = true; - } - break; - } - } - - private static void reset() { - sent270 = false; - sent300 = false; - } + private static final SkyblockerConfig.DungeonScore SCORE_CONFIG = SkyblockerConfigManager.get().locations.dungeons.dungeonScore; + private static final SkyblockerConfig.MimicMessage MIMIC_MESSAGE_CONFIG = SkyblockerConfigManager.get().locations.dungeons.mimicMessage; + private static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Dungeon Score"); + //Scoreboard patterns + private static final Pattern CLEARED_PATTERN = Pattern.compile("Cleared: (?<cleared>\\d+)%.*"); + private static final Pattern FLOOR_PATTERN = Pattern.compile(".*?(?=T)The Catacombs \\((?<floor>[EFM]\\D*\\d*)\\)"); + //Playerlist patterns + private static final Pattern SECRETS_PATTERN = Pattern.compile("Secrets Found: (?<secper>\\d+\\.?\\d*)%"); + private static final Pattern PUZZLES_PATTERN = Pattern.compile(".+?(?=:): \\[(?<state>.)](?: \\(\\w+\\))?"); + private static final Pattern PUZZLE_COUNT_PATTERN = Pattern.compile("Puzzles: \\((?<count>\\d+)\\)"); + private static final Pattern CRYPTS_PATTERN = Pattern.compile("Crypts: (?<crypts>\\d+)"); + private static final Pattern COMPLETED_ROOMS_PATTERN = Pattern.compile(" *Completed Rooms: (?<rooms>\\d+)"); + //Chat patterns + private static final Pattern DEATHS_PATTERN = Pattern.compile(" \\u2620 (?<whodied>\\S+) .*"); + private static final Pattern MIMIC_PATTERN = Pattern.compile(".*?(?:Mimic dead!?|Mimic Killed!|\\$SKYTILS-DUNGEON-SCORE-MIMIC\\$|\\Q" + MIMIC_MESSAGE_CONFIG.mimicMessage + "\\E)$"); + //Other patterns + private static final Pattern MIMIC_FLOORS_PATTERN = Pattern.compile("[FM][67]"); + + private static FloorRequirement floorRequirement; + private static String currentFloor; + private static boolean isCurrentFloorEntrance; + private static boolean floorHasMimics; + private static boolean sent270; + private static boolean sent300; + private static boolean mimicKilled; + private static boolean dungeonStarted; + private static boolean isMayorPaul; + private static boolean firstDeathHasSpiritPet; + private static boolean bloodRoomCompleted; + private static long startingTime; + private static int puzzleCount; + private static int deathCount; + private static int score; + private static final Map<String, Boolean> SpiritPetCache = new HashMap<>(); + + public static void init() { + Scheduler.INSTANCE.scheduleCyclic(DungeonScore::tick, 20); + SkyblockEvents.LEAVE.register(SpiritPetCache::clear); + ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> reset()); + ClientReceiveMessageEvents.GAME.register((message, overlay) -> { + if (overlay || !Utils.isInDungeons()) return; + String str = message.getString(); + if (!dungeonStarted) { + checkMessageForMort(str); + } else { + checkMessageForDeaths(str); + checkMessageForWatcher(str); + if (floorHasMimics) checkMessageForMimic(str); //Only called when the message is not cancelled & isn't on the action bar, complementing MimicFilter + } + }); + ClientReceiveMessageEvents.GAME_CANCELED.register((message, overlay) -> { + if (overlay || !Utils.isInDungeons() || !dungeonStarted) return; + checkMessageForDeaths(message.getString()); + }); + } + + public static void tick() { + MinecraftClient client = MinecraftClient.getInstance(); + if (!Utils.isInDungeons() || client.player == null) { + reset(); + return; + } + if (!dungeonStarted) return; + + score = calculateScore(); + if (!sent270 && !sent300 && score >= 270 && score < 300) { + if (SCORE_CONFIG.enableDungeonScore270Message) { + MessageScheduler.INSTANCE.sendMessageAfterCooldown("/pc " + Constants.PREFIX.get().getString() + SCORE_CONFIG.dungeonScore270Message.replaceAll("\\[score]", "270")); + } + if (SCORE_CONFIG.enableDungeonScore270Title) { + client.inGameHud.setDefaultTitleFade(); + client.inGameHud.setTitle(Constants.PREFIX.get().append(SCORE_CONFIG.dungeonScore270Message.replaceAll("\\[score]", "270"))); + } + if (SCORE_CONFIG.enableDungeonScore270Sound) { + client.player.playSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 100f, 0.1f); + } + sent270 = true; + } + if (!sent300 && score >= 300) { + if (SCORE_CONFIG.enableDungeonScore300Message) { + MessageScheduler.INSTANCE.sendMessageAfterCooldown("/pc " + Constants.PREFIX.get().getString() + SCORE_CONFIG.dungeonScore300Message.replaceAll("\\[score]", "300")); + } + if (SCORE_CONFIG.enableDungeonScore300Title) { + client.inGameHud.setDefaultTitleFade(); + client.inGameHud.setTitle(Constants.PREFIX.get().append(SCORE_CONFIG.dungeonScore300Message.replaceAll("\\[score]", "300"))); + } + if (SCORE_CONFIG.enableDungeonScore300Sound) { + client.player.playSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 100f, 0.1f); + } + sent300 = true; + } + } + + private static void reset() { + floorRequirement = null; + currentFloor = ""; + isCurrentFloorEntrance = false; + floorHasMimics = false; + sent270 = false; + sent300 = false; + mimicKilled = false; + dungeonStarted = false; + isMayorPaul = false; + firstDeathHasSpiritPet = false; + bloodRoomCompleted = false; + startingTime = 0L; + puzzleCount = 0; + deathCount = 0; + score = 0; + } + + private static void onDungeonStart() { + setCurrentFloor(); + dungeonStarted = true; + puzzleCount = getPuzzleCount(); + isMayorPaul = Utils.getMayor().equals("Paul"); + startingTime = System.currentTimeMillis(); + floorRequirement = FloorRequirement.valueOf(currentFloor); + floorHasMimics = MIMIC_FLOORS_PATTERN.matcher(currentFloor).matches(); + if (currentFloor.equals("E")) isCurrentFloorEntrance = true; + } + + private static int calculateScore() { + if (isCurrentFloorEntrance) return Math.round(calculateTimeScore() * 0.7f) + Math.round(calculateExploreScore() * 0.7f) + Math.round(calculateSkillScore() * 0.7f) + Math.round(calculateBonusScore() * 0.7f); + return calculateTimeScore() + calculateExploreScore() + calculateSkillScore() + calculateBonusScore(); + } + + private static int calculateSkillScore() { + int totalRooms = getTotalRooms(); //This is necessary to avoid division by 0 at the start of dungeons, which results in infinite score + return 20 + Math.max((totalRooms != 0 ? (int) (80.0 * (getCompletedRooms() + getExtraCompletedRooms()) / totalRooms) : 0) - getPuzzlePenalty() - getDeathScorePenalty(), 0); //Can't go below 20 skill score + } + + private static int calculateExploreScore() { + int totalRooms = getTotalRooms(); //This is necessary to avoid division by 0 at the start of dungeons, which results in infinite score + int completedRoomScore = totalRooms != 0 ? (int) (60.0 * (getCompletedRooms() + getExtraCompletedRooms()) / totalRooms) : 0; + int secretsScore = (int) (40 * Math.min(floorRequirement.percentage, getSecretsPercentage()) / floorRequirement.percentage); + return Math.max(completedRoomScore + secretsScore, 0); + } + + private static int calculateTimeScore() { + int score = 100; + int timeSpent = (int) (System.currentTimeMillis() - startingTime) / 1000; + if (timeSpent < floorRequirement.timeLimit) return score; + + double timePastRequirement = ((double) (timeSpent - floorRequirement.timeLimit) / floorRequirement.timeLimit) * 100; + if (timePastRequirement < 20) return score - (int) timePastRequirement / 2; + if (timePastRequirement < 40) return score - (int) (10 + (timePastRequirement - 20) / 4); + if (timePastRequirement < 50) return score - (int) (15 + (timePastRequirement - 40) / 5); + if (timePastRequirement < 60) return score - (int) (17 + (timePastRequirement - 50) / 6); + return Math.max(score - (int) (18 + (2.0 / 3.0) + (timePastRequirement - 60) / 7), 0); //This can theoretically go down to -20 if the time limit is one of the lower ones like 480, but individual score categories can't go below 0 + } + + private static int calculateBonusScore() { + int paulScore = isMayorPaul ? 10 : 0; + int cryptsScore = Math.min(getCrypts(), 5); + int mimicScore = mimicKilled ? 2 : 0; + if (getSecretsPercentage() >= 100 && floorHasMimics) mimicScore = 2; //If mimic kill is not announced but all secrets are found, mimic must've been killed + return paulScore + cryptsScore + mimicScore; + } + + public static boolean isEntityMimic(Entity entity) { + if (!Utils.isInDungeons() || !floorHasMimics || !(entity instanceof ZombieEntity zombie) || !zombie.isBaby()) return false; + try { + DefaultedList<ItemStack> armor = (DefaultedList<ItemStack>) zombie.getArmorItems(); + return armor.stream().allMatch(ItemStack::isEmpty); + } catch (Exception e) { + LOGGER.error("[Skyblocker] Failed to check if entity is a mimic!", e); + return false; + } + } + + public static void handleEntityDeath(Entity entity) { + if (mimicKilled) return; + if (!isEntityMimic(entity)) return; + if (MIMIC_MESSAGE_CONFIG.sendMimicMessage) MessageScheduler.INSTANCE.sendMessageAfterCooldown(MIMIC_MESSAGE_CONFIG.mimicMessage); + mimicKilled = true; + } + + public static void onMimicKill() { + mimicKilled = true; + } + + //This is not very accurate at the beginning of the dungeon since clear percentage is rounded to the closest integer, so at lower percentages its effect on the result is quite high. + //For example: If clear percentage is 7% with a single room completed, it can be rounded from 6.5 or 7.49. In that range, the actual total room count can be either 14 or 15 while our result is 14. + //Score might fluctuate at first if the total room amount calculated changes as it gets more accurate with each room completed. + private static int getTotalRooms() { + return (int) Math.round(getCompletedRooms() / getClearPercentage()); + } + + private static int getCompletedRooms() { + Matcher matcher = PlayerListMgr.regexAt(43, COMPLETED_ROOMS_PATTERN); + return matcher != null ? Integer.parseInt(matcher.group("rooms")) : 0; + } + + //This is needed for calculating the score before going in the boss room & completing the blood room, so we have the result sooner + //It might cause score to fluctuate when completing the blood room or entering the boss room as there's a slight delay between the room being completed (boolean set to true) and the scoreboard updating + private static int getExtraCompletedRooms() { + if (!bloodRoomCompleted) return isCurrentFloorEntrance ? 1 : 2; + if (!DungeonManager.isInBoss() && !isCurrentFloorEntrance) return 1; + return 0; + } + + private static double getClearPercentage() { + for (String sidebarLine : Utils.STRING_SCOREBOARD) { + Matcher clearMatcher = CLEARED_PATTERN.matcher(sidebarLine); + if (!clearMatcher.matches()) continue; + return Double.parseDouble(clearMatcher.group("cleared")) / 100.0; + } + LOGGER.error("[Skyblocker] Clear pattern doesn't match!"); + return 0; + } + + //Score might fluctuate when the first death has spirit pet as the boolean will be set to true after getting a response from the api, which might take a while + private static int getDeathScorePenalty() { + return deathCount * 2 - (firstDeathHasSpiritPet ? 1 : 0); + } + + private static int getPuzzleCount() { + Matcher matcher = PlayerListMgr.regexAt(47, PUZZLE_COUNT_PATTERN); + return matcher != null ? Integer.parseInt(matcher.group("count")) : 0; + } + + private static int getPuzzlePenalty() { + int incompletePuzzles = 0; + for (int index = 0; index < puzzleCount; index++) { + Matcher puzzleMatcher = PlayerListMgr.regexAt(48 + index, PUZZLES_PATTERN); + if (puzzleMatcher == null) break; + if (puzzleMatcher.group("state").matches("[✖✦]")) incompletePuzzles++; + } + return incompletePuzzles * 10; + } + + private static double getSecretsPercentage() { + Matcher matcher = PlayerListMgr.regexAt(44, SECRETS_PATTERN); + return matcher != null ? Double.parseDouble(matcher.group("secper")) : 0; + } + + private static int getCrypts() { + Matcher matcher = PlayerListMgr.regexAt(33, CRYPTS_PATTERN); + if (matcher == null) matcher = PlayerListMgr.regexAt(32, CRYPTS_PATTERN); //If class milestone 9 is reached, crypts goes up by 1 + return matcher != null ? Integer.parseInt(matcher.group("crypts")) : 0; + } + + public static boolean hasSpiritPet(String name) { + return SpiritPetCache.computeIfAbsent(name, k -> { + String playeruuid = ApiUtils.name2Uuid(name); + try (Http.ApiResponse response = Http.sendHypixelRequest("skyblock/profiles", "?uuid=" + playeruuid)) { + if (!response.ok()) throw new IllegalStateException("Failed to get profile uuid for player " + name + "! Response: " + response.content()); + JsonObject responseJson = JsonParser.parseString(response.content()).getAsJsonObject(); + + JsonObject player = StreamSupport.stream(responseJson.getAsJsonArray("profiles").spliterator(), false) + .map(JsonElement::getAsJsonObject) + .filter(profile -> profile.getAsJsonPrimitive("selected").getAsBoolean()) + .findFirst() + .orElseThrow(() -> new IllegalStateException("No selected profile found!?")) + .getAsJsonObject("members").entrySet().stream() + .filter(entry -> entry.getKey().equals(playeruuid)) + .map(Map.Entry::getValue) + .map(JsonElement::getAsJsonObject) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Player somehow not found inside their own profile!")); + + for (JsonElement element : player.getAsJsonObject("pets_data").getAsJsonArray("pets")) { + if (!element.getAsJsonObject().get("type").getAsString().equals("SPIRIT")) continue; + if (!element.getAsJsonObject().get("tier").getAsString().equals("LEGENDARY")) continue; + + return true; + } + } catch (Exception e) { + LOGGER.error("[Skyblocker] Spirit pet lookup by name failed! Name: {}", name, e); + } + return false; + }); + } + + private static void checkMessageForDeaths(String message) { + if (!message.startsWith("\u2620", 1)) return; + Matcher matcher = DEATHS_PATTERN.matcher(message); + if (!matcher.matches()) return; + deathCount++; + if (deathCount > 1) return; + final String whoDied = matcher.group("whodied").transform(s -> { + if (s.equals("You")) return MinecraftClient.getInstance().player.getName().getString(); //This will be wrong if the dead player is called 'You' but that's unlikely + else return s; + }); + CompletableFuture.supplyAsync(() -> hasSpiritPet(whoDied)) + .thenAccept(hasSpiritPet -> firstDeathHasSpiritPet = hasSpiritPet); + } + + private static void checkMessageForWatcher(String message) { + if (message.equals("[BOSS] The Watcher: You have proven yourself. You may pass.")) bloodRoomCompleted = true; + } + + private static void checkMessageForMort(String message) { + if (!message.equals("§e[NPC] §bMort§f: You should find it useful if you get lost.")) return; + onDungeonStart(); + } + + private static void checkMessageForMimic(String message) { + if (!MIMIC_PATTERN.matcher(message).matches()) return; + onMimicKill(); + } + + public static void setCurrentFloor() { + for (String sidebarLine : Utils.STRING_SCOREBOARD) { + Matcher floorMatcher = FLOOR_PATTERN.matcher(sidebarLine); + if (!floorMatcher.matches()) continue; + currentFloor = floorMatcher.group("floor"); + return; + } + LOGGER.error("[Skyblocker] Floor pattern doesn't match!"); + } + + public static int getScore() { + return score; + } + + public static boolean isDungeonStarted() { + return dungeonStarted; + } + + //Feel free to refactor this if you can think of a better name. + public static boolean isMimicOnCurrentFloor() { + return floorHasMimics; + } + + enum FloorRequirement { + E(30, 1200), + F1(30, 600), + F2(40, 600), + F3(50, 600), + F4(60, 720), + F5(70, 600), + F6(85, 720), + F7(100, 840), + M1(100, 480), + M2(100, 480), + M3(100, 480), + M4(100, 480), + M5(100, 480), + M6(100, 600), + M7(100, 840); + + private final int percentage; + private final int timeLimit; + + FloorRequirement(int percentage, int timeLimit) { + this.percentage = percentage; + this.timeLimit = timeLimit; + } + } } + diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScoreHUD.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScoreHUD.java new file mode 100644 index 00000000..1dfb1b95 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScoreHUD.java @@ -0,0 +1,44 @@ +package de.hysky.skyblocker.skyblock.dungeon; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +public class DungeonScoreHUD { + private DungeonScoreHUD() { + } + + //This is 4+5 wide, needed to offset the extra width from bold numbers (3×1 wide) in S+ and the "+" (6 wide) so that it doesn't go off the screen if the score is S+ and the hud element is at the right edge of the screen + private static final Text extraSpace = Text.literal(" ").append(Text.literal(" ").formatted(Formatting.BOLD)); + + public static void render(DrawContext context) { + int x = SkyblockerConfigManager.get().locations.dungeons.dungeonScore.scoreX; + int y = SkyblockerConfigManager.get().locations.dungeons.dungeonScore.scoreY; + render(context, x, y); + } + + public static void render(DrawContext context, int x, int y) { + float scale = SkyblockerConfigManager.get().locations.dungeons.dungeonScore.scoreScaling; + MatrixStack matrixStack = context.getMatrices(); + matrixStack.push(); + matrixStack.scale(scale, scale, 0); + context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, getFormattedScoreText(), (int) (x / scale), (int) (y / scale), 0xFFFFFFFF); + matrixStack.pop(); + } + + public static Text getFormattedScoreText() { + return Text.translatable("skyblocker.dungeons.dungeonScore.scoreText", formatScore(DungeonScore.getScore())); + } + + private static Text formatScore(int score) { + if (score < 100) return Text.literal(String.format("%03d", score)).withColor(0xDC1A1A).append(Text.literal(" (D)").formatted(Formatting.GRAY)).append(extraSpace); + if (score < 160) return Text.literal(String.format("%03d", score)).withColor(0x4141FF).append(Text.literal(" (C)").formatted(Formatting.GRAY)).append(extraSpace); + if (score < 230) return Text.literal(String.format("%03d", score)).withColor(0x7FCC19).append(Text.literal(" (B)").formatted(Formatting.GRAY)).append(extraSpace); + if (score < 270) return Text.literal(String.format("%03d", score)).withColor(0x7F3FB2).append(Text.literal(" (A)").formatted(Formatting.GRAY)).append(extraSpace); + if (score < 300) return Text.literal(String.format("%03d", score)).withColor(0xF1E252).append(Text.literal(" (S)").formatted(Formatting.GRAY)).append(extraSpace); + return Text.literal("").append(Text.literal(String.format("%03d", score)).withColor(0xF1E252).formatted(Formatting.BOLD)).append(Text.literal(" (S+)").formatted(Formatting.GRAY)); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/filters/DeathFilter.java b/src/main/java/de/hysky/skyblocker/skyblock/filters/DeathFilter.java new file mode 100644 index 00000000..f2b9e7c5 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/filters/DeathFilter.java @@ -0,0 +1,25 @@ +package de.hysky.skyblocker.skyblock.filters; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.chat.ChatFilterResult; +import de.hysky.skyblocker.utils.chat.ChatPatternListener; +import net.minecraft.text.Text; + +import java.util.regex.Matcher; + +public class DeathFilter extends ChatPatternListener { + + public DeathFilter() { + super(" \\u2620 .*"); + } + + @Override + protected ChatFilterResult state() { + return SkyblockerConfigManager.get().messages.hideDeath; + } + + @Override + protected boolean onMatch(Text message, Matcher matcher) { + return true; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/filters/MimicFilter.java b/src/main/java/de/hysky/skyblocker/skyblock/filters/MimicFilter.java new file mode 100644 index 00000000..cb845254 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/filters/MimicFilter.java @@ -0,0 +1,28 @@ +package de.hysky.skyblocker.skyblock.filters; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.dungeon.DungeonScore; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.chat.ChatFilterResult; +import de.hysky.skyblocker.utils.chat.ChatPatternListener; +import net.minecraft.text.Text; + +import java.util.regex.Matcher; + +public class MimicFilter extends ChatPatternListener { + public MimicFilter() { + super(".*?(?:Mimic dead!?|Mimic Killed!|\\$SKYTILS-DUNGEON-SCORE-MIMIC\\$|\\Q" + SkyblockerConfigManager.get().locations.dungeons.mimicMessage.mimicMessage + "\\E)$"); + } + + @Override + public ChatFilterResult state() { + return SkyblockerConfigManager.get().messages.hideMimicKill; + } + + @Override + protected boolean onMatch(Text message, Matcher matcher) { + if (!Utils.isInDungeons() || !DungeonScore.isDungeonStarted() || !DungeonScore.isMimicOnCurrentFloor()) return false; + DungeonScore.onMimicKill(); //Only called when the message is cancelled | sent to action bar, complementing DungeonScore#checkMessageForMimic + return true; + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java index 45ace085..53c0ff4a 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Utils.java +++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java @@ -24,8 +24,10 @@ import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; /** * Utility variables and methods for retrieving Skyblock related information. @@ -64,6 +66,8 @@ public class Utils { private static boolean sentLocRaw = false; private static boolean canSendLocRaw = false; + private static String mayor = ""; + /** * @implNote The parent text will always be empty, the actual text content is inside the text's siblings. */ @@ -135,7 +139,16 @@ public class Utils { return map; } + /** + * @return the current mayor as cached on skyblock join. + */ + @NotNull + public static String getMayor() { + return mayor; + } + public static void init() { + SkyblockEvents.JOIN.register(Utils::initializeMayorCache); ClientPlayConnectionEvents.JOIN.register(Utils::onClientWorldJoin); ClientReceiveMessageEvents.ALLOW_GAME.register(Utils::onChatMessage); ClientReceiveMessageEvents.GAME_CANCELED.register(Utils::onChatMessage); // Somehow this works even though onChatMessage returns a boolean @@ -381,4 +394,21 @@ public class Utils { locationRaw = ""; map = ""; } + + private static void initializeMayorCache() { + if (!mayor.isEmpty()) return; + CompletableFuture.supplyAsync(() -> { + try { + JsonObject json = JsonParser.parseString(Http.sendGetRequest("https://api.hypixel.net/v2/resources/skyblock/election")).getAsJsonObject(); + if (json.get("success").getAsBoolean()) return json.get("mayor").getAsJsonObject().get("name").getAsString(); + throw new IOException(json.get("cause").getAsString()); + } catch (Exception e) { + LOGGER.error("[Skyblocker] Failed to get mayor status!", e); + } + return ""; + }).thenAccept(s -> { + if (!s.isEmpty()) mayor = s; + }); + + } } diff --git a/src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java b/src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java index ebdb6f09..ee43bc4c 100644 --- a/src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java +++ b/src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java @@ -1,7 +1,5 @@ package de.hysky.skyblocker.utils.chat; -import de.hysky.skyblocker.skyblock.filters.*; -import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.skyblock.barn.HungryHiker; import de.hysky.skyblocker.skyblock.barn.TreasureHunter; import de.hysky.skyblocker.skyblock.dungeon.Reparty; @@ -9,6 +7,8 @@ import de.hysky.skyblocker.skyblock.dungeon.puzzle.ThreeWeirdos; import de.hysky.skyblocker.skyblock.dungeon.puzzle.Trivia; import de.hysky.skyblocker.skyblock.dwarven.Fetchur; import de.hysky.skyblocker.skyblock.dwarven.Puzzler; +import de.hysky.skyblocker.skyblock.filters.*; +import de.hysky.skyblocker.utils.Utils; import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.EventFactory; @@ -54,7 +54,9 @@ public interface ChatMessageListener { new TeleportPadFilter(), new AutopetFilter(), new ShowOffFilter(), - new ToggleSkyMallFilter() + new ToggleSkyMallFilter(), + new MimicFilter(), + new DeathFilter() }; // Register all listeners to EVENT for (ChatMessageListener listener : listeners) { diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index dfc0c035..1360f5f1 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -200,6 +200,10 @@ "text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableDungeonScoreSound.@Tooltip": "Plays a sound when reaching %d score in dungeons.", "text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.dungeonScoreMessage": "Dungeon Score %d Message", "text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.dungeonScoreMessage.@Tooltip": "Message which will be sent in the chat when reaching %d score in dungeons. The string \"[score]\" will be replaced with the dungeon score (%d).", + "text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableScoreHUD": "Enable Score HUD", + "text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableScoreHUD.@Tooltip": "Displays the dungeon score in the HUD.", + "text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableScoreHUD.deathMessagesNote": "\n\n\nNote: This only works correctly if death messages are enabled in your skyblock settings. If you want to hide death messages, use this mod's Hide Player Death Messages setting instead to allow further processing of death messages.", + "text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.scoreScaling": "Score Scaling", "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.", @@ -215,7 +219,7 @@ "text.autoconfig.skyblocker.option.locations.dungeons.croesusHelper": "Croesus Helper", "text.autoconfig.skyblocker.option.locations.dungeons.croesusHelper.@Tooltip": "Gray out chests that have already been opened.", "text.autoconfig.skyblocker.option.locations.dungeons.enableMap": "Enable Map", - "text.autoconfig.skyblocker.option.locations.dungeons.mapScreen": "Dungeon Map Placement Config...", + "text.autoconfig.skyblocker.option.locations.dungeons.mapScreen": "Dungeon Map & Score Placement Config...", "text.autoconfig.skyblocker.option.locations.dungeons.mapScaling": "Map Scaling", "text.autoconfig.skyblocker.option.locations.dungeons.playerSecretsTracker": "Player Secrets Tracker", "text.autoconfig.skyblocker.option.locations.dungeons.playerSecretsTracker.@Tooltip": "Tracks the amount of secrets people in your dungeon run are doing.", @@ -231,6 +235,11 @@ "text.autoconfig.skyblocker.option.locations.dungeons.solveTicTacToe.@Tooltip": "Puts a red box around the next best move for you to make!", "text.autoconfig.skyblocker.option.locations.dungeons.solveWaterboard": "Solve Waterboard Puzzle", "text.autoconfig.skyblocker.option.locations.dungeons.solveWaterboard.@Tooltip": "Click the levers with green boxes to solve the puzzle.", + "text.autoconfig.skyblocker.option.locations.dungeons.mimicMessage": "Mimic Message", + "text.autoconfig.skyblocker.option.locations.dungeons.mimicMessage.sendMimicMessage": "Enable Mimic Message", + "text.autoconfig.skyblocker.option.locations.dungeons.mimicMessage.sendMimicMessage.@Tooltip": "Sends a message in chat upon killing a mimic for other players' score calculation mods.", + "text.autoconfig.skyblocker.option.locations.dungeons.mimicMessage.mimicMessage": "Mimic Message", + "text.autoconfig.skyblocker.option.locations.dungeons.mimicMessage.mimicMessage.@Tooltip": "Message which will be sent in the chat upon killing a mimic. Recommended to retain the default value.", "text.autoconfig.skyblocker.option.locations.dungeons.lividColor": "Livid Color", "text.autoconfig.skyblocker.option.locations.dungeons.lividColor.enableLividColorGlow": "Enable Livid Color Glow", "text.autoconfig.skyblocker.option.locations.dungeons.lividColor.enableLividColorGlow.@Tooltip": "Applies the glowing effect to the correct Livid in F5/M5.", @@ -294,6 +303,10 @@ "text.autoconfig.skyblocker.option.messages.hideShowOff.@Tooltip": "Filters messages from the /show command", "text.autoconfig.skyblocker.option.messages.hideToggleSkyMall": "Hide Toggle Sky Mall Messages", "text.autoconfig.skyblocker.option.messages.hideToggleSkyMall.@Tooltip": "Hides those pesky messages telling you to disable the Sky Mall HOTM perk when you want it enabled!", + "text.autoconfig.skyblocker.option.messages.hideMimicKill": "Hide Mimic Kill Messages", + "text.autoconfig.skyblocker.option.messages.hideMimicKill.@Tooltip": "Filters the \"Mimic dead!\" and \"Mimic killed!\" messages from chat.", + "text.autoconfig.skyblocker.option.messages.hideDeath": "Hide Player Death Messages", + "text.autoconfig.skyblocker.option.messages.hideDeath.@Tooltip": "Filters the player death messages from chat.", "text.autoconfig.skyblocker.category.slayer": "Slayers", "text.autoconfig.skyblocker.option.slayer.vampireSlayer": "Vampire Slayer", "text.autoconfig.skyblocker.option.slayer.vampireSlayer.enableEffigyWaypoints": "Enable Effigy Waypoints", @@ -331,6 +344,7 @@ "skyblocker.dungeons.secrets.customWaypointAdded": "§rAdded a custom waypoint at X: %d, Y: %d, Z: %d for room %s secret #%d of category %s with name '%s'.", "skyblocker.dungeons.secrets.customWaypointRemoved": "§rRemoved custom waypoint at X: %d, Y: %d, Z: %d for room %s secret #%d of category %s with name '%s'.", "skyblocker.dungeons.secrets.customWaypointNotFound": "§cNo custom waypoint found at X: %d, Y: %d, Z: %d for room %s.", + "skyblocker.dungeons.dungeonScore.scoreText": "Score: %s", "skyblocker.dungeons.secretsTracker.feedback": "%s§f found %s§f secrets. %s", "skyblocker.dungeons.secretsTracker.failFeedback": "§cUnable to calculate the amount of secrets everybody did this run!", |