From 87608092e908fad427e36fd8207a3b816c16c9e4 Mon Sep 17 00:00:00 2001 From: nmccullagh Date: Mon, 19 Aug 2024 00:58:35 +0100 Subject: [PATCH] [Slayers] Adds Bossbars @ --- .../skyblocker/mixins/BossBarHudMixin.java | 41 ++++++++ .../crimson/slayer/AttunementColors.java | 2 +- .../crimson/slayer/FirePillarAnnouncer.java | 4 +- .../skyblock/rift/ManiaIndicator.java | 4 +- .../skyblock/rift/StakeIndicator.java | 2 +- .../skyblock/rift/TwinClawsIndicator.java | 4 +- .../skyblock/slayers/SlayerBossBars.java | 94 +++++++++++++++++++ .../skyblock/slayers/SlayerEntitiesGlow.java | 2 +- .../hysky/skyblocker/utils/SlayerUtils.java | 21 +++-- src/main/resources/skyblocker.mixins.json | 1 + 10 files changed, 158 insertions(+), 17 deletions(-) create mode 100644 src/main/java/de/hysky/skyblocker/mixins/BossBarHudMixin.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerBossBars.java diff --git a/src/main/java/de/hysky/skyblocker/mixins/BossBarHudMixin.java b/src/main/java/de/hysky/skyblocker/mixins/BossBarHudMixin.java new file mode 100644 index 0000000000..e307291bad --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/mixins/BossBarHudMixin.java @@ -0,0 +1,41 @@ +package de.hysky.skyblocker.mixins; + +import de.hysky.skyblocker.skyblock.slayers.SlayerBossBars; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.hud.BossBarHud; +import net.minecraft.client.gui.hud.ClientBossBar; +import net.minecraft.entity.boss.BossBar; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(BossBarHud.class) +public abstract class BossBarHudMixin { + + @Final + @Shadow + private MinecraftClient client; + + @Shadow + protected abstract void renderBossBar(DrawContext context, int x, int y, BossBar bossBar); + + @Inject(method = "render", at = @At("HEAD"), cancellable = true) + private void onRender(DrawContext context, CallbackInfo ci) { + + if (SlayerBossBars.shouldRenderBossBar()) { + ClientBossBar bar = SlayerBossBars.getBossBar(); + + int textWidth = this.client.textRenderer.getWidth(bar.getName()); + context.drawTextWithShadow(this.client.textRenderer, bar.getName(), context.getScaledWindowWidth() / 2 - textWidth / 2, 3, 16777215); + + this.renderBossBar(context, (context.getScaledWindowWidth() / 2) - 91, 12, bar); + + ci.cancel(); + } + + } +} \ No newline at end of file diff --git a/src/main/java/de/hysky/skyblocker/skyblock/crimson/slayer/AttunementColors.java b/src/main/java/de/hysky/skyblocker/skyblock/crimson/slayer/AttunementColors.java index ba94812a2a..6612d97805 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/crimson/slayer/AttunementColors.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/crimson/slayer/AttunementColors.java @@ -17,7 +17,7 @@ public class AttunementColors { */ public static int getColor(LivingEntity e) { if (!SkyblockerConfigManager.get().slayers.blazeSlayer.attunementHighlights) return 0xf57738; - for (Entity entity : SlayerUtils.getEntityArmorStands(e)) { + for (Entity entity : SlayerUtils.getEntityArmorStands(e, 2.5f)) { Matcher matcher = COLOR_PATTERN.matcher(entity.getDisplayName().getString()); if (matcher.find()) { String matchedColour = matcher.group(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/crimson/slayer/FirePillarAnnouncer.java b/src/main/java/de/hysky/skyblocker/skyblock/crimson/slayer/FirePillarAnnouncer.java index d232809674..3d977a4094 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/crimson/slayer/FirePillarAnnouncer.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/crimson/slayer/FirePillarAnnouncer.java @@ -10,8 +10,6 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.entity.Entity; import net.minecraft.entity.decoration.ArmorStandEntity; -import net.minecraft.text.MutableText; -import net.minecraft.text.PlainTextContent; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import java.util.regex.Matcher; @@ -42,7 +40,7 @@ public static void checkFirePillar(Entity entity) { // There is an edge case where the slayer has entered demon phase and temporarily despawned with // an active fire pillar in play, So fallback to the player - Entity referenceEntity = SlayerUtils.getSlayerEntity(); + Entity referenceEntity = SlayerUtils.getSlayerArmorstandEntity(); if (!(referenceEntity != null ? referenceEntity : MinecraftClient.getInstance().player).getBlockPos().isWithinDistance(entity.getPos(), 22)) return; announceFirePillarDetails(entityName); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/rift/ManiaIndicator.java b/src/main/java/de/hysky/skyblocker/skyblock/rift/ManiaIndicator.java index 54ea909395..0563f45220 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/rift/ManiaIndicator.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/rift/ManiaIndicator.java @@ -22,11 +22,11 @@ protected static void updateMania() { return; } - Entity slayerEntity = SlayerUtils.getSlayerEntity(); + Entity slayerEntity = SlayerUtils.getSlayerArmorstandEntity(); if (slayerEntity == null) return; boolean anyMania = false; - for (Entity entity : SlayerUtils.getEntityArmorStands(slayerEntity)) { + for (Entity entity : SlayerUtils.getEntityArmorStands(slayerEntity, 2.5f)) { if (entity.getDisplayName().toString().contains("MANIA")) { anyMania = true; BlockPos pos = MinecraftClient.getInstance().player.getBlockPos().down(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/rift/StakeIndicator.java b/src/main/java/de/hysky/skyblocker/skyblock/rift/StakeIndicator.java index 54bee5ec68..7489b22305 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/rift/StakeIndicator.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/rift/StakeIndicator.java @@ -17,7 +17,7 @@ protected static void updateStake() { TitleContainer.removeTitle(title); return; } - Entity slayerEntity = SlayerUtils.getSlayerEntity(); + Entity slayerEntity = SlayerUtils.getSlayerArmorstandEntity(); if (slayerEntity != null && slayerEntity.getDisplayName().toString().contains("҉")) { RenderHelper.displayInTitleContainerAndPlaySound(title); } else { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/rift/TwinClawsIndicator.java b/src/main/java/de/hysky/skyblocker/skyblock/rift/TwinClawsIndicator.java index f6952ab499..7646e69304 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/rift/TwinClawsIndicator.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/rift/TwinClawsIndicator.java @@ -20,11 +20,11 @@ protected static void updateIce() { return; } - Entity slayerEntity = SlayerUtils.getSlayerEntity(); + Entity slayerEntity = SlayerUtils.getSlayerArmorstandEntity(); if (slayerEntity == null) return; boolean anyClaws = false; - for (Entity entity : SlayerUtils.getEntityArmorStands(slayerEntity)) { + for (Entity entity : SlayerUtils.getEntityArmorStands(slayerEntity, 2.5f)) { if (entity.getDisplayName().toString().contains("TWINCLAWS")) { anyClaws = true; if (!TitleContainer.containsTitle(title) && !scheduled) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerBossBars.java b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerBossBars.java new file mode 100644 index 0000000000..3b481fe07b --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerBossBars.java @@ -0,0 +1,94 @@ +package de.hysky.skyblocker.skyblock.slayers; + +import de.hysky.skyblocker.utils.SlayerUtils; +import net.minecraft.client.gui.hud.ClientBossBar; +import net.minecraft.entity.boss.BossBar; +import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.text.Text; + +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SlayerBossBars { + + private static final Pattern HEALTH_PATTERN = Pattern.compile("(\\d+(?:\\.\\d+)?[kM]?)(?=❤)"); + private static int bossMaxHealth = -1; + private static long lastUpdateTime = 0; + private static final long UPDATE_INTERVAL = 400; + private static ClientBossBar bossBar; + public static final UUID uuid = UUID.randomUUID(); + + public static boolean shouldRenderBossBar() { + long currentTime = System.currentTimeMillis(); + if (currentTime - lastUpdateTime < UPDATE_INTERVAL) { + return bossBar != null; + } + + lastUpdateTime = currentTime; + + if (!SlayerUtils.isInSlayer()) { + bossMaxHealth = -1; + bossBar = null; + return false; + } + + if (SlayerUtils.getSlayerArmorstandEntity() != null && bossMaxHealth == -1) { + Matcher maxHealthMatcher = HEALTH_PATTERN.matcher(SlayerUtils.getSlayerArmorstandEntity().getDisplayName().getString()); + if (maxHealthMatcher.find()) bossMaxHealth = convertToInt(maxHealthMatcher.group(0)); + } + + return bossBar != null || SlayerUtils.getSlayerArmorstandEntity() != null; + } + + public static ClientBossBar getBossBar() { + ArmorStandEntity slayer = SlayerUtils.getSlayerArmorstandEntity(); + if (bossBar == null) bossBar = new ClientBossBar(uuid, slayer != null ? slayer.getDisplayName() : Text.of("Attempting to Locate Slayer..."), 1f, BossBar.Color.PURPLE, BossBar.Style.PROGRESS, false, false, false); + + if (slayer == null) { + if (SlayerUtils.isInSlayer()){ + bossBar.setStyle(BossBar.Style.PROGRESS); + bossBar.setColor(BossBar.Color.RED); + } + return bossBar; + } + + Matcher healthMatcher = HEALTH_PATTERN.matcher(slayer.getCustomName().getString()); + if (healthMatcher.find() && slayer.isAlive()) { + bossBar.setPercent(bossMaxHealth == -1 ? 1f : (float) convertToInt(healthMatcher.group(1)) / bossMaxHealth); + bossBar.setColor(BossBar.Color.PINK); + bossBar.setName(slayer.getDisplayName()); + bossBar.setStyle(BossBar.Style.NOTCHED_10); + } else { + bossBar.setColor(BossBar.Color.RED); + bossBar.setStyle(BossBar.Style.PROGRESS); + bossBar.setName(slayer.getDisplayName()); + } + + return bossBar; + } + + private static int convertToInt(String value) { + if (value == null || value.isEmpty()) { + return 0; + } + + value = value.trim().toLowerCase(); + double multiplier = 1.0; + + if (value.endsWith("m")) { + multiplier = 1_000_000; + value = value.substring(0, value.length() - 1); + } else if (value.endsWith("k")) { + multiplier = 1_000; + value = value.substring(0, value.length() - 1); + } + + try { + double numericValue = Double.parseDouble(value); + return (int) (numericValue * multiplier); + } catch (NumberFormatException e) { + return 0; + } + } +} \ No newline at end of file diff --git a/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerEntitiesGlow.java b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerEntitiesGlow.java index 79f91bfbfd..c1b8a156b0 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerEntitiesGlow.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerEntitiesGlow.java @@ -52,7 +52,7 @@ public static boolean shouldGlow(UUID entityUUID) { } public static boolean isSlayer(LivingEntity e) { - return SlayerUtils.isInSlayer() && SlayerUtils.getEntityArmorStands(e).stream().anyMatch(entity -> + return SlayerUtils.isInSlayer() && SlayerUtils.getEntityArmorStands(e, 2.5f).stream().anyMatch(entity -> entity.getDisplayName().getString().contains(MinecraftClient.getInstance().getSession().getUsername())); } diff --git a/src/main/java/de/hysky/skyblocker/utils/SlayerUtils.java b/src/main/java/de/hysky/skyblocker/utils/SlayerUtils.java index 363de85a8f..b7f45d0544 100644 --- a/src/main/java/de/hysky/skyblocker/utils/SlayerUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/SlayerUtils.java @@ -12,6 +12,7 @@ //TODO Slayer Packet system that can provide information about the current slayer boss, abstract so that different bosses can have different info public class SlayerUtils { + private static ArmorStandEntity slayerArmorstandEntity; public static final String REVENANT = "Revenant Horror"; public static final String TARA = "Tarantula Broodfather"; public static final String SVEN = "Sven Packmaster"; @@ -19,15 +20,19 @@ public class SlayerUtils { public static final String VAMPIRE = "Riftstalker Bloodfiend"; public static final String DEMONLORD = "Inferno Demonlord"; private static final Logger LOGGER = LoggerFactory.getLogger(SlayerUtils.class); - private static final Pattern SLAYER_PATTERN = Pattern.compile("Revenant Horror|Tarantula Broodfather|Sven Packmaster|Voidgloom Seraph|Inferno Demonlord|Riftstalker Bloodfiend"); + public static final Pattern SLAYER_PATTERN = Pattern.compile("Revenant Horror|Tarantula Broodfather|Sven Packmaster|Voidgloom Seraph|Inferno Demonlord|Riftstalker Bloodfiend"); //TODO: Cache this, probably included in Packet system - public static List getEntityArmorStands(Entity entity) { - return entity.getEntityWorld().getOtherEntities(entity, entity.getBoundingBox().expand(1F, 2.5F, 1F), x -> x instanceof ArmorStandEntity && x.hasCustomName()); + public static List getEntityArmorStands(Entity entity, float scanHeight) { + return entity.getEntityWorld().getOtherEntities(entity, entity.getBoundingBox().expand(1F, scanHeight, 1F), x -> x instanceof ArmorStandEntity && x.hasCustomName()); } //Eventually this should be modified so that if you hit a slayer boss all slayer features will work on that boss. - public static Entity getSlayerEntity() { + public static ArmorStandEntity getSlayerArmorstandEntity() { + if (slayerArmorstandEntity != null && slayerArmorstandEntity.isAlive()) { + return slayerArmorstandEntity; + } + if (MinecraftClient.getInstance().world != null) { for (Entity entity : MinecraftClient.getInstance().world.getEntities()) { if (entity.hasCustomName()) { @@ -35,15 +40,18 @@ public static Entity getSlayerEntity() { Matcher matcher = SLAYER_PATTERN.matcher(entityName); if (matcher.find()) { String username = MinecraftClient.getInstance().getSession().getUsername(); - for (Entity armorStand : getEntityArmorStands(entity)) { + for (Entity armorStand : getEntityArmorStands(entity, 1.5f)) { if (armorStand.getDisplayName().getString().contains(username)) { - return entity; + slayerArmorstandEntity = (ArmorStandEntity) entity; + return slayerArmorstandEntity; } } } } } } + + slayerArmorstandEntity = null; return null; } @@ -64,7 +72,6 @@ public static boolean isInSlayerType(String slayer) { boolean type = false; for (String line : Utils.STRING_SCOREBOARD) { switch (line) { - case String a when a.contains("Slayer Quest") -> { return false; } case String b when b.contains("Slay the boss!") -> inFight = true; case String c when c.contains(slayer) -> type = true; default -> { continue; } diff --git a/src/main/resources/skyblocker.mixins.json b/src/main/resources/skyblocker.mixins.json index fdbd84f06a..2b3d38bf4d 100644 --- a/src/main/resources/skyblocker.mixins.json +++ b/src/main/resources/skyblocker.mixins.json @@ -7,6 +7,7 @@ "AbstractInventoryScreenMixin", "BackgroundRendererMixin", "BatEntityMixin", + "BossBarHudMixin", "ClientPlayerEntityMixin", "ClientPlayNetworkHandlerMixin", "ClientWorldMixin",