Skip to content

Commit

Permalink
[Slayers] Adds Slayer Bossbars (#940)
Browse files Browse the repository at this point in the history
* [Slayers] Adds Bossbars
@

* Apply suggestions from code review

---------

Co-authored-by: Kevinthegreat <92656833+kevinthegreat1@users.noreply.github.com>
  • Loading branch information
BigloBot and kevinthegreat1 authored Sep 8, 2024
1 parent 04afe1a commit d241b8c
Show file tree
Hide file tree
Showing 13 changed files with 191 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig
newValue -> config.slayers.highlightBosses = newValue)
.controller(ConfigUtils::createEnumCyclingListController)
.build())
.option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.slayer.bossbar"))
.description(OptionDescription.of(
Text.translatable("skyblocker.config.slayer.bossbar.@Tooltip")))
.binding(defaults.slayers.displayBossbar,
() -> config.slayers.displayBossbar,
newValue -> config.slayers.displayBossbar = newValue)
.controller(ConfigUtils::createBooleanController)
.build())

//Enderman Slayer
.group(OptionGroup.createBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ public class SlayersConfig {
@SerialEntry
public HighlightSlayerEntities highlightBosses = HighlightSlayerEntities.OFF;

@SerialEntry
public boolean displayBossbar = true;

public enum HighlightSlayerEntities {
OFF, GLOW, HITBOX;

Expand Down
42 changes: 42 additions & 0 deletions src/main/java/de/hysky/skyblocker/mixins/BossBarHudMixin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package de.hysky.skyblocker.mixins;

import de.hysky.skyblocker.config.SkyblockerConfigManager;
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 (SkyblockerConfigManager.get().slayers.displayBossbar && SlayerBossBars.shouldRenderBossBar()) {
ClientBossBar bar = SlayerBossBars.updateBossBar();

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();
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand All @@ -58,4 +56,4 @@ private static void announceFirePillarDetails(String entityName) {
TitleContainer.addTitle(title, 15);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -39,4 +39,4 @@ protected static void updateMania() {
TitleContainer.removeTitle(title);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ 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 {
TitleContainer.removeTitle(title);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -40,4 +40,4 @@ protected static void updateIce() {
TitleContainer.removeTitle(title);
}
}
}
}
102 changes: 102 additions & 0 deletions src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerBossBars.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
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();

/**
* Determines if the boss bar should be rendered and updates the max health of the boss.
* Has a 400ms cooldown built-in.
*/
public static boolean shouldRenderBossBar() {
long currentTime = System.currentTimeMillis();
if (currentTime - lastUpdateTime < UPDATE_INTERVAL) {
return bossBar != null;
}
lastUpdateTime = currentTime;

// Reset if no slayer
if (!SlayerUtils.isInSlayer()) {
bossMaxHealth = -1;
bossBar = null;
return false;
}

// Update boss max health
if (SlayerUtils.getSlayerArmorStandEntity() != null && bossMaxHealth == -1) {
Matcher maxHealthMatcher = HEALTH_PATTERN.matcher(SlayerUtils.getSlayerArmorStandEntity().getName().getString());
if (maxHealthMatcher.find()) bossMaxHealth = convertToInt(maxHealthMatcher.group(0));
}

return bossBar != null || SlayerUtils.getSlayerArmorStandEntity() != null;
}

/**
* Updates the boss bar with the current slayer's health, called every frame.
* @return The updated boss bar.
*/
public static ClientBossBar updateBossBar() {
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 no slayer armor stand is found, display a red progress bar
if (slayer == null) {
bossBar.setStyle(BossBar.Style.PROGRESS);
bossBar.setColor(BossBar.Color.RED);
return bossBar;
}

// Update the boss bar with the current slayer's health
Matcher healthMatcher = HEALTH_PATTERN.matcher(slayer.getName().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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,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()));
}

Expand Down
27 changes: 19 additions & 8 deletions src/main/java/de/hysky/skyblocker/utils/SlayerUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,56 @@

//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";
public static final String VOIDGLOOM = "Voidgloom Seraph";
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<Entity> getEntityArmorStands(Entity entity) {
return entity.getEntityWorld().getOtherEntities(entity, entity.getBoundingBox().expand(0.3F, 2.5F, 0.3F), x -> x instanceof ArmorStandEntity && x.hasCustomName());
public static List<Entity> getEntityArmorStands(Entity entity, float expandY) {
return entity.getEntityWorld().getOtherEntities(entity, entity.getBoundingBox().expand(0.3F, expandY, 0.3F), 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() {
// TODO: This should be set when the system to detect isInSlayer is made event-driven
if (slayerArmorStandEntity != null && slayerArmorStandEntity.isAlive()) {
return slayerArmorStandEntity;
}

if (MinecraftClient.getInstance().world != null) {
for (Entity entity : MinecraftClient.getInstance().world.getEntities()) {
if (entity.hasCustomName()) {
String entityName = entity.getCustomName().getString();
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;
}

public static boolean isInSlayer() {
try {
for (String line : Utils.STRING_SCOREBOARD) {
if (line.contains("Slay the boss!")) return true;
if (line.contains("Slay the boss!")) {
return true;
}
}
} catch (NullPointerException e) {
LOGGER.error("[Skyblocker] Error while checking if player is in slayer", e);
Expand Down Expand Up @@ -102,4 +113,4 @@ public static String getSlayerType() {
}
return "";
}
}
}
3 changes: 3 additions & 0 deletions src/main/resources/assets/skyblocker/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,9 @@
"skyblocker.config.slayer.blazeSlayer.attunementHighlights" : "Attunement Highlights",
"skyblocker.config.slayer.blazeSlayer.attunementHighlights.@Tooltip": "If Boss Highlighting is on, applies a color matching the current attunement to the selected effect.",

"skyblocker.config.slayer.bossbar": "Slayer Bossbar",
"skyblocker.config.slayer.bossbar.@Tooltip": "Displays a bossbar for your active slayer boss",

"skyblocker.config.slayer.endermanSlayer": "Enderman Slayer",
"skyblocker.config.slayer.endermanSlayer.enableYangGlyphsNotification": "Enable Yang Glyphs notification",
"skyblocker.config.slayer.endermanSlayer.highlightBeacons": "Beacon Highlighting",
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/skyblocker.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"AbstractInventoryScreenMixin",
"BackgroundRendererMixin",
"BatEntityMixin",
"BossBarHudMixin",
"ClientPlayerEntityMixin",
"ClientPlayNetworkHandlerMixin",
"ClientWorldMixin",
Expand Down

0 comments on commit d241b8c

Please sign in to comment.