From 6177e513494fe66feabd830a3b8307fb8587ef67 Mon Sep 17 00:00:00 2001 From: viciscat <51047087+viciscat@users.noreply.github.com> Date: Mon, 23 Dec 2024 02:09:17 +0100 Subject: [PATCH 01/12] Add Garden Plots Widget (#929) * Add Garden Plots Widget * small fix * make for loop more boring * exclusion zones * rebase oopsie daisy and JEI exclusion zone * ctrl+shift+up arrow * Fix init merge conflicts * merge and port * 1.21.4 --------- Co-authored-by: Kevinthegreat <92656833+kevinthegreat1@users.noreply.github.com> --- .../emi/SkyblockerEMIPlugin.java | 11 + .../jei/SkyblockerJEIPlugin.java | 24 ++ .../rei/SkyblockerREIClientPlugin.java | 18 ++ .../config/categories/FarmingCategory.java | 21 +- .../config/configs/FarmingConfig.java | 6 + .../mixins/InventoryScreenMixin.java | 45 ++- .../skyblock/garden/GardenPlotsWidget.java | 299 ++++++++++++++++++ .../assets/skyblocker/lang/en_us.json | 10 + 8 files changed, 429 insertions(+), 5 deletions(-) create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/garden/GardenPlotsWidget.java diff --git a/src/main/java/de/hysky/skyblocker/compatibility/emi/SkyblockerEMIPlugin.java b/src/main/java/de/hysky/skyblocker/compatibility/emi/SkyblockerEMIPlugin.java index 9d5a2a46c0..d8d2d28c47 100644 --- a/src/main/java/de/hysky/skyblocker/compatibility/emi/SkyblockerEMIPlugin.java +++ b/src/main/java/de/hysky/skyblocker/compatibility/emi/SkyblockerEMIPlugin.java @@ -1,14 +1,20 @@ package de.hysky.skyblocker.compatibility.emi; import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.Location; +import de.hysky.skyblocker.utils.Utils; import dev.emi.emi.api.EmiPlugin; import dev.emi.emi.api.EmiRegistry; import dev.emi.emi.api.recipe.EmiRecipeCategory; import dev.emi.emi.api.render.EmiTexture; import dev.emi.emi.api.stack.Comparison; import dev.emi.emi.api.stack.EmiStack; +import dev.emi.emi.api.widget.Bounds; +import net.minecraft.client.gui.screen.ingame.InventoryScreen; import net.minecraft.item.Items; import net.minecraft.util.Identifier; @@ -29,5 +35,10 @@ public void register(EmiRegistry registry) { registry.addCategory(SKYBLOCK); registry.addWorkstation(SKYBLOCK, EmiStack.of(Items.CRAFTING_TABLE)); ItemRepository.getRecipesStream().map(SkyblockEmiRecipe::new).forEach(registry::addRecipe); + registry.addExclusionArea(InventoryScreen.class, (screen, consumer) -> { + if (!SkyblockerConfigManager.get().farming.garden.gardenPlotsWidget || !Utils.getLocation().equals(Location.GARDEN)) return; + HandledScreenAccessor accessor = (HandledScreenAccessor) screen; + consumer.accept(new Bounds(accessor.getX() + accessor.getBackgroundWidth() + 4, accessor.getY(), 104, 127)); + }); } } diff --git a/src/main/java/de/hysky/skyblocker/compatibility/jei/SkyblockerJEIPlugin.java b/src/main/java/de/hysky/skyblocker/compatibility/jei/SkyblockerJEIPlugin.java index ee54c3b50b..cf35208b3a 100644 --- a/src/main/java/de/hysky/skyblocker/compatibility/jei/SkyblockerJEIPlugin.java +++ b/src/main/java/de/hysky/skyblocker/compatibility/jei/SkyblockerJEIPlugin.java @@ -1,23 +1,33 @@ package de.hysky.skyblocker.compatibility.jei; import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; +import de.hysky.skyblocker.utils.Location; +import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.datafixer.ItemStackComponentizationFixer; import mezz.jei.api.IModPlugin; import mezz.jei.api.JeiPlugin; import mezz.jei.api.constants.VanillaTypes; +import mezz.jei.api.gui.handlers.IGuiContainerHandler; +import mezz.jei.api.registration.IGuiHandlerRegistration; import mezz.jei.api.registration.IRecipeCategoryRegistration; import mezz.jei.api.registration.IRecipeRegistration; import mezz.jei.api.registration.ISubtypeRegistration; import mezz.jei.library.ingredients.subtypes.SubtypeInterpreters; import mezz.jei.library.load.registration.SubtypeRegistration; import mezz.jei.library.plugins.vanilla.crafting.CraftingCategoryExtension; +import net.minecraft.client.gui.screen.ingame.InventoryScreen; +import net.minecraft.client.util.math.Rect2i; import net.minecraft.item.ItemStack; import net.minecraft.recipe.*; import net.minecraft.recipe.book.CraftingRecipeCategory; import net.minecraft.util.Identifier; import org.jetbrains.annotations.NotNull; +import java.util.Collections; +import java.util.List; import java.util.Map; @JeiPlugin @@ -45,6 +55,11 @@ public void registerCategories(@NotNull IRecipeCategoryRegistration registration registration.addRecipeCategories(skyblockCraftingRecipeCategory); } + @Override + public void registerGuiHandlers(@NotNull IGuiHandlerRegistration registration) { + registration.addGuiContainerHandler(InventoryScreen.class, new InventoryContainerHandler()); + } + @Override public void registerRecipes(@NotNull IRecipeRegistration registration) { //FIXME no clue what to replace any of this with, we can't use items as that does not work @@ -63,4 +78,13 @@ public void registerRecipes(@NotNull IRecipeRegistration registration) { ), "abc", "def", "ghi"), recipe.getResult())) ).toList());*/ } + + private static class InventoryContainerHandler implements IGuiContainerHandler { + @Override + public @NotNull List getGuiExtraAreas(@NotNull InventoryScreen containerScreen) { + if (!SkyblockerConfigManager.get().farming.garden.gardenPlotsWidget || !Utils.getLocation().equals(Location.GARDEN)) return List.of(); + HandledScreenAccessor accessor = (HandledScreenAccessor) containerScreen; + return Collections.singletonList(new Rect2i(accessor.getX() + accessor.getBackgroundWidth() + 4, accessor.getY(), 104, 127)); + } + } } diff --git a/src/main/java/de/hysky/skyblocker/compatibility/rei/SkyblockerREIClientPlugin.java b/src/main/java/de/hysky/skyblocker/compatibility/rei/SkyblockerREIClientPlugin.java index 7ed322a061..dccc3b7800 100644 --- a/src/main/java/de/hysky/skyblocker/compatibility/rei/SkyblockerREIClientPlugin.java +++ b/src/main/java/de/hysky/skyblocker/compatibility/rei/SkyblockerREIClientPlugin.java @@ -1,15 +1,24 @@ package de.hysky.skyblocker.compatibility.rei; import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; +import de.hysky.skyblocker.utils.Location; +import de.hysky.skyblocker.utils.Utils; +import me.shedaniel.math.Rectangle; import me.shedaniel.rei.api.client.plugins.REIClientPlugin; import me.shedaniel.rei.api.client.registry.category.CategoryRegistry; import me.shedaniel.rei.api.client.registry.display.DisplayRegistry; import me.shedaniel.rei.api.client.registry.entry.EntryRegistry; +import me.shedaniel.rei.api.client.registry.screen.ExclusionZones; import me.shedaniel.rei.api.common.category.CategoryIdentifier; import me.shedaniel.rei.api.common.util.EntryStacks; +import net.minecraft.client.gui.screen.ingame.InventoryScreen; import net.minecraft.item.Items; +import java.util.List; + /** * REI integration */ @@ -31,4 +40,13 @@ public void registerDisplays(DisplayRegistry displayRegistry) { public void registerEntries(EntryRegistry entryRegistry) { entryRegistry.addEntries(ItemRepository.getItemsStream().map(EntryStacks::of).toList()); } + + @Override + public void registerExclusionZones(ExclusionZones zones) { + zones.register(InventoryScreen.class, screen -> { + if (!SkyblockerConfigManager.get().farming.garden.gardenPlotsWidget || !Utils.getLocation().equals(Location.GARDEN)) return List.of(); + HandledScreenAccessor accessor = (HandledScreenAccessor) screen; + return List.of(new Rectangle(accessor.getX() + accessor.getBackgroundWidth() + 4, accessor.getY(), 104, 127)); + }); + } } diff --git a/src/main/java/de/hysky/skyblocker/config/categories/FarmingCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/FarmingCategory.java index 6ebbc23e1d..929256c61c 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/FarmingCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/FarmingCategory.java @@ -5,10 +5,7 @@ import de.hysky.skyblocker.skyblock.garden.FarmingHudWidget; import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; import de.hysky.skyblocker.utils.Location; -import dev.isxander.yacl3.api.ButtonOption; -import dev.isxander.yacl3.api.ConfigCategory; -import dev.isxander.yacl3.api.Option; -import dev.isxander.yacl3.api.OptionGroup; +import dev.isxander.yacl3.api.*; import net.minecraft.client.MinecraftClient; import net.minecraft.text.Text; @@ -62,6 +59,22 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig newValue -> config.farming.garden.lockMouseGroundOnly = newValue) .controller(ConfigUtils::createBooleanController) .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.farming.garden.gardenPlotsWidget")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.farming.garden.gardenPlotsWidget.@Tooltip"))) + .binding(defaults.farming.garden.gardenPlotsWidget, + () -> config.farming.garden.gardenPlotsWidget, + newValue -> config.farming.garden.gardenPlotsWidget = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.farming.garden.closeScreenOnPlotClick")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.farming.garden.closeScreenOnPlotClick.@Tooltip"))) + .binding(defaults.farming.garden.closeScreenOnPlotClick, + () -> config.farming.garden.closeScreenOnPlotClick, + newValue -> config.farming.garden.closeScreenOnPlotClick = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) .build()) .build(); } diff --git a/src/main/java/de/hysky/skyblocker/config/configs/FarmingConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/FarmingConfig.java index b2faf00518..568165a707 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/FarmingConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/FarmingConfig.java @@ -22,6 +22,12 @@ public static class Garden { @SerialEntry public boolean lockMouseGroundOnly = false; + + @SerialEntry + public boolean gardenPlotsWidget = true; + + @SerialEntry + public boolean closeScreenOnPlotClick = false; } public static class FarmingHud { diff --git a/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java index 39da111ad5..b35d79b2bd 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java @@ -10,15 +10,36 @@ import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.garden.GardenPlotsWidget; import de.hysky.skyblocker.skyblock.itemlist.recipebook.SkyblockRecipeBookWidget; +import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.Utils; import net.minecraft.client.gui.DrawContext; +import de.hysky.skyblocker.utils.scheduler.MessageScheduler; +import net.minecraft.client.gui.screen.ingame.HandledScreen; import net.minecraft.client.gui.screen.ingame.InventoryScreen; import net.minecraft.client.gui.screen.ingame.StatusEffectsDisplay; import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.screen.PlayerScreenHandler; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(InventoryScreen.class) -public abstract class InventoryScreenMixin { +public abstract class InventoryScreenMixin extends HandledScreen { + + @Unique + private GardenPlotsWidget gardenPlotsWidget; + @Unique + private ButtonWidget deskButton; + + public InventoryScreenMixin(PlayerScreenHandler handler, PlayerInventory inventory, Text title) { + super(handler, inventory, title); + } + @ModifyArg(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/RecipeBookScreen;(Lnet/minecraft/screen/AbstractRecipeScreenHandler;Lnet/minecraft/client/gui/screen/recipebook/RecipeBookWidget;Lnet/minecraft/entity/player/PlayerInventory;Lnet/minecraft/text/Text;)V")) private static RecipeBookWidget skyblocker$replaceRecipeBook(RecipeBookWidget original, @Local(argsOnly = true) PlayerEntity player) { @@ -40,4 +61,26 @@ public abstract class InventoryScreenMixin { private boolean skyblocker$markStatusEffectsHidden(boolean original) { return Utils.isOnSkyblock() ? !SkyblockerConfigManager.get().uiAndVisuals.hideStatusEffectOverlay : original; } + + @Inject(method = "onRecipeBookToggled", at = @At("TAIL")) + private void skyblocker$moveGardenPlotsWdiget(CallbackInfo ci) { + if (Utils.getLocation().equals(Location.GARDEN) && gardenPlotsWidget != null) { + gardenPlotsWidget.setPosition(x + backgroundWidth + 4, y); + if (deskButton != null) deskButton.setPosition(gardenPlotsWidget.getX() + 4, y + 108); + } + } + + @Inject(method = "init", at = @At("TAIL")) + private void skyblocker$addGardenPlotsWidget(CallbackInfo ci) { + if (Utils.getLocation().equals(Location.GARDEN) && SkyblockerConfigManager.get().farming.garden.gardenPlotsWidget) { + gardenPlotsWidget = new GardenPlotsWidget(x + backgroundWidth + 4, y); + deskButton = ButtonWidget.builder(Text.translatable("skyblocker.gardenPlots.openDesk"), button -> MessageScheduler.INSTANCE.sendMessageAfterCooldown("/desk")) + .dimensions(gardenPlotsWidget.getX() + 7, y + 108, 60, 15) + .build(); + // make desk button get selected before the widget but render after the widget + addSelectableChild(deskButton); + addDrawableChild(gardenPlotsWidget); + addDrawable(deskButton); + } + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/GardenPlotsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/GardenPlotsWidget.java new file mode 100644 index 0000000000..4e4af42bb9 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/GardenPlotsWidget.java @@ -0,0 +1,299 @@ +package de.hysky.skyblocker.skyblock.garden; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.mojang.serialization.JsonOps; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.events.SkyblockEvents; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.scheduler.MessageScheduler; +import it.unimi.dsi.fastutil.ints.*; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.screen.GenericContainerScreenHandler; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class GardenPlotsWidget extends ClickableWidget { + + private static final Logger LOGGER = LoggerFactory.getLogger("Garden Plots"); + private static final Path FOLDER = SkyblockerMod.CONFIG_DIR.resolve("garden_plots"); + + ////////////////////////// + // STATIC SHENANIGANS + ////////////////////////// + + public static final Int2IntMap GARDEN_PLOT_TO_SLOT = Int2IntMaps.unmodifiable(new Int2IntOpenHashMap(Map.ofEntries( + Map.entry(1, 7), + Map.entry(2, 11), + Map.entry(3, 13), + Map.entry(4, 17), + Map.entry(5, 6), + Map.entry(6, 8), + Map.entry(7, 16), + Map.entry(8, 18), + Map.entry(9, 2), + Map.entry(10, 10), + Map.entry(11, 14), + Map.entry(12, 22), + Map.entry(13, 1), + Map.entry(14, 3), + Map.entry(15, 5), + Map.entry(16, 9), + Map.entry(17, 15), + Map.entry(18, 19), + Map.entry(19, 21), + Map.entry(20, 23), + Map.entry(21, 0), + Map.entry(22, 4), + Map.entry(23, 20), + Map.entry(24, 24) + ))); + + private static final GardenPlot[] gardenPlots = new GardenPlot[25]; + + @Init + public static void init() { + ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> { + if (screen instanceof GenericContainerScreen containerScreen && screen.getTitle().getString().trim().equals("Configure Plots")) { + ScreenEvents.remove(screen).register(ignored -> { + GenericContainerScreenHandler screenHandler = containerScreen.getScreenHandler(); + // Take plot icons and names + for (int row = 0; row < 5; row++) for (int i = row * 9 + 2; i < row * 9 + 7; i++) { + if (i == 22) continue; // Barn icon + Slot slot = screenHandler.slots.get(i); + ItemStack stack = slot.getStack(); + if (stack.isEmpty() || stack.isOf(Items.RED_STAINED_GLASS_PANE) || stack.isOf(Items.OAK_BUTTON) || stack.isOf(Items.BLACK_STAINED_GLASS_PANE)) + continue; + gardenPlots[(i / 9) * 5 + (i % 9 - 2)] = new GardenPlot(stack.getItem(), stack.getName().getString().split("-", 2)[1].trim()); + } + + }); + } + }); + + SkyblockEvents.PROFILE_CHANGE.register(((prevProfileId, profileId) -> { + if (!prevProfileId.isEmpty()) + CompletableFuture.runAsync(() -> save(prevProfileId)).thenRun(() -> load(profileId)); + else load(profileId); + })); + + ClientLifecycleEvents.CLIENT_STOPPING.register(client1 -> { + String profileId = Utils.getProfileId(); + if (!profileId.isBlank()) { + CompletableFuture.runAsync(() -> save(profileId)); + } + }); + } + + private static void save(String profileId) { + try { + Files.createDirectories(FOLDER); + } catch (IOException e) { + LOGGER.error("[Skyblocker] Failed to create folder for garden plots!", e); + } + Path resolve = FOLDER.resolve(profileId + ".json"); + + try (BufferedWriter writer = Files.newBufferedWriter(resolve)) { + JsonArray elements = new JsonArray(); + Arrays.stream(gardenPlots).map(gardenPlot -> { + if (gardenPlot == null) return null; + JsonObject jsonObject = new JsonObject(); + jsonObject.add("icon", Item.ENTRY_CODEC.encodeStart(JsonOps.INSTANCE, gardenPlot.item.getRegistryEntry()).getOrThrow()); + jsonObject.addProperty("name", gardenPlot.name); + return jsonObject; + }).forEach(elements::add); + + SkyblockerMod.GSON.toJson(elements, writer); + } catch (Exception e) { + LOGGER.error("[Skyblocker] Failed to save Garden Plots data", e); + } + } + + private static void load(String profileId) { + Path resolve = FOLDER.resolve(profileId + ".json"); + CompletableFuture.supplyAsync(() -> { + try (BufferedReader reader = Files.newBufferedReader(resolve)) { + return SkyblockerMod.GSON.fromJson(reader, JsonArray.class).asList().stream().map(jsonElement -> { + if (jsonElement == null || jsonElement.isJsonNull()) return null; + JsonObject jsonObject = jsonElement.getAsJsonObject(); + return new GardenPlot(Item.ENTRY_CODEC.decode(JsonOps.INSTANCE, jsonObject.get("icon")).getOrThrow().getFirst().value(), jsonObject.get("name").getAsString()); + } + ).toArray(GardenPlot[]::new); + } catch (NoSuchFileException ignored) { + } catch (Exception e) { + LOGGER.error("[Skyblocker] Failed to load Equipment data", e); + } + return new GardenPlot[25]; + // Schedule on main thread to avoid any async weirdness + }).thenAccept(newPlots -> MinecraftClient.getInstance().execute(() -> System.arraycopy(newPlots, 0, gardenPlots, 0, Math.min(newPlots.length, 25)))); + } + + ///////////////////////////// + // THE WIDGET ITSELF + ///////////////////////////// + + private static final Identifier BACKGROUND_TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/garden_plots.png"); + private static final MutableText GROSS_PEST_TEXT = Text.translatable("skyblocker.gardenPlots.pests").formatted(Formatting.RED, Formatting.BOLD); + private static final MutableText TP_TEXT = Text.translatable("skyblocker.gardenPlots.tp").formatted(Formatting.YELLOW, Formatting.BOLD); + + private final ItemStack[] items; + private int hoveredSlot = -1; + private long updateFromTabTime = System.currentTimeMillis(); + private final IntList infectedPlots = new IntArrayList(8); + + public GardenPlotsWidget(int x, int y) { + super(x, y, 104, 127, Text.translatable("skyblocker.gardenPlots")); + items = Arrays.stream(gardenPlots).map(gardenPlot -> { + if (gardenPlot == null) return null; + ItemStack itemStack = new ItemStack(gardenPlot.item()); + itemStack.set(DataComponentTypes.ITEM_NAME, Text.literal(gardenPlot.name()).formatted(Formatting.GREEN, Formatting.BOLD)); + return itemStack; + }).toArray(ItemStack[]::new); + items[12] = new ItemStack(Items.LODESTONE); + items[12].set(DataComponentTypes.ITEM_NAME, Text.literal("The Barn")); + updateInfestedFromTab(); + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + MatrixStack matrices = context.getMatrices(); + matrices.push(); + matrices.translate(getX(), getY(), 0); + + context.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_TEXTURE, 0, 0, 0, 0, getWidth(), getHeight(), getWidth(), getHeight()); + + context.drawText(textRenderer, getMessage(), 8, 6, 4210752, false); + + hoveredSlot = -1; + long timeMillis = System.currentTimeMillis(); + for (int i = 0; i < items.length; i++) { + ItemStack item = items[i]; + if (item == null) continue; + + + int slotX = 7 + (i % 5) * 18; + int slotY = 17 + (i / 5) * 18; + boolean hovered = slotX + getX() <= mouseX && mouseX < slotX + getX() + 18 && slotY + getY() <= mouseY && mouseY < slotY + getY() + 18; + + if (hovered) { + context.fill(slotX + 1, slotY + 1, slotX + 17, slotY + 17, 0xAA_FF_FF_FF); + matrices.push(); + matrices.translate(slotX, slotY, 100.f); + matrices.scale(1.125f, 1.125f, 1.125f); + context.drawItem(item, 0, 0); + matrices.pop(); + hoveredSlot = i; + } else + context.drawItem(item, slotX + 1, slotY + 1); + + boolean infested = infectedPlots.contains(i); + if (infested && (timeMillis & 512) != 0) { + context.drawBorder(slotX + 1, slotY + 1, 16, 16, 0xFF_FF0000); + } + + // tooltip + if (hovered) { + List tooltip = infested ? + List.of( + Text.translatable("skyblocker.gardenPlots.plot", item.getName()), + GROSS_PEST_TEXT, + Text.empty(), + TP_TEXT) : + + i == 12 ? + List.of( + item.getName(), + Text.empty(), + TP_TEXT) : + + List.of( + Text.translatable("skyblocker.gardenPlots.plot", item.getName()), + Text.empty(), + TP_TEXT + ); + context.drawTooltip(textRenderer, tooltip, mouseX - getX(), mouseY - getY()); + } + } + + matrices.pop(); + + + if (timeMillis - updateFromTabTime > 3000) { + updateFromTabTime = timeMillis; + updateInfestedFromTab(); + } + } + + private void updateInfestedFromTab() { + infectedPlots.clear(); + for (int i = 0; i < PlayerListMgr.getPlayerStringList().size(); i++) { + String string = PlayerListMgr.getPlayerStringList().get(i); + if (string.startsWith("Plots:")) { + String[] split = string.split(":")[1].split(","); + for (String s : split) { + try { + infectedPlots.add(GARDEN_PLOT_TO_SLOT.getOrDefault(Integer.parseInt(s.strip()), -1)); + } catch (NumberFormatException ignored) {} + } + break; + } + } + } + + @Override + public void onClick(double mouseX, double mouseY) { + super.onClick(mouseX, mouseY); + if (hoveredSlot == -1) return; + + if (SkyblockerConfigManager.get().farming.garden.closeScreenOnPlotClick && MinecraftClient.getInstance().currentScreen != null) + MinecraftClient.getInstance().currentScreen.close(); + + if (hoveredSlot == 12) MessageScheduler.INSTANCE.sendMessageAfterCooldown("/warp garden"); + else MessageScheduler.INSTANCE.sendMessageAfterCooldown("/plottp " + gardenPlots[hoveredSlot].name); + } + + @Override + protected boolean isValidClickButton(int button) { + return super.isValidClickButton(button) && hoveredSlot != -1; + } + + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) { + } + + private record GardenPlot(Item item, String name) { + } +} diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 003742efb1..4625e02d5a 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -228,9 +228,13 @@ "skyblocker.config.farming": "Farming", "skyblocker.config.farming.garden": "Garden", + "skyblocker.config.farming.garden.closeScreenOnPlotClick": "Close inventory on plot tp", + "skyblocker.config.farming.garden.closeScreenOnPlotClick.@Tooltip": "Whether or not your inventory should automatically close when teleporting to a plot using the Garden Plots Widget", "skyblocker.config.farming.garden.dicerTitlePrevent": "Enable Dicer Title Prevent", "skyblocker.config.farming.garden.enableHud": "Enable Farming HUD", "skyblocker.config.farming.garden.farmingHud": "Farming HUD Config...", + "skyblocker.config.farming.garden.gardenPlotsWidget": "Enable Garden Plots Widget", + "skyblocker.config.farming.garden.gardenPlotsWidget.@Tooltip": "While in the garden, on the right of your inventory there will be a widget to quickly teleport to plots. It will also show plots that have pests (this requires the Pests widget to be enabled and visible in the Tab).", "skyblocker.config.farming.garden.lockMouseGround": "Only lock camera when on the ground", "skyblocker.config.farming.garden.lockMouseTool": "Lock camera when holding a farming tool", "skyblocker.config.farming.garden.visitorHelper": "Visitor helper", @@ -1122,6 +1126,12 @@ "skyblocker.fancyAuctionHouse.yourAuction": "This is your auction!", "skyblocker.fancyAuctionHouse.youPay": "You pay: %s", + "skyblocker.gardenPlots": "Garden Plots", + "skyblocker.gardenPlots.openDesk": "Open Desk", + "skyblocker.gardenPlots.pests": "GROSS! This plot has pests!", + "skyblocker.gardenPlots.plot": "Plot - %s", + "skyblocker.gardenPlots.tp": "Click to TP to it!", + "skyblocker.crimson.dojo": "Dojo", "skyblocker.crimson.dojo.forceHelper": "Enable Force Helper", "skyblocker.crimson.dojo.forceHelper.@Tooltip": "Shows timer showing how long until a zombie despawns and outlines negative zombies.", From c4f9bc4dd7631021af74726adec54e56f9b49b0e Mon Sep 17 00:00:00 2001 From: Hazem <79111320+7azeemm@users.noreply.github.com> Date: Mon, 23 Dec 2024 03:46:07 +0100 Subject: [PATCH 02/12] Slayer System rework (#1040) * Added Slayer HUD Added Boss slain time Added Personal Best slain time Added Boss and MiniBoss spawn alert Added Mute Enderman sounds Added Lazer phase Timer * Narrowen the startRiding method injection because it was failing * Change instanceof checks to type equality check for better intellisense * Fix TITLE related crash * Fix filled outline glow * pattern changes * Translation keys sorting * mixins * more mixins changes * .. * more translatable text mixins changes shows now previous personalBest and more * compatible with Slayer XP Buff (Aatrox perk) * fixed title spam * imporved logic * Fixed BossBar bug * Fixed Vampire slayer entity not being highlighted * HUD improvements Fixed BossBar not working with rev t5 * .. * ... * resolved conflicts Fixed crash when player starts slayer quest in private island * Refactor slayer code * Fix internationalization * Refactor slayer glow * Rewrite slayer detection * Clean up * fixed some crashes so i could test it * fixed mob detection * refactor * fixed sound checks * LazerTimer rewrite * .. * more improvements * Refactor guard clauses * Refactor SlayerManager * Clean up ClientPlayNetworkHandlerMixin * Update slayer hud * Make it actually compile... * some changes * locations * fixed locations * changes * removed extended class * Inject after forceMainThread * .. * small change * bug fixes * null check * Fix slayer hud config screen * tabs * Fix slayer hud enabled * Don't render everywhere --------- Co-authored-by: Rime <81419447+Emirlol@users.noreply.github.com> Co-authored-by: Kevin <92656833+kevinthegreat1@users.noreply.github.com> --- .../categories/OtherLocationsCategory.java | 7 + .../config/categories/SlayersCategory.java | 395 ++++++++++-------- .../config/configs/OtherLocationsConfig.java | 3 + .../config/configs/SlayersConfig.java | 147 ++++--- .../mixins/ClientPlayNetworkHandlerMixin.java | 136 +++--- .../skyblocker/mixins/ClientWorldMixin.java | 6 +- .../hysky/skyblocker/mixins/EntityMixin.java | 60 ++- .../skyblocker/mixins/WorldRendererMixin.java | 5 +- .../skyblocker/skyblock/FishingHelper.java | 8 +- .../skyblocker/skyblock/TeleportOverlay.java | 2 +- .../skyblock/crimson/dojo/DojoManager.java | 2 +- .../dungeon/secrets/SecretWaypoint.java | 2 +- .../dwarven/CrystalsChestHighlighter.java | 12 +- .../dwarven/WishingCompassSolver.java | 2 +- .../skyblocker/skyblock/end/EndHudWidget.java | 9 +- .../hysky/skyblocker/skyblock/end/TheEnd.java | 8 - .../skyblock/entity/MobBoundingBoxes.java | 12 +- .../skyblocker/skyblock/entity/MobGlow.java | 31 +- .../skyblocker/skyblock/rift/TheRift.java | 3 - .../skyblock/slayers/SlayerBossBars.java | 136 +++--- .../skyblock/slayers/SlayerEntitiesGlow.java | 174 -------- .../skyblock/slayers/SlayerManager.java | 392 +++++++++++++++++ .../skyblock/slayers/SlayerTier.java | 31 ++ .../skyblock/slayers/SlayerTimer.java | 117 ++++++ .../skyblock/slayers/SlayerType.java | 64 +++ .../boss/demonlord}/AttunementColors.java | 8 +- .../boss/demonlord}/FirePillarAnnouncer.java | 9 +- .../boss/vampire}/ManiaIndicator.java | 12 +- .../boss/vampire}/StakeIndicator.java | 10 +- .../boss/vampire}/TwinClawsIndicator.java | 14 +- .../boss/voidgloom}/BeaconHighlighter.java | 2 +- .../slayers/boss/voidgloom/LazerTimer.java | 60 +++ .../skyblock/slayers/hud/SlayerHudWidget.java | 87 ++++ .../hysky/skyblocker/utils/SlayerUtils.java | 116 ----- .../java/de/hysky/skyblocker/utils/Utils.java | 4 + .../utils/render/title/TitleContainer.java | 2 +- .../assets/skyblocker/lang/en_us.json | 25 ++ 37 files changed, 1365 insertions(+), 748 deletions(-) delete mode 100644 src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerEntitiesGlow.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerManager.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerTier.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerTimer.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerType.java rename src/main/java/de/hysky/skyblocker/skyblock/{crimson/slayer => slayers/boss/demonlord}/AttunementColors.java (86%) rename src/main/java/de/hysky/skyblocker/skyblock/{crimson/slayer => slayers/boss/demonlord}/FirePillarAnnouncer.java (89%) rename src/main/java/de/hysky/skyblocker/skyblock/{rift => slayers/boss/vampire}/ManiaIndicator.java (72%) rename src/main/java/de/hysky/skyblocker/skyblock/{rift => slayers/boss/vampire}/StakeIndicator.java (63%) rename src/main/java/de/hysky/skyblocker/skyblock/{rift => slayers/boss/vampire}/TwinClawsIndicator.java (72%) rename src/main/java/de/hysky/skyblocker/skyblock/{end => slayers/boss/voidgloom}/BeaconHighlighter.java (97%) create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/voidgloom/LazerTimer.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/slayers/hud/SlayerHudWidget.java delete mode 100644 src/main/java/de/hysky/skyblocker/utils/SlayerUtils.java 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 c98104af1b..74ad198571 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/WorldRendererMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/WorldRendererMixin.java @@ -19,7 +19,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; @@ -76,7 +77,7 @@ public class WorldRendererMixin implements CustomGlowInfo { 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 21a6771817..6994b9e3bf 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 4625e02d5a..7d22188b39 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -639,6 +639,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", @@ -675,6 +676,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)", @@ -687,10 +691,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.", @@ -706,6 +714,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)", @@ -976,6 +992,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", From 69e1ea849e195b07c3b3f9547701332df4f9866b Mon Sep 17 00:00:00 2001 From: Kevin <92656833+kevinthegreat1@users.noreply.github.com> Date: Sun, 22 Dec 2024 21:46:36 -0500 Subject: [PATCH 03/12] Add player list widget (#1086) --- .../skyblock/chat/filters/AdFilter.java | 4 +- .../skyblock/chat/filters/ShowOffFilter.java | 2 +- .../tabhud/config/preview/PreviewTab.java | 9 +++- .../skyblock/tabhud/util/PlayerListMgr.java | 45 +++++++++++-------- .../tabhud/widget/PlayerListWidget.java | 33 ++++++++++++++ .../skyblock/tabhud/widget/TabHudWidget.java | 19 ++++++-- .../widget/component/PlayerComponent.java | 5 +-- .../de/hysky/skyblocker/utils/Constants.java | 2 + 8 files changed, 90 insertions(+), 29 deletions(-) create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PlayerListWidget.java diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/filters/AdFilter.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/filters/AdFilter.java index 8f0192b709..9976d5a500 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chat/filters/AdFilter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/filters/AdFilter.java @@ -20,7 +20,7 @@ public AdFilter() { // 1. Player name // 2. Message // (?:\[[0-9]+\] )?(?:[<INSERT EMBLEMS>] )?(?:\[[A-Z+]+\] )?([A-Za-z0-9_]+): (.+) - super("(?:\\[[0-9]+\\] )?(?:[" + Constants.LEVEL_EMBLEMS+ "] )?(?:\\[[A-Z+]+\\] )?([A-Za-z0-9_]+): (.+)"); + super(Constants.PLAYER_NAME.pattern() + ": (.+)"); } @Override @@ -36,4 +36,4 @@ public boolean onMatch(Text _message, Matcher matcher) { protected ChatFilterResult state() { return SkyblockerConfigManager.get().chat.hideAds; } -} \ No newline at end of file +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/filters/ShowOffFilter.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/filters/ShowOffFilter.java index 0a0cbbe59d..384346c1f6 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chat/filters/ShowOffFilter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/filters/ShowOffFilter.java @@ -9,7 +9,7 @@ public class ShowOffFilter extends SimpleChatFilter { public ShowOffFilter() { //(?:\[[0-9]+\] )?(?:[<INSERT EMBLEMS>] )?(?:\[[A-Z+]+\] )?([A-Za-z0-9_]+) (?:<INSERT SHOW TYPES>) \[(.+)\] - super("(?:\\[[0-9]+\\] )?(?:[" + Constants.LEVEL_EMBLEMS + "] )?(?:\\[[A-Z+]+\\] )?([A-Za-z0-9_]+) (?:" + String.join("|", SHOW_TYPES) + ") \\[(.+)\\]"); + super(Constants.PLAYER_NAME.pattern() + " (?:" + String.join("|", SHOW_TYPES) + ") \\[(.+)\\]"); } @Override diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/PreviewTab.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/PreviewTab.java index 6b6c0e4669..f72bc2938a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/PreviewTab.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/PreviewTab.java @@ -1,5 +1,6 @@ package de.hysky.skyblocker.skyblock.tabhud.config.preview; +import com.mojang.authlib.GameProfile; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.tabhud.config.DungeonsTabPlaceholder; import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; @@ -25,6 +26,7 @@ import net.minecraft.client.gui.widget.ClickableWidget; import net.minecraft.client.gui.widget.ScrollableWidget; import net.minecraft.client.gui.widget.TextWidget; +import net.minecraft.client.network.PlayerListEntry; import net.minecraft.item.ItemStack; import net.minecraft.scoreboard.ScoreHolder; import net.minecraft.scoreboard.Scoreboard; @@ -41,6 +43,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; @@ -236,7 +239,11 @@ private void updatePlayerListFromPreview() { } } } - PlayerListMgr.updateWidgetsFrom(lines); + PlayerListMgr.updateWidgetsFrom(lines.stream().map(line -> { + PlayerListEntry playerListEntry = new PlayerListEntry(new GameProfile(UUID.randomUUID(), ""), false); + playerListEntry.setDisplayName(line); + return playerListEntry; + }).toList()); } void updateWidgets() { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListMgr.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListMgr.java index dbf69d33c7..d39c5d6c94 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListMgr.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListMgr.java @@ -79,7 +79,7 @@ public static void updateList() { if (Utils.isInDungeons()) { updateDungeons(null); } else { - updateWidgetsFrom(playerList.stream().map(PlayerListEntry::getDisplayName).filter(Objects::nonNull).toList()); + updateWidgetsFrom(playerList); } } @@ -116,7 +116,7 @@ public static void updateDungeons(List<Text> lines) { * * @param lines in-game TAB */ - public static void updateWidgetsFrom(List<Text> lines) { + public static void updateWidgetsFrom(List<PlayerListEntry> lines) { final Predicate<String> playersColumnPredicate = PLAYERS_COLUMN_PATTERN.asMatchPredicate(); final Predicate<String> infoColumnPredicate = INFO_COLUMN_PATTERN.asMatchPredicate(); @@ -124,9 +124,14 @@ public static void updateWidgetsFrom(List<Text> lines) { boolean doingPlayers = false; boolean playersDone = false; IntObjectPair<String> hypixelWidgetName = IntObjectPair.of(0xFFFF00, ""); + // These two lists should match each other. + // playerListEntries is only used for the player list widget List<Text> contents = new ArrayList<>(); + List<PlayerListEntry> playerListEntries = new ArrayList<>(); - for (Text displayName : lines) { + for (PlayerListEntry playerListEntry : lines) { + Text displayName = playerListEntry.getDisplayName(); + if (displayName == null) continue; String string = displayName.getString(); if (string.isBlank()) continue; @@ -143,8 +148,9 @@ public static void updateWidgetsFrom(List<Text> lines) { // Check if info, if it is, dip out if (infoColumnPredicate.test(string)) { playersDone = true; - if (!contents.isEmpty()) tabWidgetsToShow.add(getTabHudWidget(hypixelWidgetName, contents)); + if (!contents.isEmpty()) tabWidgetsToShow.add(getTabHudWidget(hypixelWidgetName, contents, playerListEntries)); contents.clear(); + playerListEntries.clear(); continue; } } else { @@ -153,18 +159,23 @@ public static void updateWidgetsFrom(List<Text> lines) { // Now check for : because of the farming contest ACTIVE // Check for mining event minutes CUZ THEY FUCKING FORGOT THE SPACE iefzeoifzeoifomezhif if (!string.startsWith(" ") && string.contains(":") && (!hypixelWidgetName.right().startsWith("Mining Event") || !string.toLowerCase().startsWith("ends in"))) { - if (!contents.isEmpty()) tabWidgetsToShow.add(getTabHudWidget(hypixelWidgetName, contents)); + if (!contents.isEmpty()) tabWidgetsToShow.add(getTabHudWidget(hypixelWidgetName, contents, playerListEntries)); contents.clear(); + playerListEntries.clear(); Pair<IntObjectPair<String>, ? extends Text> nameAndInfo = getNameAndInfo(displayName); hypixelWidgetName = nameAndInfo.left(); - if (!nameAndInfo.right().getString().isBlank()) contents.add(trim(nameAndInfo.right())); + if (!nameAndInfo.right().getString().isBlank()) { + contents.add(trim(nameAndInfo.right())); + playerListEntries.add(playerListEntry); + } continue; } } // Add the line to the content contents.add(trim(displayName)); + playerListEntries.add(playerListEntry); } - if (!contents.isEmpty()) tabWidgetsToShow.add(getTabHudWidget(hypixelWidgetName, contents)); + if (!contents.isEmpty()) tabWidgetsToShow.add(getTabHudWidget(hypixelWidgetName, contents, playerListEntries)); if (!tabWidgetsToShow.contains(tabWidgetInstances.get("Active Effects")) && SkyblockerConfigManager.get().uiAndVisuals.tabHud.effectsFromFooter) { tabWidgetsToShow.add(getTabHudWidget("Active Effects", List.of())); } @@ -203,23 +214,21 @@ private static Text trim(Text text) { return out; } - private static TabHudWidget getTabHudWidget(IntObjectPair<String> hypixelWidgetName, List<Text> lines) { + private static TabHudWidget getTabHudWidget(IntObjectPair<String> hypixelWidgetName, List<Text> lines, @Nullable List<PlayerListEntry> playerListEntries) { + TabHudWidget tabHudWidget; if (tabWidgetInstances.containsKey(hypixelWidgetName.right())) { - TabHudWidget tabHudWidget = tabWidgetInstances.get(hypixelWidgetName.right()); - tabHudWidget.updateFromTab(lines); - tabHudWidget.update(); - return tabHudWidget; + tabHudWidget = tabWidgetInstances.get(hypixelWidgetName.right()); } else { - DefaultTabHudWidget defaultTabHudWidget = new DefaultTabHudWidget(hypixelWidgetName.right(), Text.literal(hypixelWidgetName.right()).formatted(Formatting.BOLD), hypixelWidgetName.firstInt()); - ScreenMaster.addWidgetInstance(defaultTabHudWidget); - defaultTabHudWidget.updateFromTab(lines); - defaultTabHudWidget.update(); - return defaultTabHudWidget; + tabHudWidget = new DefaultTabHudWidget(hypixelWidgetName.right(), Text.literal(hypixelWidgetName.right()).formatted(Formatting.BOLD), hypixelWidgetName.firstInt()); + ScreenMaster.addWidgetInstance(tabHudWidget); } + tabHudWidget.updateFromTab(lines, playerListEntries); + tabHudWidget.update(); + return tabHudWidget; } private static TabHudWidget getTabHudWidget(String hypixelWidgetName, List<Text> lines) { - return getTabHudWidget(IntObjectPair.of(0xFFFF0000, hypixelWidgetName), lines); + return getTabHudWidget(IntObjectPair.of(0xFFFF0000, hypixelWidgetName), lines, null); } /** diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PlayerListWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PlayerListWidget.java new file mode 100644 index 0000000000..dfcf620c31 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PlayerListWidget.java @@ -0,0 +1,33 @@ +package de.hysky.skyblocker.skyblock.tabhud.widget; + +import de.hysky.skyblocker.annotations.RegisterWidget; +import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent; +import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlayerComponent; +import net.minecraft.client.network.PlayerListEntry; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +@RegisterWidget +public class PlayerListWidget extends TabHudWidget { + private static final MutableText TITLE = Text.literal("Players").formatted(Formatting.BOLD); + + public PlayerListWidget() { + super("Players", TITLE, Formatting.AQUA.getColorValue()); + } + + @Override + protected void updateContent(List<Text> lines, @Nullable List<PlayerListEntry> playerListEntries) { + if (playerListEntries == null) { + lines.forEach(text -> addComponent(new PlainTextComponent(text))); + } else { + playerListEntries.forEach(playerListEntry -> addComponent(new PlayerComponent(playerListEntry))); + } + } + + @Override + protected void updateContent(List<Text> lines) {} +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/TabHudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/TabHudWidget.java index a34d75e7e0..8fac951ce5 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/TabHudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/TabHudWidget.java @@ -2,8 +2,10 @@ import de.hysky.skyblocker.skyblock.tabhud.widget.component.Component; import de.hysky.skyblocker.utils.Location; +import net.minecraft.client.network.PlayerListEntry; import net.minecraft.text.MutableText; import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -33,9 +35,9 @@ public void updateContent() { cachedComponents.forEach(super::addComponent); } - public void updateFromTab(List<Text> lines) { + public void updateFromTab(List<Text> lines, @Nullable List<PlayerListEntry> playerListEntries) { cachedComponents.clear(); - updateContent(lines); + updateContent(lines, playerListEntries); } /** @@ -63,7 +65,18 @@ public final boolean isEnabledIn(Location location) { } /** - * Update the content from the hypixel widget's lines + * Same as {@link #updateContent(List)} but only override if you need access to {@code playerListEntries}. + * + * @param playerListEntries the player list entries, which should match the lines. + * Null in dungeons. + * @see #updateContent(List) + */ + protected void updateContent(List<Text> lines, @Nullable List<PlayerListEntry> playerListEntries) { + updateContent(lines); + } + + /** + * Updates the content from the hypixel widget's lines * * @param lines the lines, they are formatted and trimmed, no blank lines will be present. * If the vanilla tab widget has text right after the : they will be put on the first line. diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/PlayerComponent.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/PlayerComponent.java index 138a1cd610..0cbf5e5f06 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/PlayerComponent.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/PlayerComponent.java @@ -19,10 +19,7 @@ public class PlayerComponent extends Component { public PlayerComponent(PlayerListEntry ple) { - boolean plainNames = false; - Team team = ple.getScoreboardTeam(); - String username = ple.getProfile().getName(); - name = (team != null && !plainNames) ? Text.empty().append(team.getPrefix()).append(Text.literal(username).formatted(team.getColor())).append(team.getSuffix()) : Text.of(username); + name = ple.getDisplayName(); tex = ple.getSkinTextures().texture(); this.width = SKIN_ICO_DIM + PAD_S + txtRend.getWidth(name); diff --git a/src/main/java/de/hysky/skyblocker/utils/Constants.java b/src/main/java/de/hysky/skyblocker/utils/Constants.java index 50c7963849..738f71c9db 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Constants.java +++ b/src/main/java/de/hysky/skyblocker/utils/Constants.java @@ -8,12 +8,14 @@ import java.util.List; import java.util.Map; import java.util.function.Supplier; +import java.util.regex.Pattern; /** * Holds generic static constants */ public interface Constants { String LEVEL_EMBLEMS = "\u2E15\u273F\u2741\u2E19\u03B1\u270E\u2615\u2616\u2663\u213B\u2694\u27B6\u26A1\u2604\u269A\u2693\u2620\u269B\u2666\u2660\u2764\u2727\u238A\u1360\u262C\u269D\u29C9\uA214\u32D6\u2E0E\u26A0\uA541\u3020\u30C4\u2948\u2622\u2623\u273E\u269C\u0BD0\u0A6D\u2742\u16C3\u3023\u10F6\u0444\u266A\u266B\u04C3\u26C1\u26C3\u16DD\uA03E\u1C6A\u03A3\u09EB\u2603\u2654\u26C2\u0FC7\uA925\uA56A\u2592\u12DE"; + Pattern PLAYER_NAME = Pattern.compile("(?:\\[[0-9]+\\] )?(?:[" + Constants.LEVEL_EMBLEMS + "] )?(?:\\[[A-Z+]+\\] )?([A-Za-z0-9_]+)"); Supplier<MutableText> PREFIX = () -> { LocalDate time = LocalDate.now(); From bd39443716d76a854a297263d3a7614ad5c84a0d Mon Sep 17 00:00:00 2001 From: Kevin <92656833+kevinthegreat1@users.noreply.github.com> Date: Sun, 22 Dec 2024 21:46:48 -0500 Subject: [PATCH 04/12] Fix ender nodes detection (#1095) --- .../de/hysky/skyblocker/skyblock/end/EnderNodes.java | 9 ++++++--- .../de/hysky/skyblocker/utils/waypoint/Waypoint.java | 8 ++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/end/EnderNodes.java b/src/main/java/de/hysky/skyblocker/skyblock/end/EnderNodes.java index cf1d4a9cbb..118f480a8d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/end/EnderNodes.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/end/EnderNodes.java @@ -78,8 +78,11 @@ public static void onParticle(ParticleS2CPacket packet) { EnderNode enderNode = enderNodes.computeIfAbsent(pos, EnderNode::new); IntIntPair particles = enderNode.particles.get(direction); - particles.left(particles.leftInt() + 1); - particles.right(particles.rightInt() + 1); + if (ParticleTypes.PORTAL.getType().equals(particleType)) { + particles.left(particles.leftInt() + 1); + } else if (ParticleTypes.WITCH.getType().equals(particleType)) { + particles.right(particles.rightInt() + 1); + } } private static void update() { @@ -120,7 +123,7 @@ Direction.NORTH, new IntIntMutablePair(0, 0) private long lastConfirmed; private EnderNode(BlockPos pos) { - super(pos, () -> SkyblockerConfigManager.get().uiAndVisuals.waypoints.waypointType, ColorUtils.getFloatComponents(DyeColor.CYAN), false); + super(pos, () -> SkyblockerConfigManager.get().uiAndVisuals.waypoints.waypointType.withoutBeacon(), ColorUtils.getFloatComponents(DyeColor.CYAN), false); } private void updateParticles() { diff --git a/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java b/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java index a5c5d25164..499e605dd9 100644 --- a/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java +++ b/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java @@ -215,5 +215,13 @@ public String asString() { public String toString() { return I18n.translate("skyblocker.waypoints.type." + name()); } + + public Type withoutBeacon() { + return switch (this) { + case WAYPOINT -> HIGHLIGHT; + case OUTLINED_WAYPOINT -> OUTLINED_HIGHLIGHT; + default -> this; + }; + } } } From ab2d7a976495b6e20a031ab3efc20d34bff3a4a8 Mon Sep 17 00:00:00 2001 From: Aaron <51387595+AzureAaron@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:06:09 -0500 Subject: [PATCH 05/12] Change election over text (#1099) The lowercase 'o' of over didn't feel right and capitalizing it just wasn't enough either. --- .../skyblocker/skyblock/tabhud/widget/ElectionWidget.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java index 0a6f8ce15e..ed061f8a83 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java @@ -24,8 +24,8 @@ public class ElectionWidget extends TabHudWidget { private static final HashMap<String, ItemStack> MAYOR_DATA = new HashMap<>(); - private static final Text EL_OVER = Text.literal("Election ") - .append(Text.literal("over!").formatted(Formatting.RED)); + private static final Text EL_OVER = Text.literal("Election: ") + .append(Text.literal("Over!").formatted(Formatting.RED)); // pattern matching a candidate while people are voting // group 1: name From 23f986b6807eaecefb0cf4709b2dec67bd7eb1f2 Mon Sep 17 00:00:00 2001 From: viciscat <51047087+viciscat@users.noreply.github.com> Date: Mon, 23 Dec 2024 23:01:51 +0100 Subject: [PATCH 06/12] forgot the garden plots texture lol (#1097) * Add Garden Plots Widget * small fix * make for loop more boring * exclusion zones * rebase oopsie daisy and JEI exclusion zone * ctrl+shift+up arrow * Fix init merge conflicts * merge and port * 1.21.4 * forgot the texture lmao --------- Co-authored-by: Kevinthegreat <92656833+kevinthegreat1@users.noreply.github.com> --- .../skyblocker/textures/gui/garden_plots.png | Bin 0 -> 361 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/assets/skyblocker/textures/gui/garden_plots.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/garden_plots.png b/src/main/resources/assets/skyblocker/textures/gui/garden_plots.png new file mode 100644 index 0000000000000000000000000000000000000000..b7d86023492edbbb106391737bc6b7d22219a4bc GIT binary patch literal 361 zcmeAS@N?(olHy`uVBq!ia0vp^89-dm!3-o#1j{!7DaPU;cPEB*=VV?2IV|apzK#qG z8~eHcB(eheq5(c3u0Z<#|NqC19SaQ&H8(f!?(PPPq;8%r4W!shg8YJkvOvHf<<0B{ z6yhxKh%9Dc;5!Jyj5{V~zXb}O@^o<w$#8yq`ygM70*`Cpm5u-ZpS&!_#(P>k+vwz8 zX^z>#H%#<$($?A)AH5Y8AD8~d`}SWu#<y2x+xOq+=9?flK?Vr#@cvpeH+ip+f7pwd zTl1_KnSQZ8Vf@-)v)~@r+`rDxt_7=en>>3cD!h#yA}lxI`*#6hv*3Ar8q7?y7#Ytt zB%N+KJ7qcZsfLC$2M6N?H;fXEpT2A`Qdl4<aDhi-+j`#{w;G<n;Jv24hBZf{;{M#f lbZno|e*Zrn!Sip5?|-)G#LD7H-aub5c)I$ztaD0e0svw<ki`H1 literal 0 HcmV?d00001 From 6cf640e935c300ed7252f9f593387b945242404b Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Tue, 24 Dec 2024 06:44:05 +0300 Subject: [PATCH 07/12] Add unbreakable carpet highlighter (#1034) * Add mithril carpet highlighter * Make mithril carpet highlighter configurable * Halve the required getBlockState calls * Add light blue carpet as a possible carpet block * Add tungsten carpets and rename carpet highlighter Because apparently it's not limited to mithril * AVLTreeSet instead of ArraySet * Use scheduleCyclic instead of END_CLIENT_TICK event * Check if the feature is enabled before ticking * Fix rebase artifacts * Remove unused variable --- .../config/categories/MiningCategory.java | 20 ++++ .../config/configs/MiningConfig.java | 6 + .../skyblock/dwarven/CarpetHighlighter.java | 103 ++++++++++++++++++ .../assets/skyblocker/lang/en_us.json | 5 + src/main/resources/skyblocker.accesswidener | 3 + 5 files changed, 137 insertions(+) create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/dwarven/CarpetHighlighter.java diff --git a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java index 4fc056923e..343643685b 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java @@ -4,6 +4,7 @@ import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.configs.MiningConfig; import de.hysky.skyblocker.skyblock.dwarven.CrystalsHudWidget; +import de.hysky.skyblocker.skyblock.dwarven.CarpetHighlighter; import dev.isxander.yacl3.api.*; import dev.isxander.yacl3.api.controller.ColorControllerBuilder; import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; @@ -55,6 +56,25 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig newValue -> config.mining.dwarvenMines.solvePuzzler = newValue) .controller(ConfigUtils::createBooleanController) .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.mining.dwarvenMines.enableCarpetHighlight")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.mining.dwarvenMines.enableCarpetHighlight.@Tooltip"))) + .binding(defaults.mining.dwarvenMines.enableCarpetHighlighter, + () -> config.mining.dwarvenMines.enableCarpetHighlighter, + newValue -> config.mining.dwarvenMines.enableCarpetHighlighter = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Color>createBuilder() + .name(Text.translatable("skyblocker.config.mining.dwarvenMines.carpetHighlightColor")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.mining.dwarvenMines.carpetHighlightColor.@Tooltip"))) + .binding(defaults.mining.dwarvenMines.carpetHighlightColor, + () -> config.mining.dwarvenMines.carpetHighlightColor, + newValue -> { + config.mining.dwarvenMines.carpetHighlightColor = newValue; + CarpetHighlighter.INSTANCE.configCallback(newValue); + }) + .controller(opt -> ColorControllerBuilder.create(opt).allowAlpha(true)) + .build()) .build()) //Crystal Hollows diff --git a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java index 4691047c4b..0eb76f2273 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java @@ -39,6 +39,12 @@ public static class DwarvenMines { @SerialEntry public boolean solvePuzzler = true; + + @SerialEntry + public boolean enableCarpetHighlighter = true; + + @SerialEntry + public Color carpetHighlightColor = new Color(255, 0, 0, 76); } @Deprecated diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CarpetHighlighter.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CarpetHighlighter.java new file mode 100644 index 0000000000..6395e70451 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CarpetHighlighter.java @@ -0,0 +1,103 @@ +package de.hysky.skyblocker.skyblock.dwarven; + +import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.events.SkyblockEvents; +import de.hysky.skyblocker.utils.Boxes; +import de.hysky.skyblocker.utils.Location; +import de.hysky.skyblocker.utils.Resettable; +import de.hysky.skyblocker.utils.render.RenderHelper; +import de.hysky.skyblocker.utils.render.Renderable; +import de.hysky.skyblocker.utils.scheduler.Scheduler; +import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.CarpetBlock; +import net.minecraft.client.MinecraftClient; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; + +import java.awt.*; + +/** + * Highlights unbreakable carpets within ore veins in the Dwarven Mines. + */ +public final class CarpetHighlighter implements Renderable, Resettable { + public static final CarpetHighlighter INSTANCE = new CarpetHighlighter(); + + private static final Vec3d CARPET_BOUNDING_BOX = Boxes.getLengthVec(CarpetBlock.SHAPE.getBoundingBox()); + private static final int SEARCH_RADIUS = 15; + private static final int TICK_INTERVAL = 15; + private static final ObjectAVLTreeSet<BlockPos> CARPET_LOCATIONS = new ObjectAVLTreeSet<>(); + private static float[] colorComponents; + private static boolean isLocationValid = false; + + @Init + public static void init() { + INSTANCE.configCallback(SkyblockerConfigManager.get().mining.dwarvenMines.carpetHighlightColor); + WorldRenderEvents.AFTER_TRANSLUCENT.register(INSTANCE::render); + SkyblockEvents.LOCATION_CHANGE.register(INSTANCE::onLocationChange); + Scheduler.INSTANCE.scheduleCyclic(INSTANCE::tick, TICK_INTERVAL); + ClientPlayConnectionEvents.JOIN.register(INSTANCE); + } + + @Override + public void render(WorldRenderContext context) { + if (!isLocationValid || !SkyblockerConfigManager.get().mining.dwarvenMines.enableCarpetHighlighter) return; + for (BlockPos carpetLocation : CARPET_LOCATIONS) { + RenderHelper.renderFilled(context, Vec3d.of(carpetLocation), CARPET_BOUNDING_BOX, colorComponents, colorComponents[3], false); + } + } + + public void onLocationChange(Location location) { + isLocationValid = location == Location.DWARVEN_MINES; + } + + public void tick() { + if (!isLocationValid || !SkyblockerConfigManager.get().mining.dwarvenMines.enableCarpetHighlighter || MinecraftClient.getInstance().world == null || MinecraftClient.getInstance().player == null) return; + Iterable<BlockPos> iterable = BlockPos.iterateOutwards(MinecraftClient.getInstance().player.getBlockPos(), SEARCH_RADIUS, SEARCH_RADIUS, SEARCH_RADIUS); + for (BlockPos blockPos : iterable) { + //The iterator contains a BlockPos.Mutable that it changes the position of to iterate over blocks, + // so it has to be converted to an immutable BlockPos or the position will change based on the player's position && the search radius + if (checkForCarpet(blockPos)) CARPET_LOCATIONS.add(blockPos.toImmutable()); + } + } + + /** + * @param blockPos The position to check for a carpet + * @return Whether the block at the given position is a gray carpet with a sea lantern below it, which is how all unbreakable carpets are placed + * @implNote <p>getBlockState is a heavy method, so this method will become a hot spot as the search radius increases || the tick interval decreases.</p> + * <p>Consider profiling this method if either of those values are changed.</p> + */ + private boolean checkForCarpet(BlockPos blockPos) { + @SuppressWarnings("DataFlowIssue") // Null check is already done in the run method + BlockState actualBlock = MinecraftClient.getInstance().world.getBlockState(blockPos); + // Gray/light blue - mithril + // Light gray - tungsten + // There are other colors for some ores in the royal mines, + // but since the actual ores don't include wool blocks + // they're not easily confused as ores so they are not accounted for here + if (!(actualBlock.isOf(Blocks.GRAY_CARPET) || + actualBlock.isOf(Blocks.LIGHT_BLUE_CARPET) || + actualBlock.isOf(Blocks.LIGHT_GRAY_CARPET))) return false; + BlockState blockBelow = MinecraftClient.getInstance().world.getBlockState(blockPos.down()); + return blockBelow.isOf(Blocks.SEA_LANTERN); + } + + /** + * <p>Caches the color components from the given color for rendering to avoid recalculating them every frame.</p> + * <p>Called by the {@link de.hysky.skyblocker.config.categories.MiningCategory MiningCategory} > carpetHighlightColor when the color is updated.</p> + */ + public void configCallback(Color color) { + colorComponents = color.getRGBComponents(null); + } + + @Override + public void reset() { + isLocationValid = false; + CARPET_LOCATIONS.clear(); + } +} diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 7d22188b39..f980869d5f 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -611,6 +611,11 @@ "skyblocker.config.mining.dwarvenMines": "Dwarven Mines", "skyblocker.config.mining.dwarvenMines.solveFetchur": "Solve Fetchur", "skyblocker.config.mining.dwarvenMines.solvePuzzler": "Solve Puzzler Puzzle", + "skyblocker.config.mining.dwarvenMines.enableCarpetHighlight": "Enable Unbreakable Carpet Highlighter", + "skyblocker.config.mining.dwarvenMines.enableCarpetHighlight.@Tooltip": "Highlights unbreakable carpets within ore veins in the Dwarven Mines.", + "skyblocker.config.mining.dwarvenMines.carpetHighlightColor": "Carpet Highlight Color", + "skyblocker.config.mining.dwarvenMines.carpetHighlightColor.@Tooltip": "Sets the color of the highlight for the unbreakable carpets.", + "skyblocker.config.mining.enableDrillFuel": "Enable Drill Fuel", diff --git a/src/main/resources/skyblocker.accesswidener b/src/main/resources/skyblocker.accesswidener index 6ff14678ce..9bce0c27e5 100644 --- a/src/main/resources/skyblocker.accesswidener +++ b/src/main/resources/skyblocker.accesswidener @@ -27,3 +27,6 @@ extendable method net/minecraft/nbt/StringNbtReader readCompound ()Lnet/minecraf extendable method net/minecraft/nbt/StringNbtReader parseList ()Lnet/minecraft/nbt/NbtElement; extendable method net/minecraft/nbt/StringNbtReader readComma ()Z extendable method net/minecraft/nbt/StringNbtReader expect (C)V + +# Block Shapes +accessible field net/minecraft/block/CarpetBlock SHAPE Lnet/minecraft/util/shape/VoxelShape; From fbbd3aefe2c3ae456a9a1d28415ef19410c10a76 Mon Sep 17 00:00:00 2001 From: Aaron <51387595+AzureAaron@users.noreply.github.com> Date: Mon, 23 Dec 2024 22:44:19 -0500 Subject: [PATCH 08/12] Fix Simon Says Solver (#1100) --- .../skyblocker/mixins/ClientWorldMixin.java | 16 +++++++++++++--- .../skyblock/dungeon/device/SimonSays.java | 10 ++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java index 0d7e2c708a..d9d90cd82a 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java @@ -10,15 +10,25 @@ import net.minecraft.block.Blocks; import net.minecraft.client.world.ClientWorld; import net.minecraft.util.math.BlockPos; +import net.minecraft.world.BlockView; + +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; 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.sugar.Local; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; @Mixin(ClientWorld.class) -public class ClientWorldMixin { +public abstract class ClientWorldMixin implements BlockView { + + @Inject(method = "handleBlockUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;setBlockState(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;II)Z")) + private void skyblocker$beforeBlockUpdate(CallbackInfo ci, @Local(argsOnly = true) BlockPos pos, @Share("old") LocalRef<@Nullable BlockState> oldState) { + oldState.set(getBlockState(pos)); + } /** * @implNote The {@code pos} can be mutable when this is called by chunk delta updates, so if you want to copy it into memory @@ -26,7 +36,7 @@ public class ClientWorldMixin { */ //TODO might be worth creating an event for this @Inject(method = "handleBlockUpdate", at = @At("RETURN")) - private void skyblocker$handleBlockUpdate(CallbackInfo ci, @Local(argsOnly = true) BlockPos pos, @Local(argsOnly = true) BlockState state) { + private void skyblocker$afterBlockUpdate(CallbackInfo ci, @Local(argsOnly = true) BlockPos pos, @Local(argsOnly = true) BlockState state, @Share("old") LocalRef<@Nullable BlockState> oldState) { if (Utils.isInCrimson()) { DojoManager.onBlockUpdate(pos.toImmutable(), state); } else if (Utils.isInCrystalHollows()) { @@ -37,6 +47,6 @@ public class ClientWorldMixin { if (state.isOf(Blocks.BEACON)) BeaconHighlighter.beaconPositions.add(pos.toImmutable()); } - SimonSays.onBlockUpdate(pos, state); + SimonSays.onBlockUpdate(pos, state, oldState.get()); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java index e6c71b3d04..1b94ca1838 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java @@ -32,6 +32,8 @@ import java.util.Objects; +import org.jetbrains.annotations.Nullable; + public class SimonSays { private static final Box BOARD_AREA = Box.enclosing(new BlockPos(111, 123, 92), new BlockPos(111, 120, 95)); private static final Box BUTTONS_AREA = Box.enclosing(new BlockPos(110, 123, 92), new BlockPos(110, 120, 95)); @@ -71,14 +73,14 @@ private static ActionResult onBlockInteract(PlayerEntity player, World world, Ha //If the player goes out of the range required to receive block/chunk updates then their solver won't detect stuff but that //doesn't matter because if they're doing pre-4 or something they won't be doing the ss, and if they end up needing to they can //just reset it or have the other person finish the current sequence first then let them do it. - public static void onBlockUpdate(BlockPos pos, BlockState state) { + public static void onBlockUpdate(BlockPos pos, BlockState newState, @Nullable BlockState oldState) { if (shouldProcess()) { Vec3d posVec = Vec3d.of(pos); - Block block = state.getBlock(); + Block newBlock = newState.getBlock(); - if (BOARD_AREA.contains(posVec) && block.equals(Blocks.SEA_LANTERN)) { + if (BOARD_AREA.contains(posVec) && newBlock.equals(Blocks.OBSIDIAN) && oldState != null && oldState.getBlock().equals(Blocks.SEA_LANTERN)) { SIMON_PATTERN.add(pos.toImmutable()); //Convert to immutable because chunk delta updates use the mutable variant - } else if (BUTTONS_AREA.contains(posVec) && block.equals(Blocks.AIR)) { + } else if (BUTTONS_AREA.contains(posVec) && newBlock.equals(Blocks.AIR)) { //Upon reaching the showing of the next sequence we need to reset the state so that we don't show old data //Otherwise, the nextIndex will go beyond 5 and that can cause bugs, it also helps with the other case noted above reset(); From 2335b31f51f33c3d875a1f1125631f22fddf0382 Mon Sep 17 00:00:00 2001 From: Kevin <92656833+kevinthegreat1@users.noreply.github.com> Date: Tue, 24 Dec 2024 16:06:57 -0500 Subject: [PATCH 09/12] Mob glow refactor (#1101) * Add mob glow cache * Clean up mixins * Clear cache once a tick + Javadocs --- .../skyblocker/injected/CustomGlowInfo.java | 8 -- .../mixins/RenderPhaseDepthTestMixin.java | 12 +- .../skyblocker/mixins/WorldRendererMixin.java | 46 ++----- .../skyblock/entity/MobBoundingBoxes.java | 2 +- .../skyblocker/skyblock/entity/MobGlow.java | 120 +++++++++++------- src/main/resources/fabric.mod.json | 3 - 6 files changed, 86 insertions(+), 105 deletions(-) delete mode 100644 src/main/java/de/hysky/skyblocker/injected/CustomGlowInfo.java diff --git a/src/main/java/de/hysky/skyblocker/injected/CustomGlowInfo.java b/src/main/java/de/hysky/skyblocker/injected/CustomGlowInfo.java deleted file mode 100644 index fd1ca40157..0000000000 --- a/src/main/java/de/hysky/skyblocker/injected/CustomGlowInfo.java +++ /dev/null @@ -1,8 +0,0 @@ -package de.hysky.skyblocker.injected; - -public interface CustomGlowInfo { - - default boolean atLeastOneMobHasCustomGlow() { - return false; - } -} diff --git a/src/main/java/de/hysky/skyblocker/mixins/RenderPhaseDepthTestMixin.java b/src/main/java/de/hysky/skyblocker/mixins/RenderPhaseDepthTestMixin.java index d90606db15..7595d3e2a8 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/RenderPhaseDepthTestMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/RenderPhaseDepthTestMixin.java @@ -8,9 +8,9 @@ import com.llamalad7.mixinextras.sugar.Local; import com.mojang.blaze3d.systems.RenderSystem; -import net.minecraft.client.MinecraftClient; +import de.hysky.skyblocker.skyblock.entity.MobGlow; + import net.minecraft.client.render.RenderPhase; -import net.minecraft.client.render.WorldRenderer; @Mixin(RenderPhase.DepthTest.class) public class RenderPhaseDepthTestMixin { @@ -19,9 +19,7 @@ public class RenderPhaseDepthTestMixin { private static Runnable skyblocker$modifyOutlineAlwaysStartAction(Runnable original, @Local(argsOnly = true) String depthFunctionName) { if (depthFunctionName.equals("outline_always")) { return () -> { - WorldRenderer worldRenderer = MinecraftClient.getInstance().worldRenderer; - - if (worldRenderer != null && worldRenderer.atLeastOneMobHasCustomGlow()) { + if (MobGlow.atLeastOneMobHasCustomGlow()) { RenderSystem.enableDepthTest(); RenderSystem.depthFunc(GL11.GL_LEQUAL); } @@ -35,9 +33,7 @@ public class RenderPhaseDepthTestMixin { private static Runnable skyblocker$modifyOutlineAlwaysEndAction(Runnable original, @Local(argsOnly = true) String depthFunctionName) { if (depthFunctionName.equals("outline_always")) { return () -> { - WorldRenderer worldRenderer = MinecraftClient.getInstance().worldRenderer; - - if (worldRenderer != null && worldRenderer.atLeastOneMobHasCustomGlow()) { + if (MobGlow.atLeastOneMobHasCustomGlow()) { RenderSystem.disableDepthTest(); RenderSystem.depthFunc(GL11.GL_LEQUAL); } diff --git a/src/main/java/de/hysky/skyblocker/mixins/WorldRendererMixin.java b/src/main/java/de/hysky/skyblocker/mixins/WorldRendererMixin.java index 74ad198571..27a2094139 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/WorldRendererMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/WorldRendererMixin.java @@ -3,7 +3,6 @@ import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyVariable; @@ -12,10 +11,7 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.sugar.Local; -import com.llamalad7.mixinextras.sugar.Share; -import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef; -import de.hysky.skyblocker.injected.CustomGlowInfo; import de.hysky.skyblocker.skyblock.dungeon.LividColor; import de.hysky.skyblocker.skyblock.entity.MobBoundingBoxes; import de.hysky.skyblocker.skyblock.entity.MobGlow; @@ -28,23 +24,19 @@ import net.minecraft.entity.decoration.ArmorStandEntity; @Mixin(WorldRenderer.class) -public class WorldRendererMixin implements CustomGlowInfo { +public class WorldRendererMixin { @Shadow @Final private MinecraftClient client; @Shadow @Final private DefaultFramebufferSet framebufferSet; - @Unique - private boolean atLeastOneMobHasCustomGlow; - @ModifyExpressionValue(method = "getEntitiesToRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;hasOutline(Lnet/minecraft/entity/Entity;)Z")) - private boolean skyblocker$setupEntityOutlineFramebufferIfCustomGlow(boolean original, @Local Entity entity) { - boolean hasCustomGlow = MobGlow.shouldMobGlow(entity); - - if (hasCustomGlow) atLeastOneMobHasCustomGlow = true; - - return original || hasCustomGlow; + @ModifyExpressionValue(method = {"getEntitiesToRender", "renderEntities"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;hasOutline(Lnet/minecraft/entity/Entity;)Z")) + private boolean skyblocker$shouldMobGlow(boolean original, @Local Entity entity) { + boolean allowGlow = LividColor.allowGlow(); + boolean customGlow = MobGlow.hasOrComputeMobGlow(entity); + return allowGlow && original || customGlow; } @Inject(method = "method_62214", @@ -52,27 +44,19 @@ public class WorldRendererMixin implements CustomGlowInfo { at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gl/Framebuffer;clear()V", ordinal = 0, shift = At.Shift.AFTER) ) private void skyblocker$copyFramebufferDepth2AdjustGlowVisibility(CallbackInfo ci) { - if (atLeastOneMobHasCustomGlow) framebufferSet.entityOutlineFramebuffer.get().copyDepthFrom(client.getFramebuffer()); - } - - @ModifyExpressionValue(method = "renderEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;hasOutline(Lnet/minecraft/entity/Entity;)Z")) - private boolean skyblocker$shouldMobGlow(boolean original, @Local Entity entity, @Share("hasCustomGlow") LocalBooleanRef hasCustomGlow) { - boolean allowGlow = LividColor.allowGlow(); - boolean shouldGlow = MobGlow.shouldMobGlow(entity); - hasCustomGlow.set(shouldGlow); - return allowGlow && original || shouldGlow; + if (MobGlow.atLeastOneMobHasCustomGlow()) framebufferSet.entityOutlineFramebuffer.get().copyDepthFrom(client.getFramebuffer()); } @ModifyVariable(method = "renderEntities", slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;hasOutline(Lnet/minecraft/entity/Entity;)Z"), to = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/OutlineVertexConsumerProvider;setColor(IIII)V")), at = @At("STORE"), ordinal = 0 ) - private int skyblocker$modifyGlowColor(int color, @Local Entity entity, @Share("hasCustomGlow") LocalBooleanRef hasCustomGlow) { - return hasCustomGlow.get() ? MobGlow.getGlowColor(entity) : color; + private int skyblocker$modifyGlowColor(int color, @Local Entity entity) { + return MobGlow.getMobGlowOrDefault(entity, color); } @Inject(method = "renderEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;renderEntity(Lnet/minecraft/entity/Entity;DDDFLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;)V")) - private void skyblocker$beforeEntityIsRendered(CallbackInfo ci, @Local Entity entity) { + private void skyblocker$renderMobBoundingBox(CallbackInfo ci, @Local Entity entity) { boolean shouldShowBoundingBox = MobBoundingBoxes.shouldDrawMobBoundingBox(entity); if (shouldShowBoundingBox) { @@ -82,14 +66,4 @@ public class WorldRendererMixin implements CustomGlowInfo { ); } } - - @Override - public boolean atLeastOneMobHasCustomGlow() { - return atLeastOneMobHasCustomGlow; - } - - @Inject(method = "render", at = @At("TAIL")) - private void skyblocker$resetCustomGlowBool(CallbackInfo ci) { - atLeastOneMobHasCustomGlow = false; - } } 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 1f318e21c6..65ad691841 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobBoundingBoxes.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobBoundingBoxes.java @@ -52,7 +52,7 @@ public static boolean shouldDrawMobBoundingBox(Entity entity) { } public static float[] getBoxColor(Entity entity) { - int color = MobGlow.getGlowColor(entity); + int color = MobGlow.getMobGlow(entity); return new float[] { ((color >> 16) & 0xFF) / 255f, ((color >> 8) & 0xFF) / 255f, (color & 0xFF) / 255f }; } 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 8c3086bcd8..0a53b7fad7 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java @@ -1,6 +1,7 @@ package de.hysky.skyblocker.skyblock.entity; import com.google.common.collect.Streams; +import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.SlayersConfig; import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager; @@ -13,13 +14,15 @@ import de.hysky.skyblocker.skyblock.slayers.boss.demonlord.AttunementColors; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.minecraft.client.MinecraftClient; import net.minecraft.entity.Entity; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.entity.mob.*; import net.minecraft.entity.passive.BatEntity; -import net.minecraft.entity.passive.WolfEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.predicate.entity.EntityPredicates; import net.minecraft.util.Formatting; @@ -34,61 +37,107 @@ public class MobGlow { */ private static final String NUKEKUBI_HEAD_TEXTURE = "eyJ0aW1lc3RhbXAiOjE1MzQ5NjM0MzU5NjIsInByb2ZpbGVJZCI6ImQzNGFhMmI4MzFkYTRkMjY5NjU1ZTMzYzE0M2YwOTZjIiwicHJvZmlsZU5hbWUiOiJFbmRlckRyYWdvbiIsInNpZ25hdHVyZVJlcXVpcmVkIjp0cnVlLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWIwNzU5NGUyZGYyNzM5MjFhNzdjMTAxZDBiZmRmYTExMTVhYmVkNWI5YjIwMjllYjQ5NmNlYmE5YmRiYjRiMyJ9fX0="; private static final String FEL_HEAD_TEXTURE = "ewogICJ0aW1lc3RhbXAiIDogMTcyMDAyNTQ4Njg2MywKICAicHJvZmlsZUlkIiA6ICIzZDIxZTYyMTk2NzQ0Y2QwYjM3NjNkNTU3MWNlNGJlZSIsCiAgInByb2ZpbGVOYW1lIiA6ICJTcl83MUJsYWNrYmlyZCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jMjg2ZGFjYjBmMjE0NGQ3YTQxODdiZTM2YmJhYmU4YTk4ODI4ZjdjNzlkZmY1Y2UwMTM2OGI2MzAwMTU1NjYzIiwKICAgICAgIm1ldGFkYXRhIiA6IHsKICAgICAgICAibW9kZWwiIDogInNsaW0iCiAgICAgIH0KICAgIH0KICB9Cn0="; + /** + * Cache for mob glow. Absence means the entity does not have custom glow. + * If an entity is in the cache, it must have custom glow. + */ + private static final Object2IntMap<Entity> CACHE = new Object2IntOpenHashMap<>(); + + @Init + public static void init() { + // Clear the cache every tick + ClientTickEvents.END_WORLD_TICK.register(client -> clearCache()); + } + + public static boolean atLeastOneMobHasCustomGlow() { + return !CACHE.isEmpty(); + } + + public static boolean hasOrComputeMobGlow(Entity entity) { + if (CACHE.containsKey(entity)) { + return true; + } + int color = computeMobGlow(entity); + if (color != 0) { + CACHE.put(entity, color); + return true; + } + return false; + } + + public static int getMobGlow(Entity entity) { + return CACHE.getInt(entity); + } + + public static int getMobGlowOrDefault(Entity entity, int defaultColor) { + return CACHE.getOrDefault(entity, defaultColor); + } - public static boolean shouldMobGlow(Entity entity) { - return computeShouldMobGlow(entity); + public static void clearCache() { + CACHE.clear(); } - private static boolean computeShouldMobGlow(Entity entity) { + /** + * Computes the glow color for the given entity. + * <p>Only non-zero colors are valid. + */ + private static int computeMobGlow(Entity entity) { + String name = entity.getName().getString(); + // Dungeons if (Utils.isInDungeons()) { - String name = entity.getName().getString(); - return switch (entity) { // Minibosses - case PlayerEntity p when (name.equals("Lost Adventurer") || name.equals("Shadow Assassin") || name.equals("Diamond Guy")) && !DungeonManager.getBoss().isFloor(4) -> SkyblockerConfigManager.get().dungeons.starredMobGlow; - case PlayerEntity p when entity.getId() == LividColor.getCorrectLividId() -> LividColor.shouldGlow(name); + case PlayerEntity p when SkyblockerConfigManager.get().dungeons.starredMobGlow && !DungeonManager.getBoss().isFloor(4) && name.equals("Lost Adventurer") -> 0xfee15c; + case PlayerEntity p when SkyblockerConfigManager.get().dungeons.starredMobGlow && !DungeonManager.getBoss().isFloor(4) && name.equals("Shadow Assassin") -> 0x5b2cb2; + case PlayerEntity p when SkyblockerConfigManager.get().dungeons.starredMobGlow && !DungeonManager.getBoss().isFloor(4) && name.equals("Diamond Guy") -> 0x57c2f7; + case PlayerEntity p when entity.getId() == LividColor.getCorrectLividId() && LividColor.shouldGlow(name) -> LividColor.getGlowColor(name); // Bats - case BatEntity b -> SkyblockerConfigManager.get().dungeons.starredMobGlow || SkyblockerConfigManager.get().dungeons.starredMobBoundingBoxes; + case BatEntity b when SkyblockerConfigManager.get().dungeons.starredMobGlow -> 0xf57738; - //Fel Heads - case ArmorStandEntity as when SkyblockerConfigManager.get().dungeons.starredMobGlow && as.isMarker() && as.hasStackEquipped(EquipmentSlot.HEAD) -> ItemUtils.getHeadTexture(as.getEquippedStack(EquipmentSlot.HEAD)).equals(FEL_HEAD_TEXTURE); + // Fel Heads + case ArmorStandEntity as when SkyblockerConfigManager.get().dungeons.starredMobGlow && as.isMarker() && as.hasStackEquipped(EquipmentSlot.HEAD) && ItemUtils.getHeadTexture(as.getEquippedStack(EquipmentSlot.HEAD)).equals(FEL_HEAD_TEXTURE) -> 0xcc00fa; // Enderman eye color // Armor Stands - case ArmorStandEntity _armorStand -> false; + case ArmorStandEntity _armorStand -> 0; // Regular Mobs - default -> SkyblockerConfigManager.get().dungeons.starredMobGlow && isStarred(entity); + case Entity e when SkyblockerConfigManager.get().dungeons.starredMobGlow && isStarred(entity) -> 0xf57738; + default -> 0; }; } // Slayer if (SlayerManager.shouldGlow(entity, SlayersConfig.HighlightSlayerEntities.GLOW)) { - return true; + return switch (entity) { + case ArmorStandEntity e when SlayerManager.isInSlayerType(SlayerType.DEMONLORD) -> AttunementColors.getColor(e); + case BlazeEntity e when SlayerManager.isInSlayerType(SlayerType.DEMONLORD) -> AttunementColors.getColor(e); + default -> 0xf57738; + }; } return switch (entity) { // Rift Blobbercyst - case PlayerEntity p when Utils.isInTheRift() && p.getName().getString().equals("Blobbercyst ") -> SkyblockerConfigManager.get().otherLocations.rift.blobbercystGlow; + case PlayerEntity p when SkyblockerConfigManager.get().otherLocations.rift.blobbercystGlow && Utils.isInTheRift() && name.equals("Blobbercyst ") -> Formatting.GREEN.getColorValue(); // Dojo Helpers - case ZombieEntity zombie when Utils.isInCrimson() && DojoManager.inArena -> DojoManager.shouldGlow(getArmorStandName(zombie)); + case ZombieEntity zombie when Utils.isInCrimson() && DojoManager.inArena && DojoManager.shouldGlow(getArmorStandName(zombie)) -> DojoManager.getColor(); //Kuudra - case MagmaCubeEntity magmaCube when Utils.isInKuudra() -> SkyblockerConfigManager.get().crimsonIsle.kuudra.kuudraGlow && magmaCube.getSize() == Kuudra.KUUDRA_MAGMA_CUBE_SIZE; + case MagmaCubeEntity magmaCube when SkyblockerConfigManager.get().crimsonIsle.kuudra.kuudraGlow && Utils.isInKuudra() && magmaCube.getSize() == Kuudra.KUUDRA_MAGMA_CUBE_SIZE -> 0xf7510f; // Special Zealot && Slayer (Mini)Boss - case EndermanEntity enderman when Utils.isInTheEnd() -> TheEnd.isSpecialZealot(enderman); + case EndermanEntity enderman when Utils.isInTheEnd() && TheEnd.isSpecialZealot(enderman) -> Formatting.RED.getColorValue(); // Enderman Slayer's Nukekubi Skulls - case ArmorStandEntity armorStand when Utils.isInTheEnd() && armorStand.isMarker() && SlayerManager.isInSlayer() && isNukekubiHead(armorStand) -> SkyblockerConfigManager.get().slayers.endermanSlayer.highlightNukekubiHeads; + case ArmorStandEntity armorStand when SkyblockerConfigManager.get().slayers.endermanSlayer.highlightNukekubiHeads && Utils.isInTheEnd() && armorStand.isMarker() && SlayerManager.isInSlayer() && isNukekubiHead(armorStand) -> 0x990099; // Blaze Slayer's Demonic minions - 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; + case WitherSkeletonEntity e when SkyblockerConfigManager.get().slayers.highlightBosses == SlayersConfig.HighlightSlayerEntities.GLOW && SlayerManager.isInSlayerType(SlayerType.DEMONLORD) && e.distanceTo(MinecraftClient.getInstance().player) <= 15 -> AttunementColors.getColor(e); + case ZombifiedPiglinEntity e when SkyblockerConfigManager.get().slayers.highlightBosses == SlayersConfig.HighlightSlayerEntities.GLOW && SlayerManager.isInSlayerType(SlayerType.DEMONLORD) && e.distanceTo(MinecraftClient.getInstance().player) <= 15 -> AttunementColors.getColor(e); - default -> false; + default -> 0; }; } @@ -125,33 +174,6 @@ public static List<ArmorStandEntity> getArmorStands(World world, Box box) { return world.getEntitiesByClass(ArmorStandEntity.class, box.expand(0, 2, 0), EntityPredicates.NOT_MOUNTED); } - public static int getGlowColor(Entity entity) { - String name = entity.getName().getString(); - - //TODO maybe make this more like the compute method where dungeons stuff is separate - return switch (entity) { - case PlayerEntity p when name.equals("Lost Adventurer") -> 0xfee15c; - case PlayerEntity p when name.equals("Shadow Assassin") -> 0x5b2cb2; - case PlayerEntity p when name.equals("Diamond Guy") -> 0x57c2f7; - case PlayerEntity p when entity.getId() == LividColor.getCorrectLividId() -> LividColor.getGlowColor(name); - case PlayerEntity p when name.equals("Blobbercyst ") -> Formatting.GREEN.getColorValue(); - case ArmorStandEntity as when Utils.isInDungeons() && ItemUtils.getHeadTexture(as.getEquippedStack(EquipmentSlot.HEAD)).equals(FEL_HEAD_TEXTURE)-> 0xcc00fa; //Enderman eye color - - case EndermanEntity enderman when TheEnd.isSpecialZealot(enderman) -> Formatting.RED.getColorValue(); - case ArmorStandEntity armorStand when armorStand.isMarker() && isNukekubiHead(armorStand) -> 0x990099; - case ZombieEntity zombie when Utils.isInCrimson() && DojoManager.inArena -> DojoManager.getColor(); - case MagmaCubeEntity magmaCube when Utils.isInKuudra() -> 0xf7510f; - - // Blaze Slayer Attunement Colours - 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; - }; - } - /** * Compares the armor items of an armor stand to the Nukekubi head texture to determine if it is a Nukekubi head. */ diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index d2ce9ad7e8..f94d3b37df 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -64,9 +64,6 @@ "loom:injected_interfaces": { "net/minecraft/class_1799": [ "de/hysky/skyblocker/injected/SkyblockerStack" - ], - "net/minecraft/class_761": [ - "de/hysky/skyblocker/injected/CustomGlowInfo" ] } } From 4fd198f0b00e9606d6181bc60718b2aeb2458bc8 Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Wed, 25 Dec 2024 00:07:09 +0300 Subject: [PATCH 10/12] Fix the powder widget to also display the diff (#1103) --- .../skyblock/tabhud/widget/PowderWidget.java | 98 +++++++++++++++++-- 1 file changed, 89 insertions(+), 9 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PowderWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PowderWidget.java index 0fb17a6ab6..b2c4c9662e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PowderWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PowderWidget.java @@ -1,23 +1,50 @@ package de.hysky.skyblocker.skyblock.tabhud.widget; - import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent; -import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import net.minecraft.util.Util; +import org.apache.commons.lang3.math.NumberUtils; +import java.text.DecimalFormat; +import java.text.NumberFormat; import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; // this widget shows how much mithril and gemstone powder you have // (dwarven mines and crystal hollows) @RegisterWidget public class PowderWidget extends TabHudWidget { + private static final MutableText TITLE = Text.literal("Powders").formatted(Formatting.DARK_AQUA, Formatting.BOLD); + private static final DecimalFormat DECIMAL_FORMAT = (DecimalFormat) NumberFormat.getInstance(Locale.ENGLISH); + private static final short UPDATE_INTERVAL = 2000; + + static { + DECIMAL_FORMAT.setPositivePrefix("+"); // Causes the positive sign to be displayed for positive numbers, while the negative sign is always displayed for negative numbers. This removes the need to prepend a + if positive. + } - private static final MutableText TITLE = Text.literal("Powders").formatted(Formatting.DARK_AQUA, - Formatting.BOLD); + // Patterns to match the playerlist lines against + private static final Pattern MITHRIL_PATTERN = Pattern.compile("Mithril: ([\\d,]+)"); + private static final Pattern GEMSTONE_PATTERN = Pattern.compile("Gemstone: ([\\d,]+)"); + private static final Pattern GLACITE_PATTERN = Pattern.compile("Glacite: ([\\d,]+)"); + // Amounts from last update + private int lastMithril = 0; + private int lastGemstone = 0; + private int lastGlacite = 0; + // The amount difference between the 2nd last and last update + private int lastMithrilDiff = 0; + private int lastGemstoneDiff = 0; + private int lastGlaciteDiff = 0; + // A bitfield to keep track of which powders have been updated. + // First 3 bits represent mithril, gemstone and glacite respectively, with 1 being found and 0 being not found. + // The 4th bit is for whether the current tick caused an update, which will change the value of lastUpdate when 1. + private byte updated = 0b0000; + private long lastUpdate = 0; public PowderWidget() { super("Powders", TITLE, Formatting.DARK_AQUA.getColorValue()); @@ -25,15 +52,68 @@ public PowderWidget() { @Override public void updateContent(List<Text> lines) { + Matcher matcher = Pattern.compile("").matcher(""); // Placeholder pattern and input to construct a matcher that can be reused + long msAfterLastUpdate = Util.getMeasuringTimeMs() - lastUpdate; + for (Text line : lines) { - switch (line.getString().toLowerCase()) { - case String s when s.contains("mithril") -> this.addComponent(new IcoTextComponent(Ico.MITHRIL, line)); - case String s when s.contains("gemstone") -> this.addComponent(new IcoTextComponent(Ico.AMETHYST_SHARD, line)); - case String s when s.contains("glacite") -> this.addComponent(new IcoTextComponent(Ico.BLUE_ICE, line)); - default -> this.addComponent(new PlainTextComponent(line)); + switch (matcher.reset(line.getString())) { + case Matcher m when m.usePattern(MITHRIL_PATTERN).matches() -> { + int mithril = parseAmount(m); + // Generally this will only work if the update interval has passed, but we also don't want to stall the update if the amount has changed + if (mithril != lastMithril || msAfterLastUpdate > UPDATE_INTERVAL) { + lastMithrilDiff = mithril - lastMithril; + updated |= 0b1000; + addComponent(new IcoTextComponent(Ico.MITHRIL, getTextToDisplay(lastMithrilDiff, line, Formatting.DARK_GREEN))); + lastMithril = mithril; + } else { + addComponent(new IcoTextComponent(Ico.MITHRIL, getTextToDisplay(lastMithrilDiff, line, Formatting.DARK_GREEN))); + } + updated |= 0b001; + } + case Matcher m when m.usePattern(GEMSTONE_PATTERN).matches() -> { + int gemstone = parseAmount(m); + // Generally this will only work if the update interval has passed, but we also don't want to stall the update if the amount has changed + if (gemstone != lastGemstone || msAfterLastUpdate > UPDATE_INTERVAL) { + lastGemstoneDiff = gemstone - lastGemstone; + updated |= 0b1000; + addComponent(new IcoTextComponent(Ico.AMETHYST_SHARD, getTextToDisplay(lastGemstoneDiff, line, Formatting.LIGHT_PURPLE))); + lastGemstone = gemstone; + } else { + addComponent(new IcoTextComponent(Ico.AMETHYST_SHARD, getTextToDisplay(lastGemstoneDiff, line, Formatting.LIGHT_PURPLE))); + } + updated |= 0b010; + } + case Matcher m when m.usePattern(GLACITE_PATTERN).matches() -> { + int glacite = parseAmount(m); + // Generally this will only work if the update interval has passed, but we also don't want to stall the update if the amount has changed + if (glacite != lastGlacite || msAfterLastUpdate > UPDATE_INTERVAL) { + lastGlaciteDiff = glacite - lastGlacite; + updated |= 0b1000; + addComponent(new IcoTextComponent(Ico.BLUE_ICE, getTextToDisplay(lastGlaciteDiff, line, Formatting.AQUA))); + lastGlacite = glacite; + } else { + addComponent(new IcoTextComponent(Ico.BLUE_ICE, getTextToDisplay(lastGlaciteDiff, line, Formatting.AQUA))); + } + updated |= 0b100; + } + default -> {} } + if ((updated & 0b111) == 0b111) break; // All powder counts have been updated, no need to continue } + if ((updated & 0b1000) == 0b1000) lastUpdate = Util.getMeasuringTimeMs(); + updated = 0b0000; // Reset the bitfield for the next tick + } + + private int parseAmount(Matcher matcher) { + return NumberUtils.toInt(matcher.group(1).replace(",", "")); + } + private MutableText getAppendix(int diff, Formatting formatting) { + return Text.literal(" (" + DECIMAL_FORMAT.format(diff) + ")").formatted(formatting); } + // Decides whether the appendix should be appended to the line + private Text getTextToDisplay(int diff, Text line, Formatting formatting) { + return diff != 0 ? line.copy().append(getAppendix(diff, formatting)) : line; + } } From 2bf0f7f2ff312ae02d57a68c40989a59e55b622c Mon Sep 17 00:00:00 2001 From: viciscat <51047087+viciscat@users.noreply.github.com> Date: Tue, 24 Dec 2024 22:07:22 +0100 Subject: [PATCH 11/12] tab hud related fixes (#1104) --- .../skyblock/itemlist/ItemRepository.java | 3 +- .../utils/render/gui/DropdownWidget.java | 73 +++++++++++-------- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRepository.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRepository.java index 8c071f5979..b5b0cbc476 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRepository.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRepository.java @@ -117,11 +117,12 @@ public static List<ItemStack> getItems() { } public static Stream<ItemStack> getItemsStream() { + if (!filesImported) return Stream.empty(); return items.stream(); } /** - * @param neuId the NEU item id gotten through {@link NEUItem#getSkyblockItemId()}, {@link ItemStack#getNeuName()}, or {@link ItemUtils#getNeuId(String, String) ItemTooltip#getNeuName(String, String)} + * @param neuId the NEU item id gotten through {@link NEUItem#getSkyblockItemId()}, {@link ItemStack#getNeuName()}, or {@link ItemUtils#getNeuId(ItemStack) ItemTooltip#getNeuName(String, String)} */ @Nullable public static ItemStack getItemStack(String neuId) { diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/DropdownWidget.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/DropdownWidget.java index a4bfb7c353..f92ec0a5e9 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/gui/DropdownWidget.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/DropdownWidget.java @@ -15,29 +15,32 @@ import java.util.function.Consumer; public class DropdownWidget<T> extends ContainerWidget { - private static final MinecraftClient client = MinecraftClient.getInstance(); - public static final int ENTRY_HEIGHT = 15; - protected final List<T> entries; - protected final Consumer<T> selectCallback; + private static final MinecraftClient client = MinecraftClient.getInstance(); + public static final int ENTRY_HEIGHT = 15; + private static final int HEADER_HEIGHT = ENTRY_HEIGHT + 4; + protected final List<T> entries; + protected final Consumer<T> selectCallback; private final DropdownList dropdownList; protected T prevSelected; - protected T selected; - protected boolean open; - - public DropdownWidget(MinecraftClient minecraftClient, int x, int y, int width, int maxHeight, List<T> entries, Consumer<T> selectCallback, T selected) { - super(x, y, width, Math.min((entries.size() + 1) * ENTRY_HEIGHT + 8, maxHeight), Text.empty()); - this.entries = entries; - this.selectCallback = selectCallback; - this.selected = selected; - dropdownList = new DropdownList(minecraftClient, x + 1, y + ENTRY_HEIGHT + 4, width - 2, maxHeight - ENTRY_HEIGHT - 4); + protected T selected; + protected boolean open; + private int maxHeight; + + public DropdownWidget(MinecraftClient minecraftClient, int x, int y, int width, int maxHeight, List<T> entries, Consumer<T> selectCallback, T selected) { + super(x, y, width, HEADER_HEIGHT, Text.empty()); + this.maxHeight = maxHeight; + this.entries = entries; + this.selectCallback = selectCallback; + this.selected = selected; + dropdownList = new DropdownList(minecraftClient, x + 1, y + HEADER_HEIGHT, width - 2, maxHeight - HEADER_HEIGHT); for (T element : entries) { dropdownList.addEntry(new Entry(element)); } } public void setMaxHeight(int maxHeight) { - setHeight(maxHeight); - dropdownList.setHeight(maxHeight - ENTRY_HEIGHT - 4); + this.maxHeight = maxHeight; + setOpen(open); } @Override @@ -52,14 +55,14 @@ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float d matrices.translate(0, 0, 100); dropdownList.visible = open; dropdownList.render(context, mouseX, mouseY, delta); - context.fill(getX(), getY(), getRight(), getY() + ENTRY_HEIGHT + 5, 0xFF << 24); - context.drawBorder(getX(), getY(), getWidth(), ENTRY_HEIGHT + 5, -1); + context.fill(getX(), getY(), getRight(), getY() + HEADER_HEIGHT + 1, 0xFF << 24); + context.drawBorder(getX(), getY(), getWidth(), HEADER_HEIGHT + 1, -1); drawScrollableText(context, client.textRenderer, Text.literal( - selected.toString()), + selected.toString()), getX() + 2, getY() + 2, getRight() - 2, - getY() + ENTRY_HEIGHT + 2, + getY() + HEADER_HEIGHT - 2, -1); matrices.pop(); } @@ -67,9 +70,19 @@ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float d @Override protected void appendClickableNarrations(NarrationMessageBuilder builder) {} + private void setOpen(boolean open) { + this.open = open; + if (this.open) { + setHeight(maxHeight); + dropdownList.setHeight(Math.min(entries.size() * ENTRY_HEIGHT + 4, maxHeight - HEADER_HEIGHT)); + } else { + setHeight(HEADER_HEIGHT); + } + } + protected void select(T entry) { selected = entry; - open = false; + setOpen(false); if (selected != prevSelected) { selectCallback.accept(entry); prevSelected = selected; @@ -85,7 +98,7 @@ public void setX(int x) { @Override public void setY(int y) { super.setY(y); - dropdownList.setY(getY() + ENTRY_HEIGHT + 4); + dropdownList.setY(getY() + HEADER_HEIGHT); } @Override @@ -97,18 +110,18 @@ public void setWidth(int width) { @Override public void setHeight(int height) { super.setHeight(height); - dropdownList.setHeight(height - ENTRY_HEIGHT - 4); + dropdownList.setHeight(height - HEADER_HEIGHT); } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (!visible) return false; - if (getX() <= mouseX && mouseX < getX() + getWidth() && getY() <= mouseY && mouseY < getY() + ENTRY_HEIGHT + 4) { - open = !open; - playDownSound(client.getSoundManager()); - return true; - } - return super.mouseClicked(mouseX, mouseY, button); + if (!visible) return false; + if (getX() <= mouseX && mouseX < getX() + getWidth() && getY() <= mouseY && mouseY < getY() + HEADER_HEIGHT) { + setOpen(!open); + playDownSound(client.getSoundManager()); + return true; + } + return super.mouseClicked(mouseX, mouseY, button); } @Override @@ -213,7 +226,7 @@ protected void enableScissor(DrawContext context) { context.enableScissor(this.getX(), this.getY() + 1, this.getRight(), this.getBottom() - 1); } } - + private class Entry extends ElementListWidget.Entry<Entry> { private final T entry; From 7ce731f859febc0786be29b95fd0edc64707f9c9 Mon Sep 17 00:00:00 2001 From: Hazem <79111320+7azeemm@users.noreply.github.com> Date: Tue, 24 Dec 2024 22:07:32 +0100 Subject: [PATCH 12/12] Slayer Widget preview for Tab HUD (#1105) --- .../skyblock/slayers/hud/SlayerHudWidget.java | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 index 6338329ff4..9fcbfdeba8 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/slayers/hud/SlayerHudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/hud/SlayerHudWidget.java @@ -5,6 +5,7 @@ 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.config.WidgetsConfigurationScreen; 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; @@ -55,6 +56,16 @@ public boolean shouldRender(Location location) { @Override public void updateContent() { + if (MinecraftClient.getInstance().currentScreen instanceof WidgetsConfigurationScreen) { + SlayerType type = SlayerType.REVENANT; + SlayerTier tier = SlayerTier.V; + + addSimpleIcoText(type.icon, "", tier.color, type.bossName + " " + tier); + addSimpleIcoText(Ico.EXPERIENCE_BOTTLE, "XP: ", Formatting.LIGHT_PURPLE, "100,000/400,000"); + addComponent(new IcoTextComponent(Ico.NETHER_STAR, Text.translatable("skyblocker.slayer.hud.levelUpIn", Text.literal("200").formatted(Formatting.LIGHT_PURPLE)))); + return; + } + if (client.player == null || SlayerManager.getSlayerQuest() == null) return; SlayerType type = SlayerManager.getSlayerType();