diff --git a/src/main/java/de/hysky/skyblocker/config/categories/OtherLocationsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/OtherLocationsCategory.java index c578bdc206..259e91d9bf 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/OtherLocationsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/OtherLocationsCategory.java @@ -140,6 +140,13 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig TheEnd.eyes = 0; }) .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.otherLocations.end.muteEndermanSounds")) + .binding(defaults.otherLocations.end.muteEndermanSounds, + () -> config.otherLocations.end.muteEndermanSounds, + newValue -> config.otherLocations.end.muteEndermanSounds = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) .build()) //Spider's Den diff --git a/src/main/java/de/hysky/skyblocker/config/categories/SlayersCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/SlayersCategory.java index 14f5c24499..e67b22640f 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/SlayersCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/SlayersCategory.java @@ -3,45 +3,46 @@ import de.hysky.skyblocker.config.ConfigUtils; import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.configs.SlayersConfig; -import dev.isxander.yacl3.api.ConfigCategory; -import dev.isxander.yacl3.api.Option; -import dev.isxander.yacl3.api.OptionDescription; -import dev.isxander.yacl3.api.OptionGroup; +import de.hysky.skyblocker.skyblock.slayers.hud.SlayerHudWidget; +import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; +import de.hysky.skyblocker.utils.Location; +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.IntegerSliderControllerBuilder; +import net.minecraft.client.MinecraftClient; import net.minecraft.text.Text; public class SlayersCategory { - public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig config) { - return ConfigCategory.createBuilder() - .name(Text.translatable("skyblocker.config.slayer")) + public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig config) { + return ConfigCategory.createBuilder() + .name(Text.translatable("skyblocker.config.slayer")) - //General Slayers Options - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.highlightMinis")) - .description(OptionDescription.of( - Text.translatable("skyblocker.config.slayer.highlightMinis.@Tooltip[0]"), - Text.translatable("skyblocker.config.slayer.highlightMinis.@Tooltip[1]"), - Text.translatable("skyblocker.config.slayer.highlightMinis.@Tooltip[2]"))) - .binding(defaults.slayers.highlightMinis, - () -> config.slayers.highlightMinis, - newValue -> config.slayers.highlightMinis = newValue) - .controller(ConfigUtils::createEnumCyclingListController) - .build()) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.highlightBosses")) - .description(OptionDescription.of( - Text.translatable("skyblocker.config.slayer.highlightBosses.@Tooltip[0]"), - Text.translatable("skyblocker.config.slayer.highlightBosses.@Tooltip[1]"), - Text.translatable("skyblocker.config.slayer.highlightBosses.@Tooltip[2]"), - Text.translatable("skyblocker.config.slayer.highlightBosses.@Tooltip[3]"))) - .binding(defaults.slayers.highlightBosses, - () -> config.slayers.highlightBosses, - newValue -> config.slayers.highlightBosses = newValue) - .controller(ConfigUtils::createEnumCyclingListController) - .build()) + //General Slayers Options + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.highlightMinis")) + .description(OptionDescription.of( + Text.translatable("skyblocker.config.slayer.highlightMinis.@Tooltip[0]"), + Text.translatable("skyblocker.config.slayer.highlightMinis.@Tooltip[1]"), + Text.translatable("skyblocker.config.slayer.highlightMinis.@Tooltip[2]"))) + .binding(defaults.slayers.highlightMinis, + () -> config.slayers.highlightMinis, + newValue -> config.slayers.highlightMinis = newValue) + .controller(ConfigUtils::createEnumCyclingListController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.highlightBosses")) + .description(OptionDescription.of( + Text.translatable("skyblocker.config.slayer.highlightBosses.@Tooltip[0]"), + Text.translatable("skyblocker.config.slayer.highlightBosses.@Tooltip[1]"), + Text.translatable("skyblocker.config.slayer.highlightBosses.@Tooltip[2]"), + Text.translatable("skyblocker.config.slayer.highlightBosses.@Tooltip[3]"))) + .binding(defaults.slayers.highlightBosses, + () -> config.slayers.highlightBosses, + newValue -> config.slayers.highlightBosses = newValue) + .controller(ConfigUtils::createEnumCyclingListController) + .build()) .option(Option.createBuilder() .name(Text.translatable("skyblocker.config.slayer.bossbar")) .description(OptionDescription.of( @@ -51,150 +52,198 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig newValue -> config.slayers.displayBossbar = newValue) .controller(ConfigUtils::createBooleanController) .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.bossSpawnAlert")) + .description(OptionDescription.of( + Text.translatable("skyblocker.config.slayer.bossSpawnAlert.@Tooltip"))) + .binding(defaults.slayers.bossSpawnAlert, + () -> config.slayers.bossSpawnAlert, + newValue -> config.slayers.bossSpawnAlert = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.minibossSpawnAlert")) + .description(OptionDescription.of( + Text.translatable("skyblocker.config.slayer.minibossSpawnAlert.@Tooltip"))) + .binding(defaults.slayers.miniBossSpawnAlert, + () -> config.slayers.miniBossSpawnAlert, + newValue -> config.slayers.miniBossSpawnAlert = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.slainTime")) + .description(OptionDescription.of( + Text.translatable("skyblocker.config.slayer.slainTime.@Tooltip"))) + .binding(defaults.slayers.slainTime, + () -> config.slayers.slainTime, + newValue -> config.slayers.slainTime = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.enableHud")) + .description(OptionDescription.of( + Text.translatable("skyblocker.config.slayer.enableHud.@Tooltip"))) + .binding(defaults.slayers.enableHud, + () -> config.slayers.enableHud, + newValue -> config.slayers.enableHud = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.enableHud")) + .text(Text.translatable("text.skyblocker.open")) + .action((screen, opt) -> MinecraftClient.getInstance().setScreen(new WidgetsConfigurationScreen(Location.HUB, SlayerHudWidget.getInstance().getInternalID(), screen))) + .build()) - //Enderman Slayer - .group(OptionGroup.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.endermanSlayer")) - .collapsed(true) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.endermanSlayer.enableYangGlyphsNotification")) - .binding(defaults.slayers.endermanSlayer.enableYangGlyphsNotification, - () -> config.slayers.endermanSlayer.enableYangGlyphsNotification, - newValue -> config.slayers.endermanSlayer.enableYangGlyphsNotification = newValue) - .controller(ConfigUtils::createBooleanController) - .build()) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.endermanSlayer.highlightBeacons")) - .binding(defaults.slayers.endermanSlayer.highlightBeacons, - () -> config.slayers.endermanSlayer.highlightBeacons, - newValue -> config.slayers.endermanSlayer.highlightBeacons = newValue) - .controller(ConfigUtils::createBooleanController) - .build()) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.endermanSlayer.highlightNukekubiHeads")) - .binding(defaults.slayers.endermanSlayer.highlightNukekubiHeads, - () -> config.slayers.endermanSlayer.highlightNukekubiHeads, - newValue -> config.slayers.endermanSlayer.highlightNukekubiHeads = newValue) - .controller(ConfigUtils::createBooleanController) - .build()) - .build()) + //Enderman Slayer + .group(OptionGroup.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.endermanSlayer")) + .collapsed(true) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.endermanSlayer.enableYangGlyphsNotification")) + .binding(defaults.slayers.endermanSlayer.enableYangGlyphsNotification, + () -> config.slayers.endermanSlayer.enableYangGlyphsNotification, + newValue -> config.slayers.endermanSlayer.enableYangGlyphsNotification = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.endermanSlayer.highlightBeacons")) + .binding(defaults.slayers.endermanSlayer.highlightBeacons, + () -> config.slayers.endermanSlayer.highlightBeacons, + newValue -> config.slayers.endermanSlayer.highlightBeacons = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.endermanSlayer.highlightNukekubiHeads")) + .binding(defaults.slayers.endermanSlayer.highlightNukekubiHeads, + () -> config.slayers.endermanSlayer.highlightNukekubiHeads, + newValue -> config.slayers.endermanSlayer.highlightNukekubiHeads = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.endermanSlayer.lazerTimer")) + .binding(defaults.slayers.endermanSlayer.lazerTimer, + () -> config.slayers.endermanSlayer.lazerTimer, + newValue -> config.slayers.endermanSlayer.lazerTimer = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .build()) - //Vampire Slayer - .group(OptionGroup.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.vampireSlayer")) - .collapsed(true) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.enableEffigyWaypoints")) - .binding(defaults.slayers.vampireSlayer.enableEffigyWaypoints, - () -> config.slayers.vampireSlayer.enableEffigyWaypoints, - newValue -> config.slayers.vampireSlayer.enableEffigyWaypoints = newValue) - .controller(ConfigUtils::createBooleanController) - .build()) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.compactEffigyWaypoints")) - .binding(defaults.slayers.vampireSlayer.compactEffigyWaypoints, - () -> config.slayers.vampireSlayer.compactEffigyWaypoints, - newValue -> config.slayers.vampireSlayer.compactEffigyWaypoints = newValue) - .controller(ConfigUtils::createBooleanController) - .build()) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.effigyUpdateFrequency")) - .description(OptionDescription.of(Text.translatable("skyblocker.config.slayer.vampireSlayer.effigyUpdateFrequency.@Tooltip"))) - .binding(defaults.slayers.vampireSlayer.effigyUpdateFrequency, - () -> config.slayers.vampireSlayer.effigyUpdateFrequency, - newValue -> config.slayers.vampireSlayer.effigyUpdateFrequency = newValue) - .controller(opt -> IntegerSliderControllerBuilder.create(opt).range(1, 10).step(1)) - .build()) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.enableHolyIceIndicator")) - .binding(defaults.slayers.vampireSlayer.enableHolyIceIndicator, - () -> config.slayers.vampireSlayer.enableHolyIceIndicator, - newValue -> config.slayers.vampireSlayer.enableHolyIceIndicator = newValue) - .controller(ConfigUtils::createBooleanController) - .build()) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.holyIceIndicatorTickDelay")) - .binding(defaults.slayers.vampireSlayer.holyIceIndicatorTickDelay, - () -> config.slayers.vampireSlayer.holyIceIndicatorTickDelay, - newValue -> config.slayers.vampireSlayer.holyIceIndicatorTickDelay = newValue) - .controller(IntegerFieldControllerBuilder::create) - .build()) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.holyIceUpdateFrequency")) - .description(OptionDescription.of(Text.translatable("skyblocker.config.slayer.vampireSlayer.holyIceUpdateFrequency.@Tooltip"))) - .binding(defaults.slayers.vampireSlayer.holyIceUpdateFrequency, - () -> config.slayers.vampireSlayer.holyIceUpdateFrequency, - newValue -> config.slayers.vampireSlayer.holyIceUpdateFrequency = newValue) - .controller(opt -> IntegerSliderControllerBuilder.create(opt).range(1, 10).step(1)) - .build()) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.enableHealingMelonIndicator")) - .binding(defaults.slayers.vampireSlayer.enableHealingMelonIndicator, - () -> config.slayers.vampireSlayer.enableHealingMelonIndicator, - newValue -> config.slayers.vampireSlayer.enableHealingMelonIndicator = newValue) - .controller(ConfigUtils::createBooleanController) - .build()) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.healingMelonHealthThreshold")) - .binding(defaults.slayers.vampireSlayer.healingMelonHealthThreshold, - () -> config.slayers.vampireSlayer.healingMelonHealthThreshold, - newValue -> config.slayers.vampireSlayer.healingMelonHealthThreshold = newValue) - .controller(FloatFieldControllerBuilder::create) - .build()) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.enableSteakStakeIndicator")) - .binding(defaults.slayers.vampireSlayer.enableSteakStakeIndicator, - () -> config.slayers.vampireSlayer.enableSteakStakeIndicator, - newValue -> config.slayers.vampireSlayer.enableSteakStakeIndicator = newValue) - .controller(ConfigUtils::createBooleanController) - .build()) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.steakStakeUpdateFrequency")) - .description(OptionDescription.of(Text.translatable("skyblocker.config.slayer.vampireSlayer.steakStakeUpdateFrequency.@Tooltip"))) - .binding(defaults.slayers.vampireSlayer.steakStakeUpdateFrequency, - () -> config.slayers.vampireSlayer.steakStakeUpdateFrequency, - newValue -> config.slayers.vampireSlayer.steakStakeUpdateFrequency = newValue) - .controller(opt -> IntegerSliderControllerBuilder.create(opt).range(1, 10).step(1)) - .build()) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.enableManiaIndicator")) - .binding(defaults.slayers.vampireSlayer.enableManiaIndicator, - () -> config.slayers.vampireSlayer.enableManiaIndicator, - newValue -> config.slayers.vampireSlayer.enableManiaIndicator = newValue) - .controller(ConfigUtils::createBooleanController) - .build()) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.maniaUpdateFrequency")) - .description(OptionDescription.of(Text.translatable("skyblocker.config.slayer.vampireSlayer.maniaUpdateFrequency.@Tooltip"))) - .binding(defaults.slayers.vampireSlayer.maniaUpdateFrequency, - () -> config.slayers.vampireSlayer.maniaUpdateFrequency, - newValue -> config.slayers.vampireSlayer.maniaUpdateFrequency = newValue) - .controller(opt -> IntegerSliderControllerBuilder.create(opt).range(1, 10).step(1)) - .build()) - .build()) + //Vampire Slayer + .group(OptionGroup.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.vampireSlayer")) + .collapsed(true) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.enableEffigyWaypoints")) + .binding(defaults.slayers.vampireSlayer.enableEffigyWaypoints, + () -> config.slayers.vampireSlayer.enableEffigyWaypoints, + newValue -> config.slayers.vampireSlayer.enableEffigyWaypoints = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.compactEffigyWaypoints")) + .binding(defaults.slayers.vampireSlayer.compactEffigyWaypoints, + () -> config.slayers.vampireSlayer.compactEffigyWaypoints, + newValue -> config.slayers.vampireSlayer.compactEffigyWaypoints = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.effigyUpdateFrequency")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.slayer.vampireSlayer.effigyUpdateFrequency.@Tooltip"))) + .binding(defaults.slayers.vampireSlayer.effigyUpdateFrequency, + () -> config.slayers.vampireSlayer.effigyUpdateFrequency, + newValue -> config.slayers.vampireSlayer.effigyUpdateFrequency = newValue) + .controller(opt -> IntegerSliderControllerBuilder.create(opt).range(1, 10).step(1)) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.enableHolyIceIndicator")) + .binding(defaults.slayers.vampireSlayer.enableHolyIceIndicator, + () -> config.slayers.vampireSlayer.enableHolyIceIndicator, + newValue -> config.slayers.vampireSlayer.enableHolyIceIndicator = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.holyIceIndicatorTickDelay")) + .binding(defaults.slayers.vampireSlayer.holyIceIndicatorTickDelay, + () -> config.slayers.vampireSlayer.holyIceIndicatorTickDelay, + newValue -> config.slayers.vampireSlayer.holyIceIndicatorTickDelay = newValue) + .controller(IntegerFieldControllerBuilder::create) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.holyIceUpdateFrequency")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.slayer.vampireSlayer.holyIceUpdateFrequency.@Tooltip"))) + .binding(defaults.slayers.vampireSlayer.holyIceUpdateFrequency, + () -> config.slayers.vampireSlayer.holyIceUpdateFrequency, + newValue -> config.slayers.vampireSlayer.holyIceUpdateFrequency = newValue) + .controller(opt -> IntegerSliderControllerBuilder.create(opt).range(1, 10).step(1)) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.enableHealingMelonIndicator")) + .binding(defaults.slayers.vampireSlayer.enableHealingMelonIndicator, + () -> config.slayers.vampireSlayer.enableHealingMelonIndicator, + newValue -> config.slayers.vampireSlayer.enableHealingMelonIndicator = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.healingMelonHealthThreshold")) + .binding(defaults.slayers.vampireSlayer.healingMelonHealthThreshold, + () -> config.slayers.vampireSlayer.healingMelonHealthThreshold, + newValue -> config.slayers.vampireSlayer.healingMelonHealthThreshold = newValue) + .controller(FloatFieldControllerBuilder::create) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.enableSteakStakeIndicator")) + .binding(defaults.slayers.vampireSlayer.enableSteakStakeIndicator, + () -> config.slayers.vampireSlayer.enableSteakStakeIndicator, + newValue -> config.slayers.vampireSlayer.enableSteakStakeIndicator = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.steakStakeUpdateFrequency")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.slayer.vampireSlayer.steakStakeUpdateFrequency.@Tooltip"))) + .binding(defaults.slayers.vampireSlayer.steakStakeUpdateFrequency, + () -> config.slayers.vampireSlayer.steakStakeUpdateFrequency, + newValue -> config.slayers.vampireSlayer.steakStakeUpdateFrequency = newValue) + .controller(opt -> IntegerSliderControllerBuilder.create(opt).range(1, 10).step(1)) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.enableManiaIndicator")) + .binding(defaults.slayers.vampireSlayer.enableManiaIndicator, + () -> config.slayers.vampireSlayer.enableManiaIndicator, + newValue -> config.slayers.vampireSlayer.enableManiaIndicator = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.vampireSlayer.maniaUpdateFrequency")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.slayer.vampireSlayer.maniaUpdateFrequency.@Tooltip"))) + .binding(defaults.slayers.vampireSlayer.maniaUpdateFrequency, + () -> config.slayers.vampireSlayer.maniaUpdateFrequency, + newValue -> config.slayers.vampireSlayer.maniaUpdateFrequency = newValue) + .controller(opt -> IntegerSliderControllerBuilder.create(opt).range(1, 10).step(1)) + .build()) + .build()) - //Blaze Slayer - .group(OptionGroup.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.blazeSlayer")) - .collapsed(true) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.blazeSlayer.enableFirePillarAnnouncer")) - .description(OptionDescription.of(Text.translatable("skyblocker.config.slayer.blazeSlayer.enableFirePillarAnnouncer.@Tooltip"))) - .binding(defaults.slayers.blazeSlayer.firePillarCountdown, - () -> config.slayers.blazeSlayer.firePillarCountdown, - newValue -> config.slayers.blazeSlayer.firePillarCountdown = newValue) - .controller(ConfigUtils::createEnumCyclingListController) - .build()) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.slayer.blazeSlayer.attunementHighlights")) - .description(OptionDescription.of(Text.translatable("skyblocker.config.slayer.blazeSlayer.attunementHighlights.@Tooltip"))) - .binding(defaults.slayers.blazeSlayer.attunementHighlights, - () -> config.slayers.blazeSlayer.attunementHighlights, - newValue -> config.slayers.blazeSlayer.attunementHighlights = newValue) - .controller(ConfigUtils::createBooleanController) - .build()) - .build()) + //Blaze Slayer + .group(OptionGroup.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.blazeSlayer")) + .collapsed(true) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.blazeSlayer.enableFirePillarAnnouncer")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.slayer.blazeSlayer.enableFirePillarAnnouncer.@Tooltip"))) + .binding(defaults.slayers.blazeSlayer.firePillarCountdown, + () -> config.slayers.blazeSlayer.firePillarCountdown, + newValue -> config.slayers.blazeSlayer.firePillarCountdown = newValue) + .controller(ConfigUtils::createEnumCyclingListController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.slayer.blazeSlayer.attunementHighlights")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.slayer.blazeSlayer.attunementHighlights.@Tooltip"))) + .binding(defaults.slayers.blazeSlayer.attunementHighlights, + () -> config.slayers.blazeSlayer.attunementHighlights, + newValue -> config.slayers.blazeSlayer.attunementHighlights = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .build()) - .build(); - } + .build(); + } } diff --git a/src/main/java/de/hysky/skyblocker/config/configs/OtherLocationsConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/OtherLocationsConfig.java index 5bc1c4b77b..ffba5a5ba5 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/OtherLocationsConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/OtherLocationsConfig.java @@ -57,6 +57,9 @@ public static class TheEnd { @SerialEntry public boolean waypoint = true; + @SerialEntry + public boolean muteEndermanSounds = false; + @SerialEntry public int x = 10; diff --git a/src/main/java/de/hysky/skyblocker/config/configs/SlayersConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/SlayersConfig.java index e3149d52ef..6bf315a2d2 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/SlayersConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/SlayersConfig.java @@ -4,98 +4,113 @@ import net.minecraft.client.resource.language.I18n; public class SlayersConfig { - @SerialEntry - public HighlightSlayerEntities highlightMinis = HighlightSlayerEntities.OFF; + @SerialEntry + public HighlightSlayerEntities highlightMinis = HighlightSlayerEntities.GLOW; - @SerialEntry - public HighlightSlayerEntities highlightBosses = HighlightSlayerEntities.OFF; + @SerialEntry + public HighlightSlayerEntities highlightBosses = HighlightSlayerEntities.GLOW; @SerialEntry public boolean displayBossbar = true; - public enum HighlightSlayerEntities { - OFF, GLOW, HITBOX; + public enum HighlightSlayerEntities { + OFF, GLOW, HITBOX; + + @Override + public String toString() { + return I18n.translate("skyblocker.config.slayer.highlightBosses." + name()); + } + } + + @SerialEntry + public boolean bossSpawnAlert = true; + + @SerialEntry + public boolean miniBossSpawnAlert = true; + + @SerialEntry + public boolean slainTime = true; - @Override - public String toString() { - return I18n.translate("skyblocker.config.slayer.highlightBosses." + name()); - } - } + @SerialEntry + public boolean enableHud = true; - @SerialEntry - public EndermanSlayer endermanSlayer = new EndermanSlayer(); + @SerialEntry + public EndermanSlayer endermanSlayer = new EndermanSlayer(); - @SerialEntry - public VampireSlayer vampireSlayer = new VampireSlayer(); + @SerialEntry + public VampireSlayer vampireSlayer = new VampireSlayer(); + + @SerialEntry + public BlazeSlayer blazeSlayer = new BlazeSlayer(); - @SerialEntry - public BlazeSlayer blazeSlayer = new BlazeSlayer(); + public static class EndermanSlayer { + @SerialEntry + public boolean enableYangGlyphsNotification = true; - public static class EndermanSlayer { - @SerialEntry - public boolean enableYangGlyphsNotification = true; + @SerialEntry + public boolean highlightBeacons = true; - @SerialEntry - public boolean highlightBeacons = true; + @SerialEntry + public boolean highlightNukekubiHeads = true; - @SerialEntry - public boolean highlightNukekubiHeads = true; - } + @SerialEntry + public boolean lazerTimer = true; + } - public static class VampireSlayer { - @SerialEntry - public boolean enableEffigyWaypoints = true; + public static class VampireSlayer { + @SerialEntry + public boolean enableEffigyWaypoints = true; - @SerialEntry - public boolean compactEffigyWaypoints; + @SerialEntry + public boolean compactEffigyWaypoints; - @SerialEntry - public int effigyUpdateFrequency = 5; + @SerialEntry + public int effigyUpdateFrequency = 5; - @SerialEntry - public boolean enableHolyIceIndicator = true; + @SerialEntry + public boolean enableHolyIceIndicator = true; - @SerialEntry - public int holyIceIndicatorTickDelay = 10; + @SerialEntry + public int holyIceIndicatorTickDelay = 10; - @SerialEntry - public int holyIceUpdateFrequency = 5; + @SerialEntry + public int holyIceUpdateFrequency = 5; - @SerialEntry - public boolean enableHealingMelonIndicator = true; + @SerialEntry + public boolean enableHealingMelonIndicator = true; - @SerialEntry - public float healingMelonHealthThreshold = 4f; + @SerialEntry + public float healingMelonHealthThreshold = 4f; - @SerialEntry - public boolean enableSteakStakeIndicator = true; + @SerialEntry + public boolean enableSteakStakeIndicator = true; - @SerialEntry - public int steakStakeUpdateFrequency = 5; + @SerialEntry + public int steakStakeUpdateFrequency = 5; - @SerialEntry - public boolean enableManiaIndicator = true; + @SerialEntry + public boolean enableManiaIndicator = true; - @SerialEntry - public int maniaUpdateFrequency = 5; - } + @SerialEntry + public int maniaUpdateFrequency = 5; + } - public static class BlazeSlayer { - @SerialEntry - public FirePillar firePillarCountdown = FirePillar.SOUND_AND_VISUAL; + public static class BlazeSlayer { + @SerialEntry + public FirePillar firePillarCountdown = FirePillar.SOUND_AND_VISUAL; - @SerialEntry - public Boolean attunementHighlights = true; + @SerialEntry + public Boolean attunementHighlights = true; - public enum FirePillar { - OFF, - VISUAL, - SOUND_AND_VISUAL; + public enum FirePillar { + OFF, + VISUAL, + SOUND_AND_VISUAL; - @Override - public String toString() { - return I18n.translate("skyblocker.config.slayer.blazeSlayer.enableFirePillarAnnouncer.mode." + name()); - } - } - } + @Override + public String toString() { + return I18n.translate("skyblocker.config.slayer.blazeSlayer.enableFirePillarAnnouncer.mode." + name()); + } + } + } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java index a67eda0e6c..79f45a3205 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java @@ -9,14 +9,14 @@ import de.hysky.skyblocker.skyblock.FishingHelper; import de.hysky.skyblocker.skyblock.chocolatefactory.EggFinder; import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager; -import de.hysky.skyblocker.skyblock.crimson.slayer.FirePillarAnnouncer; import de.hysky.skyblocker.skyblock.dungeon.DungeonScore; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; -import de.hysky.skyblocker.skyblock.dwarven.WishingCompassSolver; import de.hysky.skyblocker.skyblock.dwarven.CrystalsChestHighlighter; +import de.hysky.skyblocker.skyblock.dwarven.WishingCompassSolver; import de.hysky.skyblocker.skyblock.end.EnderNodes; import de.hysky.skyblocker.skyblock.end.TheEnd; -import de.hysky.skyblocker.skyblock.slayers.SlayerEntitiesGlow; +import de.hysky.skyblocker.skyblock.slayers.SlayerManager; +import de.hysky.skyblocker.skyblock.slayers.boss.demonlord.FirePillarAnnouncer; import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; import de.hysky.skyblocker.skyblock.waypoint.MythologicalRitual; import de.hysky.skyblocker.utils.Utils; @@ -27,6 +27,8 @@ import net.minecraft.entity.ItemEntity; import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.network.packet.s2c.play.*; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; import net.minecraft.util.Identifier; import org.slf4j.Logger; import org.spongepowered.asm.mixin.Final; @@ -42,22 +44,15 @@ public abstract class ClientPlayNetworkHandlerMixin { @Shadow private ClientWorld world; - @Shadow - @Final - private static Logger LOGGER; + @Shadow + @Final + private static Logger LOGGER; @Inject(method = "onEntityTrackerUpdate", at = @At("TAIL")) private void skyblocker$onEntityTrackerUpdate(EntityTrackerUpdateS2CPacket packet, CallbackInfo ci, @Local Entity entity) { if (!(entity instanceof ArmorStandEntity armorStandEntity)) return; - if (SkyblockerConfigManager.get().slayers.highlightMinis == SlayersConfig.HighlightSlayerEntities.GLOW && SlayerEntitiesGlow.isSlayerMiniMob(armorStandEntity) - || SkyblockerConfigManager.get().slayers.highlightBosses == SlayersConfig.HighlightSlayerEntities.GLOW && SlayerEntitiesGlow.isSlayer(armorStandEntity)) { - if (armorStandEntity.isDead()) { - SlayerEntitiesGlow.cleanupArmorstand(armorStandEntity); - } else { - SlayerEntitiesGlow.setSlayerMobGlow(armorStandEntity); - } - } + SlayerManager.checkSlayerBoss(armorStandEntity); if (SkyblockerConfigManager.get().slayers.blazeSlayer.firePillarCountdown != SlayersConfig.BlazeSlayer.FirePillar.OFF) FirePillarAnnouncer.checkFirePillar(entity); @@ -70,29 +65,28 @@ public abstract class ClientPlayNetworkHandlerMixin { } @Inject(method = "method_64896", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/world/ClientWorld;removeEntity(ILnet/minecraft/entity/Entity$RemovalReason;)V")) - private void skyblocker$onItemDestroy(int entityId, CallbackInfo ci) { - if (world.getEntityById(entityId) instanceof ItemEntity itemEntity) { - DungeonManager.onItemPickup(itemEntity); - } - } - - @ModifyVariable(method = "onItemPickupAnimation", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/world/ClientWorld;removeEntity(ILnet/minecraft/entity/Entity$RemovalReason;)V", ordinal = 0)) - private ItemEntity skyblocker$onItemPickup(ItemEntity itemEntity) { - DungeonManager.onItemPickup(itemEntity); - return itemEntity; - } - - @WrapWithCondition(method = "onEntityPassengersSet", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;)V", remap = false)) - private boolean skyblocker$cancelEntityPassengersWarning(Logger instance, String msg) { - return !Utils.isOnHypixel(); - } + private void skyblocker$onItemDestroy(int entityId, CallbackInfo ci) { + if (world.getEntityById(entityId) instanceof ItemEntity itemEntity) { + DungeonManager.onItemPickup(itemEntity); + } + } + + @ModifyVariable(method = "onItemPickupAnimation", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/world/ClientWorld;removeEntity(ILnet/minecraft/entity/Entity$RemovalReason;)V", ordinal = 0)) + private ItemEntity skyblocker$onItemPickup(ItemEntity itemEntity) { + DungeonManager.onItemPickup(itemEntity); + return itemEntity; + } + + @WrapWithCondition(method = "onEntityPassengersSet", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;)V", remap = false)) + private boolean skyblocker$cancelEntityPassengersWarning(Logger instance, String msg) { + return !Utils.isOnHypixel(); + } @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); TheEnd.onEntityDeath(entity); - SlayerEntitiesGlow.onEntityDeath(entity); } return entity; } @@ -107,38 +101,50 @@ public abstract class ClientPlayNetworkHandlerMixin { PlayerListMgr.updateFooter(packet.footer()); } - @WrapWithCondition(method = "onPlayerList", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V", remap = false)) - private boolean skyblocker$cancelPlayerListWarning(Logger instance, String format, Object arg1, Object arg2) { - return !Utils.isOnHypixel(); - } - - @Inject(method = "onPlaySound", at = @At("RETURN")) - private void skyblocker$onPlaySound(PlaySoundS2CPacket packet, CallbackInfo ci) { - FishingHelper.onSound(packet); - CrystalsChestHighlighter.onSound(packet); - } - - @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")); - } - - @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 = "onTeam", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;[Ljava/lang/Object;)V", remap = false)) - private boolean skyblocker$cancelTeamWarning(Logger instance, String format, Object... arg) { - return !Utils.isOnHypixel(); - } - - @Inject(method = "onParticle", at = @At("RETURN")) - private void skyblocker$onParticle(ParticleS2CPacket packet, CallbackInfo ci) { - MythologicalRitual.onParticle(packet); - DojoManager.onParticle(packet); - CrystalsChestHighlighter.onParticle(packet); - EnderNodes.onParticle(packet); - WishingCompassSolver.onParticle(packet); - } + @WrapWithCondition(method = "onPlayerList", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V", remap = false)) + private boolean skyblocker$cancelPlayerListWarning(Logger instance, String format, Object arg1, Object arg2) { + return !Utils.isOnHypixel(); + } + + @Inject(method = "onPlaySound", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/util/thread/ThreadExecutor;)V", shift = At.Shift.AFTER), cancellable = true) + private void skyblocker$onPlaySound(PlaySoundS2CPacket packet, CallbackInfo ci) { + FishingHelper.onSound(packet); + CrystalsChestHighlighter.onSound(packet); + SoundEvent sound = packet.getSound().value(); + + // Mute Enderman sounds in the End + if (Utils.isInTheEnd() && SkyblockerConfigManager.get().otherLocations.end.muteEndermanSounds) { + if (sound.id().equals(SoundEvents.ENTITY_ENDERMAN_AMBIENT.id()) || + sound.id().equals(SoundEvents.ENTITY_ENDERMAN_DEATH.id()) || + sound.id().equals(SoundEvents.ENTITY_ENDERMAN_HURT.id()) || + sound.id().equals(SoundEvents.ENTITY_ENDERMAN_SCREAM.id()) || + sound.id().equals(SoundEvents.ENTITY_ENDERMAN_STARE.id())) { + ci.cancel(); + } + } + } + + @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")); + } + + @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 = "onTeam", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;[Ljava/lang/Object;)V", remap = false)) + private boolean skyblocker$cancelTeamWarning(Logger instance, String format, Object... arg) { + return !Utils.isOnHypixel(); + } + + @Inject(method = "onParticle", at = @At("RETURN")) + private void skyblocker$onParticle(ParticleS2CPacket packet, CallbackInfo ci) { + MythologicalRitual.onParticle(packet); + DojoManager.onParticle(packet); + CrystalsChestHighlighter.onParticle(packet); + EnderNodes.onParticle(packet); + WishingCompassSolver.onParticle(packet); + } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java index 28b2c7dc0d..0d7e2c708a 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java @@ -3,8 +3,8 @@ import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager; import de.hysky.skyblocker.skyblock.dungeon.device.SimonSays; import de.hysky.skyblocker.skyblock.dwarven.CrystalsChestHighlighter; -import de.hysky.skyblocker.skyblock.end.BeaconHighlighter; -import de.hysky.skyblocker.utils.SlayerUtils; +import de.hysky.skyblocker.skyblock.slayers.SlayerManager; +import de.hysky.skyblocker.skyblock.slayers.boss.voidgloom.BeaconHighlighter; import de.hysky.skyblocker.utils.Utils; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; @@ -31,7 +31,7 @@ public class ClientWorldMixin { DojoManager.onBlockUpdate(pos.toImmutable(), state); } else if (Utils.isInCrystalHollows()) { CrystalsChestHighlighter.onBlockUpdate(pos.toImmutable(), state); - } else if (Utils.isInTheEnd() && SlayerUtils.isInSlayer()) { + } else if (Utils.isInTheEnd() && SlayerManager.isBossSpawned()) { BeaconHighlighter.beaconPositions.remove(pos); if (state.isOf(Blocks.BEACON)) BeaconHighlighter.beaconPositions.add(pos.toImmutable()); diff --git a/src/main/java/de/hysky/skyblocker/mixins/EntityMixin.java b/src/main/java/de/hysky/skyblocker/mixins/EntityMixin.java index 38ff016b52..c844d51ff4 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/EntityMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/EntityMixin.java @@ -1,17 +1,27 @@ package de.hysky.skyblocker.mixins; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.debug.Debug; +import de.hysky.skyblocker.skyblock.slayers.SlayerManager; +import de.hysky.skyblocker.skyblock.slayers.SlayerType; +import de.hysky.skyblocker.skyblock.slayers.boss.voidgloom.LazerTimer; import de.hysky.skyblocker.utils.Utils; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.Nullable; 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; -import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import java.util.UUID; @Mixin(Entity.class) public abstract class EntityMixin { @@ -19,6 +29,21 @@ public abstract class EntityMixin { @Final private EntityType type; + @Shadow + public abstract UUID getUuid(); + + @Shadow + public abstract EntityType getType(); + + @Shadow + public abstract BlockPos getBlockPos(); + + @Shadow + public abstract Vec3d getPos(); + + @Shadow + public abstract @Nullable Entity getVehicle(); + @Shadow public abstract boolean isInvisible(); @@ -26,4 +51,37 @@ public abstract class EntityMixin { public boolean skyblocker$showInvisibleArmorStands(boolean isSpectator, PlayerEntity player) { return isSpectator || (isInvisible() && Utils.isOnHypixel() && Debug.debugEnabled() && SkyblockerConfigManager.get().debug.showInvisibleArmorStands && type.equals(EntityType.ARMOR_STAND)); } + + @ModifyReturnValue(method = "startRiding(Lnet/minecraft/entity/Entity;Z)Z", at = @At("RETURN")) + private boolean modifyStartRidingReturnValue(boolean originalReturnValue, Entity entity, boolean force) { + if (originalReturnValue) { + if (SkyblockerConfigManager.get().slayers.endermanSlayer.lazerTimer + && SlayerManager.isBossSpawned() + && this.getType() == EntityType.ENDERMAN + && entity.getType() == EntityType.ARMOR_STAND) { + Entity slayer = SlayerManager.getSlayerBoss(); + if (slayer != null && slayer.getUuid().equals(getUuid()) && !LazerTimer.isRiding()) { + LazerTimer.resetTimer(); + LazerTimer.setRiding(true); + } + } + } + return originalReturnValue; + } + + @Inject(method = "tick", at = @At("TAIL")) + private void onTick(CallbackInfo ci) { + if (this.getType() == EntityType.ENDERMAN && SkyblockerConfigManager.get().slayers.endermanSlayer.lazerTimer && SlayerManager.isInSlayerType(SlayerType.VOIDGLOOM)) { + if (SlayerManager.getSlayerBoss() != null && getUuid().equals(SlayerManager.getSlayerBoss().getUuid())) { + if (LazerTimer.isRiding()) { + if (getVehicle() == null) { + if (LazerTimer.remainingTime > 5.0) return; + LazerTimer.setRiding(false); + } else { + LazerTimer.updateTimer(); + } + } + } + } + } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/WorldRendererMixin.java b/src/main/java/de/hysky/skyblocker/mixins/WorldRendererMixin.java index 01b1a0d603..6a097c29d5 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/WorldRendererMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/WorldRendererMixin.java @@ -18,7 +18,8 @@ import de.hysky.skyblocker.skyblock.dungeon.LividColor; import de.hysky.skyblocker.skyblock.entity.MobBoundingBoxes; import de.hysky.skyblocker.skyblock.entity.MobGlow; -import de.hysky.skyblocker.skyblock.slayers.SlayerEntitiesGlow; +import de.hysky.skyblocker.skyblock.slayers.SlayerManager; + import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.DefaultFramebufferSet; import net.minecraft.client.render.WorldRenderer; @@ -75,7 +76,7 @@ public class WorldRendererMixin { if (shouldShowBoundingBox) { MobBoundingBoxes.submitBox2BeRendered( - entity instanceof ArmorStandEntity e ? SlayerEntitiesGlow.getSlayerMobBoundingBox(e) : entity.getBoundingBox(), + entity instanceof ArmorStandEntity e ? SlayerManager.getSlayerMobBoundingBox(e) : entity.getBoundingBox(), MobBoundingBoxes.getBoxColor(entity) ); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/FishingHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/FishingHelper.java index 868feb6ea3..dacce71346 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/FishingHelper.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/FishingHelper.java @@ -14,6 +14,8 @@ import net.minecraft.item.FishingRodItem; import net.minecraft.item.ItemStack; import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; import net.minecraft.text.Text; import net.minecraft.util.ActionResult; import net.minecraft.util.Formatting; @@ -62,12 +64,12 @@ public static void resetFish() { } public static void onSound(PlaySoundS2CPacket packet) { - String path = packet.getSound().value().id().getPath(); - if (SkyblockerConfigManager.get().helpers.fishing.enableFishingHelper && startTimeFish != 0 && System.currentTimeMillis() >= startTimeFish + 2000 && ("entity.generic.splash".equals(path) || "entity.player.splash".equals(path))) { + SoundEvent sound = packet.getSound().value(); + if (SkyblockerConfigManager.get().helpers.fishing.enableFishingHelper && startTimeFish != 0 && System.currentTimeMillis() >= startTimeFish + 2000 && (sound.id().equals(SoundEvents.ENTITY_GENERIC_SPLASH.id()) || sound.id().equals(SoundEvents.ENTITY_PLAYER_SPLASH.id()))) { ClientPlayerEntity player = MinecraftClient.getInstance().player; if (player != null && player.fishHook != null) { Vec3d soundToFishHook = player.fishHook.getPos().subtract(packet.getX(), 0, packet.getZ()); - if (Math.abs(normalYawVector.x * soundToFishHook.z - normalYawVector.z * soundToFishHook.x) < 0.2D && Math.abs(normalYawVector.dotProduct(soundToFishHook)) < 4D && player.getPos().squaredDistanceTo(packet.getX(), packet.getY(), packet.getZ()) > 1D) { + if (Math.abs(normalYawVector.x * soundToFishHook.z - normalYawVector.z * soundToFishHook.x) < 0.2D && Math.abs(normalYawVector.dotProduct(soundToFishHook)) < 4D && player.squaredDistanceTo(packet.getX(), packet.getY(), packet.getZ()) > 1D) { RenderHelper.displayInTitleContainerAndPlaySound(title, 10); resetFish(); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java b/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java index ccdb6db296..ed97c76874 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java @@ -81,7 +81,7 @@ private static void render(WorldRenderContext wrc, NbtCompound customData, int b * @implNote {@link MinecraftClient#player} and {@link MinecraftClient#world} must not be null when calling this method. */ private static void render(WorldRenderContext wrc, int range) { - if (client.crosshairTarget != null && client.crosshairTarget.getType() == HitResult.Type.BLOCK && client.crosshairTarget instanceof BlockHitResult blockHitResult && client.crosshairTarget.squaredDistanceTo(client.player) < range * range) { + if (client.crosshairTarget != null && client.crosshairTarget.getType() == HitResult.Type.BLOCK && client.crosshairTarget instanceof BlockHitResult blockHitResult && client.crosshairTarget.getPos().isInRange(client.player.getPos(), range)) { render(wrc, blockHitResult); } else if (client.interactionManager != null && range > client.player.getAttributeInstance(EntityAttributes.BLOCK_INTERACTION_RANGE).getValue()) { HitResult result = client.player.raycast(range, wrc.tickCounter().getTickDelta(true), false); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/DojoManager.java b/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/DojoManager.java index 399b435877..1aed5fa915 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/DojoManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/DojoManager.java @@ -202,7 +202,7 @@ private static void onEntitySpawn(Entity entity, ClientWorld clientWorld) { return; } // Check if within 50 blocks and 5 blocks vertically - if (entity.squaredDistanceTo(CLIENT.player) > 2500 || Math.abs(entity.getBlockY() - CLIENT.player.getBlockY()) > 5) { + if (!entity.isInRange(CLIENT.player, 50, 5)) { return; } switch (currentChallenge) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java index df3d9bf929..4f19f8f37f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java @@ -59,7 +59,7 @@ static ToDoubleFunction getSquaredDistanceToFunction(Entity enti } static Predicate getRangePredicate(Entity entity) { - return secretWaypoint -> entity.squaredDistanceTo(secretWaypoint.centerPos) <= 36D; + return secretWaypoint -> entity.getPos().isInRange(secretWaypoint.centerPos, 36); } @Override diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsChestHighlighter.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsChestHighlighter.java index b682a896de..075e016b4a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsChestHighlighter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsChestHighlighter.java @@ -16,6 +16,8 @@ import net.minecraft.network.packet.s2c.play.ParticleS2CPacket; import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket; import net.minecraft.particle.ParticleTypes; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; import net.minecraft.text.Text; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.HitResult; @@ -112,9 +114,9 @@ public static void onSound(PlaySoundS2CPacket packet) { if (player == null || !Utils.isInCrystalHollows() || !SkyblockerConfigManager.get().mining.crystalHollows.chestHighlighter) { return; } - String path = packet.getSound().value().id().getPath(); + SoundEvent sound = packet.getSound().value(); //lock picked sound - if (path.equals("entity.experience_orb.pickup") && packet.getPitch() == 1 && !activeChests.isEmpty()) { + if (sound.id().equals(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP.id()) && packet.getPitch() == 1 && !activeChests.isEmpty()) { Vec3d eyePos = player.getCameraPosVec(0); Vec3d rotationVec = player.getRotationVec(0); double range = player.getBlockInteractionRange(); @@ -125,11 +127,11 @@ public static void onSound(PlaySoundS2CPacket packet) { activeParticles.clear(); } //lock pick fail sound - } else if (path.equals("entity.villager.no")) { + } else if (sound.id().equals(SoundEvents.BLOCK_CHEST_LOCKED.id())) { currentLockCount = 0; activeParticles.clear(); //lock pick finish sound - } else if (path.equals("block.chest.open")) { + } else if (sound.id().equals(SoundEvents.BLOCK_CHEST_OPEN.id())) { //set the needed lock count to the current, so we know how many locks a chest has neededLockCount = Math.min(currentLockCount, 5); currentLockCount = 0; @@ -172,7 +174,7 @@ private static void render(WorldRenderContext context) { //add up all particle within range of active block int addedParticles = 0; for (Vec3d particlePos : activeParticles.keySet()) { - if (particlePos.squaredDistanceTo(chestPos) <= 0.8) { + if (particlePos.isInRange(chestPos, 0.8)) { highlightSpot = highlightSpot.add(particlePos); addedParticles++; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/WishingCompassSolver.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/WishingCompassSolver.java index 4350800e95..4b04430dc5 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/WishingCompassSolver.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/WishingCompassSolver.java @@ -338,7 +338,7 @@ private static boolean useCompass() { case WAITING_FOR_SECOND -> { //only continue if the player is far enough away from the first position to get a better reading - if (startPosOne.squaredDistanceTo(playerPos) < DISTANCE_BETWEEN_USES) { + if (startPosOne.isInRange(playerPos, DISTANCE_BETWEEN_USES)) { CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.moveFurtherMessage")), false); return true; } else { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java index 1e0ddc0028..a4b183f7ab 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java @@ -26,11 +26,6 @@ public class EndHudWidget extends ComponentBasedWidget { private static EndHudWidget instance = null; - public static EndHudWidget getInstance() { - if (instance == null) instance = new EndHudWidget(); - return instance; - } - private static final NumberFormat DECIMAL_FORMAT = NumberFormat.getInstance(Locale.US); private static final ItemStack ENDERMAN_HEAD = new ItemStack(Items.PLAYER_HEAD); private static final ItemStack POPPY = new ItemStack(Items.POPPY); @@ -49,6 +44,10 @@ public EndHudWidget() { this.update(); } + public static EndHudWidget getInstance() { + return instance; + } + @Override public boolean isEnabledIn(Location location) { return location.equals(Location.THE_END) && SkyblockerConfigManager.get().otherLocations.end.hudEnabled; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java b/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java index 7c51ca127f..bbbc29e47b 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java @@ -74,14 +74,6 @@ public static void init() { return ActionResult.PASS; }); - - /*HudRenderEvents.AFTER_MAIN_HUD.register((drawContext, tickCounter) -> { - if (!Utils.isInTheEnd()) return; - if (!SkyblockerConfigManager.get().otherLocations.end.hudEnabled) return; - - EndHudWidget.INSTANCE.render(drawContext, SkyblockerConfigManager.get().uiAndVisuals.tabHud.enableHudBackground); - });*/ - ClientChunkEvents.CHUNK_LOAD.register((world, chunk) -> { String lowerCase = Utils.getIslandArea().toLowerCase(); if (Utils.isInTheEnd() || lowerCase.contains("the end") || lowerCase.contains("dragon's nest")) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobBoundingBoxes.java b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobBoundingBoxes.java index fe2700549b..1f318e21c6 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobBoundingBoxes.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobBoundingBoxes.java @@ -4,7 +4,7 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.SlayersConfig; import de.hysky.skyblocker.skyblock.dungeon.LividColor; -import de.hysky.skyblocker.skyblock.slayers.SlayerEntitiesGlow; +import de.hysky.skyblocker.skyblock.slayers.SlayerManager; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.FrustumUtils; import de.hysky.skyblocker.utils.render.RenderHelper; @@ -12,7 +12,6 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; -import net.minecraft.client.MinecraftClient; import net.minecraft.entity.Entity; import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.entity.player.PlayerEntity; @@ -45,17 +44,10 @@ public static boolean shouldDrawMobBoundingBox(Entity entity) { }; } - if (SkyblockerConfigManager.get().slayers.highlightMinis == SlayersConfig.HighlightSlayerEntities.HITBOX - && entity instanceof ArmorStandEntity le && SlayerEntitiesGlow.isSlayerMiniMob(le)) { + if (SlayerManager.shouldGlow(entity, SlayersConfig.HighlightSlayerEntities.HITBOX)) { return true; } - if (SkyblockerConfigManager.get().slayers.highlightBosses == SlayersConfig.HighlightSlayerEntities.HITBOX - && entity instanceof ArmorStandEntity le) { - return le.getDisplayName().getString().contains(MinecraftClient.getInstance().getSession().getUsername()) || - entity.getDisplayName().getString().contains("Ⓣ") || entity.getDisplayName().getString().contains("Ⓐ"); - } - return false; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java index 6dc6251830..8c3086bcd8 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java @@ -5,13 +5,13 @@ import de.hysky.skyblocker.config.configs.SlayersConfig; import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager; import de.hysky.skyblocker.skyblock.crimson.kuudra.Kuudra; -import de.hysky.skyblocker.skyblock.crimson.slayer.AttunementColors; import de.hysky.skyblocker.skyblock.dungeon.LividColor; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; import de.hysky.skyblocker.skyblock.end.TheEnd; -import de.hysky.skyblocker.skyblock.slayers.SlayerEntitiesGlow; +import de.hysky.skyblocker.skyblock.slayers.SlayerManager; +import de.hysky.skyblocker.skyblock.slayers.SlayerType; +import de.hysky.skyblocker.skyblock.slayers.boss.demonlord.AttunementColors; import de.hysky.skyblocker.utils.ItemUtils; -import de.hysky.skyblocker.utils.SlayerUtils; import de.hysky.skyblocker.utils.Utils; import net.minecraft.client.MinecraftClient; import net.minecraft.entity.Entity; @@ -63,6 +63,11 @@ private static boolean computeShouldMobGlow(Entity entity) { }; } + // Slayer + if (SlayerManager.shouldGlow(entity, SlayersConfig.HighlightSlayerEntities.GLOW)) { + return true; + } + return switch (entity) { // Rift Blobbercyst case PlayerEntity p when Utils.isInTheRift() && p.getName().getString().equals("Blobbercyst ") -> SkyblockerConfigManager.get().otherLocations.rift.blobbercystGlow; @@ -74,18 +79,14 @@ private static boolean computeShouldMobGlow(Entity entity) { case MagmaCubeEntity magmaCube when Utils.isInKuudra() -> SkyblockerConfigManager.get().crimsonIsle.kuudra.kuudraGlow && magmaCube.getSize() == Kuudra.KUUDRA_MAGMA_CUBE_SIZE; // Special Zealot && Slayer (Mini)Boss - case EndermanEntity enderman when Utils.isInTheEnd() -> TheEnd.isSpecialZealot(enderman) || SlayerEntitiesGlow.shouldGlow(enderman.getUuid()); - case ZombieEntity zombie when !(zombie instanceof ZombifiedPiglinEntity) && SlayerUtils.isInSlayerQuestType(SlayerUtils.REVENANT) -> SlayerEntitiesGlow.shouldGlow(zombie.getUuid()); - case SpiderEntity spider when SlayerUtils.isInSlayerQuestType(SlayerUtils.TARA) -> SlayerEntitiesGlow.shouldGlow(spider.getUuid()); - case WolfEntity wolf when SlayerUtils.isInSlayerQuestType(SlayerUtils.SVEN) -> SlayerEntitiesGlow.shouldGlow(wolf.getUuid()); - case BlazeEntity blaze when SlayerUtils.isInSlayerQuestType(SlayerUtils.DEMONLORD) -> SlayerEntitiesGlow.shouldGlow(blaze.getUuid()); + case EndermanEntity enderman when Utils.isInTheEnd() -> TheEnd.isSpecialZealot(enderman); // Enderman Slayer's Nukekubi Skulls - case ArmorStandEntity armorStand when Utils.isInTheEnd() && armorStand.isMarker() && SlayerUtils.isInSlayer() && isNukekubiHead(armorStand) -> SkyblockerConfigManager.get().slayers.endermanSlayer.highlightNukekubiHeads; + case ArmorStandEntity armorStand when Utils.isInTheEnd() && armorStand.isMarker() && SlayerManager.isInSlayer() && isNukekubiHead(armorStand) -> SkyblockerConfigManager.get().slayers.endermanSlayer.highlightNukekubiHeads; // Blaze Slayer's Demonic minions - case WitherSkeletonEntity e when SkyblockerConfigManager.get().slayers.highlightBosses == SlayersConfig.HighlightSlayerEntities.GLOW -> SlayerUtils.isInSlayerType(SlayerUtils.DEMONLORD) && e.distanceTo(MinecraftClient.getInstance().player) <= 15; - case ZombifiedPiglinEntity e when SkyblockerConfigManager.get().slayers.highlightBosses == SlayersConfig.HighlightSlayerEntities.GLOW -> SlayerUtils.isInSlayerType(SlayerUtils.DEMONLORD) && e.distanceTo(MinecraftClient.getInstance().player) <= 15; + case WitherSkeletonEntity e when SkyblockerConfigManager.get().slayers.highlightBosses == SlayersConfig.HighlightSlayerEntities.GLOW -> SlayerManager.isInSlayerType(SlayerType.DEMONLORD) && e.distanceTo(MinecraftClient.getInstance().player) <= 15; + case ZombifiedPiglinEntity e when SkyblockerConfigManager.get().slayers.highlightBosses == SlayersConfig.HighlightSlayerEntities.GLOW -> SlayerManager.isInSlayerType(SlayerType.DEMONLORD) && e.distanceTo(MinecraftClient.getInstance().player) <= 15; default -> false; }; @@ -142,10 +143,10 @@ public static int getGlowColor(Entity entity) { case MagmaCubeEntity magmaCube when Utils.isInKuudra() -> 0xf7510f; // Blaze Slayer Attunement Colours - case ArmorStandEntity armorStand when SlayerUtils.isInSlayerQuestType(SlayerUtils.DEMONLORD) -> AttunementColors.getColor(armorStand); - case BlazeEntity blaze when SlayerUtils.isInSlayer() -> AttunementColors.getColor(blaze); - case ZombifiedPiglinEntity piglin when SlayerUtils.isInSlayer() -> AttunementColors.getColor(piglin); - case WitherSkeletonEntity wSkelly when SlayerUtils.isInSlayer() -> AttunementColors.getColor(wSkelly); + case ArmorStandEntity armorStand when SlayerManager.isInSlayerType(SlayerType.DEMONLORD) -> AttunementColors.getColor(armorStand); + case BlazeEntity blaze when SlayerManager.isInSlayerType(SlayerType.DEMONLORD) -> AttunementColors.getColor(blaze); + case ZombifiedPiglinEntity piglin when SlayerManager.isInSlayerType(SlayerType.DEMONLORD) -> AttunementColors.getColor(piglin); + case WitherSkeletonEntity wSkelly when SlayerManager.isInSlayerType(SlayerType.DEMONLORD) -> AttunementColors.getColor(wSkelly); default -> 0xf57738; }; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/rift/TheRift.java b/src/main/java/de/hysky/skyblocker/skyblock/rift/TheRift.java index 2f5d4938c9..09385bd4a3 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/rift/TheRift.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/rift/TheRift.java @@ -20,8 +20,5 @@ public static void init() { ClientReceiveMessageEvents.GAME.register(EnigmaSouls::onMessage); ClientCommandRegistrationCallback.EVENT.register(EnigmaSouls::registerCommands); Scheduler.INSTANCE.scheduleCyclic(EffigyWaypoints::updateEffigies, SkyblockerConfigManager.get().slayers.vampireSlayer.effigyUpdateFrequency); - Scheduler.INSTANCE.scheduleCyclic(TwinClawsIndicator::updateIce, SkyblockerConfigManager.get().slayers.vampireSlayer.holyIceUpdateFrequency); - Scheduler.INSTANCE.scheduleCyclic(ManiaIndicator::updateMania, SkyblockerConfigManager.get().slayers.vampireSlayer.maniaUpdateFrequency); - Scheduler.INSTANCE.scheduleCyclic(StakeIndicator::updateStake, SkyblockerConfigManager.get().slayers.vampireSlayer.steakStakeUpdateFrequency); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerBossBars.java b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerBossBars.java index c33d7af859..a7a15b226d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerBossBars.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerBossBars.java @@ -1,102 +1,104 @@ 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.Locale; 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 final UUID UUID = java.util.UUID.randomUUID(); + private static final Pattern HEALTH_PATTERN = Pattern.compile("(\\d{1,3}(?:,\\d{3})*(?:\\.\\d+)?[kM]?)(?=❤)"); + private static final long UPDATE_INTERVAL = 400; + private static int bossMaxHealth = -1; + private static long lastUpdateTime = 0; + private static ClientBossBar bossBar; /** * 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; + 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; - } + // Reset if no slayer + if (!SlayerManager.isBossSpawned()) { + 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)); - } + // Update boss max health + ArmorStandEntity bossArmorStand = SlayerManager.getSlayerBossArmorStand(); + if (bossArmorStand != null && bossMaxHealth == -1) { + Matcher maxHealthMatcher = HEALTH_PATTERN.matcher(bossArmorStand.getName().getString()); + if (maxHealthMatcher.find()) bossMaxHealth = convertToInt(maxHealthMatcher.group(0)); + } - return bossBar != null || SlayerUtils.getSlayerArmorStandEntity() != null; - } + return bossBar != null || bossArmorStand != 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); + public static ClientBossBar updateBossBar() { + ArmorStandEntity slayer = SlayerManager.getSlayerBossArmorStand(); + 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; - } + 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()); - } + 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; - } + return bossBar; + } - private static int convertToInt(String value) { - if (value == null || value.isEmpty()) { - return 0; - } + private static int convertToInt(String value) { + if (value == null || value.isEmpty()) { + return 0; + } - value = value.trim().toLowerCase(); - double multiplier = 1.0; + value = value.replace(",", "").trim().toLowerCase(Locale.ENGLISH); + 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); - } + 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; - } - } + try { + double numericValue = Double.parseDouble(value); + return (int) (numericValue * multiplier); + } catch (NumberFormatException e) { + return 0; + } + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerEntitiesGlow.java b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerEntitiesGlow.java deleted file mode 100644 index cf3ee209bb..0000000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerEntitiesGlow.java +++ /dev/null @@ -1,174 +0,0 @@ -package de.hysky.skyblocker.skyblock.slayers; - -import de.hysky.skyblocker.annotations.Init; -import de.hysky.skyblocker.utils.SlayerUtils; -import de.hysky.skyblocker.utils.render.RenderHelper; -import de.hysky.skyblocker.utils.scheduler.Scheduler; -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; -import net.minecraft.client.MinecraftClient; -import net.minecraft.entity.Entity; -import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.decoration.ArmorStandEntity; -import net.minecraft.entity.mob.*; -import net.minecraft.entity.passive.WolfEntity; -import net.minecraft.util.math.Box; -import org.jetbrains.annotations.Nullable; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - -public class SlayerEntitiesGlow { - private static final Map SLAYER_MINI_NAMES = Map.ofEntries( - Map.entry("Revenant Sycophant", SlayerUtils.REVENANT), - Map.entry("Revenant Champion", SlayerUtils.REVENANT), - Map.entry("Deformed Revenant", SlayerUtils.REVENANT), - Map.entry("Atoned Champion", SlayerUtils.REVENANT), - Map.entry("Atoned Revenant", SlayerUtils.REVENANT), - Map.entry("Tarantula Vermin", SlayerUtils.TARA), - Map.entry("Tarantula Beast", SlayerUtils.TARA), - Map.entry("Mutant Tarantula", SlayerUtils.TARA), - Map.entry("Pack Enforcer", SlayerUtils.SVEN), - Map.entry("Sven Follower", SlayerUtils.SVEN), - Map.entry("Sven Alpha", SlayerUtils.SVEN), - Map.entry("Voidling Devotee", SlayerUtils.VOIDGLOOM), - Map.entry("Voidling Radical", SlayerUtils.VOIDGLOOM), - Map.entry("Voidcrazed Maniac", SlayerUtils.VOIDGLOOM), - Map.entry("Flare Demon", SlayerUtils.DEMONLORD), - Map.entry("Kindleheart Demon", SlayerUtils.DEMONLORD), - Map.entry("Burningsoul Demon", SlayerUtils.DEMONLORD) - ); - - @Init - public static void init() { - ClientPlayConnectionEvents.JOIN.register((ignore, ignore2, ignore3) -> clearGlow()); - } - private static final Map> SLAYER_MOB_TYPE = Map.of( - SlayerUtils.REVENANT, ZombieEntity.class, - SlayerUtils.TARA, SpiderEntity.class, - SlayerUtils.SVEN, WolfEntity.class, - SlayerUtils.VOIDGLOOM, EndermanEntity.class, - SlayerUtils.DEMONLORD, BlazeEntity.class - ); - - private static final Set MOBS_TO_GLOW = new HashSet<>(); - - /** - * ARMORSTAND_TO_MOBS_TO_GLOW tracks if an armor stand already has an associated mob entity. This is used for trying to dedupe glows, - * where an armor stand has detected multiple candidates as its associated mob entity - in a vain attempt to reduce the amount of false positives - */ - private static final ConcurrentHashMap ARMORSTAND_TO_MOBS_TO_GLOW = new ConcurrentHashMap<>(); - - public static boolean shouldGlow(UUID entityUUID) { - return MOBS_TO_GLOW.contains(entityUUID); - } - - public static boolean isSlayer(LivingEntity e) { - return SlayerUtils.isInSlayer() && SlayerUtils.getEntityArmorStands(e, 2.5f).stream().anyMatch(entity -> - entity.getDisplayName().getString().contains(MinecraftClient.getInstance().getSession().getUsername())); - } - - public static boolean isSlayerMiniMob(LivingEntity entity) { - if (entity.getCustomName() == null) return false; - String entityName = entity.getCustomName().getString(); - return SLAYER_MINI_NAMES.keySet().stream().anyMatch(slayerMobName -> entityName.contains(slayerMobName) && SlayerUtils.isInSlayerQuestType(SLAYER_MINI_NAMES.get(slayerMobName))); - } - - public static Box getSlayerMobBoundingBox(LivingEntity entity) { - return switch (SlayerUtils.getSlayerType()) { - case SlayerUtils.REVENANT -> new Box(entity.getX() - 0.4, entity.getY() - 0.1, entity.getZ() - 0.4, entity.getX() + 0.4, entity.getY() - 2.2, entity.getZ() + 0.4); - case SlayerUtils.TARA -> new Box(entity.getX() - 0.9, entity.getY() - 0.2, entity.getZ() - 0.9, entity.getX() + 0.9, entity.getY() - 1.2, entity.getZ() + 0.9); - case SlayerUtils.VOIDGLOOM -> new Box(entity.getX() - 0.4, entity.getY() - 0.2, entity.getZ() - 0.4, entity.getX() + 0.4, entity.getY() - 3, entity.getZ() + 0.4); - case SlayerUtils.SVEN -> new Box(entity.getX() - 0.5, entity.getY() - 0.1, entity.getZ() - 0.5, entity.getX() + 0.5, entity.getY() - 1, entity.getZ() + 0.5); - default -> entity.getBoundingBox(); - }; - } - - /** - *

Finds the closest matching MobEntity for the armorStand using entityClass and armorStand age difference to filter - * out impossible candidates, returning the closest mob of those remaining in the search box by block distance

- * - * @param entityClass the mob type of the Slayer (i.e. ZombieEntity.class) - * @param armorStand the entity that contains the display name of the Slayer (mini)boss - */ - private static MobEntity findClosestMobEntity(Class entityClass, ArmorStandEntity armorStand) { - List mobEntities = armorStand.getWorld().getEntitiesByClass(entityClass, armorStand.getDimensions(null) - .getBoxAt(armorStand.getPos()).expand(0.3f, 1.5f, 0.3f), entity -> !entity.isDead()) - .stream() - .filter(SlayerEntitiesGlow::isValidSlayerMob) - .sorted(Comparator.comparingDouble(e -> e.squaredDistanceTo(armorStand))) - .collect(Collectors.toList()); - - return switch (mobEntities.size()) { - case 0 -> null; - case 1 -> mobEntities.getFirst(); - default -> mobEntities.stream() - .filter(entity -> entity.age > armorStand.age - 4 && entity.age < armorStand.age + 4) - .findFirst() - .orElse(mobEntities.getFirst()); - }; - } - - /** - * Use this func to add checks to prevent accidental highlights - * i.e. Cavespider extends spider and thus will highlight the broodfather's head pet instead and - */ - private static boolean isValidSlayerMob(MobEntity entity) { - return !(entity instanceof CaveSpiderEntity) && !(entity.isBaby()); - } - - /** - *

Adds the Entity UUID to the Hashset of Slayer Mobs to glow

- * - * @param armorStand the entity that contains the display name of the Slayer (mini)boss - */ - public static void setSlayerMobGlow(ArmorStandEntity armorStand) { - String slayerType = SlayerUtils.getSlayerType(); - Class entityClass = SLAYER_MOB_TYPE.get(slayerType); - if (entityClass != null) { - MobEntity closestEntity = findClosestMobEntity(entityClass, armorStand); - if (closestEntity != null) { - UUID uuid = ARMORSTAND_TO_MOBS_TO_GLOW.putIfAbsent(armorStand.getUuid(), closestEntity.getUuid()); - if (uuid != null && closestEntity.getUuid() != uuid && closestEntity.age < 80) { - Scheduler.INSTANCE.schedule(() -> recalculateMobGlow(armorStand, entityClass, uuid), 30, true); - } - MOBS_TO_GLOW.add(closestEntity.getUuid()); - } - } - } - - /** - * This method attempts self-correct by finding the true slayer mob if there's 2 candidates - * @param armorStand the armor stand we know to be a slayer mob - * @param entityClass the java class of the entity we know the armor stand to belong to - * @param oldUUID the uuid of the first detected slayer mob - */ - private static void recalculateMobGlow(ArmorStandEntity armorStand, Class entityClass, UUID oldUUID) { - MobEntity entity = findClosestMobEntity(entityClass, armorStand); - if (entity.getUuid() != oldUUID) { - RenderHelper.runOnRenderThread(() -> { - MOBS_TO_GLOW.add(entity.getUuid()); - MOBS_TO_GLOW.remove(ARMORSTAND_TO_MOBS_TO_GLOW.put(armorStand.getUuid(), entity.getUuid())); - }); - - } - } - - public static void onEntityDeath(@Nullable Entity entity) { - if (entity != null && entity.getUuid() != null) { - MOBS_TO_GLOW.remove(entity.getUuid()); - } - } - - public static void cleanupArmorstand(@Nullable ArmorStandEntity entity) { - if (entity != null && entity.getUuid() != null) { - ARMORSTAND_TO_MOBS_TO_GLOW.remove(entity.getUuid()); - } - } - - private static void clearGlow() { - MOBS_TO_GLOW.clear(); - ARMORSTAND_TO_MOBS_TO_GLOW.clear(); - } - -} \ No newline at end of file diff --git a/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerManager.java b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerManager.java new file mode 100644 index 0000000000..7e1fbf7311 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerManager.java @@ -0,0 +1,392 @@ +package de.hysky.skyblocker.skyblock.slayers; + +import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.config.configs.SlayersConfig; +import de.hysky.skyblocker.events.SkyblockEvents; +import de.hysky.skyblocker.skyblock.slayers.boss.vampire.ManiaIndicator; +import de.hysky.skyblocker.skyblock.slayers.boss.vampire.StakeIndicator; +import de.hysky.skyblocker.skyblock.slayers.boss.vampire.TwinClawsIndicator; +import de.hysky.skyblocker.utils.Location; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.mayor.MayorUtils; +import de.hysky.skyblocker.utils.render.title.Title; +import de.hysky.skyblocker.utils.render.title.TitleContainer; +import de.hysky.skyblocker.utils.scheduler.Scheduler; +import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.entity.mob.CaveSpiderEntity; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.sound.SoundEvents; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.math.Box; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Holds all information related to slayer. + *

{@link #onChatMessage(Text, boolean)} detects slayer messages and updates the state of the slayer quest. + * {@link #checkSlayerBoss(ArmorStandEntity)} processes the given armor stand and detects if it is a slayer boss or miniboss.

+ */ +public class SlayerManager { + private static final Logger LOGGER = LoggerFactory.getLogger(SlayerManager.class); + private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); + private static final Pattern SLAYER_PATTERN = Pattern.compile("Revenant Horror|Atoned Horror|Tarantula Broodfather|Sven Packmaster|Voidgloom Seraph|Inferno Demonlord|Bloodfiend"); + private static final Pattern SLAYER_TIER_PATTERN = Pattern.compile("^(Revenant Horror|Tarantula Broodfather|Sven Packmaster|Voidgloom Seraph|Inferno Demonlord|Riftstalker Bloodfiend)\\s+(I|II|III|IV|V)$"); + private static final Pattern PATTERN_XP_NEEDED = Pattern.compile("\\s*(Wolf|Zombie|Spider|Enderman|Blaze|Vampire) Slayer LVL ([0-9]) - (?:Next LVL in ([\\d,]+) XP!|LVL MAXED OUT!)\\s*"); + private static final Pattern PATTERN_LVL_UP = Pattern.compile("\\s*LVL UP! ➜ (Wolf|Zombie|Spider|Enderman|Blaze|Vampire) Slayer LVL [1-9]\\s*"); + private static final Title MINIBOSS_SPAWN = new Title(Text.translatable("skyblocker.slayer.miniBossSpawnAlert").formatted(Formatting.RED)); + private static final Title BOSS_SPAWN = new Title(Text.translatable("skyblocker.slayer.bossSpawnAlert").formatted(Formatting.RED)); + private static SlayerQuest slayerQuest; + private static BossFight bossFight; + + @Init + public static void init() { + ClientReceiveMessageEvents.GAME.register(SlayerManager::onChatMessage); + SkyblockEvents.LOCATION_CHANGE.register(SlayerManager::onLocationChange); + Scheduler.INSTANCE.scheduleCyclic(TwinClawsIndicator::updateIce, SkyblockerConfigManager.get().slayers.vampireSlayer.holyIceUpdateFrequency); + Scheduler.INSTANCE.scheduleCyclic(ManiaIndicator::updateMania, SkyblockerConfigManager.get().slayers.vampireSlayer.maniaUpdateFrequency); + Scheduler.INSTANCE.scheduleCyclic(StakeIndicator::updateStake, SkyblockerConfigManager.get().slayers.vampireSlayer.steakStakeUpdateFrequency); + } + + private static void onLocationChange(Location location) { + if (isBossSpawned()) { + slayerQuest = null; + bossFight = null; + } + } + + private static void onChatMessage(Text text, boolean overlay) { + if (!Utils.isOnSkyblock() || overlay) return; + String message = text.getString(); + + switch (message.replaceFirst("^\\s+", "")) { + case "Your Slayer Quest has been cancelled!", "SLAYER QUEST FAILED!" -> { + slayerQuest = null; + bossFight = null; + return; + } + case "SLAYER QUEST STARTED!" -> { + if (slayerQuest == null) slayerQuest = new SlayerQuest(); + bossFight = null; + return; + } + case "NICE! SLAYER BOSS SLAIN!" -> { + if (slayerQuest != null && bossFight != null) { + bossFight.slain = true; + SlayerTimer.onBossDeath(bossFight.bossSpawnTime); + } + return; + } + case "SLAYER QUEST COMPLETE!" -> { + if (slayerQuest != null && bossFight != null && !bossFight.slain) + SlayerTimer.onBossDeath(bossFight.bossSpawnTime); + bossFight = null; + return; + } + } + + if (slayerQuest == null) return; + Matcher matcherNextLvl = PATTERN_XP_NEEDED.matcher(message); + Matcher matcherLvlUp = PATTERN_LVL_UP.matcher(message); + + if (matcherNextLvl.matches()) { + if (message.contains("LVL MAXED OUT")) { + slayerQuest.level = message.contains("Vampire") ? 5 : 9; + slayerQuest.xpRemaining = -1; + slayerQuest.bossesNeeded = -1; + } else { + String xpRemainingStr = matcherNextLvl.group(3); + if (xpRemainingStr != null) { + slayerQuest.level = Integer.parseInt(matcherNextLvl.group(2)); + slayerQuest.xpRemaining = Integer.parseInt(xpRemainingStr.replace(",", "").trim()); + calculateBossesNeeded(); + } + } + } else if (matcherLvlUp.matches()) { + slayerQuest.level = Integer.parseInt(message.replaceAll("(\\d+).+", "$1")); + } + } + + public static void calculateBossesNeeded() { + int tier = slayerQuest.slayerTier.ordinal(); + if (tier == 0) { + slayerQuest.bossesNeeded = -1; + return; + } + + int xpPerTier = slayerQuest.slayerType.xpPerTier[tier - 1]; + + if (MayorUtils.getMayor().perks().stream().anyMatch(perk -> perk.name().equals("Slayer XP Buff")) || MayorUtils.getMinister().perk().name().equals("Slayer XP Buff")) { + xpPerTier = (int) (xpPerTier * 1.25); + } + + slayerQuest.bossesNeeded = (int) Math.ceil((double) slayerQuest.xpRemaining / xpPerTier); + } + + public static void getSlayerBossInfo(boolean checkStatus) { + if (checkStatus && slayerQuest == null) return; + try { + for (String line : Utils.STRING_SCOREBOARD) { + Matcher matcher = SLAYER_TIER_PATTERN.matcher(line); + if (matcher.find()) { + if (slayerQuest == null || !matcher.group(1).equals(slayerQuest.slayerType.bossName) || !matcher.group(2).equals(slayerQuest.slayerTier.name())) { + slayerQuest = new SlayerQuest(); + } + slayerQuest.slayerType = SlayerType.fromBossName(matcher.group(1)); + slayerQuest.slayerTier = SlayerTier.valueOf(matcher.group(2)); + } else if (line.equals("Slay the boss!") && !isBossSpawned()) { + bossFight = new BossFight(null); + } + } + } catch (IndexOutOfBoundsException e) { + LOGGER.error("[Skyblocker] Failed to get slayer boss info", e); + } + } + + /** + * Gets The slayer info from scoreboard when player joins SkyBlock + */ + public static void getSlayerInfoOnJoin() { + Scheduler.INSTANCE.schedule(() -> getSlayerBossInfo(false), 20 * 2); //2 seconds + } + + /** + * Checks if the given armor stand is a slayer boss or miniboss and saves it to the corresponding field. + *

This is the main mechanism for detecting slayer bosses and minibosses. All other features rely on information processed here. + * + * @implNote The resulting mob entity (not the armor stand entity) might not be entirely accurate. + * {@link #findClosestMobEntity(EntityType, ArmorStandEntity)} could be modified and run more than once to ensure the correct entity is found. + */ + public static void checkSlayerBoss(ArmorStandEntity armorStand) { + if (slayerQuest == null || (isBossSpawned() && bossFight.boss != null) || !armorStand.hasCustomName()) return; + if (armorStand.getName().getString().contains(CLIENT.getSession().getUsername())) { + for (Entity otherArmorStands : getEntityArmorStands(armorStand, 1.5f)) { + Matcher matcher = SLAYER_PATTERN.matcher(otherArmorStands.getName().getString()); + if (matcher.find()) { + if (bossFight != null && bossFight.boss == null) { + bossFight.findBoss((ArmorStandEntity) otherArmorStands); + return; + } + bossFight = new BossFight((ArmorStandEntity) otherArmorStands); + return; + } + } + } + if (!armorStand.isInRange(CLIENT.player, 15)) return; + Arrays.stream(SlayerType.values()).forEach(type -> type.minibossNames.forEach((name) -> { + if (armorStand.getName().getString().contains(name) && isInSlayerQuestType(type)) { + slayerQuest.onMiniboss(armorStand, type); + } + })); + } + + /** + * Gets nearby armor stands with custom names. Used to find other armor stands showing a different line of text above a slayer boss. + */ + public static List getEntityArmorStands(Entity entity, float expandY) { + return entity.getEntityWorld().getOtherEntities(entity, entity.getBoundingBox().expand(0.1F, expandY, 0.1F), x -> x instanceof ArmorStandEntity && x.hasCustomName()); + } + + /** + *

Finds the closest matching Entity for the armorStand using entityType and armorStand age difference to filter + * out impossible candidates, returning the closest entity of those remaining in the search box by block distance

+ * + * @param entityType the entity type of the Slayer (i.e. ZombieEntity.class) + * @param armorStand the entity that contains the display name of the Slayer (mini)boss + * @implNote This method is not perfect. Possible improvements could be sort by x and z distance only (ignore y difference). + */ + public static T findClosestMobEntity(EntityType entityType, ArmorStandEntity armorStand) { + List mobEntities = armorStand.getWorld().getEntitiesByType(entityType, armorStand.getBoundingBox().expand(0, 1.5f, 0), SlayerManager::isValidSlayerMob); + mobEntities.sort(Comparator.comparingDouble(armorStand::squaredDistanceTo)); + + return switch (mobEntities.size()) { + case 0 -> null; + case 1 -> mobEntities.getFirst(); + default -> mobEntities.stream() + .min(Comparator.comparingInt(entity -> Math.abs(entity.age - armorStand.age))) + .get(); + }; + } + + /** + * Use this func to add checks to prevent accidental highlights + * i.e. Cavespider extends spider and thus will highlight the broodfather's head pet instead and + */ + private static boolean isValidSlayerMob(Entity entity) { + return entity.isAlive() && // entity is alive + !(entity instanceof MobEntity mob && mob.isBaby()) && // entity is not a baby + !(entity instanceof CaveSpiderEntity); // entity is not a cave spider + } + + /** + * Returns whether the given entity is a slayer miniboss or boss and should be highlighted based on the given highlight type. + */ + public static boolean shouldGlow(Entity entity, SlayersConfig.HighlightSlayerEntities highlightType) { + if (!isInSlayer()) return false; + if (SkyblockerConfigManager.get().slayers.highlightMinis == highlightType && isInSlayer() && getSlayerQuest().minibosses.contains(entity)) return true; + return SkyblockerConfigManager.get().slayers.highlightBosses == highlightType && isBossSpawned() && getBossFight().boss == entity; + } + + /** + * Returns the highlight bounding box for the given slayer boss armor stand entity. + * It's slightly larger and lower than the armor stand's bounding box. + */ + public static Box getSlayerMobBoundingBox(ArmorStandEntity armorStand) { + return switch (getSlayerType()) { + case SlayerType.REVENANT -> new Box(armorStand.getX() - 0.4, armorStand.getY() - 0.1, armorStand.getZ() - 0.4, armorStand.getX() + 0.4, armorStand.getY() - 2.2, armorStand.getZ() + 0.4); + case SlayerType.TARANTULA -> new Box(armorStand.getX() - 0.9, armorStand.getY() - 0.2, armorStand.getZ() - 0.9, armorStand.getX() + 0.9, armorStand.getY() - 1.2, armorStand.getZ() + 0.9); + case SlayerType.VOIDGLOOM -> new Box(armorStand.getX() - 0.4, armorStand.getY() - 0.2, armorStand.getZ() - 0.4, armorStand.getX() + 0.4, armorStand.getY() - 3, armorStand.getZ() + 0.4); + case SlayerType.SVEN -> new Box(armorStand.getX() - 0.5, armorStand.getY() - 0.1, armorStand.getZ() - 0.5, armorStand.getX() + 0.5, armorStand.getY() - 1, armorStand.getZ() + 0.5); + case null -> null; + default -> armorStand.getBoundingBox(); + }; + } + + /** + * Checks if the player is currently in a Slayer Quest. + * Note: This does not check whether a boss has spawned. + * + * @return True if the player is in a Slayer Quest; false otherwise. + */ + public static boolean isInSlayer() { + return slayerQuest != null; + } + + /** + * Checks if a Slayer Boss has spawned for the current Slayer Quest. + * + * @return True if the boss has spawned; false otherwise. + */ + public static boolean isBossSpawned() { + return isInSlayer() && bossFight != null; + } + + /** + * Checks if the player is in a Slayer Boss fight of the specified type. + * + * @param slayerType The Slayer type to check against. + * @return True if in a boss fight of the given Slayer type; false otherwise. + */ + public static boolean isInSlayerType(SlayerType slayerType) { + return isBossSpawned() && slayerQuest.slayerType.equals(slayerType); + } + + /** + * Checks if the player is in a Slayer Quest of the specified type, + * but no boss has spawned yet. + * + * @param slayerType The Slayer type to check against. + * @return True if in a Slayer Quest of the given type and waiting for a boss to spawn; false otherwise. + */ + public static boolean isInSlayerQuestType(SlayerType slayerType) { + return !isBossSpawned() && slayerQuest.slayerType.equals(slayerType); + } + + /** + * Gets the current Boss Fight state. + * + * @return The BossFight instance, or null if no boss fight is active. + */ + public static BossFight getBossFight() { + return bossFight; + } + + /** + * Gets the current Slayer Quest details. + * + * @return The SlayerQuest instance, or null if no Slayer Quest is active. + */ + public static SlayerQuest getSlayerQuest() { + return slayerQuest; + } + + /** + * Gets the type of the current Slayer Quest. + * + * @return The SlayerType of the current quest, or null if no quest is active. + */ + public static SlayerType getSlayerType() { + return slayerQuest != null ? slayerQuest.slayerType : null; + } + + /** + * Gets the tier of the current Slayer Quest. + * + * @return The SlayerTier of the current quest, or null if no quest is active. + */ + public static SlayerTier getSlayerTier() { + return slayerQuest != null ? slayerQuest.slayerTier : null; + } + + /** + * Gets the armor stand entity associated with the Slayer boss. + * + * @return The armor stand entity, or null if no boss fight is active. + */ + public static ArmorStandEntity getSlayerBossArmorStand() { + return bossFight != null ? bossFight.bossArmorStand : null; + } + + /** + * Gets the entity representing the Slayer boss. + * + * @return The boss entity, or null if no boss fight is active. + */ + public static Entity getSlayerBoss() { + return bossFight != null ? bossFight.boss : null; + } + + public static class BossFight { + public ArmorStandEntity bossArmorStand; + public Entity boss; + public Instant bossSpawnTime; + public boolean slain = false; + + private BossFight(ArmorStandEntity armorStand) { + findBoss(armorStand); + bossSpawnTime = Instant.now(); + if (SkyblockerConfigManager.get().slayers.bossSpawnAlert) { + TitleContainer.addTitle(BOSS_SPAWN, 20); + CLIENT.player.playSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 0.5f, 0.1f); + } + } + + public void findBoss(ArmorStandEntity armorStand) { + bossArmorStand = armorStand; + boss = armorStand != null ? findClosestMobEntity(slayerQuest.slayerType.mobType, armorStand) : null; + } + } + + public static class SlayerQuest { + public SlayerType slayerType = SlayerType.UNKNOWN; + public SlayerTier slayerTier = SlayerTier.UNKNOWN; + public List minibossesArmorStand = new ArrayList<>(); + public List minibosses = new ArrayList<>(); + public int level; + public int xpRemaining; + public int bossesNeeded; + + private void onMiniboss(ArmorStandEntity armorStand, SlayerType type) { + if (minibossesArmorStand.contains(armorStand)) return; + minibossesArmorStand.add(armorStand); + minibosses.add(findClosestMobEntity(type.mobType, armorStand)); + if (SkyblockerConfigManager.get().slayers.miniBossSpawnAlert) { + TitleContainer.addTitle(SlayerManager.MINIBOSS_SPAWN, 20); + CLIENT.player.playSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 0.5f, 0.1f); + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerTier.java b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerTier.java new file mode 100644 index 0000000000..3bce7bf4bf --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerTier.java @@ -0,0 +1,31 @@ +package de.hysky.skyblocker.skyblock.slayers; + +import com.mojang.serialization.Codec; +import net.minecraft.util.Formatting; +import net.minecraft.util.StringIdentifiable; + +public enum SlayerTier implements StringIdentifiable { + UNKNOWN("unknown", Formatting.WHITE), + I("I", Formatting.GREEN), + II("II", Formatting.YELLOW), + III("III", Formatting.RED), + IV("IV", Formatting.DARK_RED), + V("V", Formatting.DARK_PURPLE); + public static final Codec CODEC = StringIdentifiable.createCodec(SlayerTier::values); + public final String name; + public final Formatting color; + + SlayerTier(String name, Formatting color) { + this.name = name; + this.color = color; + } + + public boolean isUnknown() { + return this == UNKNOWN; + } + + @Override + public String asString() { + return name; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerTimer.java b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerTimer.java new file mode 100644 index 0000000000..2f90ec22e3 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerTimer.java @@ -0,0 +1,117 @@ +package de.hysky.skyblocker.skyblock.slayers; + +import com.google.gson.JsonParser; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.Constants; +import de.hysky.skyblocker.utils.Utils; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.client.MinecraftClient; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +public class SlayerTimer { + private static final Logger LOGGER = LoggerFactory.getLogger(SlayerTimer.class); + private static final Path FILE = SkyblockerMod.CONFIG_DIR.resolve("SlayerPb.json"); + private static final Object2ObjectOpenHashMap>> CACHED_SLAYER_STATS = new Object2ObjectOpenHashMap<>(); + + @Init + public static void init() { + load(); + } + + private static void load() { + CompletableFuture.runAsync(() -> { + try (BufferedReader reader = Files.newBufferedReader(FILE)) { + CACHED_SLAYER_STATS.putAll(SlayerInfo.SERIALIZATION_CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).getOrThrow()); + } catch (NoSuchFileException ignored) { + } catch (Exception e) { + LOGGER.error("[Skyblocker Slayer Cache] Failed to load saved slayer data!", e); + } + }); + } + + private static void save() { + CompletableFuture.runAsync(() -> { + try (BufferedWriter writer = Files.newBufferedWriter(FILE)) { + SkyblockerMod.GSON.toJson(SlayerInfo.SERIALIZATION_CODEC.encodeStart(JsonOps.INSTANCE, CACHED_SLAYER_STATS).getOrThrow(), writer); + } catch (Exception e) { + LOGGER.error("[Skyblocker Slayer Cache] Failed to save slayer data to cache!", e); + } + }); + } + + public static void onBossDeath(Instant startTime) { + if (!SkyblockerConfigManager.get().slayers.slainTime || startTime == null) return; + Instant slainTime = Instant.now(); + long timeElapsed = Duration.between(startTime, slainTime).toMillis(); + String duration = formatTime(timeElapsed); + + long currentPB = getPersonalBest(SlayerManager.getSlayerType(), SlayerManager.getSlayerTier()); + + if (currentPB != -1 && (currentPB > timeElapsed)) { + MinecraftClient.getInstance().player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.slayer.slainTime", Text.literal(duration).formatted(Formatting.YELLOW)).append(" ").append(Text.translatable("skyblocker.slayer.personalBest").formatted(Formatting.LIGHT_PURPLE))), false); + MinecraftClient.getInstance().player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.slayer.previousPB", Text.literal(formatTime(currentPB)).formatted(Formatting.YELLOW))), false); + updateBestTime(SlayerManager.getSlayerType(), SlayerManager.getSlayerTier(), timeElapsed); + } else { + MinecraftClient.getInstance().player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.slayer.slainTime", Text.literal(duration).formatted(Formatting.YELLOW))), false); + if (currentPB == -1) { + updateBestTime(SlayerManager.getSlayerType(), SlayerManager.getSlayerTier(), timeElapsed); + } + } + } + + private static long getPersonalBest(SlayerType slayerType, SlayerTier slayerTier) { + String profileId = Utils.getProfileId(); + Object2ObjectOpenHashMap> profileData = CACHED_SLAYER_STATS.computeIfAbsent(profileId, _uuid -> new Object2ObjectOpenHashMap<>()); + Object2ObjectOpenHashMap typeData = profileData.computeIfAbsent(slayerType, _type -> new Object2ObjectOpenHashMap<>()); + + SlayerInfo currentBest = typeData.get(slayerTier); + return currentBest != null ? currentBest.bestTimeMillis : -1; + } + + private static void updateBestTime(SlayerType slayerType, SlayerTier slayerTier, long timeElapsed) { + String profileId = Utils.getProfileId(); + long nowMillis = System.currentTimeMillis(); + + Object2ObjectOpenHashMap> profileData = CACHED_SLAYER_STATS.computeIfAbsent(profileId, _uuid -> new Object2ObjectOpenHashMap<>()); + Object2ObjectOpenHashMap typeData = profileData.computeIfAbsent(slayerType, _type -> new Object2ObjectOpenHashMap<>()); + SlayerInfo newInfo = new SlayerInfo(timeElapsed, nowMillis); + + typeData.put(slayerTier, newInfo); + save(); + } + + private static String formatTime(long millis) { + return String.format("%.2fs", millis / 1000.0); + } + + public record SlayerInfo(long bestTimeMillis, long dateMillis) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.LONG.fieldOf("bestTimeMillis").forGetter(SlayerInfo::bestTimeMillis), + Codec.LONG.fieldOf("dateMillis").forGetter(SlayerInfo::dateMillis) + ).apply(instance, SlayerInfo::new)); + + private static final Codec>>> SERIALIZATION_CODEC = Codec.unboundedMap(Codec.STRING, + Codec.unboundedMap(SlayerType.CODEC, + Codec.unboundedMap(SlayerTier.CODEC, CODEC).xmap(Object2ObjectOpenHashMap::new, Function.identity()) + ).xmap(Object2ObjectOpenHashMap::new, Function.identity()) + ).xmap(Object2ObjectOpenHashMap::new, Function.identity()); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerType.java b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerType.java new file mode 100644 index 0000000000..08cb513c8e --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerType.java @@ -0,0 +1,64 @@ +package de.hysky.skyblocker.skyblock.slayers; + +import com.mojang.serialization.Codec; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.util.StringIdentifiable; + +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public enum SlayerType implements StringIdentifiable { + REVENANT("revenant", EntityType.ZOMBIE, "Revenant Horror", new ItemStack(Items.ROTTEN_FLESH), new int[]{5, 25, 100, 500, 1500}, new int[]{5, 15, 200, 1000, 5000, 20000, 100000, 400000, 1000000}, List.of("Revenant Sycophant", "Revenant Champion", "Deformed Revenant", "Atoned Champion", "Atoned Revenant")), + TARANTULA("tarantula", EntityType.SPIDER, "Tarantula Broodfather", new ItemStack(Items.STRING), new int[]{5, 25, 100, 500, 1500}, new int[]{5, 25, 200, 1000, 5000, 20000, 100000, 400000, 1000000}, List.of("Tarantula Vermin", "Tarantula Beast", "Mutant Tarantula")), + SVEN("sven", EntityType.WOLF, "Sven Packmaster", new ItemStack(Items.MUTTON), new int[]{5, 25, 100, 500, 1500}, new int[]{10, 30, 250, 1500, 5000, 20000, 100000, 400000, 1000000}, List.of("Pack Enforcer", "Sven Follower", "Sven Alpha")), + VOIDGLOOM("voidgloom", EntityType.ENDERMAN, "Voidgloom Seraph", new ItemStack(Items.ENDER_PEARL), new int[]{5, 25, 100, 500, 1500}, new int[]{10, 30, 250, 1500, 5000, 20000, 100000, 400000, 1000000}, List.of("Voidling Devotee", "Voidling Radical", "Voidcrazed Maniac")), + VAMPIRE("vampire", EntityType.PLAYER, "Riftstalker Bloodfiend", new ItemStack(Items.REDSTONE), new int[]{5, 25, 100, 500, 1500}, new int[]{20, 75, 240, 840, 2400}, List.of()), + DEMONLORD("demonlord", EntityType.BLAZE, "Inferno Demonlord", new ItemStack(Items.BLAZE_POWDER), new int[]{5, 25, 100, 500, 1500}, new int[]{10, 30, 250, 1500, 5000, 20000, 100000, 400000, 1000000}, List.of("Flare Demon", "Kindleheart Demon", "Burningsoul Demon")), + UNKNOWN("unknown", null, "Unknown", new ItemStack(Items.BARRIER), new int[]{}, new int[]{}, List.of()); + + public static final Codec CODEC = StringIdentifiable.createCodec(SlayerType::values); + public final String name; + public final EntityType mobType; + public final String bossName; + public final ItemStack icon; + public final int maxLevel; + public final int[] xpPerTier; + public final int[] levelMilestones; + public final List minibossNames; + private static final Map BOSS_NAME_TO_TYPE = new HashMap<>(); + + static { + for (SlayerType type : values()) { + BOSS_NAME_TO_TYPE.put(type.bossName.toLowerCase(Locale.ENGLISH), type); + } + } + + SlayerType(String name, EntityType mobType, String bossName, ItemStack icon, int[] xpPerTier, int[] levelMilestones, List minibossNames) { + this.name = name; + this.mobType = mobType; + this.bossName = bossName; + this.icon = icon; + this.maxLevel = levelMilestones.length; + this.xpPerTier = xpPerTier; + this.levelMilestones = levelMilestones; + this.minibossNames = minibossNames; + } + + public static SlayerType fromBossName(String bossName) { + return BOSS_NAME_TO_TYPE.getOrDefault(bossName.toLowerCase(), UNKNOWN); + } + + public boolean isUnknown() { + return this == UNKNOWN; + } + + @Override + public String asString() { + return name; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/crimson/slayer/AttunementColors.java b/src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/demonlord/AttunementColors.java similarity index 86% rename from src/main/java/de/hysky/skyblocker/skyblock/crimson/slayer/AttunementColors.java rename to src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/demonlord/AttunementColors.java index 6612d97805..5b48d6ab73 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/crimson/slayer/AttunementColors.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/demonlord/AttunementColors.java @@ -1,7 +1,7 @@ -package de.hysky.skyblocker.skyblock.crimson.slayer; +package de.hysky.skyblocker.skyblock.slayers.boss.demonlord; import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.utils.SlayerUtils; +import de.hysky.skyblocker.skyblock.slayers.SlayerManager; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; @@ -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, 2.5f)) { + for (Entity entity : SlayerManager.getEntityArmorStands(e, 2.5f)) { Matcher matcher = COLOR_PATTERN.matcher(entity.getDisplayName().getString()); if (matcher.find()) { String matchedColour = matcher.group(); @@ -32,4 +32,4 @@ public static int getColor(LivingEntity e) { } return Color.RED.getRGB(); } -} \ No newline at end of file +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/crimson/slayer/FirePillarAnnouncer.java b/src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/demonlord/FirePillarAnnouncer.java similarity index 89% rename from src/main/java/de/hysky/skyblocker/skyblock/crimson/slayer/FirePillarAnnouncer.java rename to src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/demonlord/FirePillarAnnouncer.java index d5db338ab8..0c60a5a8d3 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/crimson/slayer/FirePillarAnnouncer.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/demonlord/FirePillarAnnouncer.java @@ -1,8 +1,8 @@ -package de.hysky.skyblocker.skyblock.crimson.slayer; +package de.hysky.skyblocker.skyblock.slayers.boss.demonlord; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.SlayersConfig; -import de.hysky.skyblocker.utils.SlayerUtils; +import de.hysky.skyblocker.skyblock.slayers.SlayerManager; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.RenderHelper; import de.hysky.skyblocker.utils.render.title.Title; @@ -12,6 +12,7 @@ import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.text.Text; import net.minecraft.util.Formatting; + import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -29,7 +30,7 @@ public class FirePillarAnnouncer { * @param entity The updated entity that is checked to be a fire pillar */ public static void checkFirePillar(Entity entity) { - if (Utils.isInCrimson() && SlayerUtils.isInSlayer() && entity instanceof ArmorStandEntity) { + if (Utils.isInCrimson() && SlayerManager.isBossSpawned() && entity instanceof ArmorStandEntity) { String entityName = entity.getName().getString(); Matcher matcher = FIRE_PILLAR_PATTERN.matcher(entityName); @@ -40,7 +41,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.getSlayerArmorStandEntity(); + Entity referenceEntity = SlayerManager.getSlayerBossArmorStand(); 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/slayers/boss/vampire/ManiaIndicator.java similarity index 72% rename from src/main/java/de/hysky/skyblocker/skyblock/rift/ManiaIndicator.java rename to src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/vampire/ManiaIndicator.java index 484c755d75..3c343a4c42 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/rift/ManiaIndicator.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/vampire/ManiaIndicator.java @@ -1,7 +1,7 @@ -package de.hysky.skyblocker.skyblock.rift; +package de.hysky.skyblocker.skyblock.slayers.boss.vampire; import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.utils.SlayerUtils; +import de.hysky.skyblocker.skyblock.slayers.SlayerManager; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.RenderHelper; import de.hysky.skyblocker.utils.render.title.Title; @@ -16,17 +16,17 @@ public class ManiaIndicator { private static final Title title = new Title("skyblocker.rift.mania", Formatting.RED); - protected static void updateMania() { - if (!SkyblockerConfigManager.get().slayers.vampireSlayer.enableManiaIndicator || !Utils.isOnSkyblock() || !Utils.isInTheRift() || !(Utils.getIslandArea().contains("Stillgore Château")) || !SlayerUtils.isInSlayer()) { + public static void updateMania() { + if (!SkyblockerConfigManager.get().slayers.vampireSlayer.enableManiaIndicator || !Utils.isOnSkyblock() || !Utils.isInTheRift() || !(Utils.getIslandArea().contains("Stillgore Château")) || !SlayerManager.isBossSpawned()) { TitleContainer.removeTitle(title); return; } - Entity slayerEntity = SlayerUtils.getSlayerArmorStandEntity(); + Entity slayerEntity = SlayerManager.getSlayerBossArmorStand(); if (slayerEntity == null) return; boolean anyMania = false; - for (Entity entity : SlayerUtils.getEntityArmorStands(slayerEntity, 2.5f)) { + for (Entity entity : SlayerManager.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/slayers/boss/vampire/StakeIndicator.java similarity index 63% rename from src/main/java/de/hysky/skyblocker/skyblock/rift/StakeIndicator.java rename to src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/vampire/StakeIndicator.java index 5938d273d9..b9ad5925a6 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/rift/StakeIndicator.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/vampire/StakeIndicator.java @@ -1,7 +1,7 @@ -package de.hysky.skyblocker.skyblock.rift; +package de.hysky.skyblocker.skyblock.slayers.boss.vampire; import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.utils.SlayerUtils; +import de.hysky.skyblocker.skyblock.slayers.SlayerManager; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.RenderHelper; import de.hysky.skyblocker.utils.render.title.Title; @@ -12,12 +12,12 @@ public class StakeIndicator { private static final Title title = new Title("skyblocker.rift.stakeNow", Formatting.RED); - protected static void updateStake() { - if (!SkyblockerConfigManager.get().slayers.vampireSlayer.enableSteakStakeIndicator || !Utils.isOnSkyblock() || !Utils.isInTheRift() || !Utils.getIslandArea().contains("Stillgore Château") || !SlayerUtils.isInSlayer()) { + public static void updateStake() { + if (!SkyblockerConfigManager.get().slayers.vampireSlayer.enableSteakStakeIndicator || !Utils.isOnSkyblock() || !Utils.isInTheRift() || !Utils.getIslandArea().contains("Stillgore Château") || !SlayerManager.isBossSpawned()) { TitleContainer.removeTitle(title); return; } - Entity slayerEntity = SlayerUtils.getSlayerArmorStandEntity(); + Entity slayerEntity = SlayerManager.getSlayerBossArmorStand(); 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/slayers/boss/vampire/TwinClawsIndicator.java similarity index 72% rename from src/main/java/de/hysky/skyblocker/skyblock/rift/TwinClawsIndicator.java rename to src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/vampire/TwinClawsIndicator.java index b3a1f87bc3..cec983aa92 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/rift/TwinClawsIndicator.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/vampire/TwinClawsIndicator.java @@ -1,12 +1,12 @@ -package de.hysky.skyblocker.skyblock.rift; +package de.hysky.skyblocker.skyblock.slayers.boss.vampire; import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.utils.SlayerUtils; +import de.hysky.skyblocker.skyblock.slayers.SlayerManager; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.RenderHelper; -import de.hysky.skyblocker.utils.scheduler.Scheduler; import de.hysky.skyblocker.utils.render.title.Title; import de.hysky.skyblocker.utils.render.title.TitleContainer; +import de.hysky.skyblocker.utils.scheduler.Scheduler; import net.minecraft.entity.Entity; import net.minecraft.util.Formatting; @@ -14,17 +14,17 @@ public class TwinClawsIndicator { private static final Title title = new Title("skyblocker.rift.iceNow", Formatting.AQUA); private static boolean scheduled = false; - protected static void updateIce() { - if (!SkyblockerConfigManager.get().slayers.vampireSlayer.enableHolyIceIndicator || !Utils.isOnSkyblock() || !Utils.isInTheRift() || !(Utils.getIslandArea().contains("Stillgore Château")) || !SlayerUtils.isInSlayer()) { + public static void updateIce() { + if (!SkyblockerConfigManager.get().slayers.vampireSlayer.enableHolyIceIndicator || !Utils.isOnSkyblock() || !Utils.isInTheRift() || !(Utils.getIslandArea().contains("Stillgore Château")) || !SlayerManager.isBossSpawned()) { TitleContainer.removeTitle(title); return; } - Entity slayerEntity = SlayerUtils.getSlayerArmorStandEntity(); + Entity slayerEntity = SlayerManager.getSlayerBossArmorStand(); if (slayerEntity == null) return; boolean anyClaws = false; - for (Entity entity : SlayerUtils.getEntityArmorStands(slayerEntity, 2.5f)) { + for (Entity entity : SlayerManager.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/end/BeaconHighlighter.java b/src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/voidgloom/BeaconHighlighter.java similarity index 97% rename from src/main/java/de/hysky/skyblocker/skyblock/end/BeaconHighlighter.java rename to src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/voidgloom/BeaconHighlighter.java index 0296f52d52..9dff42b720 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/end/BeaconHighlighter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/voidgloom/BeaconHighlighter.java @@ -1,4 +1,4 @@ -package de.hysky.skyblocker.skyblock.end; +package de.hysky.skyblocker.skyblock.slayers.boss.voidgloom; import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/voidgloom/LazerTimer.java b/src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/voidgloom/LazerTimer.java new file mode 100644 index 0000000000..a2e4eb373c --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/voidgloom/LazerTimer.java @@ -0,0 +1,60 @@ +package de.hysky.skyblocker.skyblock.slayers.boss.voidgloom; + +import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.skyblock.slayers.SlayerManager; +import de.hysky.skyblocker.utils.render.RenderHelper; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.minecraft.entity.Entity; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +public class LazerTimer { + public static double remainingTime = 0; + private static boolean isRiding = false; + + @Init + public static void init() { + WorldRenderEvents.AFTER_TRANSLUCENT.register(LazerTimer::render); + } + + public static void updateTimer() { + if (isRiding) { + if (!SlayerManager.isBossSpawned()) { + isRiding = false; + return; + } + + remainingTime -= 0.05; + if (remainingTime <= 0) { + isRiding = false; + } + } + } + + public static void resetTimer() { + remainingTime = 8.0; + } + + public static boolean isRiding() { + return isRiding; + } + + public static void setRiding(boolean riding) { + isRiding = riding; + } + + private static void render(WorldRenderContext context) { + if (isRiding) { + Entity boss = SlayerManager.getSlayerBoss(); + if (boss != null) { + String timeText = String.format("%.2fs", remainingTime); + Text renderText = Text.literal("Lazer: ").formatted(Formatting.WHITE) + .append(Text.literal(timeText).formatted(Formatting.GREEN).formatted(Formatting.BOLD)); + + RenderHelper.renderText(context, renderText, boss.getPos().add(0, 2, 0), true); + + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/slayers/hud/SlayerHudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/slayers/hud/SlayerHudWidget.java new file mode 100644 index 0000000000..6338329ff4 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/hud/SlayerHudWidget.java @@ -0,0 +1,87 @@ +package de.hysky.skyblocker.skyblock.slayers.hud; + +import de.hysky.skyblocker.annotations.RegisterWidget; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.slayers.SlayerManager; +import de.hysky.skyblocker.skyblock.slayers.SlayerTier; +import de.hysky.skyblocker.skyblock.slayers.SlayerType; +import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.skyblock.tabhud.widget.ComponentBasedWidget; +import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent; +import de.hysky.skyblocker.utils.Location; +import net.minecraft.client.MinecraftClient; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.text.NumberFormat; +import java.util.Set; + +@RegisterWidget +public class SlayerHudWidget extends ComponentBasedWidget { + private static SlayerHudWidget instance; + private final MinecraftClient client = MinecraftClient.getInstance(); + private final NumberFormat numberFormat = NumberFormat.getInstance(); + + public SlayerHudWidget() { + super(Text.literal("Slayer").formatted(Formatting.DARK_PURPLE, Formatting.BOLD), Formatting.DARK_PURPLE.getColorValue(), "hud_slayer"); + instance = this; + update(); + } + + public static SlayerHudWidget getInstance() { + return instance; + } + + @Override + public Set availableLocations() { + return Set.of(Location.CRIMSON_ISLE, Location.HUB, Location.SPIDERS_DEN, Location.THE_END, Location.THE_PARK, Location.THE_RIFT); + } + + @Override + public void setEnabledIn(Location location, boolean enabled) { + if (!availableLocations().contains(location)) return; + SkyblockerConfigManager.get().slayers.enableHud = enabled; + } + + @Override + public boolean isEnabledIn(Location location) { + return availableLocations().contains(location) && SkyblockerConfigManager.get().slayers.enableHud; + } + + @Override + public boolean shouldRender(Location location) { + return super.shouldRender(location) && SlayerManager.isInSlayer() && !SlayerManager.getSlayerType().isUnknown() && !SlayerManager.getSlayerTier().isUnknown(); + } + + @Override + public void updateContent() { + if (client.player == null || SlayerManager.getSlayerQuest() == null) return; + + SlayerType type = SlayerManager.getSlayerType(); + SlayerTier tier = SlayerManager.getSlayerTier(); + int level = SlayerManager.getSlayerQuest().level; + int bossesNeeded = SlayerManager.getSlayerQuest().bossesNeeded; + + if (type == null || tier == null) return; + + addSimpleIcoText(type.icon, "", tier.color, type.bossName + " " + tier); + if (level > 0) { + if (level == type.maxLevel) { + addComponent(new IcoTextComponent(Ico.EXPERIENCE_BOTTLE, Text.literal("XP: ").append(Text.translatable("skyblocker.slayer.hud.levelMaxed").formatted(Formatting.GREEN)))); + } else { + int nextMilestone = type.levelMilestones[level]; + int currentXP = nextMilestone - SlayerManager.getSlayerQuest().xpRemaining; + addSimpleIcoText(Ico.EXPERIENCE_BOTTLE, "XP: ", Formatting.LIGHT_PURPLE, numberFormat.format(currentXP) + "/" + numberFormat.format(nextMilestone)); + } + } + + if (bossesNeeded > 0) { + addComponent(new IcoTextComponent(Ico.NETHER_STAR, Text.translatable("skyblocker.slayer.hud.levelUpIn", Text.literal(numberFormat.format(bossesNeeded)).formatted(Formatting.LIGHT_PURPLE)))); + } + } + + @Override + public Text getDisplayName() { + return Text.literal("Slayer Hud"); + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/SlayerUtils.java b/src/main/java/de/hysky/skyblocker/utils/SlayerUtils.java deleted file mode 100644 index 6eaa770870..0000000000 --- a/src/main/java/de/hysky/skyblocker/utils/SlayerUtils.java +++ /dev/null @@ -1,116 +0,0 @@ -package de.hysky.skyblocker.utils; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.entity.Entity; -import net.minecraft.entity.decoration.ArmorStandEntity; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -//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); - 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, 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 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, 1.5f)) { - if (armorStand.getDisplayName().getString().contains(username)) { - 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; - } - } - } catch (NullPointerException e) { - LOGGER.error("[Skyblocker] Error while checking if player is in slayer", e); - } - return false; - } - - public static boolean isInSlayerType(String slayer) { - try { - boolean inFight = false; - boolean type = false; - for (String line : Utils.STRING_SCOREBOARD) { - switch (line) { - case String a when a.contains("Slay the boss!") -> inFight = true; - case String b when b.contains(slayer) -> type = true; - default -> { continue; } - } - if (inFight && type) return true; - } - } catch (NullPointerException e) { - LOGGER.error("[Skyblocker] Error while checking if player is in slayer", e); - } - return false; - } - - public static boolean isInSlayerQuestType(String slayer) { - try { - boolean quest = false; - boolean type = false; - for (String line : Utils.STRING_SCOREBOARD) { - if (line.contains("Slayer Quest")) quest = true; - if (line.contains(slayer)) type = true; - if (quest && type) return true; - } - } catch (NullPointerException e) { - LOGGER.error("[Skyblocker] Error while checking if player is in slayer quest type", e); - } - return false; - } - - public static String getSlayerType() { - try { - for (String line : Utils.STRING_SCOREBOARD) { - Matcher matcher = SLAYER_PATTERN.matcher(line); - if (matcher.find()) return matcher.group(); - } - } catch (NullPointerException | IndexOutOfBoundsException e) { - LOGGER.error("[Skyblocker] Error while checking slayer type", e); - } - return ""; - } -} diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java index 10bf4063c0..71c40adf3b 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Utils.java +++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java @@ -7,6 +7,7 @@ import de.hysky.skyblocker.events.SkyblockEvents; import de.hysky.skyblocker.mixins.accessors.MessageHandlerAccessor; import de.hysky.skyblocker.skyblock.item.MuseumItemCache; +import de.hysky.skyblocker.skyblock.slayers.SlayerManager; import de.hysky.skyblocker.utils.purse.PurseChangeCause; import de.hysky.skyblocker.utils.scheduler.MessageScheduler; import de.hysky.skyblocker.utils.scheduler.Scheduler; @@ -54,6 +55,7 @@ public class Utils { private static final Pattern PURSE = Pattern.compile("(Purse|Piggy): (?[0-9,.]+)( \\((?[+\\-][0-9,.]+)\\))?"); private static boolean isOnHypixel = false; private static boolean isOnSkyblock = false; + /** * The player's rank. */ @@ -342,6 +344,7 @@ private static void updateScoreboard(MinecraftClient client) { TEXT_SCOREBOARD.addAll(textLines); STRING_SCOREBOARD.addAll(stringLines); Utils.updatePurse(); + SlayerManager.getSlayerBossInfo(true); } catch (NullPointerException e) { //Do nothing } @@ -408,6 +411,7 @@ case LocationUpdateS2CPacket(var serverName, var serverType, var _lobbyName, var if (Utils.gameType.equals("SKYBLOCK")) { isOnSkyblock = true; tickProfileId(); + SlayerManager.getSlayerInfoOnJoin(); if (!previousServerType.equals("SKYBLOCK")) SkyblockEvents.JOIN.invoker().onSkyblockJoin(); } else if (previousServerType.equals("SKYBLOCK")) { diff --git a/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java b/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java index 23a6ab821b..c3d7faa463 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java @@ -175,4 +175,4 @@ protected static void render(DrawContext context, Set titles, int xPos, i } } } -} \ No newline at end of file +} diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 83097d3008..657dc0e99d 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -634,6 +634,7 @@ "skyblocker.config.otherLocations.end": "The End", "skyblocker.config.otherLocations.end.enableEnderNodeHelper": "Enable Ender Node Helper", "skyblocker.config.otherLocations.end.hudEnabled": "HUD Enabled", + "skyblocker.config.otherLocations.end.muteEndermanSounds": "Mute Enderman Sounds", "skyblocker.config.otherLocations.end.protectorLocationEnable": "Show Protector Location", "skyblocker.config.otherLocations.end.resetName": "Reset stored End stats", "skyblocker.config.otherLocations.end.resetText": "Reset", @@ -670,6 +671,9 @@ "skyblocker.config.slayer": "Slayers", + "skyblocker.config.slayer.enableHud": "Slayer HUD", + "skyblocker.config.slayer.enableHud.@Tooltip": "You may have to do 1 boss before all HUD elements show", + "skyblocker.config.slayer.blazeSlayer": "Blaze Slayer", "skyblocker.config.slayer.blazeSlayer.enableFirePillarAnnouncer" : "Fire Pillar Countdown Notifications", "skyblocker.config.slayer.blazeSlayer.enableFirePillarAnnouncer.@Tooltip": "Countdowns the last five seconds before the Fire Pillar explodes. Configure 'Title Container' to customise the location on the HUD (Note: This only uses a rudimentary distance check, so the Pillar may be spawned by another player's slayer)", @@ -682,10 +686,14 @@ "skyblocker.config.slayer.bossbar": "Slayer Bossbar", "skyblocker.config.slayer.bossbar.@Tooltip": "Displays a bossbar for your active slayer boss", + "skyblocker.config.slayer.bossSpawnAlert": "Boss Spawn Alert", + "skyblocker.config.slayer.bossSpawnAlert.@Tooltip": "\nDisplays a warning when a slayer boss spawns", + "skyblocker.config.slayer.endermanSlayer": "Enderman Slayer", "skyblocker.config.slayer.endermanSlayer.enableYangGlyphsNotification": "Enable Yang Glyphs notification", "skyblocker.config.slayer.endermanSlayer.highlightBeacons": "Beacon Highlighting", "skyblocker.config.slayer.endermanSlayer.highlightNukekubiHeads": "Nukekubi Head Highlighting", + "skyblocker.config.slayer.endermanSlayer.lazerTimer": "Lazer Timer", "skyblocker.config.slayer.highlightMinis": "Highlight Minibosses", "skyblocker.config.slayer.highlightMinis.@Tooltip[0]": "\nOFF: Do not highlight Slayer Minibosses.", @@ -701,6 +709,14 @@ "skyblocker.config.slayer.highlightBosses.GLOW": "Glow", "skyblocker.config.slayer.highlightBosses.HITBOX": "Hitbox", + "skyblocker.config.slayer.minibossSpawnAlert": "MiniBoss Spawn Alert", + "skyblocker.config.slayer.minibossSpawnAlert.@Tooltip": "\nDisplays a warning when a slayer miniboss spawns", + + "skyblocker.config.slayer.slainTime": "Slayer kill time", + "skyblocker.config.slayer.slainTime.@Tooltip": "Sends the amount of time spent to kill the slayer.", + + "skyblocker.config.slayer.slayerHud": "Slayer HUD Config...", + "skyblocker.config.slayer.vampireSlayer": "Vampire Slayer", "skyblocker.config.slayer.vampireSlayer.compactEffigyWaypoints": "Compact Effigy Waypoints", "skyblocker.config.slayer.vampireSlayer.effigyUpdateFrequency": "Effigy Waypoints Update Frequency (Ticks)", @@ -968,6 +984,15 @@ "skyblocker.shortcuts.new": "New Shortcut", "skyblocker.shortcuts.commandSuggestionTooltip": "Due to limitations of Minecraft, command suggestions will only work after joining a new world.", + "skyblocker.slayer.bossSpawnAlert": "Boss Spawned!", + "skyblocker.slayer.hud.bossesNeeded": "Bosses until level up", + "skyblocker.slayer.hud.levelMaxed": "MAXED OUT", + "skyblocker.slayer.hud.levelUpIn": "Level up in %d bosses", + "skyblocker.slayer.miniBossSpawnAlert": "MiniBoss", + "skyblocker.slayer.personalBest": "PERSONAL BEST!", + "skyblocker.slayer.previousPB": "Your previous Personal Best was %d.", + "skyblocker.slayer.slainTime": "Slayer has been killed in %d!", + "skyblocker.waypoints.config": "Waypoints Config", "skyblocker.waypoints.newGroup": "New Waypoint Group", "skyblocker.waypoints.new": "New Waypoint",