Skip to content

Commit d241b8c

Browse files
[Slayers] Adds Slayer Bossbars (SkyblockerMod#940)
* [Slayers] Adds Bossbars @ * Apply suggestions from code review --------- Co-authored-by: Kevinthegreat <[email protected]>
1 parent 04afe1a commit d241b8c

File tree

13 files changed

+191
-22
lines changed

13 files changed

+191
-22
lines changed

src/main/java/de/hysky/skyblocker/config/categories/SlayersCategory.java

+9
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig
4242
newValue -> config.slayers.highlightBosses = newValue)
4343
.controller(ConfigUtils::createEnumCyclingListController)
4444
.build())
45+
.option(Option.<Boolean>createBuilder()
46+
.name(Text.translatable("skyblocker.config.slayer.bossbar"))
47+
.description(OptionDescription.of(
48+
Text.translatable("skyblocker.config.slayer.bossbar.@Tooltip")))
49+
.binding(defaults.slayers.displayBossbar,
50+
() -> config.slayers.displayBossbar,
51+
newValue -> config.slayers.displayBossbar = newValue)
52+
.controller(ConfigUtils::createBooleanController)
53+
.build())
4554

4655
//Enderman Slayer
4756
.group(OptionGroup.createBuilder()

src/main/java/de/hysky/skyblocker/config/configs/SlayersConfig.java

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ public class SlayersConfig {
1010
@SerialEntry
1111
public HighlightSlayerEntities highlightBosses = HighlightSlayerEntities.OFF;
1212

13+
@SerialEntry
14+
public boolean displayBossbar = true;
15+
1316
public enum HighlightSlayerEntities {
1417
OFF, GLOW, HITBOX;
1518

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package de.hysky.skyblocker.mixins;
2+
3+
import de.hysky.skyblocker.config.SkyblockerConfigManager;
4+
import de.hysky.skyblocker.skyblock.slayers.SlayerBossBars;
5+
import net.minecraft.client.MinecraftClient;
6+
import net.minecraft.client.gui.DrawContext;
7+
import net.minecraft.client.gui.hud.BossBarHud;
8+
import net.minecraft.client.gui.hud.ClientBossBar;
9+
import net.minecraft.entity.boss.BossBar;
10+
import org.spongepowered.asm.mixin.Final;
11+
import org.spongepowered.asm.mixin.Mixin;
12+
import org.spongepowered.asm.mixin.Shadow;
13+
import org.spongepowered.asm.mixin.injection.At;
14+
import org.spongepowered.asm.mixin.injection.Inject;
15+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
16+
17+
@Mixin(BossBarHud.class)
18+
public abstract class BossBarHudMixin {
19+
20+
@Final
21+
@Shadow
22+
private MinecraftClient client;
23+
24+
@Shadow
25+
protected abstract void renderBossBar(DrawContext context, int x, int y, BossBar bossBar);
26+
27+
@Inject(method = "render", at = @At("HEAD"), cancellable = true)
28+
private void onRender(DrawContext context, CallbackInfo ci) {
29+
30+
if (SkyblockerConfigManager.get().slayers.displayBossbar && SlayerBossBars.shouldRenderBossBar()) {
31+
ClientBossBar bar = SlayerBossBars.updateBossBar();
32+
33+
int textWidth = this.client.textRenderer.getWidth(bar.getName());
34+
context.drawTextWithShadow(this.client.textRenderer, bar.getName(), context.getScaledWindowWidth() / 2 - textWidth / 2, 3, 16777215);
35+
36+
this.renderBossBar(context, (context.getScaledWindowWidth() / 2) - 91, 12, bar);
37+
38+
ci.cancel();
39+
}
40+
41+
}
42+
}

src/main/java/de/hysky/skyblocker/skyblock/crimson/slayer/AttunementColors.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class AttunementColors {
1717
*/
1818
public static int getColor(LivingEntity e) {
1919
if (!SkyblockerConfigManager.get().slayers.blazeSlayer.attunementHighlights) return 0xf57738;
20-
for (Entity entity : SlayerUtils.getEntityArmorStands(e)) {
20+
for (Entity entity : SlayerUtils.getEntityArmorStands(e, 2.5f)) {
2121
Matcher matcher = COLOR_PATTERN.matcher(entity.getDisplayName().getString());
2222
if (matcher.find()) {
2323
String matchedColour = matcher.group();

src/main/java/de/hysky/skyblocker/skyblock/crimson/slayer/FirePillarAnnouncer.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
import net.minecraft.client.MinecraftClient;
1111
import net.minecraft.entity.Entity;
1212
import net.minecraft.entity.decoration.ArmorStandEntity;
13-
import net.minecraft.text.MutableText;
14-
import net.minecraft.text.PlainTextContent;
1513
import net.minecraft.text.Text;
1614
import net.minecraft.util.Formatting;
1715
import java.util.regex.Matcher;
@@ -42,7 +40,7 @@ public static void checkFirePillar(Entity entity) {
4240

4341
// There is an edge case where the slayer has entered demon phase and temporarily despawned with
4442
// an active fire pillar in play, So fallback to the player
45-
Entity referenceEntity = SlayerUtils.getSlayerEntity();
43+
Entity referenceEntity = SlayerUtils.getSlayerArmorStandEntity();
4644
if (!(referenceEntity != null ? referenceEntity : MinecraftClient.getInstance().player).getBlockPos().isWithinDistance(entity.getPos(), 22)) return;
4745
announceFirePillarDetails(entityName);
4846
}
@@ -58,4 +56,4 @@ private static void announceFirePillarDetails(String entityName) {
5856
TitleContainer.addTitle(title, 15);
5957
}
6058
}
61-
}
59+
}

src/main/java/de/hysky/skyblocker/skyblock/rift/ManiaIndicator.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ protected static void updateMania() {
2222
return;
2323
}
2424

25-
Entity slayerEntity = SlayerUtils.getSlayerEntity();
25+
Entity slayerEntity = SlayerUtils.getSlayerArmorStandEntity();
2626
if (slayerEntity == null) return;
2727

2828
boolean anyMania = false;
29-
for (Entity entity : SlayerUtils.getEntityArmorStands(slayerEntity)) {
29+
for (Entity entity : SlayerUtils.getEntityArmorStands(slayerEntity, 2.5f)) {
3030
if (entity.getDisplayName().toString().contains("MANIA")) {
3131
anyMania = true;
3232
BlockPos pos = MinecraftClient.getInstance().player.getBlockPos().down();
@@ -39,4 +39,4 @@ protected static void updateMania() {
3939
TitleContainer.removeTitle(title);
4040
}
4141
}
42-
}
42+
}

src/main/java/de/hysky/skyblocker/skyblock/rift/StakeIndicator.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ protected static void updateStake() {
1717
TitleContainer.removeTitle(title);
1818
return;
1919
}
20-
Entity slayerEntity = SlayerUtils.getSlayerEntity();
20+
Entity slayerEntity = SlayerUtils.getSlayerArmorStandEntity();
2121
if (slayerEntity != null && slayerEntity.getDisplayName().toString().contains("҉")) {
2222
RenderHelper.displayInTitleContainerAndPlaySound(title);
2323
} else {
2424
TitleContainer.removeTitle(title);
2525
}
2626
}
27-
}
27+
}

src/main/java/de/hysky/skyblocker/skyblock/rift/TwinClawsIndicator.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ protected static void updateIce() {
2020
return;
2121
}
2222

23-
Entity slayerEntity = SlayerUtils.getSlayerEntity();
23+
Entity slayerEntity = SlayerUtils.getSlayerArmorStandEntity();
2424
if (slayerEntity == null) return;
2525

2626
boolean anyClaws = false;
27-
for (Entity entity : SlayerUtils.getEntityArmorStands(slayerEntity)) {
27+
for (Entity entity : SlayerUtils.getEntityArmorStands(slayerEntity, 2.5f)) {
2828
if (entity.getDisplayName().toString().contains("TWINCLAWS")) {
2929
anyClaws = true;
3030
if (!TitleContainer.containsTitle(title) && !scheduled) {
@@ -40,4 +40,4 @@ protected static void updateIce() {
4040
TitleContainer.removeTitle(title);
4141
}
4242
}
43-
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package de.hysky.skyblocker.skyblock.slayers;
2+
3+
import de.hysky.skyblocker.utils.SlayerUtils;
4+
import net.minecraft.client.gui.hud.ClientBossBar;
5+
import net.minecraft.entity.boss.BossBar;
6+
import net.minecraft.entity.decoration.ArmorStandEntity;
7+
import net.minecraft.text.Text;
8+
9+
import java.util.UUID;
10+
import java.util.regex.Matcher;
11+
import java.util.regex.Pattern;
12+
13+
public class SlayerBossBars {
14+
private static final Pattern HEALTH_PATTERN = Pattern.compile("(\\d+(?:\\.\\d+)?[kM]?)(?=❤)");
15+
private static int bossMaxHealth = -1;
16+
private static long lastUpdateTime = 0;
17+
private static final long UPDATE_INTERVAL = 400;
18+
private static ClientBossBar bossBar;
19+
public static final UUID uuid = UUID.randomUUID();
20+
21+
/**
22+
* Determines if the boss bar should be rendered and updates the max health of the boss.
23+
* Has a 400ms cooldown built-in.
24+
*/
25+
public static boolean shouldRenderBossBar() {
26+
long currentTime = System.currentTimeMillis();
27+
if (currentTime - lastUpdateTime < UPDATE_INTERVAL) {
28+
return bossBar != null;
29+
}
30+
lastUpdateTime = currentTime;
31+
32+
// Reset if no slayer
33+
if (!SlayerUtils.isInSlayer()) {
34+
bossMaxHealth = -1;
35+
bossBar = null;
36+
return false;
37+
}
38+
39+
// Update boss max health
40+
if (SlayerUtils.getSlayerArmorStandEntity() != null && bossMaxHealth == -1) {
41+
Matcher maxHealthMatcher = HEALTH_PATTERN.matcher(SlayerUtils.getSlayerArmorStandEntity().getName().getString());
42+
if (maxHealthMatcher.find()) bossMaxHealth = convertToInt(maxHealthMatcher.group(0));
43+
}
44+
45+
return bossBar != null || SlayerUtils.getSlayerArmorStandEntity() != null;
46+
}
47+
48+
/**
49+
* Updates the boss bar with the current slayer's health, called every frame.
50+
* @return The updated boss bar.
51+
*/
52+
public static ClientBossBar updateBossBar() {
53+
ArmorStandEntity slayer = SlayerUtils.getSlayerArmorStandEntity();
54+
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);
55+
56+
// If no slayer armor stand is found, display a red progress bar
57+
if (slayer == null) {
58+
bossBar.setStyle(BossBar.Style.PROGRESS);
59+
bossBar.setColor(BossBar.Color.RED);
60+
return bossBar;
61+
}
62+
63+
// Update the boss bar with the current slayer's health
64+
Matcher healthMatcher = HEALTH_PATTERN.matcher(slayer.getName().getString());
65+
if (healthMatcher.find() && slayer.isAlive()) {
66+
bossBar.setPercent(bossMaxHealth == -1 ? 1f : (float) convertToInt(healthMatcher.group(1)) / bossMaxHealth);
67+
bossBar.setColor(BossBar.Color.PINK);
68+
bossBar.setName(slayer.getDisplayName());
69+
bossBar.setStyle(BossBar.Style.NOTCHED_10);
70+
} else {
71+
bossBar.setColor(BossBar.Color.RED);
72+
bossBar.setStyle(BossBar.Style.PROGRESS);
73+
bossBar.setName(slayer.getDisplayName());
74+
}
75+
76+
return bossBar;
77+
}
78+
79+
private static int convertToInt(String value) {
80+
if (value == null || value.isEmpty()) {
81+
return 0;
82+
}
83+
84+
value = value.trim().toLowerCase();
85+
double multiplier = 1.0;
86+
87+
if (value.endsWith("m")) {
88+
multiplier = 1_000_000;
89+
value = value.substring(0, value.length() - 1);
90+
} else if (value.endsWith("k")) {
91+
multiplier = 1_000;
92+
value = value.substring(0, value.length() - 1);
93+
}
94+
95+
try {
96+
double numericValue = Double.parseDouble(value);
97+
return (int) (numericValue * multiplier);
98+
} catch (NumberFormatException e) {
99+
return 0;
100+
}
101+
}
102+
}

src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerEntitiesGlow.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public static boolean shouldGlow(UUID entityUUID) {
6666
}
6767

6868
public static boolean isSlayer(LivingEntity e) {
69-
return SlayerUtils.isInSlayer() && SlayerUtils.getEntityArmorStands(e).stream().anyMatch(entity ->
69+
return SlayerUtils.isInSlayer() && SlayerUtils.getEntityArmorStands(e, 2.5f).stream().anyMatch(entity ->
7070
entity.getDisplayName().getString().contains(MinecraftClient.getInstance().getSession().getUsername()));
7171
}
7272

src/main/java/de/hysky/skyblocker/utils/SlayerUtils.java

+19-8
Original file line numberDiff line numberDiff line change
@@ -12,45 +12,56 @@
1212

1313
//TODO Slayer Packet system that can provide information about the current slayer boss, abstract so that different bosses can have different info
1414
public class SlayerUtils {
15+
private static ArmorStandEntity slayerArmorStandEntity;
1516
public static final String REVENANT = "Revenant Horror";
1617
public static final String TARA = "Tarantula Broodfather";
1718
public static final String SVEN = "Sven Packmaster";
1819
public static final String VOIDGLOOM = "Voidgloom Seraph";
1920
public static final String VAMPIRE = "Riftstalker Bloodfiend";
2021
public static final String DEMONLORD = "Inferno Demonlord";
2122
private static final Logger LOGGER = LoggerFactory.getLogger(SlayerUtils.class);
22-
private static final Pattern SLAYER_PATTERN = Pattern.compile("Revenant Horror|Tarantula Broodfather|Sven Packmaster|Voidgloom Seraph|Inferno Demonlord|Riftstalker Bloodfiend");
23+
public static final Pattern SLAYER_PATTERN = Pattern.compile("Revenant Horror|Tarantula Broodfather|Sven Packmaster|Voidgloom Seraph|Inferno Demonlord|Riftstalker Bloodfiend");
2324

2425
//TODO: Cache this, probably included in Packet system
25-
public static List<Entity> getEntityArmorStands(Entity entity) {
26-
return entity.getEntityWorld().getOtherEntities(entity, entity.getBoundingBox().expand(0.3F, 2.5F, 0.3F), x -> x instanceof ArmorStandEntity && x.hasCustomName());
26+
public static List<Entity> getEntityArmorStands(Entity entity, float expandY) {
27+
return entity.getEntityWorld().getOtherEntities(entity, entity.getBoundingBox().expand(0.3F, expandY, 0.3F), x -> x instanceof ArmorStandEntity && x.hasCustomName());
2728
}
2829

2930
//Eventually this should be modified so that if you hit a slayer boss all slayer features will work on that boss.
30-
public static Entity getSlayerEntity() {
31+
public static ArmorStandEntity getSlayerArmorStandEntity() {
32+
// TODO: This should be set when the system to detect isInSlayer is made event-driven
33+
if (slayerArmorStandEntity != null && slayerArmorStandEntity.isAlive()) {
34+
return slayerArmorStandEntity;
35+
}
36+
3137
if (MinecraftClient.getInstance().world != null) {
3238
for (Entity entity : MinecraftClient.getInstance().world.getEntities()) {
3339
if (entity.hasCustomName()) {
3440
String entityName = entity.getCustomName().getString();
3541
Matcher matcher = SLAYER_PATTERN.matcher(entityName);
3642
if (matcher.find()) {
3743
String username = MinecraftClient.getInstance().getSession().getUsername();
38-
for (Entity armorStand : getEntityArmorStands(entity)) {
44+
for (Entity armorStand : getEntityArmorStands(entity, 1.5f)) {
3945
if (armorStand.getDisplayName().getString().contains(username)) {
40-
return entity;
46+
slayerArmorStandEntity = (ArmorStandEntity) entity;
47+
return slayerArmorStandEntity;
4148
}
4249
}
4350
}
4451
}
4552
}
4653
}
54+
55+
slayerArmorStandEntity = null;
4756
return null;
4857
}
4958

5059
public static boolean isInSlayer() {
5160
try {
5261
for (String line : Utils.STRING_SCOREBOARD) {
53-
if (line.contains("Slay the boss!")) return true;
62+
if (line.contains("Slay the boss!")) {
63+
return true;
64+
}
5465
}
5566
} catch (NullPointerException e) {
5667
LOGGER.error("[Skyblocker] Error while checking if player is in slayer", e);
@@ -102,4 +113,4 @@ public static String getSlayerType() {
102113
}
103114
return "";
104115
}
105-
}
116+
}

src/main/resources/assets/skyblocker/lang/en_us.json

+3
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,9 @@
653653
"skyblocker.config.slayer.blazeSlayer.attunementHighlights" : "Attunement Highlights",
654654
"skyblocker.config.slayer.blazeSlayer.attunementHighlights.@Tooltip": "If Boss Highlighting is on, applies a color matching the current attunement to the selected effect.",
655655

656+
"skyblocker.config.slayer.bossbar": "Slayer Bossbar",
657+
"skyblocker.config.slayer.bossbar.@Tooltip": "Displays a bossbar for your active slayer boss",
658+
656659
"skyblocker.config.slayer.endermanSlayer": "Enderman Slayer",
657660
"skyblocker.config.slayer.endermanSlayer.enableYangGlyphsNotification": "Enable Yang Glyphs notification",
658661
"skyblocker.config.slayer.endermanSlayer.highlightBeacons": "Beacon Highlighting",

src/main/resources/skyblocker.mixins.json

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"AbstractInventoryScreenMixin",
88
"BackgroundRendererMixin",
99
"BatEntityMixin",
10+
"BossBarHudMixin",
1011
"ClientPlayerEntityMixin",
1112
"ClientPlayNetworkHandlerMixin",
1213
"ClientWorldMixin",

0 commit comments

Comments
 (0)